From 5f5e385fda8e3408a63ea41f21f85b203dc5805c Mon Sep 17 00:00:00 2001 From: wumode Date: Fri, 20 Jun 2025 22:49:38 +0800 Subject: [PATCH] =?UTF-8?q?update(ClashRuleProvider):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=A7=84=E5=88=99=E9=9B=86=E5=90=88;=20=E6=B7=BB=E5=8A=A0ACL4S?= =?UTF-8?q?SR=E8=A7=84=E5=88=99=E9=9B=86;=20=E9=85=8D=E7=BD=AE=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.v2.json | 3 +- plugins.v2/clashruleprovider/README.md | 38 + plugins.v2/clashruleprovider/__init__.py | 292 +++- .../clashruleprovider/clash_rule_parser.py | 90 +- ...> __federation_expose_Config-BJvXq0hj.css} | 2 +- ...=> __federation_expose_Config-Btg4HYx3.js} | 285 ++-- ...s => __federation_expose_Page-D_nruYha.js} | 1426 +++++++++++------ ... => __federation_expose_Page-Djrrbsow.css} | 18 +- .../dist/assets/remoteEntry.js | 8 +- 9 files changed, 1426 insertions(+), 736 deletions(-) create mode 100644 plugins.v2/clashruleprovider/README.md rename plugins.v2/clashruleprovider/dist/assets/{__federation_expose_Config-nL3Pv4Qs.css => __federation_expose_Config-BJvXq0hj.css} (54%) rename plugins.v2/clashruleprovider/dist/assets/{__federation_expose_Config-DZF0yyTH.js => __federation_expose_Config-Btg4HYx3.js} (81%) rename plugins.v2/clashruleprovider/dist/assets/{__federation_expose_Page-C2_enJ99.js => __federation_expose_Page-D_nruYha.js} (80%) rename plugins.v2/clashruleprovider/dist/assets/{__federation_expose_Page-_CExF_DI.css => __federation_expose_Page-Djrrbsow.css} (70%) diff --git a/package.v2.json b/package.v2.json index 6610715..befb89f 100644 --- a/package.v2.json +++ b/package.v2.json @@ -449,11 +449,12 @@ "name": "Clash Rule Provider", "description": "随时为Clash添加一些额外的规则。", "labels": "工具", - "version": "1.0.1", + "version": "1.1.0", "icon": "Mihomo_Meta_A.png", "author": "wumode", "level": 1, "history": { + "v1.1.0": "支持规则集合; 添加ACL4SSR规则集; 配置说明", "v1.0.1": "支持规则搜索, 优化细节", "v1.0.0": "支持: 规则分页; 导入规则; 代理组; 附加出站代理; 按区域分组", "v0.1.0": "新增ClashRuleProvider" diff --git a/plugins.v2/clashruleprovider/README.md b/plugins.v2/clashruleprovider/README.md new file mode 100644 index 0000000..0161977 --- /dev/null +++ b/plugins.v2/clashruleprovider/README.md @@ -0,0 +1,38 @@ +# Clash Rule Provider + +**Clash Rule Provider** 生成适用于 [Meta Kernel](https://github.com/MetaCubeX/mihomo/tree/Meta) 定制配置,便于增加、修改和删除规则。 + +- 即时通知 Clash 刷新规则集合 +- 基于 Meta 内核丰富的代理组配置,提供灵活的路由功能 +- 支持按大洲分组节点 +- 支持 [ACL4SSR](https://github.com/ACL4SSR/ACL4SSR) 规则集合 + +## 配置说明 + +### 规则集规则 + +用于添加能够在 Clash 中即时生效的规则,Clash Rule Provider 会根据每条规则的**出站**生成相应的**规则集合** `📂<-` + `出站`。 + +### 置顶规则 + +这是Clash配置文件的`rules`字段中最顶部的规则,相比于其它规则它们拥有更高的优先级。Clash Rule Provider 会自动在此处添加**规则集规则**内的**规则集合**。 + +### 代理组 + +代理组中配置项的说明请参考 [Mihomo docs](https://wiki.metacubex.one/config/proxy-groups/), +这里以两个例子说明如何定制代理组: + +- 访问北邮人的代理组 + +北邮人拒绝国内以及所有IPv4连接,可以添加一个 `type` 为 `url-test` 的代理组,在 `url` 中填写北邮人的地址,打开 `include-all-proxies`,其余配置项保持默认。 + +然后,在**置顶规则**中添加一条规则: `DOMAIN,xxx.pt,PtProxy` 。 + +![](https://images2.imgbox.com/c9/37/FhBGLNQw_o.jpg) + + +- 访问ChatGPT的代理组 + +在**高级选项**中启用按大洲分组节点。选择Asia以外的代理组,设置`url`: `https://chatgpt.com/` , `expected-status`: `200` 。 + +![](https://images2.imgbox.com/e2/37/EoITSfRi_o.jpg) \ No newline at end of file diff --git a/plugins.v2/clashruleprovider/__init__.py b/plugins.v2/clashruleprovider/__init__.py index ada175c..dbcc0a2 100644 --- a/plugins.v2/clashruleprovider/__init__.py +++ b/plugins.v2/clashruleprovider/__init__.py @@ -17,11 +17,11 @@ from app import schemas from app.core.config import settings from app.log import logger from app.plugins import _PluginBase -from app.schemas.types import EventType, NotificationType +from app.schemas.types import NotificationType from app.utils.http import RequestUtils from app.plugins.clashruleprovider.clash_rule_parser import ClashRuleParser from app.plugins.clashruleprovider.clash_rule_parser import Action, RuleType, ClashRule, MatchRule, LogicRule -from app.plugins.clashruleprovider.clash_rule_parser import ProxyGroupValidator +from app.plugins.clashruleprovider.clash_rule_parser import ProxyGroup, RuleProvider class ClashRuleProvider(_PluginBase): @@ -32,7 +32,7 @@ class ClashRuleProvider(_PluginBase): # 插件图标 plugin_icon = "Mihomo_Meta_A.png" # 插件版本 - plugin_version = "1.0.1" + plugin_version = "1.1.0" # 插件作者 plugin_author = "wumode" # 作者主页 @@ -62,19 +62,24 @@ class ClashRuleProvider(_PluginBase): _retry_times = 3 _filter_keywords = [] _auto_update_subscriptions = True - _ruleset_prefix: str = '📂<-' + _ruleset_prefix: str = '📂<=' _group_by_region: bool = False _refresh_delay: int = 5 + _discard_rules: bool = False + _enable_acl4ssr: bool = False # 插件数据 _clash_config: Optional[Dict[str, Any]] = None _top_rules: List[str] = [] _ruleset_rules: List[str] = [] _rule_provider: Dict[str, Any] = {} + _extra_rule_providers: Dict[str, Any] = {} _subscription_info = {} _ruleset_names: Dict[str, str] = {} _proxy_groups = [] _extra_proxies = [] + _acl4ssr_providers: Dict[str, Any] = {} + _acl4ssr_prefix: str = '🗂️=>' # protected variables _clash_rule_parser = None @@ -93,7 +98,9 @@ class ClashRuleProvider(_PluginBase): self._subscription_info = self.get_data("subscription_info") or \ {"download": 0, "upload": 0, "total": 0, "expire": 0, "last_update": 0} self._rule_provider = self.get_data("rule_provider") or {} + self._extra_rule_providers = self.get_data("extra_rule_providers") or {} self._ruleset_names = self.get_data("ruleset_names") or {} + self._acl4ssr_providers = self.get_data("acl4ssr_providers") or {} if config: self._enabled = config.get("enabled") self._proxy = config.get("proxy") @@ -106,12 +113,15 @@ class ClashRuleProvider(_PluginBase): self._movie_pilot_url = self._movie_pilot_url[:-1] self._cron = config.get("cron_string") self._timeout = config.get("timeout") - self._retry_times = config.get("retry_times") + self._retry_times = config.get("retry_times") or 3 self._filter_keywords = config.get("filter_keywords") - self._ruleset_prefix = config.get("ruleset_prefix", "Custom_") + self._ruleset_prefix = config.get("ruleset_prefix", "📂<=") + self._acl4ssr_prefix = config.get("acl4ssr_prefix", "🗂️=>") self._auto_update_subscriptions = config.get("auto_update_subscriptions") self._group_by_region = config.get("group_by_region") self._refresh_delay = config.get("refresh_delay") or 5 + self._discard_rules = config.get("discard_rules") or False + self._enable_acl4ssr = config.get("enable_acl4ssr") or False self._clash_rule_parser = ClashRuleParser() self._ruleset_rule_parser = ClashRuleParser() if self._enabled: @@ -125,7 +135,13 @@ class ClashRuleProvider(_PluginBase): self._scheduler.start() # 更新订阅 self._scheduler.add_job(self.__refresh_subscription, "date", - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=5)) + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=2)) + # 更新acl4ssr + if self._enable_acl4ssr: + self._scheduler.add_job(self.__refresh_acl4ssr, "date", + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=5)) + else: + self._acl4ssr_providers = {} def get_state(self) -> bool: return self._enabled @@ -229,8 +245,32 @@ class ClashRuleProvider(_PluginBase): "endpoint": self.get_rule_providers, "methods": ["GET"], "auth": "bear", - "summary": "update rule providers", - "description": "update rule providers" + "summary": "rule providers", + "description": "rule providers" + }, + { + "path": "/extra-rule-providers", + "endpoint": self.get_extra_rule_providers, + "methods": ["GET"], + "auth": "bear", + "summary": "extra rule providers", + "description": "extra rule providers" + }, + { + "path": "/extra-rule-provider", + "endpoint": self.update_extra_rule_provider, + "methods": ["POST"], + "auth": "bear", + "summary": "update an extra rule provider", + "description": "update an rule provider" + }, + { + "path": "/extra-rule-provider", + "endpoint": self.delete_extra_rule_provider, + "methods": ["DELETE"], + "auth": "bear", + "summary": "add an extra rule provider", + "description": "add an rule provider" }, { "path": "/extra-proxies", @@ -245,8 +285,8 @@ class ClashRuleProvider(_PluginBase): "endpoint": self.delete_extra_proxy, "methods": ["DELETE"], "auth": "bear", - "summary": "delete a extra proxy", - "description": "delete a extra proxy" + "summary": "delete an extra proxy", + "description": "delete an extra proxy" }, { "path": "/extra-proxies", @@ -333,31 +373,15 @@ class ClashRuleProvider(_PluginBase): "id": "ClashRuleProvider", "name": "定时更新订阅", "trigger": CronTrigger.from_crontab(self._cron), - "func": self.update_subscription_service, + "func": self.refresh_subscription_service, "kwargs": {} }] return [] - def __update_config(self): - # 保存配置 - self.update_config( - { - "enabled": self._enabled, - "cron": self._cron, - "proxy": self._proxy, - "notify": self._notify, - "sub_links": self._sub_links, - "clash_dashboard_url": self._clash_dashboard_url, - "clash_dashboard_secret": self._clash_dashboard_secret, - "movie_pilot_url": self._movie_pilot_url, - "retry_times": self._retry_times, - "timeout": self._timeout, - }) - def __save_data(self): self.__insert_ruleset() - self._top_rules = self._clash_rule_parser.to_string() - self._ruleset_rules = self._ruleset_rule_parser.to_string() + self._top_rules = self._clash_rule_parser.to_list() + self._ruleset_rules = self._ruleset_rule_parser.to_list() self.save_data('clash_config', self._clash_config) self.save_data('ruleset_rules', self._ruleset_rules) self.save_data('top_rules', self._top_rules) @@ -366,6 +390,8 @@ class ClashRuleProvider(_PluginBase): self.save_data('rule_provider', self._rule_provider) self.save_data('proxy_groups', self._proxy_groups) self.save_data('extra_proxies', self._extra_proxies) + self.save_data('extra_rule_providers', self._extra_rule_providers) + self.save_data('acl4ssr_providers', self._acl4ssr_providers) def __parse_config(self): if self._top_rules is None: @@ -457,9 +483,9 @@ class ClashRuleProvider(_PluginBase): self.append_top_rules(rules) return schemas.Response(success=True) - def reorder_rules(self, params: Dict[str, Any]): + def reorder_rules(self, params: Dict[str, Any]) -> schemas.Response: if not self._enabled: - return {"success": False, "message": ""} + return schemas.Response(success=False, message='') moved_priority = params.get('moved_priority') target_priority = params.get('target_priority') try: @@ -469,39 +495,41 @@ class ClashRuleProvider(_PluginBase): else: self.__reorder_rules(self._clash_rule_parser, moved_priority, target_priority) except Exception as e: - return {"success": False, "message": str(e)} - return {"success": True, "message": None} + return schemas.Response(success=False, message=str(e)) + return schemas.Response(success=True) - def update_rules(self, params: Dict[str, Any]): + def update_rules(self, params: Dict[str, Any]) -> schemas.Response: if not self._enabled: - return {"success": False, "message": ""} + return schemas.Response(success=False, message='') if params.get('type') == 'ruleset': self.__update_rules(params.get('rules'), self._ruleset_rule_parser) else: self.__update_rules(params.get('rules'), self._clash_rule_parser) - return {"success": True, "message": None} + return schemas.Response(success=True) def update_rule(self, params: Dict[str, Any]) -> Dict[str, Any]: if not self._enabled: return {"success": False, "message": ""} if params.get('type') == 'ruleset': - res = self.update_rule_by_priority(params.get('rule_data'), self._ruleset_rule_parser) + res = self.update_rule_by_priority(params.get('rule_data'), + params.get('priority'), + self._ruleset_rule_parser) if res: self.__add_notification_job(f"{self._ruleset_prefix}{params.get('rule_data').get('action')}") else: - res = self.update_rule_by_priority(params.get('rule_data'), self._clash_rule_parser) + res = self.update_rule_by_priority(params.get('rule_data'), params.get('priority'), self._clash_rule_parser) return {"success": bool(res), "message": None} - def add_rule(self, params: Dict[str, Any]) -> Dict[str, Any]: + def add_rule(self, params: Dict[str, Any]) -> schemas.Response: if not self._enabled: - return {"success": False, "message": ""} + return schemas.Response(success=False, message='') if params.get('type') == 'ruleset': res = self.add_rule_by_priority(params.get('rule_data'), self._ruleset_rule_parser) if res: self.__add_notification_job(f"{self._ruleset_prefix}{params.get('rule_data').get('action')}") else: res = self.add_rule_by_priority(params.get('rule_data'), self._clash_rule_parser) - return {"success": bool(res), "message": None} + return schemas.Response(success=bool(res), message='') def get_subscription(self) -> schemas.Response: if not self._sub_links: @@ -517,11 +545,61 @@ class ClashRuleProvider(_PluginBase): res = self.__refresh_subscription() if not res: return schemas.Response(success=False, message=f"订阅链接 {self._sub_links[0]} 更新失败") - return schemas.Response(success=True, message='订阅更新成功败') + return schemas.Response(success=True, message='订阅更新成功') def get_rule_providers(self) -> schemas.Response: return schemas.Response(success=True, data=self.rule_providers()) + def get_extra_rule_providers(self) -> schemas.Response: + extra_rule_providers = [] + for name, value in self._extra_rule_providers.items(): + item = {'name': name} + item.update(value) + extra_rule_providers.append(item) + return schemas.Response(success=True, data={'rule_providers': extra_rule_providers}) + + def update_extra_rule_provider(self, params: Dict[str, Any]) -> schemas.Response: + if not self._enabled: + return schemas.Response(success=False, message='') + name = params.get('name') + new_value = params.get('value') + new_name = new_value.get('name') + if not name or not new_name: + return schemas.Response(success=False, message="Missing param: name") + item = {} + for key, value in new_value.items(): + if key == 'name' or value is None: + continue + if value == '' or value is None: + continue + item[key] = value + try: + rule_provider = RuleProvider.parse_obj(item) + if rule_provider.type == 'inline' and rule_provider.behavior == 'classical': + for rule in rule_provider.payload: + clash_rule = ClashRuleParser.parse_rule_line(f"{rule},DIRECT") + if not clash_rule: + raise ValueError(f"Invalid clash_rule: {rule}") + except Exception as e: + error_message = f"Failed to parse rule provider: Invalid data={item}, error={repr(e)}" + logger.error(error_message) + return schemas.Response(success=False, message=str(error_message)) + if name != new_name: + self._extra_rule_providers.pop(name, None) + self._extra_rule_providers[new_name] = item + self.save_data('extra_rule_providers', self._extra_rule_providers) + return schemas.Response(success=True) + + def delete_extra_rule_provider(self, params: Dict[str, Any]) -> schemas.Response: + if not self._enabled: + return schemas.Response(success=False, message='') + name = params.get('name') + if not name: + return schemas.Response(success=False, message="Missing param: name") + self._extra_rule_providers.pop(name, None) + self.save_data('extra_rule_providers', self._extra_rule_providers) + return schemas.Response(success=True) + def get_proxy_groups(self) -> schemas.Response: return schemas.Response(success=True, data={'proxy_groups': self._proxy_groups}) @@ -569,14 +647,14 @@ class ClashRuleProvider(_PluginBase): if not item.get('name') or any(x.get('name') == item.get('name') for x in self._proxy_groups): return schemas.Response(success=False, message=f"The proxy group name {item.get('name')} already exists") try: - ProxyGroupValidator.parse_obj(item) + ProxyGroup.parse_obj(item) except Exception as e: error_message = f"Failed to parse proxy group: Invalid data={item}, error={repr(e)}" logger.error(error_message) return schemas.Response(success=False, message=str(error_message)) new_item = {} for k, v in item.items(): - if type(v) is str and len(v) == 0: + if v == '': continue if v is None: continue @@ -608,9 +686,11 @@ class ClashRuleProvider(_PluginBase): if not self._clash_config: return None rule_providers = {} - for key, value in self._clash_config.get("rule-providers", {}): - if value.get("path", '').startwith("./CRP/"): - continue + for key, value in self._clash_config.get('rule-providers', {}).items(): + rule_providers[key] = value + for key, value in self._extra_rule_providers.items(): + rule_providers[key] = value + for key, value in self._acl4ssr_providers.items(): rule_providers[key] = value return rule_providers @@ -618,7 +698,7 @@ class ClashRuleProvider(_PluginBase): rule_parser.rules = [] for rule in rules: clash_rule = ClashRuleParser.parse_rule_dict(rule) - rule_parser.insert_rule_at_priority(clash_rule, rule.get("priority")) + rule_parser.insert_rule_at_priority(clash_rule, rule.get('priority')) self.__save_data() def __reorder_rules(self, rule_parser: ClashRuleParser, moved_priority, target_priority): @@ -665,13 +745,14 @@ class ClashRuleProvider(_PluginBase): self.__save_data() return - def update_rule_by_priority(self, rule: Dict[str, Any], rule_parser: ClashRuleParser) -> bool: - if not isinstance(rule.get("priority"), int): + def update_rule_by_priority(self, rule: Dict[str, Any], priority: int, rule_parser: ClashRuleParser) -> bool: + if type(rule.get("priority")) is not int or type(priority) is not int: return False clash_rule = ClashRuleParser.parse_rule_dict(rule) if not clash_rule: + logger.error(f"Failed to update rule at priority {priority}. Invalid clash rule: {rule}") return False - res = rule_parser.update_rule_at_priority(clash_rule, rule.get("priority")) + res = rule_parser.update_rule_at_priority(clash_rule, priority) self.__save_data() return res @@ -712,7 +793,7 @@ class ClashRuleProvider(_PluginBase): days = seconds_left // 86400 return f"{days}天后过期" if days > 0 else "已过期" - def update_subscription_service(self): + def refresh_subscription_service(self): res = self.__refresh_subscription() if res: used = self._subscription_info['download'] + self._subscription_info['upload'] @@ -730,19 +811,51 @@ class ClashRuleProvider(_PluginBase): text=f"{message}" ) + def __refresh_acl4ssr(self): + logger.info(f"Refreshing ACL4SSR") + # 配置参数 + owner = 'ACL4SSR' + repo = 'ACL4SSR' + paths = ['Clash/Providers', 'Clash/Providers/Ruleset'] + api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/%s" + branch = 'master' + for path in paths: + response = RequestUtils().get_res(api_url % path, headers=settings.GITHUB_HEADERS, params={'ref': branch}) + if not response: + return + files = response.json() + yaml_files = [f for f in files if f["type"] == "file" and f["name"].endswith((".yaml", ".yml"))] + self._acl4ssr_providers = {} + for f in yaml_files: + name = f"{self._acl4ssr_prefix}{f['name'][:f['name'].rfind('.')]}" + path = f"./ACL4SSR/{f['name']}" + provider = {'type': 'http', 'path': path, 'url': f["download_url"], 'interval': 600, + 'behavior': 'classical', 'format': 'yaml', 'size-limit': 0} + if name not in self._acl4ssr_providers: + self._acl4ssr_providers[name] = provider + self.save_data('acl4ssr_providers', self._acl4ssr_providers) + def __refresh_subscription(self) -> bool: if not self._sub_links: logger.error(f"Invalid links: {self._sub_links}") return False url = self._sub_links[0] logger.info(f"Refreshing: {url}") - ret = RequestUtils(accept_type="text/html", - proxies=settings.PROXY if self._proxy else None - ).get_res(url) + ret = None + for i in range(0, self._retry_times): + ret = RequestUtils(accept_type="text/html", + proxies=settings.PROXY if self._proxy else None + ).get_res(url) + if ret: + break if not ret: return False try: rs = yaml.load(ret.content, Loader=yaml.FullLoader) + if rs.get('rules') is None: + rs['rules'] = [] + if self._discard_rules: + rs['rules'] = [] self._clash_config = self.__remove_nodes_by_keywords(rs) except Exception as e: logger.error(f"解析配置出错: {e}") @@ -837,16 +950,18 @@ class ClashRuleProvider(_PluginBase): removed_proxies = [] for proxy_group in clash_config.get("proxy-groups", []): proxy_group['proxies'] = [x for x in proxy_group.get('proxies') if x not in removed_proxies] - # clash_config["proxy-groups"] = [x for x in clash_config.get("proxy-groups", []) if x.get("proxies")] return clash_config def clash_config(self) -> Optional[Dict[str, Any]]: + """ + 整理clash配置,返回配置字典 + """ if not self._clash_config: return None self.__insert_ruleset() - self._top_rules = self._clash_rule_parser.to_string() + self._top_rules = self._clash_rule_parser.to_list() clash_config = copy.deepcopy(self._clash_config) - + clash_config['rule-providers'] = clash_config.get('rule-providers') or {} # 添加代理组 proxy_groups = copy.deepcopy(self._proxy_groups) if proxy_groups: @@ -864,41 +979,56 @@ class ClashRuleProvider(_PluginBase): # 添加按大洲代理组 if self._group_by_region: if self._proxy_groups_by_region: - if clash_config.get("proxy-groups"): + if clash_config.get('proxy-groups'): clash_config['proxy-groups'].extend(self._proxy_groups_by_region) else: clash_config['proxy-groups'] = copy.deepcopy(self._proxy_groups_by_region) top_rules = [] - for rule in self._clash_rule_parser.rules: - if (not isinstance(rule.action, Action) and - not len([x for x in self.clash_outbound(clash_config) if rule.action == x.get("name", '')])): - logger.warn(f"出站 {rule.action} 不存在, 跳过 {rule.raw_rule}") - continue - top_rules.append(rule.raw_rule) - clash_config["rules"] = self._top_rules + clash_config.get("rules", []) + outbound_names = list(x.get("name") for x in self.clash_outbound(clash_config)) + + # 添加 extra rule providers + if self._extra_rule_providers: + clash_config['rule-providers'].update(self._extra_rule_providers) + + # 通过 ruleset rules 添加 rule-providers self._rule_provider = {} - for r in self._clash_rule_parser.rules: - if r.rule_type == RuleType.RULE_SET and r.payload.startswith(self._ruleset_prefix): - action_str = f"{r.action.value}" if isinstance(r.action, Action) else r.action + for rule in self._ruleset_rule_parser.rules: + action_str = f"{rule.action.value}" if isinstance(rule.action, Action) else rule.action + rule_provider_name = f'{self._ruleset_prefix}{action_str}' + if rule_provider_name not in self._rule_provider: path_name = hashlib.sha256(action_str.encode('utf-8')).hexdigest()[:10] - self._ruleset_names[path_name] = r.payload + self._ruleset_names[path_name] = rule.payload sub_url = (f"{self._movie_pilot_url}/api/v1/plugin/ClashRuleProvider/ruleset?" f"name={path_name}&apikey={settings.API_TOKEN}") - self._rule_provider[r.payload] = {"behavior": "classical", - "format": "yaml", - "interval": 3600, - "path": f"./CRP/{path_name}.yaml", - "type": "http", - "url": sub_url} - if clash_config.get("rule-providers"): + self._rule_provider[rule_provider_name] = {"behavior": "classical", + "format": "yaml", + "interval": 3600, + "path": f"./CRP/{path_name}.yaml", + "type": "http", + "url": sub_url} + clash_config['rule-providers'].update(self._rule_provider) + # 添加规则 + for rule in self._clash_rule_parser.rules: + if not isinstance(rule.action, Action) and rule.action not in outbound_names: + logger.warn(f"出站 {rule.action} 不存在, 跳过 {rule.raw_rule}") + continue + if rule.rule_type == RuleType.RULE_SET: + # 添加ACL4SSR Rules + if rule.payload in self._acl4ssr_providers: + clash_config['rule-providers'][rule.payload] = self._acl4ssr_providers[rule.payload] + if rule.payload not in clash_config.get('rule-providers', {}): + logger.warn(f"规则集合 {rule.payload} 不存在, 跳过 {rule.raw_rule}") + continue + top_rules.append(rule.raw_rule) + clash_config["rules"] = self._top_rules + clash_config.get("rules", []) + if self._rule_provider: + clash_config['rule-providers'] = clash_config.get('rule-providers') or {} clash_config['rule-providers'].update(self._rule_provider) - else: - clash_config['rule-providers'] = self._rule_provider key_to_delete = [] for key, item in self._ruleset_names.items(): - if item not in clash_config['rule-providers']: + if item not in clash_config.get('rule-providers', {}): key_to_delete.append(key) for key in key_to_delete: del self._ruleset_names[key] diff --git a/plugins.v2/clashruleprovider/clash_rule_parser.py b/plugins.v2/clashruleprovider/clash_rule_parser.py index bc2a04a..579f883 100644 --- a/plugins.v2/clashruleprovider/clash_rule_parser.py +++ b/plugins.v2/clashruleprovider/clash_rule_parser.py @@ -3,9 +3,58 @@ from typing import List, Dict, Any, Optional, Union, Callable, Literal from dataclasses import dataclass from enum import Enum -from pydantic import BaseModel, Field, validator +from pydantic import BaseModel, Field, validator, HttpUrl +class RuleProvider(BaseModel): + type: Literal["http", "file", "inline"] = Field(..., description="Provider type") + url: Optional[HttpUrl] = Field(None, description="Must be configured if the type is http") + path: Optional[str] = Field(None, description="Optional, file path, must be unique.") + interval: Optional[int] = Field(None, ge=0, description="The update interval for the provider, in seconds.") + proxy: Optional[str] = Field(None, description="Download/update through the specified proxy.") + behavior: Optional[Literal["domain", "ipcidr", "classical"]] = Field(None, + description="Behavior of the rule provider") + format: Literal["yaml", "text", "mrs"] = Field("yaml", description="Format of the rule provider file") + size_limit: int = Field(0, ge=0, description="The maximum size of downloadable files in bytes (0 for no limit)") + payload: Optional[List[str]] = Field(None, description="Content, only effective when type is inline") + + @validator("url", pre=True, always=True) + def check_url_for_http_type(cls, v, values): + if values.get("type") == "http" and v is None: + raise ValueError("url must be configured if the type is 'http'") + return v + + @validator("path", pre=True, always=True) + def check_path_for_file_type(cls, v, values): + if values.get("type") == "file" and v is None: + raise ValueError("path must be configured if the type is 'file'") + return v + + @validator("payload", pre=True, always=True) + def handle_payload_for_non_inline_type(cls, v, values): + # If type is not inline, payload should be ignored (set to None) + if values.get("type") != "inline" and v is not None: + return None + return v + + @validator("payload") + def check_payload_type_for_inline(cls, v, values): + if values.get("type") == "inline" and v is not None and not isinstance(v, list): + raise ValueError("payload must be a list of strings when type is 'inline'") + if values.get("type") == "inline" and v is None: + raise ValueError("payload must be configured if the type is 'inline'") + return v + + @validator("format") + def check_format_with_behavior(cls, v, values): + behavior = values.get("behavior") + if v == "mrs" and behavior not in ["domain", "ipcidr"]: + raise ValueError("mrs format only supports 'domain' or 'ipcidr' behavior") + return v + +class RuleProviders(BaseModel): + __root__: dict[str, RuleProvider] + class ProxyGroupBase(BaseModel): """ 包含所有代理组类型共有的通用字段。 @@ -85,11 +134,7 @@ class LoadBalanceGroup(ProxyGroupBase): # --- Discriminated Union --- ProxyGroupUnion = Union[SelectGroup, RelayGroup, FallbackGroup, UrlTestGroup, LoadBalanceGroup] -class ProxyGroupValidator(BaseModel): - """ - 这是Pydantic V1的验证器。 - 它使用 __root__ 字段来处理可辨识联合。 - """ +class ProxyGroup(BaseModel): __root__: ProxyGroupUnion class RuleType(Enum): @@ -212,7 +257,6 @@ class ClashRuleParser: return ClashRuleParser._parse_regular_rule(line) except Exception as e: - print(f"Error parsing rule '{line}': {e}") return None @staticmethod @@ -228,13 +272,16 @@ class ClashRuleParser: conditions_str += f'({condition.get("type")},{condition.get("payload")})' conditions_str = f"({conditions_str})" raw_rule = f"{clash_rule.get('type')},{conditions_str},{clash_rule.get('action')}" - return ClashRuleParser._parse_logic_rule(raw_rule) + rule = ClashRuleParser._parse_logic_rule(raw_rule) elif clash_rule.get("type") == 'MATCH': raw_rule = f"{clash_rule.get('type')},{clash_rule.get('action')}" - return ClashRuleParser._parse_match_rule(raw_rule) + rule = ClashRuleParser._parse_match_rule(raw_rule) else: raw_rule = f"{clash_rule.get('type')},{clash_rule.get('payload')},{clash_rule.get('action')}" - return ClashRuleParser._parse_regular_rule(raw_rule) + rule = ClashRuleParser._parse_regular_rule(raw_rule) + if rule and 'priority' in clash_rule: + rule.priority = clash_rule['priority'] + return rule @staticmethod def _parse_match_rule(line: str) -> MatchRule: @@ -339,7 +386,7 @@ class ClashRuleParser: ) conditions.append(condition) except ValueError: - print(f"Unknown rule type in logic condition: {rule_type_str}") + continue return conditions @@ -396,7 +443,7 @@ class ClashRuleParser: except Exception: return False - def to_string(self) -> List[str]: + def to_list(self) -> List[str]: result = [] for rule in self.rules: result.append(rule.raw_rule) @@ -478,12 +525,19 @@ class ClashRuleParser: self.rules.sort(key=lambda r: r.priority) def update_rule_at_priority(self, clash_rule: Union[ClashRule, LogicRule], priority: int) -> bool: - for index, existing_rule in enumerate(self.rules): - if existing_rule.priority == priority: - self.rules[index] = clash_rule - self.rules[index].priority = priority - return True - return False + if clash_rule.priority == priority: + for index, existing_rule in enumerate(self.rules): + if existing_rule.priority == priority: + self.rules[index] = clash_rule + self.rules[index].priority = priority + return True + return False + else: + removed = self.remove_rule_at_priority(priority) + if not removed: + return False + self.insert_rule_at_priority(clash_rule, clash_rule.priority) + return True def remove_rule_at_priority(self, priority: int) -> Optional[Union[ClashRule, LogicRule, MatchRule]]: """Remove rule at specific priority and adjust remaining priorities""" diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-nL3Pv4Qs.css b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-BJvXq0hj.css similarity index 54% rename from plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-nL3Pv4Qs.css rename to plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-BJvXq0hj.css index 88bec32..3b3caf2 100644 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-nL3Pv4Qs.css +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-BJvXq0hj.css @@ -1,5 +1,5 @@ -.plugin-config[data-v-52f6e9ed] { +.plugin-config[data-v-c50374dc] { max-width: 800px; margin: 0 auto; } diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-DZF0yyTH.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-Btg4HYx3.js similarity index 81% rename from plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-DZF0yyTH.js rename to plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-Btg4HYx3.js index 4ba18b9..6a2c67b 100644 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-DZF0yyTH.js +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-Btg4HYx3.js @@ -49,18 +49,6 @@ const testResult = reactive({ message: '' }); -// Cron 选项 -const cronOptions = [ - {text: '每5分钟', value: '5min', cron: '*/5 * * * *'}, - {text: '每15分钟', value: '15min', cron: '*/15 * * * *'}, - {text: '每30分钟', value: '30min', cron: '*/30 * * * *'}, - {text: '每小时', value: '1hour', cron: '0 * * * *'}, - {text: '每2小时', value: '2hours', cron: '0 */2 * * *'}, - {text: '每6小时', value: '6hours', cron: '0 */6 * * *'}, - {text: '每12小时', value: '12hours', cron: '0 */12 * * *'}, - {text: '每天', value: '1day', cron: '0 0 * * *'}, - {text: '自定义', value: 'custom', cron: ''}, -]; // 默认配置 const defaultConfig = { @@ -76,9 +64,12 @@ const defaultConfig = { proxy: false, notify: false, auto_update_subscriptions: true, - ruleset_prefix: '📂<-', + ruleset_prefix: '📂<=', + acl4ssr_prefix: '🗂️=>', group_by_region: false, refresh_delay: 5, + discard_rules: false, + enable_acl4ssr: false, }; // 响应式配置对象 @@ -95,14 +86,6 @@ onMounted(() => { config[key] = props.initialConfig[key]; } }); - - // 设置对应的cron选项 - const cronOption = cronOptions.find(option => option.cron === config.cron_string); - if (cronOption) { - selectedCronOption.value = cronOption.value; - } else { - selectedCronOption.value = 'custom'; - } } }); @@ -129,25 +112,6 @@ function validateSubLinks(links) { return true } -function validateCronExpression(cronStr) { - if (!cronStr) return '请输入Cron表达式' - - // 简单的cron表达式验证 - const parts = cronStr.trim().split(/\s+/); - if (parts.length !== 5) { - return 'Cron表达式应包含5个部分 (分 时 日 月 周)' - } - return true -} - -// 更新cron字符串 -function updateCronString(optionValue) { - const option = cronOptions.find(opt => opt.value === optionValue); - if (option && option.cron) { - config.cron_string = option.cron; - } -} - // 测试连接 async function testConnection() { testing.value = true; @@ -268,7 +232,7 @@ return (_ctx, _cache) => { const _component_v_chip = _resolveComponent("v-chip"); const _component_v_combobox = _resolveComponent("v-combobox"); const _component_v_text_field = _resolveComponent("v-text-field"); - const _component_v_select = _resolveComponent("v-select"); + const _component_v_cron_field = _resolveComponent("v-cron-field"); const _component_v_expansion_panel_title = _resolveComponent("v-expansion-panel-title"); const _component_v_expansion_panel_text = _resolveComponent("v-expansion-panel-text"); const _component_v_expansion_panel = _resolveComponent("v-expansion-panel"); @@ -292,7 +256,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[20] || (_cache[20] = [ + default: _withCtx(() => _cache[22] || (_cache[22] = [ _createTextVNode("mdi-close") ])), _: 1 @@ -303,7 +267,7 @@ return (_ctx, _cache) => { ]), default: _withCtx(() => [ _createVNode(_component_v_card_title, null, { - default: _withCtx(() => _cache[19] || (_cache[19] = [ + default: _withCtx(() => _cache[21] || (_cache[21] = [ _createTextVNode("Clash Rule Provider 插件配置") ])), _: 1 @@ -329,16 +293,16 @@ return (_ctx, _cache) => { ref_key: "form", ref: form, modelValue: isFormValid.value, - "onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((isFormValid).value = $event)), + "onUpdate:modelValue": _cache[19] || (_cache[19] = $event => ((isFormValid).value = $event)), onSubmit: _withModifiers(saveConfig, ["prevent"]) }, { default: _withCtx(() => [ - _cache[31] || (_cache[31] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "基本设置", -1)), + _cache[33] || (_cache[33] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "基本设置", -1)), _createVNode(_component_v_row, null, { default: _withCtx(() => [ _createVNode(_component_v_col, { cols: "12", - md: "4" + md: "3" }, { default: _withCtx(() => [ _createVNode(_component_v_switch, { @@ -347,15 +311,14 @@ return (_ctx, _cache) => { label: "启用插件", color: "primary", inset: "", - hint: "启用后插件将开始监控和同步", - "persistent-hint": "" + hint: "启用后插件将开始监控和同步" }, null, 8, ["modelValue"]) ]), _: 1 }), _createVNode(_component_v_col, { cols: "12", - md: "4" + md: "3" }, { default: _withCtx(() => [ _createVNode(_component_v_switch, { @@ -364,15 +327,14 @@ return (_ctx, _cache) => { label: "启用代理", color: "primary", inset: "", - hint: "是否使用系统代理进行网络请求", - "persistent-hint": "" + hint: "是否使用系统代理进行网络请求" }, null, 8, ["modelValue"]) ]), _: 1 }), _createVNode(_component_v_col, { cols: "12", - md: "4" + md: "3" }, { default: _withCtx(() => [ _createVNode(_component_v_switch, { @@ -381,8 +343,23 @@ return (_ctx, _cache) => { label: "启用通知", color: "primary", inset: "", - hint: "执行完成后发送通知消息", - "persistent-hint": "" + hint: "执行完成后发送通知消息" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "3" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.auto_update_subscriptions, + "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ((config.auto_update_subscriptions) = $event)), + label: "自动更新订阅", + color: "primary", + inset: "", + hint: "定期自动更新Clash订阅配置" }, null, 8, ["modelValue"]) ]), _: 1 @@ -390,21 +367,20 @@ return (_ctx, _cache) => { ]), _: 1 }), - _cache[32] || (_cache[32] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "订阅配置", -1)), + _cache[34] || (_cache[34] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "订阅配置", -1)), _createVNode(_component_v_row, null, { default: _withCtx(() => [ _createVNode(_component_v_col, { cols: "12" }, { default: _withCtx(() => [ _createVNode(_component_v_combobox, { modelValue: config.sub_links, - "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ((config.sub_links) = $event)), + "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ((config.sub_links) = $event)), label: "订阅链接", variant: "outlined", multiple: "", chips: "", "closable-chips": "", hint: "添加一个Clash订阅链接, 按回车确认输入", - "persistent-hint": "", rules: [validateSubLinks] }, { chip: _withCtx(({ props, item }) => [ @@ -427,14 +403,13 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_combobox, { modelValue: config.filter_keywords, - "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ((config.filter_keywords) = $event)), + "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ((config.filter_keywords) = $event)), label: "节点过滤关键词", variant: "outlined", multiple: "", chips: "", "closable-chips": "", - hint: "添加用于过滤节点的关键词", - "persistent-hint": "" + hint: "添加用于过滤节点的关键词" }, { chip: _withCtx(({ props, item }) => [ _createVNode(_component_v_chip, _mergeProps(props, { @@ -456,24 +431,23 @@ return (_ctx, _cache) => { ]), _: 1 }), - _cache[33] || (_cache[33] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "Clash 面板设置", -1)), + _cache[35] || (_cache[35] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "Clash 面板设置", -1)), _createVNode(_component_v_row, null, { default: _withCtx(() => [ _createVNode(_component_v_col, { cols: "12" }, { default: _withCtx(() => [ _createVNode(_component_v_text_field, { modelValue: config.clash_dashboard_url, - "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ((config.clash_dashboard_url) = $event)), + "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((config.clash_dashboard_url) = $event)), label: "Clash 面板 URL", variant: "outlined", placeholder: "http://localhost:9090", hint: "Clash 控制面板的访问地址", - "persistent-hint": "", rules: [v => !v || isValidUrl(v) || '请输入有效的URL地址'] }, { "prepend-inner": _withCtx(() => [ _createVNode(_component_v_icon, { color: "primary" }, { - default: _withCtx(() => _cache[21] || (_cache[21] = [ + default: _withCtx(() => _cache[23] || (_cache[23] = [ _createTextVNode("mdi-web") ])), _: 1 @@ -488,19 +462,18 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_text_field, { modelValue: config.clash_dashboard_secret, - "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((config.clash_dashboard_secret) = $event)), + "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => ((config.clash_dashboard_secret) = $event)), label: "Clash 面板密钥", variant: "outlined", placeholder: "your-clash-secret", hint: "用于访问Clash API的密钥", - "persistent-hint": "", "append-inner-icon": showClashSecret.value ? 'mdi-eye-off' : 'mdi-eye', type: showClashSecret.value ? 'text' : 'password', - "onClick:appendInner": _cache[7] || (_cache[7] = $event => (showClashSecret.value = !showClashSecret.value)) + "onClick:appendInner": _cache[8] || (_cache[8] = $event => (showClashSecret.value = !showClashSecret.value)) }, { "prepend-inner": _withCtx(() => [ _createVNode(_component_v_icon, { color: "warning" }, { - default: _withCtx(() => _cache[22] || (_cache[22] = [ + default: _withCtx(() => _cache[24] || (_cache[24] = [ _createTextVNode("mdi-key") ])), _: 1 @@ -514,24 +487,23 @@ return (_ctx, _cache) => { ]), _: 1 }), - _cache[34] || (_cache[34] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "MoviePilot 设置", -1)), + _cache[36] || (_cache[36] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "MoviePilot 设置", -1)), _createVNode(_component_v_row, null, { default: _withCtx(() => [ _createVNode(_component_v_col, { cols: "12" }, { default: _withCtx(() => [ _createVNode(_component_v_text_field, { modelValue: config.movie_pilot_url, - "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => ((config.movie_pilot_url) = $event)), + "onUpdate:modelValue": _cache[9] || (_cache[9] = $event => ((config.movie_pilot_url) = $event)), label: "MoviePilot URL", variant: "outlined", placeholder: "http://localhost:3001", hint: "MoviePilot 服务的访问地址", - "persistent-hint": "", rules: [v => !!v || 'MoviePilot URL不能为空', v => isValidUrl(v) || '请输入有效的URL地址'] }, { "prepend-inner": _withCtx(() => [ _createVNode(_component_v_icon, { color: "success" }, { - default: _withCtx(() => _cache[23] || (_cache[23] = [ + default: _withCtx(() => _cache[25] || (_cache[25] = [ _createTextVNode("mdi-movie") ])), _: 1 @@ -545,58 +517,21 @@ return (_ctx, _cache) => { ]), _: 1 }), - _cache[35] || (_cache[35] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "执行设置", -1)), + _cache[37] || (_cache[37] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "执行设置", -1)), _createVNode(_component_v_row, null, { default: _withCtx(() => [ _createVNode(_component_v_col, { cols: "12" }, { default: _withCtx(() => [ - _createVNode(_component_v_select, { - modelValue: selectedCronOption.value, - "onUpdate:modelValue": [ - _cache[9] || (_cache[9] = $event => ((selectedCronOption).value = $event)), - updateCronString - ], + _createVNode(_component_v_cron_field, { + modelValue: config.cron_string, + "onUpdate:modelValue": _cache[10] || (_cache[10] = $event => ((config.cron_string) = $event)), label: "执行周期", - items: cronOptions, - variant: "outlined", - "item-title": "text", - "item-value": "value", - hint: "选择插件执行的时间间隔", - "persistent-hint": "" + placeholder: "0 4 * * *", + hint: "使用标准Cron表达式格式 (分 时 日 月 周)" }, null, 8, ["modelValue"]) ]), _: 1 }), - (selectedCronOption.value === 'custom') - ? (_openBlock(), _createBlock(_component_v_col, { - key: 0, - cols: "12" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: config.cron_string, - "onUpdate:modelValue": _cache[10] || (_cache[10] = $event => ((config.cron_string) = $event)), - label: "自定义 Cron 表达式", - variant: "outlined", - placeholder: "0 */6 * * *", - hint: "使用标准Cron表达式格式 (分 时 日 月 周)", - "persistent-hint": "", - rules: [validateCronExpression] - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[24] || (_cache[24] = [ - _createTextVNode("mdi-clock-outline") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue", "rules"]) - ]), - _: 1 - })) - : _createCommentVNode("", true), _createVNode(_component_v_col, { cols: "12", md: "6" @@ -612,12 +547,11 @@ return (_ctx, _cache) => { min: "1", max: "300", hint: "请求的超时时间", - "persistent-hint": "", rules: [v => v > 0 || '超时时间必须大于0'] }, { "prepend-inner": _withCtx(() => [ _createVNode(_component_v_icon, { color: "warning" }, { - default: _withCtx(() => _cache[25] || (_cache[25] = [ + default: _withCtx(() => _cache[26] || (_cache[26] = [ _createTextVNode("mdi-timer") ])), _: 1 @@ -643,12 +577,11 @@ return (_ctx, _cache) => { min: "0", max: "10", hint: "失败时的重试次数", - "persistent-hint": "", rules: [v => v >= 0 || '重试次数不能为负数'] }, { "prepend-inner": _withCtx(() => [ _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[26] || (_cache[26] = [ + default: _withCtx(() => _cache[27] || (_cache[27] = [ _createTextVNode("mdi-refresh") ])), _: 1 @@ -672,12 +605,12 @@ return (_ctx, _cache) => { _createVNode(_component_v_expansion_panel_title, null, { default: _withCtx(() => [ _createVNode(_component_v_icon, { class: "mr-2" }, { - default: _withCtx(() => _cache[27] || (_cache[27] = [ + default: _withCtx(() => _cache[28] || (_cache[28] = [ _createTextVNode("mdi-cog") ])), _: 1 }), - _cache[28] || (_cache[28] = _createTextVNode(" 高级选项 ")) + _cache[29] || (_cache[29] = _createTextVNode(" 高级选项 ")) ]), _: 1 }), @@ -687,34 +620,48 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_col, { cols: "12", - md: "6" + md: "4" }, { default: _withCtx(() => [ _createVNode(_component_v_switch, { - modelValue: config.auto_update_subscriptions, - "onUpdate:modelValue": _cache[13] || (_cache[13] = $event => ((config.auto_update_subscriptions) = $event)), - label: "自动更新订阅", + modelValue: config.discard_rules, + "onUpdate:modelValue": _cache[13] || (_cache[13] = $event => ((config.discard_rules) = $event)), + label: "丢弃订阅规则", color: "primary", inset: "", - hint: "定期自动更新Clash订阅配置", - "persistent-hint": "" + hint: "不保留订阅配置文件的rules字段" }, null, 8, ["modelValue"]) ]), _: 1 }), _createVNode(_component_v_col, { cols: "12", - md: "6" + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.enable_acl4ssr, + "onUpdate:modelValue": _cache[14] || (_cache[14] = $event => ((config.enable_acl4ssr) = $event)), + label: "ACL4SSR规则集", + color: "primary", + inset: "", + hint: "启用ACL4SSR规则集" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "4" }, { default: _withCtx(() => [ _createVNode(_component_v_switch, { modelValue: config.group_by_region, - "onUpdate:modelValue": _cache[14] || (_cache[14] = $event => ((config.group_by_region) = $event)), + "onUpdate:modelValue": _cache[15] || (_cache[15] = $event => ((config.group_by_region) = $event)), label: "按大洲分组节点", color: "primary", inset: "", - hint: "启用后根据名称,将节点添加到代理组", - "persistent-hint": "" + hint: "启用后根据名称,将节点添加到代理组" }, null, 8, ["modelValue"]) ]), _: 1 @@ -726,23 +673,22 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_col, { cols: "12", - md: "6" + md: "4" }, { default: _withCtx(() => [ _createVNode(_component_v_text_field, { modelValue: config.ruleset_prefix, - "onUpdate:modelValue": _cache[15] || (_cache[15] = $event => ((config.ruleset_prefix) = $event)), + "onUpdate:modelValue": _cache[16] || (_cache[16] = $event => ((config.ruleset_prefix) = $event)), label: "规则集前缀", variant: "outlined", - placeholder: "📂<-", + placeholder: "📂<=", rules: [v => !!v || '规则集前缀不能为空'], - hint: "为生成的规则集添加前缀", - "persistent-hint": "" + hint: "为生成的规则集添加前缀" }, { "prepend-inner": _withCtx(() => [ _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[29] || (_cache[29] = [ - _createTextVNode("mdi-prefix") + default: _withCtx(() => _cache[30] || (_cache[30] = [ + _createTextVNode("mdi-palette") ])), _: 1 }) @@ -754,12 +700,39 @@ return (_ctx, _cache) => { }), _createVNode(_component_v_col, { cols: "12", - md: "6" + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.acl4ssr_prefix, + "onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((config.acl4ssr_prefix) = $event)), + label: "ACL4SSR 规则集前缀", + variant: "outlined", + placeholder: "🗂️=>", + rules: [v => !!v || '规则集前缀不能为空'], + hint: "ACL4SSR 规则集前缀" + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "primary" }, { + default: _withCtx(() => _cache[31] || (_cache[31] = [ + _createTextVNode("mdi-palette") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "4" }, { default: _withCtx(() => [ _createVNode(_component_v_text_field, { modelValue: config.refresh_delay, - "onUpdate:modelValue": _cache[16] || (_cache[16] = $event => ((config.refresh_delay) = $event)), + "onUpdate:modelValue": _cache[18] || (_cache[18] = $event => ((config.refresh_delay) = $event)), modelModifiers: { number: true }, label: "刷新延迟", variant: "outlined", @@ -768,12 +741,11 @@ return (_ctx, _cache) => { max: "30", suffix: "秒", hint: "通知Clash刷新规则集的延迟时间", - "persistent-hint": "", rules: [v => v >= 0 || '刷新延迟不能为负数'] }, { "prepend-inner": _withCtx(() => [ _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[30] || (_cache[30] = [ + default: _withCtx(() => _cache[32] || (_cache[32] = [ _createTextVNode("mdi-clock-outline") ])), _: 1 @@ -802,6 +774,21 @@ return (_ctx, _cache) => { ]), _: 1 }), + _createVNode(_component_v_alert, { + type: "info", + text: "", + class: "mb-6", + variant: "tonal" + }, { + default: _withCtx(() => _cache[38] || (_cache[38] = [ + _createTextVNode(" 配置说明参考: "), + _createElementVNode("a", { + href: "https://github.com/wumode/MoviePilot-Plugins/tree/main/plugins.v2/clashruleprovider/README.md", + target: "_blank" + }, "README", -1) + ])), + _: 1 + }), _createVNode(_component_v_card_actions, null, { default: _withCtx(() => [ _createVNode(_component_v_btn, { @@ -810,12 +797,12 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[36] || (_cache[36] = [ + default: _withCtx(() => _cache[39] || (_cache[39] = [ _createTextVNode("mdi-view-dashboard-edit") ])), _: 1 }), - _cache[37] || (_cache[37] = _createTextVNode(" 规则 ")) + _cache[40] || (_cache[40] = _createTextVNode(" 规则 ")) ]), _: 1 }), @@ -823,7 +810,7 @@ return (_ctx, _cache) => { color: "secondary", onClick: resetForm }, { - default: _withCtx(() => _cache[38] || (_cache[38] = [ + default: _withCtx(() => _cache[41] || (_cache[41] = [ _createTextVNode("重置") ])), _: 1 @@ -833,7 +820,7 @@ return (_ctx, _cache) => { onClick: testConnection, loading: testing.value }, { - default: _withCtx(() => _cache[39] || (_cache[39] = [ + default: _withCtx(() => _cache[42] || (_cache[42] = [ _createTextVNode("测试连接") ])), _: 1 @@ -845,7 +832,7 @@ return (_ctx, _cache) => { onClick: saveConfig, loading: saving.value }, { - default: _withCtx(() => _cache[40] || (_cache[40] = [ + default: _withCtx(() => _cache[43] || (_cache[43] = [ _createTextVNode(" 保存配置 ") ])), _: 1 @@ -860,7 +847,7 @@ return (_ctx, _cache) => { variant: "tonal", closable: "", class: "ma-4 mt-0", - "onClick:close": _cache[18] || (_cache[18] = $event => (testResult.show = false)) + "onClick:close": _cache[20] || (_cache[20] = $event => (testResult.show = false)) }, { default: _withCtx(() => [ _createElementVNode("div", _hoisted_2, [ @@ -887,6 +874,6 @@ return (_ctx, _cache) => { } }; -const ConfigComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-52f6e9ed"]]); +const ConfigComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-c50374dc"]]); export { ConfigComponent as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-C2_enJ99.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-D_nruYha.js similarity index 80% rename from plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-C2_enJ99.js rename to plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-D_nruYha.js index a1b2514..764baa1 100644 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-C2_enJ99.js +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-D_nruYha.js @@ -3849,7 +3849,7 @@ var jsYaml = { safeDump: safeDump }; -const {createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,createVNode:_createVNode,toDisplayString:_toDisplayString,openBlock:_openBlock,createBlock:_createBlock,createCommentVNode:_createCommentVNode,createElementVNode:_createElementVNode,withModifiers:_withModifiers,normalizeClass:_normalizeClass,createElementBlock:_createElementBlock} = await importShared('vue'); +const {createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,createVNode:_createVNode,toDisplayString:_toDisplayString,openBlock:_openBlock,createBlock:_createBlock,createCommentVNode:_createCommentVNode,createElementVNode:_createElementVNode,withModifiers:_withModifiers,normalizeClass:_normalizeClass,createElementBlock:_createElementBlock,mergeProps:_mergeProps} = await importShared('vue'); const _hoisted_1 = { class: "plugin-page" }; @@ -3896,16 +3896,26 @@ const _hoisted_30 = { }; const _hoisted_31 = { class: "d-flex justify-center" }; const _hoisted_32 = { style: {"position":"absolute","right":"0","bottom":"0"} }; -const _hoisted_33 = { class: "d-flex justify-space-between mb-2" }; -const _hoisted_34 = { class: "d-flex justify-space-between mb-2" }; -const _hoisted_35 = { class: "d-flex justify-space-between text-caption text-grey" }; -const _hoisted_36 = { class: "d-flex justify-space-between align-center mb-2" }; +const _hoisted_33 = { class: "mb-6" }; +const _hoisted_34 = { class: "pa-4" }; +const _hoisted_35 = { class: "d-flex justify-space-between align-center mb-4" }; +const _hoisted_36 = { class: "d-flex align-center" }; const _hoisted_37 = { + class: "position-relative px-4 py-2", + style: {"min-height":"56px"} +}; +const _hoisted_38 = { class: "d-flex justify-center" }; +const _hoisted_39 = { style: {"position":"absolute","right":"0","bottom":"0"} }; +const _hoisted_40 = { class: "d-flex justify-space-between mb-2" }; +const _hoisted_41 = { class: "d-flex justify-space-between mb-2" }; +const _hoisted_42 = { class: "d-flex justify-space-between text-caption text-grey" }; +const _hoisted_43 = { class: "d-flex justify-space-between align-center mb-2" }; +const _hoisted_44 = { key: 0, class: "url-display" }; -const _hoisted_38 = ["href"]; -const _hoisted_39 = { +const _hoisted_45 = ["href"]; +const _hoisted_46 = { key: 1, class: "text-grey" }; @@ -3937,6 +3947,24 @@ const headers = ref([ {title: '出站', key: 'action', sortable: false}, {title: '操作', key: 'actions', sortable: false}, ]); + +const headersRuleset = ref([ + {title: '优先级', key: 'priority', sortable: true}, // 可以根据需要设置是否可排序 + {title: '类型', key: 'type', sortable: false}, + {title: '内容', key: 'payload', sortable: false}, + {title: '出站', key: 'action', sortable: false}, + {title: '规则集合名', key: 'name', sortable: false}, + {title: '操作', key: 'actions', sortable: false}, +]); + +const headersRuleProviders = ref([ + {title: '名称', key: 'name', sortable: false}, + {title: '类型', key: 'type', sortable: false}, + {title: '行为', key: 'behavior', sortable: false}, + {title: '格式', key: 'format', sortable: false}, + {title: '操作', key: 'actions', sortable: false}, +]); + const proxyGroupHeaders = ref([ {title: '名称', key: 'name', sortable: false}, {title: '类型', key: 'type', sortable: false}, @@ -3952,10 +3980,12 @@ const extraProxiesHeaders = ref([ const activeTab = ref(0); const page = ref(1); const pageRuleset = ref(1); +const pageRulProviders = ref(1); const pageProxyGroup = ref(1); const pageExtraProxies = ref(1); const itemsPerPage = ref(10); // v-data-table 默认的 items-per-page 值 const itemsPerPageRuleset = ref(10); +const itemsPerPageRuleProviders = ref(10); const itemsPerPageProxyGroup = ref(10); const itemsPerPageExtraProxies = ref(10); const itemsPerPageOptions = ref([ @@ -3994,6 +4024,7 @@ const filteredRulesetRules = computed(() => { ); }); + const pageCountRuleset = computed(() => { if (itemsPerPageRuleset.value === -1) { return 1; @@ -4015,6 +4046,13 @@ const pageCountExtraProxies = computed(() => { return Math.ceil(extraProxies.value.length / itemsPerPageExtraProxies.value); }); +const pageCountExtraRuleProviders = computed(() => { + if (itemsPerPageRuleProviders.value === -1) { + return 1; + } + return Math.ceil(extraRuleProviders.value.length / itemsPerPageRuleProviders.value); +}); + const expansionPanels = ref(null); const snackbar = ref({ show: false, @@ -4030,15 +4068,12 @@ const additionalParamOptions = ref([ {title: 'src', value: 'src'} ]); -// 新增状态变量 const subUrl = ref(''); const isValidUrl = (urlString) => { - if (!urlString) { - return false; - } + if (!urlString) return false; try { - new URL(urlString); - return true; + const url = new URL(urlString); + return url.protocol === 'http:' || url.protocol === 'https:'; } catch (e) { return false; } @@ -4053,6 +4088,19 @@ const urlRules = [ } ]; +const payloadRules = computed(() => { + return [ + (v) => { + // 如果 type 是 'MATCH',允许 payload 为空 + if (newRule.value.type === 'MATCH') { + return true; // 不验证 + } + // 否则,payload 不能为空 + return !!v || '内容不能为空'; + }, + ]; +}); + function dragStart(event, priority, type = 'top') { dragItem.value = {priority, type}; event.dataTransfer.effectAllowed = 'move'; @@ -4060,7 +4108,7 @@ function dragStart(event, priority, type = 'top') { function dragOver(event, priority, type = 'top') { event.preventDefault(); - const currentRules = type === 'top' ? rules.value : ruleset_rules.value; + const currentRules = type === 'top' ? rules.value : rulesetRules.value; // 高亮当前悬停行 currentRules.forEach(rule => { rule._isHovered = (rule.priority === priority); @@ -4108,7 +4156,8 @@ const newProxyGroup = ref({ const loading = ref(true); const error = ref(null); const rules = ref([]); -const ruleset_rules = ref([]); +const rulesetRules = ref([]); +const extraRuleProviders = ref([]); const status = ref('running'); const rulesetPrefix = ref('Custom_'); const lastUpdated = ref(''); @@ -4118,10 +4167,13 @@ const yamlDialog = ref(false); const displayedYaml = ref(''); const searchTopRule = ref(''); const searchRulesetRule = ref(''); +const searchRuleProviders = ref(''); // 规则编辑相关状态 const proxyGroupDialog = ref(false); const ruleDialog = ref(false); +const ruleProviderDialog = ref(false); const editingPriority = ref(null); +const editingRuleProviderName = ref(null); const editingType = ref('top'); // 记录当前编辑的规则类型('top' 或 'ruleset') const newRule = ref({ type: 'DOMAIN-SUFFIX', @@ -4131,6 +4183,18 @@ const newRule = ref({ priority: 0 }); +const newRuleProvider = ref({ + name: '', + type: 'http', + path: '', + url: '', + interval: 600, + behavior: 'classical', + format: 'yaml', + 'size-limit': 0, + payload: [], +}); + // 导入规则相关状态 const importRuleDialog = ref(false); const importExtraProxiesDialog = ref(false); @@ -4145,7 +4209,7 @@ const importExtraProxies = ref({ // 排序后的规则 const sortedRules = computed(() => [...rules.value].sort((a, b) => a.priority - b.priority)); -const sortedRulesetRules = computed(() => [...ruleset_rules.value].sort((a, b) => a.priority - b.priority)); +const sortedRulesetRules = computed(() => [...rulesetRules.value].sort((a, b) => a.priority - b.priority)); const showAdditionalParams = computed(() => { return ['IP-CIDR', 'IP-CIDR6', 'IP-ASN', 'GEOIP'].includes(newRule.value.type); }); @@ -4176,7 +4240,9 @@ ref(['Direct', 'Reject', 'RejectDrop', 'Compatible', 'Pass', 'Dns', 'Relay', 'Se const strategyTypes = ref(['round-robin', 'consistent-hashing', 'sticky-sessions']); const importRuleTypes = ['YAML']; const importProxiesTypes = ['YAML']; - +const ruleProviderTypes = ['http', 'file', 'inline']; +const ruleProviderBehaviorTypes = ['domain', 'ipcidr', 'classical']; +const ruleProviderFormatTypes = ['yaml', 'text']; // 修改actions为计算属性,合并内置动作和自定义出站 const actions = computed(() => [ 'DIRECT', 'REJECT', 'REJECT-DROP', 'PASS', 'COMPATIBLE', @@ -4295,6 +4361,24 @@ function openAddRuleDialog(type = 'top') { ruleDialog.value = true; } + +// 打开添加规则对话框 +function openAddRuleProviderDialog() { + editingRuleProviderName.value = null; + newRuleProvider.value = { + name: '', + type: 'http', + path: '', + url: '', + interval: 600, + behavior: 'classical', + format: 'yaml', + 'size-limit': 0, + payload: [], + }; + ruleProviderDialog.value = true; +} + const showProxyGroupYaml = (proxyGroup) => { displayedYaml.value = jsYaml.dump(proxyGroup); yamlDialog.value = true; @@ -4342,6 +4426,29 @@ function editRule(priority, type = 'top') { } } +function editRuleProvider(name) { + console.log("[DEBUG] extraRuleProviders:", extraRuleProviders.value); + console.log("[DEBUG] name:", name); + const ruleProvider = extraRuleProviders.value.find(r => r.name === name); + console.log("[DEBUG] ruleProvider found:", ruleProvider); + + if (ruleProvider) { + editingRuleProviderName.value = name; + newRuleProvider.value = { + name: ruleProvider.name, + type: ruleProvider.type, + path: ruleProvider.path, + url: ruleProvider.url, + interval: ruleProvider.interval, + behavior: ruleProvider.behavior, + format: ruleProvider.format, + 'size-limit': ruleProvider['size-limit'], + payload: ruleProvider.payload, + }; + ruleProviderDialog.value = true; + } +} + async function importRule() { try { const requestData = { @@ -4393,6 +4500,8 @@ async function importExtraProxiesFun() { } async function saveProxyGroups() { + const {valid} = await proxyGroupsForm.value.validate(); + if (!valid) return; try { const requestData = { proxy_group: newProxyGroup.value, @@ -4415,11 +4524,18 @@ async function saveProxyGroups() { } } +const ruleForm = ref(null); +const proxyGroupsForm = ref(null); +const ruleProvidersForm = ref(null); + // 保存规则 async function saveRule() { + const {valid} = await ruleForm.value.validate(); + if (!valid) return; try { const requestData = { type: editingType.value, // "top" 或 "ruleset" + priority: editingPriority.value, rule_data: { ...newRule.value, additional_params: newRule.value.additional_params @@ -4441,7 +4557,6 @@ async function saveRule() { color: 'success' }; } catch (err) { - console.error('保存规则失败:', err); error.value = err.message || '保存规则失败'; snackbar.value = { show: true, @@ -4451,6 +4566,33 @@ async function saveRule() { } } +async function saveRuleProvider() { + const {valid} = await ruleProvidersForm.value.validate(); + if (!valid) return; + try { + const requestData = { + name: editingRuleProviderName.value === null ? newRuleProvider.value.name : editingRuleProviderName.value, + value: newRuleProvider.value + }; + await props.api.post('/plugin/ClashRuleProvider/extra-rule-provider', requestData); + + ruleProviderDialog.value = false; + await refreshData(); + snackbar.value = { + show: true, + message: editingRuleProviderName.value === null ? '规则集合添加成功' : '规则集合更新成功', + color: 'success' + }; + } catch (err) { + error.value = err.message || '保存规则集合失败'; + snackbar.value = { + show: true, + message: '保存规则集合失败: ' + (err.message || '未知错误'), + color: 'error' + }; + } +} + // 删除规则 async function deleteRule(priority, type = 'top') { try { @@ -4466,6 +4608,19 @@ async function deleteRule(priority, type = 'top') { } } +async function deleteRuleProvider(name) { + try { + await props.api.delete('/plugin/ClashRuleProvider/extra-rule-provider', { + data: { + name: name, // 规则类型 + } + }); + await refreshData(); + } catch (err) { + error.value = err.message || '删除规则集合失败'; + } +} + async function deleteProxyGroup(name) { try { await props.api.delete('/plugin/ClashRuleProvider/proxy-group', { @@ -4537,6 +4692,7 @@ async function refreshData() { providersResponse, proxyGroupsResponse, extraProxiesResponse, + extraRuleProvidersResponse, ] = await Promise.all([ props.api.get('/plugin/ClashRuleProvider/status'), props.api.get('/plugin/ClashRuleProvider/rules?rule_type=top'), @@ -4546,6 +4702,7 @@ async function refreshData() { props.api.get('/plugin/ClashRuleProvider/rule-providers'), props.api.get('/plugin/ClashRuleProvider/proxy-groups'), props.api.get('/plugin/ClashRuleProvider/extra-proxies'), + props.api.get('/plugin/ClashRuleProvider/extra-rule-providers'), ]); // 处理状态请求的响应 @@ -4565,12 +4722,13 @@ async function refreshData() { rulesetPrefix.value = state?.data?.ruleset_prefix || 'Custom_'; rules.value = response?.data.rules || []; - ruleset_rules.value = response_ruleset?.data.rules || []; + rulesetRules.value = response_ruleset?.data.rules || []; subscriptionUrl.value = subscription?.data.url; customOutbounds.value = outboundsResponse?.data.outbound || []; ruleProviders.value = providersResponse?.data || {}; proxyGroups.value = proxyGroupsResponse?.data.proxy_groups || []; extraProxies.value = extraProxiesResponse?.data.extra_proxies || []; + extraRuleProviders.value = extraRuleProvidersResponse?.data.rule_providers || []; lastUpdated.value = new Date().toLocaleString(); // 刷新后恢复面板状态 @@ -4631,9 +4789,11 @@ return (_ctx, _cache) => { const _component_v_spacer = _resolveComponent("v-spacer"); const _component_v_card_actions = _resolveComponent("v-card-actions"); const _component_v_snackbar = _resolveComponent("v-snackbar"); + const _component_v_form = _resolveComponent("v-form"); const _component_v_dialog = _resolveComponent("v-dialog"); const _component_v_switch = _resolveComponent("v-switch"); const _component_v_textarea = _resolveComponent("v-textarea"); + const _component_v_combobox = _resolveComponent("v-combobox"); return (_openBlock(), _createElementBlock("div", _hoisted_1, [ _createVNode(_component_v_card, null, { @@ -4648,7 +4808,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[59] || (_cache[59] = [ + default: _withCtx(() => _cache[74] || (_cache[74] = [ _createTextVNode("mdi-close") ])), _: 1 @@ -4659,7 +4819,7 @@ return (_ctx, _cache) => { ]), default: _withCtx(() => [ _createVNode(_component_v_card_title, null, { - default: _withCtx(() => _cache[58] || (_cache[58] = [ + default: _withCtx(() => _cache[73] || (_cache[73] = [ _createTextVNode("Clash Rule Provider") ])), _: 1 @@ -4695,26 +4855,32 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_tab, null, { - default: _withCtx(() => _cache[60] || (_cache[60] = [ + default: _withCtx(() => _cache[75] || (_cache[75] = [ _createTextVNode("规则集规则") ])), _: 1 }), _createVNode(_component_v_tab, null, { - default: _withCtx(() => _cache[61] || (_cache[61] = [ + default: _withCtx(() => _cache[76] || (_cache[76] = [ _createTextVNode("置顶规则") ])), _: 1 }), _createVNode(_component_v_tab, null, { - default: _withCtx(() => _cache[62] || (_cache[62] = [ + default: _withCtx(() => _cache[77] || (_cache[77] = [ _createTextVNode("代理组") ])), _: 1 }), _createVNode(_component_v_tab, null, { - default: _withCtx(() => _cache[63] || (_cache[63] = [ - _createTextVNode("附加出站代理") + default: _withCtx(() => _cache[78] || (_cache[78] = [ + _createTextVNode("出站代理") + ])), + _: 1 + }), + _createVNode(_component_v_tab, null, { + default: _withCtx(() => _cache[79] || (_cache[79] = [ + _createTextVNode("规则集合") ])), _: 1 }) @@ -4723,7 +4889,7 @@ return (_ctx, _cache) => { }, 8, ["modelValue"]), _createVNode(_component_v_window, { modelValue: activeTab.value, - "onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((activeTab).value = $event)) + "onUpdate:modelValue": _cache[21] || (_cache[21] = $event => ((activeTab).value = $event)) }, { default: _withCtx(() => [ _createVNode(_component_v_window_item, null, { @@ -4731,7 +4897,7 @@ return (_ctx, _cache) => { _createElementVNode("div", _hoisted_3, [ _createElementVNode("div", _hoisted_4, [ _createElementVNode("div", _hoisted_5, [ - _cache[66] || (_cache[66] = _createElementVNode("div", { class: "text-h6" }, "规则集规则", -1)), + _cache[82] || (_cache[82] = _createElementVNode("div", { class: "text-h6" }, "规则集规则", -1)), _createElementVNode("div", _hoisted_6, [ _createVNode(_component_v_text_field, { modelValue: searchRulesetRule.value, @@ -4750,12 +4916,12 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[64] || (_cache[64] = [ + default: _withCtx(() => _cache[80] || (_cache[80] = [ _createTextVNode("mdi-plus") ])), _: 1 }), - _cache[65] || (_cache[65] = _createTextVNode(" 添加规则 ")) + _cache[81] || (_cache[81] = _createTextVNode(" 添加规则 ")) ]), _: 1 }) @@ -4763,7 +4929,7 @@ return (_ctx, _cache) => { ]) ]), _createVNode(_component_v_data_table, { - headers: headers.value, + headers: headersRuleset.value, items: sortedRulesetRules.value, search: searchRulesetRule.value, page: pageRuleset.value, @@ -4784,7 +4950,7 @@ return (_ctx, _cache) => { }, [ _createElementVNode("td", null, [ _createVNode(_component_v_icon, { class: "drag-handle" }, { - default: _withCtx(() => _cache[67] || (_cache[67] = [ + default: _withCtx(() => _cache[83] || (_cache[83] = [ _createTextVNode("mdi-drag") ])), _: 1 @@ -4815,7 +4981,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[68] || (_cache[68] = [ + default: _withCtx(() => _cache[84] || (_cache[84] = [ _createTextVNode("mdi-pencil") ])), _: 1 @@ -4832,7 +4998,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[69] || (_cache[69] = [ + default: _withCtx(() => _cache[85] || (_cache[85] = [ _createTextVNode("mdi-delete") ])), _: 1 @@ -4872,7 +5038,7 @@ return (_ctx, _cache) => { ]), _: 1 }, 8, ["headers", "items", "search", "page", "items-per-page", "items-per-page-options"]), - _cache[70] || (_cache[70] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *对规则集中规则的修改可以在Clash中立即生效。 ", -1)) + _cache[86] || (_cache[86] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *对规则集中规则的修改可以在Clash中立即生效。 ", -1)) ]) ]), _: 1 @@ -4882,7 +5048,7 @@ return (_ctx, _cache) => { _createElementVNode("div", _hoisted_11, [ _createElementVNode("div", _hoisted_12, [ _createElementVNode("div", _hoisted_13, [ - _cache[75] || (_cache[75] = _createElementVNode("div", { class: "text-h6" }, "置顶规则", -1)), + _cache[91] || (_cache[91] = _createElementVNode("div", { class: "text-h6" }, "置顶规则", -1)), _createElementVNode("div", _hoisted_14, [ _createVNode(_component_v_text_field, { modelValue: searchTopRule.value, @@ -4902,12 +5068,12 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[71] || (_cache[71] = [ + default: _withCtx(() => _cache[87] || (_cache[87] = [ _createTextVNode("mdi-import") ])), _: 1 }), - _cache[72] || (_cache[72] = _createTextVNode(" 导入规则 ")) + _cache[88] || (_cache[88] = _createTextVNode(" 导入规则 ")) ]), _: 1 }), @@ -4917,12 +5083,12 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[73] || (_cache[73] = [ + default: _withCtx(() => _cache[89] || (_cache[89] = [ _createTextVNode("mdi-plus") ])), _: 1 }), - _cache[74] || (_cache[74] = _createTextVNode(" 添加规则 ")) + _cache[90] || (_cache[90] = _createTextVNode(" 添加规则 ")) ]), _: 1 }) @@ -4951,7 +5117,7 @@ return (_ctx, _cache) => { }, [ _createElementVNode("td", null, [ _createVNode(_component_v_icon, { class: "drag-handle" }, { - default: _withCtx(() => _cache[76] || (_cache[76] = [ + default: _withCtx(() => _cache[92] || (_cache[92] = [ _createTextVNode("mdi-drag") ])), _: 1 @@ -4982,7 +5148,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[77] || (_cache[77] = [ + default: _withCtx(() => _cache[93] || (_cache[93] = [ _createTextVNode("mdi-pencil") ])), _: 1 @@ -5000,7 +5166,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[78] || (_cache[78] = [ + default: _withCtx(() => _cache[94] || (_cache[94] = [ _createTextVNode("mdi-delete") ])), _: 1 @@ -5014,7 +5180,7 @@ return (_ctx, _cache) => { activator: "parent", location: "top" }, { - default: _withCtx(() => _cache[79] || (_cache[79] = [ + default: _withCtx(() => _cache[95] || (_cache[95] = [ _createTextVNode(" 根据规则集自动添加 ") ])), _: 1 @@ -5052,8 +5218,8 @@ return (_ctx, _cache) => { ]), _: 1 }, 8, ["headers", "search", "items", "page", "items-per-page"]), - _cache[80] || (_cache[80] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *置顶规则用于管理来自规则集的匹配规则,这些规则会动态更新。 ", -1)), - _cache[81] || (_cache[81] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *对置顶规则的修改只有Clash更新配置后才会生效。 ", -1)) + _cache[96] || (_cache[96] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *置顶规则用于管理来自规则集的匹配规则,这些规则会动态更新。 ", -1)), + _cache[97] || (_cache[97] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *对置顶规则的修改只有Clash更新配置后才会生效。 ", -1)) ]) ]), _: 1 @@ -5063,7 +5229,7 @@ return (_ctx, _cache) => { _createElementVNode("div", _hoisted_19, [ _createElementVNode("div", _hoisted_20, [ _createElementVNode("div", _hoisted_21, [ - _cache[84] || (_cache[84] = _createElementVNode("div", { class: "text-h6" }, "代理组", -1)), + _cache[100] || (_cache[100] = _createElementVNode("div", { class: "text-h6" }, "代理组", -1)), _createElementVNode("div", _hoisted_22, [ _createVNode(_component_v_btn, { color: "primary", @@ -5071,12 +5237,12 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[82] || (_cache[82] = [ + default: _withCtx(() => _cache[98] || (_cache[98] = [ _createTextVNode("mdi-plus") ])), _: 1 }), - _cache[83] || (_cache[83] = _createTextVNode(" 添加代理组 ")) + _cache[99] || (_cache[99] = _createTextVNode(" 添加代理组 ")) ]), _: 1 }) @@ -5107,7 +5273,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[85] || (_cache[85] = [ + default: _withCtx(() => _cache[101] || (_cache[101] = [ _createTextVNode("mdi-code-json") ])), _: 1 @@ -5124,7 +5290,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[86] || (_cache[86] = [ + default: _withCtx(() => _cache[102] || (_cache[102] = [ _createTextVNode("mdi-delete") ])), _: 1 @@ -5164,7 +5330,7 @@ return (_ctx, _cache) => { ]), _: 1 }, 8, ["headers", "items", "page", "items-per-page"]), - _cache[87] || (_cache[87] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, null, -1)) + _cache[103] || (_cache[103] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, null, -1)) ]) ]), _: 1 @@ -5174,7 +5340,7 @@ return (_ctx, _cache) => { _createElementVNode("div", _hoisted_26, [ _createElementVNode("div", _hoisted_27, [ _createElementVNode("div", _hoisted_28, [ - _cache[90] || (_cache[90] = _createElementVNode("div", { class: "text-h6" }, "附加出站代理", -1)), + _cache[106] || (_cache[106] = _createElementVNode("div", { class: "text-h6" }, "出站代理", -1)), _createElementVNode("div", _hoisted_29, [ _createVNode(_component_v_btn, { color: "primary", @@ -5182,12 +5348,12 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[88] || (_cache[88] = [ + default: _withCtx(() => _cache[104] || (_cache[104] = [ _createTextVNode("mdi-plus") ])), _: 1 }), - _cache[89] || (_cache[89] = _createTextVNode(" 导入节点 ")) + _cache[105] || (_cache[105] = _createTextVNode(" 导入节点 ")) ]), _: 1 }) @@ -5220,7 +5386,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[91] || (_cache[91] = [ + default: _withCtx(() => _cache[107] || (_cache[107] = [ _createTextVNode("mdi-code-json") ])), _: 1 @@ -5237,7 +5403,7 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[92] || (_cache[92] = [ + default: _withCtx(() => _cache[108] || (_cache[108] = [ _createTextVNode("mdi-delete") ])), _: 1 @@ -5277,7 +5443,132 @@ return (_ctx, _cache) => { ]), _: 1 }, 8, ["headers", "items", "page", "items-per-page"]), - _cache[93] || (_cache[93] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, null, -1)) + _cache[109] || (_cache[109] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, null, -1)) + ]) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, null, { + default: _withCtx(() => [ + _createElementVNode("div", _hoisted_33, [ + _createElementVNode("div", _hoisted_34, [ + _createElementVNode("div", _hoisted_35, [ + _cache[112] || (_cache[112] = _createElementVNode("div", { class: "text-h6" }, "规则集合", -1)), + _createElementVNode("div", _hoisted_36, [ + _createVNode(_component_v_text_field, { + modelValue: searchRuleProviders.value, + "onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((searchRuleProviders).value = $event)), + label: "搜索", + density: "compact", + variant: "outlined", + "hide-details": "", + class: "mr-2", + style: {"min-width":"100px"}, + "prepend-inner-icon": "mdi-magnify" + }, null, 8, ["modelValue"]), + _createVNode(_component_v_btn, { + color: "primary", + onClick: openAddRuleProviderDialog + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[110] || (_cache[110] = [ + _createTextVNode("mdi-plus") + ])), + _: 1 + }), + _cache[111] || (_cache[111] = _createTextVNode(" 添加规则集合 ")) + ]), + _: 1 + }) + ]) + ]) + ]), + _createVNode(_component_v_data_table, { + headers: headersRuleProviders.value, + items: extraRuleProviders.value, + search: searchRuleProviders.value, + page: pageRulProviders.value, + "onUpdate:page": _cache[20] || (_cache[20] = $event => ((pageRulProviders).value = $event)), + "items-per-page": itemsPerPageRuleProviders.value, + "items-per-page-options": itemsPerPageOptions.value, + "item-key": "priority", + class: "elevation-1", + density: "compact" + }, { + item: _withCtx(({ item }) => [ + _createElementVNode("tr", null, [ + _createElementVNode("td", null, _toDisplayString(item.name), 1), + _createElementVNode("td", null, _toDisplayString(item.type), 1), + _createElementVNode("td", null, _toDisplayString(item.behavior), 1), + _createElementVNode("td", null, _toDisplayString(item.format), 1), + _createElementVNode("td", null, [ + _createVNode(_component_v_btn, { + icon: "", + size: "small", + color: "primary", + variant: "text", + onClick: $event => (editRuleProvider(item.name)) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, null, { + default: _withCtx(() => _cache[113] || (_cache[113] = [ + _createTextVNode("mdi-pencil") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick"]), + _createVNode(_component_v_btn, { + icon: "", + size: "small", + color: "error", + variant: "text", + onClick: $event => (deleteRuleProvider(item.name)) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, null, { + default: _withCtx(() => _cache[114] || (_cache[114] = [ + _createTextVNode("mdi-delete") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick"]) + ]) + ]) + ]), + bottom: _withCtx(() => [ + _createElementVNode("div", _hoisted_37, [ + _createElementVNode("div", _hoisted_38, [ + _createVNode(_component_v_pagination, { + modelValue: pageRulProviders.value, + "onUpdate:modelValue": _cache[18] || (_cache[18] = $event => ((pageRulProviders).value = $event)), + length: pageCountExtraRuleProviders.value, + class: "my-0", + "total-visible": 4, + rounded: "circle", + style: {"min-width":"300px"} + }, null, 8, ["modelValue", "length"]) + ]), + _createElementVNode("div", _hoisted_39, [ + _createVNode(_component_v_select, { + modelValue: itemsPerPageRuleProviders.value, + "onUpdate:modelValue": _cache[19] || (_cache[19] = $event => ((itemsPerPageRuleProviders).value = $event)), + items: itemsPerPageOptions.value, + label: "每页", + density: "compact", + "hide-details": "", + style: {"max-width":"150px"}, + variant: "outlined" + }, null, 8, ["modelValue", "items"]) + ]) + ]) + ]), + _: 1 + }, 8, ["headers", "items", "search", "page", "items-per-page", "items-per-page-options"]) ]) ]), _: 1 @@ -5287,7 +5578,7 @@ return (_ctx, _cache) => { }, 8, ["modelValue"]), _createVNode(_component_v_expansion_panels, { modelValue: expansionPanels.value, - "onUpdate:modelValue": _cache[19] || (_cache[19] = $event => ((expansionPanels).value = $event)) + "onUpdate:modelValue": _cache[23] || (_cache[23] = $event => ((expansionPanels).value = $event)) }, { default: _withCtx(() => [ _createVNode(_component_v_expansion_panel, null, { @@ -5295,12 +5586,12 @@ return (_ctx, _cache) => { _createVNode(_component_v_expansion_panel_title, null, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[94] || (_cache[94] = [ + default: _withCtx(() => _cache[115] || (_cache[115] = [ _createTextVNode("mdi-cloud-download") ])), _: 1 }), - _cache[95] || (_cache[95] = _createElementVNode("span", null, "订阅管理", -1)), + _cache[116] || (_cache[116] = _createElementVNode("span", null, "订阅管理", -1)), (subscriptionInfo.value.last_update) ? (_openBlock(), _createBlock(_component_v_chip, { key: 0, @@ -5334,7 +5625,7 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_text_field, { modelValue: subscriptionUrl.value, - "onUpdate:modelValue": _cache[18] || (_cache[18] = $event => ((subscriptionUrl).value = $event)), + "onUpdate:modelValue": _cache[22] || (_cache[22] = $event => ((subscriptionUrl).value = $event)), label: "订阅URL", placeholder: "https://example.com/clash-rules.txt", class: "mb-4", @@ -5348,12 +5639,12 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_card_text, null, { default: _withCtx(() => [ - _createElementVNode("div", _hoisted_33, [ - _cache[96] || (_cache[96] = _createElementVNode("span", null, "已用流量:", -1)), + _createElementVNode("div", _hoisted_40, [ + _cache[117] || (_cache[117] = _createElementVNode("span", null, "已用流量:", -1)), _createElementVNode("strong", null, _toDisplayString(formatBytes(subscriptionInfo.value.download + subscriptionInfo.value.upload)), 1) ]), - _createElementVNode("div", _hoisted_34, [ - _cache[97] || (_cache[97] = _createElementVNode("span", null, "剩余流量:", -1)), + _createElementVNode("div", _hoisted_41, [ + _cache[118] || (_cache[118] = _createElementVNode("span", null, "剩余流量:", -1)), _createElementVNode("strong", null, _toDisplayString(formatBytes(subscriptionInfo.value.total - subscriptionInfo.value.download)), 1) ]), _createVNode(_component_v_progress_linear, { @@ -5362,7 +5653,7 @@ return (_ctx, _cache) => { height: "10", class: "mb-2" }, null, 8, ["model-value", "color"]), - _createElementVNode("div", _hoisted_35, [ + _createElementVNode("div", _hoisted_42, [ _createElementVNode("span", null, "下载:" + _toDisplayString(formatBytes(subscriptionInfo.value.download)), 1), _createElementVNode("span", null, "上传:" + _toDisplayString(formatBytes(subscriptionInfo.value.upload)), 1), _createElementVNode("span", null, "总量:" + _toDisplayString(formatBytes(subscriptionInfo.value.total)), 1) @@ -5380,12 +5671,12 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[98] || (_cache[98] = [ + default: _withCtx(() => _cache[119] || (_cache[119] = [ _createTextVNode("mdi-cloud-sync") ])), _: 1 }), - _cache[99] || (_cache[99] = _createTextVNode(" 更新订阅 ")) + _cache[120] || (_cache[120] = _createTextVNode(" 更新订阅 ")) ]), _: 1 }, 8, ["loading"]) @@ -5406,9 +5697,9 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_card_text, { class: "text-subtitle-2" }, { default: _withCtx(() => [ - _cache[105] || (_cache[105] = _createElementVNode("div", { class: "text-h6 mb-2" }, "状态信息", -1)), + _cache[126] || (_cache[126] = _createElementVNode("div", { class: "text-h6 mb-2" }, "状态信息", -1)), _createElementVNode("div", null, [ - _cache[100] || (_cache[100] = _createElementVNode("strong", null, "状态: ", -1)), + _cache[121] || (_cache[121] = _createElementVNode("strong", null, "状态: ", -1)), _createVNode(_component_v_chip, { size: "small", color: status.value === 'running' ? 'success' : 'warning' @@ -5420,19 +5711,19 @@ return (_ctx, _cache) => { }, 8, ["color"]) ]), _createElementVNode("div", null, [ - _cache[101] || (_cache[101] = _createElementVNode("strong", null, "订阅配置规则数量:", -1)), + _cache[122] || (_cache[122] = _createElementVNode("strong", null, "订阅配置规则数量:", -1)), _createTextVNode(" " + _toDisplayString(subscriptionInfo.value.rule_size), 1) ]), _createElementVNode("div", null, [ - _cache[102] || (_cache[102] = _createElementVNode("strong", null, "置顶规则数量:", -1)), + _cache[123] || (_cache[123] = _createElementVNode("strong", null, "置顶规则数量:", -1)), _createTextVNode(" " + _toDisplayString(sortedRules.value.length), 1) ]), _createElementVNode("div", null, [ - _cache[103] || (_cache[103] = _createElementVNode("strong", null, "规则集规则数量:", -1)), + _cache[124] || (_cache[124] = _createElementVNode("strong", null, "规则集规则数量:", -1)), _createTextVNode(" " + _toDisplayString(sortedRulesetRules.value.length), 1) ]), _createElementVNode("div", null, [ - _cache[104] || (_cache[104] = _createElementVNode("strong", null, "最后更新:", -1)), + _cache[125] || (_cache[125] = _createElementVNode("strong", null, "最后更新:", -1)), _createTextVNode(" " + _toDisplayString(lastUpdated.value), 1) ]) ]), @@ -5450,8 +5741,8 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_card_text, { class: "text-subtitle-2" }, { default: _withCtx(() => [ - _createElementVNode("div", _hoisted_36, [ - _cache[108] || (_cache[108] = _createElementVNode("div", { class: "text-h6" }, "订阅链接", -1)), + _createElementVNode("div", _hoisted_43, [ + _cache[129] || (_cache[129] = _createElementVNode("div", { class: "text-h6" }, "订阅链接", -1)), (subUrl.value) ? (_openBlock(), _createBlock(_component_v_btn, { key: 0, @@ -5459,11 +5750,11 @@ return (_ctx, _cache) => { size: "small", variant: "text", color: "primary", - onClick: _cache[20] || (_cache[20] = $event => (copyToClipboard(subUrl.value))) + onClick: _cache[24] || (_cache[24] = $event => (copyToClipboard(subUrl.value))) }, { default: _withCtx(() => [ _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[106] || (_cache[106] = [ + default: _withCtx(() => _cache[127] || (_cache[127] = [ _createTextVNode("mdi-content-copy") ])), _: 1 @@ -5472,7 +5763,7 @@ return (_ctx, _cache) => { activator: "parent", location: "top" }, { - default: _withCtx(() => _cache[107] || (_cache[107] = [ + default: _withCtx(() => _cache[128] || (_cache[128] = [ _createTextVNode("复制链接") ])), _: 1 @@ -5483,14 +5774,14 @@ return (_ctx, _cache) => { : _createCommentVNode("", true) ]), (subUrl.value) - ? (_openBlock(), _createElementBlock("div", _hoisted_37, [ + ? (_openBlock(), _createElementBlock("div", _hoisted_44, [ _createElementVNode("a", { href: subUrl.value, target: "_blank", class: "text-primary" - }, _toDisplayString(subUrl.value), 9, _hoisted_38) + }, _toDisplayString(subUrl.value), 9, _hoisted_45) ])) - : (_openBlock(), _createElementBlock("div", _hoisted_39, "未配置订阅URL")) + : (_openBlock(), _createElementBlock("div", _hoisted_46, "未配置订阅URL")) ]), _: 1 }) @@ -5516,12 +5807,12 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[109] || (_cache[109] = [ + default: _withCtx(() => _cache[130] || (_cache[130] = [ _createTextVNode("mdi-refresh") ])), _: 1 }), - _cache[110] || (_cache[110] = _createTextVNode(" 刷新数据 ")) + _cache[131] || (_cache[131] = _createTextVNode(" 刷新数据 ")) ]), _: 1 }, 8, ["loading"]), @@ -5532,12 +5823,12 @@ return (_ctx, _cache) => { }, { default: _withCtx(() => [ _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[111] || (_cache[111] = [ + default: _withCtx(() => _cache[132] || (_cache[132] = [ _createTextVNode("mdi-cog") ])), _: 1 }), - _cache[112] || (_cache[112] = _createTextVNode(" 配置 ")) + _cache[133] || (_cache[133] = _createTextVNode(" 配置 ")) ]), _: 1 }) @@ -5546,7 +5837,7 @@ return (_ctx, _cache) => { }), _createVNode(_component_v_snackbar, { modelValue: snackbar.value.show, - "onUpdate:modelValue": _cache[21] || (_cache[21] = $event => ((snackbar.value.show) = $event)), + "onUpdate:modelValue": _cache[25] || (_cache[25] = $event => ((snackbar.value.show) = $event)), color: snackbar.value.color, location: "bottom", class: "mb-2" @@ -5561,101 +5852,112 @@ return (_ctx, _cache) => { }), _createVNode(_component_v_dialog, { modelValue: ruleDialog.value, - "onUpdate:modelValue": _cache[29] || (_cache[29] = $event => ((ruleDialog).value = $event)), + "onUpdate:modelValue": _cache[33] || (_cache[33] = $event => ((ruleDialog).value = $event)), "max-width": "600" }, { default: _withCtx(() => [ - _createVNode(_component_v_card, null, { + _createVNode(_component_v_form, { + ref_key: "ruleForm", + ref: ruleForm, + onSubmit: _withModifiers(saveRule, ["prevent"]) + }, { default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { + _createVNode(_component_v_card, null, { default: _withCtx(() => [ - _createTextVNode(_toDisplayString(editingPriority.value === null ? '添加规则' : '编辑规则'), 1) - ]), - _: 1 - }), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - _createVNode(_component_v_select, { - modelValue: newRule.value.type, - "onUpdate:modelValue": _cache[22] || (_cache[22] = $event => ((newRule.value.type) = $event)), - items: ruleTypes.value, - label: "规则类型", - required: "", - class: "mb-4" - }, null, 8, ["modelValue", "items"]), - (newRule.value.type !== 'RULE-SET') - ? (_openBlock(), _createBlock(_component_v_text_field, { - key: 0, - modelValue: newRule.value.payload, - "onUpdate:modelValue": _cache[23] || (_cache[23] = $event => ((newRule.value.payload) = $event)), - label: "内容", - required: "", - class: "mb-4" - }, null, 8, ["modelValue"])) - : (_openBlock(), _createBlock(_component_v_select, { - key: 1, - modelValue: newRule.value.payload, - "onUpdate:modelValue": _cache[24] || (_cache[24] = $event => ((newRule.value.payload) = $event)), - items: ruleProviderNames.value, - label: "选择规则集", - required: "", - class: "mb-4" - }, null, 8, ["modelValue", "items"])), - _createVNode(_component_v_select, { - modelValue: newRule.value.action, - "onUpdate:modelValue": _cache[25] || (_cache[25] = $event => ((newRule.value.action) = $event)), - items: actions.value, - label: "出站", - required: "", - class: "mb-4" - }, null, 8, ["modelValue", "items"]), - (showAdditionalParams.value) - ? (_openBlock(), _createBlock(_component_v_select, { - key: 2, - modelValue: newRule.value.additional_params, - "onUpdate:modelValue": _cache[26] || (_cache[26] = $event => ((newRule.value.additional_params) = $event)), - label: "附加参数", - items: additionalParamOptions.value, - clearable: "", - hint: "可选参数", - "persistent-hint": "", - class: "mb-4" - }, null, 8, ["modelValue", "items"])) - : _createCommentVNode("", true), - (editingPriority.value !== null) - ? (_openBlock(), _createBlock(_component_v_text_field, { - key: 3, - modelValue: newRule.value.priority, - "onUpdate:modelValue": _cache[27] || (_cache[27] = $event => ((newRule.value.priority) = $event)), - modelModifiers: { number: true }, - type: "number", - label: "优先级", - hint: "数字越小优先级越高", - "persistent-hint": "" - }, null, 8, ["modelValue"])) - : _createCommentVNode("", true) - ]), - _: 1 - }), - _createVNode(_component_v_card_actions, null, { - default: _withCtx(() => [ - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - color: "secondary", - onClick: _cache[28] || (_cache[28] = $event => (ruleDialog.value = false)) - }, { - default: _withCtx(() => _cache[113] || (_cache[113] = [ - _createTextVNode("取消") - ])), + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(editingPriority.value === null ? '添加规则' : '编辑规则'), 1) + ]), _: 1 }), - _createVNode(_component_v_btn, { - color: "primary", - onClick: saveRule - }, { - default: _withCtx(() => _cache[114] || (_cache[114] = [ - _createTextVNode("保存") - ])), + _createVNode(_component_v_card_text, null, { + default: _withCtx(() => [ + _createVNode(_component_v_select, { + modelValue: newRule.value.type, + "onUpdate:modelValue": _cache[26] || (_cache[26] = $event => ((newRule.value.type) = $event)), + items: ruleTypes.value, + label: "规则类型", + required: "", + class: "mb-4" + }, null, 8, ["modelValue", "items"]), + (newRule.value.type !== 'RULE-SET') + ? (_openBlock(), _createBlock(_component_v_text_field, { + key: 0, + modelValue: newRule.value.payload, + "onUpdate:modelValue": _cache[27] || (_cache[27] = $event => ((newRule.value.payload) = $event)), + label: "内容", + required: "", + rules: payloadRules.value, + class: "mb-4" + }, null, 8, ["modelValue", "rules"])) + : (_openBlock(), _createBlock(_component_v_select, { + key: 1, + modelValue: newRule.value.payload, + "onUpdate:modelValue": _cache[28] || (_cache[28] = $event => ((newRule.value.payload) = $event)), + items: ruleProviderNames.value, + label: "选择规则集", + required: "", + rules: [(v) => !!v || '请选择一个有效的规则集',], + class: "mb-4" + }, null, 8, ["modelValue", "items", "rules"])), + _createVNode(_component_v_select, { + modelValue: newRule.value.action, + "onUpdate:modelValue": _cache[29] || (_cache[29] = $event => ((newRule.value.action) = $event)), + items: actions.value, + label: "出站", + required: "", + class: "mb-4" + }, null, 8, ["modelValue", "items"]), + (showAdditionalParams.value) + ? (_openBlock(), _createBlock(_component_v_select, { + key: 2, + modelValue: newRule.value.additional_params, + "onUpdate:modelValue": _cache[30] || (_cache[30] = $event => ((newRule.value.additional_params) = $event)), + label: "附加参数", + items: additionalParamOptions.value, + clearable: "", + hint: "可选参数", + "persistent-hint": "", + class: "mb-4" + }, null, 8, ["modelValue", "items"])) + : _createCommentVNode("", true), + (editingPriority.value !== null) + ? (_openBlock(), _createBlock(_component_v_text_field, { + key: 3, + modelValue: newRule.value.priority, + "onUpdate:modelValue": _cache[31] || (_cache[31] = $event => ((newRule.value.priority) = $event)), + modelModifiers: { number: true }, + type: "number", + label: "优先级", + hint: "数字越小优先级越高", + "persistent-hint": "" + }, null, 8, ["modelValue"])) + : _createCommentVNode("", true) + ]), + _: 1 + }), + _createVNode(_component_v_card_actions, null, { + default: _withCtx(() => [ + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + color: "secondary", + onClick: _cache[32] || (_cache[32] = $event => (ruleDialog.value = false)) + }, { + default: _withCtx(() => _cache[134] || (_cache[134] = [ + _createTextVNode("取消") + ])), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "primary", + type: "submit" + }, { + default: _withCtx(() => _cache[135] || (_cache[135] = [ + _createTextVNode("保存") + ])), + _: 1 + }) + ]), _: 1 }) ]), @@ -5663,314 +5965,323 @@ return (_ctx, _cache) => { }) ]), _: 1 - }) + }, 512) ]), _: 1 }, 8, ["modelValue"]), _createVNode(_component_v_dialog, { modelValue: proxyGroupDialog.value, - "onUpdate:modelValue": _cache[47] || (_cache[47] = $event => ((proxyGroupDialog).value = $event)), + "onUpdate:modelValue": _cache[51] || (_cache[51] = $event => ((proxyGroupDialog).value = $event)), "max-width": "600" }, { default: _withCtx(() => [ - _createVNode(_component_v_card, null, { + _createVNode(_component_v_form, { + ref_key: "proxyGroupsForm", + ref: proxyGroupsForm, + onSubmit: _withModifiers(saveProxyGroups, ["prevent"]) + }, { default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { - default: _withCtx(() => _cache[115] || (_cache[115] = [ - _createTextVNode(_toDisplayString('添加代理组')) - ])), - _: 1 - }), - _createVNode(_component_v_card_text, null, { + _createVNode(_component_v_card, null, { default: _withCtx(() => [ - _createVNode(_component_v_row, null, { + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => _cache[136] || (_cache[136] = [ + _createTextVNode(_toDisplayString('添加代理组')) + ])), + _: 1 + }), + _createVNode(_component_v_card_text, null, { default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { + _createVNode(_component_v_row, null, { default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: newProxyGroup.value.name, - "onUpdate:modelValue": _cache[30] || (_cache[30] = $event => ((newProxyGroup.value.name) = $event)), - label: "name", - required: "", + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: newProxyGroup.value.name, + "onUpdate:modelValue": _cache[34] || (_cache[34] = $event => ((newProxyGroup.value.name) = $event)), + label: "name", + required: "", + class: "mb-4", + hint: "策略组的名字", + rules: [v => !!v || 'Name不能为空'] + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_select, { + modelValue: newProxyGroup.value.type, + "onUpdate:modelValue": _cache[35] || (_cache[35] = $event => ((newProxyGroup.value.type) = $event)), + label: "type", + items: proxyGroupTypes.value, + required: "", + hint: "策略组的类型", + class: "mb-4" + }, null, 8, ["modelValue", "items"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_select, { + modelValue: newProxyGroup.value.proxies, + "onUpdate:modelValue": _cache[36] || (_cache[36] = $event => ((newProxyGroup.value.proxies) = $event)), + label: "proxies", + items: actions.value, + multiple: "", + chips: "", + class: "mb-4", + hint: "引入出站代理或其他策略组" + }, null, 8, ["modelValue", "items"]), + _createVNode(_component_v_text_field, { + modelValue: newProxyGroup.value.url, + "onUpdate:modelValue": _cache[37] || (_cache[37] = $event => ((newProxyGroup.value.url) = $event)), + label: "url", + class: "mb-4", + hint: "健康检查测试地址", + rules: urlRules, + clearable: "" + }, null, 8, ["modelValue"]), + (newProxyGroup.value.type === 'url-test') + ? (_openBlock(), _createBlock(_component_v_text_field, { + key: 0, + modelValue: newProxyGroup.value.tolerance, + "onUpdate:modelValue": _cache[38] || (_cache[38] = $event => ((newProxyGroup.value.tolerance) = $event)), + modelModifiers: { number: true }, + label: "tolerance (ms)", + variant: "outlined", + type: "number", + min: "10", class: "mb-4", - hint: "策略组的名字", - rules: [v => !!v || 'Name不能为空'] - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_select, { - modelValue: newProxyGroup.value.type, - "onUpdate:modelValue": _cache[31] || (_cache[31] = $event => ((newProxyGroup.value.type) = $event)), - label: "type", - items: proxyGroupTypes.value, - required: "", - hint: "策略组的类型", - class: "mb-4" - }, null, 8, ["modelValue", "items"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_select, { - modelValue: newProxyGroup.value.proxies, - "onUpdate:modelValue": _cache[32] || (_cache[32] = $event => ((newProxyGroup.value.proxies) = $event)), - label: "proxies", - items: actions.value, - multiple: "", - chips: "", - class: "mb-4", - hint: "引入出站代理或其他策略组" - }, null, 8, ["modelValue", "items"]), - _createVNode(_component_v_text_field, { - modelValue: newProxyGroup.value.url, - "onUpdate:modelValue": _cache[33] || (_cache[33] = $event => ((newProxyGroup.value.url) = $event)), - label: "url", - class: "mb-4", - hint: "健康检查测试地址", - rules: urlRules, - clearable: "" - }, null, 8, ["modelValue"]), - (newProxyGroup.value.type === 'url-test') - ? (_openBlock(), _createBlock(_component_v_text_field, { - key: 0, - modelValue: newProxyGroup.value.tolerance, - "onUpdate:modelValue": _cache[34] || (_cache[34] = $event => ((newProxyGroup.value.tolerance) = $event)), - modelModifiers: { number: true }, - label: "tolerance (ms)", - variant: "outlined", - type: "number", - min: "10", + hint: "节点切换容差", + rules: [v => v >=10 || '检查间隔需不小于0'] + }, null, 8, ["modelValue", "rules"])) + : _createCommentVNode("", true), + (newProxyGroup.value.type === 'load-balance') + ? (_openBlock(), _createBlock(_component_v_select, { + key: 1, + modelValue: newProxyGroup.value.strategy, + "onUpdate:modelValue": _cache[39] || (_cache[39] = $event => ((newProxyGroup.value.strategy) = $event)), + label: "strategy", + items: strategyTypes.value, + class: "mb-4", + hint: "负载均衡策略" + }, null, 8, ["modelValue", "items"])) + : _createCommentVNode("", true), + _createVNode(_component_v_text_field, { + modelValue: newProxyGroup.value.filter, + "onUpdate:modelValue": _cache[40] || (_cache[40] = $event => ((newProxyGroup.value.filter) = $event)), + label: "filter", class: "mb-4", - hint: "节点切换容差", - rules: [v => v >=10 || '检查间隔需不小于0'] - }, null, 8, ["modelValue", "rules"])) - : _createCommentVNode("", true), - (newProxyGroup.value.type === 'load-balance') - ? (_openBlock(), _createBlock(_component_v_select, { - key: 1, - modelValue: newProxyGroup.value.strategy, - "onUpdate:modelValue": _cache[35] || (_cache[35] = $event => ((newProxyGroup.value.strategy) = $event)), - label: "strategy", - items: strategyTypes.value, + hint: "筛选满足关键词或正则表达式的节点" + }, null, 8, ["modelValue"]), + _createVNode(_component_v_text_field, { + modelValue: newProxyGroup.value['exclude-filter'], + "onUpdate:modelValue": _cache[41] || (_cache[41] = $event => ((newProxyGroup.value['exclude-filter']) = $event)), + label: "exclude-filter", class: "mb-4", - hint: "负载均衡策略" - }, null, 8, ["modelValue", "items"])) - : _createCommentVNode("", true), - _createVNode(_component_v_text_field, { - modelValue: newProxyGroup.value.filter, - "onUpdate:modelValue": _cache[36] || (_cache[36] = $event => ((newProxyGroup.value.filter) = $event)), - label: "filter", - class: "mb-4", - hint: "筛选满足关键词或正则表达式的节点" - }, null, 8, ["modelValue"]), - _createVNode(_component_v_text_field, { - modelValue: newProxyGroup.value['exclude-filter'], - "onUpdate:modelValue": _cache[37] || (_cache[37] = $event => ((newProxyGroup.value['exclude-filter']) = $event)), - label: "exclude-filter", - class: "mb-4", - hint: "排除满足关键词或正则表达式的节点" - }, null, 8, ["modelValue"]), - _createVNode(_component_v_text_field, { - modelValue: newProxyGroup.value['exclude-type'], - "onUpdate:modelValue": _cache[38] || (_cache[38] = $event => ((newProxyGroup.value['exclude-type']) = $event)), - label: "exclude-type", - class: "mb-4", - hint: "不支持正则表达式,通过 | 分割" - }, null, 8, ["modelValue"]), - _createVNode(_component_v_text_field, { - modelValue: newProxyGroup.value['expected-status'], - "onUpdate:modelValue": _cache[39] || (_cache[39] = $event => ((newProxyGroup.value['expected-status']) = $event)), - label: "expected-status", - class: "mb-4", - hint: "健康检查时期望的 HTTP 响应状态码" - }, null, 8, ["modelValue"]), - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { + hint: "排除满足关键词或正则表达式的节点" + }, null, 8, ["modelValue"]), + _createVNode(_component_v_text_field, { + modelValue: newProxyGroup.value['exclude-type'], + "onUpdate:modelValue": _cache[42] || (_cache[42] = $event => ((newProxyGroup.value['exclude-type']) = $event)), + label: "exclude-type", + class: "mb-4", + hint: "不支持正则表达式,通过 | 分割" + }, null, 8, ["modelValue"]), + _createVNode(_component_v_text_field, { + modelValue: newProxyGroup.value['expected-status'], + "onUpdate:modelValue": _cache[43] || (_cache[43] = $event => ((newProxyGroup.value['expected-status']) = $event)), + label: "expected-status", + class: "mb-4", + hint: "健康检查时期望的 HTTP 响应状态码" + }, null, 8, ["modelValue"]), + _createVNode(_component_v_row, null, { default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: newProxyGroup.value.interval, - "onUpdate:modelValue": _cache[40] || (_cache[40] = $event => ((newProxyGroup.value.interval) = $event)), - modelModifiers: { number: true }, - label: "interval (s)", - variant: "outlined", - type: "number", - min: "0", - hint: "健康检查间隔,如不为 0 则启用定时测试", - "persistent-hint": "", - rules: [v => v > -1 || '检查间隔需不小于0'] + _createVNode(_component_v_col, { + cols: "12", + md: "6" }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "warning" }, { - default: _withCtx(() => _cache[116] || (_cache[116] = [ - _createTextVNode("mdi-timer") - ])), + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: newProxyGroup.value.interval, + "onUpdate:modelValue": _cache[44] || (_cache[44] = $event => ((newProxyGroup.value.interval) = $event)), + modelModifiers: { number: true }, + label: "interval (s)", + variant: "outlined", + type: "number", + min: "0", + hint: "健康检查间隔,如不为 0 则启用定时测试", + "persistent-hint": "", + rules: [v => v > -1 || '检查间隔需不小于0'] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "warning" }, { + default: _withCtx(() => _cache[137] || (_cache[137] = [ + _createTextVNode("mdi-timer") + ])), + _: 1 + }) + ]), _: 1 - }) + }, 8, ["modelValue", "rules"]) ]), _: 1 - }, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: newProxyGroup.value.timeout, - "onUpdate:modelValue": _cache[41] || (_cache[41] = $event => ((newProxyGroup.value.timeout) = $event)), - modelModifiers: { number: true }, - label: "timeout (ms)", - variant: "outlined", - type: "number", - min: "1", - hint: "请求的超时时间", - "persistent-hint": "", - rules: [v => v > 0 || '超时时间必须大于0'] + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "warning" }, { - default: _withCtx(() => _cache[117] || (_cache[117] = [ - _createTextVNode("mdi-timer") - ])), + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: newProxyGroup.value.timeout, + "onUpdate:modelValue": _cache[45] || (_cache[45] = $event => ((newProxyGroup.value.timeout) = $event)), + modelModifiers: { number: true }, + label: "timeout (ms)", + variant: "outlined", + type: "number", + min: "1", + hint: "请求的超时时间", + "persistent-hint": "", + rules: [v => v > 0 || '超时时间必须大于0'] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "warning" }, { + default: _withCtx(() => _cache[138] || (_cache[138] = [ + _createTextVNode("mdi-timer") + ])), + _: 1 + }) + ]), _: 1 - }) + }, 8, ["modelValue", "rules"]) ]), _: 1 - }, 8, ["modelValue", "rules"]) + }) + ]), + _: 1 + }), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: newProxyGroup.value['lazy'], + "onUpdate:modelValue": _cache[46] || (_cache[46] = $event => ((newProxyGroup.value['lazy']) = $event)), + label: "lazy", + inset: "", + hint: "未选择到当前策略组时,不进行测试", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: newProxyGroup.value['disable-udp'], + "onUpdate:modelValue": _cache[47] || (_cache[47] = $event => ((newProxyGroup.value['disable-udp']) = $event)), + label: "disable-udp", + inset: "", + hint: "禁用该策略组的UDP", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: newProxyGroup.value['include-all'], + "onUpdate:modelValue": _cache[48] || (_cache[48] = $event => ((newProxyGroup.value['include-all']) = $event)), + label: "include-all", + inset: "", + hint: "引入所有出站代理以及代理集合", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: newProxyGroup.value['include-all-proxies'], + "onUpdate:modelValue": _cache[49] || (_cache[49] = $event => ((newProxyGroup.value['include-all-proxies']) = $event)), + label: "include-all-proxies", + inset: "", + hint: "引入所有出站代理", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) ]), _: 1 }) ]), _: 1 }), - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: newProxyGroup.value['lazy'], - "onUpdate:modelValue": _cache[42] || (_cache[42] = $event => ((newProxyGroup.value['lazy']) = $event)), - label: "lazy", - inset: "", - hint: "未选择到当前策略组时,不进行测试", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: newProxyGroup.value['disable-udp'], - "onUpdate:modelValue": _cache[43] || (_cache[43] = $event => ((newProxyGroup.value['disable-udp']) = $event)), - label: "disable-udp", - inset: "", - hint: "禁用该策略组的UDP", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: newProxyGroup.value['include-all'], - "onUpdate:modelValue": _cache[44] || (_cache[44] = $event => ((newProxyGroup.value['include-all']) = $event)), - label: "include-all", - inset: "", - hint: "引入所有出站代理以及代理集合", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: newProxyGroup.value['include-all-proxies'], - "onUpdate:modelValue": _cache[45] || (_cache[45] = $event => ((newProxyGroup.value['include-all-proxies']) = $event)), - label: "include-all-proxies", - inset: "", - hint: "引入所有出站代理", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_alert, { - type: "info", - text: "", - class: "mb-6", - variant: "tonal" - }, { - default: _withCtx(() => _cache[118] || (_cache[118] = [ - _createTextVNode(" 参考"), - _createElementVNode("a", { - href: "https://wiki.metacubex.one/config/proxy-groups/", - target: "_blank" - }, "虚空终端 Docs", -1) - ])), - _: 1 - }), - _createVNode(_component_v_card_actions, null, { - default: _withCtx(() => [ - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - color: "secondary", - onClick: _cache[46] || (_cache[46] = $event => (proxyGroupDialog.value = false)) + _createVNode(_component_v_alert, { + type: "info", + text: "", + class: "mb-6", + variant: "tonal" }, { - default: _withCtx(() => _cache[119] || (_cache[119] = [ - _createTextVNode("取消") + default: _withCtx(() => _cache[139] || (_cache[139] = [ + _createTextVNode(" 参考"), + _createElementVNode("a", { + href: "https://wiki.metacubex.one/config/proxy-groups/", + target: "_blank" + }, "虚空终端 Docs", -1) ])), _: 1 }), - _createVNode(_component_v_btn, { - color: "primary", - onClick: saveProxyGroups - }, { - default: _withCtx(() => _cache[120] || (_cache[120] = [ - _createTextVNode("保存") - ])), + _createVNode(_component_v_card_actions, null, { + default: _withCtx(() => [ + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + color: "secondary", + onClick: _cache[50] || (_cache[50] = $event => (proxyGroupDialog.value = false)) + }, { + default: _withCtx(() => _cache[140] || (_cache[140] = [ + _createTextVNode("取消") + ])), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "primary", + type: "submit" + }, { + default: _withCtx(() => _cache[141] || (_cache[141] = [ + _createTextVNode("保存") + ])), + _: 1 + }) + ]), _: 1 }) ]), @@ -5978,20 +6289,20 @@ return (_ctx, _cache) => { }) ]), _: 1 - }) + }, 512) ]), _: 1 }, 8, ["modelValue"]), _createVNode(_component_v_dialog, { modelValue: yamlDialog.value, - "onUpdate:modelValue": _cache[49] || (_cache[49] = $event => ((yamlDialog).value = $event)), + "onUpdate:modelValue": _cache[53] || (_cache[53] = $event => ((yamlDialog).value = $event)), "max-width": "800" }, { default: _withCtx(() => [ _createVNode(_component_v_card, null, { default: _withCtx(() => [ _createVNode(_component_v_card_title, { class: "headline" }, { - default: _withCtx(() => _cache[121] || (_cache[121] = [ + default: _withCtx(() => _cache[142] || (_cache[142] = [ _createTextVNode("YAML配置") ])), _: 1 @@ -6009,16 +6320,16 @@ return (_ctx, _cache) => { color: "primary", onClick: _ctx.copyYaml }, { - default: _withCtx(() => _cache[122] || (_cache[122] = [ + default: _withCtx(() => _cache[143] || (_cache[143] = [ _createTextVNode("复制") ])), _: 1 }, 8, ["onClick"]), _createVNode(_component_v_btn, { color: "primary", - onClick: _cache[48] || (_cache[48] = $event => (yamlDialog.value = false)) + onClick: _cache[52] || (_cache[52] = $event => (yamlDialog.value = false)) }, { - default: _withCtx(() => _cache[123] || (_cache[123] = [ + default: _withCtx(() => _cache[144] || (_cache[144] = [ _createTextVNode("关闭") ])), _: 1 @@ -6034,14 +6345,14 @@ return (_ctx, _cache) => { }, 8, ["modelValue"]), _createVNode(_component_v_dialog, { modelValue: importRuleDialog.value, - "onUpdate:modelValue": _cache[53] || (_cache[53] = $event => ((importRuleDialog).value = $event)), + "onUpdate:modelValue": _cache[57] || (_cache[57] = $event => ((importRuleDialog).value = $event)), "max-width": "600" }, { default: _withCtx(() => [ _createVNode(_component_v_card, null, { default: _withCtx(() => [ _createVNode(_component_v_card_title, null, { - default: _withCtx(() => _cache[124] || (_cache[124] = [ + default: _withCtx(() => _cache[145] || (_cache[145] = [ _createTextVNode("导入规则") ])), _: 1 @@ -6050,7 +6361,7 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_select, { modelValue: importRules.value.type, - "onUpdate:modelValue": _cache[50] || (_cache[50] = $event => ((importRules.value.type) = $event)), + "onUpdate:modelValue": _cache[54] || (_cache[54] = $event => ((importRules.value.type) = $event)), items: importRuleTypes, label: "内容格式", required: "", @@ -6058,7 +6369,7 @@ return (_ctx, _cache) => { }, null, 8, ["modelValue"]), _createVNode(_component_v_textarea, { modelValue: importRules.value.payload, - "onUpdate:modelValue": _cache[51] || (_cache[51] = $event => ((importRules.value.payload) = $event)), + "onUpdate:modelValue": _cache[55] || (_cache[55] = $event => ((importRules.value.payload) = $event)), label: "内容", required: "", placeholder: "rules: []", @@ -6073,7 +6384,7 @@ return (_ctx, _cache) => { class: "mb-4", variant: "tonal" }, { - default: _withCtx(() => _cache[125] || (_cache[125] = [ + default: _withCtx(() => _cache[146] || (_cache[146] = [ _createTextVNode(" 请输入 Clash 规则中的 "), _createElementVNode("strong", null, "rules", -1), _createTextVNode(" 字段,例如:"), @@ -6094,9 +6405,9 @@ return (_ctx, _cache) => { _createVNode(_component_v_spacer), _createVNode(_component_v_btn, { color: "secondary", - onClick: _cache[52] || (_cache[52] = $event => (importRuleDialog.value = false)) + onClick: _cache[56] || (_cache[56] = $event => (importRuleDialog.value = false)) }, { - default: _withCtx(() => _cache[126] || (_cache[126] = [ + default: _withCtx(() => _cache[147] || (_cache[147] = [ _createTextVNode("取消") ])), _: 1 @@ -6105,7 +6416,7 @@ return (_ctx, _cache) => { color: "primary", onClick: importRule }, { - default: _withCtx(() => _cache[127] || (_cache[127] = [ + default: _withCtx(() => _cache[148] || (_cache[148] = [ _createTextVNode("导入") ])), _: 1 @@ -6121,14 +6432,14 @@ return (_ctx, _cache) => { }, 8, ["modelValue"]), _createVNode(_component_v_dialog, { modelValue: importExtraProxiesDialog.value, - "onUpdate:modelValue": _cache[57] || (_cache[57] = $event => ((importExtraProxiesDialog).value = $event)), + "onUpdate:modelValue": _cache[61] || (_cache[61] = $event => ((importExtraProxiesDialog).value = $event)), "max-width": "600" }, { default: _withCtx(() => [ _createVNode(_component_v_card, null, { default: _withCtx(() => [ _createVNode(_component_v_card_title, null, { - default: _withCtx(() => _cache[128] || (_cache[128] = [ + default: _withCtx(() => _cache[149] || (_cache[149] = [ _createTextVNode("导入节点") ])), _: 1 @@ -6137,7 +6448,7 @@ return (_ctx, _cache) => { default: _withCtx(() => [ _createVNode(_component_v_select, { modelValue: importExtraProxies.value.type, - "onUpdate:modelValue": _cache[54] || (_cache[54] = $event => ((importExtraProxies.value.type) = $event)), + "onUpdate:modelValue": _cache[58] || (_cache[58] = $event => ((importExtraProxies.value.type) = $event)), items: importProxiesTypes, label: "内容格式", required: "", @@ -6145,7 +6456,7 @@ return (_ctx, _cache) => { }, null, 8, ["modelValue"]), _createVNode(_component_v_textarea, { modelValue: importExtraProxies.value.payload, - "onUpdate:modelValue": _cache[55] || (_cache[55] = $event => ((importExtraProxies.value.payload) = $event)), + "onUpdate:modelValue": _cache[59] || (_cache[59] = $event => ((importExtraProxies.value.payload) = $event)), label: "内容", required: "", placeholder: "proxies: []", @@ -6160,7 +6471,7 @@ return (_ctx, _cache) => { class: "mb-4", variant: "tonal" }, { - default: _withCtx(() => _cache[129] || (_cache[129] = [ + default: _withCtx(() => _cache[150] || (_cache[150] = [ _createTextVNode(" 请输入 Clash 规则中的 "), _createElementVNode("strong", null, "proxies", -1), _createTextVNode(" 字段,例如:"), @@ -6183,9 +6494,9 @@ return (_ctx, _cache) => { _createVNode(_component_v_spacer), _createVNode(_component_v_btn, { color: "secondary", - onClick: _cache[56] || (_cache[56] = $event => (importExtraProxiesDialog.value = false)) + onClick: _cache[60] || (_cache[60] = $event => (importExtraProxiesDialog.value = false)) }, { - default: _withCtx(() => _cache[130] || (_cache[130] = [ + default: _withCtx(() => _cache[151] || (_cache[151] = [ _createTextVNode("取消") ])), _: 1 @@ -6194,7 +6505,7 @@ return (_ctx, _cache) => { color: "primary", onClick: importExtraProxiesFun }, { - default: _withCtx(() => _cache[131] || (_cache[131] = [ + default: _withCtx(() => _cache[152] || (_cache[152] = [ _createTextVNode("导入") ])), _: 1 @@ -6207,12 +6518,181 @@ return (_ctx, _cache) => { }) ]), _: 1 + }, 8, ["modelValue"]), + _createVNode(_component_v_dialog, { + modelValue: ruleProviderDialog.value, + "onUpdate:modelValue": _cache[72] || (_cache[72] = $event => ((ruleProviderDialog).value = $event)), + "max-width": "600" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_form, { + ref_key: "ruleProvidersForm", + ref: ruleProvidersForm, + onSubmit: _withModifiers(saveRuleProvider, ["prevent"]) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_card, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(editingRuleProviderName.value === null ? '添加规则集合' : '编辑规则集合'), 1) + ]), + _: 1 + }), + _createVNode(_component_v_card_text, null, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: newRuleProvider.value.name, + "onUpdate:modelValue": _cache[62] || (_cache[62] = $event => ((newRuleProvider.value.name) = $event)), + label: "name", + required: "", + rules: [v => !!v || '名称不能为空'], + class: "mb-4" + }, null, 8, ["modelValue", "rules"]), + _createVNode(_component_v_select, { + modelValue: newRuleProvider.value.type, + "onUpdate:modelValue": _cache[63] || (_cache[63] = $event => ((newRuleProvider.value.type) = $event)), + items: ruleProviderTypes, + label: "type", + required: "", + rules: [v => !!v || '类型不能为空'], + class: "mb-4" + }, null, 8, ["modelValue", "rules"]), + (newRuleProvider.value.type === 'http') + ? (_openBlock(), _createBlock(_component_v_text_field, { + key: 0, + modelValue: newRuleProvider.value.url, + "onUpdate:modelValue": _cache[64] || (_cache[64] = $event => ((newRuleProvider.value.url) = $event)), + label: "url", + required: "", + rules: [(v) => !!v || 'URL 不能为空', (v) => isValidUrl(v) || '请输入有效的 URL',], + class: "mb-4", + hint: "当类型为 http 时必须配置" + }, null, 8, ["modelValue", "rules"])) + : _createCommentVNode("", true), + (newRuleProvider.value.type === 'file') + ? (_openBlock(), _createBlock(_component_v_text_field, { + key: 1, + modelValue: newRuleProvider.value.path, + "onUpdate:modelValue": _cache[65] || (_cache[65] = $event => ((newRuleProvider.value.path) = $event)), + label: "path", + required: "", + rules: [v => !!v || '当类型为文件时,路径不能为空'], + class: "mb-4", + hint: "文件路径,必须是唯一的" + }, null, 8, ["modelValue", "rules"])) + : _createCommentVNode("", true), + _createVNode(_component_v_text_field, { + modelValue: newRuleProvider.value.interval, + "onUpdate:modelValue": _cache[66] || (_cache[66] = $event => ((newRuleProvider.value.interval) = $event)), + modelModifiers: { number: true }, + label: "interval", + class: "mb-4", + type: "number", + min: "0", + suffix: "秒", + hint: "Provider 的更新间隔", + rules: [v => (v === null || v === undefined || v >= 0) || '更新间隔不能为负数'] + }, null, 8, ["modelValue", "rules"]), + _createVNode(_component_v_select, { + modelValue: newRuleProvider.value.behavior, + "onUpdate:modelValue": _cache[67] || (_cache[67] = $event => ((newRuleProvider.value.behavior) = $event)), + items: ruleProviderBehaviorTypes, + label: "behavior", + class: "mb-4", + hint: "对应不同格式的 rule-provider 文件" + }, null, 8, ["modelValue"]), + _createVNode(_component_v_select, { + modelValue: newRuleProvider.value.format, + "onUpdate:modelValue": _cache[68] || (_cache[68] = $event => ((newRuleProvider.value.format) = $event)), + items: ruleProviderFormatTypes, + label: "format", + class: "mb-4", + hint: "mrs目前 behavior 仅支持 domain/ipcidr" + }, null, 8, ["modelValue"]), + _createVNode(_component_v_text_field, { + modelValue: newRuleProvider.value['size-limit'], + "onUpdate:modelValue": _cache[69] || (_cache[69] = $event => ((newRuleProvider.value['size-limit']) = $event)), + modelModifiers: { number: true }, + label: "size-limit", + class: "mb-4", + type: "number", + min: "0", + suffix: "byte(s)", + hint: "可下载文件的最大大小,0 表示无限制", + rules: [v => (v === null || v === undefined || v >= 0) || '大小限制不能为负数'] + }, null, 8, ["modelValue", "rules"]), + (newRuleProvider.value.type === 'inline') + ? (_openBlock(), _createBlock(_component_v_combobox, { + key: 2, + modelValue: newRuleProvider.value.payload, + "onUpdate:modelValue": _cache[70] || (_cache[70] = $event => ((newRuleProvider.value.payload) = $event)), + multiple: "", + chips: "", + "closable-chips": "", + clearable: "", + label: "payload", + required: "", + rules: [v => !!v || '当类型为 inline 时,内容不能为空'], + class: "mb-4", + hint: "当类型为 inline 时才有效,按回车确认输入", + row: "" + }, { + chip: _withCtx(({ props, item }) => [ + _createVNode(_component_v_chip, _mergeProps(props, { + closable: "", + size: "small" + }), { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(item.value), 1) + ]), + _: 2 + }, 1040) + ]), + _: 1 + }, 8, ["modelValue", "rules"])) + : _createCommentVNode("", true) + ]), + _: 1 + }), + _createVNode(_component_v_card_actions, null, { + default: _withCtx(() => [ + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + color: "secondary", + onClick: _cache[71] || (_cache[71] = $event => (ruleProviderDialog.value = false)) + }, { + default: _withCtx(() => _cache[153] || (_cache[153] = [ + _createTextVNode("取消") + ])), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "primary", + type: "submit" + }, { + default: _withCtx(() => _cache[154] || (_cache[154] = [ + _createTextVNode("保存") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 512) + ]), + _: 1 }, 8, ["modelValue"]) ])) } } }; -const PageComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-783fd7ef"]]); +const PageComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-455476d4"]]); export { PageComponent as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-_CExF_DI.css b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Djrrbsow.css similarity index 70% rename from plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-_CExF_DI.css rename to plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Djrrbsow.css index e8b2303..fef62d6 100644 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-_CExF_DI.css +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Djrrbsow.css @@ -1,14 +1,14 @@ -.plugin-page[data-v-783fd7ef] { +.plugin-page[data-v-455476d4] { max-width: 1200px; margin: 0 auto; } /* 使卡片等宽并适应移动端 */ -.d-flex.flex-wrap[data-v-783fd7ef] { +.d-flex.flex-wrap[data-v-455476d4] { gap: 16px; } -.url-display[data-v-783fd7ef] { +.url-display[data-v-455476d4] { word-break: break-all; padding: 8px; background: rgba(0, 0, 0, 0.05); @@ -17,19 +17,19 @@ /* 移动端堆叠布局 */ @media (max-width: 768px) { -.d-flex.flex-wrap[data-v-783fd7ef] { +.d-flex.flex-wrap[data-v-455476d4] { flex-direction: column; } } /* Add visual distinction between sections */ -.ruleset-section[data-v-783fd7ef] { +.ruleset-section[data-v-455476d4] { border: 1px solid #e0e0e0; border-radius: 4px; padding: 16px; background-color: #f5f5f5; } -.top-section[data-v-783fd7ef] { +.top-section[data-v-455476d4] { border: 1px solid #e0e0e0; border-radius: 4px; padding: 16px; @@ -37,12 +37,12 @@ } /* Optional: Add different border colors to further distinguish */ -.ruleset-section[data-v-783fd7ef] { +.ruleset-section[data-v-455476d4] { border-left: 4px solid #2196F3; /* Blue accent */ } -.top-section[data-v-783fd7ef] { +.top-section[data-v-455476d4] { border-left: 4px solid #4CAF50; /* Green accent */ } -.drag-handle[data-v-783fd7ef] { +.drag-handle[data-v-455476d4] { cursor: move; } diff --git a/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js b/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js index fab550c..eef2f01 100644 --- a/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js +++ b/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js @@ -2,11 +2,11 @@ const currentImports = {}; const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); let moduleMap = { "./Page":()=>{ - dynamicLoadingCss(["__federation_expose_Page-_CExF_DI.css"], false, './Page'); - return __federation_import('./__federation_expose_Page-C2_enJ99.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, + dynamicLoadingCss(["__federation_expose_Page-Djrrbsow.css"], false, './Page'); + return __federation_import('./__federation_expose_Page-D_nruYha.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, "./Config":()=>{ - dynamicLoadingCss(["__federation_expose_Config-nL3Pv4Qs.css"], false, './Config'); - return __federation_import('./__federation_expose_Config-DZF0yyTH.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, + dynamicLoadingCss(["__federation_expose_Config-BJvXq0hj.css"], false, './Config'); + return __federation_import('./__federation_expose_Config-Btg4HYx3.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, "./Dashboard":()=>{ dynamicLoadingCss([], false, './Dashboard'); return __federation_import('./__federation_expose_Dashboard-DKtydfsT.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},};