From 232042b1e30ae6ef6a1fb931e925d41464e0d784 Mon Sep 17 00:00:00 2001 From: Simon Ding Date: Tue, 9 Jul 2024 15:36:29 +0800 Subject: [PATCH] add indexer setting --- db/db.go | 20 ++- server/resources.go | 13 +- server/server.go | 1 + ui/lib/APIs.dart | 3 + ui/lib/discover.dart | 0 ui/lib/providers/welcome_data.dart | 37 +++++ ui/lib/system_settings.dart | 214 +++++++++++++++++++++++------ 7 files changed, 239 insertions(+), 49 deletions(-) create mode 100644 ui/lib/discover.dart diff --git a/db/db.go b/db/db.go index 1139e2d..422f130 100644 --- a/db/db.go +++ b/db/db.go @@ -127,22 +127,29 @@ func (c *Client) SaveTorznabInfo(name string, setting TorznabSetting) error { if err != nil { return errors.Wrap(err, "marshal json") } - _, err = c.ent.Indexers.Create(). - SetName(name).SetImplementation(IndexerTorznabImpl).SetPriority(1).SetSettings(string(data)).Save(context.TODO()) - if err != nil { - return errors.Wrap(err, "save db") + if _, err = c.ent.Indexers.Update().Where(indexers.Name(name)).SetSettings(string(data)).Save(context.TODO()); err != nil { + _, err = c.ent.Indexers.Create(). + SetName(name).SetImplementation(IndexerTorznabImpl).SetPriority(1).SetSettings(string(data)).Save(context.TODO()) + if err != nil { + return errors.Wrap(err, "save db") + } } return nil } +func (c *Client) DeleteTorznab(id int) { + c.ent.Indexers.Delete().Where(indexers.ID(id)).Exec(context.TODO()) +} + type TorznabInfo struct { + ID int `json:"id"` Name string `json:"name"` TorznabSetting } func (c *Client) GetAllTorznabInfo() []*TorznabInfo { res := c.ent.Indexers.Query().Where(indexers.Implementation(IndexerTorznabImpl)).AllX(context.TODO()) - + var l = make([]*TorznabInfo, 0, len(res)) for _, r := range res { var ss TorznabSetting @@ -152,7 +159,8 @@ func (c *Client) GetAllTorznabInfo() []*TorznabInfo { continue } l = append(l, &TorznabInfo{ - Name: r.Name, + ID: r.ID, + Name: r.Name, TorznabSetting: ss, }) } diff --git a/server/resources.go b/server/resources.go index 3d52efa..2b98cf5 100644 --- a/server/resources.go +++ b/server/resources.go @@ -6,6 +6,7 @@ import ( "polaris/log" "polaris/pkg/torznab" "polaris/pkg/transmission" + "strconv" "github.com/gin-gonic/gin" "github.com/pkg/errors" @@ -48,6 +49,16 @@ func (s *Server) AddTorznabInfo(c *gin.Context) (interface{}, error) { return nil, nil } +func (s *Server) DeleteTorznabInfo(c *gin.Context) (interface{}, error) { + var ids = c.Param("id") + id, err := strconv.Atoi(ids) + if err != nil { + return nil, fmt.Errorf("id is not correct: %v", ids) + } + s.db.DeleteTorznab(id) + return "success", nil +} + func (s *Server) GetAllIndexers(c *gin.Context) (interface{}, error) { indexers := s.db.GetAllTorznabInfo() if len(indexers) == 0 { @@ -124,4 +135,4 @@ func (s *Server) GetAllDonloadClients(c *gin.Context) (interface{}, error) { return nil, fmt.Errorf("no download client") } return res, nil -} \ No newline at end of file +} diff --git a/server/server.go b/server/server.go index ab4e487..c9998c5 100644 --- a/server/server.go +++ b/server/server.go @@ -67,6 +67,7 @@ func (s *Server) Serve() error { indexer.GET("/", HttpHandler(s.GetAllIndexers)) indexer.POST("/add", HttpHandler(s.AddTorznabInfo)) indexer.POST("/download", HttpHandler(s.SearchAndDownload)) + indexer.DELETE("/del/:id", HttpHandler(s.DeleteTorznabInfo)) } downloader := api.Group("/downloader") diff --git a/ui/lib/APIs.dart b/ui/lib/APIs.dart index 998a4d4..d79eae7 100644 --- a/ui/lib/APIs.dart +++ b/ui/lib/APIs.dart @@ -7,6 +7,9 @@ class APIs { static final watchlistUrl = "$_baseUrl/api/v1/tv/watchlist"; static final seriesDetailUrl = "$_baseUrl/api/v1/tv/series/"; static final searchAndDownloadUrl = "$_baseUrl/api/v1/indexer/download"; + static final allIndexersUrl = "$_baseUrl/api/v1/indexer/"; + static final addIndexerUrl = "$_baseUrl/api/v1/indexer/add"; + static final delIndexerUrl = "$_baseUrl/api/v1/indexer/del/"; static const tmdbImgBaseUrl = "https://image.tmdb.org/t/p/w500/"; diff --git a/ui/lib/discover.dart b/ui/lib/discover.dart new file mode 100644 index 0000000..e69de29 diff --git a/ui/lib/providers/welcome_data.dart b/ui/lib/providers/welcome_data.dart index d89d53e..8e141d9 100644 --- a/ui/lib/providers/welcome_data.dart +++ b/ui/lib/providers/welcome_data.dart @@ -56,3 +56,40 @@ var tmdbApiSettingProvider = FutureProvider( }, ); +var indexersProvider = FutureProvider((ref) async { + final dio = Dio(); + var resp = await dio.get(APIs.allIndexersUrl); + var sp = ServerResponse.fromJson(resp.data); + if (sp.code != 0) { + throw sp.message; + } + List indexers = List.empty(growable: true); + for (final item in sp.data as List) { + indexers.add(Indexer.fromJson(item)); + } + print("indexers: ${indexers[0].name}"); + return indexers; +}); + +class Indexer { + String? name; + String? url; + String? apiKey; + int? id; + + Indexer({this.name, this.url, this.apiKey}); + + Indexer.fromJson(Map json) { + name = json['name']; + url = json['url']; + apiKey = json['api_key']; + id = json["id"]; + } + Map toJson() { + final Map data = new Map(); + data['name'] = this.name; + data['url'] = this.url; + data['api_key'] = this.apiKey; + return data; + } +} diff --git a/ui/lib/system_settings.dart b/ui/lib/system_settings.dart index 802d87d..1ccad9a 100644 --- a/ui/lib/system_settings.dart +++ b/ui/lib/system_settings.dart @@ -30,54 +30,109 @@ class _SystemSettingsPageState extends ConsumerState { Widget build(BuildContext context) { var key = ref.watch(tmdbApiSettingProvider); - return key.when( - data: (data ) => Container( - padding: const EdgeInsets.fromLTRB(40, 10, 40, 0), - child: Form( - key: _formKey, //设置globalKey,用于后面获取FormState - autovalidateMode: AutovalidateMode.onUserInteraction, - child: Column( - children: [ - TextFormField( - autofocus: true, - initialValue: data, - decoration: const InputDecoration( - labelText: "TMDB Api Key", - icon: Icon(Icons.key), - ), - // - validator: (v) { - return v!.trim().isNotEmpty ? null : "ApiKey 不能为空"; - }, - onSaved: (newValue) { - _submitSettings(context, newValue!); - }, - ), - Center( - child: Padding( - padding: const EdgeInsets.only(top: 28.0), - child: ElevatedButton( - child: const Padding( - padding: EdgeInsets.all(16.0), - child: Text("保存"), + var tmdbSetting = key.when( + data: (data) => Container( + padding: const EdgeInsets.fromLTRB(40, 10, 40, 0), + child: Form( + key: _formKey, //设置globalKey,用于后面获取FormState + autovalidateMode: AutovalidateMode.onUserInteraction, + child: Column( + children: [ + TextFormField( + autofocus: true, + initialValue: data, + decoration: const InputDecoration( + labelText: "TMDB Api Key", + icon: Icon(Icons.key), ), - onPressed: () { - // 通过_formKey.currentState 获取FormState后, - // 调用validate()方法校验用户名密码是否合法,校验 - // 通过后再提交数据。 - if ((_formKey.currentState as FormState).validate()) { - (_formKey.currentState as FormState).save(); - } + // + validator: (v) { + return v!.trim().isNotEmpty ? null : "ApiKey 不能为空"; + }, + onSaved: (newValue) { + _submitSettings(context, newValue!); }, ), - ), - ) - ], + Center( + child: Padding( + padding: const EdgeInsets.only(top: 28.0), + child: ElevatedButton( + child: const Padding( + padding: EdgeInsets.all(16.0), + child: Text("保存"), + ), + onPressed: () { + // 通过_formKey.currentState 获取FormState后, + // 调用validate()方法校验用户名密码是否合法,校验 + // 通过后再提交数据。 + if ((_formKey.currentState as FormState) + .validate()) { + (_formKey.currentState as FormState).save(); + } + }, + ), + ), + ) + ], + ), + ), ), - ), - ), error: (err, trace) => Text("$err"), loading: () => const CircularProgressIndicator()); + + var indexers = ref.watch(indexersProvider); + var indexerSetting = indexers.when( + data: (value) => GridView.builder( + itemCount: value.length + 1, + scrollDirection: Axis.vertical, + shrinkWrap: true, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 6), + itemBuilder: (context, i) { + if (i < value.length) { + var indexer = value[i]; + return Card( + margin: const EdgeInsets.all(4), + clipBehavior: Clip.hardEdge, + child: InkWell( + //splashColor: Colors.blue.withAlpha(30), + onTap: () { + showIndexerDetails(context, indexer); + }, + child: Text(indexer.name!))); + } + return Card( + margin: const EdgeInsets.all(4), + clipBehavior: Clip.hardEdge, + child: InkWell( + //splashColor: Colors.blue.withAlpha(30), + onTap: () { + showIndexerDetails(context, Indexer()); + }, + child: const Center( + child: Icon(Icons.add), + ))); + }), + error: (err, trace) => Text("$err"), + loading: () => const CircularProgressIndicator()); + return ListView( + children: [ + ExpansionTile( + tilePadding: const EdgeInsets.fromLTRB(10, 0, 10, 0), + childrenPadding: const EdgeInsets.fromLTRB(50, 0, 50, 0), + initiallyExpanded: true, + title: const Text("TMDB 设置"), + children: [tmdbSetting], + ), + ExpansionTile( + tilePadding: const EdgeInsets.fromLTRB(10, 0, 10, 0), + childrenPadding: const EdgeInsets.fromLTRB(50, 0, 50, 0), + initiallyExpanded: true, + title: const Text("索引器设置"), + children: [indexerSetting], + ), + ], + ); } void _submitSettings(BuildContext context, String v) async { @@ -89,4 +144,79 @@ class _SystemSettingsPageState extends ConsumerState { } } } + + Future showIndexerDetails(BuildContext context, Indexer indexer) { + var nameController = TextEditingController(text: indexer.name); + var urlController = TextEditingController(text: indexer.url); + var apiKeyController = TextEditingController(text: indexer.apiKey); + return showDialog( + context: context, + barrierDismissible: true, // user must tap button! + builder: (BuildContext context) { + return AlertDialog( + title: const Text('索引器'), + content: SingleChildScrollView( + child: ListBody( + children: [ + TextField( + decoration: const InputDecoration(labelText: "名称"), + controller: nameController, + ), + TextField( + decoration: const InputDecoration(labelText: "网址"), + controller: urlController, + ), + TextField( + decoration: const InputDecoration(labelText: "API Key"), + controller: apiKeyController, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => { + deleteIndexer(context, indexer.id!) + }, + child: const Text('删除')), + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('取消')), + TextButton( + child: const Text('确定'), + onPressed: () { + addIndexer(context, nameController.text, urlController.text, + apiKeyController.text); + }, + ), + ], + ); + }); + } + + void addIndexer( + BuildContext context, String name, String url, String apiKey) async { + var dio = Dio(); + var resp = await dio.post(APIs.addIndexerUrl, + data: Indexer(name: name, url: url, apiKey: apiKey).toJson()); + var sp = ServerResponse.fromJson(resp.data); + if (sp.code != 0 && context.mounted) { + Utils.showAlertDialog(context, sp.message); + return; + } + Navigator.of(context).pop(); + ref.refresh(indexersProvider); + } + + void deleteIndexer(BuildContext context, int id) async { + var dio = Dio(); + var resp = await dio.delete("${APIs.delIndexerUrl}$id"); + var sp = ServerResponse.fromJson(resp.data); + if (sp.code != 0 && context.mounted) { + Utils.showAlertDialog(context, sp.message); + return; + } + Navigator.of(context).pop(); + ref.refresh(indexersProvider); + } }