From e15733b7de1cda391aee91bbfb4a6c5078fd20a6 Mon Sep 17 00:00:00 2001 From: wumode Date: Wed, 31 Dec 2025 19:10:54 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ClashRuleProvider):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=90=8E=E7=AB=AF=E6=A0=B8=E5=BF=83=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=B8=8E=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9E=8B=20-=20=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=A8=A1=E5=9E=8B=E9=87=8D=E6=9E=84:=20=E5=85=A8?= =?UTF-8?q?=E9=9D=A2=E5=BC=95=E5=85=A5=20Pydantic=20=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=EF=BC=88ClashConfig,=20Proxy,=20ProxyGroup=20=E7=AD=89?= =?UTF-8?q?=EF=BC=89=E6=9B=BF=E4=BB=A3=E5=8E=9F=E6=9C=89=E5=AD=97=E5=85=B8?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=8C=E6=8F=90=E4=BE=9B=E6=9B=B4=E4=B8=A5?= =?UTF-8?q?=E6=A0=BC=E7=9A=84=E6=95=B0=E6=8D=AE=E9=AA=8C=E8=AF=81=E4=B8=8E?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AE=89=E5=85=A8=E3=80=82=20-=20=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=BF=81=E7=A7=BB=E6=9C=BA=E5=88=B6:=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20v2.1.0=20=E6=95=B0=E6=8D=AE=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=EF=BC=8C=E6=94=AF=E6=8C=81=E5=B0=86=E6=97=A7?= =?UTF-8?q?=E7=89=88=E4=BB=A3=E7=90=86=E3=80=81=E7=AD=96=E7=95=A5=E7=BB=84?= =?UTF-8?q?=E5=8F=8A=E8=A7=84=E5=88=99=E6=95=B0=E6=8D=AE=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E8=87=B3=E6=96=B0=E6=9E=B6=E6=9E=84=E3=80=82?= =?UTF-8?q?=20-=20=E9=85=8D=E7=BD=AE=E8=A1=A5=E4=B8=81=E7=B3=BB=E7=BB=9F:?= =?UTF-8?q?=20=E5=AE=9E=E7=8E=B0=E5=9F=BA=E4=BA=8E=20JSON=20Patch=20?= =?UTF-8?q?=E7=9A=84=E7=BB=86=E7=B2=92=E5=BA=A6=E9=85=8D=E7=BD=AE=E4=BF=AE?= =?UTF-8?q?=E8=A1=A5=E6=9C=BA=E5=88=B6=EF=BC=8C=E6=9B=BF=E4=BB=A3=E6=97=A7?= =?UTF-8?q?=E7=89=88=E8=A6=86=E7=9B=96=E9=80=BB=E8=BE=91=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9=E7=9A=84=E7=81=B5?= =?UTF-8?q?=E6=B4=BB=E6=80=A7=E3=80=82=20-=20=E6=9C=8D=E5=8A=A1=E5=B1=82?= =?UTF-8?q?=E4=BC=98=E5=8C=96:=20=E9=87=8D=E5=86=99=20ClashRuleProviderSer?= =?UTF-8?q?vice=20=E4=BB=A5=E9=80=82=E9=85=8D=E6=96=B0=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=EF=BC=8C=E5=A2=9E=E5=BC=BA=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7=E4=B8=8E=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E6=80=A7=E3=80=82=20-=20API=E6=A8=A1=E5=9E=8B=E5=90=8C?= =?UTF-8?q?=E6=AD=A5:=20=E6=9B=B4=E6=96=B0=E7=9B=B8=E5=85=B3=20API=20?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9E=8B=E4=BB=A5=E4=BF=9D=E6=8C=81?= =?UTF-8?q?=E4=B8=8E=E5=86=85=E9=83=A8=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E7=9A=84=E4=B8=80=E8=87=B4=E6=80=A7=E3=80=82=20-=20=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=95=8C=E9=9D=A2:=20=E6=89=B9=E9=87=8F=E8=A7=84?= =?UTF-8?q?=E5=88=99=E7=AE=A1=E7=90=86=E5=92=8C=E6=95=B0=E6=8D=AE=E9=A1=B9?= =?UTF-8?q?=E9=9A=90=E8=97=8F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.v2.json | 3 +- plugins.v2/clashruleprovider/README.md | 31 +- plugins.v2/clashruleprovider/__init__.py | 170 +- plugins.v2/clashruleprovider/api.py | 162 +- plugins.v2/clashruleprovider/base.py | 32 +- plugins.v2/clashruleprovider/config.py | 47 +- .../dist/assets/Meta-1zu2nKV2.js | 3 + .../dist/assets/Meta-uqWbsmWL.png | Bin 0 -> 81167 bytes .../__federation_expose_Config-C8YPPEsk.js | 1513 -- .../__federation_expose_Config-CY46uj5g.js | 1479 ++ .../__federation_expose_Config-CwbjkOP2.css | 4 + .../__federation_expose_Config-D7x82s8Y.css | 4 - ..._federation_expose_Dashboard-CFBdUa27.css} | 2 +- ...__federation_expose_Dashboard-CybypqLB.js} | 1070 +- .../__federation_expose_Page-BLBLx7jX.css | 84 + .../__federation_expose_Page-CUYOswsP.js | 11795 ------------- .../__federation_expose_Page-Dx-0nC8K.css | 70 - .../__federation_expose_Page-KIk8A7rq.js | 14224 ++++++++++++++++ .../_plugin-vue_export-helper-D32QZFxh.js | 208 + .../_plugin-vue_export-helper-pcqpp-6-.js | 9 - .../dist/assets/remoteEntry.js | 12 +- ...-Bn79mBHh.js => theme-monokai-CF_yROe-.js} | 509 +- .../helper/clashrulemanager.py | 68 +- .../helper/clashruleparser.py | 31 +- .../helper/configconverter.py | 17 +- .../helper/converters/hysteria.py | 1 + .../helper/converters/hysteria2.py | 1 + .../helper/dataupgrader/upgrader_2_1_0.py | 0 .../helper/dataupgrader/v_2_1_0.py | 114 + .../helper/proxiesmanager.py | 109 +- .../clashruleprovider/models/__init__.py | 3 + plugins.v2/clashruleprovider/models/api.py | 87 +- .../clashruleprovider/models/configuration.py | 226 + .../clashruleprovider/models/datamodel.py | 31 + .../clashruleprovider/models/datapatch.py | 32 + .../clashruleprovider/models/generics.py | 93 + plugins.v2/clashruleprovider/models/hosts.py | 33 + .../clashruleprovider/models/metadata.py | 25 + .../models/proxy/__init__.py | 34 +- .../models/proxy/hysteria2proxy.py | 4 +- .../models/proxy/hysteriaproxy.py | 6 +- .../models/proxy/tlsmixin.py | 17 +- .../clashruleprovider/models/proxygroups.py | 122 +- .../models/proxyproviders.py | 130 + .../clashruleprovider/models/rule/__init__.py | 17 +- .../clashruleprovider/models/ruleitem.py | 40 + .../clashruleprovider/models/ruleproviders.py | 39 +- plugins.v2/clashruleprovider/models/types.py | 57 + plugins.v2/clashruleprovider/requirements.txt | 6 +- plugins.v2/clashruleprovider/services.py | 1269 +- plugins.v2/clashruleprovider/state.py | 298 +- plugins.v2/clashruleprovider/store.py | 19 - 52 files changed, 19084 insertions(+), 15276 deletions(-) create mode 100644 plugins.v2/clashruleprovider/dist/assets/Meta-1zu2nKV2.js create mode 100644 plugins.v2/clashruleprovider/dist/assets/Meta-uqWbsmWL.png delete mode 100644 plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-C8YPPEsk.js create mode 100644 plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CY46uj5g.js create mode 100644 plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CwbjkOP2.css delete mode 100644 plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-D7x82s8Y.css rename plugins.v2/clashruleprovider/dist/assets/{__federation_expose_Dashboard-vS9Qm2ZB.css => __federation_expose_Dashboard-CFBdUa27.css} (51%) rename plugins.v2/clashruleprovider/dist/assets/{__federation_expose_Dashboard-BDSt5WaH.js => __federation_expose_Dashboard-CybypqLB.js} (95%) create mode 100644 plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-BLBLx7jX.css delete mode 100644 plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-CUYOswsP.js delete mode 100644 plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Dx-0nC8K.css create mode 100644 plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-KIk8A7rq.js create mode 100644 plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-D32QZFxh.js delete mode 100644 plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-pcqpp-6-.js rename plugins.v2/clashruleprovider/dist/assets/{theme-monokai-Bn79mBHh.js => theme-monokai-CF_yROe-.js} (98%) create mode 100644 plugins.v2/clashruleprovider/helper/dataupgrader/upgrader_2_1_0.py create mode 100644 plugins.v2/clashruleprovider/helper/dataupgrader/v_2_1_0.py create mode 100644 plugins.v2/clashruleprovider/models/configuration.py create mode 100644 plugins.v2/clashruleprovider/models/datamodel.py create mode 100644 plugins.v2/clashruleprovider/models/datapatch.py create mode 100644 plugins.v2/clashruleprovider/models/generics.py create mode 100644 plugins.v2/clashruleprovider/models/hosts.py create mode 100644 plugins.v2/clashruleprovider/models/metadata.py create mode 100644 plugins.v2/clashruleprovider/models/proxyproviders.py create mode 100644 plugins.v2/clashruleprovider/models/ruleitem.py create mode 100644 plugins.v2/clashruleprovider/models/types.py delete mode 100755 plugins.v2/clashruleprovider/store.py diff --git a/package.v2.json b/package.v2.json index 7262e07..bb9d08f 100644 --- a/package.v2.json +++ b/package.v2.json @@ -508,12 +508,13 @@ "name": "Clash Rule Provider", "description": "随时为Clash添加一些额外的规则。", "labels": "工具", - "version": "2.0.10", + "version": "2.1.1", "icon": "Mihomo_Meta_A.png", "author": "wumode", "level": 1, "release": true, "history": { + "v2.1.1": "增强数据管理功能", "v2.0.10": "适配 MoviePilot 2.8.4", "v2.0.9": "适配 Pydantic V2 (主程序版本需高于 2.8.1-1)", "v2.0.8": "修复已知问题", diff --git a/plugins.v2/clashruleprovider/README.md b/plugins.v2/clashruleprovider/README.md index 77ab8e8..2f03cba 100644 --- a/plugins.v2/clashruleprovider/README.md +++ b/plugins.v2/clashruleprovider/README.md @@ -1,9 +1,8 @@ # Clash Rule Provider -**Clash Rule Provider** 生成适用于 [Meta Kernel](https://github.com/MetaCubeX/mihomo/tree/Meta) 定制配置,便于增加、修改和删除规则。 +**Clash Rule Provider** 是一个[MoviePilot](https://github.com/jxxghp/MoviePilot)插件,用于生成适用于 [Meta Kernel](https://github.com/MetaCubeX/mihomo/tree/Meta) 定制配置,便于增加、修改和删除规则,基于 Meta 内核丰富的代理组配置,提供灵活的路由功能。 - 即时通知 Clash 刷新规则集合 -- 基于 Meta 内核丰富的代理组配置,提供灵活的路由功能 - 支持按大洲和国家分组节点 - 支持覆写出站代理 - GEO 规则输入提示 @@ -13,7 +12,7 @@ ### 规则集规则 -用于添加能够在 Clash 中即时生效的规则,Clash Rule Provider 会根据每条规则的**出站**生成相应的**规则集合** `📂<-` + `出站`。 +用于添加能够在 Clash 中即时生效的规则,Clash Rule Provider 会根据每条规则的**出站**生成相应的**规则集合**。 ### 置顶规则 @@ -41,4 +40,28 @@ ### Hosts -如果需要自动更新此处使用的 Cloudflare IP, 可以通过其它[插件](https://github.com/wumode/MoviePilot-Addons)实现。 \ No newline at end of file +如果需要自动更新此处使用的 Cloudflare IP, 可以通过其它[插件](https://github.com/wumode/MoviePilot-Addons)实现。 + +### 配置隐藏 + +如果希望某些代理组、规则或是代理节点仅在特定条件下可见,可以使用可见性限制功能。例如,可以设置某些规则集仅在特定网络环境下可见。 +自定义表达式是个返回`bool`值的Python表达式,可以使用以下变量: + +```python +# 请求 URL +url: str +# 客户端的IP地址 +client_host: str +# 请求的标识符 +identifier: str | None = None +# User-Agent +user_agent : str | None = None +``` + +表达式示例: +- `client_host == '192.168.1.1'` +- `identifier == 'office-laptop' and 'Mobile' in user_agent` + +## 远程组件 + +[ClashRuleProvider-Remote](https://github.com/wumode/ClashRuleProvider-Remote) \ No newline at end of file diff --git a/plugins.v2/clashruleprovider/__init__.py b/plugins.v2/clashruleprovider/__init__.py index 94f30fc..50ef89d 100644 --- a/plugins.v2/clashruleprovider/__init__.py +++ b/plugins.v2/clashruleprovider/__init__.py @@ -1,5 +1,3 @@ -import asyncio -import copy import pytz from datetime import datetime, timedelta from typing import Any, Optional, List, Dict, Tuple @@ -9,21 +7,28 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger from pydantic import ValidationError +from app.api.endpoints.plugin import register_plugin_api from app.core.config import settings, global_vars from app.core.event import eventmanager, Event from app.log import logger +from app.plugins import _PluginBase from app.schemas.types import EventType, NotificationType +from app.utils.string import StringUtils from .api import ClashRuleProviderApi, apis -from .base import _ClashRuleProviderBase +from .base import Constant from .config import PluginConfig from .helper.utilsprovider import UtilsProvider -from .state import PluginState +from .models import ProxyGroup, ProxyGroups, RuleProviders, Hosts +from .models.api import SubscriptionsInfo +from .models.configuration import ClashConfig +from .models.datapatch import DataPatch +from .models.types import DataKey, DataSource +from .state import PluginState, GeoRules from .services import ClashRuleProviderService -from .store import PluginStore -class ClashRuleProvider(_ClashRuleProviderBase): +class ClashRuleProvider(_PluginBase): # 插件名称 plugin_name = "Clash Rule Provider" # 插件描述 @@ -31,7 +36,7 @@ class ClashRuleProvider(_ClashRuleProviderBase): # 插件图标 plugin_icon = "Mihomo_Meta_A.png" # 插件版本 - plugin_version = "2.0.10" + plugin_version = "2.1.1" # 插件作者 plugin_author = "wumode" # 作者主页 @@ -42,110 +47,82 @@ class ClashRuleProvider(_ClashRuleProviderBase): plugin_order = 99 # 可使用的用户级别 auth_level = 1 - # 主线程事件循环 - event_loop: Optional[asyncio.AbstractEventLoop] = None # Runtime variables services: ClashRuleProviderService api: ClashRuleProviderApi - - def __init__(self): - # Configuration attributes - super().__init__() + state: PluginState + scheduler: AsyncIOScheduler | None = None def init_plugin(self, conf: dict = None): self.stop_service() - self.state = PluginState() - self.config = PluginConfig() - self.store = PluginStore(self.__class__.__name__) - - # Load persistent data into state - self.state.proxy_groups = self.get_data("proxy_groups") or [] - self.state.extra_proxies = self.get_data("extra_proxies") or [] - self.state.subscription_info = self.get_data("subscription_info") or {} - self.state.rule_provider = self.get_data("rule_provider") or {} - self.state.rule_providers = self.get_data("extra_rule_providers") or {} - self.state.ruleset_names = self.get_data("ruleset_names") or {} - self.state.acl4ssr_providers = self.get_data("acl4ssr_providers") or {} - self.state.clash_configs = self.get_data("clash_configs") or {} - self.state.hosts = self.get_data("hosts") or [] - self.state.overwritten_region_groups = self.get_data("overwritten_region_groups") or {} - self.state.overwritten_proxies = self.get_data("overwritten_proxies") or {} - self.state.geo_rules = self.get_data("geo_rules") or {'geoip': [], 'geosite': []} + self.state = PluginState(self.__class__.__name__) + self.upgrade_data() if conf: try: - raw_conf = PluginConfig.upgrade_conf(conf) - self.config = PluginConfig.model_validate(raw_conf) + self.state.config = PluginConfig.model_validate(conf) except ValidationError as e: logger.error(f"解析配置出错: {e}") return self._update_config() - if self.config.enabled: + if self.state.config.enabled: self._initialize_plugin() + def upgrade_data(self): + data_version = self.get_data(DataKey.DATA_VERSION) or "2.0.10" + if StringUtils.compare_version(data_version, '<', "2.1.0"): + from .helper.dataupgrader import v_2_1_0 + v_2_1_0.upgrade(self.__class__.__name__) + def _initialize_plugin(self): - self.state.proxies_manager.clear() self.state.top_rules_manager.clear() self.state.ruleset_rules_manager.clear() - if ClashRuleProvider.event_loop is None: - ClashRuleProvider.event_loop = global_vars.loop - self.scheduler = AsyncIOScheduler(timezone=settings.TZ, event_loop=ClashRuleProvider.event_loop) - self.services = ClashRuleProviderService(self.__class__.__name__, self.config, self.state, self.store, - self.scheduler) - self.api = ClashRuleProviderApi(self.services, self.config) + self.scheduler = AsyncIOScheduler(timezone=settings.TZ, event_loop=global_vars.loop) + self.services = ClashRuleProviderService(self.__class__.__name__, self.state, self.scheduler) + self.api = ClashRuleProviderApi(self.services, self.state.config) try: - self.state.clash_template_dict = yaml.load(self.config.clash_template, Loader=yaml.SafeLoader) or {} - if not isinstance(self.state.clash_template_dict, dict): - self.state.clash_template_dict = {} + clash_template_dict = yaml.load(self.state.config.clash_template, Loader=yaml.SafeLoader) or {} + if isinstance(clash_template_dict, dict): + self.state.clash_template = ClashConfig.model_validate(clash_template_dict) + else: logger.error("Invalid clash template yaml") except yaml.YAMLError as exc: logger.error(f"Error loading clash template yaml: {exc}") - self.state.clash_template_dict = {} - - # Normalize template - for key, default in self.DEFAULT_CLASH_CONF.items(): - self.state.clash_template_dict.setdefault(key, copy.deepcopy(default)) self.services.load_rules() - self.services.load_proxies() - self.state.subscription_info = {url: self.state.subscription_info.get(url) or {} - for url in self.config.sub_links} - for _, sub_info in self.state.subscription_info.items(): - sub_info.setdefault('enabled', True) - self.state.clash_configs = {url: self.state.clash_configs[url] for url in self.config.sub_links if - self.state.clash_configs.get(url)} + # Accessing subscription_info property triggers load from DB. + sub_info_map = self.state.subscription_info + sub_info_map.update(self.state.config.sub_links) + self.state.subscription_info = sub_info_map - for url, conf in self.state.clash_configs.items(): - self.services.add_proxies_to_manager(conf.get('proxies', []), - f"Sub:{UtilsProvider.get_url_domain(url)}-{abs(hash(url))}") - self.services.add_proxies_to_manager(self.state.clash_template_dict.get('proxies', []), 'Template') + # sub_configs loaded from DB. Filter by current sub_links. + sub_configs_map = self.state.sub_configs + sub_configs_map = {url: sub_configs_map[url] for url in self.state.config.sub_links if sub_configs_map.get(url)} + self.state.sub_configs = sub_configs_map - self.services.check_proxies_lifetime() + self.services.check_patch_lifetime() self._start_scheduler() def _start_scheduler(self): self.scheduler.start() now = datetime.now(tz=pytz.timezone(settings.TZ)) self.scheduler.add_job(self.services.async_refresh_subscriptions, "date", - run_date=now + timedelta(seconds=2), misfire_grace_time=self.MISFIRE_GRACE_TIME) - if self.config.hint_geo_dat: + run_date=now + timedelta(seconds=2), misfire_grace_time=Constant.MISFIRE_GRACE_TIME) + if self.state.config.hint_geo_dat: self.scheduler.add_job(self.services.async_refresh_geo_dat, "date", - run_date=now + timedelta(seconds=3), misfire_grace_time=self.MISFIRE_GRACE_TIME) - else: - self.state.geo_rules = {'geoip': [], 'geosite': []} - if self.config.enable_acl4ssr: + run_date=now + timedelta(seconds=3), misfire_grace_time=Constant.MISFIRE_GRACE_TIME) + + if self.state.config.enable_acl4ssr: self.scheduler.add_job(self.services.async_refresh_acl4ssr, "date", - run_date=now + timedelta(seconds=4), misfire_grace_time=self.MISFIRE_GRACE_TIME) - else: - self.state.acl4ssr_providers = {} + run_date=now + timedelta(seconds=4), misfire_grace_time=Constant.MISFIRE_GRACE_TIME) def get_state(self) -> bool: - return self.config.enabled + return self.state.config.enabled @staticmethod def get_command() -> List[Dict[str, Any]]: @@ -165,10 +142,10 @@ class ClashRuleProvider(_ClashRuleProviderBase): {"key": "clash_info", "name": "Clash Info"}, {"key": "traffic_stats", "name": "Traffic Stats"} ] - return [c for c in components if c.get("name") in self.config.dashboard_components] + return [c for c in components if c.get("name") in self.state.config.dashboard_components] def get_dashboard(self, key: str, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: - clash_available = bool(self.config.dashboard_url and self.config.dashboard_secret) + clash_available = bool(self.state.config.dashboard_url and self.state.config.dashboard_secret) components = {'clash_info': {'title': 'Clash Info', 'md': 4}, 'traffic_stats': {'title': 'Traffic Stats', 'md': 8}} col_config = {'cols': 12, 'md': components.get(key, {}).get('md', 4)} @@ -176,7 +153,7 @@ class ClashRuleProvider(_ClashRuleProviderBase): 'title': components.get(key, {}).get('title', 'Clash Info'), 'border': True, 'clash_available': clash_available, - 'secret': self.config.dashboard_secret, + 'secret': self.state.config.dashboard_secret, } return col_config, global_config, [] @@ -193,18 +170,18 @@ class ClashRuleProvider(_ClashRuleProviderBase): logger.error(f"退出插件失败:{e}") def get_service(self) -> List[Dict[str, Any]]: - if self.get_state() and self.config.auto_update_subscriptions and self.config.sub_links: + if self.get_state() and self.state.config.auto_update_subscriptions and self.state.config.sub_links: return [{ "id": "ClashRuleProvider", "name": "定时更新订阅", - "trigger": CronTrigger.from_crontab(self.config.cron_string), + "trigger": CronTrigger.from_crontab(self.state.config.cron_string), "func": self.refresh_subscription_service, "kwargs": {} }] return [] async def refresh_subscription_service(self): - if not self.config.sub_links: + if not self.state.config.sub_links: return res = await self.services.async_refresh_subscriptions() messages = [] @@ -214,37 +191,36 @@ class ClashRuleProvider(_ClashRuleProviderBase): message = f"{index}. 「 {host_name} 」\n" index += 1 if result: - sub_info = self.state.subscription_info.get(url, {}) - if sub_info.get('total') is not None: - used = sub_info.get('download', 0) + sub_info.get('upload', 0) - remaining = sub_info.get('total', 0) - used + sub_info = self.state.subscription_info.get(url) + if sub_info.total: + used = sub_info.download + sub_info.upload + remaining = sub_info.total- used info = ( - f"节点数量: {sub_info.get('proxy_num', 0)}\n" + f"节点数量: {sub_info.proxy_num}\n" f"已用流量: {UtilsProvider.format_bytes(used)}\n" f"剩余流量: {UtilsProvider.format_bytes(remaining)}\n" - f"总量: {UtilsProvider.format_bytes(sub_info.get('total', 0))}\n" - f"过期时间: {UtilsProvider.format_expire_time(sub_info.get('expire', 0))}" + f"总量: {UtilsProvider.format_bytes(sub_info.total)}\n" + f"过期时间: {UtilsProvider.format_expire_time(sub_info.expire)}" ) else: - info = f"节点数量: {sub_info.get('proxy_num', 0)}\n" + info = f"节点数量: {sub_info.proxy_num}\n" message += f"订阅更新成功\n{info}" else: message += '订阅更新失败' messages.append(message) - if self.config.notify: - self.post_message(title=f"【{self.plugin_name}】", - mtype=NotificationType.Plugin, - text='\n'.join(messages) - ) + if self.state.config.notify: + self.post_message( + title=f"【{self.plugin_name}】", mtype=NotificationType.Plugin, text='\n'.join(messages) + ) def _update_config(self): - conf = self.config.model_dump(by_alias=True) + conf = self.state.config.model_dump(by_alias=True) self.update_config(conf) def update_best_cf_ip(self, ips: List[str]): - self.config.best_cf_ip = [*ips] + self.state.config.best_cf_ip = [*ips] conf = self.get_config() - conf['best_cf_ip'] = self.config.best_cf_ip + conf['best_cf_ip'] = self.state.config.best_cf_ip self.update_config(conf) @eventmanager.register(EventType.PluginAction) @@ -258,3 +234,13 @@ class ClashRuleProvider(_ClashRuleProviderBase): if isinstance(ips, list): logger.info("更新 Cloudflare 优选 IP ...") self.update_best_cf_ip(ips) + + @eventmanager.register(EventType.PluginReload) + def reload(self, event): + """ + 响应插件重载事件 + """ + plugin_id = event.event_data.get("plugin_id") + if plugin_id == self.__class__.__name__: + logger.info("正在注册 API ...") + register_plugin_api(plugin_id=plugin_id) diff --git a/plugins.v2/clashruleprovider/api.py b/plugins.v2/clashruleprovider/api.py index 537ef49..b3bd097 100644 --- a/plugins.v2/clashruleprovider/api.py +++ b/plugins.v2/clashruleprovider/api.py @@ -5,7 +5,7 @@ from typing import Any, Dict, List, Callable, Optional, Literal import websockets import yaml -from fastapi import HTTPException, Request, status, Response +from fastapi import HTTPException, Request, status, Response, Body from fastapi.responses import PlainTextResponse from sse_starlette.sse import EventSourceResponse @@ -14,8 +14,10 @@ from app.core.config import settings from app.log import logger from .config import PluginConfig -from .models import ProxyGroup -from .models.api import RuleData, Connectivity, Subscription, RuleProviderData, SubscriptionInfo, HostData +from .models import ProxyGroup, Proxy, HostData, RuleData, RuleProvider, RuleProviderData +from .models.api import Connectivity, SubscriptionSetting, ConfigRequest +from .models.metadata import Metadata +from .models.types import RuleSet, DataSource from .services import ClashRuleProviderService @@ -27,14 +29,16 @@ class ApiCollection: methods: List[Literal['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD', 'TRACE']], allow_anonymous: Optional[bool] = None, auth: Optional[str] = None, - summary: Optional[str] = ''): + summary: Optional[str] = '', + **kwargs): def decorator(func: Callable): route_meta: Dict[str, Any] = { 'path': path, 'methods': methods, 'summary': summary, - 'endpoint': func + 'endpoint': func, + **kwargs } if allow_anonymous is not None: route_meta['allow_anonymous'] = allow_anonymous @@ -63,7 +67,7 @@ class ClashRuleProviderApi: self.services: ClashRuleProviderService = services self.config = config - @apis.register(path='/connectivity', methods=['POST'], auth='bear', summary='测试连接') + @apis.register(path="/connectivity", methods=["POST"], auth="bear", summary="测试连接") async def test_connectivity(self, item: Connectivity) -> schemas.Response: success, message = await self.services.test_connectivity(item.clash_apis, item.sub_links) return schemas.Response(success=success, message=message) @@ -71,7 +75,7 @@ class ClashRuleProviderApi: @apis.register(path="/clash-outbound", methods=["GET"], auth="bear", summary="获取所有出站") def get_clash_outbound(self) -> schemas.Response: outbound = self.services.clash_outbound() - return schemas.Response(success=True, data={"outbound": outbound}) + return schemas.Response(success=True, data=outbound) @apis.register(path="/status", methods=["GET"], auth="bear", summary="插件状态") def get_status(self) -> schemas.Response: @@ -79,56 +83,80 @@ class ClashRuleProviderApi: return schemas.Response(success=True, data=data) @apis.register(path="/rules/{ruleset}", methods=["GET"], auth="bear", summary="获取指定集合中的规则") - def get_rules(self, ruleset: Literal['ruleset', 'top']) -> schemas.Response: + def get_rules(self, ruleset: RuleSet) -> schemas.Response: data = self.services.get_rules(ruleset) - return schemas.Response(success=True, data={'rules': data}) + return schemas.Response(success=True, data=data) - @apis.register(path="/reorder-rules/{ruleset}/{target_priority}", methods=["PUT"], auth="bear", - summary="重新排序规则") - def reorder_rules(self, ruleset: Literal['ruleset', 'top'], target_priority: int, - rule_data: RuleData) -> schemas.Response: - moved_priority = rule_data.priority - success, message = self.services.reorder_rules(ruleset, moved_priority, target_priority) + @apis.register(path="/reorder-rules/{ruleset}/{target}", methods=["PUT"], auth="bear", summary="重新排序规则") + def reorder_rules(self, ruleset: RuleSet, target: int, + moved_priority: int = Body(..., embed=True)) -> schemas.Response: + success, message = self.services.reorder_rules(ruleset, moved_priority, target) return schemas.Response(success=success, message=message) @apis.register(path="/rules/{ruleset}/{priority}", methods=["PATCH"], auth="bear", summary="更新规则") - def update_rule(self, ruleset: Literal['ruleset', 'top'], priority: int, rule_data: RuleData) -> schemas.Response: + def update_rule(self, ruleset: RuleSet, priority: int, rule_data: RuleData) -> schemas.Response: success, message = self.services.update_rule(ruleset, priority, rule_data) return schemas.Response(success=success, message=message) @apis.register(path="/rules/{ruleset}", methods=["POST"], auth="bear", summary="添加规则") - def add_rule(self, ruleset: Literal['ruleset', 'top'], rule_data: RuleData) -> schemas.Response: + def add_rule(self, ruleset: RuleSet, rule_data: RuleData = Body(...)) -> schemas.Response: success, message = self.services.add_rule(ruleset, rule_data) return schemas.Response(success=success, message=message) + @apis.register(path="/rules/{ruleset}/{priority}/meta", methods=["PATCH"], auth="bear", summary="更新规则元数据") + def update_rule_meta(self, ruleset: RuleSet, priority: int, meta: Metadata = Body(...)) -> schemas.Response: + success, message = self.services.update_rule_meta(ruleset, priority, meta) + return schemas.Response(success=success, message=message) + + @apis.register(path="/rules/{ruleset}/metadata/disabled", methods=["POST"], auth="bear", summary="设置规则状态") + def set_rules_status(self, ruleset: RuleSet, priorities: dict[int, bool] = Body(...)): + self.services.set_rules_status(ruleset, priorities) + @apis.register(path="/rules/{ruleset}/{priority}", methods=["DELETE"], auth="bear", summary="删除规则") - def delete_rule(self, ruleset: Literal['ruleset', 'top'], priority: int) -> schemas.Response: + def delete_rule(self, ruleset: RuleSet, priority: int) -> schemas.Response: self.services.delete_rule(ruleset, priority) return schemas.Response(success=True) + @apis.register(path="/rules/{ruleset}", methods=["DELETE"], auth="bear", summary="批量删除规则") + def delete_rules(self, ruleset: RuleSet, priority: list[int] = Body(...)) -> schemas.Response: + self.services.delete_rules(ruleset, priority) + return schemas.Response(success=True) + @apis.register(path="/refresh", methods=["PUT"], auth="bear", summary="更新订阅") - async def refresh_subscription(self, subscription: Subscription) -> schemas.Response: - success, message = await self.services.refresh_subscription(subscription.url) + async def refresh_subscription(self, url: str = Body(..., embed=True)) -> schemas.Response: + success, message = await self.services.refresh_subscription(url) return schemas.Response(success=success, message=message) - @apis.register(path="/rule-providers", methods=["GET"], auth="bear", summary="获取规则集合") + @apis.register(path="/rule-providers", methods=["GET"], auth="bear", summary="获取规则集合", + response_model=schemas.Response, response_model_exclude_none=True) def get_rule_providers(self) -> schemas.Response: - return schemas.Response(success=True, data=self.services.rule_providers()) + return schemas.Response(success=True, data=self.services.state.all_rule_providers) - @apis.register(path="/rule-providers/{name}", methods=["POST"], auth="bear", summary="更新规则集合") + @apis.register(path="/rule-providers/{name}", methods=["POST"], auth="bear", summary="添加代理组") + def add_rule_provider(self, name: str, item: RuleProvider): + success, message = self.services.add_rule_provider(name, item) + return schemas.Response(success=success, message=message) + + @apis.register(path="/rule-providers/{name}", methods=["PATCH"], auth="bear", summary="更新规则集合") def update_rule_provider(self, name: str, item: RuleProviderData): success, message = self.services.update_rule_provider(name, item) return schemas.Response(success=success, message=message) + @apis.register(path="/rule-providers/{name}/meta", methods=["PATCH"], auth="bear", summary="更新规则集元数据") + def update_rule_providers_meta(self, name: str, meta: Metadata): + success, message = self.services.update_rule_providers_meta(name, meta) + return schemas.Response(success=success, message=message) + @apis.register(path="/rule-providers/{name}", methods=["DELETE"], auth="bear", summary="删除规则集合") def delete_rule_provider(self, name: str): self.services.delete_rule_provider(name) return schemas.Response(success=True) - @apis.register(path="/proxies", methods=["GET"], auth="bear", summary="获取出站代理") + @apis.register(path="/proxies", methods=["GET"], auth="bear", summary="获取代理", + response_model=schemas.Response, response_model_exclude_none=True) def get_proxies(self): - proxies = self.services.get_all_proxies_with_details() - return schemas.Response(success=True, data={'proxies': proxies}) + proxies = self.services.get_proxies() + return schemas.Response(success=True, data=proxies) @apis.register(path="/proxies/{name}", methods=["DELETE"], auth="bear", summary="删除出站代理") def delete_proxy(self, name: str): @@ -136,39 +164,61 @@ class ClashRuleProviderApi: return schemas.Response(success=True) @apis.register(path="/proxies", methods=["PUT"], auth="bear", summary="添加出站代理") - def import_proxies(self, params: Dict[str, Any]): - success, message = self.services.import_proxies(params) + def import_proxies(self, vehicle: Literal["YAML", "LINK"] = Body(...), payload: str = Body(...)): + success, message = self.services.import_proxies(vehicle, payload) return schemas.Response(success=success, message=message) @apis.register(path="/proxies/{name}", methods=["PATCH"], auth="bear", summary="更新出站代理") - def update_proxy(self, name: str, param: Dict[str, Any]) -> schemas.Response: - success, message = self.services.update_proxy(name, param) + def update_proxy(self, name: str, source: DataSource = Body(...), proxy: Proxy = Body(...)) -> schemas.Response: + success, message = self.services.update_proxy(name, source, proxy) return schemas.Response(success=success, message=message) - @apis.register(path="/proxy-groups", methods=["GET"], auth="bear", summary="获取代理组") + @apis.register(path="/proxies/{name}/meta", methods=["PATCH"], auth="bear", summary="更新代理组元数据") + def update_proxy_meta(self, name: str, meta: Metadata): + success, message = self.services.update_proxy_meta(name, meta) + return schemas.Response(success=success, message=message) + + @apis.register(path="/proxies/{name}/patch", methods=["DELETE"], auth="bear", summary="删除代理补丁") + def delete_proxy_patch(self, name: str): + success, message = self.services.delete_proxy_patch(name) + return schemas.Response(success=success, message=message) + + @apis.register(path="/proxy-groups", methods=["GET"], auth="bear", summary="获取代理组", + response_model=schemas.Response, response_model_exclude_none=True) def get_proxy_groups(self): - proxy_groups = self.services.get_all_proxy_groups_with_source() - return schemas.Response(success=True, data={'proxy_groups': proxy_groups}) + proxy_groups = self.services.get_proxy_groups() + return schemas.Response(success=True, data=proxy_groups) @apis.register(path="/proxy-groups/{name}", methods=["DELETE"], auth="bear", summary="删除代理组") def delete_proxy_group(self, name: str): success, message = self.services.delete_proxy_group(name) return schemas.Response(success=success, message=message) + @apis.register(path="/proxy-groups/{name}/meta", methods=["PATCH"], auth="bear", summary="更新代理组元数据") + def update_proxy_group_meta(self, name: str, meta: Metadata): + success, message = self.services.update_proxy_group_meta(name, meta) + return schemas.Response(success=success, message=message) + + @apis.register(path="/proxy-groups/{name}/patch", methods=["DELETE"], auth="bear", summary="删除代理组补丁") + def delete_proxy_group_patch(self, name: str): + success, message = self.services.delete_proxy_group_patch(name) + return schemas.Response(success=success, message=message) + @apis.register(path="/proxy-groups", methods=["POST"], auth="bear", summary="添加代理组") def add_proxy_group(self, item: ProxyGroup): success, message = self.services.add_proxy_group(item) return schemas.Response(success=success, message=message) - @apis.register(path="/proxy-groups/{previous_name}", methods=["PATCH"], auth="bear", summary="更新代理组") - def update_proxy_group(self, previous_name: str, item: ProxyGroup): - success, message = self.services.update_proxy_group(previous_name, item) + @apis.register(path="/proxy-groups/{name}", methods=["PATCH"], auth="bear", summary="更新代理组") + def update_proxy_group(self, name: str, source: DataSource = Body(...), proxy_group: ProxyGroup = Body(...)): + success, message = self.services.update_proxy_group(name, source, proxy_group) return schemas.Response(success=success, message=message) - @apis.register(path="/proxy-providers", methods=["GET"], auth="bear", summary="获取代理集合") + @apis.register(path="/proxy-providers", methods=["GET"], auth="bear", summary="获取代理集合", + response_model=schemas.Response, response_model_exclude_none=True) def get_proxy_providers(self): - proxy_providers = self.services.all_proxy_providers() - return schemas.Response(success=True, data={'proxy_providers': proxy_providers}) + proxy_providers = self.services.state.all_proxy_providers + return schemas.Response(success=True, data=proxy_providers) @apis.register(path="/ruleset", methods=["GET"], allow_anonymous=True, summary="获取规则集规则") def get_ruleset(self, name: str, apikey: str) -> PlainTextResponse: @@ -181,43 +231,49 @@ class ClashRuleProviderApi: return PlainTextResponse(content=res, media_type="application/x-yaml") @apis.register(path="/import", methods=["POST"], auth="bear", summary="导入规则") - def import_rules(self, params: Dict[str, Any]): - self.services.import_rules(params) + def import_rules(self, vehicle: Literal["YAML"] = Body(...), payload: str = Body(...)): + self.services.import_rules(vehicle, payload) return schemas.Response(success=True) @apis.register(path="/hosts", methods=["GET"], auth="bear", summary="获取 Hosts") def get_hosts(self): - return schemas.Response(success=True, data={'hosts': self.services.get_hosts()}) + return schemas.Response(success=True, data=self.services.state.hosts.model_dump(mode='json')) @apis.register(path="/hosts", methods=["POST"], auth="bear", summary="更新 Hosts") - def update_hosts(self, host: HostData): - success, message = self.services.update_hosts(host) + def update_hosts(self, domain: str = Body(..., embed=True), host: HostData = Body(...)): + success, message = self.services.update_hosts(domain, host) return schemas.Response(success=success, message=message) - @apis.register(path="/hosts", methods=["DELETE"], auth="bear", summary="删除 Hosts") - def delete_host(self, host: HostData): - success, message = self.services.delete_host(host) + @apis.register(path="/hosts/{domain}", methods=["DELETE"], auth="bear", summary="删除 Hosts") + def delete_host(self, domain: str): + success, message = self.services.delete_host(domain) return schemas.Response(success=success, message=message) @apis.register(path="/subscription-info", methods=["POST"], auth="bear", summary="更新订阅信息") - def update_subscription_info(self, sub_info: SubscriptionInfo): + def update_subscription_info(self, sub_info: SubscriptionSetting): self.services.update_subscription_info(sub_info) return schemas.Response(success=True) @apis.register(path="/config", methods=["GET"], allow_anonymous=bool(True), summary="获取 Clash 配置") - def get_clash_config(self, apikey: str, request: Request): + def get_clash_config(self, apikey: str, request: Request, identifier: str | None = None): _apikey = self.config.apikey or settings.API_TOKEN + param = ConfigRequest( + url=str(request.url), + client_host=request.client.host, + identifier=identifier, + user_agent=request.headers.get("user-agent") + ) if not secrets.compare_digest(apikey, _apikey): raise HTTPException(status_code=403, detail="Invalid API Key") logger.info(f"{request.client.host} 正在获取配置") - config = self.services.clash_config() + config = self.services.build_clash_config(param=param) if not config: raise HTTPException(status_code=500, detail="配置不可用") - res = yaml.dump(config, allow_unicode=True, sort_keys=False) + config_dict = config.model_dump(mode="json", by_alias=True, exclude_none=True) + res = yaml.dump(config_dict, allow_unicode=True, sort_keys=False) sub_info = self.services.get_subscription_user_info() - headers = {'Subscription-Userinfo': f'upload={sub_info["upload"]}; download={sub_info["download"]}; ' - f'total={sub_info["total"]}; expire={sub_info["expire"]}'} + headers = {'Subscription-Userinfo': sub_info.header} return Response(headers=headers, content=res, media_type="text/yaml") @apis.register(path="/clash/proxy/{path:path}", methods=["GET"], auth="bear", summary="转发 Clash API 请求") diff --git a/plugins.v2/clashruleprovider/base.py b/plugins.v2/clashruleprovider/base.py index 008be01..5f5bdb0 100644 --- a/plugins.v2/clashruleprovider/base.py +++ b/plugins.v2/clashruleprovider/base.py @@ -1,34 +1,8 @@ -from abc import ABC -from typing import Final, Literal, Dict - -from apscheduler.schedulers.asyncio import AsyncIOScheduler - -from app.plugins import _PluginBase - -from .config import PluginConfig -from .state import PluginState -from .store import PluginStore +from typing import Final -class _ClashRuleProviderBase(_PluginBase, ABC): - # Constants - DEFAULT_CLASH_CONF: Final[ - Dict[Literal['rules', 'rule-providers', 'proxies', 'proxy-groups', 'proxy-providers'], dict | list]] = { - 'rules': [], 'rule-providers': {}, - 'proxies': [], 'proxy-groups': [], 'proxy-providers': {} - } - OVERWRITTEN_PROXIES_LIFETIME: Final[int] = 10 +class Constant: + PATCH_LIFESPAN: Final[int] = 10 ACL4SSR_API: Final[str] = "https://api.github.com/repos/ACL4SSR/ACL4SSR" METACUBEX_RULE_DAT_API: Final[str] = "https://api.github.com/repos/MetaCubeX/meta-rules-dat" MISFIRE_GRACE_TIME: Final[int] = 120 - KEY_TOP_RULES: Final[str] = "top_rules" - KEY_RULESET_RULES: Final[str] = "ruleset_rules" - KEY_PROXIES: Final[str] = "proxies" - KEY_PROXY_GROUPS: Final[str] = "proxy-groups" - KEY_NAME: Final[str] = "name" - - # Runtime variables - state: PluginState - config: PluginConfig - store: PluginStore - scheduler: AsyncIOScheduler = None diff --git a/plugins.v2/clashruleprovider/config.py b/plugins.v2/clashruleprovider/config.py index 32b126e..6c078c7 100644 --- a/plugins.v2/clashruleprovider/config.py +++ b/plugins.v2/clashruleprovider/config.py @@ -1,6 +1,6 @@ -from typing import Any, Dict, List, Optional +from typing import List, Optional -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator from .models.api import ClashApi @@ -8,10 +8,10 @@ from .models.api import ClashApi class SubscriptionConfig(BaseModel): url: str rules: Optional[bool] = True - rule_providers: Optional[bool] = Field(True, alias='rule-providers') + rule_providers: Optional[bool] = Field(default=True, alias='rule-providers') proxies: Optional[bool] = True - proxy_groups: Optional[bool] = Field(True, alias='proxy-groups') - proxy_providers: Optional[bool] = Field(True, alias='proxy-providers') + proxy_groups: Optional[bool] = Field(default=True, alias='proxy-groups') + proxy_providers: Optional[bool] = Field(default=True, alias='proxy-providers') @field_validator('url') @classmethod @@ -23,10 +23,14 @@ class PluginConfig(BaseModel): """ A dataclass to hold all the configuration of the ClashRuleProvider plugin. """ + model_config = ConfigDict( + str_strip_whitespace=True, + ) + enabled: bool = False proxy: bool = False notify: bool = False - subscriptions_config: List[SubscriptionConfig] = Field(default_factory=list) + subscriptions_config: list[SubscriptionConfig] = Field(default_factory=list) movie_pilot_url: str = '' cron_string: str = '30 12 * * *' timeout: int = 10 @@ -46,6 +50,8 @@ class PluginConfig(BaseModel): apikey: Optional[str] = None clash_dashboards: List[ClashApi] = Field(default_factory=list) active_dashboard: Optional[int] = None + identifiers: list[str] = Field(default_factory=list) + cache_ttl: int = 3600 @field_validator('clash_dashboards') @classmethod @@ -62,32 +68,6 @@ class PluginConfig(BaseModel): def validate_movie_pilot_url(cls, v: str): return v.rstrip('/') - @field_validator('ruleset_prefix') - @classmethod - def validate_ruleset_prefix(cls, v: str): - return v.strip() - - @field_validator('acl4ssr_prefix') - @classmethod - def validate_acl4ssr_prefix(cls, v: str): - return v.strip() - - @staticmethod - def upgrade_conf(conf: Dict[str, Any]) -> Dict[str, Any]: - if conf.get('sub_links'): - subscriptions_config = conf.get('subscriptions_config') or [] - subscriptions_config.extend( - [{'url': url, 'rules': True, 'rule-providers': True, 'proxies': True, 'proxy-groups': True, - 'proxy-providers': True} - for url in conf['sub_links']] - ) - conf['subscriptions_config'] = subscriptions_config - if conf.get('clash_dashboard_url') and conf.get('clash_dashboard_secret'): - clash_dashboards = conf.get('clash_dashboards') or [] - clash_dashboards.append({'url': conf.get('clash_dashboard_url'), 'secret': conf.get('clash_dashboard_secret')}) - conf['clash_dashboards'] = clash_dashboards - return conf - @property def sub_links(self) -> List[str]: return [sub.url for sub in self.subscriptions_config] @@ -105,3 +85,6 @@ class PluginConfig(BaseModel): if self.active_dashboard is not None and self.active_dashboard in range(len(self.clash_dashboards)): dashboard_secret = self.clash_dashboards[self.active_dashboard].secret return dashboard_secret + + def get_sub_conf(self, url: str) -> SubscriptionConfig: + return next((conf for conf in self.subscriptions_config if conf.url == url), SubscriptionConfig(url=url)) diff --git a/plugins.v2/clashruleprovider/dist/assets/Meta-1zu2nKV2.js b/plugins.v2/clashruleprovider/dist/assets/Meta-1zu2nKV2.js new file mode 100644 index 0000000..d090f9d --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/Meta-1zu2nKV2.js @@ -0,0 +1,3 @@ +const MetaLogo = "/assets/Meta-uqWbsmWL.png"; + +export { MetaLogo as M }; diff --git a/plugins.v2/clashruleprovider/dist/assets/Meta-uqWbsmWL.png b/plugins.v2/clashruleprovider/dist/assets/Meta-uqWbsmWL.png new file mode 100644 index 0000000000000000000000000000000000000000..1f6323dad3ba9c5d32060d61c498a12f371e8fd5 GIT binary patch literal 81167 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 EAL3)tdH?_b literal 0 HcmV?d00001 diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-C8YPPEsk.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-C8YPPEsk.js deleted file mode 100644 index 038958b..0000000 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-C8YPPEsk.js +++ /dev/null @@ -1,1513 +0,0 @@ -import { importShared } from './__federation_fn_import-JrT3xvdd.js'; -import { V as VAceEditor } from './theme-monokai-Bn79mBHh.js'; -import { _ as _export_sfc } from './_plugin-vue_export-helper-pcqpp-6-.js'; - -const {toDisplayString:_toDisplayString,createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,openBlock:_openBlock,createBlock:_createBlock,createCommentVNode:_createCommentVNode,createVNode:_createVNode,mergeProps:_mergeProps,renderList:_renderList,Fragment:_Fragment,createElementBlock:_createElementBlock,createElementVNode:_createElementVNode,withModifiers:_withModifiers,unref:_unref} = await importShared('vue'); - - -const _hoisted_1 = { class: "plugin-config" }; -const _hoisted_2 = { class: "text-subtitle-1 font-weight-medium" }; -const _hoisted_3 = { class: "d-flex align-center" }; -const _hoisted_4 = { class: "font-weight-medium" }; -const _hoisted_5 = { class: "text-body-2" }; - -const {ref,reactive,onMounted,computed} = await importShared('vue'); - - -const _sfc_main = { - __name: 'Config', - props: { - initialConfig: { - type: Object, - default: () => ({}), - }, - api: { - type: Object, - default: () => { - }, - }, -}, - emits: ['save', 'close'], - setup(__props, { emit: __emit }) { - -const activeTab = ref("subscription"); -const editorOptions = { - enableBasicAutocompletion: true, - enableSnippets: true, - enableLiveAutocompletion: true, - showLineNumbers: true, - tabSize: 2 -}; -const configPlaceholder = ref( - `profile: - store-selected: true -mode: rule -log-level: silent` -); -// Props -const props = __props; - -// 状态变量 -const clashTemplateDialog = ref(false); -const clashTemplateType = ref('YAML'); -const clashTemplateContent = ref(''); -const form = ref(null); -const isFormValid = ref(true); -const error = ref(null); -const saving = ref(false); -const testing = ref(false); -const dashboardComponents = ['Clash Info', 'Traffic Stats']; -const showSecrets = ref({0: false}); - -// Test result state -const testResult = reactive({ - show: false, - success: false, - title: '', - message: '' -}); - - -// 默认配置 -const defaultConfig = { - enabled: false, - subscriptions_config: [], - filter_keywords: ["公益性", "高延迟", "域名", "官网", "重启", "过期时间", "系统代理"], - clash_dashboards: [{url: '', secret: ''}], - movie_pilot_url: '', - cron_string: '0 */6 * * *', - timeout: 10, - retry_times: 3, - proxy: false, - notify: false, - auto_update_subscriptions: true, - ruleset_prefix: '📂<=', - acl4ssr_prefix: '🗂️=>', - group_by_region: false, - group_by_country: false, - refresh_delay: 5, - enable_acl4ssr: false, - dashboard_components: [], - clash_template: '', - hint_geo_dat: false, - best_cf_ip: [], - active_dashboard: 0, - apikey: null -}; - -// 响应式配置对象 -const config = reactive({...defaultConfig}); - -// 自定义事件 -const emit = __emit; - -// 初始化 -onMounted(() => { - if (props.initialConfig) { - Object.keys(props.initialConfig).forEach(key => { - if (key in config) { - config[key] = props.initialConfig[key]; - } - }); - } -}); - -const sub_links = computed(() => { - if (!config.subscriptions_config) { - return []; - } - return config.subscriptions_config.map(item => item.url); -}); - -// 验证函数 -const isValidUrl = (urlString) => { - if (!urlString) return false; - try { - const url = new URL(urlString); - return url.protocol === 'http:' || url.protocol === 'https:'; - } catch (e) { - return false; - } -}; - -function isValidIP(ip) { - // IPv4 正则:四段数字(0–255),用点隔开 - const ipv4Regex = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; - - // IPv6 正则:八组 1–4 位 16 进制数,用冒号隔开,支持简写 ::(不严格支持所有极端情况,但能覆盖大多数合法 IPv6) - const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(([0-9a-fA-F]{1,4}:){1,7}|:):([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})$/; - - return ipv4Regex.test(ip) || ipv6Regex.test(ip); -} - -function validateIPs(ips) { - for (const ip of ips) { - if (!isValidIP(ip)) { - return `无效的 IP 地址: ${ip}` - } - } - return true -} - -const generateApiKey = () => { - // 简单生成随机字符串,可替换为更安全的算法 - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let key = ''; - for (let i = 0; i < 32; i++) { - key += chars.charAt(Math.floor(Math.random() * chars.length)); - } - config.apikey = key; -}; - -// 测试连接 -async function testConnection() { - testing.value = true; - error.value = null; - testResult.show = false; - - try { - // 验证必需的参数 - if (sub_links.value.length === 0) { - throw new Error('请先配置至少一个订阅链接') - } - - // 准备API请求参数 - const testParams = { - clash_apis: config.clash_dashboards, - sub_links: sub_links.value - }; - - // 调用API进行连接测试 - const result = await props.api.post('/plugin/ClashRuleProvider/connectivity', testParams); - - // 根据返回结果显示相应消息 - if (result.success) { - testResult.success = true; - testResult.title = '连接测试成功!'; - testResult.message = 'Clash面板和订阅链接连接正常,配置验证通过'; - testResult.show = true; - - // Auto hide after 5 seconds - setTimeout(() => { - testResult.show = false; - }, 5000); - } else { - throw new Error(result.message || '连接测试失败,请检查配置') - } - - } catch (err) { - console.error('连接测试失败:', err); - testResult.success = false; - testResult.title = '连接测试失败'; - testResult.message = err.message; - testResult.show = true; - } finally { - testing.value = false; - } -} - -// 保存配置 -async function saveConfig() { - // 手动验证所有订阅链接 - for (let i = 0; i < config.subscriptions_config.length; i++) { - const sub = config.subscriptions_config[i]; - if (!sub.url || !isValidUrl(sub.url)) { - error.value = `订阅配置 ${i + 1} 中的URL无效或为空`; - // 展开对应的面板以提示用户 - // 注意:这需要给 v-expansion-panel 设置一个 ref 或者 model 来控制展开状态 - return; - } - } - - if (!isFormValid.value) { - error.value = '请修正表单中的错误'; - return - } - - saving.value = true; - error.value = null; - - try { - await new Promise(resolve => setTimeout(resolve, 1000)); - emit('save', {...config}); - } catch (err) { - console.error('保存配置失败:', err); - error.value = err.message || '保存配置失败'; - } finally { - saving.value = false; - } -} - -const toggleSecret = (index) => { - showSecrets.value[index] = !showSecrets.value[index]; -}; -const addClashConfig = () => { - const newIndex = config.clash_dashboards.length; - config.clash_dashboards.push({url: '', secret: ''}); - showSecrets.value[newIndex] = false; -}; - -const removeClashConfig = (index) => { - config.clash_dashboards.splice(index, 1); - delete showSecrets.value[index]; - - // 如果删除的是当前激活项,重置激活 - if (config.active_dashboard === index) { - config.active_dashboard = config.clash_dashboards.length > 0 ? 0 : null; - } -}; - -const addSubscriptionConfig = () => { - config.subscriptions_config.push({ - url: '', - rules: false, - 'proxies': true, - 'proxy-groups': false, - 'rule-providers': false, - 'proxy-providers': false - }); -}; - -const removeSubscriptionConfig = (index) => { - config.subscriptions_config.splice(index, 1); -}; - -function openClashTemplateDialog() { - clashTemplateContent.value = config.clash_template; - clashTemplateDialog.value = true; -} - -function saveClashTemplate() { - config.clash_template = clashTemplateContent.value; - clashTemplateDialog.value = false; -} - -// 重置表单 -function resetForm() { - Object.keys(defaultConfig).forEach(key => { - config[key] = defaultConfig[key]; - }); - - if (form.value) { - form.value.resetValidation(); - } -} - -// 关闭组件 -function notifyClose() { - emit('close'); -} - -// 通知主应用切换到Page页面 -function notifySwitch() { - emit('switch'); -} - -return (_ctx, _cache) => { - const _component_v_alert = _resolveComponent("v-alert"); - const _component_v_card_title = _resolveComponent("v-card-title"); - const _component_v_icon = _resolveComponent("v-icon"); - const _component_v_btn = _resolveComponent("v-btn"); - const _component_v_card_item = _resolveComponent("v-card-item"); - const _component_v_switch = _resolveComponent("v-switch"); - const _component_v_col = _resolveComponent("v-col"); - const _component_v_row = _resolveComponent("v-row"); - const _component_v_text_field = _resolveComponent("v-text-field"); - const _component_v_select = _resolveComponent("v-select"); - const _component_v_tab = _resolveComponent("v-tab"); - const _component_v_tabs = _resolveComponent("v-tabs"); - const _component_v_divider = _resolveComponent("v-divider"); - const _component_v_chip = _resolveComponent("v-chip"); - const _component_v_combobox = _resolveComponent("v-combobox"); - const _component_v_spacer = _resolveComponent("v-spacer"); - 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"); - const _component_v_expansion_panels = _resolveComponent("v-expansion-panels"); - const _component_v_window_item = _resolveComponent("v-window-item"); - const _component_v_radio = _resolveComponent("v-radio"); - const _component_v_radio_group = _resolveComponent("v-radio-group"); - const _component_row = _resolveComponent("row"); - const _component_v_cron_field = _resolveComponent("v-cron-field"); - const _component_v_window = _resolveComponent("v-window"); - const _component_v_form = _resolveComponent("v-form"); - const _component_v_card_text = _resolveComponent("v-card-text"); - const _component_v_card_actions = _resolveComponent("v-card-actions"); - const _component_v_card = _resolveComponent("v-card"); - const _component_v_dialog = _resolveComponent("v-dialog"); - - return (_openBlock(), _createElementBlock(_Fragment, null, [ - _createElementVNode("div", _hoisted_1, [ - (error.value) - ? (_openBlock(), _createBlock(_component_v_alert, { - key: 0, - type: "error", - class: "mb-4" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(error.value), 1) - ]), - _: 1 - })) - : _createCommentVNode("", true), - _createVNode(_component_v_card, null, { - default: _withCtx(() => [ - _createVNode(_component_v_card_item, null, { - append: _withCtx(() => [ - _createVNode(_component_v_btn, { - icon: "", - color: "primary", - variant: "text", - onClick: notifyClose - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[29] || (_cache[29] = [ - _createTextVNode("mdi-close") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { - default: _withCtx(() => _cache[28] || (_cache[28] = [ - _createTextVNode("Clash Rule Provider 插件配置") - ])), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_card_text, { class: "overflow-y-auto" }, { - default: _withCtx(() => [ - _createVNode(_component_v_form, { - ref_key: "form", - ref: form, - modelValue: isFormValid.value, - "onUpdate:modelValue": _cache[22] || (_cache[22] = $event => ((isFormValid).value = $event)), - onSubmit: _withModifiers(saveConfig, ["prevent"]) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: config.enabled, - "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((config.enabled) = $event)), - label: "启用插件", - color: "primary", - inset: "", - density: "compact", - hint: "启用插件" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: config.proxy, - "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => ((config.proxy) = $event)), - label: "启用代理", - color: "primary", - inset: "", - density: "compact", - hint: "是否使用系统代理进行网络请求" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: config.notify, - "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ((config.notify) = $event)), - label: "启用通知", - color: "primary", - inset: "", - density: "compact", - 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: "", - density: "compact", - hint: "定期自动更新 Clash 订阅配置" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "4" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: config.movie_pilot_url, - "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ((config.movie_pilot_url) = $event)), - label: "MoviePilot URL", - variant: "outlined", - placeholder: "http://localhost:3001", - hint: "MoviePilot 服务的访问地址", - rules: [v => !!v || 'MoviePilot URL不能为空', v => isValidUrl(v) || '请输入有效的URL地址'] - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "success" }, { - default: _withCtx(() => _cache[30] || (_cache[30] = [ - _createTextVNode("mdi-movie") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "4" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: config.apikey, - "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ((config.apikey) = $event)), - label: "API Key", - variant: "outlined", - placeholder: "留空使用系统 API Key", - hint: "用于服务认证的 API Key" - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "warning" }, { - default: _withCtx(() => _cache[31] || (_cache[31] = [ - _createTextVNode("mdi-key") - ])), - _: 1 - }) - ]), - "append-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { - color: "primary", - class: "cursor-pointer", - onClick: generateApiKey - }, { - default: _withCtx(() => _cache[32] || (_cache[32] = [ - _createTextVNode(" mdi-autorenew ") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "4" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_select, { - modelValue: config.dashboard_components, - "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((config.dashboard_components) = $event)), - items: dashboardComponents, - label: "仪表盘组件", - "hide-details": "", - variant: "outlined", - multiple: "", - chips: "", - class: "mb-4", - hint: "添加仪表盘组件" - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[33] || (_cache[33] = [ - _createTextVNode("mdi-view-dashboard") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_tabs, { - modelValue: activeTab.value, - "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => ((activeTab).value = $event)), - class: "mt-4", - grow: "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_tab, { value: "subscription" }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[34] || (_cache[34] = [ - _createTextVNode("mdi-link-variant") - ])), - _: 1 - }), - _cache[35] || (_cache[35] = _createTextVNode(" 订阅配置 ")) - ]), - _: 1 - }), - _createVNode(_component_v_tab, { value: "clash" }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[36] || (_cache[36] = [ - _createTextVNode("mdi-application-brackets") - ])), - _: 1 - }), - _cache[37] || (_cache[37] = _createTextVNode(" Clash API 配置 ")) - ]), - _: 1 - }), - _createVNode(_component_v_tab, { value: "execution" }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[38] || (_cache[38] = [ - _createTextVNode("mdi-play-circle") - ])), - _: 1 - }), - _cache[39] || (_cache[39] = _createTextVNode(" 执行设置 ")) - ]), - _: 1 - }), - _createVNode(_component_v_tab, { value: "settings" }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[40] || (_cache[40] = [ - _createTextVNode("mdi-cog") - ])), - _: 1 - }), - _cache[41] || (_cache[41] = _createTextVNode(" 高级选项 ")) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]), - _createVNode(_component_v_divider), - _createVNode(_component_v_window, { - modelValue: activeTab.value, - "onUpdate:modelValue": _cache[21] || (_cache[21] = $event => ((activeTab).value = $event)), - class: "pa-4" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_window_item, { value: "subscription" }, { - default: _withCtx(() => [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: config.group_by_country, - "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => ((config.group_by_country) = $event)), - label: "按国家分组节点", - color: "primary", - inset: "", - hint: "启用后,根据名称将节点添加到代理组" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: config.group_by_region, - "onUpdate:modelValue": _cache[9] || (_cache[9] = $event => ((config.group_by_region) = $event)), - label: "按大洲分组节点", - color: "primary", - inset: "", - hint: "启用后,根据名称将节点添加到代理组" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { cols: "12" }, { - default: _withCtx(() => [ - _createVNode(_component_v_combobox, { - modelValue: config.filter_keywords, - "onUpdate:modelValue": _cache[10] || (_cache[10] = $event => ((config.filter_keywords) = $event)), - label: "节点过滤关键词", - variant: "outlined", - multiple: "", - chips: "", - "closable-chips": "", - clearable: "", - hint: "添加用于过滤节点的关键词" - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[42] || (_cache[42] = [ - _createTextVNode("mdi-filter") - ])), - _: 1 - }) - ]), - chip: _withCtx(({ props, item }) => [ - _createVNode(_component_v_chip, _mergeProps(props, { - closable: "", - size: "small", - color: "info" - }), { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.value), 1) - ]), - _: 2 - }, 1040) - ]), - _: 1 - }, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_divider), - _createVNode(_component_v_col, { cols: "12" }, { - default: _withCtx(() => [ - _createVNode(_component_v_expansion_panels, { multiple: "" }, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(config.subscriptions_config, (item, index) => { - return (_openBlock(), _createBlock(_component_v_expansion_panel, { key: index }, { - default: _withCtx(() => [ - _createVNode(_component_v_expansion_panel_title, null, { - default: _withCtx(() => [ - _createElementVNode("span", _hoisted_2, " 订阅配置 " + _toDisplayString(index + 1), 1), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: _withModifiers($event => (removeSubscriptionConfig(index)), ["stop"]) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[43] || (_cache[43] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_expansion_panel_text, null, { - default: _withCtx(() => [ - _createVNode(_component_v_row, { dense: "" }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { cols: "12" }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: item.url, - "onUpdate:modelValue": $event => ((item.url) = $event), - label: "订阅链接", - variant: "underlined", - placeholder: "https://xxx.com/clash/config.yaml", - density: "compact", - rules: [v => !!v || '订阅链接不能为空', v => isValidUrl(v) || '请输入有效的URL地址'] - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "primary" }, { - default: _withCtx(() => _cache[44] || (_cache[44] = [ - _createTextVNode("mdi-link") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["modelValue", "onUpdate:modelValue", "rules"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_col, { - cols: "12", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: item.rules, - "onUpdate:modelValue": $event => ((item.rules) = $event), - label: "保留规则", - color: "primary", - density: "compact" - }, null, 8, ["modelValue", "onUpdate:modelValue"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_col, { - cols: "12", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: item['rule-providers'], - "onUpdate:modelValue": $event => ((item['rule-providers']) = $event), - label: "保留规则集合", - color: "primary", - density: "compact" - }, null, 8, ["modelValue", "onUpdate:modelValue"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_col, { - cols: "12", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: item['proxy-groups'], - "onUpdate:modelValue": $event => ((item['proxy-groups']) = $event), - label: "保留代理组", - color: "primary", - density: "compact" - }, null, 8, ["modelValue", "onUpdate:modelValue"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_col, { - cols: "12", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: item['proxy-providers'], - "onUpdate:modelValue": $event => ((item['proxy-providers']) = $event), - label: "保留代理集合", - color: "primary", - density: "compact" - }, null, 8, ["modelValue", "onUpdate:modelValue"]) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024)) - }), 128)) - ]), - _: 1 - }), - _createVNode(_component_v_row, { - dense: "", - justify: "space-between" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - size: "small", - color: "primary", - variant: "tonal", - class: "mt-2", - onClick: addSubscriptionConfig - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[45] || (_cache[45] = [ - _createTextVNode("mdi-plus") - ])), - _: 1 - }), - _cache[46] || (_cache[46] = _createTextVNode(" 添加 ")) - ]), - _: 1 - }), - _createVNode(_component_v_btn, { - size: "small", - color: "primary", - variant: "tonal", - class: "mt-2", - onClick: openClashTemplateDialog - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[47] || (_cache[47] = [ - _createTextVNode("mdi-import") - ])), - _: 1 - }), - _cache[48] || (_cache[48] = _createTextVNode(" 配置模板 ")) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_window_item, { value: "clash" }, { - default: _withCtx(() => [ - _createVNode(_component_v_alert, { - "border-color": "info", - text: "", - variant: "tonal", - border: "start" - }, { - default: _withCtx(() => _cache[49] || (_cache[49] = [ - _createTextVNode(" Clash 访问地址用于通知 Clash 更新规则集; 选中的面板用于小组件显示 ") - ])), - _: 1 - }), - _createVNode(_component_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { cols: "12" }, { - default: _withCtx(() => [ - _createVNode(_component_v_radio_group, { - modelValue: config.active_dashboard, - "onUpdate:modelValue": _cache[11] || (_cache[11] = $event => ((config.active_dashboard) = $event)) - }, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(config.clash_dashboards, (item, index) => { - return (_openBlock(), _createBlock(_component_v_row, { key: index }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "2", - md: "1", - class: "d-flex align-center" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_radio, { - value: index, - color: "primary", - label: "" - }, null, 8, ["value"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_col, { - cols: "10", - md: "5" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: item.url, - "onUpdate:modelValue": $event => ((item.url) = $event), - label: "API URL", - variant: "outlined", - placeholder: "http://localhost:9090", - density: "compact", - rules: [v => !v || isValidUrl(v) || '请输入有效的URL地址'] - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "primary" }, { - default: _withCtx(() => _cache[50] || (_cache[50] = [ - _createTextVNode("mdi-web") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["modelValue", "onUpdate:modelValue", "rules"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_col, { - cols: "10", - md: "5" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: item.secret, - "onUpdate:modelValue": $event => ((item.secret) = $event), - label: "API 密钥", - variant: "outlined", - placeholder: "your-clash-secret", - density: "compact", - "append-inner-icon": showSecrets.value[index] ? 'mdi-eye-off' : 'mdi-eye', - type: showSecrets.value[index] ? 'text' : 'password', - "onClick:appendInner": $event => (toggleSecret(index)) - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "warning" }, { - default: _withCtx(() => _cache[51] || (_cache[51] = [ - _createTextVNode("mdi-key") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["modelValue", "onUpdate:modelValue", "append-inner-icon", "type", "onClick:appendInner"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_col, { - cols: "2", - md: "1", - class: "d-flex align-center" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - icon: "", - color: "error", - variant: "text", - onClick: $event => (removeClashConfig(index)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[52] || (_cache[52] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024)) - }), 128)) - ]), - _: 1 - }, 8, ["modelValue"]), - _createVNode(_component_v_btn, { - size: "small", - color: "primary", - variant: "tonal", - class: "mt-2", - onClick: addClashConfig - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[53] || (_cache[53] = [ - _createTextVNode("mdi-plus") - ])), - _: 1 - }), - _cache[54] || (_cache[54] = _createTextVNode(" 添加 ")) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_window_item, { value: "execution" }, { - default: _withCtx(() => [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_cron_field, { - modelValue: config.cron_string, - "onUpdate:modelValue": _cache[12] || (_cache[12] = $event => ((config.cron_string) = $event)), - label: "执行周期", - placeholder: "0 4 * * *", - hint: "使用标准Cron表达式格式 (分 时 日 月 周)" - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[55] || (_cache[55] = [ - _createTextVNode("mdi-clock-time-four-outline") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: config.timeout, - "onUpdate:modelValue": _cache[13] || (_cache[13] = $event => ((config.timeout) = $event)), - modelModifiers: { number: true }, - label: "超时时间", - variant: "outlined", - type: "number", - min: "1", - max: "300", - suffix: "秒", - hint: "请求的超时时间", - rules: [v => v > 0 || '超时时间必须大于0'] - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: config.retry_times, - "onUpdate:modelValue": _cache[14] || (_cache[14] = $event => ((config.retry_times) = $event)), - modelModifiers: { number: true }, - label: "重试次数", - variant: "outlined", - type: "number", - min: "0", - max: "10", - hint: "失败时的重试次数", - rules: [v => v >= 0 || '重试次数不能为负数'] - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[56] || (_cache[56] = [ - _createTextVNode("mdi-refresh") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: config.refresh_delay, - "onUpdate:modelValue": _cache[15] || (_cache[15] = $event => ((config.refresh_delay) = $event)), - modelModifiers: { number: true }, - label: "刷新延迟", - variant: "outlined", - type: "number", - min: "1", - max: "30", - suffix: "秒", - hint: "通知Clash刷新规则集的延迟时间", - rules: [v => v >= 0 || '刷新延迟不能为负数'] - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[57] || (_cache[57] = [ - _createTextVNode("mdi-clock-outline") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue", "rules"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_window_item, { value: "settings" }, { - default: _withCtx(() => [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: config.hint_geo_dat, - "onUpdate:modelValue": _cache[16] || (_cache[16] = $event => ((config.hint_geo_dat) = $event)), - label: "Geo规则补全", - color: "primary", - inset: "", - hint: "获取官方Geo数据库, 并在输入时补全" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: config.enable_acl4ssr, - "onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((config.enable_acl4ssr) = $event)), - label: "ACL4SSR规则集", - color: "primary", - inset: "", - hint: "启用ACL4SSR规则集" - }, 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_text_field, { - modelValue: config.ruleset_prefix, - "onUpdate:modelValue": _cache[18] || (_cache[18] = $event => ((config.ruleset_prefix) = $event)), - label: "规则集前缀", - variant: "outlined", - placeholder: "📂<=", - hint: "为生成的规则集添加前缀" - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "info" }, { - default: _withCtx(() => _cache[58] || (_cache[58] = [ - _createTextVNode("mdi-palette") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: config.acl4ssr_prefix, - "onUpdate:modelValue": _cache[19] || (_cache[19] = $event => ((config.acl4ssr_prefix) = $event)), - label: "ACL4SSR 规则集前缀", - variant: "outlined", - placeholder: "🗂️=>", - hint: "ACL4SSR 规则集前缀" - }, { - "prepend-inner": _withCtx(() => [ - _createVNode(_component_v_icon, { color: "primary" }, { - default: _withCtx(() => _cache[59] || (_cache[59] = [ - _createTextVNode("mdi-palette") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "12" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_combobox, { - modelValue: config.best_cf_ip, - "onUpdate:modelValue": _cache[20] || (_cache[20] = $event => ((config.best_cf_ip) = $event)), - label: "Cloudflare CDN 优选 IPs", - variant: "outlined", - multiple: "", - chips: "", - "closable-chips": "", - clearable: "", - hint: "用于设置 Hosts 中的 Cloudflare 域名", - rules: [validateIPs] - }, { - 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"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]) - ]), - _: 1 - }, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_alert, { - type: "info", - text: "", - variant: "tonal" - }, { - default: _withCtx(() => _cache[60] || (_cache[60] = [ - _createTextVNode(" 配置说明参考: "), - _createElementVNode("a", { - href: "https://github.com/wumode/MoviePilot-Plugins/tree/main/plugins.v2/clashruleprovider/README.md", - target: "_blank", - style: {"text-decoration":"underline"} - }, "README", -1) - ])), - _: 1 - }), - _createVNode(_component_v_card_actions, null, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - color: "primary", - onClick: notifySwitch - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[61] || (_cache[61] = [ - _createTextVNode("mdi-view-dashboard-edit") - ])), - _: 1 - }), - _cache[62] || (_cache[62] = _createTextVNode(" 规则 ")) - ]), - _: 1 - }), - _createVNode(_component_v_btn, { - color: "secondary", - onClick: resetForm - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[63] || (_cache[63] = [ - _createTextVNode("mdi-autorenew") - ])), - _: 1 - }), - _cache[64] || (_cache[64] = _createTextVNode(" 重置 ")) - ]), - _: 1 - }), - _createVNode(_component_v_btn, { - color: "info", - onClick: testConnection, - loading: testing.value - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[65] || (_cache[65] = [ - _createTextVNode("mdi-connection") - ])), - _: 1 - }), - _cache[66] || (_cache[66] = _createTextVNode(" 测试连接 ")) - ]), - _: 1 - }, 8, ["loading"]), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - color: "primary", - disabled: !isFormValid.value, - onClick: saveConfig, - loading: saving.value - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[67] || (_cache[67] = [ - _createTextVNode("mdi-content-save") - ])), - _: 1 - }), - _cache[68] || (_cache[68] = _createTextVNode(" 保存配置 ")) - ]), - _: 1 - }, 8, ["disabled", "loading"]) - ]), - _: 1 - }), - (testResult.show) - ? (_openBlock(), _createBlock(_component_v_alert, { - key: 0, - type: testResult.success ? 'success' : 'error', - variant: "tonal", - closable: "", - class: "ma-4 mt-0", - "onClick:close": _cache[23] || (_cache[23] = $event => (testResult.show = false)) - }, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_3, [ - _createVNode(_component_v_icon, { class: "mr-2" }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(testResult.success ? 'mdi-check-circle' : 'mdi-alert-circle'), 1) - ]), - _: 1 - }), - _createElementVNode("div", null, [ - _createElementVNode("div", _hoisted_4, _toDisplayString(testResult.title), 1), - _createElementVNode("div", _hoisted_5, _toDisplayString(testResult.message), 1) - ]) - ]) - ]), - _: 1 - }, 8, ["type"])) - : _createCommentVNode("", true) - ]), - _: 1 - }) - ]), - _createVNode(_component_v_dialog, { - modelValue: clashTemplateDialog.value, - "onUpdate:modelValue": _cache[27] || (_cache[27] = $event => ((clashTemplateDialog).value = $event)), - "max-width": "600" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, null, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { - default: _withCtx(() => _cache[69] || (_cache[69] = [ - _createTextVNode("Clash 配置模板") - ])), - _: 1 - }), - _createVNode(_component_v_card_text, { style: {"max-height":"900px","overflow-y":"auto"} }, { - default: _withCtx(() => [ - _createVNode(_component_v_select, { - modelValue: clashTemplateType.value, - "onUpdate:modelValue": _cache[24] || (_cache[24] = $event => ((clashTemplateType).value = $event)), - items: ['YAML'], - label: "配置类型", - class: "mb-4" - }, null, 8, ["modelValue"]), - _createVNode(_unref(VAceEditor), { - value: clashTemplateContent.value, - "onUpdate:value": _cache[25] || (_cache[25] = $event => ((clashTemplateContent).value = $event)), - lang: "yaml", - theme: "monokai", - hint: "", - options: editorOptions, - placeholder: configPlaceholder.value, - style: {"height":"30rem","width":"100%","margin-bottom":"16px"} - }, null, 8, ["value", "placeholder"]), - _createVNode(_component_v_alert, { - type: "info", - dense: "", - text: "", - class: "mb-4", - variant: "tonal" - }, { - default: _withCtx(() => _cache[70] || (_cache[70] = [ - _createTextVNode("规则和出站代理会被添加在配置模板上 ") - ])), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_card_actions, null, { - default: _withCtx(() => [ - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - text: "", - onClick: _cache[26] || (_cache[26] = $event => (clashTemplateDialog.value = false)) - }, { - default: _withCtx(() => _cache[71] || (_cache[71] = [ - _createTextVNode("取消") - ])), - _: 1 - }), - _createVNode(_component_v_btn, { - color: "primary", - onClick: saveClashTemplate - }, { - default: _withCtx(() => _cache[72] || (_cache[72] = [ - _createTextVNode("确定") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]) - ], 64)) -} -} - -}; -const ConfigComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-5f383f33"]]); - -export { ConfigComponent as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CY46uj5g.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CY46uj5g.js new file mode 100644 index 0000000..9e94a0e --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CY46uj5g.js @@ -0,0 +1,1479 @@ +import { importShared } from './__federation_fn_import-JrT3xvdd.js'; +import { V as VAceEditor } from './theme-monokai-CF_yROe-.js'; +import { f as isValidUrl, v as validateIPs, _ as _export_sfc } from './_plugin-vue_export-helper-D32QZFxh.js'; + +const {defineComponent:_defineComponent} = await importShared('vue'); + +const {toDisplayString:_toDisplayString,createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,openBlock:_openBlock,createBlock:_createBlock,createCommentVNode:_createCommentVNode,createVNode:_createVNode,unref:_unref,mergeProps:_mergeProps,renderList:_renderList,Fragment:_Fragment,createElementBlock:_createElementBlock,createElementVNode:_createElementVNode,withModifiers:_withModifiers} = await importShared('vue'); + +const _hoisted_1 = { class: "plugin-config" }; +const _hoisted_2 = { class: "text-subtitle-1 font-weight-medium" }; +const _hoisted_3 = { class: "d-flex align-center" }; +const _hoisted_4 = { class: "font-weight-medium" }; +const _hoisted_5 = { class: "text-body-2" }; +const {ref,reactive,onMounted,computed} = await importShared('vue'); +const _sfc_main = /* @__PURE__ */ _defineComponent({ + __name: "Config", + props: { + initialConfig: { + type: Object, + default: () => ({}) + }, + api: { + type: Object, + default: () => { + } + } + }, + emits: ["save", "close", "switch"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const activeTab = ref("subscription"); + const editorOptions = { + enableBasicAutocompletion: true, + enableSnippets: true, + enableLiveAutocompletion: true, + showLineNumbers: true, + tabSize: 2 + }; + const configPlaceholder = ref( + `profile: + store-selected: true +mode: rule +log-level: silent` + ); + const clashTemplateDialog = ref(false); + const clashTemplateType = ref("YAML"); + const clashTemplateContent = ref(""); + const form = ref(null); + const isFormValid = ref(true); + const error = ref(""); + const saving = ref(false); + const testing = ref(false); + const dashboardComponents = ["Clash Info", "Traffic Stats"]; + const showSecrets = ref({ 0: false }); + const testResult = reactive({ + show: false, + success: false, + title: "", + message: "" + }); + const defaultConfig = { + enabled: false, + subscriptions_config: [], + filter_keywords: ["公益性", "高延迟", "域名", "官网", "重启", "过期时间", "系统代理"], + clash_dashboards: [{ url: "", secret: "" }], + movie_pilot_url: "", + cron_string: "0 */6 * * *", + timeout: 10, + retry_times: 3, + proxy: false, + notify: false, + auto_update_subscriptions: true, + ruleset_prefix: "📂<=", + acl4ssr_prefix: "🗂️=>", + group_by_region: false, + group_by_country: false, + refresh_delay: 5, + enable_acl4ssr: false, + dashboard_components: [], + clash_template: "", + hint_geo_dat: false, + best_cf_ip: [], + active_dashboard: 0, + apikey: null, + identifiers: [], + cache_ttl: 3600 + }; + const config = reactive({ ...defaultConfig }); + onMounted(() => { + if (props.initialConfig) { + Object.keys(props.initialConfig).forEach((key) => { + if (key in config) { + config[key] = props.initialConfig[key]; + } + }); + } + }); + const sub_links = computed(() => { + if (!config.subscriptions_config) { + return []; + } + return config.subscriptions_config.map((item) => item.url); + }); + const generateApiKey = () => { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let key = ""; + for (let i = 0; i < 32; i++) { + key += chars.charAt(Math.floor(Math.random() * chars.length)); + } + config.apikey = key; + }; + function showError(title, msg) { + testResult.title = title; + testResult.success = false; + testResult.message = msg; + testResult.show = true; + } + async function testConnection() { + testing.value = true; + error.value = ""; + testResult.show = false; + try { + if (sub_links.value.length === 0) { + showError("连接测试失败", "请先配置至少一个订阅链接"); + } + const testParams = { + clash_apis: config.clash_dashboards, + sub_links: sub_links.value + }; + const result = await props.api.post("/plugin/ClashRuleProvider/connectivity", testParams); + if (result.success) { + testResult.success = true; + testResult.title = "连接测试成功!"; + testResult.message = "Clash面板和订阅链接连接正常,配置验证通过"; + testResult.show = true; + setTimeout(() => { + testResult.show = false; + }, 5e3); + } else { + showError("连接测试失败", result.message || "连接测试失败,请检查配置"); + } + } catch (err) { + if (err instanceof Error) showError("连接测试失败", err.message); + } finally { + testing.value = false; + } + } + async function saveConfig() { + for (let i = 0; i < config.subscriptions_config.length; i++) { + const sub = config.subscriptions_config[i]; + if (!sub.url || !isValidUrl(sub.url)) { + error.value = `订阅配置 ${i + 1} 中的URL无效或为空`; + return; + } + } + if (!isFormValid.value) { + error.value = "请修正表单中的错误"; + return; + } + saving.value = true; + error.value = ""; + try { + await new Promise((resolve) => setTimeout(resolve, 1e3)); + emit("save", { ...config }); + } catch (err) { + if (err instanceof Error) error.value = err.message || "保存配置失败"; + } finally { + saving.value = false; + } + } + const toggleSecret = (index) => { + showSecrets.value[index] = !showSecrets.value[index]; + }; + const addClashConfig = () => { + const newIndex = config.clash_dashboards.length; + config.clash_dashboards.push({ url: "", secret: "" }); + showSecrets.value[newIndex] = false; + }; + const removeClashConfig = (index) => { + config.clash_dashboards.splice(index, 1); + delete showSecrets.value[index]; + if (config.active_dashboard === index) { + config.active_dashboard = config.clash_dashboards.length > 0 ? 0 : null; + } + }; + const addSubscriptionConfig = () => { + config.subscriptions_config.push({ + url: "", + rules: false, + proxies: true, + "proxy-groups": false, + "rule-providers": false, + "proxy-providers": false + }); + }; + const removeSubscriptionConfig = (index) => { + config.subscriptions_config.splice(index, 1); + }; + function openClashTemplateDialog() { + clashTemplateContent.value = config.clash_template; + clashTemplateDialog.value = true; + } + function saveClashTemplate() { + config.clash_template = clashTemplateContent.value; + clashTemplateDialog.value = false; + } + function resetForm() { + Object.assign(config, JSON.parse(JSON.stringify(defaultConfig))); + if (form.value) { + form.value.resetValidation(); + } + } + return (_ctx, _cache) => { + const _component_v_alert = _resolveComponent("v-alert"); + const _component_v_card_title = _resolveComponent("v-card-title"); + const _component_v_icon = _resolveComponent("v-icon"); + const _component_v_btn = _resolveComponent("v-btn"); + const _component_v_card_item = _resolveComponent("v-card-item"); + const _component_v_switch = _resolveComponent("v-switch"); + const _component_v_col = _resolveComponent("v-col"); + const _component_v_row = _resolveComponent("v-row"); + const _component_v_text_field = _resolveComponent("v-text-field"); + const _component_v_select = _resolveComponent("v-select"); + const _component_v_tab = _resolveComponent("v-tab"); + const _component_v_tabs = _resolveComponent("v-tabs"); + const _component_v_divider = _resolveComponent("v-divider"); + const _component_v_chip = _resolveComponent("v-chip"); + const _component_v_combobox = _resolveComponent("v-combobox"); + const _component_v_spacer = _resolveComponent("v-spacer"); + 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"); + const _component_v_expansion_panels = _resolveComponent("v-expansion-panels"); + const _component_v_window_item = _resolveComponent("v-window-item"); + const _component_v_radio = _resolveComponent("v-radio"); + const _component_v_radio_group = _resolveComponent("v-radio-group"); + const _component_v_cron_field = _resolveComponent("v-cron-field"); + const _component_v_window = _resolveComponent("v-window"); + const _component_v_form = _resolveComponent("v-form"); + const _component_v_card_text = _resolveComponent("v-card-text"); + const _component_v_card_actions = _resolveComponent("v-card-actions"); + const _component_v_card = _resolveComponent("v-card"); + const _component_v_dialog = _resolveComponent("v-dialog"); + return _openBlock(), _createElementBlock(_Fragment, null, [ + _createElementVNode("div", _hoisted_1, [ + error.value ? (_openBlock(), _createBlock(_component_v_alert, { + key: 0, + type: "error", + class: "mb-4" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(error.value), 1) + ]), + _: 1 + })) : _createCommentVNode("", true), + _createVNode(_component_v_card, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card_item, null, { + append: _withCtx(() => [ + _createVNode(_component_v_btn, { + icon: "", + color: "primary", + variant: "text", + onClick: _cache[0] || (_cache[0] = ($event) => emit("close")) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[33] || (_cache[33] = [ + _createTextVNode("mdi-close") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + default: _withCtx(() => [ + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => _cache[32] || (_cache[32] = [ + _createTextVNode("Clash Rule Provider 插件配置") + ])), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_card_text, { class: "overflow-y-auto" }, { + default: _withCtx(() => [ + _createVNode(_component_v_form, { + ref_key: "form", + ref: form, + modelValue: isFormValid.value, + "onUpdate:modelValue": _cache[25] || (_cache[25] = ($event) => isFormValid.value = $event), + onSubmit: _withModifiers(saveConfig, ["prevent"]) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.enabled, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => config.enabled = $event), + label: "启用插件", + color: "primary", + inset: "", + density: "compact", + hint: "启用插件" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.proxy, + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => config.proxy = $event), + label: "启用代理", + color: "primary", + inset: "", + density: "compact", + hint: "是否使用系统代理进行网络请求" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.notify, + "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => config.notify = $event), + label: "启用通知", + color: "primary", + inset: "", + density: "compact", + hint: "执行完成后发送通知消息" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.auto_update_subscriptions, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => config.auto_update_subscriptions = $event), + label: "自动更新订阅", + color: "primary", + inset: "", + density: "compact", + hint: "定期自动更新 Clash 订阅配置" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.movie_pilot_url, + "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => config.movie_pilot_url = $event), + label: "MoviePilot URL", + variant: "outlined", + placeholder: "http://localhost:3001", + hint: "MoviePilot 服务的访问地址", + rules: [ + (v) => !!v || "MoviePilot URL不能为空", + (v) => _unref(isValidUrl)(v) || "请输入有效的URL地址" + ] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "success" }, { + default: _withCtx(() => _cache[34] || (_cache[34] = [ + _createTextVNode("mdi-movie") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.apikey, + "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => config.apikey = $event), + label: "API Key", + variant: "outlined", + placeholder: "留空使用系统 API Key", + hint: "用于服务认证的 API Key" + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "warning" }, { + default: _withCtx(() => _cache[35] || (_cache[35] = [ + _createTextVNode("mdi-key") + ])), + _: 1 + }) + ]), + "append-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { + color: "primary", + class: "cursor-pointer", + onClick: generateApiKey + }, { + default: _withCtx(() => _cache[36] || (_cache[36] = [ + _createTextVNode(" mdi-autorenew ") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_select, { + modelValue: config.dashboard_components, + "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => config.dashboard_components = $event), + items: dashboardComponents, + label: "仪表盘组件", + "hide-details": "", + variant: "outlined", + multiple: "", + chips: "", + class: "mb-4", + hint: "添加仪表盘组件" + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "info" }, { + default: _withCtx(() => _cache[37] || (_cache[37] = [ + _createTextVNode("mdi-view-dashboard") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_tabs, { + modelValue: activeTab.value, + "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => activeTab.value = $event), + class: "mt-4", + grow: "" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_tab, { value: "subscription" }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[38] || (_cache[38] = [ + _createTextVNode("mdi-link-variant") + ])), + _: 1 + }), + _cache[39] || (_cache[39] = _createTextVNode(" 订阅配置 ")) + ]), + _: 1 + }), + _createVNode(_component_v_tab, { value: "clash" }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[40] || (_cache[40] = [ + _createTextVNode("mdi-application-brackets") + ])), + _: 1 + }), + _cache[41] || (_cache[41] = _createTextVNode(" Clash API 配置 ")) + ]), + _: 1 + }), + _createVNode(_component_v_tab, { value: "execution" }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[42] || (_cache[42] = [ + _createTextVNode("mdi-play-circle") + ])), + _: 1 + }), + _cache[43] || (_cache[43] = _createTextVNode(" 执行设置 ")) + ]), + _: 1 + }), + _createVNode(_component_v_tab, { value: "settings" }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[44] || (_cache[44] = [ + _createTextVNode("mdi-cog") + ])), + _: 1 + }), + _cache[45] || (_cache[45] = _createTextVNode(" 高级选项 ")) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createVNode(_component_v_divider), + _createVNode(_component_v_window, { + modelValue: activeTab.value, + "onUpdate:modelValue": _cache[24] || (_cache[24] = ($event) => activeTab.value = $event), + class: "pa-4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_window_item, { value: "subscription" }, { + default: _withCtx(() => [ + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.group_by_country, + "onUpdate:modelValue": _cache[9] || (_cache[9] = ($event) => config.group_by_country = $event), + label: "按国家分组节点", + color: "primary", + inset: "", + hint: "启用后,根据名称将节点添加到代理组" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.group_by_region, + "onUpdate:modelValue": _cache[10] || (_cache[10] = ($event) => config.group_by_region = $event), + label: "按大洲分组节点", + color: "primary", + inset: "", + hint: "启用后,根据名称将节点添加到代理组" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_combobox, { + modelValue: config.filter_keywords, + "onUpdate:modelValue": _cache[11] || (_cache[11] = ($event) => config.filter_keywords = $event), + label: "节点过滤关键词", + variant: "outlined", + multiple: "", + chips: "", + "closable-chips": "", + clearable: "", + hint: "添加用于过滤节点的关键词" + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "info" }, { + default: _withCtx(() => _cache[46] || (_cache[46] = [ + _createTextVNode("mdi-filter") + ])), + _: 1 + }) + ]), + chip: _withCtx(({ props: props2, item }) => [ + _createVNode(_component_v_chip, _mergeProps(props2, { + closable: "", + size: "small", + color: "info" + }), { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(item.value), 1) + ]), + _: 2 + }, 1040) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_divider), + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_expansion_panels, { multiple: "" }, { + default: _withCtx(() => [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(config.subscriptions_config, (item, index) => { + return _openBlock(), _createBlock(_component_v_expansion_panel, { key: index }, { + default: _withCtx(() => [ + _createVNode(_component_v_expansion_panel_title, null, { + default: _withCtx(() => [ + _createElementVNode("span", _hoisted_2, " 订阅配置 " + _toDisplayString(index + 1), 1), + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + icon: "", + size: "small", + color: "error", + variant: "text", + onClick: _withModifiers(($event) => removeSubscriptionConfig(index), ["stop"]) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, null, { + default: _withCtx(() => _cache[47] || (_cache[47] = [ + _createTextVNode("mdi-delete") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick"]) + ]), + _: 2 + }, 1024), + _createVNode(_component_v_expansion_panel_text, null, { + default: _withCtx(() => [ + _createVNode(_component_v_row, { dense: "" }, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: item.url, + "onUpdate:modelValue": ($event) => item.url = $event, + label: "订阅链接", + variant: "underlined", + placeholder: "https://xxx.com/clash/config.yaml", + density: "compact", + rules: [ + (v) => !!v || "订阅链接不能为空", + (v) => _unref(isValidUrl)(v) || "请输入有效的 URL 地址" + ] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "primary" }, { + default: _withCtx(() => _cache[48] || (_cache[48] = [ + _createTextVNode("mdi-link") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["modelValue", "onUpdate:modelValue", "rules"]) + ]), + _: 2 + }, 1024), + _createVNode(_component_v_col, { + cols: "12", + md: "3" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: item.rules, + "onUpdate:modelValue": ($event) => item.rules = $event, + label: "保留规则", + color: "primary", + density: "compact" + }, null, 8, ["modelValue", "onUpdate:modelValue"]) + ]), + _: 2 + }, 1024), + _createVNode(_component_v_col, { + cols: "12", + md: "3" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: item["rule-providers"], + "onUpdate:modelValue": ($event) => item["rule-providers"] = $event, + label: "保留规则集合", + color: "primary", + density: "compact" + }, null, 8, ["modelValue", "onUpdate:modelValue"]) + ]), + _: 2 + }, 1024), + _createVNode(_component_v_col, { + cols: "12", + md: "3" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: item["proxy-groups"], + "onUpdate:modelValue": ($event) => item["proxy-groups"] = $event, + label: "保留代理组", + color: "primary", + density: "compact" + }, null, 8, ["modelValue", "onUpdate:modelValue"]) + ]), + _: 2 + }, 1024), + _createVNode(_component_v_col, { + cols: "12", + md: "3" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: item["proxy-providers"], + "onUpdate:modelValue": ($event) => item["proxy-providers"] = $event, + label: "保留代理集合", + color: "primary", + density: "compact" + }, null, 8, ["modelValue", "onUpdate:modelValue"]) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + _: 1 + }), + _createVNode(_component_v_row, { + dense: "", + justify: "space-between" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_btn, { + size: "small", + color: "primary", + variant: "tonal", + class: "mt-2", + onClick: addSubscriptionConfig + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[49] || (_cache[49] = [ + _createTextVNode("mdi-plus") + ])), + _: 1 + }), + _cache[50] || (_cache[50] = _createTextVNode(" 添加 ")) + ]), + _: 1 + }), + _createVNode(_component_v_btn, { + size: "small", + color: "primary", + variant: "tonal", + class: "mt-2", + onClick: openClashTemplateDialog + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[51] || (_cache[51] = [ + _createTextVNode("mdi-import") + ])), + _: 1 + }), + _cache[52] || (_cache[52] = _createTextVNode(" 配置模板 ")) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, { value: "clash" }, { + default: _withCtx(() => [ + _createVNode(_component_v_alert, { + "border-color": "info", + variant: "tonal", + border: "start", + text: "Clash 访问地址用于通知 Clash 更新规则集; 选中的面板用于小组件显示", + class: "mb-3" + }), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_radio_group, { + modelValue: config.active_dashboard, + "onUpdate:modelValue": _cache[12] || (_cache[12] = ($event) => config.active_dashboard = $event) + }, { + default: _withCtx(() => [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(config.clash_dashboards, (item, index) => { + return _openBlock(), _createBlock(_component_v_row, { key: index }, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "2", + md: "1", + class: "d-flex align-center" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_radio, { + value: index, + color: "primary", + label: "" + }, null, 8, ["value"]) + ]), + _: 2 + }, 1024), + _createVNode(_component_v_col, { + cols: "10", + md: "5" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: item.url, + "onUpdate:modelValue": ($event) => item.url = $event, + label: "API URL", + variant: "outlined", + placeholder: "http://localhost:9090", + density: "compact", + rules: [(v) => !v || _unref(isValidUrl)(v) || "请输入有效的URL地址"] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "primary" }, { + default: _withCtx(() => _cache[53] || (_cache[53] = [ + _createTextVNode("mdi-web") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["modelValue", "onUpdate:modelValue", "rules"]) + ]), + _: 2 + }, 1024), + _createVNode(_component_v_col, { + cols: "10", + md: "5" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: item.secret, + "onUpdate:modelValue": ($event) => item.secret = $event, + label: "API 密钥", + variant: "outlined", + placeholder: "your-clash-secret", + density: "compact", + "append-inner-icon": showSecrets.value[index] ? "mdi-eye-off" : "mdi-eye", + type: showSecrets.value[index] ? "text" : "password", + "onClick:appendInner": ($event) => toggleSecret(index) + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "warning" }, { + default: _withCtx(() => _cache[54] || (_cache[54] = [ + _createTextVNode("mdi-key") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["modelValue", "onUpdate:modelValue", "append-inner-icon", "type", "onClick:appendInner"]) + ]), + _: 2 + }, 1024), + _createVNode(_component_v_col, { + cols: "2", + md: "1", + class: "d-flex align-center" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_btn, { + icon: "", + color: "error", + variant: "text", + onClick: ($event) => removeClashConfig(index) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, null, { + default: _withCtx(() => _cache[55] || (_cache[55] = [ + _createTextVNode("mdi-delete") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick"]) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + _: 1 + }, 8, ["modelValue"]), + _createVNode(_component_v_btn, { + size: "small", + color: "primary", + variant: "tonal", + class: "mt-2", + onClick: addClashConfig + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[56] || (_cache[56] = [ + _createTextVNode("mdi-plus") + ])), + _: 1 + }), + _cache[57] || (_cache[57] = _createTextVNode(" 添加 ")) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, { value: "execution" }, { + default: _withCtx(() => [ + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_cron_field, { + modelValue: config.cron_string, + "onUpdate:modelValue": _cache[13] || (_cache[13] = ($event) => config.cron_string = $event), + label: "执行周期", + placeholder: "0 4 * * *", + hint: "使用标准Cron表达式格式 (分 时 日 月 周)" + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "info" }, { + default: _withCtx(() => _cache[58] || (_cache[58] = [ + _createTextVNode("mdi-clock-time-four-outline") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.timeout, + "onUpdate:modelValue": _cache[14] || (_cache[14] = ($event) => config.timeout = $event), + modelModifiers: { number: true }, + label: "超时时间", + variant: "outlined", + type: "number", + min: "1", + max: "300", + suffix: "秒", + hint: "请求的超时时间", + rules: [(v) => v > 0 || "超时时间必须大于0"] + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.retry_times, + "onUpdate:modelValue": _cache[15] || (_cache[15] = ($event) => config.retry_times = $event), + modelModifiers: { number: true }, + label: "重试次数", + variant: "outlined", + type: "number", + min: "0", + max: "10", + hint: "失败时的重试次数", + rules: [(v) => v >= 0 || "重试次数不能为负数"] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "info" }, { + default: _withCtx(() => _cache[59] || (_cache[59] = [ + _createTextVNode("mdi-refresh") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.refresh_delay, + "onUpdate:modelValue": _cache[16] || (_cache[16] = ($event) => config.refresh_delay = $event), + modelModifiers: { number: true }, + label: "刷新延迟", + variant: "outlined", + type: "number", + min: "1", + max: "30", + suffix: "秒", + hint: "通知Clash刷新规则集的延迟时间", + rules: [(v) => v >= 0 || "刷新延迟不能为负数"] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "info" }, { + default: _withCtx(() => _cache[60] || (_cache[60] = [ + _createTextVNode("mdi-clock-outline") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, { value: "settings" }, { + default: _withCtx(() => [ + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.hint_geo_dat, + "onUpdate:modelValue": _cache[17] || (_cache[17] = ($event) => config.hint_geo_dat = $event), + label: "Geo规则补全", + color: "primary", + inset: "", + hint: "获取官方Geo数据库, 并在输入时补全" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.enable_acl4ssr, + "onUpdate:modelValue": _cache[18] || (_cache[18] = ($event) => config.enable_acl4ssr = $event), + label: "ACL4SSR规则集", + color: "primary", + inset: "", + hint: "启用ACL4SSR规则集" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.ruleset_prefix, + "onUpdate:modelValue": _cache[19] || (_cache[19] = ($event) => config.ruleset_prefix = $event), + label: "规则集前缀", + variant: "outlined", + placeholder: "📂<=", + hint: "为生成的规则集添加前缀" + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "info" }, { + default: _withCtx(() => _cache[61] || (_cache[61] = [ + _createTextVNode("mdi-palette") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.acl4ssr_prefix, + "onUpdate:modelValue": _cache[20] || (_cache[20] = ($event) => config.acl4ssr_prefix = $event), + label: "ACL4SSR 规则集前缀", + variant: "outlined", + placeholder: "🗂️=>", + hint: "ACL4SSR 规则集前缀" + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "primary" }, { + default: _withCtx(() => _cache[62] || (_cache[62] = [ + _createTextVNode("mdi-palette") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.cache_ttl, + "onUpdate:modelValue": _cache[21] || (_cache[21] = ($event) => config.cache_ttl = $event), + label: "缓存 TTL", + variant: "outlined", + placeholder: "3600", + type: "number", + min: "600", + suffix: "秒" + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "warning" }, { + default: _withCtx(() => _cache[63] || (_cache[63] = [ + _createTextVNode("mdi-alarm") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "12" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_combobox, { + modelValue: config.best_cf_ip, + "onUpdate:modelValue": _cache[22] || (_cache[22] = ($event) => config.best_cf_ip = $event), + label: "Cloudflare CDN 优选 IPs", + variant: "outlined", + multiple: "", + chips: "", + "closable-chips": "", + clearable: "", + hint: "用于设置 Hosts 中的 Cloudflare 域名", + rules: [_unref(validateIPs)] + }, { + chip: _withCtx(({ props: props2, item }) => [ + _createVNode(_component_v_chip, _mergeProps(props2, { + closable: "", + size: "small" + }), { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(item.value), 1) + ]), + _: 2 + }, 1040) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "12" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_combobox, { + modelValue: config.identifiers, + "onUpdate:modelValue": _cache[23] || (_cache[23] = ($event) => config.identifiers = $event), + label: "预设设备标识", + variant: "outlined", + multiple: "", + chips: "", + "closable-chips": "", + clearable: "", + hint: "获取配置时的额外查询参数 「identifier」" + }, { + chip: _withCtx(({ props: props2, item }) => [ + _createVNode(_component_v_chip, _mergeProps(props2, { + closable: "", + size: "small" + }), { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(item.value), 1) + ]), + _: 2 + }, 1040) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_alert, { + type: "info", + variant: "tonal" + }, { + default: _withCtx(() => _cache[64] || (_cache[64] = [ + _createTextVNode(" 配置说明参考: "), + _createElementVNode("a", { + href: "https://github.com/wumode/MoviePilot-Plugins/tree/main/plugins.v2/clashruleprovider/README.md", + target: "_blank", + style: { "text-decoration": "underline" } + }, "README", -1) + ])), + _: 1 + }), + _createVNode(_component_v_card_actions, null, { + default: _withCtx(() => [ + _createVNode(_component_v_btn, { + color: "primary", + onClick: _cache[26] || (_cache[26] = ($event) => emit("switch")) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[65] || (_cache[65] = [ + _createTextVNode("mdi-view-dashboard-edit") + ])), + _: 1 + }), + _cache[66] || (_cache[66] = _createTextVNode(" 规则 ")) + ]), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "secondary", + onClick: resetForm + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[67] || (_cache[67] = [ + _createTextVNode("mdi-autorenew") + ])), + _: 1 + }), + _cache[68] || (_cache[68] = _createTextVNode(" 重置 ")) + ]), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "info", + loading: testing.value, + onClick: testConnection + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[69] || (_cache[69] = [ + _createTextVNode("mdi-connection") + ])), + _: 1 + }), + _cache[70] || (_cache[70] = _createTextVNode(" 测试连接 ")) + ]), + _: 1 + }, 8, ["loading"]), + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + color: "primary", + disabled: !isFormValid.value, + loading: saving.value, + onClick: saveConfig + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[71] || (_cache[71] = [ + _createTextVNode("mdi-content-save") + ])), + _: 1 + }), + _cache[72] || (_cache[72] = _createTextVNode(" 保存配置 ")) + ]), + _: 1 + }, 8, ["disabled", "loading"]) + ]), + _: 1 + }), + testResult.show ? (_openBlock(), _createBlock(_component_v_alert, { + key: 0, + type: testResult.success ? "success" : "error", + variant: "tonal", + closable: "", + class: "ma-4 mt-0", + "onClick:close": _cache[27] || (_cache[27] = ($event) => testResult.show = false) + }, { + default: _withCtx(() => [ + _createElementVNode("div", _hoisted_3, [ + _createVNode(_component_v_icon, { class: "mr-2" }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(testResult.success ? "mdi-check-circle" : "mdi-alert-circle"), 1) + ]), + _: 1 + }), + _createElementVNode("div", null, [ + _createElementVNode("div", _hoisted_4, _toDisplayString(testResult.title), 1), + _createElementVNode("div", _hoisted_5, _toDisplayString(testResult.message), 1) + ]) + ]) + ]), + _: 1 + }, 8, ["type"])) : _createCommentVNode("", true) + ]), + _: 1 + }) + ]), + _createVNode(_component_v_dialog, { + modelValue: clashTemplateDialog.value, + "onUpdate:modelValue": _cache[31] || (_cache[31] = ($event) => clashTemplateDialog.value = $event), + "max-width": "600" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_card, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => _cache[73] || (_cache[73] = [ + _createTextVNode("Clash 配置模板") + ])), + _: 1 + }), + _createVNode(_component_v_card_text, { style: { "max-height": "900px", "overflow-y": "auto" } }, { + default: _withCtx(() => [ + _createVNode(_component_v_select, { + modelValue: clashTemplateType.value, + "onUpdate:modelValue": _cache[28] || (_cache[28] = ($event) => clashTemplateType.value = $event), + items: ["YAML"], + label: "配置类型", + class: "mb-4" + }, null, 8, ["modelValue"]), + _createVNode(_unref(VAceEditor), { + value: clashTemplateContent.value, + "onUpdate:value": _cache[29] || (_cache[29] = ($event) => clashTemplateContent.value = $event), + lang: "yaml", + theme: "monokai", + hint: "", + options: editorOptions, + placeholder: configPlaceholder.value, + style: { "height": "30rem", "width": "100%", "margin-bottom": "16px" } + }, null, 8, ["value", "placeholder"]), + _createVNode(_component_v_alert, { + type: "info", + dense: "", + class: "mb-4", + variant: "tonal" + }, { + default: _withCtx(() => _cache[74] || (_cache[74] = [ + _createTextVNode("规则和出站代理会被添加在配置模板上 ") + ])), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_card_actions, null, { + default: _withCtx(() => [ + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + onClick: _cache[30] || (_cache[30] = ($event) => clashTemplateDialog.value = false) + }, { + default: _withCtx(() => _cache[75] || (_cache[75] = [ + _createTextVNode("取消") + ])), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "primary", + onClick: saveClashTemplate + }, { + default: _withCtx(() => _cache[76] || (_cache[76] = [ + _createTextVNode("确定") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ], 64); + }; + } +}); + +const ConfigComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-3fef8398"]]); + +export { ConfigComponent as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CwbjkOP2.css b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CwbjkOP2.css new file mode 100644 index 0000000..512599e --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-CwbjkOP2.css @@ -0,0 +1,4 @@ + +.plugin-config[data-v-3fef8398] { + margin: 0 auto; +} diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-D7x82s8Y.css b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-D7x82s8Y.css deleted file mode 100644 index cda78ab..0000000 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-D7x82s8Y.css +++ /dev/null @@ -1,4 +0,0 @@ - -.plugin-config[data-v-5f383f33] { - margin: 0 auto; -} diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-vS9Qm2ZB.css b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-CFBdUa27.css similarity index 51% rename from plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-vS9Qm2ZB.css rename to plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-CFBdUa27.css index 3e49ef2..196e7ee 100644 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-vS9Qm2ZB.css +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-CFBdUa27.css @@ -1,4 +1,4 @@ -.dashboard-widget[data-v-de7a088e] { +.dashboard-widget[data-v-318a5020] { font-family: 'Inter', sans-serif; } diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-BDSt5WaH.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-CybypqLB.js similarity index 95% rename from plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-BDSt5WaH.js rename to plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-CybypqLB.js index 65c146c..af928de 100644 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-BDSt5WaH.js +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-CybypqLB.js @@ -1,5 +1,6 @@ import { importShared } from './__federation_fn_import-JrT3xvdd.js'; -import { _ as _export_sfc } from './_plugin-vue_export-helper-pcqpp-6-.js'; +import { n as formatBytes, _ as _export_sfc } from './_plugin-vue_export-helper-D32QZFxh.js'; +import { M as MetaLogo } from './Meta-1zu2nKV2.js'; /*! * @kurkle/color v0.3.4 @@ -583,7 +584,7 @@ class Color { } /*! - * Chart.js v4.5.0 + * Chart.js v4.5.1 * https://www.chartjs.org * (c) 2025 Chart.js Contributors * Released under the MIT License @@ -2868,10 +2869,10 @@ function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) { * @returns True if the canvas context size or transformation has changed. */ function retinaScale(chart, forceRatio, forceStyle) { const pixelRatio = forceRatio || 1; - const deviceHeight = Math.floor(chart.height * pixelRatio); - const deviceWidth = Math.floor(chart.width * pixelRatio); - chart.height = Math.floor(chart.height); - chart.width = Math.floor(chart.width); + const deviceHeight = round1(chart.height * pixelRatio); + const deviceWidth = round1(chart.width * pixelRatio); + chart.height = round1(chart.height); + chart.width = round1(chart.width); const canvas = chart.canvas; // If no style has been set on the canvas, the render size is used as display size, // making the chart visually bigger, so let's enforce it to the "correct" values. @@ -3341,7 +3342,7 @@ function getDatasetClipArea(chart, meta) { } /*! - * Chart.js v4.5.0 + * Chart.js v4.5.1 * https://www.chartjs.org * (c) 2025 Chart.js Contributors * Released under the MIT License @@ -5193,19 +5194,24 @@ class DoughnutController extends DatasetController { labels: { generateLabels (chart) { const data = chart.data; + const { labels: { pointStyle , textAlign , color , useBorderRadius , borderRadius } } = chart.legend.options; if (data.labels.length && data.datasets.length) { - const { labels: { pointStyle , color } } = chart.legend.options; return data.labels.map((label, i)=>{ const meta = chart.getDatasetMeta(0); const style = meta.controller.getStyle(i); return { text: label, fillStyle: style.backgroundColor, - strokeStyle: style.borderColor, fontColor: color, - lineWidth: style.borderWidth, - pointStyle: pointStyle, hidden: !chart.getDataVisibility(i), + lineDash: style.borderDash, + lineDashOffset: style.borderDashOffset, + lineJoin: style.borderJoinStyle, + lineWidth: style.borderWidth, + strokeStyle: style.borderColor, + textAlign: textAlign, + pointStyle: pointStyle, + borderRadius: useBorderRadius && (borderRadius || style.borderRadius), index: i }; }); @@ -8435,18 +8441,22 @@ var registry = /* #__PURE__ */ new Registry(); class PluginService { constructor(){ - this._init = []; + this._init = undefined; } notify(chart, hook, args, filter) { if (hook === 'beforeInit') { this._init = this._createDescriptors(chart, true); this._notify(this._init, chart, 'install'); } + if (this._init === undefined) { + return; + } const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart); const result = this._notify(descriptors, chart, hook, args); if (hook === 'afterDestroy') { this._notify(descriptors, chart, 'stop'); this._notify(this._init, chart, 'uninstall'); + this._init = undefined; } return result; } @@ -8889,7 +8899,7 @@ function needContext(proxy, names) { return false; } -var version = "4.5.0"; +var version = "4.5.1"; const KNOWN_POSITIONS = [ 'top', @@ -14926,29 +14936,20 @@ const registerables = [ Chart.register(...registerables); -const {defineComponent:_defineComponent} = await importShared('vue'); +const {defineComponent:_defineComponent$2} = await importShared('vue'); -const {toDisplayString:_toDisplayString,createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,createVNode:_createVNode,createElementVNode:_createElementVNode,openBlock:_openBlock,createBlock:_createBlock,createElementBlock:_createElementBlock} = await importShared('vue'); +const {toDisplayString:_toDisplayString$1,createTextVNode:_createTextVNode$1,resolveComponent:_resolveComponent$1,withCtx:_withCtx$1,createVNode:_createVNode$1,createElementVNode:_createElementVNode$1,unref:_unref$1,openBlock:_openBlock$2,createBlock:_createBlock$2} = await importShared('vue'); -const _hoisted_1 = { class: "dashboard-widget" }; -const _hoisted_2 = { class: "font-weight-bold text-caption" }; -const _hoisted_3 = { class: "font-weight-bold text-caption" }; -const _hoisted_4 = { class: "font-weight-bold text-caption" }; -const _hoisted_5 = { class: "font-weight-bold text-caption" }; -const _hoisted_6 = { class: "font-weight-bold text-caption" }; -const _hoisted_7 = { class: "font-weight-bold text-caption" }; -const _hoisted_8 = { class: "d-flex flex-column" }; -const _hoisted_9 = { class: "d-flex align-center text-center" }; -const _hoisted_10 = { class: "d-flex flex-column flex-grow-1 justify-space-between gap-4 mt-8" }; -const _hoisted_11 = { class: "d-flex justify-space-between text-body-2 border-b pb-1" }; -const _hoisted_12 = { class: "d-flex justify-space-between text-body-2 border-b pb-1" }; -const _hoisted_13 = { class: "d-flex justify-space-between text-body-2 border-b pb-1" }; -const _hoisted_14 = { class: "d-flex justify-space-between text-body-2 border-b pb-1" }; -const _hoisted_15 = { class: "d-flex justify-space-between text-body-2" }; -const {reactive,onMounted,onUnmounted,ref} = await importShared('vue'); +const _hoisted_1$2 = { class: "font-weight-bold text-caption" }; +const _hoisted_2$1 = { class: "font-weight-bold text-caption" }; +const _hoisted_3$1 = { class: "font-weight-bold text-caption" }; +const _hoisted_4$1 = { class: "font-weight-bold text-caption" }; +const _hoisted_5$1 = { class: "font-weight-bold text-caption" }; +const _hoisted_6$1 = { class: "font-weight-bold text-caption" }; +const {reactive: reactive$1,onMounted: onMounted$1,onUnmounted: onUnmounted$1,ref: ref$1} = await importShared('vue'); const maxPoints = 150; -const _sfc_main = /* @__PURE__ */ _defineComponent({ - __name: "Dashboard", +const _sfc_main$2 = /* @__PURE__ */ _defineComponent$2({ + __name: "TrafficStatsCard", props: { config: { type: Object, @@ -14957,39 +14958,33 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({ allowRefresh: { type: Boolean, default: true - }, - api: { - type: Object, - required: true } }, setup(__props) { - const uploadSpeed = ref(0); - const downloadSpeed = ref(0); - const uploaded = ref(0); - const downloaded = ref(0); - const activeConnections = ref(0); - const coreUsage = ref(0); - const uploadHistory = ref(Array(maxPoints).fill(0)); - const downloadHistory = ref(Array(maxPoints).fill(0)); - const memoryHistory = ref(Array(maxPoints).fill(0)); - const speedLabels = ref([]); - const memoryLabels = ref([]); + const props = __props; + const uploadSpeed = ref$1(0); + const downloadSpeed = ref$1(0); + const uploaded = ref$1(0); + const downloaded = ref$1(0); + const activeConnections = ref$1(0); + const coreUsage = ref$1(0); + const uploadHistory = ref$1(Array(maxPoints).fill(0)); + const downloadHistory = ref$1(Array(maxPoints).fill(0)); + const memoryHistory = ref$1(Array(maxPoints).fill(0)); + const speedLabels = ref$1([]); + const memoryLabels = ref$1([]); + const speedChartCanvas = ref$1(null); + const memoryChartCanvas = ref$1(null); let chartInstance = null; let memChartInstance = null; - let refreshTimer = null; let evtSource = null; let connectionsEvtSource = null; - function formatBytes(bytes) { - if (bytes < 1024) return `${bytes} B`; - const units = ["KB", "MB", "GB"]; - let i = -1; - do { - bytes /= 1024; - i++; - } while (bytes >= 1024 && i < units.length - 1); - return `${bytes.toFixed(1)} ${units[i]}`; - } + const defaultConfig = { + title: "Clash Info", + clash_available: false, + secret: "" + }; + const componentConfig = reactive$1({ ...defaultConfig }); function formatTime(date) { return date.toTimeString().split(" ")[0]; } @@ -15024,39 +15019,428 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({ memChartInstance.data.labels = [...memoryLabels.value]; memChartInstance.update(); } - const defaultConfig = { - "title": "Clash Info", - "clash_available": false, - "secret": "" + function getCurrentTimeLabel() { + const now = /* @__PURE__ */ new Date(); + return formatTime(now); + } + onMounted$1(() => { + if (props.config?.attrs) { + Object.keys(props.config.attrs).forEach((key) => { + if (key in componentConfig) { + componentConfig[key] = props.config.attrs[key]; + } + }); + } + const ctx = speedChartCanvas.value?.getContext("2d"); + const memCtx = memoryChartCanvas.value?.getContext("2d"); + if (!ctx || !memCtx) { + return; + } + speedLabels.value = initializeTimeLabels(); + chartInstance = new Chart(ctx, { + type: "line", + data: { + labels: Array(maxPoints).fill(""), + datasets: [ + { + label: "上传速度", + data: [...uploadHistory.value], + borderColor: "rgba(255, 206, 86, 1)", + borderWidth: 0.75, + backgroundColor: "rgba(255, 206, 86, 0.2)", + fill: true, + tension: 0.3, + pointRadius: 0, + pointHoverRadius: 0 + }, + { + label: "下载速度", + data: [...downloadHistory.value], + borderWidth: 0.75, + borderColor: "rgba(54, 162, 235, 1)", + backgroundColor: "rgba(54, 162, 235, 0.2)", + fill: true, + tension: 0.3, + pointRadius: 0, + pointHoverRadius: 0 + } + ] + }, + options: { + responsive: true, + animation: false, + scales: { + x: { + display: true, + grid: { + display: false + }, + ticks: { + maxRotation: 0, + minRotation: 0, + autoSkip: false, + callback: function(value) { + const label = this.getLabelForValue(Number(value)); + const [hh, mm, ss] = label.split(":"); + if (ss === "00") { + return `${hh}:${mm}`; + } else { + return ""; + } + } + } + }, + y: { + beginAtZero: true, + grid: { + color: "rgba(121, 114, 235, 0.1)" + }, + ticks: { + callback: function(value) { + return formatBytes(Number(value)) + "/s"; + } + } + } + } + } + }); + memoryLabels.value = initializeTimeLabels(); + memChartInstance = new Chart(memCtx, { + type: "line", + data: { + labels: Array(maxPoints).fill(""), + datasets: [ + { + label: "内核占用", + data: [...memoryHistory.value], + borderWidth: 0.75, + borderColor: "rgba(85, 206, 86, 1)", + backgroundColor: "rgba(85, 206, 86, 0.2)", + fill: true, + tension: 0.3, + pointRadius: 0, + pointHoverRadius: 0 + } + ] + }, + options: { + responsive: true, + animation: false, + scales: { + x: { + display: true, + grid: { + display: false + }, + ticks: { + maxRotation: 0, + minRotation: 0, + autoSkip: false, + callback: function(value) { + const label = this.getLabelForValue(Number(value)); + const [hh, mm, ss] = label.split(":"); + if (ss === "00") { + return `${hh}:${mm}`; + } else { + return ""; + } + } + } + }, + y: { + grid: { + color: "rgba(121, 114, 235, 0.1)" + }, + beginAtZero: true, + ticks: { + callback: function(value) { + return formatBytes(Number(value)); + } + } + } + } + } + }); + if (props.allowRefresh && componentConfig.clash_available) { + evtSource = new EventSource( + "api/v1/plugin/ClashRuleProvider/clash/ws/traffic?secret=" + componentConfig.secret + ); + evtSource.addEventListener("traffic", (event) => { + const data = JSON.parse(event.data); + if (!data?.error) { + uploadSpeed.value = data.up; + downloadSpeed.value = data.down; + uploadHistory.value.push(data.up); + downloadHistory.value.push(data.down); + speedLabels.value.push(getCurrentTimeLabel()); + updateChart(); + } + }); + connectionsEvtSource = new EventSource( + "api/v1/plugin/ClashRuleProvider/clash/ws/connections?secret=" + componentConfig.secret + ); + connectionsEvtSource.addEventListener("connections", (event) => { + const data = JSON.parse(event.data); + if (!data?.error) { + uploaded.value = data.uploadTotal; + downloaded.value = data.downloadTotal; + activeConnections.value = data.connections.length; + coreUsage.value = data.memory; + memoryHistory.value.push(data.memory); + memoryLabels.value.push(getCurrentTimeLabel()); + updateMemoryChart(); + } + }); + } + }); + onUnmounted$1(() => { + if (connectionsEvtSource) connectionsEvtSource.close(); + if (evtSource) evtSource.close(); + if (chartInstance) chartInstance.destroy(); + if (memChartInstance) memChartInstance.destroy(); + }); + return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent$1("v-card-title"); + const _component_v_card_item = _resolveComponent$1("v-card-item"); + const _component_v_sheet = _resolveComponent$1("v-sheet"); + const _component_v_col = _resolveComponent$1("v-col"); + const _component_v_row = _resolveComponent$1("v-row"); + const _component_v_container = _resolveComponent$1("v-container"); + const _component_v_card_text = _resolveComponent$1("v-card-text"); + const _component_v_card = _resolveComponent$1("v-card"); + return _openBlock$2(), _createBlock$2(_component_v_card, { class: "h-100" }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card_item, null, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card_title, null, { + default: _withCtx$1(() => [ + _createTextVNode$1(_toDisplayString$1(__props.config?.attrs?.title || "Clash Info"), 1) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_card_text, { class: "pa-0 h-100 d-flex flex-column" }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_container, { + fluid: "", + class: "fill-height pa-0" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_row, { + dense: "", + class: "fill-height" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_col, { + cols: "12", + md: "6", + class: "d-flex flex-column gap-2" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_sheet, { class: "pa-2 rounded elevation-6 d-flex flex-column flex-grow-1" }, { + default: _withCtx$1(() => [ + _createElementVNode$1("canvas", { + ref_key: "speedChartCanvas", + ref: speedChartCanvas, + style: { "max-height": "200px" }, + class: "flex-grow-1" + }, null, 512) + ]), + _: 1 + }), + _createVNode$1(_component_v_row, { + dense: "", + justify: "space-between" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_col, { cols: "4" }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_sheet, { class: "pa-3 rounded text-center" }, { + default: _withCtx$1(() => [ + _createElementVNode$1("div", _hoisted_1$2, _toDisplayString$1(_unref$1(formatBytes)(uploadSpeed.value)) + "/s", 1), + _cache[0] || (_cache[0] = _createElementVNode$1("div", null, "上传速度", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { cols: "4" }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_sheet, { class: "pa-3 rounded text-center" }, { + default: _withCtx$1(() => [ + _createElementVNode$1("div", _hoisted_2$1, _toDisplayString$1(_unref$1(formatBytes)(downloadSpeed.value)) + "/s ", 1), + _cache[1] || (_cache[1] = _createElementVNode$1("div", null, "下载速度", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { cols: "4" }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_sheet, { class: "pa-3 rounded text-center" }, { + default: _withCtx$1(() => [ + _createElementVNode$1("div", _hoisted_3$1, _toDisplayString$1(activeConnections.value), 1), + _cache[2] || (_cache[2] = _createElementVNode$1("div", null, "活跃连接", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { + cols: "12", + md: "6", + class: "d-flex flex-column gap-2" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_sheet, { class: "pa-2 rounded elevation-6 flex-grow-1" }, { + default: _withCtx$1(() => [ + _createElementVNode$1("canvas", { + ref_key: "memoryChartCanvas", + ref: memoryChartCanvas, + style: { "max-height": "200px" }, + class: "flex-grow-1" + }, null, 512) + ]), + _: 1 + }), + _createVNode$1(_component_v_row, { + dense: "", + justify: "space-between" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_col, { cols: "4" }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_sheet, { class: "pa-3 rounded text-center" }, { + default: _withCtx$1(() => [ + _createElementVNode$1("div", _hoisted_4$1, _toDisplayString$1(_unref$1(formatBytes)(uploaded.value)), 1), + _cache[3] || (_cache[3] = _createElementVNode$1("div", null, "上传量", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { cols: "4" }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_sheet, { class: "pa-3 rounded text-center" }, { + default: _withCtx$1(() => [ + _createElementVNode$1("div", _hoisted_5$1, _toDisplayString$1(_unref$1(formatBytes)(downloaded.value)), 1), + _cache[4] || (_cache[4] = _createElementVNode$1("div", null, "下载量", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { cols: "4" }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_sheet, { class: "pa-3 rounded text-center" }, { + default: _withCtx$1(() => [ + _createElementVNode$1("div", _hoisted_6$1, _toDisplayString$1(_unref$1(formatBytes)(coreUsage.value)), 1), + _cache[5] || (_cache[5] = _createElementVNode$1("div", null, "内核占用", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }); }; + } +}); + +const {defineComponent:_defineComponent$1} = await importShared('vue'); + +const {toDisplayString:_toDisplayString,createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,createVNode:_createVNode,unref:_unref,openBlock:_openBlock$1,createBlock:_createBlock$1,createElementVNode:_createElementVNode} = await importShared('vue'); + +const _hoisted_1$1 = { class: "d-flex flex-column" }; +const _hoisted_2 = { class: "d-flex align-center text-center" }; +const _hoisted_3 = { class: "d-flex flex-column flex-grow-1 justify-space-between gap-4 mt-8" }; +const _hoisted_4 = { class: "d-flex justify-space-between text-body-2 border-b pb-1" }; +const _hoisted_5 = { class: "d-flex justify-space-between text-body-2 border-b pb-1" }; +const _hoisted_6 = { class: "d-flex justify-space-between text-body-2 border-b pb-1" }; +const _hoisted_7 = { class: "d-flex justify-space-between text-body-2 border-b pb-1" }; +const _hoisted_8 = { class: "d-flex justify-space-between text-body-2" }; +const {reactive,onMounted,onUnmounted,ref} = await importShared('vue'); +const _sfc_main$1 = /* @__PURE__ */ _defineComponent$1({ + __name: "ClashInfoCard", + props: { + config: { + type: Object, + default: () => ({}) + }, + allowRefresh: { + type: Boolean, + default: true + }, + api: { + type: Object, + required: true + } + }, + setup(__props) { + const props = __props; + const defaultConfig = { + title: "Clash Info", + clash_available: false, + secret: "" + }; + const componentConfig = reactive({ ...defaultConfig }); const clashInfo = ref({ - "version": { "meta": false, "version": "unknown" }, - "status": "not connected", - "configs": {}, - "rules": {}, - "proxies": [], + version: { meta: false, version: "unknown" }, + status: "not connected", + configs: {}, + rules: {}, + proxies: [], "rule-providers": {} }); - const componentConfig = reactive({ ...defaultConfig }); + let refreshTimer = null; async function fetchClashData(endpoint, dataKey) { try { - const data = await props.api.get(`plugin/ClashRuleProvider/clash/proxy${endpoint}`); + const data = await props.api.get(`plugin/ClashRuleProvider/clash/proxy/${endpoint}`); if (!data) { - throw new Error("Network response was not ok"); + clashInfo.value.status = "not connected"; } clashInfo.value.status = "connected"; clashInfo.value[dataKey] = data; } catch (error) { - console.error(`Error fetching ${endpoint}:`, error); clashInfo.value.status = "not connected"; } } async function fetchAllClashData() { await Promise.all([ - fetchClashData("/version", "version"), - fetchClashData("/configs", "configs"), - fetchClashData("/rules", "rules"), - fetchClashData("/providers/rules", "rule-providers") + fetchClashData("version", "version"), + fetchClashData("configs", "configs"), + fetchClashData("rules", "rules"), + fetchClashData("providers/rules", "rule-providers") ]); } function setupRefreshTimer() { @@ -15066,454 +15450,142 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({ }, 3e4); } } - function getCurrentTimeLabel() { - const now = /* @__PURE__ */ new Date(); - return formatTime(now); - } - onMounted( - () => { - if (props.config?.attrs) { - Object.keys(props.config.attrs).forEach((key) => { - if (key in componentConfig) { - componentConfig[key] = props.config.attrs[key]; - } - }); - } - if (props.config?.key === "traffic_stats") { - const ctx = document.getElementById("speedChart").getContext("2d"); - const memCtx = document.getElementById("memoryChart").getContext("2d"); - speedLabels.value = initializeTimeLabels(); - chartInstance = new Chart(ctx, { - type: "line", - data: { - labels: Array(maxPoints).fill(""), - datasets: [ - { - label: "上传速度", - data: [...uploadHistory.value], - borderColor: "rgba(255, 206, 86, 1)", - borderWidth: 0.75, - backgroundColor: "rgba(255, 206, 86, 0.2)", - fill: true, - tension: 0.3, - pointRadius: 0, - pointHoverRadius: 0 - }, - { - label: "下载速度", - data: [...downloadHistory.value], - borderWidth: 0.75, - borderColor: "rgba(54, 162, 235, 1)", - backgroundColor: "rgba(54, 162, 235, 0.2)", - fill: true, - tension: 0.3, - pointRadius: 0, - pointHoverRadius: 0 - } - ] - }, - options: { - responsive: true, - animation: false, - scales: { - x: { - display: true, - grid: { - display: false - }, - ticks: { - maxRotation: 0, - minRotation: 0, - autoSkip: false, - callback: function(value, index, values) { - const label = this.getLabelForValue(value); - const [hh, mm, ss] = label.split(":"); - if (ss === "00") { - return `${hh}:${mm}`; - } else { - return ""; - } - } - } - }, - y: { - beginAtZero: true, - grid: { - color: "rgba(121, 114, 235, 0.1)" - }, - ticks: { - callback: function(value) { - return formatBytes(value) + "/s"; - } - } - } - } - } - }); - memoryLabels.value = initializeTimeLabels(); - memChartInstance = new Chart(memCtx, { - type: "line", - data: { - labels: Array(maxPoints).fill(""), - datasets: [ - { - label: "内核占用", - data: [...memoryHistory.value], - borderWidth: 0.75, - borderColor: "rgba(85, 206, 86, 1)", - backgroundColor: "rgba(85, 206, 86, 0.2)", - fill: true, - tension: 0.3, - pointRadius: 0, - pointHoverRadius: 0 - } - ] - }, - options: { - responsive: true, - animation: false, - scales: { - x: { - display: true, - grid: { - display: false - }, - ticks: { - maxRotation: 0, - minRotation: 0, - autoSkip: false, - callback: function(value, index, values) { - const label = this.getLabelForValue(value); - const [hh, mm, ss] = label.split(":"); - if (ss === "00") { - return `${hh}:${mm}`; - } else { - return ""; - } - } - } - }, - y: { - grid: { - color: "rgba(121, 114, 235, 0.1)" - }, - beginAtZero: true, - ticks: { - callback: function(value) { - return formatBytes(value); - } - } - } - } - } - }); - if (props.allowRefresh && componentConfig.clash_available) { - evtSource = new EventSource("api/v1/plugin/ClashRuleProvider/clash/ws/traffic?secret=" + componentConfig.secret); - evtSource.addEventListener("traffic", (event) => { - const data = JSON.parse(event.data); - if (!data?.error) { - uploadSpeed.value = data.up; - downloadSpeed.value = data.down; - uploadHistory.value.push(data.up); - downloadHistory.value.push(data.down); - speedLabels.value.push(getCurrentTimeLabel()); - clashInfo.value.status = "connected"; - updateChart(); - } else { - clashInfo.value.status = "not connected"; - } - }); - evtSource.onerror = (err) => { - clashInfo.value.status = "not connected"; - }; - connectionsEvtSource = new EventSource("api/v1/plugin/ClashRuleProvider/clash/ws/connections?secret=" + componentConfig.secret); - connectionsEvtSource.addEventListener("connections", (event) => { - const data = JSON.parse(event.data); - if (!data?.error) { - uploaded.value = data.uploadTotal; - downloaded.value = data.downloadTotal; - activeConnections.value = data.connections.length; - coreUsage.value = data.memory; - memoryHistory.value.push(data.memory); - memoryLabels.value.push(getCurrentTimeLabel()); - updateMemoryChart(); - } else { - clashInfo.value.status = "not connected"; - } - }); - connectionsEvtSource.onerror = (err) => { - clashInfo.value.status = "not connected"; - }; + onMounted(() => { + if (props.config?.attrs) { + Object.keys(props.config.attrs).forEach((key) => { + if (key in componentConfig) { + componentConfig[key] = props.config.attrs[key]; } - } else { - if (componentConfig.clash_available) { - fetchAllClashData(); - setupRefreshTimer(); - } - } + }); } - ); + if (componentConfig.clash_available) { + fetchAllClashData(); + setupRefreshTimer(); + } + }); onUnmounted(() => { if (refreshTimer) { clearInterval(refreshTimer); } - if (connectionsEvtSource) connectionsEvtSource.close(); - if (evtSource) evtSource.close(); }); - const props = __props; return (_ctx, _cache) => { const _component_v_card_title = _resolveComponent("v-card-title"); const _component_v_card_item = _resolveComponent("v-card-item"); - const _component_v_sheet = _resolveComponent("v-sheet"); - const _component_v_col = _resolveComponent("v-col"); - const _component_v_row = _resolveComponent("v-row"); - const _component_v_container = _resolveComponent("v-container"); - const _component_v_card_text = _resolveComponent("v-card-text"); - const _component_v_card = _resolveComponent("v-card"); const _component_v_img = _resolveComponent("v-img"); const _component_v_chip = _resolveComponent("v-chip"); - return _openBlock(), _createElementBlock("div", _hoisted_1, [ - __props.config?.key === "traffic_stats" ? (_openBlock(), _createBlock(_component_v_card, { - key: 0, - class: "h-100" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card_item, null, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(__props.config?.attrs?.title || "Clash Info"), 1) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_card_text, { class: "pa-0 h-100 d-flex flex-column" }, { - default: _withCtx(() => [ - _createVNode(_component_v_container, { - fluid: "", - class: "fill-height pa-0" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_row, { - dense: "", - class: "fill-height" + const _component_v_card_text = _resolveComponent("v-card-text"); + const _component_v_card = _resolveComponent("v-card"); + return _openBlock$1(), _createBlock$1(_component_v_card, { class: "h-100" }, { + default: _withCtx(() => [ + _createVNode(_component_v_card_item, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(__props.config?.attrs?.title || "Clash Info"), 1) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_card_text, { class: "h-100 d-flex flex-column pt-4" }, { + default: _withCtx(() => [ + _createElementVNode("div", _hoisted_1$1, [ + _createElementVNode("div", _hoisted_2, [ + clashInfo.value.version.meta ? (_openBlock$1(), _createBlock$1(_component_v_img, { + key: 0, + src: `/api/v1/plugin/file/clashruleprovider/dist${_unref(MetaLogo)}`, + alt: "Logo", + "max-height": "48", + contain: "" + }, null, 8, ["src"])) : (_openBlock$1(), _createBlock$1(_component_v_img, { + key: 1, + src: "/api/v1/system/img/1?cache=1&imgurl=https://raw.githubusercontent.com/jxxghp/MoviePilot-Plugins/refs/heads/main/icons/Clash_A.png", + alt: "Logo", + "max-height": "48", + contain: "" + })) + ]), + _createElementVNode("div", _hoisted_3, [ + _createElementVNode("div", _hoisted_4, [ + _cache[0] || (_cache[0] = _createElementVNode("span", null, "连接状态", -1)), + _createVNode(_component_v_chip, { + size: "small", + color: clashInfo.value.status === "connected" ? "success" : "grey" }, { default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "6", - class: "d-flex flex-column gap-2" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_sheet, { class: "pa-2 rounded elevation-6 d-flex flex-column flex-grow-1" }, { - default: _withCtx(() => _cache[0] || (_cache[0] = [ - _createElementVNode("canvas", { - id: "speedChart", - style: { "max-height": "200px" }, - class: "flex-grow-1" - }, null, -1) - ])), - _: 1 - }), - _createVNode(_component_v_row, { - dense: "", - justify: "space-between" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { cols: "4" }, { - default: _withCtx(() => [ - _createVNode(_component_v_sheet, { class: "pa-3 rounded text-center" }, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_2, _toDisplayString(formatBytes(uploadSpeed.value)) + "/s", 1), - _cache[1] || (_cache[1] = _createElementVNode("div", null, "上传速度", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { cols: "4" }, { - default: _withCtx(() => [ - _createVNode(_component_v_sheet, { class: "pa-3 rounded text-center" }, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_3, _toDisplayString(formatBytes(downloadSpeed.value)) + "/s", 1), - _cache[2] || (_cache[2] = _createElementVNode("div", null, "下载速度", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { cols: "4" }, { - default: _withCtx(() => [ - _createVNode(_component_v_sheet, { class: "pa-3 rounded text-center" }, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_4, _toDisplayString(activeConnections.value), 1), - _cache[3] || (_cache[3] = _createElementVNode("div", null, "活跃连接", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "12", - md: "6", - class: "d-flex flex-column gap-2" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_sheet, { class: "pa-2 rounded elevation-6 flex-grow-1" }, { - default: _withCtx(() => _cache[4] || (_cache[4] = [ - _createElementVNode("canvas", { - id: "memoryChart", - style: { "max-height": "200px" }, - class: "flex-grow-1" - }, null, -1) - ])), - _: 1 - }), - _createVNode(_component_v_row, { - dense: "", - justify: "space-between" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { cols: "4" }, { - default: _withCtx(() => [ - _createVNode(_component_v_sheet, { class: "pa-3 rounded text-center" }, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_5, _toDisplayString(formatBytes(uploaded.value)), 1), - _cache[5] || (_cache[5] = _createElementVNode("div", null, "上传量", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { cols: "4" }, { - default: _withCtx(() => [ - _createVNode(_component_v_sheet, { class: "pa-3 rounded text-center" }, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_6, _toDisplayString(formatBytes(downloaded.value)), 1), - _cache[6] || (_cache[6] = _createElementVNode("div", null, "下载量", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { cols: "4" }, { - default: _withCtx(() => [ - _createVNode(_component_v_sheet, { class: "pa-3 rounded text-center" }, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_7, _toDisplayString(formatBytes(coreUsage.value)), 1), - _cache[7] || (_cache[7] = _createElementVNode("div", null, "内核占用", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) + _createTextVNode(_toDisplayString(clashInfo.value.status), 1) ]), _: 1 - }) + }, 8, ["color"]) ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - })) : (_openBlock(), _createBlock(_component_v_card, { - key: 1, - class: "h-100" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card_item, null, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(__props.config?.attrs?.title || "Clash Info"), 1) + _createElementVNode("div", _hoisted_5, [ + _cache[1] || (_cache[1] = _createElementVNode("span", null, "内核版本", -1)), + _createElementVNode("span", null, _toDisplayString(clashInfo.value.version.version), 1) ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_card_text, { class: "h-100 d-flex flex-column pt-4" }, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_8, [ - _createElementVNode("div", _hoisted_9, [ - clashInfo.value.version.meta ? (_openBlock(), _createBlock(_component_v_img, { - key: 0, - src: "/api/v1/system/img/1?cache=1&imgurl=https://raw.githubusercontent.com/MetaCubeX/mihomo/refs/heads/Meta/Meta.png", - alt: "Logo", - "max-height": "48", - contain: "" - })) : (_openBlock(), _createBlock(_component_v_img, { - key: 1, - src: "/api/v1/system/img/1?cache=1&imgurl=https://raw.githubusercontent.com/jxxghp/MoviePilot-Plugins/refs/heads/main/icons/Clash_A.png", - alt: "Logo", - "max-height": "48", - contain: "" - })) + _createElementVNode("div", _hoisted_6, [ + _cache[2] || (_cache[2] = _createElementVNode("span", null, "混合代理端口", -1)), + _createElementVNode("span", null, _toDisplayString(clashInfo.value.configs?.["mixed-port"] || 0), 1) ]), - _createElementVNode("div", _hoisted_10, [ - _createElementVNode("div", _hoisted_11, [ - _cache[8] || (_cache[8] = _createElementVNode("span", null, "连接状态", -1)), - _createVNode(_component_v_chip, { - size: "small", - color: clashInfo.value.status === "connected" ? "success" : "grey" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(clashInfo.value.status), 1) - ]), - _: 1 - }, 8, ["color"]) - ]), - _createElementVNode("div", _hoisted_12, [ - _cache[9] || (_cache[9] = _createElementVNode("span", null, "内核版本", -1)), - _createElementVNode("span", null, _toDisplayString(clashInfo.value.version.version), 1) - ]), - _createElementVNode("div", _hoisted_13, [ - _cache[10] || (_cache[10] = _createElementVNode("span", null, "混合代理端口", -1)), - _createElementVNode("span", null, _toDisplayString(clashInfo.value.configs?.["mixed-port"] || 0), 1) - ]), - _createElementVNode("div", _hoisted_14, [ - _cache[11] || (_cache[11] = _createElementVNode("span", null, "规则数量", -1)), - _createElementVNode("span", null, _toDisplayString(clashInfo.value.rules?.rules.length), 1) - ]), - _createElementVNode("div", _hoisted_15, [ - _cache[12] || (_cache[12] = _createElementVNode("span", null, "规则集数量", -1)), - _createElementVNode("span", null, _toDisplayString(Object.keys(clashInfo.value?.["rule-providers"]?.["providers"] || {}).length || 0), 1) - ]) + _createElementVNode("div", _hoisted_7, [ + _cache[3] || (_cache[3] = _createElementVNode("span", null, "规则数量", -1)), + _createElementVNode("span", null, _toDisplayString(clashInfo.value.rules?.rules.length), 1) + ]), + _createElementVNode("div", _hoisted_8, [ + _cache[4] || (_cache[4] = _createElementVNode("span", null, "规则集数量", -1)), + _createElementVNode("span", null, _toDisplayString(Object.keys(clashInfo.value?.["rule-providers"]?.["providers"] || {}).length || 0), 1) ]) ]) - ]), - _: 1 - }) - ]), - _: 1 - })) + ]) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent} = await importShared('vue'); + +const {openBlock:_openBlock,createBlock:_createBlock,createElementBlock:_createElementBlock} = await importShared('vue'); + +const _hoisted_1 = { class: "dashboard-widget" }; +const _sfc_main = /* @__PURE__ */ _defineComponent({ + __name: "Dashboard", + props: { + config: { + type: Object, + default: () => ({}) + }, + allowRefresh: { + type: Boolean, + default: true + }, + api: { + type: Object, + required: true + } + }, + setup(__props) { + return (_ctx, _cache) => { + return _openBlock(), _createElementBlock("div", _hoisted_1, [ + __props.config?.key === "traffic_stats" ? (_openBlock(), _createBlock(_sfc_main$2, { + key: 0, + config: __props.config, + "allow-refresh": __props.allowRefresh + }, null, 8, ["config", "allow-refresh"])) : (_openBlock(), _createBlock(_sfc_main$1, { + key: 1, + config: __props.config, + "allow-refresh": __props.allowRefresh, + api: __props.api + }, null, 8, ["config", "allow-refresh", "api"])) ]); }; } }); -const DashboardComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-de7a088e"]]); +const DashboardComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-318a5020"]]); export { DashboardComponent as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-BLBLx7jX.css b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-BLBLx7jX.css new file mode 100644 index 0000000..43b92d2 --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-BLBLx7jX.css @@ -0,0 +1,84 @@ + +.rule-card[data-v-da4a3497]:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important; +} + +.proxy-group-card[data-v-ef6241d5]:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important; +} + +.proxy-card[data-v-ca5a79a2]:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important; +} + +.subscription-card[data-v-97c0f367] { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} +.subscription-card[data-v-97c0f367]:hover { + transform: translateY(-4px); + box-shadow: 0 4px 25px 0 rgba(0, 0, 0, 0.1); + border-color: rgb(var(--v-theme-primary)); +} +.card-header[data-v-97c0f367] { + background: rgba(var(--v-theme-surface-variant), 0.05); +} +.bg-surface-variant-lighten[data-v-97c0f367] { + background: rgba(var(--v-theme-surface-variant), 0.02); +} +.stats-grid[data-v-97c0f367] { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +.bounce[data-v-6a1d5a83] { + animation: bounce-6a1d5a83 2s infinite; +} +@keyframes bounce-6a1d5a83 { +0%, + 20%, + 50%, + 80%, + 100% { + transform: translateY(0); +} +40% { + transform: translateY(-20px); +} +60% { + transform: translateY(-10px); +} +} + +.rule-provider-card[data-v-24eb2895]:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important; +} + +.host-card[data-v-a5d6e0e6]:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important; +} +.search-field { + max-width: 25rem; +} + +.clash-data-table { + max-height: 40rem; + overflow-y: auto; +} + +.drag-handle { + cursor: move; +} + +.dragging-item { + opacity: 0.5; + background-color: rgb(var(--v-theme-grey-200)); +} + +.drop-over { + background-color: rgba(var(--v-theme-primary), 0.1) !important; +} + +.plugin-page[data-v-ab912b83] { + margin: 0 auto; +} diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-CUYOswsP.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-CUYOswsP.js deleted file mode 100644 index 015b75a..0000000 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-CUYOswsP.js +++ /dev/null @@ -1,11795 +0,0 @@ -import { importShared } from './__federation_fn_import-JrT3xvdd.js'; -import { c as commonjsGlobal, g as getDefaultExportFromCjs, V as VAceEditor } from './theme-monokai-Bn79mBHh.js'; -import { _ as _export_sfc } from './_plugin-vue_export-helper-pcqpp-6-.js'; - -/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ -function isNothing(subject) { - return (typeof subject === 'undefined') || (subject === null); -} - - -function isObject$1(subject) { - return (typeof subject === 'object') && (subject !== null); -} - - -function toArray(sequence) { - if (Array.isArray(sequence)) return sequence; - else if (isNothing(sequence)) return []; - - return [ sequence ]; -} - - -function extend(target, source) { - var index, length, key, sourceKeys; - - if (source) { - sourceKeys = Object.keys(source); - - for (index = 0, length = sourceKeys.length; index < length; index += 1) { - key = sourceKeys[index]; - target[key] = source[key]; - } - } - - return target; -} - - -function repeat(string, count) { - var result = '', cycle; - - for (cycle = 0; cycle < count; cycle += 1) { - result += string; - } - - return result; -} - - -function isNegativeZero(number) { - return (number === 0) && (Number.NEGATIVE_INFINITY === 1 / number); -} - - -var isNothing_1 = isNothing; -var isObject_1 = isObject$1; -var toArray_1 = toArray; -var repeat_1 = repeat; -var isNegativeZero_1 = isNegativeZero; -var extend_1 = extend; - -var common = { - isNothing: isNothing_1, - isObject: isObject_1, - toArray: toArray_1, - repeat: repeat_1, - isNegativeZero: isNegativeZero_1, - extend: extend_1 -}; - -// YAML error class. http://stackoverflow.com/questions/8458984 - - -function formatError(exception, compact) { - var where = '', message = exception.reason || '(unknown reason)'; - - if (!exception.mark) return message; - - if (exception.mark.name) { - where += 'in "' + exception.mark.name + '" '; - } - - where += '(' + (exception.mark.line + 1) + ':' + (exception.mark.column + 1) + ')'; - - if (!compact && exception.mark.snippet) { - where += '\n\n' + exception.mark.snippet; - } - - return message + ' ' + where; -} - - -function YAMLException$1(reason, mark) { - // Super constructor - Error.call(this); - - this.name = 'YAMLException'; - this.reason = reason; - this.mark = mark; - this.message = formatError(this, false); - - // Include stack trace in error object - if (Error.captureStackTrace) { - // Chrome and NodeJS - Error.captureStackTrace(this, this.constructor); - } else { - // FF, IE 10+ and Safari 6+. Fallback for others - this.stack = (new Error()).stack || ''; - } -} - - -// Inherit from Error -YAMLException$1.prototype = Object.create(Error.prototype); -YAMLException$1.prototype.constructor = YAMLException$1; - - -YAMLException$1.prototype.toString = function toString(compact) { - return this.name + ': ' + formatError(this, compact); -}; - - -var exception = YAMLException$1; - -// get snippet for a single line, respecting maxLength -function getLine(buffer, lineStart, lineEnd, position, maxLineLength) { - var head = ''; - var tail = ''; - var maxHalfLength = Math.floor(maxLineLength / 2) - 1; - - if (position - lineStart > maxHalfLength) { - head = ' ... '; - lineStart = position - maxHalfLength + head.length; - } - - if (lineEnd - position > maxHalfLength) { - tail = ' ...'; - lineEnd = position + maxHalfLength - tail.length; - } - - return { - str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, '→') + tail, - pos: position - lineStart + head.length // relative position - }; -} - - -function padStart(string, max) { - return common.repeat(' ', max - string.length) + string; -} - - -function makeSnippet(mark, options) { - options = Object.create(options || null); - - if (!mark.buffer) return null; - - if (!options.maxLength) options.maxLength = 79; - if (typeof options.indent !== 'number') options.indent = 1; - if (typeof options.linesBefore !== 'number') options.linesBefore = 3; - if (typeof options.linesAfter !== 'number') options.linesAfter = 2; - - var re = /\r?\n|\r|\0/g; - var lineStarts = [ 0 ]; - var lineEnds = []; - var match; - var foundLineNo = -1; - - while ((match = re.exec(mark.buffer))) { - lineEnds.push(match.index); - lineStarts.push(match.index + match[0].length); - - if (mark.position <= match.index && foundLineNo < 0) { - foundLineNo = lineStarts.length - 2; - } - } - - if (foundLineNo < 0) foundLineNo = lineStarts.length - 1; - - var result = '', i, line; - var lineNoLength = Math.min(mark.line + options.linesAfter, lineEnds.length).toString().length; - var maxLineLength = options.maxLength - (options.indent + lineNoLength + 3); - - for (i = 1; i <= options.linesBefore; i++) { - if (foundLineNo - i < 0) break; - line = getLine( - mark.buffer, - lineStarts[foundLineNo - i], - lineEnds[foundLineNo - i], - mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo - i]), - maxLineLength - ); - result = common.repeat(' ', options.indent) + padStart((mark.line - i + 1).toString(), lineNoLength) + - ' | ' + line.str + '\n' + result; - } - - line = getLine(mark.buffer, lineStarts[foundLineNo], lineEnds[foundLineNo], mark.position, maxLineLength); - result += common.repeat(' ', options.indent) + padStart((mark.line + 1).toString(), lineNoLength) + - ' | ' + line.str + '\n'; - result += common.repeat('-', options.indent + lineNoLength + 3 + line.pos) + '^' + '\n'; - - for (i = 1; i <= options.linesAfter; i++) { - if (foundLineNo + i >= lineEnds.length) break; - line = getLine( - mark.buffer, - lineStarts[foundLineNo + i], - lineEnds[foundLineNo + i], - mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo + i]), - maxLineLength - ); - result += common.repeat(' ', options.indent) + padStart((mark.line + i + 1).toString(), lineNoLength) + - ' | ' + line.str + '\n'; - } - - return result.replace(/\n$/, ''); -} - - -var snippet = makeSnippet; - -var TYPE_CONSTRUCTOR_OPTIONS = [ - 'kind', - 'multi', - 'resolve', - 'construct', - 'instanceOf', - 'predicate', - 'represent', - 'representName', - 'defaultStyle', - 'styleAliases' -]; - -var YAML_NODE_KINDS = [ - 'scalar', - 'sequence', - 'mapping' -]; - -function compileStyleAliases(map) { - var result = {}; - - if (map !== null) { - Object.keys(map).forEach(function (style) { - map[style].forEach(function (alias) { - result[String(alias)] = style; - }); - }); - } - - return result; -} - -function Type$1(tag, options) { - options = options || {}; - - Object.keys(options).forEach(function (name) { - if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { - throw new exception('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); - } - }); - - // TODO: Add tag format check. - this.options = options; // keep original options in case user wants to extend this type later - this.tag = tag; - this.kind = options['kind'] || null; - this.resolve = options['resolve'] || function () { return true; }; - this.construct = options['construct'] || function (data) { return data; }; - this.instanceOf = options['instanceOf'] || null; - this.predicate = options['predicate'] || null; - this.represent = options['represent'] || null; - this.representName = options['representName'] || null; - this.defaultStyle = options['defaultStyle'] || null; - this.multi = options['multi'] || false; - this.styleAliases = compileStyleAliases(options['styleAliases'] || null); - - if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { - throw new exception('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); - } -} - -var type = Type$1; - -/*eslint-disable max-len*/ - - - - - -function compileList(schema, name) { - var result = []; - - schema[name].forEach(function (currentType) { - var newIndex = result.length; - - result.forEach(function (previousType, previousIndex) { - if (previousType.tag === currentType.tag && - previousType.kind === currentType.kind && - previousType.multi === currentType.multi) { - - newIndex = previousIndex; - } - }); - - result[newIndex] = currentType; - }); - - return result; -} - - -function compileMap(/* lists... */) { - var result = { - scalar: {}, - sequence: {}, - mapping: {}, - fallback: {}, - multi: { - scalar: [], - sequence: [], - mapping: [], - fallback: [] - } - }, index, length; - - function collectType(type) { - if (type.multi) { - result.multi[type.kind].push(type); - result.multi['fallback'].push(type); - } else { - result[type.kind][type.tag] = result['fallback'][type.tag] = type; - } - } - - for (index = 0, length = arguments.length; index < length; index += 1) { - arguments[index].forEach(collectType); - } - return result; -} - - -function Schema$1(definition) { - return this.extend(definition); -} - - -Schema$1.prototype.extend = function extend(definition) { - var implicit = []; - var explicit = []; - - if (definition instanceof type) { - // Schema.extend(type) - explicit.push(definition); - - } else if (Array.isArray(definition)) { - // Schema.extend([ type1, type2, ... ]) - explicit = explicit.concat(definition); - - } else if (definition && (Array.isArray(definition.implicit) || Array.isArray(definition.explicit))) { - // Schema.extend({ explicit: [ type1, type2, ... ], implicit: [ type1, type2, ... ] }) - if (definition.implicit) implicit = implicit.concat(definition.implicit); - if (definition.explicit) explicit = explicit.concat(definition.explicit); - - } else { - throw new exception('Schema.extend argument should be a Type, [ Type ], ' + - 'or a schema definition ({ implicit: [...], explicit: [...] })'); - } - - implicit.forEach(function (type$1) { - if (!(type$1 instanceof type)) { - throw new exception('Specified list of YAML types (or a single Type object) contains a non-Type object.'); - } - - if (type$1.loadKind && type$1.loadKind !== 'scalar') { - throw new exception('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.'); - } - - if (type$1.multi) { - throw new exception('There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.'); - } - }); - - explicit.forEach(function (type$1) { - if (!(type$1 instanceof type)) { - throw new exception('Specified list of YAML types (or a single Type object) contains a non-Type object.'); - } - }); - - var result = Object.create(Schema$1.prototype); - - result.implicit = (this.implicit || []).concat(implicit); - result.explicit = (this.explicit || []).concat(explicit); - - result.compiledImplicit = compileList(result, 'implicit'); - result.compiledExplicit = compileList(result, 'explicit'); - result.compiledTypeMap = compileMap(result.compiledImplicit, result.compiledExplicit); - - return result; -}; - - -var schema = Schema$1; - -var str = new type('tag:yaml.org,2002:str', { - kind: 'scalar', - construct: function (data) { return data !== null ? data : ''; } -}); - -var seq = new type('tag:yaml.org,2002:seq', { - kind: 'sequence', - construct: function (data) { return data !== null ? data : []; } -}); - -var map = new type('tag:yaml.org,2002:map', { - kind: 'mapping', - construct: function (data) { return data !== null ? data : {}; } -}); - -var failsafe = new schema({ - explicit: [ - str, - seq, - map - ] -}); - -function resolveYamlNull(data) { - if (data === null) return true; - - var max = data.length; - - return (max === 1 && data === '~') || - (max === 4 && (data === 'null' || data === 'Null' || data === 'NULL')); -} - -function constructYamlNull() { - return null; -} - -function isNull(object) { - return object === null; -} - -var _null = new type('tag:yaml.org,2002:null', { - kind: 'scalar', - resolve: resolveYamlNull, - construct: constructYamlNull, - predicate: isNull, - represent: { - canonical: function () { return '~'; }, - lowercase: function () { return 'null'; }, - uppercase: function () { return 'NULL'; }, - camelcase: function () { return 'Null'; }, - empty: function () { return ''; } - }, - defaultStyle: 'lowercase' -}); - -function resolveYamlBoolean(data) { - if (data === null) return false; - - var max = data.length; - - return (max === 4 && (data === 'true' || data === 'True' || data === 'TRUE')) || - (max === 5 && (data === 'false' || data === 'False' || data === 'FALSE')); -} - -function constructYamlBoolean(data) { - return data === 'true' || - data === 'True' || - data === 'TRUE'; -} - -function isBoolean(object) { - return Object.prototype.toString.call(object) === '[object Boolean]'; -} - -var bool = new type('tag:yaml.org,2002:bool', { - kind: 'scalar', - resolve: resolveYamlBoolean, - construct: constructYamlBoolean, - predicate: isBoolean, - represent: { - lowercase: function (object) { return object ? 'true' : 'false'; }, - uppercase: function (object) { return object ? 'TRUE' : 'FALSE'; }, - camelcase: function (object) { return object ? 'True' : 'False'; } - }, - defaultStyle: 'lowercase' -}); - -function isHexCode(c) { - return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) || - ((0x41/* A */ <= c) && (c <= 0x46/* F */)) || - ((0x61/* a */ <= c) && (c <= 0x66/* f */)); -} - -function isOctCode(c) { - return ((0x30/* 0 */ <= c) && (c <= 0x37/* 7 */)); -} - -function isDecCode(c) { - return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)); -} - -function resolveYamlInteger(data) { - if (data === null) return false; - - var max = data.length, - index = 0, - hasDigits = false, - ch; - - if (!max) return false; - - ch = data[index]; - - // sign - if (ch === '-' || ch === '+') { - ch = data[++index]; - } - - if (ch === '0') { - // 0 - if (index + 1 === max) return true; - ch = data[++index]; - - // base 2, base 8, base 16 - - if (ch === 'b') { - // base 2 - index++; - - for (; index < max; index++) { - ch = data[index]; - if (ch === '_') continue; - if (ch !== '0' && ch !== '1') return false; - hasDigits = true; - } - return hasDigits && ch !== '_'; - } - - - if (ch === 'x') { - // base 16 - index++; - - for (; index < max; index++) { - ch = data[index]; - if (ch === '_') continue; - if (!isHexCode(data.charCodeAt(index))) return false; - hasDigits = true; - } - return hasDigits && ch !== '_'; - } - - - if (ch === 'o') { - // base 8 - index++; - - for (; index < max; index++) { - ch = data[index]; - if (ch === '_') continue; - if (!isOctCode(data.charCodeAt(index))) return false; - hasDigits = true; - } - return hasDigits && ch !== '_'; - } - } - - // base 10 (except 0) - - // value should not start with `_`; - if (ch === '_') return false; - - for (; index < max; index++) { - ch = data[index]; - if (ch === '_') continue; - if (!isDecCode(data.charCodeAt(index))) { - return false; - } - hasDigits = true; - } - - // Should have digits and should not end with `_` - if (!hasDigits || ch === '_') return false; - - return true; -} - -function constructYamlInteger(data) { - var value = data, sign = 1, ch; - - if (value.indexOf('_') !== -1) { - value = value.replace(/_/g, ''); - } - - ch = value[0]; - - if (ch === '-' || ch === '+') { - if (ch === '-') sign = -1; - value = value.slice(1); - ch = value[0]; - } - - if (value === '0') return 0; - - if (ch === '0') { - if (value[1] === 'b') return sign * parseInt(value.slice(2), 2); - if (value[1] === 'x') return sign * parseInt(value.slice(2), 16); - if (value[1] === 'o') return sign * parseInt(value.slice(2), 8); - } - - return sign * parseInt(value, 10); -} - -function isInteger(object) { - return (Object.prototype.toString.call(object)) === '[object Number]' && - (object % 1 === 0 && !common.isNegativeZero(object)); -} - -var int = new type('tag:yaml.org,2002:int', { - kind: 'scalar', - resolve: resolveYamlInteger, - construct: constructYamlInteger, - predicate: isInteger, - represent: { - binary: function (obj) { return obj >= 0 ? '0b' + obj.toString(2) : '-0b' + obj.toString(2).slice(1); }, - octal: function (obj) { return obj >= 0 ? '0o' + obj.toString(8) : '-0o' + obj.toString(8).slice(1); }, - decimal: function (obj) { return obj.toString(10); }, - /* eslint-disable max-len */ - hexadecimal: function (obj) { return obj >= 0 ? '0x' + obj.toString(16).toUpperCase() : '-0x' + obj.toString(16).toUpperCase().slice(1); } - }, - defaultStyle: 'decimal', - styleAliases: { - binary: [ 2, 'bin' ], - octal: [ 8, 'oct' ], - decimal: [ 10, 'dec' ], - hexadecimal: [ 16, 'hex' ] - } -}); - -var YAML_FLOAT_PATTERN = new RegExp( - // 2.5e4, 2.5 and integers - '^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?' + - // .2e4, .2 - // special case, seems not from spec - '|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?' + - // .inf - '|[-+]?\\.(?:inf|Inf|INF)' + - // .nan - '|\\.(?:nan|NaN|NAN))$'); - -function resolveYamlFloat(data) { - if (data === null) return false; - - if (!YAML_FLOAT_PATTERN.test(data) || - // Quick hack to not allow integers end with `_` - // Probably should update regexp & check speed - data[data.length - 1] === '_') { - return false; - } - - return true; -} - -function constructYamlFloat(data) { - var value, sign; - - value = data.replace(/_/g, '').toLowerCase(); - sign = value[0] === '-' ? -1 : 1; - - if ('+-'.indexOf(value[0]) >= 0) { - value = value.slice(1); - } - - if (value === '.inf') { - return (sign === 1) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; - - } else if (value === '.nan') { - return NaN; - } - return sign * parseFloat(value, 10); -} - - -var SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; - -function representYamlFloat(object, style) { - var res; - - if (isNaN(object)) { - switch (style) { - case 'lowercase': return '.nan'; - case 'uppercase': return '.NAN'; - case 'camelcase': return '.NaN'; - } - } else if (Number.POSITIVE_INFINITY === object) { - switch (style) { - case 'lowercase': return '.inf'; - case 'uppercase': return '.INF'; - case 'camelcase': return '.Inf'; - } - } else if (Number.NEGATIVE_INFINITY === object) { - switch (style) { - case 'lowercase': return '-.inf'; - case 'uppercase': return '-.INF'; - case 'camelcase': return '-.Inf'; - } - } else if (common.isNegativeZero(object)) { - return '-0.0'; - } - - res = object.toString(10); - - // JS stringifier can build scientific format without dots: 5e-100, - // while YAML requres dot: 5.e-100. Fix it with simple hack - - return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace('e', '.e') : res; -} - -function isFloat(object) { - return (Object.prototype.toString.call(object) === '[object Number]') && - (object % 1 !== 0 || common.isNegativeZero(object)); -} - -var float = new type('tag:yaml.org,2002:float', { - kind: 'scalar', - resolve: resolveYamlFloat, - construct: constructYamlFloat, - predicate: isFloat, - represent: representYamlFloat, - defaultStyle: 'lowercase' -}); - -var json = failsafe.extend({ - implicit: [ - _null, - bool, - int, - float - ] -}); - -var core = json; - -var YAML_DATE_REGEXP = new RegExp( - '^([0-9][0-9][0-9][0-9])' + // [1] year - '-([0-9][0-9])' + // [2] month - '-([0-9][0-9])$'); // [3] day - -var YAML_TIMESTAMP_REGEXP = new RegExp( - '^([0-9][0-9][0-9][0-9])' + // [1] year - '-([0-9][0-9]?)' + // [2] month - '-([0-9][0-9]?)' + // [3] day - '(?:[Tt]|[ \\t]+)' + // ... - '([0-9][0-9]?)' + // [4] hour - ':([0-9][0-9])' + // [5] minute - ':([0-9][0-9])' + // [6] second - '(?:\\.([0-9]*))?' + // [7] fraction - '(?:[ \\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour - '(?::([0-9][0-9]))?))?$'); // [11] tz_minute - -function resolveYamlTimestamp(data) { - if (data === null) return false; - if (YAML_DATE_REGEXP.exec(data) !== null) return true; - if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true; - return false; -} - -function constructYamlTimestamp(data) { - var match, year, month, day, hour, minute, second, fraction = 0, - delta = null, tz_hour, tz_minute, date; - - match = YAML_DATE_REGEXP.exec(data); - if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data); - - if (match === null) throw new Error('Date resolve error'); - - // match: [1] year [2] month [3] day - - year = +(match[1]); - month = +(match[2]) - 1; // JS month starts with 0 - day = +(match[3]); - - if (!match[4]) { // no hour - return new Date(Date.UTC(year, month, day)); - } - - // match: [4] hour [5] minute [6] second [7] fraction - - hour = +(match[4]); - minute = +(match[5]); - second = +(match[6]); - - if (match[7]) { - fraction = match[7].slice(0, 3); - while (fraction.length < 3) { // milli-seconds - fraction += '0'; - } - fraction = +fraction; - } - - // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute - - if (match[9]) { - tz_hour = +(match[10]); - tz_minute = +(match[11] || 0); - delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds - if (match[9] === '-') delta = -delta; - } - - date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction)); - - if (delta) date.setTime(date.getTime() - delta); - - return date; -} - -function representYamlTimestamp(object /*, style*/) { - return object.toISOString(); -} - -var timestamp = new type('tag:yaml.org,2002:timestamp', { - kind: 'scalar', - resolve: resolveYamlTimestamp, - construct: constructYamlTimestamp, - instanceOf: Date, - represent: representYamlTimestamp -}); - -function resolveYamlMerge(data) { - return data === '<<' || data === null; -} - -var merge = new type('tag:yaml.org,2002:merge', { - kind: 'scalar', - resolve: resolveYamlMerge -}); - -/*eslint-disable no-bitwise*/ - - - - - -// [ 64, 65, 66 ] -> [ padding, CR, LF ] -var BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r'; - - -function resolveYamlBinary(data) { - if (data === null) return false; - - var code, idx, bitlen = 0, max = data.length, map = BASE64_MAP; - - // Convert one by one. - for (idx = 0; idx < max; idx++) { - code = map.indexOf(data.charAt(idx)); - - // Skip CR/LF - if (code > 64) continue; - - // Fail on illegal characters - if (code < 0) return false; - - bitlen += 6; - } - - // If there are any bits left, source was corrupted - return (bitlen % 8) === 0; -} - -function constructYamlBinary(data) { - var idx, tailbits, - input = data.replace(/[\r\n=]/g, ''), // remove CR/LF & padding to simplify scan - max = input.length, - map = BASE64_MAP, - bits = 0, - result = []; - - // Collect by 6*4 bits (3 bytes) - - for (idx = 0; idx < max; idx++) { - if ((idx % 4 === 0) && idx) { - result.push((bits >> 16) & 0xFF); - result.push((bits >> 8) & 0xFF); - result.push(bits & 0xFF); - } - - bits = (bits << 6) | map.indexOf(input.charAt(idx)); - } - - // Dump tail - - tailbits = (max % 4) * 6; - - if (tailbits === 0) { - result.push((bits >> 16) & 0xFF); - result.push((bits >> 8) & 0xFF); - result.push(bits & 0xFF); - } else if (tailbits === 18) { - result.push((bits >> 10) & 0xFF); - result.push((bits >> 2) & 0xFF); - } else if (tailbits === 12) { - result.push((bits >> 4) & 0xFF); - } - - return new Uint8Array(result); -} - -function representYamlBinary(object /*, style*/) { - var result = '', bits = 0, idx, tail, - max = object.length, - map = BASE64_MAP; - - // Convert every three bytes to 4 ASCII characters. - - for (idx = 0; idx < max; idx++) { - if ((idx % 3 === 0) && idx) { - result += map[(bits >> 18) & 0x3F]; - result += map[(bits >> 12) & 0x3F]; - result += map[(bits >> 6) & 0x3F]; - result += map[bits & 0x3F]; - } - - bits = (bits << 8) + object[idx]; - } - - // Dump tail - - tail = max % 3; - - if (tail === 0) { - result += map[(bits >> 18) & 0x3F]; - result += map[(bits >> 12) & 0x3F]; - result += map[(bits >> 6) & 0x3F]; - result += map[bits & 0x3F]; - } else if (tail === 2) { - result += map[(bits >> 10) & 0x3F]; - result += map[(bits >> 4) & 0x3F]; - result += map[(bits << 2) & 0x3F]; - result += map[64]; - } else if (tail === 1) { - result += map[(bits >> 2) & 0x3F]; - result += map[(bits << 4) & 0x3F]; - result += map[64]; - result += map[64]; - } - - return result; -} - -function isBinary(obj) { - return Object.prototype.toString.call(obj) === '[object Uint8Array]'; -} - -var binary = new type('tag:yaml.org,2002:binary', { - kind: 'scalar', - resolve: resolveYamlBinary, - construct: constructYamlBinary, - predicate: isBinary, - represent: representYamlBinary -}); - -var _hasOwnProperty$3 = Object.prototype.hasOwnProperty; -var _toString$2 = Object.prototype.toString; - -function resolveYamlOmap(data) { - if (data === null) return true; - - var objectKeys = [], index, length, pair, pairKey, pairHasKey, - object = data; - - for (index = 0, length = object.length; index < length; index += 1) { - pair = object[index]; - pairHasKey = false; - - if (_toString$2.call(pair) !== '[object Object]') return false; - - for (pairKey in pair) { - if (_hasOwnProperty$3.call(pair, pairKey)) { - if (!pairHasKey) pairHasKey = true; - else return false; - } - } - - if (!pairHasKey) return false; - - if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey); - else return false; - } - - return true; -} - -function constructYamlOmap(data) { - return data !== null ? data : []; -} - -var omap = new type('tag:yaml.org,2002:omap', { - kind: 'sequence', - resolve: resolveYamlOmap, - construct: constructYamlOmap -}); - -var _toString$1 = Object.prototype.toString; - -function resolveYamlPairs(data) { - if (data === null) return true; - - var index, length, pair, keys, result, - object = data; - - result = new Array(object.length); - - for (index = 0, length = object.length; index < length; index += 1) { - pair = object[index]; - - if (_toString$1.call(pair) !== '[object Object]') return false; - - keys = Object.keys(pair); - - if (keys.length !== 1) return false; - - result[index] = [ keys[0], pair[keys[0]] ]; - } - - return true; -} - -function constructYamlPairs(data) { - if (data === null) return []; - - var index, length, pair, keys, result, - object = data; - - result = new Array(object.length); - - for (index = 0, length = object.length; index < length; index += 1) { - pair = object[index]; - - keys = Object.keys(pair); - - result[index] = [ keys[0], pair[keys[0]] ]; - } - - return result; -} - -var pairs = new type('tag:yaml.org,2002:pairs', { - kind: 'sequence', - resolve: resolveYamlPairs, - construct: constructYamlPairs -}); - -var _hasOwnProperty$2 = Object.prototype.hasOwnProperty; - -function resolveYamlSet(data) { - if (data === null) return true; - - var key, object = data; - - for (key in object) { - if (_hasOwnProperty$2.call(object, key)) { - if (object[key] !== null) return false; - } - } - - return true; -} - -function constructYamlSet(data) { - return data !== null ? data : {}; -} - -var set = new type('tag:yaml.org,2002:set', { - kind: 'mapping', - resolve: resolveYamlSet, - construct: constructYamlSet -}); - -var _default = core.extend({ - implicit: [ - timestamp, - merge - ], - explicit: [ - binary, - omap, - pairs, - set - ] -}); - -/*eslint-disable max-len,no-use-before-define*/ - - - - - - - -var _hasOwnProperty$1 = Object.prototype.hasOwnProperty; - - -var CONTEXT_FLOW_IN = 1; -var CONTEXT_FLOW_OUT = 2; -var CONTEXT_BLOCK_IN = 3; -var CONTEXT_BLOCK_OUT = 4; - - -var CHOMPING_CLIP = 1; -var CHOMPING_STRIP = 2; -var CHOMPING_KEEP = 3; - - -var PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; -var PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/; -var PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/; -var PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i; -var PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i; - - -function _class(obj) { return Object.prototype.toString.call(obj); } - -function is_EOL(c) { - return (c === 0x0A/* LF */) || (c === 0x0D/* CR */); -} - -function is_WHITE_SPACE(c) { - return (c === 0x09/* Tab */) || (c === 0x20/* Space */); -} - -function is_WS_OR_EOL(c) { - return (c === 0x09/* Tab */) || - (c === 0x20/* Space */) || - (c === 0x0A/* LF */) || - (c === 0x0D/* CR */); -} - -function is_FLOW_INDICATOR(c) { - return c === 0x2C/* , */ || - c === 0x5B/* [ */ || - c === 0x5D/* ] */ || - c === 0x7B/* { */ || - c === 0x7D/* } */; -} - -function fromHexCode(c) { - var lc; - - if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { - return c - 0x30; - } - - /*eslint-disable no-bitwise*/ - lc = c | 0x20; - - if ((0x61/* a */ <= lc) && (lc <= 0x66/* f */)) { - return lc - 0x61 + 10; - } - - return -1; -} - -function escapedHexLen(c) { - if (c === 0x78/* x */) { return 2; } - if (c === 0x75/* u */) { return 4; } - if (c === 0x55/* U */) { return 8; } - return 0; -} - -function fromDecimalCode(c) { - if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { - return c - 0x30; - } - - return -1; -} - -function simpleEscapeSequence(c) { - /* eslint-disable indent */ - return (c === 0x30/* 0 */) ? '\x00' : - (c === 0x61/* a */) ? '\x07' : - (c === 0x62/* b */) ? '\x08' : - (c === 0x74/* t */) ? '\x09' : - (c === 0x09/* Tab */) ? '\x09' : - (c === 0x6E/* n */) ? '\x0A' : - (c === 0x76/* v */) ? '\x0B' : - (c === 0x66/* f */) ? '\x0C' : - (c === 0x72/* r */) ? '\x0D' : - (c === 0x65/* e */) ? '\x1B' : - (c === 0x20/* Space */) ? ' ' : - (c === 0x22/* " */) ? '\x22' : - (c === 0x2F/* / */) ? '/' : - (c === 0x5C/* \ */) ? '\x5C' : - (c === 0x4E/* N */) ? '\x85' : - (c === 0x5F/* _ */) ? '\xA0' : - (c === 0x4C/* L */) ? '\u2028' : - (c === 0x50/* P */) ? '\u2029' : ''; -} - -function charFromCodepoint(c) { - if (c <= 0xFFFF) { - return String.fromCharCode(c); - } - // Encode UTF-16 surrogate pair - // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF - return String.fromCharCode( - ((c - 0x010000) >> 10) + 0xD800, - ((c - 0x010000) & 0x03FF) + 0xDC00 - ); -} - -var simpleEscapeCheck = new Array(256); // integer, for fast access -var simpleEscapeMap = new Array(256); -for (var i = 0; i < 256; i++) { - simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0; - simpleEscapeMap[i] = simpleEscapeSequence(i); -} - - -function State$1(input, options) { - this.input = input; - - this.filename = options['filename'] || null; - this.schema = options['schema'] || _default; - this.onWarning = options['onWarning'] || null; - // (Hidden) Remove? makes the loader to expect YAML 1.1 documents - // if such documents have no explicit %YAML directive - this.legacy = options['legacy'] || false; - - this.json = options['json'] || false; - this.listener = options['listener'] || null; - - this.implicitTypes = this.schema.compiledImplicit; - this.typeMap = this.schema.compiledTypeMap; - - this.length = input.length; - this.position = 0; - this.line = 0; - this.lineStart = 0; - this.lineIndent = 0; - - // position of first leading tab in the current line, - // used to make sure there are no tabs in the indentation - this.firstTabInLine = -1; - - this.documents = []; - - /* - this.version; - this.checkLineBreaks; - this.tagMap; - this.anchorMap; - this.tag; - this.anchor; - this.kind; - this.result;*/ - -} - - -function generateError(state, message) { - var mark = { - name: state.filename, - buffer: state.input.slice(0, -1), // omit trailing \0 - position: state.position, - line: state.line, - column: state.position - state.lineStart - }; - - mark.snippet = snippet(mark); - - return new exception(message, mark); -} - -function throwError(state, message) { - throw generateError(state, message); -} - -function throwWarning(state, message) { - if (state.onWarning) { - state.onWarning.call(null, generateError(state, message)); - } -} - - -var directiveHandlers = { - - YAML: function handleYamlDirective(state, name, args) { - - var match, major, minor; - - if (state.version !== null) { - throwError(state, 'duplication of %YAML directive'); - } - - if (args.length !== 1) { - throwError(state, 'YAML directive accepts exactly one argument'); - } - - match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]); - - if (match === null) { - throwError(state, 'ill-formed argument of the YAML directive'); - } - - major = parseInt(match[1], 10); - minor = parseInt(match[2], 10); - - if (major !== 1) { - throwError(state, 'unacceptable YAML version of the document'); - } - - state.version = args[0]; - state.checkLineBreaks = (minor < 2); - - if (minor !== 1 && minor !== 2) { - throwWarning(state, 'unsupported YAML version of the document'); - } - }, - - TAG: function handleTagDirective(state, name, args) { - - var handle, prefix; - - if (args.length !== 2) { - throwError(state, 'TAG directive accepts exactly two arguments'); - } - - handle = args[0]; - prefix = args[1]; - - if (!PATTERN_TAG_HANDLE.test(handle)) { - throwError(state, 'ill-formed tag handle (first argument) of the TAG directive'); - } - - if (_hasOwnProperty$1.call(state.tagMap, handle)) { - throwError(state, 'there is a previously declared suffix for "' + handle + '" tag handle'); - } - - if (!PATTERN_TAG_URI.test(prefix)) { - throwError(state, 'ill-formed tag prefix (second argument) of the TAG directive'); - } - - try { - prefix = decodeURIComponent(prefix); - } catch (err) { - throwError(state, 'tag prefix is malformed: ' + prefix); - } - - state.tagMap[handle] = prefix; - } -}; - - -function captureSegment(state, start, end, checkJson) { - var _position, _length, _character, _result; - - if (start < end) { - _result = state.input.slice(start, end); - - if (checkJson) { - for (_position = 0, _length = _result.length; _position < _length; _position += 1) { - _character = _result.charCodeAt(_position); - if (!(_character === 0x09 || - (0x20 <= _character && _character <= 0x10FFFF))) { - throwError(state, 'expected valid JSON character'); - } - } - } else if (PATTERN_NON_PRINTABLE.test(_result)) { - throwError(state, 'the stream contains non-printable characters'); - } - - state.result += _result; - } -} - -function mergeMappings(state, destination, source, overridableKeys) { - var sourceKeys, key, index, quantity; - - if (!common.isObject(source)) { - throwError(state, 'cannot merge mappings; the provided source object is unacceptable'); - } - - sourceKeys = Object.keys(source); - - for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) { - key = sourceKeys[index]; - - if (!_hasOwnProperty$1.call(destination, key)) { - destination[key] = source[key]; - overridableKeys[key] = true; - } - } -} - -function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, - startLine, startLineStart, startPos) { - - var index, quantity; - - // The output is a plain object here, so keys can only be strings. - // We need to convert keyNode to a string, but doing so can hang the process - // (deeply nested arrays that explode exponentially using aliases). - if (Array.isArray(keyNode)) { - keyNode = Array.prototype.slice.call(keyNode); - - for (index = 0, quantity = keyNode.length; index < quantity; index += 1) { - if (Array.isArray(keyNode[index])) { - throwError(state, 'nested arrays are not supported inside keys'); - } - - if (typeof keyNode === 'object' && _class(keyNode[index]) === '[object Object]') { - keyNode[index] = '[object Object]'; - } - } - } - - // Avoid code execution in load() via toString property - // (still use its own toString for arrays, timestamps, - // and whatever user schema extensions happen to have @@toStringTag) - if (typeof keyNode === 'object' && _class(keyNode) === '[object Object]') { - keyNode = '[object Object]'; - } - - - keyNode = String(keyNode); - - if (_result === null) { - _result = {}; - } - - if (keyTag === 'tag:yaml.org,2002:merge') { - if (Array.isArray(valueNode)) { - for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { - mergeMappings(state, _result, valueNode[index], overridableKeys); - } - } else { - mergeMappings(state, _result, valueNode, overridableKeys); - } - } else { - if (!state.json && - !_hasOwnProperty$1.call(overridableKeys, keyNode) && - _hasOwnProperty$1.call(_result, keyNode)) { - state.line = startLine || state.line; - state.lineStart = startLineStart || state.lineStart; - state.position = startPos || state.position; - throwError(state, 'duplicated mapping key'); - } - - // used for this specific key only because Object.defineProperty is slow - if (keyNode === '__proto__') { - Object.defineProperty(_result, keyNode, { - configurable: true, - enumerable: true, - writable: true, - value: valueNode - }); - } else { - _result[keyNode] = valueNode; - } - delete overridableKeys[keyNode]; - } - - return _result; -} - -function readLineBreak(state) { - var ch; - - ch = state.input.charCodeAt(state.position); - - if (ch === 0x0A/* LF */) { - state.position++; - } else if (ch === 0x0D/* CR */) { - state.position++; - if (state.input.charCodeAt(state.position) === 0x0A/* LF */) { - state.position++; - } - } else { - throwError(state, 'a line break is expected'); - } - - state.line += 1; - state.lineStart = state.position; - state.firstTabInLine = -1; -} - -function skipSeparationSpace(state, allowComments, checkIndent) { - var lineBreaks = 0, - ch = state.input.charCodeAt(state.position); - - while (ch !== 0) { - while (is_WHITE_SPACE(ch)) { - if (ch === 0x09/* Tab */ && state.firstTabInLine === -1) { - state.firstTabInLine = state.position; - } - ch = state.input.charCodeAt(++state.position); - } - - if (allowComments && ch === 0x23/* # */) { - do { - ch = state.input.charCodeAt(++state.position); - } while (ch !== 0x0A/* LF */ && ch !== 0x0D/* CR */ && ch !== 0); - } - - if (is_EOL(ch)) { - readLineBreak(state); - - ch = state.input.charCodeAt(state.position); - lineBreaks++; - state.lineIndent = 0; - - while (ch === 0x20/* Space */) { - state.lineIndent++; - ch = state.input.charCodeAt(++state.position); - } - } else { - break; - } - } - - if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) { - throwWarning(state, 'deficient indentation'); - } - - return lineBreaks; -} - -function testDocumentSeparator(state) { - var _position = state.position, - ch; - - ch = state.input.charCodeAt(_position); - - // Condition state.position === state.lineStart is tested - // in parent on each call, for efficiency. No needs to test here again. - if ((ch === 0x2D/* - */ || ch === 0x2E/* . */) && - ch === state.input.charCodeAt(_position + 1) && - ch === state.input.charCodeAt(_position + 2)) { - - _position += 3; - - ch = state.input.charCodeAt(_position); - - if (ch === 0 || is_WS_OR_EOL(ch)) { - return true; - } - } - - return false; -} - -function writeFoldedLines(state, count) { - if (count === 1) { - state.result += ' '; - } else if (count > 1) { - state.result += common.repeat('\n', count - 1); - } -} - - -function readPlainScalar(state, nodeIndent, withinFlowCollection) { - var preceding, - following, - captureStart, - captureEnd, - hasPendingContent, - _line, - _lineStart, - _lineIndent, - _kind = state.kind, - _result = state.result, - ch; - - ch = state.input.charCodeAt(state.position); - - if (is_WS_OR_EOL(ch) || - is_FLOW_INDICATOR(ch) || - ch === 0x23/* # */ || - ch === 0x26/* & */ || - ch === 0x2A/* * */ || - ch === 0x21/* ! */ || - ch === 0x7C/* | */ || - ch === 0x3E/* > */ || - ch === 0x27/* ' */ || - ch === 0x22/* " */ || - ch === 0x25/* % */ || - ch === 0x40/* @ */ || - ch === 0x60/* ` */) { - return false; - } - - if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) { - following = state.input.charCodeAt(state.position + 1); - - if (is_WS_OR_EOL(following) || - withinFlowCollection && is_FLOW_INDICATOR(following)) { - return false; - } - } - - state.kind = 'scalar'; - state.result = ''; - captureStart = captureEnd = state.position; - hasPendingContent = false; - - while (ch !== 0) { - if (ch === 0x3A/* : */) { - following = state.input.charCodeAt(state.position + 1); - - if (is_WS_OR_EOL(following) || - withinFlowCollection && is_FLOW_INDICATOR(following)) { - break; - } - - } else if (ch === 0x23/* # */) { - preceding = state.input.charCodeAt(state.position - 1); - - if (is_WS_OR_EOL(preceding)) { - break; - } - - } else if ((state.position === state.lineStart && testDocumentSeparator(state)) || - withinFlowCollection && is_FLOW_INDICATOR(ch)) { - break; - - } else if (is_EOL(ch)) { - _line = state.line; - _lineStart = state.lineStart; - _lineIndent = state.lineIndent; - skipSeparationSpace(state, false, -1); - - if (state.lineIndent >= nodeIndent) { - hasPendingContent = true; - ch = state.input.charCodeAt(state.position); - continue; - } else { - state.position = captureEnd; - state.line = _line; - state.lineStart = _lineStart; - state.lineIndent = _lineIndent; - break; - } - } - - if (hasPendingContent) { - captureSegment(state, captureStart, captureEnd, false); - writeFoldedLines(state, state.line - _line); - captureStart = captureEnd = state.position; - hasPendingContent = false; - } - - if (!is_WHITE_SPACE(ch)) { - captureEnd = state.position + 1; - } - - ch = state.input.charCodeAt(++state.position); - } - - captureSegment(state, captureStart, captureEnd, false); - - if (state.result) { - return true; - } - - state.kind = _kind; - state.result = _result; - return false; -} - -function readSingleQuotedScalar(state, nodeIndent) { - var ch, - captureStart, captureEnd; - - ch = state.input.charCodeAt(state.position); - - if (ch !== 0x27/* ' */) { - return false; - } - - state.kind = 'scalar'; - state.result = ''; - state.position++; - captureStart = captureEnd = state.position; - - while ((ch = state.input.charCodeAt(state.position)) !== 0) { - if (ch === 0x27/* ' */) { - captureSegment(state, captureStart, state.position, true); - ch = state.input.charCodeAt(++state.position); - - if (ch === 0x27/* ' */) { - captureStart = state.position; - state.position++; - captureEnd = state.position; - } else { - return true; - } - - } else if (is_EOL(ch)) { - captureSegment(state, captureStart, captureEnd, true); - writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); - captureStart = captureEnd = state.position; - - } else if (state.position === state.lineStart && testDocumentSeparator(state)) { - throwError(state, 'unexpected end of the document within a single quoted scalar'); - - } else { - state.position++; - captureEnd = state.position; - } - } - - throwError(state, 'unexpected end of the stream within a single quoted scalar'); -} - -function readDoubleQuotedScalar(state, nodeIndent) { - var captureStart, - captureEnd, - hexLength, - hexResult, - tmp, - ch; - - ch = state.input.charCodeAt(state.position); - - if (ch !== 0x22/* " */) { - return false; - } - - state.kind = 'scalar'; - state.result = ''; - state.position++; - captureStart = captureEnd = state.position; - - while ((ch = state.input.charCodeAt(state.position)) !== 0) { - if (ch === 0x22/* " */) { - captureSegment(state, captureStart, state.position, true); - state.position++; - return true; - - } else if (ch === 0x5C/* \ */) { - captureSegment(state, captureStart, state.position, true); - ch = state.input.charCodeAt(++state.position); - - if (is_EOL(ch)) { - skipSeparationSpace(state, false, nodeIndent); - - // TODO: rework to inline fn with no type cast? - } else if (ch < 256 && simpleEscapeCheck[ch]) { - state.result += simpleEscapeMap[ch]; - state.position++; - - } else if ((tmp = escapedHexLen(ch)) > 0) { - hexLength = tmp; - hexResult = 0; - - for (; hexLength > 0; hexLength--) { - ch = state.input.charCodeAt(++state.position); - - if ((tmp = fromHexCode(ch)) >= 0) { - hexResult = (hexResult << 4) + tmp; - - } else { - throwError(state, 'expected hexadecimal character'); - } - } - - state.result += charFromCodepoint(hexResult); - - state.position++; - - } else { - throwError(state, 'unknown escape sequence'); - } - - captureStart = captureEnd = state.position; - - } else if (is_EOL(ch)) { - captureSegment(state, captureStart, captureEnd, true); - writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); - captureStart = captureEnd = state.position; - - } else if (state.position === state.lineStart && testDocumentSeparator(state)) { - throwError(state, 'unexpected end of the document within a double quoted scalar'); - - } else { - state.position++; - captureEnd = state.position; - } - } - - throwError(state, 'unexpected end of the stream within a double quoted scalar'); -} - -function readFlowCollection(state, nodeIndent) { - var readNext = true, - _line, - _lineStart, - _pos, - _tag = state.tag, - _result, - _anchor = state.anchor, - following, - terminator, - isPair, - isExplicitPair, - isMapping, - overridableKeys = Object.create(null), - keyNode, - keyTag, - valueNode, - ch; - - ch = state.input.charCodeAt(state.position); - - if (ch === 0x5B/* [ */) { - terminator = 0x5D;/* ] */ - isMapping = false; - _result = []; - } else if (ch === 0x7B/* { */) { - terminator = 0x7D;/* } */ - isMapping = true; - _result = {}; - } else { - return false; - } - - if (state.anchor !== null) { - state.anchorMap[state.anchor] = _result; - } - - ch = state.input.charCodeAt(++state.position); - - while (ch !== 0) { - skipSeparationSpace(state, true, nodeIndent); - - ch = state.input.charCodeAt(state.position); - - if (ch === terminator) { - state.position++; - state.tag = _tag; - state.anchor = _anchor; - state.kind = isMapping ? 'mapping' : 'sequence'; - state.result = _result; - return true; - } else if (!readNext) { - throwError(state, 'missed comma between flow collection entries'); - } else if (ch === 0x2C/* , */) { - // "flow collection entries can never be completely empty", as per YAML 1.2, section 7.4 - throwError(state, "expected the node content, but found ','"); - } - - keyTag = keyNode = valueNode = null; - isPair = isExplicitPair = false; - - if (ch === 0x3F/* ? */) { - following = state.input.charCodeAt(state.position + 1); - - if (is_WS_OR_EOL(following)) { - isPair = isExplicitPair = true; - state.position++; - skipSeparationSpace(state, true, nodeIndent); - } - } - - _line = state.line; // Save the current line. - _lineStart = state.lineStart; - _pos = state.position; - composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); - keyTag = state.tag; - keyNode = state.result; - skipSeparationSpace(state, true, nodeIndent); - - ch = state.input.charCodeAt(state.position); - - if ((isExplicitPair || state.line === _line) && ch === 0x3A/* : */) { - isPair = true; - ch = state.input.charCodeAt(++state.position); - skipSeparationSpace(state, true, nodeIndent); - composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); - valueNode = state.result; - } - - if (isMapping) { - storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos); - } else if (isPair) { - _result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos)); - } else { - _result.push(keyNode); - } - - skipSeparationSpace(state, true, nodeIndent); - - ch = state.input.charCodeAt(state.position); - - if (ch === 0x2C/* , */) { - readNext = true; - ch = state.input.charCodeAt(++state.position); - } else { - readNext = false; - } - } - - throwError(state, 'unexpected end of the stream within a flow collection'); -} - -function readBlockScalar(state, nodeIndent) { - var captureStart, - folding, - chomping = CHOMPING_CLIP, - didReadContent = false, - detectedIndent = false, - textIndent = nodeIndent, - emptyLines = 0, - atMoreIndented = false, - tmp, - ch; - - ch = state.input.charCodeAt(state.position); - - if (ch === 0x7C/* | */) { - folding = false; - } else if (ch === 0x3E/* > */) { - folding = true; - } else { - return false; - } - - state.kind = 'scalar'; - state.result = ''; - - while (ch !== 0) { - ch = state.input.charCodeAt(++state.position); - - if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { - if (CHOMPING_CLIP === chomping) { - chomping = (ch === 0x2B/* + */) ? CHOMPING_KEEP : CHOMPING_STRIP; - } else { - throwError(state, 'repeat of a chomping mode identifier'); - } - - } else if ((tmp = fromDecimalCode(ch)) >= 0) { - if (tmp === 0) { - throwError(state, 'bad explicit indentation width of a block scalar; it cannot be less than one'); - } else if (!detectedIndent) { - textIndent = nodeIndent + tmp - 1; - detectedIndent = true; - } else { - throwError(state, 'repeat of an indentation width identifier'); - } - - } else { - break; - } - } - - if (is_WHITE_SPACE(ch)) { - do { ch = state.input.charCodeAt(++state.position); } - while (is_WHITE_SPACE(ch)); - - if (ch === 0x23/* # */) { - do { ch = state.input.charCodeAt(++state.position); } - while (!is_EOL(ch) && (ch !== 0)); - } - } - - while (ch !== 0) { - readLineBreak(state); - state.lineIndent = 0; - - ch = state.input.charCodeAt(state.position); - - while ((!detectedIndent || state.lineIndent < textIndent) && - (ch === 0x20/* Space */)) { - state.lineIndent++; - ch = state.input.charCodeAt(++state.position); - } - - if (!detectedIndent && state.lineIndent > textIndent) { - textIndent = state.lineIndent; - } - - if (is_EOL(ch)) { - emptyLines++; - continue; - } - - // End of the scalar. - if (state.lineIndent < textIndent) { - - // Perform the chomping. - if (chomping === CHOMPING_KEEP) { - state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); - } else if (chomping === CHOMPING_CLIP) { - if (didReadContent) { // i.e. only if the scalar is not empty. - state.result += '\n'; - } - } - - // Break this `while` cycle and go to the funciton's epilogue. - break; - } - - // Folded style: use fancy rules to handle line breaks. - if (folding) { - - // Lines starting with white space characters (more-indented lines) are not folded. - if (is_WHITE_SPACE(ch)) { - atMoreIndented = true; - // except for the first content line (cf. Example 8.1) - state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); - - // End of more-indented block. - } else if (atMoreIndented) { - atMoreIndented = false; - state.result += common.repeat('\n', emptyLines + 1); - - // Just one line break - perceive as the same line. - } else if (emptyLines === 0) { - if (didReadContent) { // i.e. only if we have already read some scalar content. - state.result += ' '; - } - - // Several line breaks - perceive as different lines. - } else { - state.result += common.repeat('\n', emptyLines); - } - - // Literal style: just add exact number of line breaks between content lines. - } else { - // Keep all line breaks except the header line break. - state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); - } - - didReadContent = true; - detectedIndent = true; - emptyLines = 0; - captureStart = state.position; - - while (!is_EOL(ch) && (ch !== 0)) { - ch = state.input.charCodeAt(++state.position); - } - - captureSegment(state, captureStart, state.position, false); - } - - return true; -} - -function readBlockSequence(state, nodeIndent) { - var _line, - _tag = state.tag, - _anchor = state.anchor, - _result = [], - following, - detected = false, - ch; - - // there is a leading tab before this token, so it can't be a block sequence/mapping; - // it can still be flow sequence/mapping or a scalar - if (state.firstTabInLine !== -1) return false; - - if (state.anchor !== null) { - state.anchorMap[state.anchor] = _result; - } - - ch = state.input.charCodeAt(state.position); - - while (ch !== 0) { - if (state.firstTabInLine !== -1) { - state.position = state.firstTabInLine; - throwError(state, 'tab characters must not be used in indentation'); - } - - if (ch !== 0x2D/* - */) { - break; - } - - following = state.input.charCodeAt(state.position + 1); - - if (!is_WS_OR_EOL(following)) { - break; - } - - detected = true; - state.position++; - - if (skipSeparationSpace(state, true, -1)) { - if (state.lineIndent <= nodeIndent) { - _result.push(null); - ch = state.input.charCodeAt(state.position); - continue; - } - } - - _line = state.line; - composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true); - _result.push(state.result); - skipSeparationSpace(state, true, -1); - - ch = state.input.charCodeAt(state.position); - - if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) { - throwError(state, 'bad indentation of a sequence entry'); - } else if (state.lineIndent < nodeIndent) { - break; - } - } - - if (detected) { - state.tag = _tag; - state.anchor = _anchor; - state.kind = 'sequence'; - state.result = _result; - return true; - } - return false; -} - -function readBlockMapping(state, nodeIndent, flowIndent) { - var following, - allowCompact, - _line, - _keyLine, - _keyLineStart, - _keyPos, - _tag = state.tag, - _anchor = state.anchor, - _result = {}, - overridableKeys = Object.create(null), - keyTag = null, - keyNode = null, - valueNode = null, - atExplicitKey = false, - detected = false, - ch; - - // there is a leading tab before this token, so it can't be a block sequence/mapping; - // it can still be flow sequence/mapping or a scalar - if (state.firstTabInLine !== -1) return false; - - if (state.anchor !== null) { - state.anchorMap[state.anchor] = _result; - } - - ch = state.input.charCodeAt(state.position); - - while (ch !== 0) { - if (!atExplicitKey && state.firstTabInLine !== -1) { - state.position = state.firstTabInLine; - throwError(state, 'tab characters must not be used in indentation'); - } - - following = state.input.charCodeAt(state.position + 1); - _line = state.line; // Save the current line. - - // - // Explicit notation case. There are two separate blocks: - // first for the key (denoted by "?") and second for the value (denoted by ":") - // - if ((ch === 0x3F/* ? */ || ch === 0x3A/* : */) && is_WS_OR_EOL(following)) { - - if (ch === 0x3F/* ? */) { - if (atExplicitKey) { - storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); - keyTag = keyNode = valueNode = null; - } - - detected = true; - atExplicitKey = true; - allowCompact = true; - - } else if (atExplicitKey) { - // i.e. 0x3A/* : */ === character after the explicit key. - atExplicitKey = false; - allowCompact = true; - - } else { - throwError(state, 'incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line'); - } - - state.position += 1; - ch = following; - - // - // Implicit notation case. Flow-style node as the key first, then ":", and the value. - // - } else { - _keyLine = state.line; - _keyLineStart = state.lineStart; - _keyPos = state.position; - - if (!composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) { - // Neither implicit nor explicit notation. - // Reading is done. Go to the epilogue. - break; - } - - if (state.line === _line) { - ch = state.input.charCodeAt(state.position); - - while (is_WHITE_SPACE(ch)) { - ch = state.input.charCodeAt(++state.position); - } - - if (ch === 0x3A/* : */) { - ch = state.input.charCodeAt(++state.position); - - if (!is_WS_OR_EOL(ch)) { - throwError(state, 'a whitespace character is expected after the key-value separator within a block mapping'); - } - - if (atExplicitKey) { - storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); - keyTag = keyNode = valueNode = null; - } - - detected = true; - atExplicitKey = false; - allowCompact = false; - keyTag = state.tag; - keyNode = state.result; - - } else if (detected) { - throwError(state, 'can not read an implicit mapping pair; a colon is missed'); - - } else { - state.tag = _tag; - state.anchor = _anchor; - return true; // Keep the result of `composeNode`. - } - - } else if (detected) { - throwError(state, 'can not read a block mapping entry; a multiline key may not be an implicit key'); - - } else { - state.tag = _tag; - state.anchor = _anchor; - return true; // Keep the result of `composeNode`. - } - } - - // - // Common reading code for both explicit and implicit notations. - // - if (state.line === _line || state.lineIndent > nodeIndent) { - if (atExplicitKey) { - _keyLine = state.line; - _keyLineStart = state.lineStart; - _keyPos = state.position; - } - - if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) { - if (atExplicitKey) { - keyNode = state.result; - } else { - valueNode = state.result; - } - } - - if (!atExplicitKey) { - storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _keyLine, _keyLineStart, _keyPos); - keyTag = keyNode = valueNode = null; - } - - skipSeparationSpace(state, true, -1); - ch = state.input.charCodeAt(state.position); - } - - if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) { - throwError(state, 'bad indentation of a mapping entry'); - } else if (state.lineIndent < nodeIndent) { - break; - } - } - - // - // Epilogue. - // - - // Special case: last mapping's node contains only the key in explicit notation. - if (atExplicitKey) { - storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); - } - - // Expose the resulting mapping. - if (detected) { - state.tag = _tag; - state.anchor = _anchor; - state.kind = 'mapping'; - state.result = _result; - } - - return detected; -} - -function readTagProperty(state) { - var _position, - isVerbatim = false, - isNamed = false, - tagHandle, - tagName, - ch; - - ch = state.input.charCodeAt(state.position); - - if (ch !== 0x21/* ! */) return false; - - if (state.tag !== null) { - throwError(state, 'duplication of a tag property'); - } - - ch = state.input.charCodeAt(++state.position); - - if (ch === 0x3C/* < */) { - isVerbatim = true; - ch = state.input.charCodeAt(++state.position); - - } else if (ch === 0x21/* ! */) { - isNamed = true; - tagHandle = '!!'; - ch = state.input.charCodeAt(++state.position); - - } else { - tagHandle = '!'; - } - - _position = state.position; - - if (isVerbatim) { - do { ch = state.input.charCodeAt(++state.position); } - while (ch !== 0 && ch !== 0x3E/* > */); - - if (state.position < state.length) { - tagName = state.input.slice(_position, state.position); - ch = state.input.charCodeAt(++state.position); - } else { - throwError(state, 'unexpected end of the stream within a verbatim tag'); - } - } else { - while (ch !== 0 && !is_WS_OR_EOL(ch)) { - - if (ch === 0x21/* ! */) { - if (!isNamed) { - tagHandle = state.input.slice(_position - 1, state.position + 1); - - if (!PATTERN_TAG_HANDLE.test(tagHandle)) { - throwError(state, 'named tag handle cannot contain such characters'); - } - - isNamed = true; - _position = state.position + 1; - } else { - throwError(state, 'tag suffix cannot contain exclamation marks'); - } - } - - ch = state.input.charCodeAt(++state.position); - } - - tagName = state.input.slice(_position, state.position); - - if (PATTERN_FLOW_INDICATORS.test(tagName)) { - throwError(state, 'tag suffix cannot contain flow indicator characters'); - } - } - - if (tagName && !PATTERN_TAG_URI.test(tagName)) { - throwError(state, 'tag name cannot contain such characters: ' + tagName); - } - - try { - tagName = decodeURIComponent(tagName); - } catch (err) { - throwError(state, 'tag name is malformed: ' + tagName); - } - - if (isVerbatim) { - state.tag = tagName; - - } else if (_hasOwnProperty$1.call(state.tagMap, tagHandle)) { - state.tag = state.tagMap[tagHandle] + tagName; - - } else if (tagHandle === '!') { - state.tag = '!' + tagName; - - } else if (tagHandle === '!!') { - state.tag = 'tag:yaml.org,2002:' + tagName; - - } else { - throwError(state, 'undeclared tag handle "' + tagHandle + '"'); - } - - return true; -} - -function readAnchorProperty(state) { - var _position, - ch; - - ch = state.input.charCodeAt(state.position); - - if (ch !== 0x26/* & */) return false; - - if (state.anchor !== null) { - throwError(state, 'duplication of an anchor property'); - } - - ch = state.input.charCodeAt(++state.position); - _position = state.position; - - while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { - ch = state.input.charCodeAt(++state.position); - } - - if (state.position === _position) { - throwError(state, 'name of an anchor node must contain at least one character'); - } - - state.anchor = state.input.slice(_position, state.position); - return true; -} - -function readAlias(state) { - var _position, alias, - ch; - - ch = state.input.charCodeAt(state.position); - - if (ch !== 0x2A/* * */) return false; - - ch = state.input.charCodeAt(++state.position); - _position = state.position; - - while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { - ch = state.input.charCodeAt(++state.position); - } - - if (state.position === _position) { - throwError(state, 'name of an alias node must contain at least one character'); - } - - alias = state.input.slice(_position, state.position); - - if (!_hasOwnProperty$1.call(state.anchorMap, alias)) { - throwError(state, 'unidentified alias "' + alias + '"'); - } - - state.result = state.anchorMap[alias]; - skipSeparationSpace(state, true, -1); - return true; -} - -function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) { - var allowBlockStyles, - allowBlockScalars, - allowBlockCollections, - indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this parentIndent) { - indentStatus = 1; - } else if (state.lineIndent === parentIndent) { - indentStatus = 0; - } else if (state.lineIndent < parentIndent) { - indentStatus = -1; - } - } - } - - if (indentStatus === 1) { - while (readTagProperty(state) || readAnchorProperty(state)) { - if (skipSeparationSpace(state, true, -1)) { - atNewLine = true; - allowBlockCollections = allowBlockStyles; - - if (state.lineIndent > parentIndent) { - indentStatus = 1; - } else if (state.lineIndent === parentIndent) { - indentStatus = 0; - } else if (state.lineIndent < parentIndent) { - indentStatus = -1; - } - } else { - allowBlockCollections = false; - } - } - } - - if (allowBlockCollections) { - allowBlockCollections = atNewLine || allowCompact; - } - - if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) { - if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) { - flowIndent = parentIndent; - } else { - flowIndent = parentIndent + 1; - } - - blockIndent = state.position - state.lineStart; - - if (indentStatus === 1) { - if (allowBlockCollections && - (readBlockSequence(state, blockIndent) || - readBlockMapping(state, blockIndent, flowIndent)) || - readFlowCollection(state, flowIndent)) { - hasContent = true; - } else { - if ((allowBlockScalars && readBlockScalar(state, flowIndent)) || - readSingleQuotedScalar(state, flowIndent) || - readDoubleQuotedScalar(state, flowIndent)) { - hasContent = true; - - } else if (readAlias(state)) { - hasContent = true; - - if (state.tag !== null || state.anchor !== null) { - throwError(state, 'alias node should not have any properties'); - } - - } else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) { - hasContent = true; - - if (state.tag === null) { - state.tag = '?'; - } - } - - if (state.anchor !== null) { - state.anchorMap[state.anchor] = state.result; - } - } - } else if (indentStatus === 0) { - // Special case: block sequences are allowed to have same indentation level as the parent. - // http://www.yaml.org/spec/1.2/spec.html#id2799784 - hasContent = allowBlockCollections && readBlockSequence(state, blockIndent); - } - } - - if (state.tag === null) { - if (state.anchor !== null) { - state.anchorMap[state.anchor] = state.result; - } - - } else if (state.tag === '?') { - // Implicit resolving is not allowed for non-scalar types, and '?' - // non-specific tag is only automatically assigned to plain scalars. - // - // We only need to check kind conformity in case user explicitly assigns '?' - // tag, for example like this: "! [0]" - // - if (state.result !== null && state.kind !== 'scalar') { - throwError(state, 'unacceptable node kind for ! tag; it should be "scalar", not "' + state.kind + '"'); - } - - for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) { - type = state.implicitTypes[typeIndex]; - - if (type.resolve(state.result)) { // `state.result` updated in resolver if matched - state.result = type.construct(state.result); - state.tag = type.tag; - if (state.anchor !== null) { - state.anchorMap[state.anchor] = state.result; - } - break; - } - } - } else if (state.tag !== '!') { - if (_hasOwnProperty$1.call(state.typeMap[state.kind || 'fallback'], state.tag)) { - type = state.typeMap[state.kind || 'fallback'][state.tag]; - } else { - // looking for multi type - type = null; - typeList = state.typeMap.multi[state.kind || 'fallback']; - - for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) { - if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) { - type = typeList[typeIndex]; - break; - } - } - } - - if (!type) { - throwError(state, 'unknown tag !<' + state.tag + '>'); - } - - if (state.result !== null && type.kind !== state.kind) { - throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"'); - } - - if (!type.resolve(state.result, state.tag)) { // `state.result` updated in resolver if matched - throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag'); - } else { - state.result = type.construct(state.result, state.tag); - if (state.anchor !== null) { - state.anchorMap[state.anchor] = state.result; - } - } - } - - if (state.listener !== null) { - state.listener('close', state); - } - return state.tag !== null || state.anchor !== null || hasContent; -} - -function readDocument(state) { - var documentStart = state.position, - _position, - directiveName, - directiveArgs, - hasDirectives = false, - ch; - - state.version = null; - state.checkLineBreaks = state.legacy; - state.tagMap = Object.create(null); - state.anchorMap = Object.create(null); - - while ((ch = state.input.charCodeAt(state.position)) !== 0) { - skipSeparationSpace(state, true, -1); - - ch = state.input.charCodeAt(state.position); - - if (state.lineIndent > 0 || ch !== 0x25/* % */) { - break; - } - - hasDirectives = true; - ch = state.input.charCodeAt(++state.position); - _position = state.position; - - while (ch !== 0 && !is_WS_OR_EOL(ch)) { - ch = state.input.charCodeAt(++state.position); - } - - directiveName = state.input.slice(_position, state.position); - directiveArgs = []; - - if (directiveName.length < 1) { - throwError(state, 'directive name must not be less than one character in length'); - } - - while (ch !== 0) { - while (is_WHITE_SPACE(ch)) { - ch = state.input.charCodeAt(++state.position); - } - - if (ch === 0x23/* # */) { - do { ch = state.input.charCodeAt(++state.position); } - while (ch !== 0 && !is_EOL(ch)); - break; - } - - if (is_EOL(ch)) break; - - _position = state.position; - - while (ch !== 0 && !is_WS_OR_EOL(ch)) { - ch = state.input.charCodeAt(++state.position); - } - - directiveArgs.push(state.input.slice(_position, state.position)); - } - - if (ch !== 0) readLineBreak(state); - - if (_hasOwnProperty$1.call(directiveHandlers, directiveName)) { - directiveHandlers[directiveName](state, directiveName, directiveArgs); - } else { - throwWarning(state, 'unknown document directive "' + directiveName + '"'); - } - } - - skipSeparationSpace(state, true, -1); - - if (state.lineIndent === 0 && - state.input.charCodeAt(state.position) === 0x2D/* - */ && - state.input.charCodeAt(state.position + 1) === 0x2D/* - */ && - state.input.charCodeAt(state.position + 2) === 0x2D/* - */) { - state.position += 3; - skipSeparationSpace(state, true, -1); - - } else if (hasDirectives) { - throwError(state, 'directives end mark is expected'); - } - - composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true); - skipSeparationSpace(state, true, -1); - - if (state.checkLineBreaks && - PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) { - throwWarning(state, 'non-ASCII line breaks are interpreted as content'); - } - - state.documents.push(state.result); - - if (state.position === state.lineStart && testDocumentSeparator(state)) { - - if (state.input.charCodeAt(state.position) === 0x2E/* . */) { - state.position += 3; - skipSeparationSpace(state, true, -1); - } - return; - } - - if (state.position < (state.length - 1)) { - throwError(state, 'end of the stream or a document separator is expected'); - } else { - return; - } -} - - -function loadDocuments(input, options) { - input = String(input); - options = options || {}; - - if (input.length !== 0) { - - // Add tailing `\n` if not exists - if (input.charCodeAt(input.length - 1) !== 0x0A/* LF */ && - input.charCodeAt(input.length - 1) !== 0x0D/* CR */) { - input += '\n'; - } - - // Strip BOM - if (input.charCodeAt(0) === 0xFEFF) { - input = input.slice(1); - } - } - - var state = new State$1(input, options); - - var nullpos = input.indexOf('\0'); - - if (nullpos !== -1) { - state.position = nullpos; - throwError(state, 'null byte is not allowed in input'); - } - - // Use 0 as string terminator. That significantly simplifies bounds check. - state.input += '\0'; - - while (state.input.charCodeAt(state.position) === 0x20/* Space */) { - state.lineIndent += 1; - state.position += 1; - } - - while (state.position < (state.length - 1)) { - readDocument(state); - } - - return state.documents; -} - - -function loadAll$1(input, iterator, options) { - if (iterator !== null && typeof iterator === 'object' && typeof options === 'undefined') { - options = iterator; - iterator = null; - } - - var documents = loadDocuments(input, options); - - if (typeof iterator !== 'function') { - return documents; - } - - for (var index = 0, length = documents.length; index < length; index += 1) { - iterator(documents[index]); - } -} - - -function load$1(input, options) { - var documents = loadDocuments(input, options); - - if (documents.length === 0) { - /*eslint-disable no-undefined*/ - return undefined; - } else if (documents.length === 1) { - return documents[0]; - } - throw new exception('expected a single document in the stream, but found more'); -} - - -var loadAll_1 = loadAll$1; -var load_1 = load$1; - -var loader = { - loadAll: loadAll_1, - load: load_1 -}; - -/*eslint-disable no-use-before-define*/ - - - - - -var _toString = Object.prototype.toString; -var _hasOwnProperty = Object.prototype.hasOwnProperty; - -var CHAR_BOM = 0xFEFF; -var CHAR_TAB = 0x09; /* Tab */ -var CHAR_LINE_FEED = 0x0A; /* LF */ -var CHAR_CARRIAGE_RETURN = 0x0D; /* CR */ -var CHAR_SPACE = 0x20; /* Space */ -var CHAR_EXCLAMATION = 0x21; /* ! */ -var CHAR_DOUBLE_QUOTE = 0x22; /* " */ -var CHAR_SHARP = 0x23; /* # */ -var CHAR_PERCENT = 0x25; /* % */ -var CHAR_AMPERSAND = 0x26; /* & */ -var CHAR_SINGLE_QUOTE = 0x27; /* ' */ -var CHAR_ASTERISK = 0x2A; /* * */ -var CHAR_COMMA = 0x2C; /* , */ -var CHAR_MINUS = 0x2D; /* - */ -var CHAR_COLON = 0x3A; /* : */ -var CHAR_EQUALS = 0x3D; /* = */ -var CHAR_GREATER_THAN = 0x3E; /* > */ -var CHAR_QUESTION = 0x3F; /* ? */ -var CHAR_COMMERCIAL_AT = 0x40; /* @ */ -var CHAR_LEFT_SQUARE_BRACKET = 0x5B; /* [ */ -var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */ -var CHAR_GRAVE_ACCENT = 0x60; /* ` */ -var CHAR_LEFT_CURLY_BRACKET = 0x7B; /* { */ -var CHAR_VERTICAL_LINE = 0x7C; /* | */ -var CHAR_RIGHT_CURLY_BRACKET = 0x7D; /* } */ - -var ESCAPE_SEQUENCES = {}; - -ESCAPE_SEQUENCES[0x00] = '\\0'; -ESCAPE_SEQUENCES[0x07] = '\\a'; -ESCAPE_SEQUENCES[0x08] = '\\b'; -ESCAPE_SEQUENCES[0x09] = '\\t'; -ESCAPE_SEQUENCES[0x0A] = '\\n'; -ESCAPE_SEQUENCES[0x0B] = '\\v'; -ESCAPE_SEQUENCES[0x0C] = '\\f'; -ESCAPE_SEQUENCES[0x0D] = '\\r'; -ESCAPE_SEQUENCES[0x1B] = '\\e'; -ESCAPE_SEQUENCES[0x22] = '\\"'; -ESCAPE_SEQUENCES[0x5C] = '\\\\'; -ESCAPE_SEQUENCES[0x85] = '\\N'; -ESCAPE_SEQUENCES[0xA0] = '\\_'; -ESCAPE_SEQUENCES[0x2028] = '\\L'; -ESCAPE_SEQUENCES[0x2029] = '\\P'; - -var DEPRECATED_BOOLEANS_SYNTAX = [ - 'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON', - 'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF' -]; - -var DEPRECATED_BASE60_SYNTAX = /^[-+]?[0-9_]+(?::[0-9_]+)+(?:\.[0-9_]*)?$/; - -function compileStyleMap(schema, map) { - var result, keys, index, length, tag, style, type; - - if (map === null) return {}; - - result = {}; - keys = Object.keys(map); - - for (index = 0, length = keys.length; index < length; index += 1) { - tag = keys[index]; - style = String(map[tag]); - - if (tag.slice(0, 2) === '!!') { - tag = 'tag:yaml.org,2002:' + tag.slice(2); - } - type = schema.compiledTypeMap['fallback'][tag]; - - if (type && _hasOwnProperty.call(type.styleAliases, style)) { - style = type.styleAliases[style]; - } - - result[tag] = style; - } - - return result; -} - -function encodeHex(character) { - var string, handle, length; - - string = character.toString(16).toUpperCase(); - - if (character <= 0xFF) { - handle = 'x'; - length = 2; - } else if (character <= 0xFFFF) { - handle = 'u'; - length = 4; - } else if (character <= 0xFFFFFFFF) { - handle = 'U'; - length = 8; - } else { - throw new exception('code point within a string may not be greater than 0xFFFFFFFF'); - } - - return '\\' + handle + common.repeat('0', length - string.length) + string; -} - - -var QUOTING_TYPE_SINGLE = 1, - QUOTING_TYPE_DOUBLE = 2; - -function State(options) { - this.schema = options['schema'] || _default; - this.indent = Math.max(1, (options['indent'] || 2)); - this.noArrayIndent = options['noArrayIndent'] || false; - this.skipInvalid = options['skipInvalid'] || false; - this.flowLevel = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']); - this.styleMap = compileStyleMap(this.schema, options['styles'] || null); - this.sortKeys = options['sortKeys'] || false; - this.lineWidth = options['lineWidth'] || 80; - this.noRefs = options['noRefs'] || false; - this.noCompatMode = options['noCompatMode'] || false; - this.condenseFlow = options['condenseFlow'] || false; - this.quotingType = options['quotingType'] === '"' ? QUOTING_TYPE_DOUBLE : QUOTING_TYPE_SINGLE; - this.forceQuotes = options['forceQuotes'] || false; - this.replacer = typeof options['replacer'] === 'function' ? options['replacer'] : null; - - this.implicitTypes = this.schema.compiledImplicit; - this.explicitTypes = this.schema.compiledExplicit; - - this.tag = null; - this.result = ''; - - this.duplicates = []; - this.usedDuplicates = null; -} - -// Indents every line in a string. Empty lines (\n only) are not indented. -function indentString(string, spaces) { - var ind = common.repeat(' ', spaces), - position = 0, - next = -1, - result = '', - line, - length = string.length; - - while (position < length) { - next = string.indexOf('\n', position); - if (next === -1) { - line = string.slice(position); - position = length; - } else { - line = string.slice(position, next + 1); - position = next + 1; - } - - if (line.length && line !== '\n') result += ind; - - result += line; - } - - return result; -} - -function generateNextLine(state, level) { - return '\n' + common.repeat(' ', state.indent * level); -} - -function testImplicitResolving(state, str) { - var index, length, type; - - for (index = 0, length = state.implicitTypes.length; index < length; index += 1) { - type = state.implicitTypes[index]; - - if (type.resolve(str)) { - return true; - } - } - - return false; -} - -// [33] s-white ::= s-space | s-tab -function isWhitespace(c) { - return c === CHAR_SPACE || c === CHAR_TAB; -} - -// Returns true if the character can be printed without escaping. -// From YAML 1.2: "any allowed characters known to be non-printable -// should also be escaped. [However,] This isn’t mandatory" -// Derived from nb-char - \t - #x85 - #xA0 - #x2028 - #x2029. -function isPrintable(c) { - return (0x00020 <= c && c <= 0x00007E) - || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029) - || ((0x0E000 <= c && c <= 0x00FFFD) && c !== CHAR_BOM) - || (0x10000 <= c && c <= 0x10FFFF); -} - -// [34] ns-char ::= nb-char - s-white -// [27] nb-char ::= c-printable - b-char - c-byte-order-mark -// [26] b-char ::= b-line-feed | b-carriage-return -// Including s-white (for some reason, examples doesn't match specs in this aspect) -// ns-char ::= c-printable - b-line-feed - b-carriage-return - c-byte-order-mark -function isNsCharOrWhitespace(c) { - return isPrintable(c) - && c !== CHAR_BOM - // - b-char - && c !== CHAR_CARRIAGE_RETURN - && c !== CHAR_LINE_FEED; -} - -// [127] ns-plain-safe(c) ::= c = flow-out ⇒ ns-plain-safe-out -// c = flow-in ⇒ ns-plain-safe-in -// c = block-key ⇒ ns-plain-safe-out -// c = flow-key ⇒ ns-plain-safe-in -// [128] ns-plain-safe-out ::= ns-char -// [129] ns-plain-safe-in ::= ns-char - c-flow-indicator -// [130] ns-plain-char(c) ::= ( ns-plain-safe(c) - “:” - “#” ) -// | ( /* An ns-char preceding */ “#” ) -// | ( “:” /* Followed by an ns-plain-safe(c) */ ) -function isPlainSafe(c, prev, inblock) { - var cIsNsCharOrWhitespace = isNsCharOrWhitespace(c); - var cIsNsChar = cIsNsCharOrWhitespace && !isWhitespace(c); - return ( - // ns-plain-safe - inblock ? // c = flow-in - cIsNsCharOrWhitespace - : cIsNsCharOrWhitespace - // - c-flow-indicator - && c !== CHAR_COMMA - && c !== CHAR_LEFT_SQUARE_BRACKET - && c !== CHAR_RIGHT_SQUARE_BRACKET - && c !== CHAR_LEFT_CURLY_BRACKET - && c !== CHAR_RIGHT_CURLY_BRACKET - ) - // ns-plain-char - && c !== CHAR_SHARP // false on '#' - && !(prev === CHAR_COLON && !cIsNsChar) // false on ': ' - || (isNsCharOrWhitespace(prev) && !isWhitespace(prev) && c === CHAR_SHARP) // change to true on '[^ ]#' - || (prev === CHAR_COLON && cIsNsChar); // change to true on ':[^ ]' -} - -// Simplified test for values allowed as the first character in plain style. -function isPlainSafeFirst(c) { - // Uses a subset of ns-char - c-indicator - // where ns-char = nb-char - s-white. - // No support of ( ( “?” | “:” | “-” ) /* Followed by an ns-plain-safe(c)) */ ) part - return isPrintable(c) && c !== CHAR_BOM - && !isWhitespace(c) // - s-white - // - (c-indicator ::= - // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}” - && c !== CHAR_MINUS - && c !== CHAR_QUESTION - && c !== CHAR_COLON - && c !== CHAR_COMMA - && c !== CHAR_LEFT_SQUARE_BRACKET - && c !== CHAR_RIGHT_SQUARE_BRACKET - && c !== CHAR_LEFT_CURLY_BRACKET - && c !== CHAR_RIGHT_CURLY_BRACKET - // | “#” | “&” | “*” | “!” | “|” | “=” | “>” | “'” | “"” - && c !== CHAR_SHARP - && c !== CHAR_AMPERSAND - && c !== CHAR_ASTERISK - && c !== CHAR_EXCLAMATION - && c !== CHAR_VERTICAL_LINE - && c !== CHAR_EQUALS - && c !== CHAR_GREATER_THAN - && c !== CHAR_SINGLE_QUOTE - && c !== CHAR_DOUBLE_QUOTE - // | “%” | “@” | “`”) - && c !== CHAR_PERCENT - && c !== CHAR_COMMERCIAL_AT - && c !== CHAR_GRAVE_ACCENT; -} - -// Simplified test for values allowed as the last character in plain style. -function isPlainSafeLast(c) { - // just not whitespace or colon, it will be checked to be plain character later - return !isWhitespace(c) && c !== CHAR_COLON; -} - -// Same as 'string'.codePointAt(pos), but works in older browsers. -function codePointAt(string, pos) { - var first = string.charCodeAt(pos), second; - if (first >= 0xD800 && first <= 0xDBFF && pos + 1 < string.length) { - second = string.charCodeAt(pos + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } - } - return first; -} - -// Determines whether block indentation indicator is required. -function needIndentIndicator(string) { - var leadingSpaceRe = /^\n* /; - return leadingSpaceRe.test(string); -} - -var STYLE_PLAIN = 1, - STYLE_SINGLE = 2, - STYLE_LITERAL = 3, - STYLE_FOLDED = 4, - STYLE_DOUBLE = 5; - -// Determines which scalar styles are possible and returns the preferred style. -// lineWidth = -1 => no limit. -// Pre-conditions: str.length > 0. -// Post-conditions: -// STYLE_PLAIN or STYLE_SINGLE => no \n are in the string. -// STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1). -// STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1). -function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, - testAmbiguousType, quotingType, forceQuotes, inblock) { - - var i; - var char = 0; - var prevChar = null; - var hasLineBreak = false; - var hasFoldableLine = false; // only checked if shouldTrackWidth - var shouldTrackWidth = lineWidth !== -1; - var previousLineBreak = -1; // count the first line correctly - var plain = isPlainSafeFirst(codePointAt(string, 0)) - && isPlainSafeLast(codePointAt(string, string.length - 1)); - - if (singleLineOnly || forceQuotes) { - // Case: no block styles. - // Check for disallowed characters to rule out plain and single. - for (i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) { - char = codePointAt(string, i); - if (!isPrintable(char)) { - return STYLE_DOUBLE; - } - plain = plain && isPlainSafe(char, prevChar, inblock); - prevChar = char; - } - } else { - // Case: block styles permitted. - for (i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) { - char = codePointAt(string, i); - if (char === CHAR_LINE_FEED) { - hasLineBreak = true; - // Check if any line can be folded. - if (shouldTrackWidth) { - hasFoldableLine = hasFoldableLine || - // Foldable line = too long, and not more-indented. - (i - previousLineBreak - 1 > lineWidth && - string[previousLineBreak + 1] !== ' '); - previousLineBreak = i; - } - } else if (!isPrintable(char)) { - return STYLE_DOUBLE; - } - plain = plain && isPlainSafe(char, prevChar, inblock); - prevChar = char; - } - // in case the end is missing a \n - hasFoldableLine = hasFoldableLine || (shouldTrackWidth && - (i - previousLineBreak - 1 > lineWidth && - string[previousLineBreak + 1] !== ' ')); - } - // Although every style can represent \n without escaping, prefer block styles - // for multiline, since they're more readable and they don't add empty lines. - // Also prefer folding a super-long line. - if (!hasLineBreak && !hasFoldableLine) { - // Strings interpretable as another type have to be quoted; - // e.g. the string 'true' vs. the boolean true. - if (plain && !forceQuotes && !testAmbiguousType(string)) { - return STYLE_PLAIN; - } - return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE; - } - // Edge case: block indentation indicator can only have one digit. - if (indentPerLevel > 9 && needIndentIndicator(string)) { - return STYLE_DOUBLE; - } - // At this point we know block styles are valid. - // Prefer literal style unless we want to fold. - if (!forceQuotes) { - return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL; - } - return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE; -} - -// Note: line breaking/folding is implemented for only the folded style. -// NB. We drop the last trailing newline (if any) of a returned block scalar -// since the dumper adds its own newline. This always works: -// • No ending newline => unaffected; already using strip "-" chomping. -// • Ending newline => removed then restored. -// Importantly, this keeps the "+" chomp indicator from gaining an extra line. -function writeScalar(state, string, level, iskey, inblock) { - state.dump = (function () { - if (string.length === 0) { - return state.quotingType === QUOTING_TYPE_DOUBLE ? '""' : "''"; - } - if (!state.noCompatMode) { - if (DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1 || DEPRECATED_BASE60_SYNTAX.test(string)) { - return state.quotingType === QUOTING_TYPE_DOUBLE ? ('"' + string + '"') : ("'" + string + "'"); - } - } - - var indent = state.indent * Math.max(1, level); // no 0-indent scalars - // As indentation gets deeper, let the width decrease monotonically - // to the lower bound min(state.lineWidth, 40). - // Note that this implies - // state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound. - // state.lineWidth > 40 + state.indent: width decreases until the lower bound. - // This behaves better than a constant minimum width which disallows narrower options, - // or an indent threshold which causes the width to suddenly increase. - var lineWidth = state.lineWidth === -1 - ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent); - - // Without knowing if keys are implicit/explicit, assume implicit for safety. - var singleLineOnly = iskey - // No block styles in flow mode. - || (state.flowLevel > -1 && level >= state.flowLevel); - function testAmbiguity(string) { - return testImplicitResolving(state, string); - } - - switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, - testAmbiguity, state.quotingType, state.forceQuotes && !iskey, inblock)) { - - case STYLE_PLAIN: - return string; - case STYLE_SINGLE: - return "'" + string.replace(/'/g, "''") + "'"; - case STYLE_LITERAL: - return '|' + blockHeader(string, state.indent) - + dropEndingNewline(indentString(string, indent)); - case STYLE_FOLDED: - return '>' + blockHeader(string, state.indent) - + dropEndingNewline(indentString(foldString(string, lineWidth), indent)); - case STYLE_DOUBLE: - return '"' + escapeString(string) + '"'; - default: - throw new exception('impossible error: invalid scalar style'); - } - }()); -} - -// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9. -function blockHeader(string, indentPerLevel) { - var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : ''; - - // note the special case: the string '\n' counts as a "trailing" empty line. - var clip = string[string.length - 1] === '\n'; - var keep = clip && (string[string.length - 2] === '\n' || string === '\n'); - var chomp = keep ? '+' : (clip ? '' : '-'); - - return indentIndicator + chomp + '\n'; -} - -// (See the note for writeScalar.) -function dropEndingNewline(string) { - return string[string.length - 1] === '\n' ? string.slice(0, -1) : string; -} - -// Note: a long line without a suitable break point will exceed the width limit. -// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0. -function foldString(string, width) { - // In folded style, $k$ consecutive newlines output as $k+1$ newlines— - // unless they're before or after a more-indented line, or at the very - // beginning or end, in which case $k$ maps to $k$. - // Therefore, parse each chunk as newline(s) followed by a content line. - var lineRe = /(\n+)([^\n]*)/g; - - // first line (possibly an empty line) - var result = (function () { - var nextLF = string.indexOf('\n'); - nextLF = nextLF !== -1 ? nextLF : string.length; - lineRe.lastIndex = nextLF; - return foldLine(string.slice(0, nextLF), width); - }()); - // If we haven't reached the first content line yet, don't add an extra \n. - var prevMoreIndented = string[0] === '\n' || string[0] === ' '; - var moreIndented; - - // rest of the lines - var match; - while ((match = lineRe.exec(string))) { - var prefix = match[1], line = match[2]; - moreIndented = (line[0] === ' '); - result += prefix - + (!prevMoreIndented && !moreIndented && line !== '' - ? '\n' : '') - + foldLine(line, width); - prevMoreIndented = moreIndented; - } - - return result; -} - -// Greedy line breaking. -// Picks the longest line under the limit each time, -// otherwise settles for the shortest line over the limit. -// NB. More-indented lines *cannot* be folded, as that would add an extra \n. -function foldLine(line, width) { - if (line === '' || line[0] === ' ') return line; - - // Since a more-indented line adds a \n, breaks can't be followed by a space. - var breakRe = / [^ ]/g; // note: the match index will always be <= length-2. - var match; - // start is an inclusive index. end, curr, and next are exclusive. - var start = 0, end, curr = 0, next = 0; - var result = ''; - - // Invariants: 0 <= start <= length-1. - // 0 <= curr <= next <= max(0, length-2). curr - start <= width. - // Inside the loop: - // A match implies length >= 2, so curr and next are <= length-2. - while ((match = breakRe.exec(line))) { - next = match.index; - // maintain invariant: curr - start <= width - if (next - start > width) { - end = (curr > start) ? curr : next; // derive end <= length-2 - result += '\n' + line.slice(start, end); - // skip the space that was output as \n - start = end + 1; // derive start <= length-1 - } - curr = next; - } - - // By the invariants, start <= length-1, so there is something left over. - // It is either the whole string or a part starting from non-whitespace. - result += '\n'; - // Insert a break if the remainder is too long and there is a break available. - if (line.length - start > width && curr > start) { - result += line.slice(start, curr) + '\n' + line.slice(curr + 1); - } else { - result += line.slice(start); - } - - return result.slice(1); // drop extra \n joiner -} - -// Escapes a double-quoted string. -function escapeString(string) { - var result = ''; - var char = 0; - var escapeSeq; - - for (var i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) { - char = codePointAt(string, i); - escapeSeq = ESCAPE_SEQUENCES[char]; - - if (!escapeSeq && isPrintable(char)) { - result += string[i]; - if (char >= 0x10000) result += string[i + 1]; - } else { - result += escapeSeq || encodeHex(char); - } - } - - return result; -} - -function writeFlowSequence(state, level, object) { - var _result = '', - _tag = state.tag, - index, - length, - value; - - for (index = 0, length = object.length; index < length; index += 1) { - value = object[index]; - - if (state.replacer) { - value = state.replacer.call(object, String(index), value); - } - - // Write only valid elements, put null instead of invalid elements. - if (writeNode(state, level, value, false, false) || - (typeof value === 'undefined' && - writeNode(state, level, null, false, false))) { - - if (_result !== '') _result += ',' + (!state.condenseFlow ? ' ' : ''); - _result += state.dump; - } - } - - state.tag = _tag; - state.dump = '[' + _result + ']'; -} - -function writeBlockSequence(state, level, object, compact) { - var _result = '', - _tag = state.tag, - index, - length, - value; - - for (index = 0, length = object.length; index < length; index += 1) { - value = object[index]; - - if (state.replacer) { - value = state.replacer.call(object, String(index), value); - } - - // Write only valid elements, put null instead of invalid elements. - if (writeNode(state, level + 1, value, true, true, false, true) || - (typeof value === 'undefined' && - writeNode(state, level + 1, null, true, true, false, true))) { - - if (!compact || _result !== '') { - _result += generateNextLine(state, level); - } - - if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { - _result += '-'; - } else { - _result += '- '; - } - - _result += state.dump; - } - } - - state.tag = _tag; - state.dump = _result || '[]'; // Empty sequence if no valid values. -} - -function writeFlowMapping(state, level, object) { - var _result = '', - _tag = state.tag, - objectKeyList = Object.keys(object), - index, - length, - objectKey, - objectValue, - pairBuffer; - - for (index = 0, length = objectKeyList.length; index < length; index += 1) { - - pairBuffer = ''; - if (_result !== '') pairBuffer += ', '; - - if (state.condenseFlow) pairBuffer += '"'; - - objectKey = objectKeyList[index]; - objectValue = object[objectKey]; - - if (state.replacer) { - objectValue = state.replacer.call(object, objectKey, objectValue); - } - - if (!writeNode(state, level, objectKey, false, false)) { - continue; // Skip this pair because of invalid key; - } - - if (state.dump.length > 1024) pairBuffer += '? '; - - pairBuffer += state.dump + (state.condenseFlow ? '"' : '') + ':' + (state.condenseFlow ? '' : ' '); - - if (!writeNode(state, level, objectValue, false, false)) { - continue; // Skip this pair because of invalid value. - } - - pairBuffer += state.dump; - - // Both key and value are valid. - _result += pairBuffer; - } - - state.tag = _tag; - state.dump = '{' + _result + '}'; -} - -function writeBlockMapping(state, level, object, compact) { - var _result = '', - _tag = state.tag, - objectKeyList = Object.keys(object), - index, - length, - objectKey, - objectValue, - explicitPair, - pairBuffer; - - // Allow sorting keys so that the output file is deterministic - if (state.sortKeys === true) { - // Default sorting - objectKeyList.sort(); - } else if (typeof state.sortKeys === 'function') { - // Custom sort function - objectKeyList.sort(state.sortKeys); - } else if (state.sortKeys) { - // Something is wrong - throw new exception('sortKeys must be a boolean or a function'); - } - - for (index = 0, length = objectKeyList.length; index < length; index += 1) { - pairBuffer = ''; - - if (!compact || _result !== '') { - pairBuffer += generateNextLine(state, level); - } - - objectKey = objectKeyList[index]; - objectValue = object[objectKey]; - - if (state.replacer) { - objectValue = state.replacer.call(object, objectKey, objectValue); - } - - if (!writeNode(state, level + 1, objectKey, true, true, true)) { - continue; // Skip this pair because of invalid key. - } - - explicitPair = (state.tag !== null && state.tag !== '?') || - (state.dump && state.dump.length > 1024); - - if (explicitPair) { - if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { - pairBuffer += '?'; - } else { - pairBuffer += '? '; - } - } - - pairBuffer += state.dump; - - if (explicitPair) { - pairBuffer += generateNextLine(state, level); - } - - if (!writeNode(state, level + 1, objectValue, true, explicitPair)) { - continue; // Skip this pair because of invalid value. - } - - if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { - pairBuffer += ':'; - } else { - pairBuffer += ': '; - } - - pairBuffer += state.dump; - - // Both key and value are valid. - _result += pairBuffer; - } - - state.tag = _tag; - state.dump = _result || '{}'; // Empty mapping if no valid pairs. -} - -function detectType(state, object, explicit) { - var _result, typeList, index, length, type, style; - - typeList = explicit ? state.explicitTypes : state.implicitTypes; - - for (index = 0, length = typeList.length; index < length; index += 1) { - type = typeList[index]; - - if ((type.instanceOf || type.predicate) && - (!type.instanceOf || ((typeof object === 'object') && (object instanceof type.instanceOf))) && - (!type.predicate || type.predicate(object))) { - - if (explicit) { - if (type.multi && type.representName) { - state.tag = type.representName(object); - } else { - state.tag = type.tag; - } - } else { - state.tag = '?'; - } - - if (type.represent) { - style = state.styleMap[type.tag] || type.defaultStyle; - - if (_toString.call(type.represent) === '[object Function]') { - _result = type.represent(object, style); - } else if (_hasOwnProperty.call(type.represent, style)) { - _result = type.represent[style](object, style); - } else { - throw new exception('!<' + type.tag + '> tag resolver accepts not "' + style + '" style'); - } - - state.dump = _result; - } - - return true; - } - } - - return false; -} - -// Serializes `object` and writes it to global `result`. -// Returns true on success, or false on invalid object. -// -function writeNode(state, level, object, block, compact, iskey, isblockseq) { - state.tag = null; - state.dump = object; - - if (!detectType(state, object, false)) { - detectType(state, object, true); - } - - var type = _toString.call(state.dump); - var inblock = block; - var tagStr; - - if (block) { - block = (state.flowLevel < 0 || state.flowLevel > level); - } - - var objectOrArray = type === '[object Object]' || type === '[object Array]', - duplicateIndex, - duplicate; - - if (objectOrArray) { - duplicateIndex = state.duplicates.indexOf(object); - duplicate = duplicateIndex !== -1; - } - - if ((state.tag !== null && state.tag !== '?') || duplicate || (state.indent !== 2 && level > 0)) { - compact = false; - } - - if (duplicate && state.usedDuplicates[duplicateIndex]) { - state.dump = '*ref_' + duplicateIndex; - } else { - if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) { - state.usedDuplicates[duplicateIndex] = true; - } - if (type === '[object Object]') { - if (block && (Object.keys(state.dump).length !== 0)) { - writeBlockMapping(state, level, state.dump, compact); - if (duplicate) { - state.dump = '&ref_' + duplicateIndex + state.dump; - } - } else { - writeFlowMapping(state, level, state.dump); - if (duplicate) { - state.dump = '&ref_' + duplicateIndex + ' ' + state.dump; - } - } - } else if (type === '[object Array]') { - if (block && (state.dump.length !== 0)) { - if (state.noArrayIndent && !isblockseq && level > 0) { - writeBlockSequence(state, level - 1, state.dump, compact); - } else { - writeBlockSequence(state, level, state.dump, compact); - } - if (duplicate) { - state.dump = '&ref_' + duplicateIndex + state.dump; - } - } else { - writeFlowSequence(state, level, state.dump); - if (duplicate) { - state.dump = '&ref_' + duplicateIndex + ' ' + state.dump; - } - } - } else if (type === '[object String]') { - if (state.tag !== '?') { - writeScalar(state, state.dump, level, iskey, inblock); - } - } else if (type === '[object Undefined]') { - return false; - } else { - if (state.skipInvalid) return false; - throw new exception('unacceptable kind of an object to dump ' + type); - } - - if (state.tag !== null && state.tag !== '?') { - // Need to encode all characters except those allowed by the spec: - // - // [35] ns-dec-digit ::= [#x30-#x39] /* 0-9 */ - // [36] ns-hex-digit ::= ns-dec-digit - // | [#x41-#x46] /* A-F */ | [#x61-#x66] /* a-f */ - // [37] ns-ascii-letter ::= [#x41-#x5A] /* A-Z */ | [#x61-#x7A] /* a-z */ - // [38] ns-word-char ::= ns-dec-digit | ns-ascii-letter | “-” - // [39] ns-uri-char ::= “%” ns-hex-digit ns-hex-digit | ns-word-char | “#” - // | “;” | “/” | “?” | “:” | “@” | “&” | “=” | “+” | “$” | “,” - // | “_” | “.” | “!” | “~” | “*” | “'” | “(” | “)” | “[” | “]” - // - // Also need to encode '!' because it has special meaning (end of tag prefix). - // - tagStr = encodeURI( - state.tag[0] === '!' ? state.tag.slice(1) : state.tag - ).replace(/!/g, '%21'); - - if (state.tag[0] === '!') { - tagStr = '!' + tagStr; - } else if (tagStr.slice(0, 18) === 'tag:yaml.org,2002:') { - tagStr = '!!' + tagStr.slice(18); - } else { - tagStr = '!<' + tagStr + '>'; - } - - state.dump = tagStr + ' ' + state.dump; - } - } - - return true; -} - -function getDuplicateReferences(object, state) { - var objects = [], - duplicatesIndexes = [], - index, - length; - - inspectNode(object, objects, duplicatesIndexes); - - for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) { - state.duplicates.push(objects[duplicatesIndexes[index]]); - } - state.usedDuplicates = new Array(length); -} - -function inspectNode(object, objects, duplicatesIndexes) { - var objectKeyList, - index, - length; - - if (object !== null && typeof object === 'object') { - index = objects.indexOf(object); - if (index !== -1) { - if (duplicatesIndexes.indexOf(index) === -1) { - duplicatesIndexes.push(index); - } - } else { - objects.push(object); - - if (Array.isArray(object)) { - for (index = 0, length = object.length; index < length; index += 1) { - inspectNode(object[index], objects, duplicatesIndexes); - } - } else { - objectKeyList = Object.keys(object); - - for (index = 0, length = objectKeyList.length; index < length; index += 1) { - inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes); - } - } - } - } -} - -function dump$1(input, options) { - options = options || {}; - - var state = new State(options); - - if (!state.noRefs) getDuplicateReferences(input, state); - - var value = input; - - if (state.replacer) { - value = state.replacer.call({ '': value }, '', value); - } - - if (writeNode(state, 0, value, true, true)) return state.dump + '\n'; - - return ''; -} - -var dump_1 = dump$1; - -var dumper = { - dump: dump_1 -}; - -function renamed(from, to) { - return function () { - throw new Error('Function yaml.' + from + ' is removed in js-yaml 4. ' + - 'Use yaml.' + to + ' instead, which is now safe by default.'); - }; -} - - -var Type = type; -var Schema = schema; -var FAILSAFE_SCHEMA = failsafe; -var JSON_SCHEMA = json; -var CORE_SCHEMA = core; -var DEFAULT_SCHEMA = _default; -var load = loader.load; -var loadAll = loader.loadAll; -var dump = dumper.dump; -var YAMLException = exception; - -// Re-export all types in case user wants to create custom schema -var types = { - binary: binary, - float: float, - map: map, - null: _null, - pairs: pairs, - set: set, - timestamp: timestamp, - bool: bool, - int: int, - merge: merge, - omap: omap, - seq: seq, - str: str -}; - -// Removed functions from JS-YAML 3.0.x -var safeLoad = renamed('safeLoad', 'load'); -var safeLoadAll = renamed('safeLoadAll', 'loadAll'); -var safeDump = renamed('safeDump', 'dump'); - -var jsYaml = { - Type: Type, - Schema: Schema, - FAILSAFE_SCHEMA: FAILSAFE_SCHEMA, - JSON_SCHEMA: JSON_SCHEMA, - CORE_SCHEMA: CORE_SCHEMA, - DEFAULT_SCHEMA: DEFAULT_SCHEMA, - load: load, - loadAll: loadAll, - dump: dump, - YAMLException: YAMLException, - types: types, - safeLoad: safeLoad, - safeLoadAll: safeLoadAll, - safeDump: safeDump -}; - -const {toDisplayString:_toDisplayString$3,createTextVNode:_createTextVNode$3,resolveComponent:_resolveComponent$3,withCtx:_withCtx$3,createVNode:_createVNode$3,openBlock:_openBlock$3,createBlock:_createBlock$3,createCommentVNode:_createCommentVNode$3,createElementBlock:_createElementBlock$1,withModifiers:_withModifiers$3,createElementVNode:_createElementVNode$2} = await importShared('vue'); - - -const _hoisted_1$1 = { key: 0 }; -const _hoisted_2$1 = { - key: 0, - class: "mt-2" -}; -const _hoisted_3$1 = { - key: 0, - class: "mt-2" -}; -const _hoisted_4$1 = { - key: 0, - class: "mt-2" -}; -const _hoisted_5$1 = { - key: 0, - class: "mt-2" -}; -const _hoisted_6$1 = { - key: 0, - class: "mt-2" -}; -const _hoisted_7$1 = { key: 0 }; - -const {ref: ref$3,watch,computed: computed$3} = await importShared('vue'); - - - -const _sfc_main$3 = { - __name: 'ProxiesDialog', - props: { - modelValue: {type: Boolean, default: false}, - proxyData: {type: Object, default: null}, - loading: {type: Boolean, default: false} -}, - emits: ['update:modelValue', 'save'], - setup(__props, { emit: __emit }) { - -const props = __props; - -const emit = __emit; - -const dialog = computed$3({ - get: () => props.modelValue, - set: (value) => emit('update:modelValue', value) -}); - -const isEditing = computed$3(() => !!props.proxyData?.name); - -const proxyForm = ref$3(null); -const tab = ref$3('general'); -const proxy = ref$3({}); -const wsHeaderString = ref$3(''); -const httpHeaderString = ref$3(''); - -const jsonValidator = (value) => { - if (!value) return true - try { - JSON.parse(value); - return true - } catch (e) { - return '无效的JSON格式' - } -}; - -const proxyTypes = ['ss', 'ssr', 'vmess', 'vless', 'trojan', 'http', 'snell', 'tuic', 'hysteria', 'hysteria2']; - -// Default structure to avoid template errors -const getDefaultProxy = () => ({ - name: '', - type: 'ss', - server: '', - port: 443, - udp: false, - tfo: false, - mptcp: false, - tls: false, - 'skip-cert-verify': false, - alpn: [], - network: null, - 'ws-opts': { - path: '/', - headers: {}, - 'max-early-data': null, - 'early-data-header-name': null, - 'v2ray-http-upgrade': false, - 'v2ray-http-upgrade-fast-open': false - }, - 'http-opts': {method: 'GET', path: ['/'], headers: {}}, - 'h2-opts': {host: [], path: '/'}, - 'grpc-opts': {'grpc-service-name': ''}, - smux: { - enabled: false, - protocol: 'h2mux', - 'max-connections': null, - 'min-streams': null, - 'max-streams': null, - padding: false, - statistic: false, - 'only-tcp': false, - 'brutal-opts': { - enabled: false, - up: null, - down: null - } - }, - rescind: false, -}); - -// Watch for props changes to initialize data when the dialog opens -watch(() => props.modelValue, (newValue) => { - // Guard to run only when the dialog opens - if (newValue) { - // Deep copy proxyData or use default. - const defaultProxy = getDefaultProxy(); - proxy.value = JSON.parse(JSON.stringify(props.proxyData || defaultProxy)); - - // Ensure network field exists to prevent undefined model value - if (!('network' in proxy.value)) { - proxy.value.network = null; - } - - // Ensure nested opts objects exist to prevent template errors - proxy.value['ws-opts'] = Object.assign({}, defaultProxy['ws-opts'], proxy.value['ws-opts']); - proxy.value['http-opts'] = Object.assign({}, defaultProxy['http-opts'], proxy.value['http-opts']); - proxy.value['h2-opts'] = Object.assign({}, defaultProxy['h2-opts'], proxy.value['h2-opts']); - proxy.value['grpc-opts'] = Object.assign({}, defaultProxy['grpc-opts'], proxy.value['grpc-opts']); - proxy.value.smux = Object.assign({}, defaultProxy.smux, proxy.value.smux); - proxy.value.smux['brutal-opts'] = Object.assign({}, defaultProxy.smux['brutal-opts'], proxy.value.smux['brutal-opts']); - - // Convert headers object to JSON string for ws-opts for easier editing - if (proxy.value['ws-opts']?.headers) { - try { - wsHeaderString.value = JSON.stringify(proxy.value['ws-opts'].headers, null, 2); - } catch (e) { - wsHeaderString.value = '{}'; - } - } else { - wsHeaderString.value = '{}'; - } - - // Convert headers object to JSON string for http-opts for easier editing - if (proxy.value['http-opts']?.headers) { - try { - httpHeaderString.value = JSON.stringify(proxy.value['http-opts'].headers, null, 2); - } catch (e) { - httpHeaderString.value = '{}'; - } - } else { - httpHeaderString.value = '{}'; - } - } -}, {immediate: true}); - -// Parse header strings back to objects before saving -const parseHeaders = () => { - if (proxy.value.network === 'ws') { - try { - proxy.value['ws-opts'].headers = JSON.parse(wsHeaderString.value || '{}'); - } catch (e) { - console.error("Invalid JSON format for ws headers:", e); - proxy.value['ws-opts'].headers = {}; - } - } - if (proxy.value.network === 'http') { - try { - proxy.value['http-opts'].headers = JSON.parse(httpHeaderString.value || '{}'); - } catch (e) { - console.error("Invalid JSON format for http headers:", e); - proxy.value['http-opts'].headers = {}; - } - } -}; - -const handleSave = async () => { - const {valid} = await proxyForm.value.validate(); - if (valid) { - parseHeaders(); - - // Create a clean proxy object to emit - const finalProxy = {...proxy.value}; - - // Clean up unused transport options - if (finalProxy.network !== 'ws') delete finalProxy['ws-opts']; - if (finalProxy.network !== 'http') delete finalProxy['http-opts']; - if (finalProxy.network !== 'h2') delete finalProxy['h2-opts']; - if (finalProxy.network !== 'grpc') delete finalProxy['grpc-opts']; - - // Clean up smux if disabled - if (!finalProxy.smux?.enabled) { - delete finalProxy.smux; - } else if (!finalProxy.smux['brutal-opts']?.enabled) { - delete finalProxy.smux['brutal-opts']; - } - - // Clean up unused TLS options - if (!finalProxy.tls) { - delete finalProxy.servername; - delete finalProxy.fingerprint; - delete finalProxy.alpn; - delete finalProxy['skip-cert-verify']; - delete finalProxy['client-fingerprint']; - delete finalProxy.sni; - } else { - // If TLS is enabled, clean up the one that is not used - if (['vmess', 'vless'].includes(finalProxy.type)) { - delete finalProxy.sni; - } else { - delete finalProxy.servername; - } - if (!['vmess', 'vless', 'trojan'].includes(finalProxy.type)) { - delete finalProxy['client-fingerprint']; - } - } - emit('save', finalProxy); - } -}; - -const handleCancel = () => { - dialog.value = false; -}; - -return (_ctx, _cache) => { - const _component_v_card_title = _resolveComponent$3("v-card-title"); - const _component_v_tab = _resolveComponent$3("v-tab"); - const _component_v_tabs = _resolveComponent$3("v-tabs"); - const _component_v_text_field = _resolveComponent$3("v-text-field"); - const _component_v_col = _resolveComponent$3("v-col"); - const _component_v_select = _resolveComponent$3("v-select"); - const _component_v_switch = _resolveComponent$3("v-switch"); - const _component_v_row = _resolveComponent$3("v-row"); - const _component_v_container = _resolveComponent$3("v-container"); - const _component_v_window_item = _resolveComponent$3("v-window-item"); - const _component_v_combobox = _resolveComponent$3("v-combobox"); - const _component_v_expand_transition = _resolveComponent$3("v-expand-transition"); - const _component_v_textarea = _resolveComponent$3("v-textarea"); - const _component_v_card_text = _resolveComponent$3("v-card-text"); - const _component_v_card = _resolveComponent$3("v-card"); - const _component_v_window = _resolveComponent$3("v-window"); - const _component_v_form = _resolveComponent$3("v-form"); - const _component_v_alert = _resolveComponent$3("v-alert"); - const _component_v_checkbox = _resolveComponent$3("v-checkbox"); - const _component_v_spacer = _resolveComponent$3("v-spacer"); - const _component_v_btn = _resolveComponent$3("v-btn"); - const _component_v_card_actions = _resolveComponent$3("v-card-actions"); - const _component_v_dialog = _resolveComponent$3("v-dialog"); - - return (_openBlock$3(), _createBlock$3(_component_v_dialog, { - modelValue: dialog.value, - "onUpdate:modelValue": _cache[45] || (_cache[45] = $event => ((dialog).value = $event)), - "max-width": "50rem", - persistent: "" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_card, null, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_card_title, null, { - default: _withCtx$3(() => [ - _createTextVNode$3(_toDisplayString$3(isEditing.value ? '覆写代理' : '添加代理'), 1) - ]), - _: 1 - }), - _createVNode$3(_component_v_card_text, { class: "pa-2" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_form, { - ref_key: "proxyForm", - ref: proxyForm, - onSubmit: _withModifiers$3(handleSave, ["prevent"]) - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_tabs, { - modelValue: tab.value, - "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((tab).value = $event)), - "background-color": "primary", - dark: "", - grow: "" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_tab, { value: "general" }, { - default: _withCtx$3(() => _cache[46] || (_cache[46] = [ - _createTextVNode$3("通用") - ])), - _: 1 - }), - _createVNode$3(_component_v_tab, { value: "tls" }, { - default: _withCtx$3(() => _cache[47] || (_cache[47] = [ - _createTextVNode$3("TLS") - ])), - _: 1 - }), - _createVNode$3(_component_v_tab, { value: "transport" }, { - default: _withCtx$3(() => _cache[48] || (_cache[48] = [ - _createTextVNode$3("传输层") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]), - _createVNode$3(_component_v_window, { - modelValue: tab.value, - "onUpdate:modelValue": _cache[43] || (_cache[43] = $event => ((tab).value = $event)), - class: "pt-4" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_window_item, { value: "general" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_container, { fluid: "" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_row, { dense: "" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value.name, - "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => ((proxy.value.name) = $event)), - disabled: "", - label: "名称 (name)", - rules: [v => !!v || '名称不能为空'], - hint: "代理名称", - variant: "outlined" - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_select, { - modelValue: proxy.value.type, - "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ((proxy.value.type) = $event)), - label: "类型 (type)", - items: proxyTypes, - rules: [v => !!v || '类型不能为空'], - hint: "代理协议类型", - variant: "outlined" - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value.server, - "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ((proxy.value.server) = $event)), - label: "服务器 (server)", - rules: [v => !!v || '服务器地址不能为空'], - hint: "代理服务器地址 (域名/IP)", - variant: "outlined" - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value.port, - "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ((proxy.value.port) = $event)), - modelModifiers: { number: true }, - label: "端口 (port)", - type: "number", - rules: [v => !!v || '端口不能为空'], - hint: "代理服务器端口", - variant: "outlined" - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_select, { - modelValue: proxy.value['ip-version'], - "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ((proxy.value['ip-version']) = $event)), - label: "IP版本 (ip-version)", - items: ['dual', 'ipv4', 'ipv6', 'ipv4-prefer', 'ipv6-prefer'], - hint: "出站使用的IP版本", - clearable: "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value['interface-name'], - "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((proxy.value['interface-name']) = $event)), - label: "网络接口 (interface-name)", - hint: "指定出站网络接口", - clearable: "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value['routing-mark'], - "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => ((proxy.value['routing-mark']) = $event)), - modelModifiers: { number: true }, - label: "路由标记 (routing-mark)", - type: "number", - hint: "为出站连接设置路由标记", - clearable: "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value['dialer-proxy'], - "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => ((proxy.value['dialer-proxy']) = $event)), - label: "拨号代理 (dialer-proxy)", - hint: "指定当前代理通过哪个代理建立连接", - clearable: "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "4", - sm: "4" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value.udp, - "onUpdate:modelValue": _cache[9] || (_cache[9] = $event => ((proxy.value.udp) = $event)), - label: "UDP", - hint: "是否允许UDP", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "4", - sm: "4" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value.tfo, - "onUpdate:modelValue": _cache[10] || (_cache[10] = $event => ((proxy.value.tfo) = $event)), - label: "TFO", - hint: "启用 TCP Fast Open", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "4", - sm: "4" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value.mptcp, - "onUpdate:modelValue": _cache[11] || (_cache[11] = $event => ((proxy.value.mptcp) = $event)), - label: "MPTCP", - hint: "启用 Multi-Path TCP", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$3(_component_v_window_item, { value: "tls" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_container, { fluid: "" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_row, { dense: "" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_col, { cols: "12" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value.tls, - "onUpdate:modelValue": _cache[12] || (_cache[12] = $event => ((proxy.value.tls) = $event)), - label: "启用 TLS", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$3(_component_v_expand_transition, null, { - default: _withCtx$3(() => [ - (proxy.value.tls) - ? (_openBlock$3(), _createElementBlock$1("div", _hoisted_1$1, [ - _createVNode$3(_component_v_row, { dense: "" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - (['vmess', 'vless'].includes(proxy.value.type)) - ? (_openBlock$3(), _createBlock$3(_component_v_text_field, { - key: 0, - modelValue: proxy.value.servername, - "onUpdate:modelValue": _cache[13] || (_cache[13] = $event => ((proxy.value.servername) = $event)), - label: "服务器名称 (servername)", - hint: "TLS服务器名称(SNI)", - clearable: "", - "persistent-hint": "", - variant: "outlined" - }, null, 8, ["modelValue"])) - : (_openBlock$3(), _createBlock$3(_component_v_text_field, { - key: 1, - modelValue: proxy.value.sni, - "onUpdate:modelValue": _cache[14] || (_cache[14] = $event => ((proxy.value.sni) = $event)), - label: "SNI", - hint: "TLS服务器名称(SNI)", - clearable: "", - "persistent-hint": "", - variant: "outlined" - }, null, 8, ["modelValue"])) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value.fingerprint, - "onUpdate:modelValue": _cache[15] || (_cache[15] = $event => ((proxy.value.fingerprint) = $event)), - label: "指纹 (fingerprint)", - hint: "证书指纹", - clearable: "", - "persistent-hint": "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { cols: "12" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_combobox, { - modelValue: proxy.value.alpn, - "onUpdate:modelValue": _cache[16] || (_cache[16] = $event => ((proxy.value.alpn) = $event)), - label: "ALPN", - hint: "应用层协议协商", - multiple: "", - chips: "", - clearable: "", - "deletable-chips": "", - "persistent-hint": "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - (['vmess', 'vless', 'trojan'].includes(proxy.value.type)) - ? (_openBlock$3(), _createBlock$3(_component_v_col, { - key: 0, - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_select, { - modelValue: proxy.value['client-fingerprint'], - "onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((proxy.value['client-fingerprint']) = $event)), - label: "客户端指纹 (client-fingerprint)", - items: ['chrome', 'firefox', 'safari', 'ios', 'android', 'edge', '360', 'qq', 'random'], - hint: "uTLS客户端指紋", - clearable: "", - "persistent-hint": "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - })) - : _createCommentVNode$3("", true), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6", - class: "d-flex align-center" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value['skip-cert-verify'], - "onUpdate:modelValue": _cache[18] || (_cache[18] = $event => ((proxy.value['skip-cert-verify']) = $event)), - label: "跳过证书验证", - inset: "", - "persistent-hint": "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ])) - : _createCommentVNode$3("", true) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$3(_component_v_window_item, { value: "transport" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_container, { fluid: "" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_row, null, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_col, { cols: "12" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_select, { - modelValue: proxy.value.network, - "onUpdate:modelValue": _cache[19] || (_cache[19] = $event => ((proxy.value.network) = $event)), - label: "网络 (network)", - items: ['http', 'h2', 'grpc', 'ws'], - hint: "传输层协议", - clearable: "", - "persistent-hint": "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$3(_component_v_expand_transition, null, { - default: _withCtx$3(() => [ - (proxy.value.network === 'ws') - ? (_openBlock$3(), _createElementBlock$1("div", _hoisted_2$1, [ - _createVNode$3(_component_v_card, { variant: "tonal" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { - default: _withCtx$3(() => _cache[49] || (_cache[49] = [ - _createTextVNode$3("WebSocket 选项") - ])), - _: 1 - }), - _createVNode$3(_component_v_card_text, null, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_row, null, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value['ws-opts']['v2ray-http-upgrade'], - "onUpdate:modelValue": _cache[20] || (_cache[20] = $event => ((proxy.value['ws-opts']['v2ray-http-upgrade']) = $event)), - label: "v2ray-http-upgrade", - hint: "启用 http upgrade", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value['ws-opts']['v2ray-http-upgrade-fast-open'], - "onUpdate:modelValue": _cache[21] || (_cache[21] = $event => ((proxy.value['ws-opts']['v2ray-http-upgrade-fast-open']) = $event)), - label: "v2ray-http-upgrade-fast-open", - hint: "启用 http upgrade 的 fast open", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value['ws-opts'].path, - "onUpdate:modelValue": _cache[22] || (_cache[22] = $event => ((proxy.value['ws-opts'].path) = $event)), - label: "路径 (path)", - hint: "WebSocket请求路径", - variant: "outlined", - class: "mb-2" - }, null, 8, ["modelValue"]), - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value['ws-opts']['max-early-data'], - "onUpdate:modelValue": _cache[23] || (_cache[23] = $event => ((proxy.value['ws-opts']['max-early-data']) = $event)), - modelModifiers: { number: true }, - label: "max-early-data", - type: "number", - hint: "Early Data 首包长度阈值", - variant: "outlined", - class: "mb-2", - clearable: "" - }, null, 8, ["modelValue"]), - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value['ws-opts']['early-data-header-name'], - "onUpdate:modelValue": _cache[24] || (_cache[24] = $event => ((proxy.value['ws-opts']['early-data-header-name']) = $event)), - label: "early-data-header-name", - variant: "outlined", - class: "mb-2", - clearable: "" - }, null, 8, ["modelValue"]), - _createVNode$3(_component_v_textarea, { - modelValue: wsHeaderString.value, - "onUpdate:modelValue": _cache[25] || (_cache[25] = $event => ((wsHeaderString).value = $event)), - label: "请求头 (headers)", - hint: "请输入JSON格式字符串, 例如: {\"Host\":\"example.com\"}", - variant: "outlined", - rows: "3", - rules: [jsonValidator] - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ])) - : _createCommentVNode$3("", true) - ]), - _: 1 - }), - _createVNode$3(_component_v_expand_transition, null, { - default: _withCtx$3(() => [ - (proxy.value.network === 'http') - ? (_openBlock$3(), _createElementBlock$1("div", _hoisted_3$1, [ - _createVNode$3(_component_v_card, { variant: "tonal" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { - default: _withCtx$3(() => _cache[50] || (_cache[50] = [ - _createTextVNode$3("HTTP 选项") - ])), - _: 1 - }), - _createVNode$3(_component_v_card_text, null, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value['http-opts'].method, - "onUpdate:modelValue": _cache[26] || (_cache[26] = $event => ((proxy.value['http-opts'].method) = $event)), - label: "方法 (method)", - hint: "HTTP请求方法", - variant: "outlined", - class: "mb-2" - }, null, 8, ["modelValue"]), - _createVNode$3(_component_v_combobox, { - modelValue: proxy.value['http-opts'].path, - "onUpdate:modelValue": _cache[27] || (_cache[27] = $event => ((proxy.value['http-opts'].path) = $event)), - label: "路径 (path)", - hint: "HTTP请求路径", - multiple: "", - chips: "", - clearable: "", - "deletable-chips": "", - variant: "outlined", - class: "mb-2" - }, null, 8, ["modelValue"]), - _createVNode$3(_component_v_textarea, { - modelValue: httpHeaderString.value, - "onUpdate:modelValue": _cache[28] || (_cache[28] = $event => ((httpHeaderString).value = $event)), - label: "请求头 (headers)", - hint: "请输入JSON格式字符串, 例如: {\"Host\":\"example.com\"}", - variant: "outlined", - rows: "3", - rules: [jsonValidator] - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ])) - : _createCommentVNode$3("", true) - ]), - _: 1 - }), - _createVNode$3(_component_v_expand_transition, null, { - default: _withCtx$3(() => [ - (proxy.value.network === 'h2') - ? (_openBlock$3(), _createElementBlock$1("div", _hoisted_4$1, [ - _createVNode$3(_component_v_card, { variant: "tonal" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { - default: _withCtx$3(() => _cache[51] || (_cache[51] = [ - _createTextVNode$3("H2 选项") - ])), - _: 1 - }), - _createVNode$3(_component_v_card_text, null, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value['h2-opts'].path, - "onUpdate:modelValue": _cache[29] || (_cache[29] = $event => ((proxy.value['h2-opts'].path) = $event)), - label: "路径 (path)", - hint: "H2请求路径", - variant: "outlined", - class: "mb-2" - }, null, 8, ["modelValue"]), - _createVNode$3(_component_v_combobox, { - modelValue: proxy.value['h2-opts'].host, - "onUpdate:modelValue": _cache[30] || (_cache[30] = $event => ((proxy.value['h2-opts'].host) = $event)), - label: "主机 (host)", - hint: "主机域名列表", - multiple: "", - chips: "", - clearable: "", - "deletable-chips": "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ])) - : _createCommentVNode$3("", true) - ]), - _: 1 - }), - _createVNode$3(_component_v_expand_transition, null, { - default: _withCtx$3(() => [ - (proxy.value.network === 'grpc') - ? (_openBlock$3(), _createElementBlock$1("div", _hoisted_5$1, [ - _createVNode$3(_component_v_card, { variant: "tonal" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { - default: _withCtx$3(() => _cache[52] || (_cache[52] = [ - _createTextVNode$3("gRPC 选项") - ])), - _: 1 - }), - _createVNode$3(_component_v_card_text, null, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value['grpc-opts']['grpc-service-name'], - "onUpdate:modelValue": _cache[31] || (_cache[31] = $event => ((proxy.value['grpc-opts']['grpc-service-name']) = $event)), - label: "服务名称 (grpc-service-name)", - hint: "gRPC服务名称", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ])) - : _createCommentVNode$3("", true) - ]), - _: 1 - }), - _createVNode$3(_component_v_expand_transition, null, { - default: _withCtx$3(() => [ - (!proxy.value.network || proxy.value.network === 'tcp') - ? (_openBlock$3(), _createElementBlock$1("div", _hoisted_6$1, [ - _createVNode$3(_component_v_card, { variant: "tonal" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { - default: _withCtx$3(() => _cache[53] || (_cache[53] = [ - _createTextVNode$3("Smux 选项") - ])), - _: 1 - }), - _createVNode$3(_component_v_card_text, null, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_row, { dense: "" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_col, { cols: "12" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value.smux.enabled, - "onUpdate:modelValue": _cache[32] || (_cache[32] = $event => ((proxy.value.smux.enabled) = $event)), - label: "启用 Smux", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$3(_component_v_expand_transition, null, { - default: _withCtx$3(() => [ - (proxy.value.smux.enabled) - ? (_openBlock$3(), _createElementBlock$1("div", _hoisted_7$1, [ - _createVNode$3(_component_v_row, { dense: "" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_select, { - modelValue: proxy.value.smux.protocol, - "onUpdate:modelValue": _cache[33] || (_cache[33] = $event => ((proxy.value.smux.protocol) = $event)), - label: "协议 (protocol)", - items: ['smux', 'yamux', 'h2mux'], - hint: "Smux协议类型", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value.smux['max-connections'], - "onUpdate:modelValue": _cache[34] || (_cache[34] = $event => ((proxy.value.smux['max-connections']) = $event)), - modelModifiers: { number: true }, - label: "最大连接数 (max-connections)", - type: "number", - hint: "最大复用连接数", - clearable: "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value.smux['min-streams'], - "onUpdate:modelValue": _cache[35] || (_cache[35] = $event => ((proxy.value.smux['min-streams']) = $event)), - modelModifiers: { number: true }, - label: "最小流数 (min-streams)", - type: "number", - hint: "每个连接的最小流数", - clearable: "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value.smux['max-streams'], - "onUpdate:modelValue": _cache[36] || (_cache[36] = $event => ((proxy.value.smux['max-streams']) = $event)), - modelModifiers: { number: true }, - label: "最大流数 (max-streams)", - type: "number", - hint: "每个连接的最大流数", - clearable: "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "4", - sm: "4" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value.smux.padding, - "onUpdate:modelValue": _cache[37] || (_cache[37] = $event => ((proxy.value.smux.padding) = $event)), - label: "Padding", - hint: "启用Padding", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "4", - sm: "4" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value.smux.statistic, - "onUpdate:modelValue": _cache[38] || (_cache[38] = $event => ((proxy.value.smux.statistic) = $event)), - label: "Statistic", - hint: "启用统计", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "4", - sm: "4" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value.smux['only-tcp'], - "onUpdate:modelValue": _cache[39] || (_cache[39] = $event => ((proxy.value.smux['only-tcp']) = $event)), - label: "Only TCP", - hint: "仅用于TCP", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { cols: "12" }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_switch, { - modelValue: proxy.value.smux['brutal-opts'].enabled, - "onUpdate:modelValue": _cache[40] || (_cache[40] = $event => ((proxy.value.smux['brutal-opts'].enabled) = $event)), - label: "启用 Brutal 优化", - inset: "", - color: "primary" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$3(_component_v_expand_transition, null, { - default: _withCtx$3(() => [ - (proxy.value.smux['brutal-opts'].enabled) - ? (_openBlock$3(), _createBlock$3(_component_v_row, { - key: 0, - dense: "" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value.smux['brutal-opts'].up, - "onUpdate:modelValue": _cache[41] || (_cache[41] = $event => ((proxy.value.smux['brutal-opts'].up) = $event)), - modelModifiers: { number: true }, - label: "上行带宽 (up)", - hint: "上行带宽, 默认以 Mbps 为单位", - clearable: "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$3(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_text_field, { - modelValue: proxy.value.smux['brutal-opts'].down, - "onUpdate:modelValue": _cache[42] || (_cache[42] = $event => ((proxy.value.smux['brutal-opts'].down) = $event)), - modelModifiers: { number: true }, - label: "下行带宽 (down)", - hint: "下行带宽, 默认以 Mbps 为单位", - clearable: "", - variant: "outlined" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - })) - : _createCommentVNode$3("", true) - ]), - _: 1 - }) - ])) - : _createCommentVNode$3("", true) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ])) - : _createCommentVNode$3("", true) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]) - ]), - _: 1 - }, 512), - _createVNode$3(_component_v_alert, { - type: "info", - text: "", - variant: "tonal" - }, { - default: _withCtx$3(() => _cache[54] || (_cache[54] = [ - _createTextVNode$3(" 参考"), - _createElementVNode$2("a", { - href: "https://wiki.metacubex.one/config/proxies/", - target: "_blank", - style: {"text-decoration":"underline"} - }, "Docs", -1), - _createTextVNode$3(", 覆写某些选项可能导致代理不可用。 ") - ])), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$3(_component_v_card_actions, null, { - default: _withCtx$3(() => [ - _createVNode$3(_component_v_checkbox, { - modelValue: proxy.value.rescind, - "onUpdate:modelValue": _cache[44] || (_cache[44] = $event => ((proxy.value.rescind) = $event)), - label: "删除覆写" - }, null, 8, ["modelValue"]), - _createVNode$3(_component_v_spacer), - _createVNode$3(_component_v_btn, { - text: "", - onClick: handleCancel - }, { - default: _withCtx$3(() => _cache[55] || (_cache[55] = [ - _createTextVNode$3("取消") - ])), - _: 1 - }), - _createVNode$3(_component_v_btn, { - color: "primary", - loading: __props.loading, - disabled: __props.loading, - onClick: handleSave - }, { - default: _withCtx$3(() => _cache[56] || (_cache[56] = [ - _createTextVNode$3("保存") - ])), - _: 1 - }, 8, ["loading", "disabled"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"])) -} -} - -}; -const ProxiesDialog = /*#__PURE__*/_export_sfc(_sfc_main$3, [['__scopeId',"data-v-5b19bb19"]]); - -/** - * lodash (Custom Build) - * Build: `lodash modularize exports="npm" -o ./` - * Copyright jQuery Foundation and other contributors - * Released under MIT license - * Based on Underscore.js 1.8.3 - * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - */ - -/** Used as the `TypeError` message for "Functions" methods. */ -var FUNC_ERROR_TEXT = 'Expected a function'; - -/** Used as references for various `Number` constants. */ -var NAN = 0 / 0; - -/** `Object#toString` result references. */ -var symbolTag = '[object Symbol]'; - -/** Used to match leading and trailing whitespace. */ -var reTrim = /^\s+|\s+$/g; - -/** Used to detect bad signed hexadecimal string values. */ -var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; - -/** Used to detect binary string values. */ -var reIsBinary = /^0b[01]+$/i; - -/** Used to detect octal string values. */ -var reIsOctal = /^0o[0-7]+$/i; - -/** Built-in method references without a dependency on `root`. */ -var freeParseInt = parseInt; - -/** Detect free variable `global` from Node.js. */ -var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; - -/** Detect free variable `self`. */ -var freeSelf = typeof self == 'object' && self && self.Object === Object && self; - -/** Used as a reference to the global object. */ -var root = freeGlobal || freeSelf || Function('return this')(); - -/** Used for built-in method references. */ -var objectProto = Object.prototype; - -/** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ -var objectToString = objectProto.toString; - -/* Built-in method references for those with the same name as other `lodash` methods. */ -var nativeMax = Math.max, - nativeMin = Math.min; - -/** - * Gets the timestamp of the number of milliseconds that have elapsed since - * the Unix epoch (1 January 1970 00:00:00 UTC). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Date - * @returns {number} Returns the timestamp. - * @example - * - * _.defer(function(stamp) { - * console.log(_.now() - stamp); - * }, _.now()); - * // => Logs the number of milliseconds it took for the deferred invocation. - */ -var now = function() { - return root.Date.now(); -}; - -/** - * Creates a debounced function that delays invoking `func` until after `wait` - * milliseconds have elapsed since the last time the debounced function was - * invoked. The debounced function comes with a `cancel` method to cancel - * delayed `func` invocations and a `flush` method to immediately invoke them. - * Provide `options` to indicate whether `func` should be invoked on the - * leading and/or trailing edge of the `wait` timeout. The `func` is invoked - * with the last arguments provided to the debounced function. Subsequent - * calls to the debounced function return the result of the last `func` - * invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the debounced function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.debounce` and `_.throttle`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to debounce. - * @param {number} [wait=0] The number of milliseconds to delay. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=false] - * Specify invoking on the leading edge of the timeout. - * @param {number} [options.maxWait] - * The maximum time `func` is allowed to be delayed before it's invoked. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new debounced function. - * @example - * - * // Avoid costly calculations while the window size is in flux. - * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); - * - * // Invoke `sendMail` when clicked, debouncing subsequent calls. - * jQuery(element).on('click', _.debounce(sendMail, 300, { - * 'leading': true, - * 'trailing': false - * })); - * - * // Ensure `batchLog` is invoked once after 1 second of debounced calls. - * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); - * var source = new EventSource('/stream'); - * jQuery(source).on('message', debounced); - * - * // Cancel the trailing debounced invocation. - * jQuery(window).on('popstate', debounced.cancel); - */ -function debounce(func, wait, options) { - var lastArgs, - lastThis, - maxWait, - result, - timerId, - lastCallTime, - lastInvokeTime = 0, - leading = false, - maxing = false, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - wait = toNumber(wait) || 0; - if (isObject(options)) { - leading = !!options.leading; - maxing = 'maxWait' in options; - maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - - function invokeFunc(time) { - var args = lastArgs, - thisArg = lastThis; - - lastArgs = lastThis = undefined; - lastInvokeTime = time; - result = func.apply(thisArg, args); - return result; - } - - function leadingEdge(time) { - // Reset any `maxWait` timer. - lastInvokeTime = time; - // Start the timer for the trailing edge. - timerId = setTimeout(timerExpired, wait); - // Invoke the leading edge. - return leading ? invokeFunc(time) : result; - } - - function remainingWait(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime, - result = wait - timeSinceLastCall; - - return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result; - } - - function shouldInvoke(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime; - - // Either this is the first call, activity has stopped and we're at the - // trailing edge, the system time has gone backwards and we're treating - // it as the trailing edge, or we've hit the `maxWait` limit. - return (lastCallTime === undefined || (timeSinceLastCall >= wait) || - (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); - } - - function timerExpired() { - var time = now(); - if (shouldInvoke(time)) { - return trailingEdge(time); - } - // Restart the timer. - timerId = setTimeout(timerExpired, remainingWait(time)); - } - - function trailingEdge(time) { - timerId = undefined; - - // Only invoke if we have `lastArgs` which means `func` has been - // debounced at least once. - if (trailing && lastArgs) { - return invokeFunc(time); - } - lastArgs = lastThis = undefined; - return result; - } - - function cancel() { - if (timerId !== undefined) { - clearTimeout(timerId); - } - lastInvokeTime = 0; - lastArgs = lastCallTime = lastThis = timerId = undefined; - } - - function flush() { - return timerId === undefined ? result : trailingEdge(now()); - } - - function debounced() { - var time = now(), - isInvoking = shouldInvoke(time); - - lastArgs = arguments; - lastThis = this; - lastCallTime = time; - - if (isInvoking) { - if (timerId === undefined) { - return leadingEdge(lastCallTime); - } - if (maxing) { - // Handle invocations in a tight loop. - timerId = setTimeout(timerExpired, wait); - return invokeFunc(lastCallTime); - } - } - if (timerId === undefined) { - timerId = setTimeout(timerExpired, wait); - } - return result; - } - debounced.cancel = cancel; - debounced.flush = flush; - return debounced; -} - -/** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); - * // => false - */ -function isObject(value) { - var type = typeof value; - return !!value && (type == 'object' || type == 'function'); -} - -/** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. - * @example - * - * _.isObjectLike({}); - * // => true - * - * _.isObjectLike([1, 2, 3]); - * // => true - * - * _.isObjectLike(_.noop); - * // => false - * - * _.isObjectLike(null); - * // => false - */ -function isObjectLike(value) { - return !!value && typeof value == 'object'; -} - -/** - * Checks if `value` is classified as a `Symbol` primitive or object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. - * @example - * - * _.isSymbol(Symbol.iterator); - * // => true - * - * _.isSymbol('abc'); - * // => false - */ -function isSymbol(value) { - return typeof value == 'symbol' || - (isObjectLike(value) && objectToString.call(value) == symbolTag); -} - -/** - * Converts `value` to a number. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to process. - * @returns {number} Returns the number. - * @example - * - * _.toNumber(3.2); - * // => 3.2 - * - * _.toNumber(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toNumber(Infinity); - * // => Infinity - * - * _.toNumber('3.2'); - * // => 3.2 - */ -function toNumber(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - if (isObject(value)) { - var other = typeof value.valueOf == 'function' ? value.valueOf() : value; - value = isObject(other) ? (other + '') : other; - } - if (typeof value != 'string') { - return value === 0 ? value : +value; - } - value = value.replace(reTrim, ''); - var isBinary = reIsBinary.test(value); - return (isBinary || reIsOctal.test(value)) - ? freeParseInt(value.slice(2), isBinary ? 2 : 8) - : (reIsBadHex.test(value) ? NAN : +value); -} - -var lodash_debounce = debounce; - -const debounce$1 = /*@__PURE__*/getDefaultExportFromCjs(lodash_debounce); - -const {toDisplayString:_toDisplayString$2,createTextVNode:_createTextVNode$2,resolveComponent:_resolveComponent$2,withCtx:_withCtx$2,createVNode:_createVNode$2,openBlock:_openBlock$2,createBlock:_createBlock$2,createCommentVNode:_createCommentVNode$2,withModifiers:_withModifiers$2} = await importShared('vue'); - - -const {ref: ref$2,computed: computed$2} = await importShared('vue'); - - -const _sfc_main$2 = { - __name: 'RuleDialog', - props: { - // 父组件传递的规则数据 - initialRule: { - type: Object, - default: () => ({ - type: 'DOMAIN', - payload: null, - action: 'DIRECT', - priority: 0, - additional_params: null, - conditions: [], - condition: null - }) - }, - // 控制对话框的显示与隐藏 - showDialog: { - type: Boolean, - default: false - }, - isAddingRule: { - type: Boolean, - default: true - }, - editingType: { - type: String, - default: 'top' - }, - ruleProviderNames: { - type: Array, - default: () => [] - }, - geoIp: { - type: Array, - default: () => [] - }, - geoSite: { - type: Array, - default: () => [] - }, - customOutbounds: { - type: Array, - default: () => [] - }, - loading: { - type: Boolean, - default: false - } -}, - emits: ['save', 'update:showDialog'], - setup(__props, { emit: __emit }) { - - -const ruleForm = ref$2(null); -const geoSearch = ref$2(''); -const geoIPSearch = ref$2(''); -const geoFilterLoading = ref$2(false); -const filteredGeoItems = ref$2([]); -const ruleTypes = computed$2(() => { - const allTypes = [ - 'DOMAIN', 'DOMAIN-SUFFIX', 'DOMAIN-KEYWORD', 'DOMAIN-REGEX', 'DOMAIN-WILDCARD', 'GEOSITE', 'GEOIP', - 'IP-CIDR', 'IP-CIDR6', 'IP-SUFFIX', 'IP-ASN', - 'SRC-GEOIP', 'SRC-IP-ASN', 'SRC-IP-CIDR', 'SRC-IP-SUFFIX', - 'DST-PORT', 'SRC-PORT', 'IN-PORT', 'IN-TYPE', 'IN-USER', 'IN-NAME', - 'PROCESS-PATH', 'PROCESS-PATH-REGEX', 'PROCESS-NAME', 'PROCESS-NAME-REGEX', - 'UID', 'NETWORK', 'DSCP', 'RULE-SET', 'AND', 'OR', 'NOT', 'SUB-RULE', 'MATCH' - ]; - - // 如果是 ruleset 规则,过滤掉 SUB-RULE 和 RULE-SET - if (props.editingType.value === 'ruleset') { - return allTypes.filter(type => !['SUB-RULE', 'RULE-SET'].includes(type)); - } - return allTypes; -}); -const showAdditionalParams = computed$2(() => { - return ['IP-CIDR', 'IP-CIDR6', 'IP-ASN', 'GEOIP'].includes(rule.value.type); -}); -const onGeoSearch = (val) => { - geoSearch.value = val; - performGeoSiteFilter(val); -}; -const onGeoIPSearch = (val) => { - geoIPSearch.value = val; - performGeoIPFilter(val); -}; -const performGeoSiteFilter = debounce$1((val) => { - if (!val) { - filteredGeoItems.value = []; - geoFilterLoading.value = false; - return - } - geoFilterLoading.value = true; - filteredGeoItems.value = props.geoSite.filter(item => - item.toLowerCase().includes(val.toLowerCase()) - ); - geoFilterLoading.value = false; -}, 100); // 10ms debounce - -const performGeoIPFilter = debounce$1((val) => { - if (!val) { - filteredGeoItems.value = []; - geoFilterLoading.value = false; - return - } - geoFilterLoading.value = true; - filteredGeoItems.value = props.geoIp.filter(item => - item.toLowerCase().includes(val.toLowerCase()) - ); - geoFilterLoading.value = false; -}, 200); // 20ms debounce - -// 当输入框失去焦点时,将当前搜索词设置为选中项(如果它不在候选列表中) -const onGeoSiteBlur = () => { - if (!filteredGeoItems.value.includes(geoSearch.value)) { - rule.value.payload = geoSearch.value; - } -}; -const onGeoIPBlur = () => { - if (!filteredGeoItems.value.includes(geoIPSearch.value)) { - rule.value.payload = geoIPSearch.value; - } -}; - -const actions = computed$2(() => [ - 'DIRECT', 'REJECT', 'REJECT-DROP', 'PASS', 'COMPATIBLE', - ...props.customOutbounds.map(outbound => outbound.name) -]); - -const additionalParamOptions = ref$2([ - {title: '无', value: ''}, - {title: 'no-resolve', value: 'no-resolve'}, - {title: 'src', value: 'src'} -]); - - -// 1. 定义 Props -// 使用 defineProps 宏。传入的参数与 defineComponent 的 props 选项内容相同 -const props = __props; - -// 2. 定义 Emits -// 使用 defineEmits 宏。传入一个字符串数组,列出组件会触发的事件 -const emit = __emit; - -// 3. 响应式数据 -// 将 props 中的 initialRule 复制到 rule 中,以便在组件内部修改而不影响父组件的 prop -const rule = ref$2({...props.initialRule}); - -const show = computed$2({ - get: () => props.showDialog, - set: (value) => emit('update:showDialog', value) -}); - - -const payloadRules = computed$2(() => { - return [ - (v) => { - // 如果 type 是 'MATCH',允许 payload 为空 - if (rule.value.type === 'MATCH') { - return true; // 不验证 - } - // 否则,payload 不能为空 - return !!v || '内容不能为空'; - }, - ]; -}); - -// 4. 方法 -// 提交表单 -const submitForm = async () => { - const {valid} = await ruleForm.value.validate(); - if (valid) { - // 触发父组件的保存事件 - emit('save', rule.value); - // 关闭对话框 - // emit('update:showDialog', false); - } -}; - -// 关闭对话框 -const closeDialog = () => { - emit('update:showDialog', false); -}; - - -return (_ctx, _cache) => { - const _component_v_card_title = _resolveComponent$2("v-card-title"); - const _component_v_select = _resolveComponent$2("v-select"); - const _component_v_autocomplete = _resolveComponent$2("v-autocomplete"); - const _component_v_combobox = _resolveComponent$2("v-combobox"); - const _component_v_text_field = _resolveComponent$2("v-text-field"); - const _component_v_card_text = _resolveComponent$2("v-card-text"); - const _component_v_spacer = _resolveComponent$2("v-spacer"); - const _component_v_btn = _resolveComponent$2("v-btn"); - const _component_v_card_actions = _resolveComponent$2("v-card-actions"); - const _component_v_card = _resolveComponent$2("v-card"); - const _component_v_form = _resolveComponent$2("v-form"); - const _component_v_dialog = _resolveComponent$2("v-dialog"); - - return (_openBlock$2(), _createBlock$2(_component_v_dialog, { - modelValue: show.value, - "onUpdate:modelValue": _cache[11] || (_cache[11] = $event => ((show).value = $event)), - "max-width": "40rem" - }, { - default: _withCtx$2(() => [ - _createVNode$2(_component_v_form, { - ref_key: "ruleForm", - ref: ruleForm, - onSubmit: _withModifiers$2(submitForm, ["prevent"]) - }, { - default: _withCtx$2(() => [ - _createVNode$2(_component_v_card, null, { - default: _withCtx$2(() => [ - _createVNode$2(_component_v_card_title, null, { - default: _withCtx$2(() => [ - _createTextVNode$2(_toDisplayString$2(props.isAddingRule ? '添加规则' : '编辑规则'), 1) - ]), - _: 1 - }), - _createVNode$2(_component_v_card_text, null, { - default: _withCtx$2(() => [ - _createVNode$2(_component_v_select, { - modelValue: rule.value.type, - "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((rule.value.type) = $event)), - items: ruleTypes.value, - label: "规则类型", - required: "", - class: "mb-4" - }, null, 8, ["modelValue", "items"]), - (rule.value.type === 'RULE-SET') - ? (_openBlock$2(), _createBlock$2(_component_v_select, { - key: 0, - modelValue: rule.value.payload, - "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => ((rule.value.payload) = $event)), - items: props.ruleProviderNames, - label: "选择规则集", - required: "", - rules: [(v) => !!v || '请选择一个有效的规则集',], - class: "mb-4" - }, null, 8, ["modelValue", "items", "rules"])) - : (rule.value.type === 'GEOSITE') - ? (_openBlock$2(), _createBlock$2(_component_v_autocomplete, { - key: 1, - modelValue: rule.value.payload, - "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ((rule.value.payload) = $event)), - search: geoSearch.value, - items: filteredGeoItems.value, - loading: geoFilterLoading.value, - "hide-no-data": "", - "hide-selected": "", - label: "内容", - "no-filter": "", - solo: "", - "custom-filter": () => true, - clearable: "", - "onUpdate:search": onGeoSearch, - onBlur: onGeoSiteBlur, - class: "mb-4", - rules: payloadRules.value - }, null, 8, ["modelValue", "search", "items", "loading", "rules"])) - : (rule.value.type === 'GEOIP') - ? (_openBlock$2(), _createBlock$2(_component_v_autocomplete, { - key: 2, - modelValue: rule.value.payload, - "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ((rule.value.payload) = $event)), - search: geoIPSearch.value, - items: filteredGeoItems.value, - loading: geoFilterLoading.value, - "hide-no-data": "", - "hide-selected": "", - label: "内容", - "no-filter": "", - solo: "", - "custom-filter": () => true, - clearable: "", - "onUpdate:search": onGeoIPSearch, - onBlur: onGeoIPBlur, - class: "mb-4", - rules: payloadRules.value - }, null, 8, ["modelValue", "search", "items", "loading", "rules"])) - : (rule.value.type === 'AND' || rule.value.type === 'OR' || rule.value.type === 'NOT') - ? (_openBlock$2(), _createBlock$2(_component_v_combobox, { - key: 3, - modelValue: rule.value.conditions, - "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ((rule.value.conditions) = $event)), - label: "条件", - multiple: "", - chips: "", - hint: "「(DOMAIN,baidu.com)」,「(NETWORK,TCP)」", - clearable: "", - required: "", - class: "mb-4" - }, null, 8, ["modelValue"])) - : (rule.value.type === 'SUB-RULE') - ? (_openBlock$2(), _createBlock$2(_component_v_text_field, { - key: 4, - modelValue: rule.value.condition, - "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ((rule.value.condition) = $event)), - label: "条件", - required: "", - placeholder: "(AND,(DOMAIN,baidu.com),(NETWORK,TCP))", - rules: payloadRules.value, - class: "mb-4" - }, null, 8, ["modelValue", "rules"])) - : (_openBlock$2(), _createBlock$2(_component_v_text_field, { - key: 5, - modelValue: rule.value.payload, - "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((rule.value.payload) = $event)), - label: "内容", - required: "", - rules: payloadRules.value, - class: "mb-4" - }, null, 8, ["modelValue", "rules"])), - (rule.value.type === 'SUB-RULE') - ? (_openBlock$2(), _createBlock$2(_component_v_text_field, { - key: 6, - modelValue: rule.value.action, - "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => ((rule.value.action) = $event)), - label: "子规则", - required: "", - class: "mb-4" - }, null, 8, ["modelValue"])) - : (_openBlock$2(), _createBlock$2(_component_v_select, { - key: 7, - modelValue: rule.value.action, - "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => ((rule.value.action) = $event)), - items: actions.value, - label: "出站", - required: "", - class: "mb-4" - }, null, 8, ["modelValue", "items"])), - (showAdditionalParams.value) - ? (_openBlock$2(), _createBlock$2(_component_v_select, { - key: 8, - modelValue: rule.value.additional_params, - "onUpdate:modelValue": _cache[9] || (_cache[9] = $event => ((rule.value.additional_params) = $event)), - label: "附加参数", - items: additionalParamOptions.value, - clearable: "", - hint: "可选参数", - "persistent-hint": "", - class: "mb-4" - }, null, 8, ["modelValue", "items"])) - : _createCommentVNode$2("", true), - _createVNode$2(_component_v_text_field, { - modelValue: rule.value.priority, - "onUpdate:modelValue": _cache[10] || (_cache[10] = $event => ((rule.value.priority) = $event)), - modelModifiers: { number: true }, - type: "number", - label: "优先级", - hint: "数字越小优先级越高", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$2(_component_v_card_actions, null, { - default: _withCtx$2(() => [ - _createVNode$2(_component_v_spacer), - _createVNode$2(_component_v_btn, { - color: "secondary", - onClick: closeDialog - }, { - default: _withCtx$2(() => _cache[12] || (_cache[12] = [ - _createTextVNode$2("取消") - ])), - _: 1 - }), - _createVNode$2(_component_v_btn, { - color: "primary", - type: "submit", - loading: props.loading - }, { - default: _withCtx$2(() => _cache[13] || (_cache[13] = [ - _createTextVNode$2("保存 ") - ])), - _: 1 - }, 8, ["loading"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 512) - ]), - _: 1 - }, 8, ["modelValue"])) -} -} - -}; - -const {toDisplayString:_toDisplayString$1,createTextVNode:_createTextVNode$1,resolveComponent:_resolveComponent$1,withCtx:_withCtx$1,createVNode:_createVNode$1,openBlock:_openBlock$1,createBlock:_createBlock$1,createCommentVNode:_createCommentVNode$1,createElementVNode:_createElementVNode$1,withModifiers:_withModifiers$1} = await importShared('vue'); - - -const {ref: ref$1,computed: computed$1} = await importShared('vue'); - - - -const _sfc_main$1 = { - __name: 'ProxyGroupsDialog', - props: { - initialValue: { - type: Object, - default: () => ({ - name: '', - type: 'select', - proxies: [], - url: null, - lazy: true, - interval: null, - timeout: null, - 'disable-udp': false, - filter: null, - 'include-all': false, - 'include-all-proxies': false, - 'include-all-providers': false, - 'exclude-filter': null, - 'exclude-type': null, - tolerance: null, - strategy: null, - 'expected-status': '*', - hidden: false, - icon: null, - use: null, - 'max-failed-times': 5, - 'policy-priority': "1", - uselightgbm: false, - collectdata: false, - 'sample-rate': 1, - }) - }, - showDialog: { - type: Boolean, - default: false - }, - loading: { - type: Boolean, - default: false - }, - isAdding: { - type: Boolean, - default: true - }, - proxyProviders: { - type: Object, - default: () => ({}), - }, - customOutbounds: { - type: Array, - default: () => [] - }, -}, - emits: ['save', 'update:showDialog'], - setup(__props, { emit: __emit }) { - -const props = __props; - -// 2. 定义 Emits -const emit = __emit; - -// 3. 响应式数据 -const show = computed$1({ - get: () => props.showDialog, - set: (value) => emit('update:showDialog', value) -}); -const proxyGroup = ref$1({...props.initialValue}); - -// 4. 方法 -// 提交表单 -const submitForm = async () => { - const {valid} = await form.value.validate(); - if (valid) { - // 触发父组件的保存事件 - emit('save', proxyGroup.value); - } -}; -// 关闭对话框 -const closeDialog = () => { - emit('update:showDialog', false); -}; - -const proxyGroupTypes = ref$1(['select', 'url-test', 'fallback', 'load-balance', 'smart']); -const form = ref$1(null); -const proxyProviderNames = computed$1(() => Object.keys(props.proxyProviders)); -const strategyTypes = ref$1(['round-robin', 'consistent-hashing', 'sticky-sessions']); -const smartStrategyTypes = ref$1(['round-robin', 'sticky-sessions']); -// 修改actions为计算属性,合并内置动作和自定义出站 -const actions = computed$1(() => [ - 'DIRECT', 'REJECT', 'REJECT-DROP', 'PASS', 'COMPATIBLE', - ...props.customOutbounds.map(outbound => outbound.name) -]); -const isValidUrl = (urlString) => { - if (!urlString) return false; - try { - const url = new URL(urlString); - return url.protocol === 'http:' || url.protocol === 'https:'; - } catch (e) { - return false; - } -}; -// 定义验证规则数组 -const urlRules = [ - (v) => { - // 规则:值v可以为空 (falsy, 如''), 或者必须满足isValidUrl(v)的校验 - // 如果校验失败,则返回字符串作为错误提示 - return !v || isValidUrl(v) || '请输入一个有效的URL地址'; - } -]; - -return (_ctx, _cache) => { - const _component_v_card_title = _resolveComponent$1("v-card-title"); - const _component_v_text_field = _resolveComponent$1("v-text-field"); - const _component_v_col = _resolveComponent$1("v-col"); - const _component_v_select = _resolveComponent$1("v-select"); - const _component_v_row = _resolveComponent$1("v-row"); - const _component_v_icon = _resolveComponent$1("v-icon"); - const _component_v_switch = _resolveComponent$1("v-switch"); - const _component_v_card_text = _resolveComponent$1("v-card-text"); - const _component_v_alert = _resolveComponent$1("v-alert"); - const _component_v_spacer = _resolveComponent$1("v-spacer"); - const _component_v_btn = _resolveComponent$1("v-btn"); - const _component_v_card_actions = _resolveComponent$1("v-card-actions"); - const _component_v_card = _resolveComponent$1("v-card"); - const _component_v_form = _resolveComponent$1("v-form"); - const _component_v_dialog = _resolveComponent$1("v-dialog"); - - return (_openBlock$1(), _createBlock$1(_component_v_dialog, { - modelValue: show.value, - "onUpdate:modelValue": _cache[25] || (_cache[25] = $event => ((show).value = $event)), - "max-width": "40rem" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_form, { - ref_key: "form", - ref: form, - onSubmit: _withModifiers$1(submitForm, ["prevent"]) - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_card, null, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_card_title, null, { - default: _withCtx$1(() => [ - _createTextVNode$1(_toDisplayString$1(props.isAdding ? '添加代理组' : '编辑代理组'), 1) - ]), - _: 1 - }), - _createVNode$1(_component_v_card_text, { - style: {"overflow-y":"auto"}, - "max-height": "60rem" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_row, null, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value.name, - "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((proxyGroup.value.name) = $event)), - label: "name", - required: "", - hint: "策略组的名字", - rules: [v => !!v || 'Name不能为空'], - class: "mb-4" - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_select, { - modelValue: proxyGroup.value.type, - "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => ((proxyGroup.value.type) = $event)), - label: "type", - items: proxyGroupTypes.value, - required: "", - hint: "策略组的类型,smart组需要内核支持", - class: "mb-4" - }, null, 8, ["modelValue", "items"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$1(_component_v_select, { - modelValue: proxyGroup.value.proxies, - "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ((proxyGroup.value.proxies) = $event)), - label: "proxies", - items: actions.value, - multiple: "", - chips: "", - clearable: "", - hint: "引入出站代理或其他策略组", - class: "mb-4" - }, null, 8, ["modelValue", "items"]), - _createVNode$1(_component_v_select, { - modelValue: proxyGroup.value.use, - "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ((proxyGroup.value.use) = $event)), - label: "use", - items: proxyProviderNames.value, - multiple: "", - chips: "", - clearable: "", - hint: "引入代理集合", - class: "mb-4" - }, null, 8, ["modelValue", "items"]), - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value.url, - "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ((proxyGroup.value.url) = $event)), - label: "url", - hint: "健康检查测试地址", - rules: urlRules, - clearable: "", - class: "mb-4" - }, null, 8, ["modelValue"]), - (proxyGroup.value.type === 'url-test') - ? (_openBlock$1(), _createBlock$1(_component_v_text_field, { - key: 0, - modelValue: proxyGroup.value.tolerance, - "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ((proxyGroup.value.tolerance) = $event)), - modelModifiers: { number: true }, - label: "tolerance (ms)", - variant: "outlined", - type: "number", - min: "10", - hint: "节点切换容差", - rules: [v => v >=0 || '容差需不小于0'], - class: "mb-4" - }, null, 8, ["modelValue", "rules"])) - : _createCommentVNode$1("", true), - (proxyGroup.value.type === 'load-balance') - ? (_openBlock$1(), _createBlock$1(_component_v_select, { - key: 1, - modelValue: proxyGroup.value.strategy, - "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((proxyGroup.value.strategy) = $event)), - label: "strategy", - items: strategyTypes.value, - hint: "负载均衡策略", - class: "mb-4" - }, null, 8, ["modelValue", "items"])) - : _createCommentVNode$1("", true), - (proxyGroup.value.type === 'smart') - ? (_openBlock$1(), _createBlock$1(_component_v_select, { - key: 2, - modelValue: proxyGroup.value.strategy, - "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => ((proxyGroup.value.strategy) = $event)), - label: "strategy", - items: smartStrategyTypes.value, - hint: "负载均衡策略", - class: "mb-4" - }, null, 8, ["modelValue", "items"])) - : _createCommentVNode$1("", true), - (proxyGroup.value.type === 'smart') - ? (_openBlock$1(), _createBlock$1(_component_v_row, { key: 3 }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value['policy-priority'], - "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => ((proxyGroup.value['policy-priority']) = $event)), - label: "policy-priority", - hint: "优先级" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value['sample-rate'], - "onUpdate:modelValue": _cache[9] || (_cache[9] = $event => ((proxyGroup.value['sample-rate']) = $event)), - label: "sample-rate", - hint: "数据采集率" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - })) - : _createCommentVNode$1("", true), - _createVNode$1(_component_v_row, null, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value.filter, - "onUpdate:modelValue": _cache[10] || (_cache[10] = $event => ((proxyGroup.value.filter) = $event)), - label: "filter", - hint: "筛选满足关键词或正则表达式的节点" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value['exclude-filter'], - "onUpdate:modelValue": _cache[11] || (_cache[11] = $event => ((proxyGroup.value['exclude-filter']) = $event)), - label: "exclude-filter", - hint: "排除满足关键词或正则表达式的节点" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value['exclude-type'], - "onUpdate:modelValue": _cache[12] || (_cache[12] = $event => ((proxyGroup.value['exclude-type']) = $event)), - label: "exclude-type", - hint: "不支持正则表达式,通过 | 分割" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value['expected-status'], - "onUpdate:modelValue": _cache[13] || (_cache[13] = $event => ((proxyGroup.value['expected-status']) = $event)), - label: "expected-status", - hint: "健康检查时期望的 HTTP 响应状态码" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$1(_component_v_row, null, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_col, { - cols: "12", - md: "12" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value.icon, - "onUpdate:modelValue": _cache[14] || (_cache[14] = $event => ((proxyGroup.value.icon) = $event)), - label: "icon", - clearable: "", - hint: "在 api 返回icon所输入的字符串" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$1(_component_v_row, null, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value.interval, - "onUpdate:modelValue": _cache[15] || (_cache[15] = $event => ((proxyGroup.value.interval) = $event)), - modelModifiers: { number: true }, - label: "interval", - variant: "outlined", - type: "number", - min: "0", - clearable: "", - suffix: "s", - hint: "健康检查间隔,如不为 0 则启用定时测试", - rules: [v => ( v === null || v === undefined || v > -1) || '检查间隔需不小于0'] - }, { - "prepend-inner": _withCtx$1(() => [ - _createVNode$1(_component_v_icon, { color: "warning" }, { - default: _withCtx$1(() => _cache[26] || (_cache[26] = [ - _createTextVNode$1("mdi-timer") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value.timeout, - "onUpdate:modelValue": _cache[16] || (_cache[16] = $event => ((proxyGroup.value.timeout) = $event)), - modelModifiers: { number: true }, - label: "timeout", - variant: "outlined", - type: "number", - min: "1", - hint: "请求的超时时间", - suffix: "ms", - clearable: "", - rules: [v => (v === null || v === undefined || v > 0) || '超时时间必须大于0'] - }, { - "prepend-inner": _withCtx$1(() => [ - _createVNode$1(_component_v_icon, { color: "warning" }, { - default: _withCtx$1(() => _cache[27] || (_cache[27] = [ - _createTextVNode$1("mdi-timer") - ])), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue", "rules"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$1(_component_v_row, null, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_text_field, { - modelValue: proxyGroup.value['max-failed-times'], - "onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((proxyGroup.value['max-failed-times']) = $event)), - modelModifiers: { number: true }, - label: "max-failed-times", - variant: "outlined", - type: "number", - min: "0", - hint: "最大失败次数", - rules: [v => v >= 0 || '最大失败次数必须大于等于0'] - }, null, 8, ["modelValue", "rules"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_switch, { - modelValue: proxyGroup.value['lazy'], - "onUpdate:modelValue": _cache[18] || (_cache[18] = $event => ((proxyGroup.value['lazy']) = $event)), - label: "lazy", - inset: "", - hint: "未选择到当前策略组时,不进行测试", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_switch, { - modelValue: proxyGroup.value['disable-udp'], - "onUpdate:modelValue": _cache[19] || (_cache[19] = $event => ((proxyGroup.value['disable-udp']) = $event)), - label: "disable-udp", - inset: "", - hint: "禁用该策略组的UDP", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_switch, { - modelValue: proxyGroup.value.hidden, - "onUpdate:modelValue": _cache[20] || (_cache[20] = $event => ((proxyGroup.value.hidden) = $event)), - label: "hidden", - inset: "", - hint: "在 api 返回hidden状态", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode$1(_component_v_row, null, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_switch, { - modelValue: proxyGroup.value['include-all'], - "onUpdate:modelValue": _cache[21] || (_cache[21] = $event => ((proxyGroup.value['include-all']) = $event)), - label: "include-all", - inset: "", - hint: "引入所有出站代理以及代理集合", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_switch, { - modelValue: proxyGroup.value['include-all-proxies'], - "onUpdate:modelValue": _cache[22] || (_cache[22] = $event => ((proxyGroup.value['include-all-proxies']) = $event)), - label: "include-all-proxies", - inset: "", - hint: "引入所有出站代理", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - (proxyGroup.value.type === 'smart') - ? (_openBlock$1(), _createBlock$1(_component_v_row, { key: 4 }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_switch, { - modelValue: proxyGroup.value['uselightgbm'], - "onUpdate:modelValue": _cache[23] || (_cache[23] = $event => ((proxyGroup.value['uselightgbm']) = $event)), - label: "uselightgbm", - inset: "", - hint: "使用LightGBM进行权重预测", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode$1(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_switch, { - modelValue: proxyGroup.value['collectdata'], - "onUpdate:modelValue": _cache[24] || (_cache[24] = $event => ((proxyGroup.value['collectdata']) = $event)), - label: "collectdata", - inset: "", - hint: "收集数据进行模型训练", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - })) - : _createCommentVNode$1("", true) - ]), - _: 1 - }), - _createVNode$1(_component_v_alert, { - type: "info", - text: "", - variant: "tonal" - }, { - default: _withCtx$1(() => _cache[28] || (_cache[28] = [ - _createTextVNode$1(" 参考"), - _createElementVNode$1("a", { - href: "https://wiki.metacubex.one/config/proxy-groups/", - target: "_blank", - style: {"text-decoration":"underline"} - }, "Docs", -1) - ])), - _: 1 - }), - _createVNode$1(_component_v_card_actions, null, { - default: _withCtx$1(() => [ - _createVNode$1(_component_v_spacer), - _createVNode$1(_component_v_btn, { - color: "secondary", - onClick: closeDialog - }, { - default: _withCtx$1(() => _cache[29] || (_cache[29] = [ - _createTextVNode$1("取消") - ])), - _: 1 - }), - _createVNode$1(_component_v_btn, { - color: "primary", - type: "submit", - loading: props.loading - }, { - default: _withCtx$1(() => _cache[30] || (_cache[30] = [ - _createTextVNode$1("保存 ") - ])), - _: 1 - }, 8, ["loading"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 512) - ]), - _: 1 - }, 8, ["modelValue"])) -} -} - -}; - -const {toDisplayString:_toDisplayString,createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,openBlock:_openBlock,createBlock:_createBlock,createCommentVNode:_createCommentVNode,createElementVNode:_createElementVNode,createVNode:_createVNode,createElementBlock:_createElementBlock,withModifiers:_withModifiers,normalizeClass:_normalizeClass,renderList:_renderList,Fragment:_Fragment,mergeProps:_mergeProps,unref:_unref} = await importShared('vue'); - - -const _hoisted_1 = { class: "plugin-page" }; -const _hoisted_2 = ["src"]; -const _hoisted_3 = { key: 1 }; -const _hoisted_4 = { class: "mb-2" }; -const _hoisted_5 = { class: "pa-4" }; -const _hoisted_6 = { class: "d-none d-sm-flex clash-data-table" }; -const _hoisted_7 = ["onDragstart", "onDragover", "onDrop"]; -const _hoisted_8 = { key: 0 }; -const _hoisted_9 = { key: 1 }; -const _hoisted_10 = { class: "d-sm-none" }; -const _hoisted_11 = { class: "font-weight-bold" }; -const _hoisted_12 = { class: "text-body-2" }; -const _hoisted_13 = { class: "text-body-2" }; -const _hoisted_14 = { class: "text-body-2" }; -const _hoisted_15 = { - class: "pa-4", - style: {"min-height":"4rem"} -}; -const _hoisted_16 = { class: "mb-2" }; -const _hoisted_17 = { class: "pa-4" }; -const _hoisted_18 = { class: "d-none d-sm-flex clash-data-table" }; -const _hoisted_19 = ["onDragstart", "onDragover", "onDrop"]; -const _hoisted_20 = { key: 0 }; -const _hoisted_21 = { key: 1 }; -const _hoisted_22 = { class: "d-sm-none" }; -const _hoisted_23 = { class: "font-weight-bold" }; -const _hoisted_24 = { class: "text-body-2" }; -const _hoisted_25 = { class: "text-body-2" }; -const _hoisted_26 = { - class: "pa-4", - style: {"min-height":"4rem"} -}; -const _hoisted_27 = { class: "mb-2" }; -const _hoisted_28 = { class: "pa-4" }; -const _hoisted_29 = { class: "d-none d-sm-flex clash-data-table" }; -const _hoisted_30 = { class: "d-sm-none" }; -const _hoisted_31 = { class: "font-weight-bold" }; -const _hoisted_32 = { class: "text-body-2" }; -const _hoisted_33 = { - class: "pa-4", - style: {"min-height":"4rem"} -}; -const _hoisted_34 = { class: "mb-2" }; -const _hoisted_35 = { class: "pa-4" }; -const _hoisted_36 = { class: "d-none d-sm-flex clash-data-table" }; -const _hoisted_37 = { class: "d-sm-none" }; -const _hoisted_38 = { class: "font-weight-bold" }; -const _hoisted_39 = { class: "text-body-2" }; -const _hoisted_40 = { class: "text-body-2" }; -const _hoisted_41 = { - class: "pa-4", - style: {"min-height":"4rem"} -}; -const _hoisted_42 = { class: "mb-2" }; -const _hoisted_43 = { class: "pa-4" }; -const _hoisted_44 = { class: "d-none d-sm-flex clash-data-table" }; -const _hoisted_45 = { class: "d-sm-none" }; -const _hoisted_46 = { class: "font-weight-bold" }; -const _hoisted_47 = { class: "text-body-2" }; -const _hoisted_48 = { class: "text-body-2" }; -const _hoisted_49 = { - class: "pa-4", - style: {"min-height":"4rem"} -}; -const _hoisted_50 = { class: "mb-2" }; -const _hoisted_51 = { class: "pa-4" }; -const _hoisted_52 = { class: "d-none d-sm-flex clash-data-table" }; -const _hoisted_53 = { class: "d-sm-none" }; -const _hoisted_54 = { class: "font-weight-bold" }; -const _hoisted_55 = { class: "text-body-2" }; -const _hoisted_56 = { - class: "pa-4", - style: {"min-height":"4rem"} -}; -const _hoisted_57 = { class: "mb-2" }; -const _hoisted_58 = { class: "card-header" }; -const _hoisted_59 = { class: "mb-2" }; -const _hoisted_60 = { class: "d-flex justify-space-between mb-2" }; -const _hoisted_61 = { class: "d-flex justify-space-between mb-2" }; -const _hoisted_62 = { class: "d-flex py-2 justify-space-between text-caption text-grey" }; -const _hoisted_63 = { class: "toggle-container" }; -const _hoisted_64 = { key: 0 }; -const _hoisted_65 = { class: "text-h6 mt-2 font-weight-bold" }; -const _hoisted_66 = { class: "text-h6 mt-2 font-weight-bold" }; -const _hoisted_67 = { class: "text-h6 mt-2 font-weight-bold" }; -const _hoisted_68 = { class: "text-h6 mt-2 font-weight-bold" }; -const _hoisted_69 = { class: "text-h6 mt-2 font-weight-bold" }; -const _hoisted_70 = { class: "text-h6 mt-2 font-weight-bold" }; -const _hoisted_71 = { class: "text-h6 mt-2 font-weight-bold" }; -const _hoisted_72 = { class: "text-h6 mt-2 font-weight-bold" }; - -const {ref,onMounted,computed} = await importShared('vue'); - - -const _sfc_main = { - __name: 'Page', - props: { - model: { - type: Object, - default: () => { - }, - }, - api: { - type: Object, - default: () => { - }, - }, -}, - emits: ['action', 'switch', 'close'], - setup(__props, { emit: __emit }) { - -const editorOptions = { - enableBasicAutocompletion: true, - enableSnippets: true, - enableLiveAutocompletion: true, - showLineNumbers: true, - tabSize: 2 -}; - -const readOnlyEditorOptions = { - readOnly: true, - ...editorOptions.value, -}; - -const proxiesPlaceholder = ref( - `proxies: - - name: "ss node" - type: "ss"` -); -const rulesPlaceholder = ref( - `rules: - - DOMAIN,gemini.google.com,Openai` -); -// v-data-table 的 headers 定义 -const headers = ref([ - {title: '优先级', key: 'priority', sortable: true}, // 可以根据需要设置是否可排序 - {title: '类型', key: 'type', sortable: true}, - {title: '内容', key: 'payload', sortable: true}, - {title: '出站', key: 'action', sortable: false}, - {title: '日期', key: 'time_modified', sortable: true}, - {title: '操作', key: 'actions', sortable: false}, -]); - -const headersRuleset = ref([ - {title: '优先级', key: 'priority', sortable: true}, - {title: '类型', key: 'type', sortable: true}, - {title: '内容', key: 'payload', sortable: true}, - {title: '出站', key: 'action', sortable: true}, - {title: '规则集合名', key: 'name', sortable: true}, - {title: '日期', key: 'time_modified', sortable: true}, - {title: '操作', key: 'actions', sortable: false}, -]); - -const headersRuleProviders = ref([ - {title: '名称', key: 'name', sortable: true}, - {title: '类型', key: 'type', sortable: true}, - {title: '行为', key: 'behavior', sortable: false}, - {title: '格式', key: 'format', sortable: false}, - {title: '来源', key: 'source', sortable: false}, - {title: '操作', key: 'actions', sortable: false}, -]); - -const headersHosts = ref([ - {title: '域名', key: 'domain', sortable: true}, - {title: 'IP', key: 'value', sortable: false}, - {title: 'Cloudflare CDN', key: 'using_cloudflare', sortable: false}, - {title: '操作', key: 'actions', sortable: false}, -]); - -const proxyGroupHeaders = ref([ - {title: '名称', key: 'name', sortable: true}, - {title: '类型', key: 'type', sortable: false}, - {title: '来源', key: 'source', sortable: false}, - {title: '操作', key: 'actions', sortable: false}, -]); -const extraProxiesHeaders = ref([ - {title: '名称', key: 'name', sortable: true}, - {title: '类型', key: 'type', sortable: false}, - {title: '服务器', key: 'server', sortable: false}, - {title: '端口', key: 'port', sortable: false}, - {title: '来源', key: 'source', sortable: false}, - {title: '操作', key: 'actions', sortable: false}, -]); -const activeTab = ref(0); -const page = ref(1); -const pageRuleset = ref(1); -const pageRulProviders = ref(1); -const pageHosts = 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 itemsPerPageHosts = ref(10); -const itemsPerPageProxyGroup = ref(10); -const itemsPerPageExtraProxies = ref(10); -const itemsPerPageOptions = ref([ - {title: '5', value: 5}, - {title: '10', value: 10}, - {title: '20', value: 20}, - {title: '50', value: 50}, - {title: 'All', value: -1}, -]); - -const pageCount = computed(() => { - if (itemsPerPage.value === -1) { - return 1; - } - return Math.ceil(filteredRules.value.length / itemsPerPage.value); -}); - -const filteredRules = computed(() => { - // 模拟 Vuetify 内部的 search 逻辑 - if (!searchTopRule.value) return sortedRules.value; - const keyword = searchTopRule.value.toLowerCase(); - return sortedRules.value.filter(item => - Object.values(item).some(val => - String(val).toLowerCase().includes(keyword) - ) - ); -}); - -const paginatedTopRules = computed(() => { - const start = (page.value - 1) * itemsPerPage.value; - const end = start + itemsPerPage.value; - return filteredRules.value.slice(start, end) -}); - -const filteredRulesetRules = computed(() => { - if (!searchRulesetRule.value) return sortedRulesetRules.value; - const keyword = searchRulesetRule.value.toLowerCase(); - return sortedRulesetRules.value.filter(item => - Object.values(item).some(val => - String(val).toLowerCase().includes(keyword) - ) - ); -}); - -const filteredProxyGroups = computed(() => { - if (!searchProxyGroups.value) return proxyGroups.value; - const keyword = searchProxyGroups.value.toLowerCase(); - return proxyGroups.value.filter(item => - Object.values(item).some(val => - String(val).toLowerCase().includes(keyword) - ) - ); -}); - -const filteredExtraProxies = computed(() => { - if (!searchExtraProxies.value) return extraProxies.value; - const keyword = searchExtraProxies.value.toLowerCase(); - return extraProxies.value.filter(item => - Object.values(item).some(val => - String(val).toLowerCase().includes(keyword) - ) - ); -}); - -const filteredExtraRuleProviders = computed(() => { - if (!searchRuleProviders.value) return extraRuleProviders.value; - const keyword = searchRuleProviders.value.toLowerCase(); - return extraRuleProviders.value.filter(item => - Object.values(item).some(val => - String(val).toLowerCase().includes(keyword) - ) - ); -}); - -const filteredHosts = computed(() => { - if (!searchHosts.value) return hosts.value; - const keyword = searchHosts.value.toLowerCase(); - return hosts.value.filter(item => - Object.values(item).some(val => - String(val).toLowerCase().includes(keyword) - ) - ); -}); - -const paginatedHosts = computed(() => { - const start = (pageHosts.value - 1) * itemsPerPageHosts.value; - const end = start + itemsPerPageHosts.value; - return filteredHosts.value.slice(start, end) -}); - -const paginatedExtraRuleProviders = computed(() => { - const start = (pageRulProviders.value - 1) * itemsPerPageRuleProviders.value; - const end = start + itemsPerPageRuleProviders.value; - return filteredExtraRuleProviders.value.slice(start, end) -}); - -const paginatedExtraProxies = computed(() => { - const start = (pageExtraProxies.value - 1) * itemsPerPageExtraProxies.value; - const end = start + itemsPerPageExtraProxies.value; - return filteredExtraProxies.value.slice(start, end) -}); - -const paginatedProxyGroups = computed(() => { - const start = (pageProxyGroup.value - 1) * itemsPerPageProxyGroup.value; - const end = start + itemsPerPageProxyGroup.value; - return filteredProxyGroups.value.slice(start, end) -}); - -const paginatedRulesetRules = computed(() => { - const start = (pageRuleset.value - 1) * itemsPerPageRuleset.value; - const end = start + itemsPerPageRuleset.value; - return filteredRulesetRules.value.slice(start, end) -}); - -const pageCountRuleset = computed(() => { - if (itemsPerPageRuleset.value === -1) { - return 1; - } - return Math.ceil(filteredRulesetRules.value.length / itemsPerPageRuleset.value); -}); - -const pageCountProxyGroups = computed(() => { - if (itemsPerPageProxyGroup.value === -1) { - return 1; - } - return Math.ceil(filteredProxyGroups.value.length / itemsPerPageProxyGroup.value); -}); - -const pageCountExtraProxies = computed(() => { - if (itemsPerPageExtraProxies.value === -1) { - return 1; - } - 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 pageCountHosts = computed(() => { - if (itemsPerPageHosts.value === -1) { - return 1; - } - return Math.ceil(hosts.value.length / itemsPerPageHosts.value); -}); - -const snackbar = ref({ - show: false, - message: '', - color: 'success' -}); -const dragItem = ref(null); -// 添加自定义出站状态 -const customOutbounds = ref([]); - - -const subUrl = ref(''); -const isValidUrl = (urlString) => { - if (!urlString) return false; - try { - const url = new URL(urlString); - return url.protocol === 'http:' || url.protocol === 'https:'; - } catch (e) { - return false; - } -}; - -function isValidIP(ip) { - const ipv4Regex = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; - const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(([0-9a-fA-F]{1,4}:){1,7}|:):([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})$/; - return ipv4Regex.test(ip) || ipv6Regex.test(ip); -} - -function validateIPs(ips) { - if (ips.length === 0) { - return `至少需要一个 IP 地址` - } - for (const ip of ips) { - if (!isValidIP(ip)) { - return `无效的 IP 地址: ${ip}` - } - } - return true -} - -function dragStart(event, priority, type = 'top') { - const currentRules = type === 'top' ? rules.value : rulesetRules.value; - dragItem.value = currentRules.find(r => r.priority === priority); - event.dataTransfer.effectAllowed = 'move'; -} - -function dragOver(event, priority, type = 'top') { - event.preventDefault(); - const currentRules = type === 'top' ? rules.value : rulesetRules.value; - // 高亮当前悬停行 - currentRules.forEach(rule => { - rule._isHovered = (rule.priority === priority); - }); -} - -async function drop(event, targetPriority, type = 'top') { - // 5. 调用 API 提交 - await props.api.put(`/plugin/ClashRuleProvider/reorder-rules/${type}/${targetPriority}`, dragItem.value); - dragItem.value = null; - await refreshAllRegions(["top", "ruleset"]); -} - -// 接收初始配置 -const props = __props; - -const proxyGroups = ref([]); -const proxyProviders = ref({}); -const extraProxies = ref([]); -const currentProxyGroup = ref({}); - -// 组件状态 -const expand = ref(false); -const loading = ref(true); -const importProxiesLoading = ref(false); -const saveRuleLoading = ref(false); -const saveHostLoading = ref(false); -const saveProxyLoading = ref(false); -const saveRuleProviderLoading = ref(false); -const importRuleLoading = ref(false); -const saveProxyGroupLoading = ref(false); -const error = ref(null); -const errorMsg = ref(''); -const rules = ref([]); -const rulesetRules = ref([]); -const extraRuleProviders = ref([]); -const hosts = ref([]); -const status = ref('running'); -const rulesetPrefix = ref('📂<='); -const geoRules = ref({ - geoip: [], - geosite: [], -}); -const lastUpdated = ref(''); -const refreshingSubscription = ref({}); -const yamlDialog = ref(false); -const displayedYaml = ref(''); -const searchTopRule = ref(''); -const searchRulesetRule = ref(''); -const searchRuleProviders = ref(''); -const searchExtraProxies = ref(''); -const searchProxyGroups = ref(''); -const searchHosts = ref(''); -// 规则编辑相关状态 -const proxyGroupDialogVisible = ref(false); -const ruleDialogVisible = ref(false); -const ruleProviderDialog = ref(false); -const hostDialog = ref(false); -const proxiesDialogVisible = ref(false); -const editingPriority = ref(null); -const editingProxyGroupName = ref(null); -const editingRuleProviderName = ref(null); -const editingHostDomainName = ref(null); -const editingType = ref('top'); // 记录当前编辑的规则类型('top' 或 'ruleset') -const editingProxy = ref(null); -const editingProxyName = ref(null); -const currentRule = ref({ - type: 'DOMAIN-SUFFIX', - payload: '', - action: 'DIRECT', - additional_params: null, - priority: 0 -}); - -const newRuleProvider = ref({ - name: '', - type: 'http', - path: null, - url: null, - interval: 600, - behavior: 'classical', - format: 'yaml', - 'size-limit': 0, - payload: [], -}); - -const newHost = ref({ - domain: '', - value: [], - using_cloudflare: false, -}); - -// 导入规则相关状态 -const importRuleDialog = ref(false); -const importExtraProxiesDialog = ref(false); -const importRules = ref({ - type: 'YAML', - payload: '' -}); -const importExtraProxies = ref({ - type: 'YAML', - payload: '' -}); - -// 排序后的规则 -const sortedRules = computed(() => [...rules.value].sort((a, b) => a.priority - b.priority)); -const sortedRulesetRules = computed(() => [...rulesetRules.value].sort((a, b) => a.priority - b.priority)); -const ruleProviderNames = computed(() => { - return extraRuleProviders.value.map(provider => provider.name) -}); - -const importExtraProxiesPlaceholderText = computed(() => { - return importExtraProxies.value.type === 'YAML' - ? 'proxies: []' - : 'vless://xxxx'; -}); - -const importRuleTypes = ['YAML']; -const importProxiesTypes = ['YAML', 'LINK']; -const ruleProviderTypes = ['http', 'file', 'inline']; -const ruleProviderBehaviorTypes = ['domain', 'ipcidr', 'classical']; -const ruleProviderFormatTypes = ['yaml', 'text', 'mrs']; -// 修改actions为计算属性,合并内置动作和自定义出站 -computed(() => [ - 'DIRECT', 'REJECT', 'REJECT-DROP', 'PASS', 'COMPATIBLE', - ...customOutbounds.value.map(outbound => outbound.name) -]); - -const subscriptionsInfo = ref({}); -const bestCloudflareIPs = ref([]); - -// 自定义事件,用于通知主应用刷新数据 -const emit = __emit; - -function pageTitle(itemPerPageValue) { - if (itemPerPageValue < 0) { - return "♾️" - } - return `${itemPerPageValue}` -} - -// 格式化字节为易读单位(如 1.5 GB) -function formatBytes(bytes) { - if (bytes === 0) return '0 B'; - const k = 1024; - const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} - -// 在工具函数中添加时间戳转换 -function formatTimestamp(timestamp) { - if (!timestamp) return 'N/A'; - const date = new Date(timestamp * 1000); // 注意:JS时间戳是毫秒,需乘以1000 - return date.toLocaleDateString('zh-CN'); // 或使用其他格式如 date.toISOString().split('T')[0] -} - -function timestampToDate(timestamp) { - if (!timestamp) return 'N/A'; - const date = new Date(timestamp * 1000); // 注意:JS时间戳是毫秒,需乘以1000 - return date.toLocaleString('zh-CN', { // 'en-GB' 表示使用英国格式(YYYY-MM-DD HH:mm:ss) - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false // 24小时制 - }); -} - -// 更新过期时间颜色判断(基于时间戳) -function getExpireColor(timestamp) { - if (!timestamp) return 'grey'; - const secondsLeft = timestamp - Math.floor(Date.now() / 1000); - const daysLeft = secondsLeft / 86400; - return daysLeft < 7 ? 'error' : daysLeft < 30 ? 'warning' : 'success'; -} - -// 复制功能 -function copyToClipboard(text) { - navigator.clipboard.writeText(text).then(() => { - snackbar.value = { - show: true, - message: '已复制到剪贴板', - color: 'success' - }; - }).catch(() => { - snackbar.value = { - show: true, - message: '复制失败', - color: 'error' - }; - }); -} - -// 计算已用流量百分比 -function calculatePercentage(download, total) { - return total > 0 ? Math.round((download / total) * 100) : 0; -} - -// 根据流量百分比获取颜色 -function getUsageColor(percentage) { - return percentage > 90 ? 'error' : percentage > 70 ? 'warning' : 'success'; -} - -function getBehaviorColor(action) { - const colors = { - 'classical': 'success', - 'domain': 'error', - 'ipcidr': 'error', - }; - return colors[action] || 'primary' -} - -function getActionColor(action) { - const colors = { - 'DIRECT': 'success', - 'REJECT': 'error', - 'REJECT-DROP': 'error', - 'PASS': 'warning', - 'COMPATIBLE': 'info' - }; - return colors[action] || 'primary' -} - -function getProxyGroupTypeColor(action) { - const colors = { - 'url-test': 'success', - 'fallback': 'error', - 'load-balance': 'primary', - 'select': 'info' - }; - return colors[action] || 'warning' -} - -function getProxyColor(action) { - const colors = { - 'ss': 'success', - 'ssr': 'success', - 'trojan': 'error', - 'vmess': 'primary', - 'vless': 'primary', - 'hysteria': 'info', - 'hysteria2': 'info', - 'anytls': 'warning', - }; - return colors[action] || 'secondary' -} - -function getBoolColor(value) { - if (value) { - return 'primary' - } - return 'success' -} - -function isSystemRule(rule) { - return rule.remark?.startsWith('Auto'); -} - -function isManual(source) { - return source === 'Manual'; -} - -function isInvalid(source) { - return source === 'Invalid'; -} - -function isRegion(source) { - return source === 'Region'; -} - -function isTemplate(source) { - return source === 'Template'; -} - -function openImportRuleDialog() { - importRules.value = { - type: 'YAML', - payload: '' - }; - importRuleDialog.value = true; -} - -function openImportExtraProxiesDialog() { - importExtraProxies.value = { - type: 'YAML', - payload: '' - }; - importExtraProxiesDialog.value = true; -} - -// 打开添加规则对话框 -function openAddRuleDialog(type = 'top') { - editingPriority.value = null; - editingType.value = type; - const currentRules = type === 'top' ? sortedRules.value : sortedRulesetRules.value; - const nextPriority = currentRules.length > 0 - ? Math.max(...currentRules.map(r => r.priority)) + 1 - : 0; - currentRule.value = { - type: 'DOMAIN-SUFFIX', - payload: '', - action: 'DIRECT', - additional_params: null, - priority: nextPriority - }; - ruleDialogVisible.value = true; -} - -function openAddRuleProviderDialog() { - editingRuleProviderName.value = null; - newRuleProvider.value = { - name: '', - type: 'http', - path: null, - url: null, - interval: 600, - behavior: 'classical', - format: 'yaml', - 'size-limit': 0, - payload: [], - }; - ruleProviderDialog.value = true; -} - -function openAddHostDialog() { - editingHostDomainName.value = null; - newHost.value = { - domain: '', - value: [], - using_cloudflare: false, - }; - hostDialog.value = true; -} - -const showProxyGroupYaml = (proxyGroup) => { - const proxyGroupCopy = {...proxyGroup}; - // 如果存在 source 键,删除它 - if ('source' in proxyGroupCopy) { - delete proxyGroupCopy.source; - } - if ('v2ray_link' in proxyGroupCopy) { - delete proxyGroupCopy.v2ray_link; - } - if ('overwritten' in proxyGroupCopy) { - delete proxyGroupCopy.overwritten; - } - // 生成 YAML 并显示 - displayedYaml.value = jsYaml.dump(proxyGroupCopy); - yamlDialog.value = true; -}; - -function openAddProxyGroupDialog() { - editingProxyGroupName.value = null; - currentProxyGroup.value = { - name: '', - type: 'select', - proxies: [], - url: 'https://www.gstatic.com/generate_204', - lazy: true, - interval: null, - timeout: null, - 'disable-udp': false, - filter: null, - 'include-all': false, - 'include-all-proxies': false, - 'include-all-providers': false, - 'exclude-filter': null, - 'expected-status': '*', - 'exclude-type': null, - tolerance: null, - strategy: null, - hidden: false, - icon: null, - use: null, - 'max-failed-times': 5, - 'sample-rate': 1, - 'policy-priority': '1', - uselightgbm: false, - collectdata: false - }; - proxyGroupDialogVisible.value = true; -} - -// 编辑规则 -function editRule(priority, type = 'top') { - editingType.value = type; // 记录当前编辑的类型 - const currentRules = type === 'top' ? sortedRules.value : sortedRulesetRules.value; - const rule = currentRules.find(r => r.priority === priority); - - if (rule) { - editingPriority.value = priority; - currentRule.value = { - type: rule.type, - payload: rule?.payload, - action: rule.action, - additional_params: rule.additional_params || null, - priority: rule.priority, - conditions: rule?.conditions, - condition: rule?.condition, - }; - ruleDialogVisible.value = true; - } -} - -function openProxiesDialog(proxy) { - editingProxyName.value = proxy.name; - editingProxy.value = proxy; - proxiesDialogVisible.value = true; -} - -function editProxyGroup(name) { - const proxyGroup = proxyGroups.value.find(p => p.name === name); - if (proxyGroup) { - editingProxyGroupName.value = name; - currentProxyGroup.value = { - name: proxyGroup.name, - type: proxyGroup.type, - proxies: proxyGroup?.proxies || [], - url: proxyGroup?.url || null, - lazy: proxyGroup?.lazy ?? true, - interval: proxyGroup?.interval ?? null, - timeout: proxyGroup?.timeout ?? null, - 'disable-udp': proxyGroup?.['disable-udp'] ?? false, - filter: proxyGroup?.filter, - 'include-all': proxyGroup?.['include-all'] ?? false, - 'include-all-proxies': proxyGroup?.['include-all-proxies'] ?? false, - 'include-all-providers': proxyGroup?.['include-all-providers'] ?? false, - 'exclude-filter': proxyGroup?.['exclude-filter'] || null, - 'exclude-type': proxyGroup?.['exclude-type'] || null, - tolerance: proxyGroup?.tolerance ?? null, - strategy: proxyGroup?.strategy ?? null, - 'expected-status': proxyGroup?.['expected-status'] || '*', - hidden: proxyGroup?.hidden ?? false, - icon: proxyGroup?.icon || null, - use: proxyGroup?.use || null, - 'max-failed-times': proxyGroup?.['max-failed-times'] ?? 5, - 'policy-priority': proxyGroup?.['policy-priority'] ?? '1', - uselightgbm: proxyGroup?.['uselightgbm'] ?? false, - collectdata: proxyGroup?.['collectdata'] ?? false, - 'sample-rate': proxyGroup?.['sample-rate'] ?? 1, - }; - proxyGroupDialogVisible.value = true; - } -} - -function editRuleProvider(name) { - const ruleProvider = extraRuleProviders.value.find(r => r.name === name); - 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; - } -} - -function editHost(domain) { - const hostItem = hosts.value.find(r => r.domain === domain); - if (hostItem) { - editingHostDomainName.value = domain; - newHost.value = { - domain: hostItem.domain, - value: hostItem.value, - using_cloudflare: hostItem.using_cloudflare, - }; - hostDialog.value = true; - } -} - -function showError(Msg) { - error.value = true; - errorMsg.value = Msg; -} - -async function importRule() { - try { - importRuleLoading.value = true; - const requestData = { - type: importRules.value.type, - payload: importRules.value.payload - }; - const result = await props.api.post('/plugin/ClashRuleProvider/import', requestData); - if (!result.success) { - showError('规则导入失败: ' + (result.message || '未知错误')); - snackbar.value = { - show: true, - message: '规则导入失败', - color: 'error' - }; - return - } - importRuleDialog.value = false; - await refreshAllRegions(["top"]); - // 显示成功提示 - snackbar.value = { - show: true, - message: '规则导入成功', - color: 'success' - }; - } catch (err) { - showError('导入规则失败: ' + (err.message || '未知错误')); - snackbar.value = { - show: true, - message: '导入规则失败', - color: 'error' - }; - } finally { - importRuleLoading.value = false; - } -} - -async function importExtraProxiesFun() { - try { - importProxiesLoading.value = true; - const requestData = { - type: importExtraProxies.value.type, - payload: importExtraProxies.value.payload - }; - const result = await props.api.put('/plugin/ClashRuleProvider/proxies', requestData); - if (!result.success) { - showError('节点导入失败: ' + (result.message || '未知错误')); - snackbar.value = { - show: true, - message: '节点导入失败', - color: 'error' - }; - return - } - importExtraProxiesDialog.value = false; - await refreshAllRegions(["proxies", "clash-outbounds"]); - // 显示成功提示 - snackbar.value = { - show: true, - message: '节点导入成功', - color: 'success' - }; - } catch (err) { - showError('节点导入失败: ' + (err.message || '未知错误')); - snackbar.value = { - show: true, - message: '节点导入失败', - color: 'error' - }; - } finally { - importProxiesLoading.value = false; - } -} - -async function saveProxyGroups(newProxyGroup) { - const action = editingProxyGroupName.value === null ? '添加代理组' : '更新代理组'; - try { - saveProxyGroupLoading.value = true; - const requestData = newProxyGroup; - const name = encodeURIComponent(editingProxyGroupName.value); - const path = editingProxyGroupName.value === null ? '' : `/${name}`; - const method = editingProxyGroupName.value === null ? 'post' : 'patch'; - const result = await props.api[method](`/plugin/ClashRuleProvider/proxy-groups${path}`, requestData); - if (!result.success) { - showError(action + '失败: ' + (result.message || '未知错误')); - snackbar.value = { - show: true, - message: action + '失败', - color: 'error' - }; - return - } - proxyGroupDialogVisible.value = false; - await refreshAllRegions(["clash-outbounds", "proxy-groups"]); - snackbar.value = { - show: true, - message: action + '成功', - color: 'success' - }; - } catch (err) { - showError(action + '失败: ' + (err.message || '未知错误')); - snackbar.value = { - show: true, - message: action + '失败', - color: 'error' - }; - } finally { - saveProxyGroupLoading.value = false; - } -} - -const ruleProvidersForm = ref(null); -const hostForm = ref(null); - -function closeRuleDialog() { - ruleDialogVisible.value = false; -} - -// 保存规则 -async function saveRule(newRule) { - try { - saveRuleLoading.value = true; - if (newRule.payload) { - newRule.payload = newRule.payload.trim(); - } - const requestData = - { - ...newRule, - additional_params: newRule.additional_params - ? newRule.additional_params - : null - } - ; - const priority = editingPriority.value === null ? '' : `/${editingPriority.value}`; - const method = editingPriority.value === null ? 'post' : 'patch'; - const result = await props.api[method](`/plugin/ClashRuleProvider/rules/${editingType.value}${priority}`, requestData); - if (!result.success) { - showError('保存规则失败: ' + (result.message || '未知错误')); - snackbar.value = { - show: true, - message: '保存规则合失败', - color: 'error' - }; - return - } - closeRuleDialog(); - await refreshAllRegions(["top", "ruleset"]); - // 显示成功提示 - snackbar.value = { - show: true, - message: editingPriority.value === null ? '规则添加成功' : '规则更新成功', - color: 'success' - }; - } catch (err) { - showError('保存规则失败: ' + (err.message || '未知错误')); - snackbar.value = { - show: true, - message: '保存规则失败', - color: 'error' - }; - } finally { - saveRuleLoading.value = false; - } -} - -async function saveRuleProvider() { - const {valid} = await ruleProvidersForm.value.validate(); - if (!valid) return; - try { - saveRuleProviderLoading.value = true; - const name = encodeURIComponent(editingRuleProviderName.value === null ? newRuleProvider.value.name : editingRuleProviderName.value); - const requestData = { - name: newRuleProvider.value.name, - rule_provider: newRuleProvider.value, - }; - const result = await props.api.post(`/plugin/ClashRuleProvider/rule-providers/${name}`, requestData); - if (!result.success) { - showError('保存规则集合失败: ' + (result.message || '未知错误')); - snackbar.value = { - show: true, - message: '保存规则集合失败', - color: 'error' - }; - return - } - ruleProviderDialog.value = false; - await refreshAllRegions(["rule-providers"]); - snackbar.value = { - show: true, - message: editingRuleProviderName.value === null ? '规则集合添加成功' : '规则集合更新成功', - color: 'success' - }; - } catch (err) { - showError('保存规则集合失败: ' + (err.message || '未知错误')); - snackbar.value = { - show: true, - message: '保存规则集合失败', - color: 'error' - }; - } finally { - saveRuleProviderLoading.value = false; - } -} - -const saveProxy = async (proxy) => { - saveProxyLoading.value = true; - try { - const requestData = proxy; - const name = encodeURIComponent(editingProxyName.value); - const result = await props.api.patch(`/plugin/ClashRuleProvider/proxies/${name}`, requestData); - if (!result.success) { - showError('保存出站代理失败: ' + (result.message || '未知错误')); - snackbar.value = { - show: true, - message: '保存出站代理失败', - color: 'error' - }; - return - } - proxiesDialogVisible.value = false; - await refreshAllRegions(["proxies"]); - snackbar.value = { - show: true, - message: editingProxyName.value === null ? '出站代理添加成功' : '出站代理更新成功', - color: 'success' - }; - } catch (err) { - showError('保存 Proxy 失败: ' + (err.message || '未知错误')); - snackbar.value = { - show: true, - message: '保存代理失败', - color: 'error' - }; - } finally { - saveProxyLoading.value = false; - } -}; - -async function saveHost() { - const {valid} = await hostForm.value.validate(); - if (!valid) return; - try { - saveHostLoading.value = true; - newHost.value.domain = newHost.value.domain.trim(); - const requestData = { - domain: editingHostDomainName.value === null ? newHost.value.domain : editingHostDomainName.value, - value: newHost.value - }; - const result = await props.api.post('/plugin/ClashRuleProvider/hosts', requestData); - if (!result.success) { - showError('保存 Host 失败: ' + (result.message || '未知错误')); - snackbar.value = { - show: true, - message: '保存 Host 失败', - color: 'error' - }; - return - } - hostDialog.value = false; - await refreshAllRegions(["hosts"]); - snackbar.value = { - show: true, - message: editingHostDomainName.value === null ? 'Host 添加成功' : 'Host 更新成功', - color: 'success' - }; - } catch (err) { - showError('保存 Host 失败: ' + (err.message || '未知错误')); - snackbar.value = { - show: true, - message: '保存 Host 失败', - color: 'error' - }; - } finally { - saveHostLoading.value = false; - } -} - -// 删除规则 -async function deleteRule(priority, type = 'top') { - try { - await props.api.delete(`/plugin/ClashRuleProvider/rules/${type}/${priority}`); - await refreshAllRegions(["top", "ruleset"]); - } catch (err) { - showError(err.message || '删除规则失败'); - } finally { - } -} - -async function deleteRuleProvider(name) { - try { - const n = encodeURIComponent(name); - await props.api.delete(`/plugin/ClashRuleProvider/rule-providers/${n}`); - await refreshAllRegions(["rule-providers"]); - } catch (err) { - showError(err.message || '删除规则集合失败'); - } -} - -async function deleteHost(name) { - try { - await props.api.delete('/plugin/ClashRuleProvider/hosts', { - domain: name - }); - await refreshAllRegions(["hosts"]); - } catch (err) { - showError(err.message || '删除 host 失败'); - } -} - -async function deleteProxyGroup(name) { - try { - const n = encodeURIComponent(name); - await props.api.delete(`/plugin/ClashRuleProvider/proxy-groups/${n}`); - await refreshAllRegions(["proxy-groups", "clash-outbounds"]); - } catch (err) { - showError(err.message || '删除规则失败'); - } -} - -async function deleteExtraProxies(name) { - try { - await props.api.delete(`/plugin/ClashRuleProvider/proxies/${name}`); - await refreshAllRegions(["proxies", "clash-outbounds"]); - } catch (err) { - showError(err.message || '删除规则失败'); - } -} - -// 更新订阅 -async function updateSubscription(url) { - if (!url) { - showError('请先输入订阅URL'); - return - } - - refreshingSubscription.value[url] = true; - try { - await props.api.put('plugin/ClashRuleProvider/refresh', { - url: url - }); - // 显示成功提示 - snackbar.value = { - show: true, - message: '订阅更新成功', - color: 'success' - }; - await refreshAllRegions(["status", "clash-outbounds", "rule-providers", "proxy-groups", "proxies", "proxy-providers"]); - } catch (err) { - showError(err.message); - } finally { - refreshingSubscription.value[url] = false; - } -} - -async function toggleSubscription(url, enabled) { - try { - await props.api.post('plugin/ClashRuleProvider/subscription-info', { - url: url, - value: enabled, - field: 'enabled' - }); - // 显示成功提示 - snackbar.value = { - show: true, - message: '设置成功', - color: 'success' - }; - await refreshAllRegions(["status"]); - } catch (err) { - showError(err.message); - } finally { - refreshingSubscription.value[url] = false; - } -} - -function extractDomain(url) { - try { - const hostname = new URL(url).hostname; - // 按点分割 - const parts = hostname.split('.'); - // 如果是 IPv4/IPv6,直接返回 - if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname) || hostname.includes(':')) { - return hostname - } - // 只有一段的直接返回 - if (parts.length <= 2) { - return hostname - } - // 返回最后两段(example.com) - return parts.slice(-2).join('.') - } catch { - return url - } -} - -async function refreshStatus() { - const state = await props.api.get('/plugin/ClashRuleProvider/status'); - // 处理状态请求的响应 - status.value = state?.data?.state ? 'running' : 'disabled'; - subUrl.value = state?.data?.sub_url || ''; - - if (state?.data?.subscription_info) { - subscriptionsInfo.value = {}; - refreshingSubscription.value = {}; - Object.keys(state.data.subscription_info).forEach(url => { - const newSubInfo = { - download: 0, - upload: 0, - total: 0, - expire: 0, - last_update: 0, - used_percentage: 0, - rule_size: 0, - proxy_num: 0, - enabled: false - }; - Object.keys(state.data.subscription_info[url]).forEach(key => { - if (key in newSubInfo) { - newSubInfo[key] = state.data.subscription_info[url][key]; - } - }); - newSubInfo.used_percentage = calculatePercentage( - state.data.subscription_info[url]?.download || 0, - state.data.subscription_info[url]?.total || 0 - ); - refreshingSubscription[url] = false; - subscriptionsInfo.value[url] = newSubInfo; - }); - } - bestCloudflareIPs.value = state?.data?.best_cf_ip || []; - rulesetPrefix.value = state?.data?.ruleset_prefix || '📂<='; - geoRules.value = state?.data?.geoRules ?? geoRules.value; -} - -async function refreshTopRules() { - const response = await props.api.get('/plugin/ClashRuleProvider/rules/top'); - rules.value = response?.data.rules || []; -} - -async function refreshRulesetRules() { - const response = await props.api.get('/plugin/ClashRuleProvider/rules/ruleset'); - rulesetRules.value = response?.data.rules || []; -} - -async function refreshOutbounds() { - const outboundsResponse = await props.api.get('/plugin/ClashRuleProvider/clash-outbound'); - customOutbounds.value = outboundsResponse?.data.outbound || []; -} - -async function refreshExtraRuleProviders() { - const providersResponse = await props.api.get('/plugin/ClashRuleProvider/rule-providers'); - extraRuleProviders.value = providersResponse?.data || []; -} - -async function refreshProxyGroups() { - const proxyGroupsResponse = await props.api.get('/plugin/ClashRuleProvider/proxy-groups'); - proxyGroups.value = proxyGroupsResponse?.data.proxy_groups || []; -} - -async function refreshExtraProxies() { - const extraProxiesResponse = await props.api.get('/plugin/ClashRuleProvider/proxies'); - extraProxies.value = extraProxiesResponse?.data.proxies || []; -} - -async function refreshHosts() { - const hostsResponse = await props.api.get('/plugin/ClashRuleProvider/hosts'); - hosts.value = hostsResponse?.data.hosts || []; -} - -async function refreshProxyProviders() { - const proxyProvidersResponse = await props.api.get('/plugin/ClashRuleProvider/proxy-providers'); - proxyProviders.value = proxyProvidersResponse?.data.proxy_providers || {}; -} - -async function refreshDataOf(region) { - switch (region) { - case "status": - return refreshStatus(); - case "top": - return refreshTopRules(); - case "ruleset": - return refreshRulesetRules(); - case "clash-outbounds": - return refreshOutbounds(); - case "rule-providers": - return refreshExtraRuleProviders(); - case "proxy-groups": - return refreshProxyGroups(); - case "proxies": - return refreshExtraProxies(); - case "hosts": - return refreshHosts(); - case "proxy-providers": - return refreshProxyProviders(); - default: - throw new Error("Unknown region: " + region); - } -} - -async function refreshAllRegions(regions) { - try { - await Promise.all(regions.map(refreshDataOf)); - } catch (err) { - console.error('获取数据失败:', err); - showError(err.message || '获取数据失败'); - status.value = 'error'; - } finally { - lastUpdated.value = new Date().toLocaleString(); - } -} - -// 获取和刷新数据 -async function refreshData() { - loading.value = true; - error.value = false; - errorMsg.value = ''; - try { - // 并发发送所有独立的请求 - const [ - state, - response, - response_ruleset, - outboundsResponse, - providersResponse, - proxyGroupsResponse, - extraProxiesResponse, - hostsResponse, - proxyProvidersResponse, - ] = await Promise.all([ - props.api.get('/plugin/ClashRuleProvider/status'), - props.api.get('/plugin/ClashRuleProvider/rules/top'), - props.api.get('/plugin/ClashRuleProvider/rules/ruleset'), - props.api.get('/plugin/ClashRuleProvider/clash-outbound'), - props.api.get('/plugin/ClashRuleProvider/rule-providers'), - props.api.get('/plugin/ClashRuleProvider/proxy-groups'), - props.api.get('/plugin/ClashRuleProvider/proxies'), - props.api.get('/plugin/ClashRuleProvider/hosts'), - props.api.get('/plugin/ClashRuleProvider/proxy-providers'), - ]); - - // 处理状态请求的响应 - status.value = state?.data?.state ? 'running' : 'disabled'; - subUrl.value = state?.data?.sub_url || ''; - - if (state?.data?.subscription_info) { - subscriptionsInfo.value = {}; - refreshingSubscription.value = {}; - Object.keys(state.data.subscription_info).forEach(url => { - const newSubInfo = { - download: 0, - upload: 0, - total: 0, - expire: 0, - last_update: 0, - used_percentage: 0, - rule_size: 0, - proxy_num: 0, - enabled: false - }; - Object.keys(state.data.subscription_info[url]).forEach(key => { - if (key in newSubInfo) { - newSubInfo[key] = state.data.subscription_info[url][key]; - } - }); - newSubInfo.used_percentage = calculatePercentage( - state.data.subscription_info[url]?.download || 0, - state.data.subscription_info[url]?.total || 0 - ); - refreshingSubscription[url] = false; - subscriptionsInfo.value[url] = newSubInfo; - }); - } - bestCloudflareIPs.value = state?.data?.best_cf_ip || []; - rulesetPrefix.value = state?.data?.ruleset_prefix || ''; - geoRules.value = state?.data?.geoRules ?? geoRules.value; - rules.value = response?.data.rules || []; - rulesetRules.value = response_ruleset?.data.rules || []; - customOutbounds.value = outboundsResponse?.data.outbound || []; - extraRuleProviders.value = providersResponse?.data || []; - proxyGroups.value = proxyGroupsResponse?.data.proxy_groups || []; - extraProxies.value = extraProxiesResponse?.data.proxies || []; - hosts.value = hostsResponse?.data.hosts || []; - proxyProviders.value = proxyProvidersResponse?.data.proxy_providers || {}; - lastUpdated.value = new Date().toLocaleString(); - } catch (err) { - console.error('获取数据失败:', err); - showError(err.message || '获取数据失败'); - status.value = 'error'; - } finally { - loading.value = false; - } -} - -// 通知主应用切换到配置页面 -function notifySwitch() { - emit('switch'); -} - -// 通知主应用关闭组件 -function notifyClose() { - emit('close'); -} - -// 组件挂载时加载数据 -onMounted(() => { - refreshData(); -}); - - -return (_ctx, _cache) => { - const _component_v_alert = _resolveComponent("v-alert"); - const _component_v_icon = _resolveComponent("v-icon"); - const _component_v_chip = _resolveComponent("v-chip"); - const _component_v_card_title = _resolveComponent("v-card-title"); - const _component_v_btn = _resolveComponent("v-btn"); - const _component_v_card_item = _resolveComponent("v-card-item"); - const _component_v_skeleton_loader = _resolveComponent("v-skeleton-loader"); - const _component_v_tab = _resolveComponent("v-tab"); - const _component_v_tabs = _resolveComponent("v-tabs"); - const _component_v_text_field = _resolveComponent("v-text-field"); - const _component_v_col = _resolveComponent("v-col"); - const _component_v_row = _resolveComponent("v-row"); - const _component_v_data_table = _resolveComponent("v-data-table"); - const _component_v_card_text = _resolveComponent("v-card-text"); - const _component_v_divider = _resolveComponent("v-divider"); - const _component_v_spacer = _resolveComponent("v-spacer"); - const _component_v_card_actions = _resolveComponent("v-card-actions"); - const _component_v_card = _resolveComponent("v-card"); - const _component_v_pagination = _resolveComponent("v-pagination"); - const _component_v_list_item_title = _resolveComponent("v-list-item-title"); - const _component_v_list_item = _resolveComponent("v-list-item"); - const _component_v_list = _resolveComponent("v-list"); - const _component_v_menu = _resolveComponent("v-menu"); - const _component_v_window_item = _resolveComponent("v-window-item"); - const _component_v_tooltip = _resolveComponent("v-tooltip"); - const _component_v_card_action = _resolveComponent("v-card-action"); - const _component_v_progress_linear = _resolveComponent("v-progress-linear"); - const _component_v_switch = _resolveComponent("v-switch"); - const _component_v_window = _resolveComponent("v-window"); - const _component_v_expand_transition = _resolveComponent("v-expand-transition"); - const _component_v_snackbar = _resolveComponent("v-snackbar"); - const _component_v_dialog = _resolveComponent("v-dialog"); - const _component_v_select = _resolveComponent("v-select"); - const _component_v_textarea = _resolveComponent("v-textarea"); - const _component_v_combobox = _resolveComponent("v-combobox"); - const _component_v_form = _resolveComponent("v-form"); - - return (_openBlock(), _createElementBlock("div", _hoisted_1, [ - _createVNode(_component_v_card, null, { - default: _withCtx(() => [ - (error.value) - ? (_openBlock(), _createBlock(_component_v_alert, { - key: 0, - modelValue: error.value, - "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((error).value = $event)), - type: "error", - class: "mb-4", - closable: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(errorMsg.value), 1) - ]), - _: 1 - }, 8, ["modelValue"])) - : _createCommentVNode("", true), - _createVNode(_component_v_card_item, null, { - append: _withCtx(() => [ - _createVNode(_component_v_btn, { - icon: "", - color: "primary", - variant: "text", - onClick: notifyClose - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[65] || (_cache[65] = [ - _createTextVNode("mdi-close") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - class: "mr-2", - size: "24" - }, { - default: _withCtx(() => [ - _createElementVNode("img", { - src: `/api/v1/system/img/1?cache=1&imgurl=${encodeURIComponent('https://raw.githubusercontent.com/MetaCubeX/mihomo/Meta/Meta.png')}`, - alt: "icon", - style: {"width":"100%","height":"100%"} - }, null, 8, _hoisted_2) - ]), - _: 1 - }), - _cache[64] || (_cache[64] = _createTextVNode(" Clash Rule Provider ")), - _createVNode(_component_v_chip, { - onClick: _cache[1] || (_cache[1] = $event => (copyToClipboard(subUrl.value))), - size: "small", - color: status.value === 'running' ? 'success' : 'warning' - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(status.value), 1) - ]), - _: 1 - }, 8, ["color"]) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - (loading.value) - ? (_openBlock(), _createBlock(_component_v_skeleton_loader, { - key: 0, - type: "card" - })) - : (_openBlock(), _createElementBlock("div", _hoisted_3, [ - _createVNode(_component_v_tabs, { - modelValue: activeTab.value, - "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ((activeTab).value = $event)), - "background-color": "primary", - dark: "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_tab, null, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[66] || (_cache[66] = [ - _createTextVNode("mdi-format-list-bulleted") - ])), - _: 1 - }), - _cache[67] || (_cache[67] = _createTextVNode(" 规则集规则 ")) - ]), - _: 1 - }), - _createVNode(_component_v_tab, null, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[68] || (_cache[68] = [ - _createTextVNode("mdi-pin") - ])), - _: 1 - }), - _cache[69] || (_cache[69] = _createTextVNode(" 置顶规则 ")) - ]), - _: 1 - }), - _createVNode(_component_v_tab, null, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[70] || (_cache[70] = [ - _createTextVNode("mdi-source-branch") - ])), - _: 1 - }), - _cache[71] || (_cache[71] = _createTextVNode(" 代理组 ")) - ]), - _: 1 - }), - _createVNode(_component_v_tab, null, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[72] || (_cache[72] = [ - _createTextVNode("mdi-rocket-launch") - ])), - _: 1 - }), - _cache[73] || (_cache[73] = _createTextVNode(" 出站代理 ")) - ]), - _: 1 - }), - _createVNode(_component_v_tab, null, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[74] || (_cache[74] = [ - _createTextVNode("mdi-folder-multiple") - ])), - _: 1 - }), - _cache[75] || (_cache[75] = _createTextVNode(" 规则集合 ")) - ]), - _: 1 - }), - _createVNode(_component_v_tab, null, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[76] || (_cache[76] = [ - _createTextVNode("mdi-lan") - ])), - _: 1 - }), - _cache[77] || (_cache[77] = _createTextVNode(" Hosts ")) - ]), - _: 1 - }), - _createVNode(_component_v_tab, null, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { start: "" }, { - default: _withCtx(() => _cache[78] || (_cache[78] = [ - _createTextVNode("mdi-cloud-sync") - ])), - _: 1 - }), - _cache[79] || (_cache[79] = _createTextVNode(" 订阅状态 ")) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]), - _createVNode(_component_v_window, { - modelValue: activeTab.value, - "onUpdate:modelValue": _cache[29] || (_cache[29] = $event => ((activeTab).value = $event)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_window_item, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_4, [ - _createElementVNode("div", _hoisted_5, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "10", - sm: "6", - class: "d-flex justify-start" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: searchRulesetRule.value, - "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ((searchRulesetRule).value = $event)), - label: "搜索规则", - clearable: "", - density: "compact", - variant: "solo-filled", - "hide-details": "", - class: "search-field", - "prepend-inner-icon": "mdi-magnify", - flat: "", - rounded: "pill", - "single-line": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - sm: "6", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - color: "primary", - onClick: _cache[4] || (_cache[4] = $event => (openAddRuleDialog('ruleset'))), - icon: "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[80] || (_cache[80] = [ - _createTextVNode("mdi-plus") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_6, [ - (_openBlock(), _createBlock(_component_v_data_table, { - "fixed-header": "", - class: "px-4", - headers: headersRuleset.value, - items: sortedRulesetRules.value, - search: searchRulesetRule.value, - page: pageRuleset.value, - "onUpdate:page": _cache[5] || (_cache[5] = $event => ((pageRuleset).value = $event)), - "items-per-page": itemsPerPageRuleset.value, - "items-per-page-options": itemsPerPageOptions.value, - "item-key": "priority", - density: "compact", - "hide-default-footer": "", - key: pageRuleset.value - }, { - item: _withCtx(({ item }) => [ - _createElementVNode("tr", { - class: _normalizeClass({ 'bg-blue-lighten-5': item._isHovered }), - draggable: "true", - onDragstart: $event => (dragStart($event, item.priority, 'ruleset')), - onDragover: _withModifiers($event => (dragOver($event, item.priority, 'ruleset')), ["prevent"]), - onDrop: $event => (drop($event, item.priority, 'ruleset')) - }, [ - _createElementVNode("td", null, [ - _createVNode(_component_v_icon, { class: "drag-handle mr-1" }, { - default: _withCtx(() => _cache[81] || (_cache[81] = [ - _createTextVNode("mdi-drag") - ])), - _: 1 - }), - _createTextVNode(" " + _toDisplayString(item.priority), 1) - ]), - _createElementVNode("td", null, _toDisplayString(item.type), 1), - _createElementVNode("td", null, _toDisplayString(item.payload), 1), - _createElementVNode("td", null, [ - _createVNode(_component_v_chip, { - color: getActionColor(item.action), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.action), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _createElementVNode("td", null, _toDisplayString(rulesetPrefix.value) + _toDisplayString(item.action), 1), - (item?.time_modified) - ? (_openBlock(), _createElementBlock("td", _hoisted_8, _toDisplayString(timestampToDate(item.time_modified)), 1)) - : (_openBlock(), _createElementBlock("td", _hoisted_9)), - _createElementVNode("td", null, [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (editRule(item.priority, 'ruleset')) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[82] || (_cache[82] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteRule(item.priority, 'ruleset')) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[83] || (_cache[83] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]) - ]) - ], 42, _hoisted_7) - ]), - _: 1 - }, 8, ["headers", "items", "search", "page", "items-per-page", "items-per-page-options"])) - ]), - _createElementVNode("div", _hoisted_10, [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(paginatedRulesetRules.value, (item) => { - return (_openBlock(), _createBlock(_component_v_col, { - key: item.priority, - cols: "12" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - rounded: "lg", - elevation: "1" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, { class: "d-flex justify-space-between align-center" }, { - default: _withCtx(() => [ - _createElementVNode("div", null, [ - _createVNode(_component_v_icon, { - small: "", - class: "mr-1" - }, { - default: _withCtx(() => _cache[84] || (_cache[84] = [ - _createTextVNode("mdi-order-numeric-ascending") - ])), - _: 1 - }), - _createElementVNode("span", _hoisted_11, "优先级 " + _toDisplayString(item.priority), 1) - ]), - _createVNode(_component_v_chip, { - color: getActionColor(item.action), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.action), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_12, [ - _cache[85] || (_cache[85] = _createElementVNode("strong", null, "类型:", -1)), - _createTextVNode(_toDisplayString(item.type), 1) - ]), - _createElementVNode("div", _hoisted_13, [ - _cache[86] || (_cache[86] = _createElementVNode("strong", null, "内容:", -1)), - _createTextVNode(_toDisplayString(item.payload), 1) - ]), - _createElementVNode("div", _hoisted_14, [ - _cache[87] || (_cache[87] = _createElementVNode("strong", null, "规则:", -1)), - _createTextVNode(_toDisplayString(rulesetPrefix.value) + _toDisplayString(item.action), 1) - ]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_divider), - _createVNode(_component_v_card_actions, { class: "d-flex justify-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - icon: "", - color: "primary", - size: "small", - onClick: $event => (editRule(item.priority, 'ruleset')) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[88] || (_cache[88] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - icon: "", - color: "error", - size: "small", - onClick: $event => (deleteRule(item.priority, 'ruleset')) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[89] || (_cache[89] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024)) - }), 128)) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_15, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "2", - md: "1" - }), - _createVNode(_component_v_col, { - cols: "8", - md: "10", - class: "d-flex justify-center" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_pagination, { - modelValue: pageRuleset.value, - "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((pageRuleset).value = $event)), - length: pageCountRuleset.value, - "total-visible": "5", - rounded: "circle", - class: "d-none d-sm-flex my-0" - }, null, 8, ["modelValue", "length"]), - _createVNode(_component_v_pagination, { - modelValue: pageRuleset.value, - "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => ((pageRuleset).value = $event)), - length: pageCountRuleset.value, - "total-visible": "0", - rounded: "circle", - class: "d-sm-none my-0" - }, null, 8, ["modelValue", "length"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - md: "1", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_menu, null, { - activator: _withCtx(({ props }) => [ - _createVNode(_component_v_btn, _mergeProps(props, { - icon: "", - rounded: "circle", - variant: "tonal" - }), { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(pageTitle(itemsPerPageRuleset.value)), 1) - ]), - _: 2 - }, 1040) - ]), - default: _withCtx(() => [ - _createVNode(_component_v_list, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(itemsPerPageOptions.value, (item, index) => { - return (_openBlock(), _createBlock(_component_v_list_item, { - key: index, - value: item.value, - onClick: $event => (itemsPerPageRuleset.value = item.value) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_list_item_title, null, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.title), 1) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1032, ["value", "onClick"])) - }), 128)) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createVNode(_component_v_divider), - _cache[90] || (_cache[90] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " * 对规则集的修改会在 Clash 中立即生效。 ", -1)) - ]) - ]), - _: 1 - }), - _createVNode(_component_v_window_item, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_16, [ - _createElementVNode("div", _hoisted_17, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "8", - sm: "6", - class: "d-flex justify-start" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: searchTopRule.value, - "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => ((searchTopRule).value = $event)), - label: "搜索规则", - clearable: "", - density: "compact", - variant: "solo-filled", - "hide-details": "", - class: "search-field", - "prepend-inner-icon": "mdi-magnify", - flat: "", - rounded: "pill", - "single-line": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "4", - sm: "6", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - color: "secondary", - onClick: openImportRuleDialog, - icon: "", - class: "mr-2" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[91] || (_cache[91] = [ - _createTextVNode("mdi-import") - ])), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_btn, { - color: "primary", - onClick: _cache[9] || (_cache[9] = $event => (openAddRuleDialog('top'))), - icon: "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[92] || (_cache[92] = [ - _createTextVNode("mdi-plus") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_18, [ - _createVNode(_component_v_data_table, { - class: "px-4", - headers: headers.value, - search: searchTopRule.value, - items: sortedRules.value, - page: page.value, - "onUpdate:page": _cache[10] || (_cache[10] = $event => ((page).value = $event)), - "items-per-page": itemsPerPage.value, - "items-per-page-options": itemsPerPageOptions.value, - "item-key": "priority", - density: "compact", - "hide-default-footer": "", - "fixed-header": "" - }, { - item: _withCtx(({ item, index }) => [ - _createElementVNode("tr", { - class: _normalizeClass({ 'bg-blue-lighten-5': item._isHovered }), - draggable: "true", - onDragstart: $event => (dragStart($event, item.priority, 'top')), - onDragover: _withModifiers($event => (dragOver($event, item.priority, 'top')), ["prevent"]), - onDrop: $event => (drop($event, item.priority, 'top')) - }, [ - _createElementVNode("td", null, [ - _createVNode(_component_v_icon, { class: "drag-handle" }, { - default: _withCtx(() => _cache[93] || (_cache[93] = [ - _createTextVNode("mdi-drag") - ])), - _: 1 - }), - _createTextVNode(" " + _toDisplayString(item.priority), 1) - ]), - _createElementVNode("td", null, _toDisplayString(item.type), 1), - _createElementVNode("td", null, _toDisplayString(item.payload), 1), - _createElementVNode("td", null, [ - _createVNode(_component_v_chip, { - color: getActionColor(item.action), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.action), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - (item?.time_modified) - ? (_openBlock(), _createElementBlock("td", _hoisted_20, _toDisplayString(timestampToDate(item.time_modified)), 1)) - : (_openBlock(), _createElementBlock("td", _hoisted_21)), - _createElementVNode("td", null, [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (editRule(item.priority, 'top')), - disabled: isSystemRule(item) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[94] || (_cache[94] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteRule(item.priority, 'top')), - disabled: isSystemRule(item) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[95] || (_cache[95] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]), - (isSystemRule(item)) - ? (_openBlock(), _createBlock(_component_v_tooltip, { - key: 0, - activator: "parent", - location: "top" - }, { - default: _withCtx(() => _cache[96] || (_cache[96] = [ - _createTextVNode(" 根据规则集自动添加 ") - ])), - _: 1 - })) - : _createCommentVNode("", true) - ]) - ], 42, _hoisted_19) - ]), - _: 1 - }, 8, ["headers", "search", "items", "page", "items-per-page", "items-per-page-options"]) - ]), - _createElementVNode("div", _hoisted_22, [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(paginatedTopRules.value, (item) => { - return (_openBlock(), _createBlock(_component_v_col, { - key: item.priority, - cols: "12" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - rounded: "lg", - elevation: "1" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, { class: "d-flex justify-space-between align-center" }, { - default: _withCtx(() => [ - _createElementVNode("div", null, [ - _createVNode(_component_v_icon, { - small: "", - class: "mr-1" - }, { - default: _withCtx(() => _cache[97] || (_cache[97] = [ - _createTextVNode("mdi-order-numeric-ascending") - ])), - _: 1 - }), - _createElementVNode("span", _hoisted_23, "优先级 " + _toDisplayString(item.priority), 1) - ]), - _createVNode(_component_v_chip, { - color: getActionColor(item.action), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.action), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_24, [ - _cache[98] || (_cache[98] = _createElementVNode("strong", null, "类型:", -1)), - _createTextVNode(_toDisplayString(item.type), 1) - ]), - _createElementVNode("div", _hoisted_25, [ - _cache[99] || (_cache[99] = _createElementVNode("strong", null, "内容:", -1)), - _createTextVNode(_toDisplayString(item.payload), 1) - ]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_divider), - _createVNode(_component_v_card_actions, { class: "d-flex justify-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - icon: "", - color: "primary", - size: "small", - disabled: isSystemRule(item), - onClick: $event => (editRule(item.priority, 'top')) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[100] || (_cache[100] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["disabled", "onClick"]), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - icon: "", - color: "error", - size: "small", - onClick: $event => (deleteRule(item.priority, 'top')), - disabled: isSystemRule(item) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[101] || (_cache[101] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024)) - }), 128)) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_26, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "2", - md: "1" - }), - _createVNode(_component_v_col, { - cols: "8", - md: "10", - class: "d-flex justify-center" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_pagination, { - modelValue: page.value, - "onUpdate:modelValue": _cache[11] || (_cache[11] = $event => ((page).value = $event)), - length: pageCount.value, - "total-visible": "5", - rounded: "circle", - class: "d-none d-sm-flex my-0" - }, null, 8, ["modelValue", "length"]), - _createVNode(_component_v_pagination, { - modelValue: page.value, - "onUpdate:modelValue": _cache[12] || (_cache[12] = $event => ((page).value = $event)), - length: pageCount.value, - "total-visible": "0", - rounded: "circle", - class: "d-sm-none my-0" - }, null, 8, ["modelValue", "length"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - md: "1", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_menu, null, { - activator: _withCtx(({ props }) => [ - _createVNode(_component_v_btn, _mergeProps(props, { - icon: "", - rounded: "circle", - variant: "tonal" - }), { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(pageTitle(itemsPerPage.value)), 1) - ]), - _: 2 - }, 1040) - ]), - default: _withCtx(() => [ - _createVNode(_component_v_list, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(itemsPerPageOptions.value, (item, index) => { - return (_openBlock(), _createBlock(_component_v_list_item, { - key: index, - value: item.value, - onClick: $event => (itemsPerPage.value = item.value) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_list_item_title, null, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.title), 1) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1032, ["value", "onClick"])) - }), 128)) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createVNode(_component_v_divider), - _cache[102] || (_cache[102] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *置顶规则用于管理来自规则集的匹配规则,这些规则会动态更新。 ", -1)), - _cache[103] || (_cache[103] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *对置顶规则的修改只有Clash更新配置后才会生效。 ", -1)) - ]) - ]), - _: 1 - }), - _createVNode(_component_v_window_item, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_27, [ - _createElementVNode("div", _hoisted_28, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "10", - sm: "6", - class: "d-flex justify-start" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: searchProxyGroups.value, - "onUpdate:modelValue": _cache[13] || (_cache[13] = $event => ((searchProxyGroups).value = $event)), - label: "搜索代理组", - clearable: "", - density: "compact", - variant: "solo-filled", - "hide-details": "", - class: "search-field", - "prepend-inner-icon": "mdi-magnify", - flat: "", - rounded: "pill", - "single-line": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - sm: "6", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - color: "primary", - onClick: openAddProxyGroupDialog, - icon: "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[104] || (_cache[104] = [ - _createTextVNode("mdi-plus") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_29, [ - _createVNode(_component_v_data_table, { - class: "px-4", - headers: proxyGroupHeaders.value, - search: searchProxyGroups.value, - items: proxyGroups.value, - page: pageProxyGroup.value, - "onUpdate:page": _cache[14] || (_cache[14] = $event => ((pageProxyGroup).value = $event)), - "items-per-page": itemsPerPageProxyGroup.value, - "items-per-page-options": itemsPerPageOptions.value, - density: "compact", - "hide-default-footer": "", - "fixed-header": "", - "item-key": "name" - }, { - item: _withCtx(({ item, index }) => [ - _createElementVNode("tr", null, [ - _createElementVNode("td", null, _toDisplayString(item.name), 1), - _createElementVNode("td", null, [ - _createVNode(_component_v_chip, { - color: getProxyGroupTypeColor(item.type), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.type), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _createElementVNode("td", null, [ - (isManual(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 0, - color: "primary" - }, { - default: _withCtx(() => _cache[105] || (_cache[105] = [ - _createTextVNode("mdi-feather") - ])), - _: 1 - })) - : (isRegion(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 1, - color: "info" - }, { - default: _withCtx(() => _cache[106] || (_cache[106] = [ - _createTextVNode("mdi-earth") - ])), - _: 1 - })) - : (isTemplate(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 2, - color: "success" - }, { - default: _withCtx(() => _cache[107] || (_cache[107] = [ - _createTextVNode("mdi-file-code-outline") - ])), - _: 1 - })) - : (_openBlock(), _createBlock(_component_v_chip, { - key: 3, - size: "small", - label: "" - }, { - default: _withCtx(() => _cache[108] || (_cache[108] = [ - _createTextVNode("item.source") - ])), - _: 1 - })) - ]), - _createElementVNode("td", null, [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "info", - variant: "text", - onClick: $event => (showProxyGroupYaml(item)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[109] || (_cache[109] = [ - _createTextVNode("mdi-code-json") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (editProxyGroup(item.name)), - disabled: !(isManual(item.source)||isRegion(item.source)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[110] || (_cache[110] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteProxyGroup(item.name)), - disabled: !isManual(item.source) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[111] || (_cache[111] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]) - ]) - ]) - ]), - _: 1 - }, 8, ["headers", "search", "items", "page", "items-per-page", "items-per-page-options"]) - ]), - _createElementVNode("div", _hoisted_30, [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(paginatedProxyGroups.value, (item) => { - return (_openBlock(), _createBlock(_component_v_col, { - key: item.name, - cols: "12" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - rounded: "lg", - elevation: "1" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, { class: "d-flex justify-space-between align-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_row, { align: "center" }, { - default: _withCtx(() => [ - (isManual(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 0, - start: "", - size: "x-small", - color: "primary" - }, { - default: _withCtx(() => _cache[112] || (_cache[112] = [ - _createTextVNode("mdi-feather ") - ])), - _: 1 - })) - : (isRegion(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 1, - start: "", - size: "x-small", - color: "info" - }, { - default: _withCtx(() => _cache[113] || (_cache[113] = [ - _createTextVNode("mdi-earth ") - ])), - _: 1 - })) - : (isTemplate(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 2, - start: "", - size: "x-small", - color: "success" - }, { - default: _withCtx(() => _cache[114] || (_cache[114] = [ - _createTextVNode(" mdi-file-code-outline ") - ])), - _: 1 - })) - : (_openBlock(), _createBlock(_component_v_chip, { - key: 3, - size: "small", - label: "" - }, { - default: _withCtx(() => _cache[115] || (_cache[115] = [ - _createTextVNode("item.source") - ])), - _: 1 - })), - _createElementVNode("span", _hoisted_31, _toDisplayString(item.name), 1) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_chip, { - color: getProxyGroupTypeColor(item.type), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.type), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_32, [ - _cache[116] || (_cache[116] = _createElementVNode("strong", null, "类型:", -1)), - _createTextVNode(_toDisplayString(item.type), 1) - ]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_divider), - _createVNode(_component_v_card_actions, { class: "d-flex justify-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (editProxyGroup(item.name)), - disabled: !(isManual(item.source)||isRegion(item.source)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[117] || (_cache[117] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "info", - variant: "text", - onClick: $event => (showProxyGroupYaml(item)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[118] || (_cache[118] = [ - _createTextVNode("mdi-code-json") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteProxyGroup(item.name)), - disabled: !isManual(item.source) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[119] || (_cache[119] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024)) - }), 128)) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_33, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "2", - md: "1" - }), - _createVNode(_component_v_col, { - cols: "8", - md: "10", - class: "d-flex justify-center" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_pagination, { - modelValue: pageProxyGroup.value, - "onUpdate:modelValue": _cache[15] || (_cache[15] = $event => ((pageProxyGroup).value = $event)), - length: pageCountProxyGroups.value, - "total-visible": "5", - class: "d-none d-sm-flex my-0", - rounded: "circle" - }, null, 8, ["modelValue", "length"]), - _createVNode(_component_v_pagination, { - modelValue: pageProxyGroup.value, - "onUpdate:modelValue": _cache[16] || (_cache[16] = $event => ((pageProxyGroup).value = $event)), - length: pageCountProxyGroups.value, - "total-visible": "0", - class: "d-sm-none my-0", - rounded: "circle" - }, null, 8, ["modelValue", "length"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - md: "1", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_menu, null, { - activator: _withCtx(({ props }) => [ - _createVNode(_component_v_btn, _mergeProps(props, { - icon: "", - rounded: "circle", - variant: "tonal" - }), { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(pageTitle(itemsPerPageProxyGroup.value)), 1) - ]), - _: 2 - }, 1040) - ]), - default: _withCtx(() => [ - _createVNode(_component_v_list, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(itemsPerPageOptions.value, (item, index) => { - return (_openBlock(), _createBlock(_component_v_list_item, { - key: index, - value: item.value, - onClick: $event => (itemsPerPageProxyGroup.value = item.value) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_list_item_title, null, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.title), 1) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1032, ["value", "onClick"])) - }), 128)) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createVNode(_component_v_divider) - ]) - ]), - _: 1 - }), - _createVNode(_component_v_window_item, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_34, [ - _createElementVNode("div", _hoisted_35, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "10", - sm: "6", - class: "d-flex justify-start" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: searchExtraProxies.value, - "onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((searchExtraProxies).value = $event)), - label: "搜索出站代理", - clearable: "", - density: "compact", - variant: "solo-filled", - "hide-details": "", - class: "search-field", - "prepend-inner-icon": "mdi-magnify", - flat: "", - rounded: "pill", - "single-line": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - sm: "6", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - color: "primary", - onClick: openImportExtraProxiesDialog, - icon: "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[120] || (_cache[120] = [ - _createTextVNode("mdi-import") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_36, [ - _createVNode(_component_v_data_table, { - class: "px-4", - headers: extraProxiesHeaders.value, - search: searchExtraProxies.value, - items: extraProxies.value, - page: pageExtraProxies.value, - "onUpdate:page": _cache[18] || (_cache[18] = $event => ((pageExtraProxies).value = $event)), - "items-per-page": itemsPerPageExtraProxies.value, - "items-per-page-options": itemsPerPageOptions.value, - "item-key": "name", - density: "compact", - "hide-default-footer": "", - "fixed-header": "" - }, { - item: _withCtx(({ item, index }) => [ - _createElementVNode("tr", null, [ - _createElementVNode("td", null, [ - _createTextVNode(_toDisplayString(item.name) + " ", 1), - (item.v2ray_link) - ? (_openBlock(), _createBlock(_component_v_btn, { - key: 0, - icon: "", - size: "small", - color: "secondary", - variant: "text", - onClick: $event => (copyToClipboard(item.v2ray_link)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[121] || (_cache[121] = [ - _createTextVNode("mdi-link") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"])) - : _createCommentVNode("", true) - ]), - _createElementVNode("td", null, [ - _createVNode(_component_v_chip, { - color: getProxyColor(item.type), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.type), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _createElementVNode("td", null, _toDisplayString(item.server), 1), - _createElementVNode("td", null, _toDisplayString(item.port), 1), - _createElementVNode("td", null, [ - (isManual(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 0, - color: "primary" - }, { - default: _withCtx(() => _cache[122] || (_cache[122] = [ - _createTextVNode("mdi-feather") - ])), - _: 1 - })) - : (isTemplate(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 1, - color: "success" - }, { - default: _withCtx(() => _cache[123] || (_cache[123] = [ - _createTextVNode("mdi-file-code-outline") - ])), - _: 1 - })) - : (_openBlock(), _createBlock(_component_v_icon, { - key: 2, - color: "info" - }, { - default: _withCtx(() => _cache[124] || (_cache[124] = [ - _createTextVNode("mdi-file-download-outline") - ])), - _: 1 - })) - ]), - _createElementVNode("td", null, [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "info", - variant: "text", - onClick: $event => (showProxyGroupYaml(item)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[125] || (_cache[125] = [ - _createTextVNode("mdi-code-json") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (openProxiesDialog(item)) - }, { - default: _withCtx(() => [ - (item.overwritten) - ? (_openBlock(), _createBlock(_component_v_icon, { key: 0 }, { - default: _withCtx(() => _cache[126] || (_cache[126] = [ - _createTextVNode("mdi-wrench-check") - ])), - _: 1 - })) - : (_openBlock(), _createBlock(_component_v_icon, { key: 1 }, { - default: _withCtx(() => _cache[127] || (_cache[127] = [ - _createTextVNode("mdi-cog") - ])), - _: 1 - })) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteExtraProxies(item.name)), - disabled: !(isManual(item.source)||isInvalid(item.source)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[128] || (_cache[128] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]) - ]) - ]) - ]), - _: 1 - }, 8, ["headers", "search", "items", "page", "items-per-page", "items-per-page-options"]) - ]), - _createElementVNode("div", _hoisted_37, [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(paginatedExtraProxies.value, (item) => { - return (_openBlock(), _createBlock(_component_v_col, { - key: item.name, - cols: "12" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - rounded: "lg", - elevation: "1" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, { class: "d-flex justify-space-between align-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_row, { align: "center" }, { - default: _withCtx(() => [ - (isManual(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 0, - start: "", - size: "x-small", - color: "primary" - }, { - default: _withCtx(() => _cache[129] || (_cache[129] = [ - _createTextVNode("mdi-feather ") - ])), - _: 1 - })) - : (isTemplate(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 1, - start: "", - size: "x-small", - color: "success" - }, { - default: _withCtx(() => _cache[130] || (_cache[130] = [ - _createTextVNode(" mdi-file-code-outline ") - ])), - _: 1 - })) - : (_openBlock(), _createBlock(_component_v_icon, { - key: 2, - start: "", - size: "x-small", - color: "info" - }, { - default: _withCtx(() => _cache[131] || (_cache[131] = [ - _createTextVNode("mdi-file-download-outline") - ])), - _: 1 - })), - _createElementVNode("span", _hoisted_38, _toDisplayString(item.name), 1), - (item.v2ray_link) - ? (_openBlock(), _createBlock(_component_v_btn, { - key: 3, - icon: "", - size: "small", - color: "secondary", - variant: "text", - onClick: $event => (copyToClipboard(item.v2ray_link)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[132] || (_cache[132] = [ - _createTextVNode("mdi-link") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"])) - : _createCommentVNode("", true) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_chip, { - color: getProxyColor(item.type), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.type), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_39, [ - _cache[133] || (_cache[133] = _createElementVNode("strong", null, "类型:", -1)), - _createTextVNode(_toDisplayString(item.type), 1) - ]), - _createElementVNode("div", _hoisted_40, [ - _cache[134] || (_cache[134] = _createElementVNode("strong", null, "服务器:", -1)), - _createTextVNode(_toDisplayString(item.server), 1) - ]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_divider), - _createVNode(_component_v_card_actions, { class: "d-flex justify-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (openProxiesDialog(item)) - }, { - default: _withCtx(() => [ - (item.overwritten) - ? (_openBlock(), _createBlock(_component_v_icon, { key: 0 }, { - default: _withCtx(() => _cache[135] || (_cache[135] = [ - _createTextVNode("mdi-wrench-check") - ])), - _: 1 - })) - : (_openBlock(), _createBlock(_component_v_icon, { key: 1 }, { - default: _withCtx(() => _cache[136] || (_cache[136] = [ - _createTextVNode("mdi-cog") - ])), - _: 1 - })) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "info", - variant: "text", - onClick: $event => (showProxyGroupYaml(item)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[137] || (_cache[137] = [ - _createTextVNode("mdi-code-json") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteExtraProxies(item.name)), - disabled: !isManual(item.source) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[138] || (_cache[138] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024)) - }), 128)) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_41, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "2", - md: "1" - }), - _createVNode(_component_v_col, { - cols: "8", - md: "10", - class: "d-flex justify-center" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_pagination, { - modelValue: pageExtraProxies.value, - "onUpdate:modelValue": _cache[19] || (_cache[19] = $event => ((pageExtraProxies).value = $event)), - length: pageCountExtraProxies.value, - "total-visible": "5", - class: "d-none d-sm-flex my-0", - rounded: "circle" - }, null, 8, ["modelValue", "length"]), - _createVNode(_component_v_pagination, { - modelValue: pageExtraProxies.value, - "onUpdate:modelValue": _cache[20] || (_cache[20] = $event => ((pageExtraProxies).value = $event)), - length: pageCountExtraProxies.value, - "total-visible": "0", - class: "d-sm-none my-0", - rounded: "circle" - }, null, 8, ["modelValue", "length"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - md: "1", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_menu, null, { - activator: _withCtx(({ props }) => [ - _createVNode(_component_v_btn, _mergeProps(props, { - icon: "", - rounded: "circle", - variant: "tonal" - }), { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(pageTitle(itemsPerPageExtraProxies.value)), 1) - ]), - _: 2 - }, 1040) - ]), - default: _withCtx(() => [ - _createVNode(_component_v_list, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(itemsPerPageOptions.value, (item, index) => { - return (_openBlock(), _createBlock(_component_v_list_item, { - key: index, - value: item.value, - onClick: $event => (itemsPerPageExtraProxies.value = item.value) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_list_item_title, null, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.title), 1) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1032, ["value", "onClick"])) - }), 128)) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createVNode(_component_v_divider) - ]) - ]), - _: 1 - }), - _createVNode(_component_v_window_item, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_42, [ - _createElementVNode("div", _hoisted_43, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "10", - sm: "6", - class: "d-flex justify-start" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: searchRuleProviders.value, - "onUpdate:modelValue": _cache[21] || (_cache[21] = $event => ((searchRuleProviders).value = $event)), - label: "搜索规则集合", - clearable: "", - density: "compact", - variant: "solo-filled", - "hide-details": "", - class: "search-field", - "prepend-inner-icon": "mdi-magnify", - flat: "", - rounded: "pill", - "single-line": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - sm: "6", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - color: "primary", - onClick: openAddRuleProviderDialog, - icon: "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[139] || (_cache[139] = [ - _createTextVNode("mdi-plus") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_44, [ - _createVNode(_component_v_data_table, { - headers: headersRuleProviders.value, - items: extraRuleProviders.value, - search: searchRuleProviders.value, - page: pageRulProviders.value, - "onUpdate:page": _cache[22] || (_cache[22] = $event => ((pageRulProviders).value = $event)), - "items-per-page": itemsPerPageRuleProviders.value, - "items-per-page-options": itemsPerPageOptions.value, - "item-key": "name", - class: "px-4", - density: "compact", - "hide-default-footer": "", - "fixed-header": "" - }, { - item: _withCtx(({ item }) => [ - _createElementVNode("tr", null, [ - _createElementVNode("td", null, _toDisplayString(item.name), 1), - _createElementVNode("td", null, _toDisplayString(item.type), 1), - _createElementVNode("td", null, [ - _createVNode(_component_v_chip, { - color: getBehaviorColor(item.behavior), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.behavior), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _createElementVNode("td", null, _toDisplayString(item.format), 1), - _createElementVNode("td", null, [ - (isManual(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 0, - color: "primary" - }, { - default: _withCtx(() => _cache[140] || (_cache[140] = [ - _createTextVNode("mdi-feather") - ])), - _: 1 - })) - : (isTemplate(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 1, - color: "success" - }, { - default: _withCtx(() => _cache[141] || (_cache[141] = [ - _createTextVNode("mdi-file-code-outline") - ])), - _: 1 - })) - : (_openBlock(), _createBlock(_component_v_icon, { - key: 2, - color: "info" - }, { - default: _withCtx(() => _cache[142] || (_cache[142] = [ - _createTextVNode("mdi-file-download-outline") - ])), - _: 1 - })) - ]), - _createElementVNode("td", null, [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (editRuleProvider(item.name)), - disabled: !isManual(item.source) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[143] || (_cache[143] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteRuleProvider(item.name)), - disabled: !isManual(item.source) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[144] || (_cache[144] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]), - (!isManual(item.source)) - ? (_openBlock(), _createBlock(_component_v_tooltip, { - key: 0, - activator: "parent", - location: "top" - }, { - default: _withCtx(() => _cache[145] || (_cache[145] = [ - _createTextVNode(" 非手动添加 ") - ])), - _: 1 - })) - : _createCommentVNode("", true) - ]) - ]) - ]), - _: 1 - }, 8, ["headers", "items", "search", "page", "items-per-page", "items-per-page-options"]) - ]), - _createElementVNode("div", _hoisted_45, [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(paginatedExtraRuleProviders.value, (item) => { - return (_openBlock(), _createBlock(_component_v_col, { - key: item.name, - cols: "12" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - rounded: "lg", - elevation: "1" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, { class: "d-flex justify-space-between align-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_row, { align: "center" }, { - default: _withCtx(() => [ - (isManual(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 0, - start: "", - size: "x-small", - color: "primary" - }, { - default: _withCtx(() => _cache[146] || (_cache[146] = [ - _createTextVNode("mdi-feather ") - ])), - _: 1 - })) - : (isTemplate(item.source)) - ? (_openBlock(), _createBlock(_component_v_icon, { - key: 1, - start: "", - size: "x-small", - color: "success" - }, { - default: _withCtx(() => _cache[147] || (_cache[147] = [ - _createTextVNode(" mdi-file-code-outline ") - ])), - _: 1 - })) - : (_openBlock(), _createBlock(_component_v_icon, { - key: 2, - start: "", - size: "x-small", - color: "info" - }, { - default: _withCtx(() => _cache[148] || (_cache[148] = [ - _createTextVNode("mdi-file-download-outline") - ])), - _: 1 - })), - _createElementVNode("span", _hoisted_46, _toDisplayString(item.name), 1) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_chip, { - color: getBehaviorColor(item.behavior), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.behavior), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_47, [ - _cache[149] || (_cache[149] = _createElementVNode("strong", null, "类型:", -1)), - _createTextVNode(_toDisplayString(item.type), 1) - ]), - _createElementVNode("div", _hoisted_48, [ - _cache[150] || (_cache[150] = _createElementVNode("strong", null, "内容:", -1)), - _createTextVNode(_toDisplayString(item.format), 1) - ]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_divider), - _createVNode(_component_v_card_actions, { class: "d-flex justify-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (editRuleProvider(item.name)), - disabled: !isManual(item.source) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[151] || (_cache[151] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteRuleProvider(item.name)), - disabled: !isManual(item.source) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[152] || (_cache[152] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick", "disabled"]) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024)) - }), 128)) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_49, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "2", - md: "1" - }), - _createVNode(_component_v_col, { - cols: "8", - md: "10", - class: "d-flex justify-center" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_pagination, { - modelValue: pageRulProviders.value, - "onUpdate:modelValue": _cache[23] || (_cache[23] = $event => ((pageRulProviders).value = $event)), - length: pageCountExtraRuleProviders.value, - "total-visible": "5", - rounded: "circle", - class: "d-none d-sm-flex my-0" - }, null, 8, ["modelValue", "length"]), - _createVNode(_component_v_pagination, { - modelValue: pageRulProviders.value, - "onUpdate:modelValue": _cache[24] || (_cache[24] = $event => ((pageRulProviders).value = $event)), - length: pageCountExtraRuleProviders.value, - "total-visible": "0", - rounded: "circle", - class: "d-sm-none my-0" - }, null, 8, ["modelValue", "length"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - md: "1", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_menu, null, { - activator: _withCtx(({ props }) => [ - _createVNode(_component_v_btn, _mergeProps(props, { - icon: "", - rounded: "circle", - variant: "tonal" - }), { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(pageTitle(itemsPerPageRuleProviders.value)), 1) - ]), - _: 2 - }, 1040) - ]), - default: _withCtx(() => [ - _createVNode(_component_v_list, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(itemsPerPageOptions.value, (item, index) => { - return (_openBlock(), _createBlock(_component_v_list_item, { - key: index, - value: item.value, - onClick: $event => (itemsPerPageRuleProviders.value = item.value) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_list_item_title, null, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.title), 1) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1032, ["value", "onClick"])) - }), 128)) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createVNode(_component_v_divider) - ]) - ]), - _: 1 - }), - _createVNode(_component_v_window_item, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_50, [ - _createElementVNode("div", _hoisted_51, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "10", - sm: "6", - class: "d-flex justify-start" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: searchHosts.value, - "onUpdate:modelValue": _cache[25] || (_cache[25] = $event => ((searchHosts).value = $event)), - label: "搜索Hosts", - clearable: "", - density: "compact", - variant: "solo-filled", - "hide-details": "", - class: "search-field", - "prepend-inner-icon": "mdi-magnify", - flat: "", - rounded: "pill", - "single-line": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - sm: "6", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - color: "primary", - onClick: openAddHostDialog, - icon: "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[153] || (_cache[153] = [ - _createTextVNode("mdi-plus") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_52, [ - _createVNode(_component_v_data_table, { - headers: headersHosts.value, - items: hosts.value, - search: searchHosts.value, - page: pageHosts.value, - "onUpdate:page": _cache[26] || (_cache[26] = $event => ((pageHosts).value = $event)), - "items-per-page": itemsPerPageHosts.value, - "items-per-page-options": itemsPerPageOptions.value, - class: "px-4", - density: "compact", - "hide-default-footer": "", - "fixed-header": "", - "item-key": "domain" - }, { - item: _withCtx(({ item }) => [ - _createElementVNode("tr", null, [ - _createElementVNode("td", null, _toDisplayString(item.domain), 1), - _createElementVNode("td", null, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(item.value, (ip) => { - return (_openBlock(), _createBlock(_component_v_chip, { - key: ip, - size: "small", - class: "ma-1" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(ip), 1) - ]), - _: 2 - }, 1024)) - }), 128)) - ]), - _createElementVNode("td", null, [ - _createVNode(_component_v_chip, { - color: getBoolColor(item.using_cloudflare), - size: "small" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.using_cloudflare ? '是' : '否'), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _createElementVNode("td", null, [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (editHost(item.domain)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[154] || (_cache[154] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteHost(item.domain)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[155] || (_cache[155] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]) - ]) - ]) - ]), - _: 1 - }, 8, ["headers", "items", "search", "page", "items-per-page", "items-per-page-options"]) - ]), - _createElementVNode("div", _hoisted_53, [ - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(paginatedHosts.value, (item) => { - return (_openBlock(), _createBlock(_component_v_col, { - key: item.domain, - cols: "12" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - rounded: "lg", - elevation: "1" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, { class: "d-flex justify-space-between align-center" }, { - default: _withCtx(() => [ - _createElementVNode("div", null, [ - _createElementVNode("span", _hoisted_54, _toDisplayString(item.domain), 1) - ]), - _createVNode(_component_v_chip, { - color: getBoolColor(item.using_cloudflare), - size: "small", - label: "" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.using_cloudflare ? 'Cloudflare' : 'hosts'), 1) - ]), - _: 2 - }, 1032, ["color"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_divider), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_55, [ - _cache[156] || (_cache[156] = _createElementVNode("strong", null, "IP:", -1)), - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList((item.using_cloudflare? bestCloudflareIPs.value : item.value), (ip) => { - return (_openBlock(), _createBlock(_component_v_chip, { - key: ip, - size: "small", - class: "ma-1" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(ip), 1) - ]), - _: 2 - }, 1024)) - }), 128)) - ]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_divider), - _createVNode(_component_v_card_actions, { class: "d-flex justify-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "primary", - variant: "text", - onClick: $event => (editHost(item.domain)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[157] || (_cache[157] = [ - _createTextVNode("mdi-pencil") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - icon: "", - size: "small", - color: "error", - variant: "text", - onClick: $event => (deleteHost(item.domain)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[158] || (_cache[158] = [ - _createTextVNode("mdi-delete") - ])), - _: 1 - }) - ]), - _: 2 - }, 1032, ["onClick"]) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024)) - }), 128)) - ]), - _: 1 - }) - ]), - _createElementVNode("div", _hoisted_56, [ - _createVNode(_component_v_row, { - align: "center", - "no-gutters": "" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "2", - md: "1" - }), - _createVNode(_component_v_col, { - cols: "8", - md: "10", - class: "d-flex justify-center" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_pagination, { - modelValue: pageHosts.value, - "onUpdate:modelValue": _cache[27] || (_cache[27] = $event => ((pageHosts).value = $event)), - length: pageCountHosts.value, - "total-visible": "5", - rounded: "circle", - class: "d-none d-sm-flex my-0" - }, null, 8, ["modelValue", "length"]), - _createVNode(_component_v_pagination, { - modelValue: pageHosts.value, - "onUpdate:modelValue": _cache[28] || (_cache[28] = $event => ((pageHosts).value = $event)), - length: pageCountHosts.value, - "total-visible": "0", - rounded: "circle", - class: "d-sm-none my-0" - }, null, 8, ["modelValue", "length"]) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "2", - md: "1", - class: "d-flex justify-end" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_menu, null, { - activator: _withCtx(({ props }) => [ - _createVNode(_component_v_btn, _mergeProps(props, { - icon: "", - rounded: "circle", - variant: "tonal" - }), { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(pageTitle(itemsPerPageHosts.value)), 1) - ]), - _: 2 - }, 1040) - ]), - default: _withCtx(() => [ - _createVNode(_component_v_list, null, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(itemsPerPageOptions.value, (item, index) => { - return (_openBlock(), _createBlock(_component_v_list_item, { - key: index, - value: item.value, - onClick: $event => (itemsPerPageHosts.value = item.value) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_list_item_title, null, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(item.title), 1) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1032, ["value", "onClick"])) - }), 128)) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _createVNode(_component_v_divider) - ]) - ]), - _: 1 - }), - _createVNode(_component_v_window_item, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_57, [ - (Object.keys(subscriptionsInfo.value).length === 0) - ? (_openBlock(), _createBlock(_component_v_row, { - key: 0, - class: "pa-6 justify-center" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - class: "mx-auto text-center py-8 px-4", - "max-width": "400", - elevation: "10", - rounded: "xl", - style: {"background":"linear-gradient(135deg, #d6c355 0%, #fda085 100%)"} - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card_text, { class: "d-flex flex-column align-center" }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - size: "64", - color: "white", - class: "mb-4 bounce" - }, { - default: _withCtx(() => _cache[159] || (_cache[159] = [ - _createTextVNode(" mdi-emoticon-happy-outline ") - ])), - _: 1 - }), - _cache[161] || (_cache[161] = _createElementVNode("h2", { class: "text-h6 font-weight-bold white--text mb-2" }, " 还没有订阅呢 🎉 ", -1)), - _cache[162] || (_cache[162] = _createElementVNode("p", { class: "white--text mb-4" }, " 试试添加一个订阅吧! ", -1)), - _createVNode(_component_v_btn, { - color: "info", - dark: "", - rounded: "", - elevation: "6", - onClick: notifySwitch - }, { - default: _withCtx(() => _cache[160] || (_cache[160] = [ - _createTextVNode(" 去配置 🚀 ") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - })) - : _createCommentVNode("", true), - _createVNode(_component_v_row, { class: "pa-4" }, { - default: _withCtx(() => [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(subscriptionsInfo.value, (info, url, index) => { - return (_openBlock(), _createBlock(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - variant: "tonal", - class: "subscription-card mb-2 mt-2" - }, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_58, [ - _createVNode(_component_v_card_title, { class: "card-title" }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(extractDomain(url)), 1) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_card_action, null, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - class: "card-refresh-button", - onClick: $event => (updateSubscription(url)), - loading: refreshingSubscription.value[url] - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - left: "", - class: "mr-2" - }, { - default: _withCtx(() => _cache[163] || (_cache[163] = [ - _createTextVNode(" mdi-refresh ") - ])), - _: 1 - }), - _cache[164] || (_cache[164] = _createTextVNode(" 刷新 ")) - ]), - _: 2 - }, 1032, ["onClick", "loading"]) - ]), - _: 2 - }, 1024) - ]), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - _createElementVNode("div", _hoisted_59, [ - (info.proxy_num != null) - ? (_openBlock(), _createBlock(_component_v_chip, { - key: 0, - size: "small", - color: "info", - class: "mr-2" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { class: "mr-2" }, { - default: _withCtx(() => _cache[165] || (_cache[165] = [ - _createTextVNode(" mdi-server-network ") - ])), - _: 1 - }), - _createTextVNode(" 节点:" + _toDisplayString(info.proxy_num), 1) - ]), - _: 2 - }, 1024)) - : _createCommentVNode("", true), - (info.last_update) - ? (_openBlock(), _createBlock(_component_v_chip, { - key: 1, - size: "small", - color: "info", - class: "mr-2" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { class: "mr-2" }, { - default: _withCtx(() => _cache[166] || (_cache[166] = [ - _createTextVNode(" mdi-clock-outline ") - ])), - _: 1 - }), - _createTextVNode(" 更新:" + _toDisplayString(formatTimestamp(info.last_update)), 1) - ]), - _: 2 - }, 1024)) - : _createCommentVNode("", true), - (info.expire) - ? (_openBlock(), _createBlock(_component_v_chip, { - key: 2, - size: "small", - color: getExpireColor(info.expire) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { class: "mr-2" }, { - default: _withCtx(() => _cache[167] || (_cache[167] = [ - _createTextVNode(" mdi-calendar-clock ") - ])), - _: 1 - }), - _createTextVNode(" 到期:" + _toDisplayString(formatTimestamp(info.expire)), 1) - ]), - _: 2 - }, 1032, ["color"])) - : _createCommentVNode("", true) - ]), - _createElementVNode("div", _hoisted_60, [ - _cache[168] || (_cache[168] = _createElementVNode("span", null, "已用流量:", -1)), - _createElementVNode("strong", null, _toDisplayString(formatBytes(info.download + info.upload)), 1) - ]), - _createElementVNode("div", _hoisted_61, [ - _cache[169] || (_cache[169] = _createElementVNode("span", null, "剩余流量:", -1)), - _createElementVNode("strong", null, _toDisplayString(formatBytes(info.total - info.download)), 1) - ]), - _createVNode(_component_v_progress_linear, { - "model-value": info.used_percentage, - color: getUsageColor(info.used_percentage), - height: "10", - class: "mb-2", - rounded: "", - striped: "" - }, null, 8, ["model-value", "color"]), - _createElementVNode("div", _hoisted_62, [ - _createElementVNode("span", null, "下载:" + _toDisplayString(formatBytes(info.download)), 1), - _createElementVNode("span", null, "上传:" + _toDisplayString(formatBytes(info.upload)), 1), - _createElementVNode("span", null, "总量:" + _toDisplayString(formatBytes(info.total)), 1) - ]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_divider), - _createElementVNode("div", _hoisted_63, [ - _createVNode(_component_v_tooltip, { - location: "top", - text: "复制链接" - }, { - activator: _withCtx(({ props }) => [ - _createVNode(_component_v_btn, _mergeProps({ ref_for: true }, props, { - icon: "", - size: "small", - variant: "text", - onClick: $event => (copyToClipboard(url)) - }), { - default: _withCtx(() => [ - _createVNode(_component_v_icon, null, { - default: _withCtx(() => _cache[170] || (_cache[170] = [ - _createTextVNode("mdi-content-copy") - ])), - _: 1 - }) - ]), - _: 2 - }, 1040, ["onClick"]) - ]), - _: 2 - }, 1024), - _createVNode(_component_v_tooltip, { - location: "top", - text: "启用自动更新" - }, { - activator: _withCtx(({ props }) => [ - _createVNode(_component_v_switch, _mergeProps({ - modelValue: info.enabled, - "onUpdate:modelValue": $event => ((info.enabled) = $event), - ref_for: true - }, props, { - onChange: $event => (toggleSubscription(url, info.enabled)) - }), null, 16, ["modelValue", "onUpdate:modelValue", "onChange"]) - ]), - _: 2 - }, 1024) - ]) - ]), - _: 2 - }, 1024) - ]), - _: 2 - }, 1024)) - }), 256)) - ]), - _: 1 - }) - ]) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]) - ])) - ]), - _: 1 - }), - _createVNode(_component_v_expand_transition, null, { - default: _withCtx(() => [ - (expand.value) - ? (_openBlock(), _createElementBlock("div", _hoisted_64, [ - _createVNode(_component_v_row, { dense: "" }, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "6", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - class: "pa-4 d-flex flex-column align-center", - rounded: "xl" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - size: "40", - color: "primary" - }, { - default: _withCtx(() => _cache[171] || (_cache[171] = [ - _createTextVNode("mdi-format-list-bulleted") - ])), - _: 1 - }), - _createElementVNode("div", _hoisted_65, _toDisplayString(sortedRulesetRules.value.length), 1), - _cache[172] || (_cache[172] = _createElementVNode("div", { class: "text-subtitle-2 grey--text" }, "规则集规则", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "6", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - class: "pa-4 d-flex flex-column align-center", - rounded: "xl" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - size: "40", - color: "info" - }, { - default: _withCtx(() => _cache[173] || (_cache[173] = [ - _createTextVNode("mdi-pin") - ])), - _: 1 - }), - _createElementVNode("div", _hoisted_66, _toDisplayString(sortedRules.value.length), 1), - _cache[174] || (_cache[174] = _createElementVNode("div", { class: "text-subtitle-2 grey--text" }, "置顶规则", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "6", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - class: "pa-4 d-flex flex-column align-center", - rounded: "xl" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - size: "40", - color: "success" - }, { - default: _withCtx(() => _cache[175] || (_cache[175] = [ - _createTextVNode("mdi-source-branch") - ])), - _: 1 - }), - _createElementVNode("div", _hoisted_67, _toDisplayString(proxyGroups.value.length), 1), - _cache[176] || (_cache[176] = _createElementVNode("div", { class: "text-subtitle-2 grey--text" }, "代理组", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "6", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - class: "pa-4 d-flex flex-column align-center", - rounded: "xl" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - size: "40", - color: "error" - }, { - default: _withCtx(() => _cache[177] || (_cache[177] = [ - _createTextVNode("mdi-rocket-launch") - ])), - _: 1 - }), - _createElementVNode("div", _hoisted_68, _toDisplayString(extraProxies.value.length), 1), - _cache[178] || (_cache[178] = _createElementVNode("div", { class: "text-subtitle-2 grey--text" }, "出站代理", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "6", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - class: "pa-4 d-flex flex-column align-center", - rounded: "xl" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - size: "40", - color: "warning" - }, { - default: _withCtx(() => _cache[179] || (_cache[179] = [ - _createTextVNode("mdi-folder-multiple") - ])), - _: 1 - }), - _createElementVNode("div", _hoisted_69, _toDisplayString(extraRuleProviders.value.length), 1), - _cache[180] || (_cache[180] = _createElementVNode("div", { class: "text-subtitle-2 grey--text" }, "规则集合", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "6", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - class: "pa-4 d-flex flex-column align-center", - rounded: "xl" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - size: "40", - color: "secondary" - }, { - default: _withCtx(() => _cache[181] || (_cache[181] = [ - _createTextVNode("mdi-lan") - ])), - _: 1 - }), - _createElementVNode("div", _hoisted_70, _toDisplayString(hosts.value.length), 1), - _cache[182] || (_cache[182] = _createElementVNode("div", { class: "text-subtitle-2 grey--text" }, "Hosts", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "6", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - class: "pa-4 d-flex flex-column align-center", - rounded: "xl" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - size: "40", - color: "info" - }, { - default: _withCtx(() => _cache[183] || (_cache[183] = [ - _createTextVNode("mdi-earth") - ])), - _: 1 - }), - _createElementVNode("div", _hoisted_71, _toDisplayString(geoRules.value.geosite.length), 1), - _cache[184] || (_cache[184] = _createElementVNode("div", { class: "text-subtitle-2 grey--text" }, "Geosite", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_col, { - cols: "6", - md: "3" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, { - class: "pa-4 d-flex flex-column align-center", - rounded: "xl" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { - size: "40", - color: "success" - }, { - default: _withCtx(() => _cache[185] || (_cache[185] = [ - _createTextVNode("mdi-clock-time-four-outline") - ])), - _: 1 - }), - _createElementVNode("div", _hoisted_72, _toDisplayString(lastUpdated.value), 1), - _cache[186] || (_cache[186] = _createElementVNode("div", { class: "text-subtitle-2 grey--text" }, "最后更新", -1)) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ])) - : _createCommentVNode("", true) - ]), - _: 1 - }), - _createVNode(_component_v_card_actions, null, { - default: _withCtx(() => [ - _createVNode(_component_v_btn, { - color: "primary", - onClick: refreshData, - loading: loading.value - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[187] || (_cache[187] = [ - _createTextVNode("mdi-refresh") - ])), - _: 1 - }), - _cache[188] || (_cache[188] = _createTextVNode(" 刷新数据 ")) - ]), - _: 1 - }, 8, ["loading"]), - _createVNode(_component_v_btn, { - color: "info", - href: subUrl.value, - target: "_blank" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[189] || (_cache[189] = [ - _createTextVNode("mdi-link-variant") - ])), - _: 1 - }), - _cache[190] || (_cache[190] = _createTextVNode(" 生成链接 ")) - ]), - _: 1 - }, 8, ["href"]), - _createVNode(_component_v_btn, { - color: "success", - onClick: _cache[30] || (_cache[30] = $event => (expand.value = !expand.value)) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[191] || (_cache[191] = [ - _createTextVNode("mdi-chart-bar") - ])), - _: 1 - }), - _cache[192] || (_cache[192] = _createTextVNode(" 统计信息 ")) - ]), - _: 1 - }), - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - color: "primary", - onClick: notifySwitch - }, { - default: _withCtx(() => [ - _createVNode(_component_v_icon, { left: "" }, { - default: _withCtx(() => _cache[193] || (_cache[193] = [ - _createTextVNode("mdi-cog") - ])), - _: 1 - }), - _cache[194] || (_cache[194] = _createTextVNode(" 配置 ")) - ]), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_snackbar, { - modelValue: snackbar.value.show, - "onUpdate:modelValue": _cache[31] || (_cache[31] = $event => ((snackbar.value.show) = $event)), - color: snackbar.value.color, - location: "bottom", - class: "mb-2" - }, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(snackbar.value.message), 1) - ]), - _: 1 - }, 8, ["modelValue", "color"]) - ]), - _: 1 - }), - (ruleDialogVisible.value) - ? (_openBlock(), _createBlock(_sfc_main$2, { - key: 0, - "initial-rule": currentRule.value, - loading: saveRuleLoading.value, - "is-adding-rule": editingPriority.value === null, - "editing-type": editingType.value, - "rule-provider-names": ruleProviderNames.value, - "geo-ip": geoRules.value.geoip, - "geo-site": geoRules.value.geosite, - "custom-outbounds": customOutbounds.value, - onSave: saveRule, - showDialog: ruleDialogVisible.value, - "onUpdate:showDialog": _cache[32] || (_cache[32] = $event => ((ruleDialogVisible).value = $event)) - }, null, 8, ["initial-rule", "loading", "is-adding-rule", "editing-type", "rule-provider-names", "geo-ip", "geo-site", "custom-outbounds", "showDialog"])) - : _createCommentVNode("", true), - (proxyGroupDialogVisible.value) - ? (_openBlock(), _createBlock(_sfc_main$1, { - key: 1, - "initial-value": currentProxyGroup.value, - loading: saveProxyGroupLoading.value, - "is-adding": editingProxyGroupName.value === null, - "proxy-providers": proxyProviders.value, - "custom-outbounds": customOutbounds.value, - onSave: saveProxyGroups, - showDialog: proxyGroupDialogVisible.value, - "onUpdate:showDialog": _cache[33] || (_cache[33] = $event => ((proxyGroupDialogVisible).value = $event)) - }, null, 8, ["initial-value", "loading", "is-adding", "proxy-providers", "custom-outbounds", "showDialog"])) - : _createCommentVNode("", true), - _createVNode(_component_v_dialog, { - modelValue: yamlDialog.value, - "onUpdate:modelValue": _cache[37] || (_cache[37] = $event => ((yamlDialog).value = $event)), - "max-width": "40rem" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, null, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, { class: "headline" }, { - default: _withCtx(() => _cache[195] || (_cache[195] = [ - _createTextVNode("YAML 配置") - ])), - _: 1 - }), - _createVNode(_component_v_card_text, { style: {"max-height":"600px","overflow-y":"auto"} }, { - default: _withCtx(() => [ - _createVNode(_unref(VAceEditor), { - value: displayedYaml.value, - "onUpdate:value": _cache[34] || (_cache[34] = $event => ((displayedYaml).value = $event)), - lang: "yaml", - theme: "monokai", - options: readOnlyEditorOptions, - placeholder: rulesPlaceholder.value, - style: {"height":"30rem","width":"100%","margin-bottom":"16px"} - }, null, 8, ["value", "placeholder"]) - ]), - _: 1 - }), - _createVNode(_component_v_card_actions, null, { - default: _withCtx(() => [ - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - color: "primary", - onClick: _cache[35] || (_cache[35] = $event => (copyToClipboard(displayedYaml.value))) - }, { - default: _withCtx(() => _cache[196] || (_cache[196] = [ - _createTextVNode("复制") - ])), - _: 1 - }), - _createVNode(_component_v_btn, { - color: "primary", - onClick: _cache[36] || (_cache[36] = $event => (yamlDialog.value = false)) - }, { - default: _withCtx(() => _cache[197] || (_cache[197] = [ - _createTextVNode("关闭") - ])), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]), - _createVNode(_component_v_dialog, { - modelValue: importRuleDialog.value, - "onUpdate:modelValue": _cache[41] || (_cache[41] = $event => ((importRuleDialog).value = $event)), - "max-width": "40rem" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, null, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { - default: _withCtx(() => _cache[198] || (_cache[198] = [ - _createTextVNode("导入规则") - ])), - _: 1 - }), - _createVNode(_component_v_card_text, { style: {"max-height":"900px","overflow-y":"auto"} }, { - default: _withCtx(() => [ - _createVNode(_component_v_select, { - modelValue: importRules.value.type, - "onUpdate:modelValue": _cache[38] || (_cache[38] = $event => ((importRules.value.type) = $event)), - items: importRuleTypes, - label: "内容格式", - required: "", - class: "mb-4" - }, null, 8, ["modelValue"]), - _createVNode(_unref(VAceEditor), { - value: importRules.value.payload, - "onUpdate:value": _cache[39] || (_cache[39] = $event => ((importRules.value.payload) = $event)), - lang: "yaml", - theme: "monokai", - options: editorOptions, - placeholder: rulesPlaceholder.value, - style: {"height":"30rem","width":"100%","margin-bottom":"16px"} - }, null, 8, ["value", "placeholder"]), - _createVNode(_component_v_alert, { - type: "info", - dense: "", - text: "", - class: "mb-4", - variant: "tonal" - }, { - default: _withCtx(() => _cache[199] || (_cache[199] = [ - _createTextVNode(" 请输入 Clash 规则中的 "), - _createElementVNode("strong", null, "rules", -1), - _createTextVNode(" 字段,例如:"), - _createElementVNode("br", null, null, -1), - _createElementVNode("code", null, [ - _createTextVNode("rules:"), - _createElementVNode("br"), - _createTextVNode("- DOMAIN,gemini.google.com,Openai") - ], -1) - ])), - _: 1 - }) - ]), - _: 1 - }), - _createVNode(_component_v_card_actions, null, { - default: _withCtx(() => [ - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - color: "secondary", - onClick: _cache[40] || (_cache[40] = $event => (importRuleDialog.value = false)) - }, { - default: _withCtx(() => _cache[200] || (_cache[200] = [ - _createTextVNode("取消") - ])), - _: 1 - }), - _createVNode(_component_v_btn, { - color: "primary", - onClick: importRule, - loading: importRuleLoading.value - }, { - default: _withCtx(() => _cache[201] || (_cache[201] = [ - _createTextVNode("导入 ") - ])), - _: 1 - }, 8, ["loading"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]), - _createVNode(_component_v_dialog, { - modelValue: importExtraProxiesDialog.value, - "onUpdate:modelValue": _cache[46] || (_cache[46] = $event => ((importExtraProxiesDialog).value = $event)), - "max-width": "40rem" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, null, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { - default: _withCtx(() => _cache[202] || (_cache[202] = [ - _createTextVNode("导入节点") - ])), - _: 1 - }), - _createVNode(_component_v_card_text, { style: {"max-height":"900px","overflow-y":"auto"} }, { - default: _withCtx(() => [ - _createVNode(_component_v_select, { - modelValue: importExtraProxies.value.type, - "onUpdate:modelValue": _cache[42] || (_cache[42] = $event => ((importExtraProxies.value.type) = $event)), - items: importProxiesTypes, - label: "内容格式", - required: "", - class: "mb-4" - }, null, 8, ["modelValue"]), - (importExtraProxies.value.type === 'YAML') - ? (_openBlock(), _createBlock(_unref(VAceEditor), { - key: 0, - value: importExtraProxies.value.payload, - "onUpdate:value": _cache[43] || (_cache[43] = $event => ((importExtraProxies.value.payload) = $event)), - lang: "yaml", - theme: "monokai", - options: editorOptions, - placeholder: proxiesPlaceholder.value, - style: {"height":"30rem","width":"100%","margin-bottom":"16px"} - }, null, 8, ["value", "placeholder"])) - : (_openBlock(), _createBlock(_component_v_textarea, { - key: 1, - modelValue: importExtraProxies.value.payload, - "onUpdate:modelValue": _cache[44] || (_cache[44] = $event => ((importExtraProxies.value.payload) = $event)), - label: "内容", - required: "", - placeholder: importExtraProxiesPlaceholderText.value, - class: "mb-4", - rows: "4", - "auto-grow": "" - }, null, 8, ["modelValue", "placeholder"])), - (importExtraProxies.value.type === 'YAML') - ? (_openBlock(), _createBlock(_component_v_alert, { - key: 2, - type: "info", - dense: "", - text: "", - variant: "tonal" - }, { - default: _withCtx(() => _cache[203] || (_cache[203] = [ - _createTextVNode(" 请输入 Clash 规则中的 "), - _createElementVNode("strong", null, "proxies", -1), - _createTextVNode(" 字段,例如:"), - _createElementVNode("br", null, null, -1), - _createElementVNode("pre", { style: {"white-space":"pre-wrap","font-family":"monospace","margin":"0"} }, [ - _createTextVNode(""), - _createElementVNode("code", null, "proxies:\n - name: \"ss node\"\n type: \"ss\"") - ], -1) - ])), - _: 1 - })) - : _createCommentVNode("", true), - (importExtraProxies.value.type === 'LINK') - ? (_openBlock(), _createBlock(_component_v_alert, { - key: 3, - type: "info", - dense: "", - text: "", - variant: "tonal" - }, { - default: _withCtx(() => _cache[204] || (_cache[204] = [ - _createTextVNode(" 请输入 V2RayN 格式的分享链接,例如:"), - _createElementVNode("br", null, null, -1), - _createElementVNode("code", null, "vmess://xxxx", -1), - _createElementVNode("br", null, null, -1), - _createElementVNode("code", null, "ss://xxxx", -1) - ])), - _: 1 - })) - : _createCommentVNode("", true) - ]), - _: 1 - }), - _createVNode(_component_v_card_actions, null, { - default: _withCtx(() => [ - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - color: "secondary", - onClick: _cache[45] || (_cache[45] = $event => (importExtraProxiesDialog.value=false)) - }, { - default: _withCtx(() => _cache[205] || (_cache[205] = [ - _createTextVNode("取消") - ])), - _: 1 - }), - _createVNode(_component_v_btn, { - color: "primary", - onClick: importExtraProxiesFun, - loading: importProxiesLoading.value - }, { - default: _withCtx(() => _cache[206] || (_cache[206] = [ - _createTextVNode(" 导入 ") - ])), - _: 1 - }, 8, ["loading"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 8, ["modelValue"]), - _createVNode(_component_v_dialog, { - modelValue: ruleProviderDialog.value, - "onUpdate:modelValue": _cache[57] || (_cache[57] = $event => ((ruleProviderDialog).value = $event)), - "max-width": "40rem" - }, { - 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[47] || (_cache[47] = $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[48] || (_cache[48] = $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[49] || (_cache[49] = $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[50] || (_cache[50] = $event => ((newRuleProvider.value.path) = $event)), - label: "path", - required: "", - rules: [v => !!v || '当类型为文件时,路径不能为空'], - class: "mb-4", - hint: "文件路径,不填写时会使用 url 的 MD5 作为文件名" - }, null, 8, ["modelValue", "rules"])) - : _createCommentVNode("", true), - _createVNode(_component_v_text_field, { - modelValue: newRuleProvider.value.interval, - "onUpdate:modelValue": _cache[51] || (_cache[51] = $event => ((newRuleProvider.value.interval) = $event)), - modelModifiers: { number: true }, - label: "interval", - class: "mb-4", - type: "number", - min: "0", - suffix: "s", - hint: "Provider 的更新间隔", - clearable: "", - rules: [v => (v === null || v === undefined || v >= 0) || '更新间隔不能为负数'] - }, null, 8, ["modelValue", "rules"]), - _createVNode(_component_v_select, { - modelValue: newRuleProvider.value.behavior, - "onUpdate:modelValue": _cache[52] || (_cache[52] = $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[53] || (_cache[53] = $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[54] || (_cache[54] = $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[55] || (_cache[55] = $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[56] || (_cache[56] = $event => (ruleProviderDialog.value = false)) - }, { - default: _withCtx(() => _cache[207] || (_cache[207] = [ - _createTextVNode("取消") - ])), - _: 1 - }), - _createVNode(_component_v_btn, { - color: "primary", - type: "submit", - loading: saveRuleProviderLoading.value - }, { - default: _withCtx(() => _cache[208] || (_cache[208] = [ - _createTextVNode("保存 ") - ])), - _: 1 - }, 8, ["loading"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 512) - ]), - _: 1 - }, 8, ["modelValue"]), - _createVNode(_component_v_dialog, { - modelValue: hostDialog.value, - "onUpdate:modelValue": _cache[62] || (_cache[62] = $event => ((hostDialog).value = $event)), - "max-width": "40rem" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_form, { - ref_key: "hostForm", - ref: hostForm, - onSubmit: _withModifiers(saveHost, ["prevent"]) - }, { - default: _withCtx(() => [ - _createVNode(_component_v_card, null, { - default: _withCtx(() => [ - _createVNode(_component_v_card_title, null, { - default: _withCtx(() => [ - _createTextVNode(_toDisplayString(editingHostDomainName.value === null ? '添加 Host' : '编辑 Host'), 1) - ]), - _: 1 - }), - _createVNode(_component_v_card_text, null, { - default: _withCtx(() => [ - _createVNode(_component_v_text_field, { - modelValue: newHost.value.domain, - "onUpdate:modelValue": _cache[58] || (_cache[58] = $event => ((newHost.value.domain) = $event)), - label: "域名", - required: "", - rules: [v => !!v || '域名不能为空'], - class: "mb-4" - }, null, 8, ["modelValue", "rules"]), - (!newHost.value.using_cloudflare) - ? (_openBlock(), _createBlock(_component_v_combobox, { - key: 0, - modelValue: newHost.value.value, - "onUpdate:modelValue": _cache[59] || (_cache[59] = $event => ((newHost.value.value) = $event)), - multiple: "", - chips: "", - "closable-chips": "", - clearable: "", - label: "IP", - required: "", - rules: [validateIPs], - class: "mb-4", - hint: "一个或多个 IP 地址" - }, { - 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), - _createVNode(_component_v_row, null, { - default: _withCtx(() => [ - _createVNode(_component_v_col, { - cols: "12", - md: "6" - }, { - default: _withCtx(() => [ - _createVNode(_component_v_switch, { - modelValue: newHost.value.using_cloudflare, - "onUpdate:modelValue": _cache[60] || (_cache[60] = $event => ((newHost.value.using_cloudflare) = $event)), - label: "使用 Cloudflare CDN", - inset: "", - hint: "设置为 CF 优选 IPs", - "persistent-hint": "" - }, null, 8, ["modelValue"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }), - (bestCloudflareIPs.value.length === 0 && newHost.value.using_cloudflare) - ? (_openBlock(), _createBlock(_component_v_alert, { - key: 0, - type: "warning", - text: "", - variant: "outlined", - class: "mb-2" - }, { - default: _withCtx(() => _cache[209] || (_cache[209] = [ - _createTextVNode(" 请在「高级选项」配置 Cloudflare CDN 优选 IPs ") - ])), - _: 1 - })) - : _createCommentVNode("", true), - _createVNode(_component_v_alert, { - type: "info", - text: "", - variant: "tonal" - }, { - default: _withCtx(() => _cache[210] || (_cache[210] = [ - _createTextVNode(" 支持"), - _createElementVNode("a", { - href: "https://wiki.metacubex.one/handbook/syntax/#_8", - target: "_blank" - }, "域名通配符", -1) - ])), - _: 1 - }), - _createVNode(_component_v_card_actions, null, { - default: _withCtx(() => [ - _createVNode(_component_v_spacer), - _createVNode(_component_v_btn, { - color: "secondary", - onClick: _cache[61] || (_cache[61] = $event => (hostDialog.value = false)) - }, { - default: _withCtx(() => _cache[211] || (_cache[211] = [ - _createTextVNode("取消") - ])), - _: 1 - }), - _createVNode(_component_v_btn, { - color: "primary", - type: "submit", - loading: saveHostLoading.value - }, { - default: _withCtx(() => _cache[212] || (_cache[212] = [ - _createTextVNode("保存 ") - ])), - _: 1 - }, 8, ["loading"]) - ]), - _: 1 - }) - ]), - _: 1 - }) - ]), - _: 1 - }, 512) - ]), - _: 1 - }, 8, ["modelValue"]), - (proxiesDialogVisible.value) - ? (_openBlock(), _createBlock(ProxiesDialog, { - key: 2, - modelValue: proxiesDialogVisible.value, - "onUpdate:modelValue": _cache[63] || (_cache[63] = $event => ((proxiesDialogVisible).value = $event)), - "proxy-data": editingProxy.value, - loading: saveProxyLoading.value, - onSave: saveProxy - }, null, 8, ["modelValue", "proxy-data", "loading"])) - : _createCommentVNode("", true) - ])) -} -} - -}; -const PageComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-67d1defe"]]); - -export { PageComponent as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Dx-0nC8K.css b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Dx-0nC8K.css deleted file mode 100644 index 389c4ee..0000000 --- a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Dx-0nC8K.css +++ /dev/null @@ -1,70 +0,0 @@ - - -.plugin-page[data-v-67d1defe] { - margin: 0 auto; -} - -/* 使卡片等宽并适应移动端 */ -.d-flex.flex-wrap[data-v-67d1defe] { - gap: 16px; -} - -/* 移动端堆叠布局 */ -@media (max-width: 768px) { -.d-flex.flex-wrap[data-v-67d1defe] { - flex-direction: column; -} -} -.drag-handle[data-v-67d1defe] { - cursor: move; -} -.toggle-container[data-v-67d1defe] { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.625rem; - margin-left: 0.75rem; - margin-right: 0.75rem; -} -.subscription-card[data-v-67d1defe] { - border-radius: 12px; - overflow: hidden; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - transition: transform 0.3s, box-shadow 0.3s; - background: white; -} -.subscription-card[data-v-67d1defe]:hover { - transform: translateY(-5px); - box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); -} -.card-title[data-v-67d1defe] { - color: whitesmoke; -} -.card-header[data-v-67d1defe] { - padding: 0.625rem; - background: linear-gradient(135deg, rgba(var(--v-theme-primary), 1) 0%, rgba(var(--v-theme-primary), 0.7) 100%); - color: white; - display: flex; - justify-content: space-between; - align-items: center; -} -.card-refresh-button[data-v-67d1defe] { - background-color: rgba(var(--v-theme-primary), 0.9); - color: whitesmoke; - border: none; - border-radius: 6px; - padding: 0.625rem; - font-weight: 500; - cursor: pointer; - display: flex; - align-items: center; - transition: background-color 0.3s; -} -.search-field[data-v-67d1defe] { - max-width: 25rem; -} -.clash-data-table[data-v-67d1defe] { - max-height: 40rem; - overflow-y: auto; -} - diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-KIk8A7rq.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-KIk8A7rq.js new file mode 100644 index 0000000..9faeeb3 --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-KIk8A7rq.js @@ -0,0 +1,14224 @@ +import { importShared } from './__federation_fn_import-JrT3xvdd.js'; +import { V as VAceEditor, c as commonjsGlobal, g as getDefaultExportFromCjs } from './theme-monokai-CF_yROe-.js'; +import { i as isSystemRule, t as timestampToDate, g as getRuleTypeColor, a as getActionColor, _ as _export_sfc, p as pageTitle, b as isManual, c as isRegion, d as getSourceColor, e as getProxyGroupTypeColor, f as isValidUrl, h as isInvalid, j as getProxyColor, k as extractDomain, l as formatTimestamp, m as getExpireColor, n as formatBytes, o as getUsageColor, q as getUsedPercentageFloor, r as getFormatColor, s as getBehaviorColor, u as getBoolColor, v as validateIPs } from './_plugin-vue_export-helper-D32QZFxh.js'; +import { M as MetaLogo } from './Meta-1zu2nKV2.js'; + +/*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */ +function isNothing(subject) { + return (typeof subject === 'undefined') || (subject === null); +} + + +function isObject$1(subject) { + return (typeof subject === 'object') && (subject !== null); +} + + +function toArray(sequence) { + if (Array.isArray(sequence)) return sequence; + else if (isNothing(sequence)) return []; + + return [ sequence ]; +} + + +function extend(target, source) { + var index, length, key, sourceKeys; + + if (source) { + sourceKeys = Object.keys(source); + + for (index = 0, length = sourceKeys.length; index < length; index += 1) { + key = sourceKeys[index]; + target[key] = source[key]; + } + } + + return target; +} + + +function repeat(string, count) { + var result = '', cycle; + + for (cycle = 0; cycle < count; cycle += 1) { + result += string; + } + + return result; +} + + +function isNegativeZero(number) { + return (number === 0) && (Number.NEGATIVE_INFINITY === 1 / number); +} + + +var isNothing_1 = isNothing; +var isObject_1 = isObject$1; +var toArray_1 = toArray; +var repeat_1 = repeat; +var isNegativeZero_1 = isNegativeZero; +var extend_1 = extend; + +var common = { + isNothing: isNothing_1, + isObject: isObject_1, + toArray: toArray_1, + repeat: repeat_1, + isNegativeZero: isNegativeZero_1, + extend: extend_1 +}; + +// YAML error class. http://stackoverflow.com/questions/8458984 + + +function formatError(exception, compact) { + var where = '', message = exception.reason || '(unknown reason)'; + + if (!exception.mark) return message; + + if (exception.mark.name) { + where += 'in "' + exception.mark.name + '" '; + } + + where += '(' + (exception.mark.line + 1) + ':' + (exception.mark.column + 1) + ')'; + + if (!compact && exception.mark.snippet) { + where += '\n\n' + exception.mark.snippet; + } + + return message + ' ' + where; +} + + +function YAMLException$1(reason, mark) { + // Super constructor + Error.call(this); + + this.name = 'YAMLException'; + this.reason = reason; + this.mark = mark; + this.message = formatError(this, false); + + // Include stack trace in error object + if (Error.captureStackTrace) { + // Chrome and NodeJS + Error.captureStackTrace(this, this.constructor); + } else { + // FF, IE 10+ and Safari 6+. Fallback for others + this.stack = (new Error()).stack || ''; + } +} + + +// Inherit from Error +YAMLException$1.prototype = Object.create(Error.prototype); +YAMLException$1.prototype.constructor = YAMLException$1; + + +YAMLException$1.prototype.toString = function toString(compact) { + return this.name + ': ' + formatError(this, compact); +}; + + +var exception = YAMLException$1; + +// get snippet for a single line, respecting maxLength +function getLine(buffer, lineStart, lineEnd, position, maxLineLength) { + var head = ''; + var tail = ''; + var maxHalfLength = Math.floor(maxLineLength / 2) - 1; + + if (position - lineStart > maxHalfLength) { + head = ' ... '; + lineStart = position - maxHalfLength + head.length; + } + + if (lineEnd - position > maxHalfLength) { + tail = ' ...'; + lineEnd = position + maxHalfLength - tail.length; + } + + return { + str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, '→') + tail, + pos: position - lineStart + head.length // relative position + }; +} + + +function padStart(string, max) { + return common.repeat(' ', max - string.length) + string; +} + + +function makeSnippet(mark, options) { + options = Object.create(options || null); + + if (!mark.buffer) return null; + + if (!options.maxLength) options.maxLength = 79; + if (typeof options.indent !== 'number') options.indent = 1; + if (typeof options.linesBefore !== 'number') options.linesBefore = 3; + if (typeof options.linesAfter !== 'number') options.linesAfter = 2; + + var re = /\r?\n|\r|\0/g; + var lineStarts = [ 0 ]; + var lineEnds = []; + var match; + var foundLineNo = -1; + + while ((match = re.exec(mark.buffer))) { + lineEnds.push(match.index); + lineStarts.push(match.index + match[0].length); + + if (mark.position <= match.index && foundLineNo < 0) { + foundLineNo = lineStarts.length - 2; + } + } + + if (foundLineNo < 0) foundLineNo = lineStarts.length - 1; + + var result = '', i, line; + var lineNoLength = Math.min(mark.line + options.linesAfter, lineEnds.length).toString().length; + var maxLineLength = options.maxLength - (options.indent + lineNoLength + 3); + + for (i = 1; i <= options.linesBefore; i++) { + if (foundLineNo - i < 0) break; + line = getLine( + mark.buffer, + lineStarts[foundLineNo - i], + lineEnds[foundLineNo - i], + mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo - i]), + maxLineLength + ); + result = common.repeat(' ', options.indent) + padStart((mark.line - i + 1).toString(), lineNoLength) + + ' | ' + line.str + '\n' + result; + } + + line = getLine(mark.buffer, lineStarts[foundLineNo], lineEnds[foundLineNo], mark.position, maxLineLength); + result += common.repeat(' ', options.indent) + padStart((mark.line + 1).toString(), lineNoLength) + + ' | ' + line.str + '\n'; + result += common.repeat('-', options.indent + lineNoLength + 3 + line.pos) + '^' + '\n'; + + for (i = 1; i <= options.linesAfter; i++) { + if (foundLineNo + i >= lineEnds.length) break; + line = getLine( + mark.buffer, + lineStarts[foundLineNo + i], + lineEnds[foundLineNo + i], + mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo + i]), + maxLineLength + ); + result += common.repeat(' ', options.indent) + padStart((mark.line + i + 1).toString(), lineNoLength) + + ' | ' + line.str + '\n'; + } + + return result.replace(/\n$/, ''); +} + + +var snippet = makeSnippet; + +var TYPE_CONSTRUCTOR_OPTIONS = [ + 'kind', + 'multi', + 'resolve', + 'construct', + 'instanceOf', + 'predicate', + 'represent', + 'representName', + 'defaultStyle', + 'styleAliases' +]; + +var YAML_NODE_KINDS = [ + 'scalar', + 'sequence', + 'mapping' +]; + +function compileStyleAliases(map) { + var result = {}; + + if (map !== null) { + Object.keys(map).forEach(function (style) { + map[style].forEach(function (alias) { + result[String(alias)] = style; + }); + }); + } + + return result; +} + +function Type$1(tag, options) { + options = options || {}; + + Object.keys(options).forEach(function (name) { + if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { + throw new exception('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); + } + }); + + // TODO: Add tag format check. + this.options = options; // keep original options in case user wants to extend this type later + this.tag = tag; + this.kind = options['kind'] || null; + this.resolve = options['resolve'] || function () { return true; }; + this.construct = options['construct'] || function (data) { return data; }; + this.instanceOf = options['instanceOf'] || null; + this.predicate = options['predicate'] || null; + this.represent = options['represent'] || null; + this.representName = options['representName'] || null; + this.defaultStyle = options['defaultStyle'] || null; + this.multi = options['multi'] || false; + this.styleAliases = compileStyleAliases(options['styleAliases'] || null); + + if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { + throw new exception('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); + } +} + +var type = Type$1; + +/*eslint-disable max-len*/ + + + + + +function compileList(schema, name) { + var result = []; + + schema[name].forEach(function (currentType) { + var newIndex = result.length; + + result.forEach(function (previousType, previousIndex) { + if (previousType.tag === currentType.tag && + previousType.kind === currentType.kind && + previousType.multi === currentType.multi) { + + newIndex = previousIndex; + } + }); + + result[newIndex] = currentType; + }); + + return result; +} + + +function compileMap(/* lists... */) { + var result = { + scalar: {}, + sequence: {}, + mapping: {}, + fallback: {}, + multi: { + scalar: [], + sequence: [], + mapping: [], + fallback: [] + } + }, index, length; + + function collectType(type) { + if (type.multi) { + result.multi[type.kind].push(type); + result.multi['fallback'].push(type); + } else { + result[type.kind][type.tag] = result['fallback'][type.tag] = type; + } + } + + for (index = 0, length = arguments.length; index < length; index += 1) { + arguments[index].forEach(collectType); + } + return result; +} + + +function Schema$1(definition) { + return this.extend(definition); +} + + +Schema$1.prototype.extend = function extend(definition) { + var implicit = []; + var explicit = []; + + if (definition instanceof type) { + // Schema.extend(type) + explicit.push(definition); + + } else if (Array.isArray(definition)) { + // Schema.extend([ type1, type2, ... ]) + explicit = explicit.concat(definition); + + } else if (definition && (Array.isArray(definition.implicit) || Array.isArray(definition.explicit))) { + // Schema.extend({ explicit: [ type1, type2, ... ], implicit: [ type1, type2, ... ] }) + if (definition.implicit) implicit = implicit.concat(definition.implicit); + if (definition.explicit) explicit = explicit.concat(definition.explicit); + + } else { + throw new exception('Schema.extend argument should be a Type, [ Type ], ' + + 'or a schema definition ({ implicit: [...], explicit: [...] })'); + } + + implicit.forEach(function (type$1) { + if (!(type$1 instanceof type)) { + throw new exception('Specified list of YAML types (or a single Type object) contains a non-Type object.'); + } + + if (type$1.loadKind && type$1.loadKind !== 'scalar') { + throw new exception('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.'); + } + + if (type$1.multi) { + throw new exception('There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.'); + } + }); + + explicit.forEach(function (type$1) { + if (!(type$1 instanceof type)) { + throw new exception('Specified list of YAML types (or a single Type object) contains a non-Type object.'); + } + }); + + var result = Object.create(Schema$1.prototype); + + result.implicit = (this.implicit || []).concat(implicit); + result.explicit = (this.explicit || []).concat(explicit); + + result.compiledImplicit = compileList(result, 'implicit'); + result.compiledExplicit = compileList(result, 'explicit'); + result.compiledTypeMap = compileMap(result.compiledImplicit, result.compiledExplicit); + + return result; +}; + + +var schema = Schema$1; + +var str = new type('tag:yaml.org,2002:str', { + kind: 'scalar', + construct: function (data) { return data !== null ? data : ''; } +}); + +var seq = new type('tag:yaml.org,2002:seq', { + kind: 'sequence', + construct: function (data) { return data !== null ? data : []; } +}); + +var map = new type('tag:yaml.org,2002:map', { + kind: 'mapping', + construct: function (data) { return data !== null ? data : {}; } +}); + +var failsafe = new schema({ + explicit: [ + str, + seq, + map + ] +}); + +function resolveYamlNull(data) { + if (data === null) return true; + + var max = data.length; + + return (max === 1 && data === '~') || + (max === 4 && (data === 'null' || data === 'Null' || data === 'NULL')); +} + +function constructYamlNull() { + return null; +} + +function isNull(object) { + return object === null; +} + +var _null = new type('tag:yaml.org,2002:null', { + kind: 'scalar', + resolve: resolveYamlNull, + construct: constructYamlNull, + predicate: isNull, + represent: { + canonical: function () { return '~'; }, + lowercase: function () { return 'null'; }, + uppercase: function () { return 'NULL'; }, + camelcase: function () { return 'Null'; }, + empty: function () { return ''; } + }, + defaultStyle: 'lowercase' +}); + +function resolveYamlBoolean(data) { + if (data === null) return false; + + var max = data.length; + + return (max === 4 && (data === 'true' || data === 'True' || data === 'TRUE')) || + (max === 5 && (data === 'false' || data === 'False' || data === 'FALSE')); +} + +function constructYamlBoolean(data) { + return data === 'true' || + data === 'True' || + data === 'TRUE'; +} + +function isBoolean(object) { + return Object.prototype.toString.call(object) === '[object Boolean]'; +} + +var bool = new type('tag:yaml.org,2002:bool', { + kind: 'scalar', + resolve: resolveYamlBoolean, + construct: constructYamlBoolean, + predicate: isBoolean, + represent: { + lowercase: function (object) { return object ? 'true' : 'false'; }, + uppercase: function (object) { return object ? 'TRUE' : 'FALSE'; }, + camelcase: function (object) { return object ? 'True' : 'False'; } + }, + defaultStyle: 'lowercase' +}); + +function isHexCode(c) { + return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) || + ((0x41/* A */ <= c) && (c <= 0x46/* F */)) || + ((0x61/* a */ <= c) && (c <= 0x66/* f */)); +} + +function isOctCode(c) { + return ((0x30/* 0 */ <= c) && (c <= 0x37/* 7 */)); +} + +function isDecCode(c) { + return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)); +} + +function resolveYamlInteger(data) { + if (data === null) return false; + + var max = data.length, + index = 0, + hasDigits = false, + ch; + + if (!max) return false; + + ch = data[index]; + + // sign + if (ch === '-' || ch === '+') { + ch = data[++index]; + } + + if (ch === '0') { + // 0 + if (index + 1 === max) return true; + ch = data[++index]; + + // base 2, base 8, base 16 + + if (ch === 'b') { + // base 2 + index++; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (ch !== '0' && ch !== '1') return false; + hasDigits = true; + } + return hasDigits && ch !== '_'; + } + + + if (ch === 'x') { + // base 16 + index++; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (!isHexCode(data.charCodeAt(index))) return false; + hasDigits = true; + } + return hasDigits && ch !== '_'; + } + + + if (ch === 'o') { + // base 8 + index++; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (!isOctCode(data.charCodeAt(index))) return false; + hasDigits = true; + } + return hasDigits && ch !== '_'; + } + } + + // base 10 (except 0) + + // value should not start with `_`; + if (ch === '_') return false; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (!isDecCode(data.charCodeAt(index))) { + return false; + } + hasDigits = true; + } + + // Should have digits and should not end with `_` + if (!hasDigits || ch === '_') return false; + + return true; +} + +function constructYamlInteger(data) { + var value = data, sign = 1, ch; + + if (value.indexOf('_') !== -1) { + value = value.replace(/_/g, ''); + } + + ch = value[0]; + + if (ch === '-' || ch === '+') { + if (ch === '-') sign = -1; + value = value.slice(1); + ch = value[0]; + } + + if (value === '0') return 0; + + if (ch === '0') { + if (value[1] === 'b') return sign * parseInt(value.slice(2), 2); + if (value[1] === 'x') return sign * parseInt(value.slice(2), 16); + if (value[1] === 'o') return sign * parseInt(value.slice(2), 8); + } + + return sign * parseInt(value, 10); +} + +function isInteger(object) { + return (Object.prototype.toString.call(object)) === '[object Number]' && + (object % 1 === 0 && !common.isNegativeZero(object)); +} + +var int = new type('tag:yaml.org,2002:int', { + kind: 'scalar', + resolve: resolveYamlInteger, + construct: constructYamlInteger, + predicate: isInteger, + represent: { + binary: function (obj) { return obj >= 0 ? '0b' + obj.toString(2) : '-0b' + obj.toString(2).slice(1); }, + octal: function (obj) { return obj >= 0 ? '0o' + obj.toString(8) : '-0o' + obj.toString(8).slice(1); }, + decimal: function (obj) { return obj.toString(10); }, + /* eslint-disable max-len */ + hexadecimal: function (obj) { return obj >= 0 ? '0x' + obj.toString(16).toUpperCase() : '-0x' + obj.toString(16).toUpperCase().slice(1); } + }, + defaultStyle: 'decimal', + styleAliases: { + binary: [ 2, 'bin' ], + octal: [ 8, 'oct' ], + decimal: [ 10, 'dec' ], + hexadecimal: [ 16, 'hex' ] + } +}); + +var YAML_FLOAT_PATTERN = new RegExp( + // 2.5e4, 2.5 and integers + '^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?' + + // .2e4, .2 + // special case, seems not from spec + '|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?' + + // .inf + '|[-+]?\\.(?:inf|Inf|INF)' + + // .nan + '|\\.(?:nan|NaN|NAN))$'); + +function resolveYamlFloat(data) { + if (data === null) return false; + + if (!YAML_FLOAT_PATTERN.test(data) || + // Quick hack to not allow integers end with `_` + // Probably should update regexp & check speed + data[data.length - 1] === '_') { + return false; + } + + return true; +} + +function constructYamlFloat(data) { + var value, sign; + + value = data.replace(/_/g, '').toLowerCase(); + sign = value[0] === '-' ? -1 : 1; + + if ('+-'.indexOf(value[0]) >= 0) { + value = value.slice(1); + } + + if (value === '.inf') { + return (sign === 1) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; + + } else if (value === '.nan') { + return NaN; + } + return sign * parseFloat(value, 10); +} + + +var SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; + +function representYamlFloat(object, style) { + var res; + + if (isNaN(object)) { + switch (style) { + case 'lowercase': return '.nan'; + case 'uppercase': return '.NAN'; + case 'camelcase': return '.NaN'; + } + } else if (Number.POSITIVE_INFINITY === object) { + switch (style) { + case 'lowercase': return '.inf'; + case 'uppercase': return '.INF'; + case 'camelcase': return '.Inf'; + } + } else if (Number.NEGATIVE_INFINITY === object) { + switch (style) { + case 'lowercase': return '-.inf'; + case 'uppercase': return '-.INF'; + case 'camelcase': return '-.Inf'; + } + } else if (common.isNegativeZero(object)) { + return '-0.0'; + } + + res = object.toString(10); + + // JS stringifier can build scientific format without dots: 5e-100, + // while YAML requres dot: 5.e-100. Fix it with simple hack + + return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace('e', '.e') : res; +} + +function isFloat(object) { + return (Object.prototype.toString.call(object) === '[object Number]') && + (object % 1 !== 0 || common.isNegativeZero(object)); +} + +var float = new type('tag:yaml.org,2002:float', { + kind: 'scalar', + resolve: resolveYamlFloat, + construct: constructYamlFloat, + predicate: isFloat, + represent: representYamlFloat, + defaultStyle: 'lowercase' +}); + +var json = failsafe.extend({ + implicit: [ + _null, + bool, + int, + float + ] +}); + +var core = json; + +var YAML_DATE_REGEXP = new RegExp( + '^([0-9][0-9][0-9][0-9])' + // [1] year + '-([0-9][0-9])' + // [2] month + '-([0-9][0-9])$'); // [3] day + +var YAML_TIMESTAMP_REGEXP = new RegExp( + '^([0-9][0-9][0-9][0-9])' + // [1] year + '-([0-9][0-9]?)' + // [2] month + '-([0-9][0-9]?)' + // [3] day + '(?:[Tt]|[ \\t]+)' + // ... + '([0-9][0-9]?)' + // [4] hour + ':([0-9][0-9])' + // [5] minute + ':([0-9][0-9])' + // [6] second + '(?:\\.([0-9]*))?' + // [7] fraction + '(?:[ \\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour + '(?::([0-9][0-9]))?))?$'); // [11] tz_minute + +function resolveYamlTimestamp(data) { + if (data === null) return false; + if (YAML_DATE_REGEXP.exec(data) !== null) return true; + if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true; + return false; +} + +function constructYamlTimestamp(data) { + var match, year, month, day, hour, minute, second, fraction = 0, + delta = null, tz_hour, tz_minute, date; + + match = YAML_DATE_REGEXP.exec(data); + if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data); + + if (match === null) throw new Error('Date resolve error'); + + // match: [1] year [2] month [3] day + + year = +(match[1]); + month = +(match[2]) - 1; // JS month starts with 0 + day = +(match[3]); + + if (!match[4]) { // no hour + return new Date(Date.UTC(year, month, day)); + } + + // match: [4] hour [5] minute [6] second [7] fraction + + hour = +(match[4]); + minute = +(match[5]); + second = +(match[6]); + + if (match[7]) { + fraction = match[7].slice(0, 3); + while (fraction.length < 3) { // milli-seconds + fraction += '0'; + } + fraction = +fraction; + } + + // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute + + if (match[9]) { + tz_hour = +(match[10]); + tz_minute = +(match[11] || 0); + delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds + if (match[9] === '-') delta = -delta; + } + + date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction)); + + if (delta) date.setTime(date.getTime() - delta); + + return date; +} + +function representYamlTimestamp(object /*, style*/) { + return object.toISOString(); +} + +var timestamp = new type('tag:yaml.org,2002:timestamp', { + kind: 'scalar', + resolve: resolveYamlTimestamp, + construct: constructYamlTimestamp, + instanceOf: Date, + represent: representYamlTimestamp +}); + +function resolveYamlMerge(data) { + return data === '<<' || data === null; +} + +var merge = new type('tag:yaml.org,2002:merge', { + kind: 'scalar', + resolve: resolveYamlMerge +}); + +/*eslint-disable no-bitwise*/ + + + + + +// [ 64, 65, 66 ] -> [ padding, CR, LF ] +var BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r'; + + +function resolveYamlBinary(data) { + if (data === null) return false; + + var code, idx, bitlen = 0, max = data.length, map = BASE64_MAP; + + // Convert one by one. + for (idx = 0; idx < max; idx++) { + code = map.indexOf(data.charAt(idx)); + + // Skip CR/LF + if (code > 64) continue; + + // Fail on illegal characters + if (code < 0) return false; + + bitlen += 6; + } + + // If there are any bits left, source was corrupted + return (bitlen % 8) === 0; +} + +function constructYamlBinary(data) { + var idx, tailbits, + input = data.replace(/[\r\n=]/g, ''), // remove CR/LF & padding to simplify scan + max = input.length, + map = BASE64_MAP, + bits = 0, + result = []; + + // Collect by 6*4 bits (3 bytes) + + for (idx = 0; idx < max; idx++) { + if ((idx % 4 === 0) && idx) { + result.push((bits >> 16) & 0xFF); + result.push((bits >> 8) & 0xFF); + result.push(bits & 0xFF); + } + + bits = (bits << 6) | map.indexOf(input.charAt(idx)); + } + + // Dump tail + + tailbits = (max % 4) * 6; + + if (tailbits === 0) { + result.push((bits >> 16) & 0xFF); + result.push((bits >> 8) & 0xFF); + result.push(bits & 0xFF); + } else if (tailbits === 18) { + result.push((bits >> 10) & 0xFF); + result.push((bits >> 2) & 0xFF); + } else if (tailbits === 12) { + result.push((bits >> 4) & 0xFF); + } + + return new Uint8Array(result); +} + +function representYamlBinary(object /*, style*/) { + var result = '', bits = 0, idx, tail, + max = object.length, + map = BASE64_MAP; + + // Convert every three bytes to 4 ASCII characters. + + for (idx = 0; idx < max; idx++) { + if ((idx % 3 === 0) && idx) { + result += map[(bits >> 18) & 0x3F]; + result += map[(bits >> 12) & 0x3F]; + result += map[(bits >> 6) & 0x3F]; + result += map[bits & 0x3F]; + } + + bits = (bits << 8) + object[idx]; + } + + // Dump tail + + tail = max % 3; + + if (tail === 0) { + result += map[(bits >> 18) & 0x3F]; + result += map[(bits >> 12) & 0x3F]; + result += map[(bits >> 6) & 0x3F]; + result += map[bits & 0x3F]; + } else if (tail === 2) { + result += map[(bits >> 10) & 0x3F]; + result += map[(bits >> 4) & 0x3F]; + result += map[(bits << 2) & 0x3F]; + result += map[64]; + } else if (tail === 1) { + result += map[(bits >> 2) & 0x3F]; + result += map[(bits << 4) & 0x3F]; + result += map[64]; + result += map[64]; + } + + return result; +} + +function isBinary(obj) { + return Object.prototype.toString.call(obj) === '[object Uint8Array]'; +} + +var binary = new type('tag:yaml.org,2002:binary', { + kind: 'scalar', + resolve: resolveYamlBinary, + construct: constructYamlBinary, + predicate: isBinary, + represent: representYamlBinary +}); + +var _hasOwnProperty$3 = Object.prototype.hasOwnProperty; +var _toString$2 = Object.prototype.toString; + +function resolveYamlOmap(data) { + if (data === null) return true; + + var objectKeys = [], index, length, pair, pairKey, pairHasKey, + object = data; + + for (index = 0, length = object.length; index < length; index += 1) { + pair = object[index]; + pairHasKey = false; + + if (_toString$2.call(pair) !== '[object Object]') return false; + + for (pairKey in pair) { + if (_hasOwnProperty$3.call(pair, pairKey)) { + if (!pairHasKey) pairHasKey = true; + else return false; + } + } + + if (!pairHasKey) return false; + + if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey); + else return false; + } + + return true; +} + +function constructYamlOmap(data) { + return data !== null ? data : []; +} + +var omap = new type('tag:yaml.org,2002:omap', { + kind: 'sequence', + resolve: resolveYamlOmap, + construct: constructYamlOmap +}); + +var _toString$1 = Object.prototype.toString; + +function resolveYamlPairs(data) { + if (data === null) return true; + + var index, length, pair, keys, result, + object = data; + + result = new Array(object.length); + + for (index = 0, length = object.length; index < length; index += 1) { + pair = object[index]; + + if (_toString$1.call(pair) !== '[object Object]') return false; + + keys = Object.keys(pair); + + if (keys.length !== 1) return false; + + result[index] = [ keys[0], pair[keys[0]] ]; + } + + return true; +} + +function constructYamlPairs(data) { + if (data === null) return []; + + var index, length, pair, keys, result, + object = data; + + result = new Array(object.length); + + for (index = 0, length = object.length; index < length; index += 1) { + pair = object[index]; + + keys = Object.keys(pair); + + result[index] = [ keys[0], pair[keys[0]] ]; + } + + return result; +} + +var pairs = new type('tag:yaml.org,2002:pairs', { + kind: 'sequence', + resolve: resolveYamlPairs, + construct: constructYamlPairs +}); + +var _hasOwnProperty$2 = Object.prototype.hasOwnProperty; + +function resolveYamlSet(data) { + if (data === null) return true; + + var key, object = data; + + for (key in object) { + if (_hasOwnProperty$2.call(object, key)) { + if (object[key] !== null) return false; + } + } + + return true; +} + +function constructYamlSet(data) { + return data !== null ? data : {}; +} + +var set = new type('tag:yaml.org,2002:set', { + kind: 'mapping', + resolve: resolveYamlSet, + construct: constructYamlSet +}); + +var _default = core.extend({ + implicit: [ + timestamp, + merge + ], + explicit: [ + binary, + omap, + pairs, + set + ] +}); + +/*eslint-disable max-len,no-use-before-define*/ + + + + + + + +var _hasOwnProperty$1 = Object.prototype.hasOwnProperty; + + +var CONTEXT_FLOW_IN = 1; +var CONTEXT_FLOW_OUT = 2; +var CONTEXT_BLOCK_IN = 3; +var CONTEXT_BLOCK_OUT = 4; + + +var CHOMPING_CLIP = 1; +var CHOMPING_STRIP = 2; +var CHOMPING_KEEP = 3; + + +var PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; +var PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/; +var PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/; +var PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i; +var PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i; + + +function _class(obj) { return Object.prototype.toString.call(obj); } + +function is_EOL(c) { + return (c === 0x0A/* LF */) || (c === 0x0D/* CR */); +} + +function is_WHITE_SPACE(c) { + return (c === 0x09/* Tab */) || (c === 0x20/* Space */); +} + +function is_WS_OR_EOL(c) { + return (c === 0x09/* Tab */) || + (c === 0x20/* Space */) || + (c === 0x0A/* LF */) || + (c === 0x0D/* CR */); +} + +function is_FLOW_INDICATOR(c) { + return c === 0x2C/* , */ || + c === 0x5B/* [ */ || + c === 0x5D/* ] */ || + c === 0x7B/* { */ || + c === 0x7D/* } */; +} + +function fromHexCode(c) { + var lc; + + if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { + return c - 0x30; + } + + /*eslint-disable no-bitwise*/ + lc = c | 0x20; + + if ((0x61/* a */ <= lc) && (lc <= 0x66/* f */)) { + return lc - 0x61 + 10; + } + + return -1; +} + +function escapedHexLen(c) { + if (c === 0x78/* x */) { return 2; } + if (c === 0x75/* u */) { return 4; } + if (c === 0x55/* U */) { return 8; } + return 0; +} + +function fromDecimalCode(c) { + if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { + return c - 0x30; + } + + return -1; +} + +function simpleEscapeSequence(c) { + /* eslint-disable indent */ + return (c === 0x30/* 0 */) ? '\x00' : + (c === 0x61/* a */) ? '\x07' : + (c === 0x62/* b */) ? '\x08' : + (c === 0x74/* t */) ? '\x09' : + (c === 0x09/* Tab */) ? '\x09' : + (c === 0x6E/* n */) ? '\x0A' : + (c === 0x76/* v */) ? '\x0B' : + (c === 0x66/* f */) ? '\x0C' : + (c === 0x72/* r */) ? '\x0D' : + (c === 0x65/* e */) ? '\x1B' : + (c === 0x20/* Space */) ? ' ' : + (c === 0x22/* " */) ? '\x22' : + (c === 0x2F/* / */) ? '/' : + (c === 0x5C/* \ */) ? '\x5C' : + (c === 0x4E/* N */) ? '\x85' : + (c === 0x5F/* _ */) ? '\xA0' : + (c === 0x4C/* L */) ? '\u2028' : + (c === 0x50/* P */) ? '\u2029' : ''; +} + +function charFromCodepoint(c) { + if (c <= 0xFFFF) { + return String.fromCharCode(c); + } + // Encode UTF-16 surrogate pair + // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF + return String.fromCharCode( + ((c - 0x010000) >> 10) + 0xD800, + ((c - 0x010000) & 0x03FF) + 0xDC00 + ); +} + +// set a property of a literal object, while protecting against prototype pollution, +// see https://github.com/nodeca/js-yaml/issues/164 for more details +function setProperty(object, key, value) { + // used for this specific key only because Object.defineProperty is slow + if (key === '__proto__') { + Object.defineProperty(object, key, { + configurable: true, + enumerable: true, + writable: true, + value: value + }); + } else { + object[key] = value; + } +} + +var simpleEscapeCheck = new Array(256); // integer, for fast access +var simpleEscapeMap = new Array(256); +for (var i = 0; i < 256; i++) { + simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0; + simpleEscapeMap[i] = simpleEscapeSequence(i); +} + + +function State$1(input, options) { + this.input = input; + + this.filename = options['filename'] || null; + this.schema = options['schema'] || _default; + this.onWarning = options['onWarning'] || null; + // (Hidden) Remove? makes the loader to expect YAML 1.1 documents + // if such documents have no explicit %YAML directive + this.legacy = options['legacy'] || false; + + this.json = options['json'] || false; + this.listener = options['listener'] || null; + + this.implicitTypes = this.schema.compiledImplicit; + this.typeMap = this.schema.compiledTypeMap; + + this.length = input.length; + this.position = 0; + this.line = 0; + this.lineStart = 0; + this.lineIndent = 0; + + // position of first leading tab in the current line, + // used to make sure there are no tabs in the indentation + this.firstTabInLine = -1; + + this.documents = []; + + /* + this.version; + this.checkLineBreaks; + this.tagMap; + this.anchorMap; + this.tag; + this.anchor; + this.kind; + this.result;*/ + +} + + +function generateError(state, message) { + var mark = { + name: state.filename, + buffer: state.input.slice(0, -1), // omit trailing \0 + position: state.position, + line: state.line, + column: state.position - state.lineStart + }; + + mark.snippet = snippet(mark); + + return new exception(message, mark); +} + +function throwError(state, message) { + throw generateError(state, message); +} + +function throwWarning(state, message) { + if (state.onWarning) { + state.onWarning.call(null, generateError(state, message)); + } +} + + +var directiveHandlers = { + + YAML: function handleYamlDirective(state, name, args) { + + var match, major, minor; + + if (state.version !== null) { + throwError(state, 'duplication of %YAML directive'); + } + + if (args.length !== 1) { + throwError(state, 'YAML directive accepts exactly one argument'); + } + + match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]); + + if (match === null) { + throwError(state, 'ill-formed argument of the YAML directive'); + } + + major = parseInt(match[1], 10); + minor = parseInt(match[2], 10); + + if (major !== 1) { + throwError(state, 'unacceptable YAML version of the document'); + } + + state.version = args[0]; + state.checkLineBreaks = (minor < 2); + + if (minor !== 1 && minor !== 2) { + throwWarning(state, 'unsupported YAML version of the document'); + } + }, + + TAG: function handleTagDirective(state, name, args) { + + var handle, prefix; + + if (args.length !== 2) { + throwError(state, 'TAG directive accepts exactly two arguments'); + } + + handle = args[0]; + prefix = args[1]; + + if (!PATTERN_TAG_HANDLE.test(handle)) { + throwError(state, 'ill-formed tag handle (first argument) of the TAG directive'); + } + + if (_hasOwnProperty$1.call(state.tagMap, handle)) { + throwError(state, 'there is a previously declared suffix for "' + handle + '" tag handle'); + } + + if (!PATTERN_TAG_URI.test(prefix)) { + throwError(state, 'ill-formed tag prefix (second argument) of the TAG directive'); + } + + try { + prefix = decodeURIComponent(prefix); + } catch (err) { + throwError(state, 'tag prefix is malformed: ' + prefix); + } + + state.tagMap[handle] = prefix; + } +}; + + +function captureSegment(state, start, end, checkJson) { + var _position, _length, _character, _result; + + if (start < end) { + _result = state.input.slice(start, end); + + if (checkJson) { + for (_position = 0, _length = _result.length; _position < _length; _position += 1) { + _character = _result.charCodeAt(_position); + if (!(_character === 0x09 || + (0x20 <= _character && _character <= 0x10FFFF))) { + throwError(state, 'expected valid JSON character'); + } + } + } else if (PATTERN_NON_PRINTABLE.test(_result)) { + throwError(state, 'the stream contains non-printable characters'); + } + + state.result += _result; + } +} + +function mergeMappings(state, destination, source, overridableKeys) { + var sourceKeys, key, index, quantity; + + if (!common.isObject(source)) { + throwError(state, 'cannot merge mappings; the provided source object is unacceptable'); + } + + sourceKeys = Object.keys(source); + + for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) { + key = sourceKeys[index]; + + if (!_hasOwnProperty$1.call(destination, key)) { + setProperty(destination, key, source[key]); + overridableKeys[key] = true; + } + } +} + +function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, + startLine, startLineStart, startPos) { + + var index, quantity; + + // The output is a plain object here, so keys can only be strings. + // We need to convert keyNode to a string, but doing so can hang the process + // (deeply nested arrays that explode exponentially using aliases). + if (Array.isArray(keyNode)) { + keyNode = Array.prototype.slice.call(keyNode); + + for (index = 0, quantity = keyNode.length; index < quantity; index += 1) { + if (Array.isArray(keyNode[index])) { + throwError(state, 'nested arrays are not supported inside keys'); + } + + if (typeof keyNode === 'object' && _class(keyNode[index]) === '[object Object]') { + keyNode[index] = '[object Object]'; + } + } + } + + // Avoid code execution in load() via toString property + // (still use its own toString for arrays, timestamps, + // and whatever user schema extensions happen to have @@toStringTag) + if (typeof keyNode === 'object' && _class(keyNode) === '[object Object]') { + keyNode = '[object Object]'; + } + + + keyNode = String(keyNode); + + if (_result === null) { + _result = {}; + } + + if (keyTag === 'tag:yaml.org,2002:merge') { + if (Array.isArray(valueNode)) { + for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { + mergeMappings(state, _result, valueNode[index], overridableKeys); + } + } else { + mergeMappings(state, _result, valueNode, overridableKeys); + } + } else { + if (!state.json && + !_hasOwnProperty$1.call(overridableKeys, keyNode) && + _hasOwnProperty$1.call(_result, keyNode)) { + state.line = startLine || state.line; + state.lineStart = startLineStart || state.lineStart; + state.position = startPos || state.position; + throwError(state, 'duplicated mapping key'); + } + + setProperty(_result, keyNode, valueNode); + delete overridableKeys[keyNode]; + } + + return _result; +} + +function readLineBreak(state) { + var ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x0A/* LF */) { + state.position++; + } else if (ch === 0x0D/* CR */) { + state.position++; + if (state.input.charCodeAt(state.position) === 0x0A/* LF */) { + state.position++; + } + } else { + throwError(state, 'a line break is expected'); + } + + state.line += 1; + state.lineStart = state.position; + state.firstTabInLine = -1; +} + +function skipSeparationSpace(state, allowComments, checkIndent) { + var lineBreaks = 0, + ch = state.input.charCodeAt(state.position); + + while (ch !== 0) { + while (is_WHITE_SPACE(ch)) { + if (ch === 0x09/* Tab */ && state.firstTabInLine === -1) { + state.firstTabInLine = state.position; + } + ch = state.input.charCodeAt(++state.position); + } + + if (allowComments && ch === 0x23/* # */) { + do { + ch = state.input.charCodeAt(++state.position); + } while (ch !== 0x0A/* LF */ && ch !== 0x0D/* CR */ && ch !== 0); + } + + if (is_EOL(ch)) { + readLineBreak(state); + + ch = state.input.charCodeAt(state.position); + lineBreaks++; + state.lineIndent = 0; + + while (ch === 0x20/* Space */) { + state.lineIndent++; + ch = state.input.charCodeAt(++state.position); + } + } else { + break; + } + } + + if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) { + throwWarning(state, 'deficient indentation'); + } + + return lineBreaks; +} + +function testDocumentSeparator(state) { + var _position = state.position, + ch; + + ch = state.input.charCodeAt(_position); + + // Condition state.position === state.lineStart is tested + // in parent on each call, for efficiency. No needs to test here again. + if ((ch === 0x2D/* - */ || ch === 0x2E/* . */) && + ch === state.input.charCodeAt(_position + 1) && + ch === state.input.charCodeAt(_position + 2)) { + + _position += 3; + + ch = state.input.charCodeAt(_position); + + if (ch === 0 || is_WS_OR_EOL(ch)) { + return true; + } + } + + return false; +} + +function writeFoldedLines(state, count) { + if (count === 1) { + state.result += ' '; + } else if (count > 1) { + state.result += common.repeat('\n', count - 1); + } +} + + +function readPlainScalar(state, nodeIndent, withinFlowCollection) { + var preceding, + following, + captureStart, + captureEnd, + hasPendingContent, + _line, + _lineStart, + _lineIndent, + _kind = state.kind, + _result = state.result, + ch; + + ch = state.input.charCodeAt(state.position); + + if (is_WS_OR_EOL(ch) || + is_FLOW_INDICATOR(ch) || + ch === 0x23/* # */ || + ch === 0x26/* & */ || + ch === 0x2A/* * */ || + ch === 0x21/* ! */ || + ch === 0x7C/* | */ || + ch === 0x3E/* > */ || + ch === 0x27/* ' */ || + ch === 0x22/* " */ || + ch === 0x25/* % */ || + ch === 0x40/* @ */ || + ch === 0x60/* ` */) { + return false; + } + + if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following) || + withinFlowCollection && is_FLOW_INDICATOR(following)) { + return false; + } + } + + state.kind = 'scalar'; + state.result = ''; + captureStart = captureEnd = state.position; + hasPendingContent = false; + + while (ch !== 0) { + if (ch === 0x3A/* : */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following) || + withinFlowCollection && is_FLOW_INDICATOR(following)) { + break; + } + + } else if (ch === 0x23/* # */) { + preceding = state.input.charCodeAt(state.position - 1); + + if (is_WS_OR_EOL(preceding)) { + break; + } + + } else if ((state.position === state.lineStart && testDocumentSeparator(state)) || + withinFlowCollection && is_FLOW_INDICATOR(ch)) { + break; + + } else if (is_EOL(ch)) { + _line = state.line; + _lineStart = state.lineStart; + _lineIndent = state.lineIndent; + skipSeparationSpace(state, false, -1); + + if (state.lineIndent >= nodeIndent) { + hasPendingContent = true; + ch = state.input.charCodeAt(state.position); + continue; + } else { + state.position = captureEnd; + state.line = _line; + state.lineStart = _lineStart; + state.lineIndent = _lineIndent; + break; + } + } + + if (hasPendingContent) { + captureSegment(state, captureStart, captureEnd, false); + writeFoldedLines(state, state.line - _line); + captureStart = captureEnd = state.position; + hasPendingContent = false; + } + + if (!is_WHITE_SPACE(ch)) { + captureEnd = state.position + 1; + } + + ch = state.input.charCodeAt(++state.position); + } + + captureSegment(state, captureStart, captureEnd, false); + + if (state.result) { + return true; + } + + state.kind = _kind; + state.result = _result; + return false; +} + +function readSingleQuotedScalar(state, nodeIndent) { + var ch, + captureStart, captureEnd; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x27/* ' */) { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + state.position++; + captureStart = captureEnd = state.position; + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + if (ch === 0x27/* ' */) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x27/* ' */) { + captureStart = state.position; + state.position++; + captureEnd = state.position; + } else { + return true; + } + + } else if (is_EOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; + + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + throwError(state, 'unexpected end of the document within a single quoted scalar'); + + } else { + state.position++; + captureEnd = state.position; + } + } + + throwError(state, 'unexpected end of the stream within a single quoted scalar'); +} + +function readDoubleQuotedScalar(state, nodeIndent) { + var captureStart, + captureEnd, + hexLength, + hexResult, + tmp, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x22/* " */) { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + state.position++; + captureStart = captureEnd = state.position; + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + if (ch === 0x22/* " */) { + captureSegment(state, captureStart, state.position, true); + state.position++; + return true; + + } else if (ch === 0x5C/* \ */) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); + + if (is_EOL(ch)) { + skipSeparationSpace(state, false, nodeIndent); + + // TODO: rework to inline fn with no type cast? + } else if (ch < 256 && simpleEscapeCheck[ch]) { + state.result += simpleEscapeMap[ch]; + state.position++; + + } else if ((tmp = escapedHexLen(ch)) > 0) { + hexLength = tmp; + hexResult = 0; + + for (; hexLength > 0; hexLength--) { + ch = state.input.charCodeAt(++state.position); + + if ((tmp = fromHexCode(ch)) >= 0) { + hexResult = (hexResult << 4) + tmp; + + } else { + throwError(state, 'expected hexadecimal character'); + } + } + + state.result += charFromCodepoint(hexResult); + + state.position++; + + } else { + throwError(state, 'unknown escape sequence'); + } + + captureStart = captureEnd = state.position; + + } else if (is_EOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; + + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + throwError(state, 'unexpected end of the document within a double quoted scalar'); + + } else { + state.position++; + captureEnd = state.position; + } + } + + throwError(state, 'unexpected end of the stream within a double quoted scalar'); +} + +function readFlowCollection(state, nodeIndent) { + var readNext = true, + _line, + _lineStart, + _pos, + _tag = state.tag, + _result, + _anchor = state.anchor, + following, + terminator, + isPair, + isExplicitPair, + isMapping, + overridableKeys = Object.create(null), + keyNode, + keyTag, + valueNode, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x5B/* [ */) { + terminator = 0x5D;/* ] */ + isMapping = false; + _result = []; + } else if (ch === 0x7B/* { */) { + terminator = 0x7D;/* } */ + isMapping = true; + _result = {}; + } else { + return false; + } + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } + + ch = state.input.charCodeAt(++state.position); + + while (ch !== 0) { + skipSeparationSpace(state, true, nodeIndent); + + ch = state.input.charCodeAt(state.position); + + if (ch === terminator) { + state.position++; + state.tag = _tag; + state.anchor = _anchor; + state.kind = isMapping ? 'mapping' : 'sequence'; + state.result = _result; + return true; + } else if (!readNext) { + throwError(state, 'missed comma between flow collection entries'); + } else if (ch === 0x2C/* , */) { + // "flow collection entries can never be completely empty", as per YAML 1.2, section 7.4 + throwError(state, "expected the node content, but found ','"); + } + + keyTag = keyNode = valueNode = null; + isPair = isExplicitPair = false; + + if (ch === 0x3F/* ? */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following)) { + isPair = isExplicitPair = true; + state.position++; + skipSeparationSpace(state, true, nodeIndent); + } + } + + _line = state.line; // Save the current line. + _lineStart = state.lineStart; + _pos = state.position; + composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); + keyTag = state.tag; + keyNode = state.result; + skipSeparationSpace(state, true, nodeIndent); + + ch = state.input.charCodeAt(state.position); + + if ((isExplicitPair || state.line === _line) && ch === 0x3A/* : */) { + isPair = true; + ch = state.input.charCodeAt(++state.position); + skipSeparationSpace(state, true, nodeIndent); + composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); + valueNode = state.result; + } + + if (isMapping) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos); + } else if (isPair) { + _result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos)); + } else { + _result.push(keyNode); + } + + skipSeparationSpace(state, true, nodeIndent); + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x2C/* , */) { + readNext = true; + ch = state.input.charCodeAt(++state.position); + } else { + readNext = false; + } + } + + throwError(state, 'unexpected end of the stream within a flow collection'); +} + +function readBlockScalar(state, nodeIndent) { + var captureStart, + folding, + chomping = CHOMPING_CLIP, + didReadContent = false, + detectedIndent = false, + textIndent = nodeIndent, + emptyLines = 0, + atMoreIndented = false, + tmp, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x7C/* | */) { + folding = false; + } else if (ch === 0x3E/* > */) { + folding = true; + } else { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + + while (ch !== 0) { + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { + if (CHOMPING_CLIP === chomping) { + chomping = (ch === 0x2B/* + */) ? CHOMPING_KEEP : CHOMPING_STRIP; + } else { + throwError(state, 'repeat of a chomping mode identifier'); + } + + } else if ((tmp = fromDecimalCode(ch)) >= 0) { + if (tmp === 0) { + throwError(state, 'bad explicit indentation width of a block scalar; it cannot be less than one'); + } else if (!detectedIndent) { + textIndent = nodeIndent + tmp - 1; + detectedIndent = true; + } else { + throwError(state, 'repeat of an indentation width identifier'); + } + + } else { + break; + } + } + + if (is_WHITE_SPACE(ch)) { + do { ch = state.input.charCodeAt(++state.position); } + while (is_WHITE_SPACE(ch)); + + if (ch === 0x23/* # */) { + do { ch = state.input.charCodeAt(++state.position); } + while (!is_EOL(ch) && (ch !== 0)); + } + } + + while (ch !== 0) { + readLineBreak(state); + state.lineIndent = 0; + + ch = state.input.charCodeAt(state.position); + + while ((!detectedIndent || state.lineIndent < textIndent) && + (ch === 0x20/* Space */)) { + state.lineIndent++; + ch = state.input.charCodeAt(++state.position); + } + + if (!detectedIndent && state.lineIndent > textIndent) { + textIndent = state.lineIndent; + } + + if (is_EOL(ch)) { + emptyLines++; + continue; + } + + // End of the scalar. + if (state.lineIndent < textIndent) { + + // Perform the chomping. + if (chomping === CHOMPING_KEEP) { + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + } else if (chomping === CHOMPING_CLIP) { + if (didReadContent) { // i.e. only if the scalar is not empty. + state.result += '\n'; + } + } + + // Break this `while` cycle and go to the funciton's epilogue. + break; + } + + // Folded style: use fancy rules to handle line breaks. + if (folding) { + + // Lines starting with white space characters (more-indented lines) are not folded. + if (is_WHITE_SPACE(ch)) { + atMoreIndented = true; + // except for the first content line (cf. Example 8.1) + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + + // End of more-indented block. + } else if (atMoreIndented) { + atMoreIndented = false; + state.result += common.repeat('\n', emptyLines + 1); + + // Just one line break - perceive as the same line. + } else if (emptyLines === 0) { + if (didReadContent) { // i.e. only if we have already read some scalar content. + state.result += ' '; + } + + // Several line breaks - perceive as different lines. + } else { + state.result += common.repeat('\n', emptyLines); + } + + // Literal style: just add exact number of line breaks between content lines. + } else { + // Keep all line breaks except the header line break. + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + } + + didReadContent = true; + detectedIndent = true; + emptyLines = 0; + captureStart = state.position; + + while (!is_EOL(ch) && (ch !== 0)) { + ch = state.input.charCodeAt(++state.position); + } + + captureSegment(state, captureStart, state.position, false); + } + + return true; +} + +function readBlockSequence(state, nodeIndent) { + var _line, + _tag = state.tag, + _anchor = state.anchor, + _result = [], + following, + detected = false, + ch; + + // there is a leading tab before this token, so it can't be a block sequence/mapping; + // it can still be flow sequence/mapping or a scalar + if (state.firstTabInLine !== -1) return false; + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } + + ch = state.input.charCodeAt(state.position); + + while (ch !== 0) { + if (state.firstTabInLine !== -1) { + state.position = state.firstTabInLine; + throwError(state, 'tab characters must not be used in indentation'); + } + + if (ch !== 0x2D/* - */) { + break; + } + + following = state.input.charCodeAt(state.position + 1); + + if (!is_WS_OR_EOL(following)) { + break; + } + + detected = true; + state.position++; + + if (skipSeparationSpace(state, true, -1)) { + if (state.lineIndent <= nodeIndent) { + _result.push(null); + ch = state.input.charCodeAt(state.position); + continue; + } + } + + _line = state.line; + composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true); + _result.push(state.result); + skipSeparationSpace(state, true, -1); + + ch = state.input.charCodeAt(state.position); + + if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) { + throwError(state, 'bad indentation of a sequence entry'); + } else if (state.lineIndent < nodeIndent) { + break; + } + } + + if (detected) { + state.tag = _tag; + state.anchor = _anchor; + state.kind = 'sequence'; + state.result = _result; + return true; + } + return false; +} + +function readBlockMapping(state, nodeIndent, flowIndent) { + var following, + allowCompact, + _line, + _keyLine, + _keyLineStart, + _keyPos, + _tag = state.tag, + _anchor = state.anchor, + _result = {}, + overridableKeys = Object.create(null), + keyTag = null, + keyNode = null, + valueNode = null, + atExplicitKey = false, + detected = false, + ch; + + // there is a leading tab before this token, so it can't be a block sequence/mapping; + // it can still be flow sequence/mapping or a scalar + if (state.firstTabInLine !== -1) return false; + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } + + ch = state.input.charCodeAt(state.position); + + while (ch !== 0) { + if (!atExplicitKey && state.firstTabInLine !== -1) { + state.position = state.firstTabInLine; + throwError(state, 'tab characters must not be used in indentation'); + } + + following = state.input.charCodeAt(state.position + 1); + _line = state.line; // Save the current line. + + // + // Explicit notation case. There are two separate blocks: + // first for the key (denoted by "?") and second for the value (denoted by ":") + // + if ((ch === 0x3F/* ? */ || ch === 0x3A/* : */) && is_WS_OR_EOL(following)) { + + if (ch === 0x3F/* ? */) { + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); + keyTag = keyNode = valueNode = null; + } + + detected = true; + atExplicitKey = true; + allowCompact = true; + + } else if (atExplicitKey) { + // i.e. 0x3A/* : */ === character after the explicit key. + atExplicitKey = false; + allowCompact = true; + + } else { + throwError(state, 'incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line'); + } + + state.position += 1; + ch = following; + + // + // Implicit notation case. Flow-style node as the key first, then ":", and the value. + // + } else { + _keyLine = state.line; + _keyLineStart = state.lineStart; + _keyPos = state.position; + + if (!composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) { + // Neither implicit nor explicit notation. + // Reading is done. Go to the epilogue. + break; + } + + if (state.line === _line) { + ch = state.input.charCodeAt(state.position); + + while (is_WHITE_SPACE(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (ch === 0x3A/* : */) { + ch = state.input.charCodeAt(++state.position); + + if (!is_WS_OR_EOL(ch)) { + throwError(state, 'a whitespace character is expected after the key-value separator within a block mapping'); + } + + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); + keyTag = keyNode = valueNode = null; + } + + detected = true; + atExplicitKey = false; + allowCompact = false; + keyTag = state.tag; + keyNode = state.result; + + } else if (detected) { + throwError(state, 'can not read an implicit mapping pair; a colon is missed'); + + } else { + state.tag = _tag; + state.anchor = _anchor; + return true; // Keep the result of `composeNode`. + } + + } else if (detected) { + throwError(state, 'can not read a block mapping entry; a multiline key may not be an implicit key'); + + } else { + state.tag = _tag; + state.anchor = _anchor; + return true; // Keep the result of `composeNode`. + } + } + + // + // Common reading code for both explicit and implicit notations. + // + if (state.line === _line || state.lineIndent > nodeIndent) { + if (atExplicitKey) { + _keyLine = state.line; + _keyLineStart = state.lineStart; + _keyPos = state.position; + } + + if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) { + if (atExplicitKey) { + keyNode = state.result; + } else { + valueNode = state.result; + } + } + + if (!atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _keyLine, _keyLineStart, _keyPos); + keyTag = keyNode = valueNode = null; + } + + skipSeparationSpace(state, true, -1); + ch = state.input.charCodeAt(state.position); + } + + if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) { + throwError(state, 'bad indentation of a mapping entry'); + } else if (state.lineIndent < nodeIndent) { + break; + } + } + + // + // Epilogue. + // + + // Special case: last mapping's node contains only the key in explicit notation. + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); + } + + // Expose the resulting mapping. + if (detected) { + state.tag = _tag; + state.anchor = _anchor; + state.kind = 'mapping'; + state.result = _result; + } + + return detected; +} + +function readTagProperty(state) { + var _position, + isVerbatim = false, + isNamed = false, + tagHandle, + tagName, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x21/* ! */) return false; + + if (state.tag !== null) { + throwError(state, 'duplication of a tag property'); + } + + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x3C/* < */) { + isVerbatim = true; + ch = state.input.charCodeAt(++state.position); + + } else if (ch === 0x21/* ! */) { + isNamed = true; + tagHandle = '!!'; + ch = state.input.charCodeAt(++state.position); + + } else { + tagHandle = '!'; + } + + _position = state.position; + + if (isVerbatim) { + do { ch = state.input.charCodeAt(++state.position); } + while (ch !== 0 && ch !== 0x3E/* > */); + + if (state.position < state.length) { + tagName = state.input.slice(_position, state.position); + ch = state.input.charCodeAt(++state.position); + } else { + throwError(state, 'unexpected end of the stream within a verbatim tag'); + } + } else { + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + + if (ch === 0x21/* ! */) { + if (!isNamed) { + tagHandle = state.input.slice(_position - 1, state.position + 1); + + if (!PATTERN_TAG_HANDLE.test(tagHandle)) { + throwError(state, 'named tag handle cannot contain such characters'); + } + + isNamed = true; + _position = state.position + 1; + } else { + throwError(state, 'tag suffix cannot contain exclamation marks'); + } + } + + ch = state.input.charCodeAt(++state.position); + } + + tagName = state.input.slice(_position, state.position); + + if (PATTERN_FLOW_INDICATORS.test(tagName)) { + throwError(state, 'tag suffix cannot contain flow indicator characters'); + } + } + + if (tagName && !PATTERN_TAG_URI.test(tagName)) { + throwError(state, 'tag name cannot contain such characters: ' + tagName); + } + + try { + tagName = decodeURIComponent(tagName); + } catch (err) { + throwError(state, 'tag name is malformed: ' + tagName); + } + + if (isVerbatim) { + state.tag = tagName; + + } else if (_hasOwnProperty$1.call(state.tagMap, tagHandle)) { + state.tag = state.tagMap[tagHandle] + tagName; + + } else if (tagHandle === '!') { + state.tag = '!' + tagName; + + } else if (tagHandle === '!!') { + state.tag = 'tag:yaml.org,2002:' + tagName; + + } else { + throwError(state, 'undeclared tag handle "' + tagHandle + '"'); + } + + return true; +} + +function readAnchorProperty(state) { + var _position, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x26/* & */) return false; + + if (state.anchor !== null) { + throwError(state, 'duplication of an anchor property'); + } + + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (state.position === _position) { + throwError(state, 'name of an anchor node must contain at least one character'); + } + + state.anchor = state.input.slice(_position, state.position); + return true; +} + +function readAlias(state) { + var _position, alias, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x2A/* * */) return false; + + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (state.position === _position) { + throwError(state, 'name of an alias node must contain at least one character'); + } + + alias = state.input.slice(_position, state.position); + + if (!_hasOwnProperty$1.call(state.anchorMap, alias)) { + throwError(state, 'unidentified alias "' + alias + '"'); + } + + state.result = state.anchorMap[alias]; + skipSeparationSpace(state, true, -1); + return true; +} + +function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) { + var allowBlockStyles, + allowBlockScalars, + allowBlockCollections, + indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } + } + } + + if (indentStatus === 1) { + while (readTagProperty(state) || readAnchorProperty(state)) { + if (skipSeparationSpace(state, true, -1)) { + atNewLine = true; + allowBlockCollections = allowBlockStyles; + + if (state.lineIndent > parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } + } else { + allowBlockCollections = false; + } + } + } + + if (allowBlockCollections) { + allowBlockCollections = atNewLine || allowCompact; + } + + if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) { + if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) { + flowIndent = parentIndent; + } else { + flowIndent = parentIndent + 1; + } + + blockIndent = state.position - state.lineStart; + + if (indentStatus === 1) { + if (allowBlockCollections && + (readBlockSequence(state, blockIndent) || + readBlockMapping(state, blockIndent, flowIndent)) || + readFlowCollection(state, flowIndent)) { + hasContent = true; + } else { + if ((allowBlockScalars && readBlockScalar(state, flowIndent)) || + readSingleQuotedScalar(state, flowIndent) || + readDoubleQuotedScalar(state, flowIndent)) { + hasContent = true; + + } else if (readAlias(state)) { + hasContent = true; + + if (state.tag !== null || state.anchor !== null) { + throwError(state, 'alias node should not have any properties'); + } + + } else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) { + hasContent = true; + + if (state.tag === null) { + state.tag = '?'; + } + } + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + } + } else if (indentStatus === 0) { + // Special case: block sequences are allowed to have same indentation level as the parent. + // http://www.yaml.org/spec/1.2/spec.html#id2799784 + hasContent = allowBlockCollections && readBlockSequence(state, blockIndent); + } + } + + if (state.tag === null) { + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + + } else if (state.tag === '?') { + // Implicit resolving is not allowed for non-scalar types, and '?' + // non-specific tag is only automatically assigned to plain scalars. + // + // We only need to check kind conformity in case user explicitly assigns '?' + // tag, for example like this: "! [0]" + // + if (state.result !== null && state.kind !== 'scalar') { + throwError(state, 'unacceptable node kind for ! tag; it should be "scalar", not "' + state.kind + '"'); + } + + for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) { + type = state.implicitTypes[typeIndex]; + + if (type.resolve(state.result)) { // `state.result` updated in resolver if matched + state.result = type.construct(state.result); + state.tag = type.tag; + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + break; + } + } + } else if (state.tag !== '!') { + if (_hasOwnProperty$1.call(state.typeMap[state.kind || 'fallback'], state.tag)) { + type = state.typeMap[state.kind || 'fallback'][state.tag]; + } else { + // looking for multi type + type = null; + typeList = state.typeMap.multi[state.kind || 'fallback']; + + for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) { + if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) { + type = typeList[typeIndex]; + break; + } + } + } + + if (!type) { + throwError(state, 'unknown tag !<' + state.tag + '>'); + } + + if (state.result !== null && type.kind !== state.kind) { + throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"'); + } + + if (!type.resolve(state.result, state.tag)) { // `state.result` updated in resolver if matched + throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag'); + } else { + state.result = type.construct(state.result, state.tag); + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + } + } + + if (state.listener !== null) { + state.listener('close', state); + } + return state.tag !== null || state.anchor !== null || hasContent; +} + +function readDocument(state) { + var documentStart = state.position, + _position, + directiveName, + directiveArgs, + hasDirectives = false, + ch; + + state.version = null; + state.checkLineBreaks = state.legacy; + state.tagMap = Object.create(null); + state.anchorMap = Object.create(null); + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + skipSeparationSpace(state, true, -1); + + ch = state.input.charCodeAt(state.position); + + if (state.lineIndent > 0 || ch !== 0x25/* % */) { + break; + } + + hasDirectives = true; + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + directiveName = state.input.slice(_position, state.position); + directiveArgs = []; + + if (directiveName.length < 1) { + throwError(state, 'directive name must not be less than one character in length'); + } + + while (ch !== 0) { + while (is_WHITE_SPACE(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (ch === 0x23/* # */) { + do { ch = state.input.charCodeAt(++state.position); } + while (ch !== 0 && !is_EOL(ch)); + break; + } + + if (is_EOL(ch)) break; + + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + directiveArgs.push(state.input.slice(_position, state.position)); + } + + if (ch !== 0) readLineBreak(state); + + if (_hasOwnProperty$1.call(directiveHandlers, directiveName)) { + directiveHandlers[directiveName](state, directiveName, directiveArgs); + } else { + throwWarning(state, 'unknown document directive "' + directiveName + '"'); + } + } + + skipSeparationSpace(state, true, -1); + + if (state.lineIndent === 0 && + state.input.charCodeAt(state.position) === 0x2D/* - */ && + state.input.charCodeAt(state.position + 1) === 0x2D/* - */ && + state.input.charCodeAt(state.position + 2) === 0x2D/* - */) { + state.position += 3; + skipSeparationSpace(state, true, -1); + + } else if (hasDirectives) { + throwError(state, 'directives end mark is expected'); + } + + composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true); + skipSeparationSpace(state, true, -1); + + if (state.checkLineBreaks && + PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) { + throwWarning(state, 'non-ASCII line breaks are interpreted as content'); + } + + state.documents.push(state.result); + + if (state.position === state.lineStart && testDocumentSeparator(state)) { + + if (state.input.charCodeAt(state.position) === 0x2E/* . */) { + state.position += 3; + skipSeparationSpace(state, true, -1); + } + return; + } + + if (state.position < (state.length - 1)) { + throwError(state, 'end of the stream or a document separator is expected'); + } else { + return; + } +} + + +function loadDocuments(input, options) { + input = String(input); + options = options || {}; + + if (input.length !== 0) { + + // Add tailing `\n` if not exists + if (input.charCodeAt(input.length - 1) !== 0x0A/* LF */ && + input.charCodeAt(input.length - 1) !== 0x0D/* CR */) { + input += '\n'; + } + + // Strip BOM + if (input.charCodeAt(0) === 0xFEFF) { + input = input.slice(1); + } + } + + var state = new State$1(input, options); + + var nullpos = input.indexOf('\0'); + + if (nullpos !== -1) { + state.position = nullpos; + throwError(state, 'null byte is not allowed in input'); + } + + // Use 0 as string terminator. That significantly simplifies bounds check. + state.input += '\0'; + + while (state.input.charCodeAt(state.position) === 0x20/* Space */) { + state.lineIndent += 1; + state.position += 1; + } + + while (state.position < (state.length - 1)) { + readDocument(state); + } + + return state.documents; +} + + +function loadAll$1(input, iterator, options) { + if (iterator !== null && typeof iterator === 'object' && typeof options === 'undefined') { + options = iterator; + iterator = null; + } + + var documents = loadDocuments(input, options); + + if (typeof iterator !== 'function') { + return documents; + } + + for (var index = 0, length = documents.length; index < length; index += 1) { + iterator(documents[index]); + } +} + + +function load$1(input, options) { + var documents = loadDocuments(input, options); + + if (documents.length === 0) { + /*eslint-disable no-undefined*/ + return undefined; + } else if (documents.length === 1) { + return documents[0]; + } + throw new exception('expected a single document in the stream, but found more'); +} + + +var loadAll_1 = loadAll$1; +var load_1 = load$1; + +var loader = { + loadAll: loadAll_1, + load: load_1 +}; + +/*eslint-disable no-use-before-define*/ + + + + + +var _toString = Object.prototype.toString; +var _hasOwnProperty = Object.prototype.hasOwnProperty; + +var CHAR_BOM = 0xFEFF; +var CHAR_TAB = 0x09; /* Tab */ +var CHAR_LINE_FEED = 0x0A; /* LF */ +var CHAR_CARRIAGE_RETURN = 0x0D; /* CR */ +var CHAR_SPACE = 0x20; /* Space */ +var CHAR_EXCLAMATION = 0x21; /* ! */ +var CHAR_DOUBLE_QUOTE = 0x22; /* " */ +var CHAR_SHARP = 0x23; /* # */ +var CHAR_PERCENT = 0x25; /* % */ +var CHAR_AMPERSAND = 0x26; /* & */ +var CHAR_SINGLE_QUOTE = 0x27; /* ' */ +var CHAR_ASTERISK = 0x2A; /* * */ +var CHAR_COMMA = 0x2C; /* , */ +var CHAR_MINUS = 0x2D; /* - */ +var CHAR_COLON = 0x3A; /* : */ +var CHAR_EQUALS = 0x3D; /* = */ +var CHAR_GREATER_THAN = 0x3E; /* > */ +var CHAR_QUESTION = 0x3F; /* ? */ +var CHAR_COMMERCIAL_AT = 0x40; /* @ */ +var CHAR_LEFT_SQUARE_BRACKET = 0x5B; /* [ */ +var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */ +var CHAR_GRAVE_ACCENT = 0x60; /* ` */ +var CHAR_LEFT_CURLY_BRACKET = 0x7B; /* { */ +var CHAR_VERTICAL_LINE = 0x7C; /* | */ +var CHAR_RIGHT_CURLY_BRACKET = 0x7D; /* } */ + +var ESCAPE_SEQUENCES = {}; + +ESCAPE_SEQUENCES[0x00] = '\\0'; +ESCAPE_SEQUENCES[0x07] = '\\a'; +ESCAPE_SEQUENCES[0x08] = '\\b'; +ESCAPE_SEQUENCES[0x09] = '\\t'; +ESCAPE_SEQUENCES[0x0A] = '\\n'; +ESCAPE_SEQUENCES[0x0B] = '\\v'; +ESCAPE_SEQUENCES[0x0C] = '\\f'; +ESCAPE_SEQUENCES[0x0D] = '\\r'; +ESCAPE_SEQUENCES[0x1B] = '\\e'; +ESCAPE_SEQUENCES[0x22] = '\\"'; +ESCAPE_SEQUENCES[0x5C] = '\\\\'; +ESCAPE_SEQUENCES[0x85] = '\\N'; +ESCAPE_SEQUENCES[0xA0] = '\\_'; +ESCAPE_SEQUENCES[0x2028] = '\\L'; +ESCAPE_SEQUENCES[0x2029] = '\\P'; + +var DEPRECATED_BOOLEANS_SYNTAX = [ + 'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON', + 'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF' +]; + +var DEPRECATED_BASE60_SYNTAX = /^[-+]?[0-9_]+(?::[0-9_]+)+(?:\.[0-9_]*)?$/; + +function compileStyleMap(schema, map) { + var result, keys, index, length, tag, style, type; + + if (map === null) return {}; + + result = {}; + keys = Object.keys(map); + + for (index = 0, length = keys.length; index < length; index += 1) { + tag = keys[index]; + style = String(map[tag]); + + if (tag.slice(0, 2) === '!!') { + tag = 'tag:yaml.org,2002:' + tag.slice(2); + } + type = schema.compiledTypeMap['fallback'][tag]; + + if (type && _hasOwnProperty.call(type.styleAliases, style)) { + style = type.styleAliases[style]; + } + + result[tag] = style; + } + + return result; +} + +function encodeHex(character) { + var string, handle, length; + + string = character.toString(16).toUpperCase(); + + if (character <= 0xFF) { + handle = 'x'; + length = 2; + } else if (character <= 0xFFFF) { + handle = 'u'; + length = 4; + } else if (character <= 0xFFFFFFFF) { + handle = 'U'; + length = 8; + } else { + throw new exception('code point within a string may not be greater than 0xFFFFFFFF'); + } + + return '\\' + handle + common.repeat('0', length - string.length) + string; +} + + +var QUOTING_TYPE_SINGLE = 1, + QUOTING_TYPE_DOUBLE = 2; + +function State(options) { + this.schema = options['schema'] || _default; + this.indent = Math.max(1, (options['indent'] || 2)); + this.noArrayIndent = options['noArrayIndent'] || false; + this.skipInvalid = options['skipInvalid'] || false; + this.flowLevel = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']); + this.styleMap = compileStyleMap(this.schema, options['styles'] || null); + this.sortKeys = options['sortKeys'] || false; + this.lineWidth = options['lineWidth'] || 80; + this.noRefs = options['noRefs'] || false; + this.noCompatMode = options['noCompatMode'] || false; + this.condenseFlow = options['condenseFlow'] || false; + this.quotingType = options['quotingType'] === '"' ? QUOTING_TYPE_DOUBLE : QUOTING_TYPE_SINGLE; + this.forceQuotes = options['forceQuotes'] || false; + this.replacer = typeof options['replacer'] === 'function' ? options['replacer'] : null; + + this.implicitTypes = this.schema.compiledImplicit; + this.explicitTypes = this.schema.compiledExplicit; + + this.tag = null; + this.result = ''; + + this.duplicates = []; + this.usedDuplicates = null; +} + +// Indents every line in a string. Empty lines (\n only) are not indented. +function indentString(string, spaces) { + var ind = common.repeat(' ', spaces), + position = 0, + next = -1, + result = '', + line, + length = string.length; + + while (position < length) { + next = string.indexOf('\n', position); + if (next === -1) { + line = string.slice(position); + position = length; + } else { + line = string.slice(position, next + 1); + position = next + 1; + } + + if (line.length && line !== '\n') result += ind; + + result += line; + } + + return result; +} + +function generateNextLine(state, level) { + return '\n' + common.repeat(' ', state.indent * level); +} + +function testImplicitResolving(state, str) { + var index, length, type; + + for (index = 0, length = state.implicitTypes.length; index < length; index += 1) { + type = state.implicitTypes[index]; + + if (type.resolve(str)) { + return true; + } + } + + return false; +} + +// [33] s-white ::= s-space | s-tab +function isWhitespace(c) { + return c === CHAR_SPACE || c === CHAR_TAB; +} + +// Returns true if the character can be printed without escaping. +// From YAML 1.2: "any allowed characters known to be non-printable +// should also be escaped. [However,] This isn’t mandatory" +// Derived from nb-char - \t - #x85 - #xA0 - #x2028 - #x2029. +function isPrintable(c) { + return (0x00020 <= c && c <= 0x00007E) + || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029) + || ((0x0E000 <= c && c <= 0x00FFFD) && c !== CHAR_BOM) + || (0x10000 <= c && c <= 0x10FFFF); +} + +// [34] ns-char ::= nb-char - s-white +// [27] nb-char ::= c-printable - b-char - c-byte-order-mark +// [26] b-char ::= b-line-feed | b-carriage-return +// Including s-white (for some reason, examples doesn't match specs in this aspect) +// ns-char ::= c-printable - b-line-feed - b-carriage-return - c-byte-order-mark +function isNsCharOrWhitespace(c) { + return isPrintable(c) + && c !== CHAR_BOM + // - b-char + && c !== CHAR_CARRIAGE_RETURN + && c !== CHAR_LINE_FEED; +} + +// [127] ns-plain-safe(c) ::= c = flow-out ⇒ ns-plain-safe-out +// c = flow-in ⇒ ns-plain-safe-in +// c = block-key ⇒ ns-plain-safe-out +// c = flow-key ⇒ ns-plain-safe-in +// [128] ns-plain-safe-out ::= ns-char +// [129] ns-plain-safe-in ::= ns-char - c-flow-indicator +// [130] ns-plain-char(c) ::= ( ns-plain-safe(c) - “:” - “#” ) +// | ( /* An ns-char preceding */ “#” ) +// | ( “:” /* Followed by an ns-plain-safe(c) */ ) +function isPlainSafe(c, prev, inblock) { + var cIsNsCharOrWhitespace = isNsCharOrWhitespace(c); + var cIsNsChar = cIsNsCharOrWhitespace && !isWhitespace(c); + return ( + // ns-plain-safe + inblock ? // c = flow-in + cIsNsCharOrWhitespace + : cIsNsCharOrWhitespace + // - c-flow-indicator + && c !== CHAR_COMMA + && c !== CHAR_LEFT_SQUARE_BRACKET + && c !== CHAR_RIGHT_SQUARE_BRACKET + && c !== CHAR_LEFT_CURLY_BRACKET + && c !== CHAR_RIGHT_CURLY_BRACKET + ) + // ns-plain-char + && c !== CHAR_SHARP // false on '#' + && !(prev === CHAR_COLON && !cIsNsChar) // false on ': ' + || (isNsCharOrWhitespace(prev) && !isWhitespace(prev) && c === CHAR_SHARP) // change to true on '[^ ]#' + || (prev === CHAR_COLON && cIsNsChar); // change to true on ':[^ ]' +} + +// Simplified test for values allowed as the first character in plain style. +function isPlainSafeFirst(c) { + // Uses a subset of ns-char - c-indicator + // where ns-char = nb-char - s-white. + // No support of ( ( “?” | “:” | “-” ) /* Followed by an ns-plain-safe(c)) */ ) part + return isPrintable(c) && c !== CHAR_BOM + && !isWhitespace(c) // - s-white + // - (c-indicator ::= + // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}” + && c !== CHAR_MINUS + && c !== CHAR_QUESTION + && c !== CHAR_COLON + && c !== CHAR_COMMA + && c !== CHAR_LEFT_SQUARE_BRACKET + && c !== CHAR_RIGHT_SQUARE_BRACKET + && c !== CHAR_LEFT_CURLY_BRACKET + && c !== CHAR_RIGHT_CURLY_BRACKET + // | “#” | “&” | “*” | “!” | “|” | “=” | “>” | “'” | “"” + && c !== CHAR_SHARP + && c !== CHAR_AMPERSAND + && c !== CHAR_ASTERISK + && c !== CHAR_EXCLAMATION + && c !== CHAR_VERTICAL_LINE + && c !== CHAR_EQUALS + && c !== CHAR_GREATER_THAN + && c !== CHAR_SINGLE_QUOTE + && c !== CHAR_DOUBLE_QUOTE + // | “%” | “@” | “`”) + && c !== CHAR_PERCENT + && c !== CHAR_COMMERCIAL_AT + && c !== CHAR_GRAVE_ACCENT; +} + +// Simplified test for values allowed as the last character in plain style. +function isPlainSafeLast(c) { + // just not whitespace or colon, it will be checked to be plain character later + return !isWhitespace(c) && c !== CHAR_COLON; +} + +// Same as 'string'.codePointAt(pos), but works in older browsers. +function codePointAt(string, pos) { + var first = string.charCodeAt(pos), second; + if (first >= 0xD800 && first <= 0xDBFF && pos + 1 < string.length) { + second = string.charCodeAt(pos + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; +} + +// Determines whether block indentation indicator is required. +function needIndentIndicator(string) { + var leadingSpaceRe = /^\n* /; + return leadingSpaceRe.test(string); +} + +var STYLE_PLAIN = 1, + STYLE_SINGLE = 2, + STYLE_LITERAL = 3, + STYLE_FOLDED = 4, + STYLE_DOUBLE = 5; + +// Determines which scalar styles are possible and returns the preferred style. +// lineWidth = -1 => no limit. +// Pre-conditions: str.length > 0. +// Post-conditions: +// STYLE_PLAIN or STYLE_SINGLE => no \n are in the string. +// STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1). +// STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1). +function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, + testAmbiguousType, quotingType, forceQuotes, inblock) { + + var i; + var char = 0; + var prevChar = null; + var hasLineBreak = false; + var hasFoldableLine = false; // only checked if shouldTrackWidth + var shouldTrackWidth = lineWidth !== -1; + var previousLineBreak = -1; // count the first line correctly + var plain = isPlainSafeFirst(codePointAt(string, 0)) + && isPlainSafeLast(codePointAt(string, string.length - 1)); + + if (singleLineOnly || forceQuotes) { + // Case: no block styles. + // Check for disallowed characters to rule out plain and single. + for (i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) { + char = codePointAt(string, i); + if (!isPrintable(char)) { + return STYLE_DOUBLE; + } + plain = plain && isPlainSafe(char, prevChar, inblock); + prevChar = char; + } + } else { + // Case: block styles permitted. + for (i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) { + char = codePointAt(string, i); + if (char === CHAR_LINE_FEED) { + hasLineBreak = true; + // Check if any line can be folded. + if (shouldTrackWidth) { + hasFoldableLine = hasFoldableLine || + // Foldable line = too long, and not more-indented. + (i - previousLineBreak - 1 > lineWidth && + string[previousLineBreak + 1] !== ' '); + previousLineBreak = i; + } + } else if (!isPrintable(char)) { + return STYLE_DOUBLE; + } + plain = plain && isPlainSafe(char, prevChar, inblock); + prevChar = char; + } + // in case the end is missing a \n + hasFoldableLine = hasFoldableLine || (shouldTrackWidth && + (i - previousLineBreak - 1 > lineWidth && + string[previousLineBreak + 1] !== ' ')); + } + // Although every style can represent \n without escaping, prefer block styles + // for multiline, since they're more readable and they don't add empty lines. + // Also prefer folding a super-long line. + if (!hasLineBreak && !hasFoldableLine) { + // Strings interpretable as another type have to be quoted; + // e.g. the string 'true' vs. the boolean true. + if (plain && !forceQuotes && !testAmbiguousType(string)) { + return STYLE_PLAIN; + } + return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE; + } + // Edge case: block indentation indicator can only have one digit. + if (indentPerLevel > 9 && needIndentIndicator(string)) { + return STYLE_DOUBLE; + } + // At this point we know block styles are valid. + // Prefer literal style unless we want to fold. + if (!forceQuotes) { + return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL; + } + return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE; +} + +// Note: line breaking/folding is implemented for only the folded style. +// NB. We drop the last trailing newline (if any) of a returned block scalar +// since the dumper adds its own newline. This always works: +// • No ending newline => unaffected; already using strip "-" chomping. +// • Ending newline => removed then restored. +// Importantly, this keeps the "+" chomp indicator from gaining an extra line. +function writeScalar(state, string, level, iskey, inblock) { + state.dump = (function () { + if (string.length === 0) { + return state.quotingType === QUOTING_TYPE_DOUBLE ? '""' : "''"; + } + if (!state.noCompatMode) { + if (DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1 || DEPRECATED_BASE60_SYNTAX.test(string)) { + return state.quotingType === QUOTING_TYPE_DOUBLE ? ('"' + string + '"') : ("'" + string + "'"); + } + } + + var indent = state.indent * Math.max(1, level); // no 0-indent scalars + // As indentation gets deeper, let the width decrease monotonically + // to the lower bound min(state.lineWidth, 40). + // Note that this implies + // state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound. + // state.lineWidth > 40 + state.indent: width decreases until the lower bound. + // This behaves better than a constant minimum width which disallows narrower options, + // or an indent threshold which causes the width to suddenly increase. + var lineWidth = state.lineWidth === -1 + ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent); + + // Without knowing if keys are implicit/explicit, assume implicit for safety. + var singleLineOnly = iskey + // No block styles in flow mode. + || (state.flowLevel > -1 && level >= state.flowLevel); + function testAmbiguity(string) { + return testImplicitResolving(state, string); + } + + switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, + testAmbiguity, state.quotingType, state.forceQuotes && !iskey, inblock)) { + + case STYLE_PLAIN: + return string; + case STYLE_SINGLE: + return "'" + string.replace(/'/g, "''") + "'"; + case STYLE_LITERAL: + return '|' + blockHeader(string, state.indent) + + dropEndingNewline(indentString(string, indent)); + case STYLE_FOLDED: + return '>' + blockHeader(string, state.indent) + + dropEndingNewline(indentString(foldString(string, lineWidth), indent)); + case STYLE_DOUBLE: + return '"' + escapeString(string) + '"'; + default: + throw new exception('impossible error: invalid scalar style'); + } + }()); +} + +// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9. +function blockHeader(string, indentPerLevel) { + var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : ''; + + // note the special case: the string '\n' counts as a "trailing" empty line. + var clip = string[string.length - 1] === '\n'; + var keep = clip && (string[string.length - 2] === '\n' || string === '\n'); + var chomp = keep ? '+' : (clip ? '' : '-'); + + return indentIndicator + chomp + '\n'; +} + +// (See the note for writeScalar.) +function dropEndingNewline(string) { + return string[string.length - 1] === '\n' ? string.slice(0, -1) : string; +} + +// Note: a long line without a suitable break point will exceed the width limit. +// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0. +function foldString(string, width) { + // In folded style, $k$ consecutive newlines output as $k+1$ newlines— + // unless they're before or after a more-indented line, or at the very + // beginning or end, in which case $k$ maps to $k$. + // Therefore, parse each chunk as newline(s) followed by a content line. + var lineRe = /(\n+)([^\n]*)/g; + + // first line (possibly an empty line) + var result = (function () { + var nextLF = string.indexOf('\n'); + nextLF = nextLF !== -1 ? nextLF : string.length; + lineRe.lastIndex = nextLF; + return foldLine(string.slice(0, nextLF), width); + }()); + // If we haven't reached the first content line yet, don't add an extra \n. + var prevMoreIndented = string[0] === '\n' || string[0] === ' '; + var moreIndented; + + // rest of the lines + var match; + while ((match = lineRe.exec(string))) { + var prefix = match[1], line = match[2]; + moreIndented = (line[0] === ' '); + result += prefix + + (!prevMoreIndented && !moreIndented && line !== '' + ? '\n' : '') + + foldLine(line, width); + prevMoreIndented = moreIndented; + } + + return result; +} + +// Greedy line breaking. +// Picks the longest line under the limit each time, +// otherwise settles for the shortest line over the limit. +// NB. More-indented lines *cannot* be folded, as that would add an extra \n. +function foldLine(line, width) { + if (line === '' || line[0] === ' ') return line; + + // Since a more-indented line adds a \n, breaks can't be followed by a space. + var breakRe = / [^ ]/g; // note: the match index will always be <= length-2. + var match; + // start is an inclusive index. end, curr, and next are exclusive. + var start = 0, end, curr = 0, next = 0; + var result = ''; + + // Invariants: 0 <= start <= length-1. + // 0 <= curr <= next <= max(0, length-2). curr - start <= width. + // Inside the loop: + // A match implies length >= 2, so curr and next are <= length-2. + while ((match = breakRe.exec(line))) { + next = match.index; + // maintain invariant: curr - start <= width + if (next - start > width) { + end = (curr > start) ? curr : next; // derive end <= length-2 + result += '\n' + line.slice(start, end); + // skip the space that was output as \n + start = end + 1; // derive start <= length-1 + } + curr = next; + } + + // By the invariants, start <= length-1, so there is something left over. + // It is either the whole string or a part starting from non-whitespace. + result += '\n'; + // Insert a break if the remainder is too long and there is a break available. + if (line.length - start > width && curr > start) { + result += line.slice(start, curr) + '\n' + line.slice(curr + 1); + } else { + result += line.slice(start); + } + + return result.slice(1); // drop extra \n joiner +} + +// Escapes a double-quoted string. +function escapeString(string) { + var result = ''; + var char = 0; + var escapeSeq; + + for (var i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) { + char = codePointAt(string, i); + escapeSeq = ESCAPE_SEQUENCES[char]; + + if (!escapeSeq && isPrintable(char)) { + result += string[i]; + if (char >= 0x10000) result += string[i + 1]; + } else { + result += escapeSeq || encodeHex(char); + } + } + + return result; +} + +function writeFlowSequence(state, level, object) { + var _result = '', + _tag = state.tag, + index, + length, + value; + + for (index = 0, length = object.length; index < length; index += 1) { + value = object[index]; + + if (state.replacer) { + value = state.replacer.call(object, String(index), value); + } + + // Write only valid elements, put null instead of invalid elements. + if (writeNode(state, level, value, false, false) || + (typeof value === 'undefined' && + writeNode(state, level, null, false, false))) { + + if (_result !== '') _result += ',' + (!state.condenseFlow ? ' ' : ''); + _result += state.dump; + } + } + + state.tag = _tag; + state.dump = '[' + _result + ']'; +} + +function writeBlockSequence(state, level, object, compact) { + var _result = '', + _tag = state.tag, + index, + length, + value; + + for (index = 0, length = object.length; index < length; index += 1) { + value = object[index]; + + if (state.replacer) { + value = state.replacer.call(object, String(index), value); + } + + // Write only valid elements, put null instead of invalid elements. + if (writeNode(state, level + 1, value, true, true, false, true) || + (typeof value === 'undefined' && + writeNode(state, level + 1, null, true, true, false, true))) { + + if (!compact || _result !== '') { + _result += generateNextLine(state, level); + } + + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + _result += '-'; + } else { + _result += '- '; + } + + _result += state.dump; + } + } + + state.tag = _tag; + state.dump = _result || '[]'; // Empty sequence if no valid values. +} + +function writeFlowMapping(state, level, object) { + var _result = '', + _tag = state.tag, + objectKeyList = Object.keys(object), + index, + length, + objectKey, + objectValue, + pairBuffer; + + for (index = 0, length = objectKeyList.length; index < length; index += 1) { + + pairBuffer = ''; + if (_result !== '') pairBuffer += ', '; + + if (state.condenseFlow) pairBuffer += '"'; + + objectKey = objectKeyList[index]; + objectValue = object[objectKey]; + + if (state.replacer) { + objectValue = state.replacer.call(object, objectKey, objectValue); + } + + if (!writeNode(state, level, objectKey, false, false)) { + continue; // Skip this pair because of invalid key; + } + + if (state.dump.length > 1024) pairBuffer += '? '; + + pairBuffer += state.dump + (state.condenseFlow ? '"' : '') + ':' + (state.condenseFlow ? '' : ' '); + + if (!writeNode(state, level, objectValue, false, false)) { + continue; // Skip this pair because of invalid value. + } + + pairBuffer += state.dump; + + // Both key and value are valid. + _result += pairBuffer; + } + + state.tag = _tag; + state.dump = '{' + _result + '}'; +} + +function writeBlockMapping(state, level, object, compact) { + var _result = '', + _tag = state.tag, + objectKeyList = Object.keys(object), + index, + length, + objectKey, + objectValue, + explicitPair, + pairBuffer; + + // Allow sorting keys so that the output file is deterministic + if (state.sortKeys === true) { + // Default sorting + objectKeyList.sort(); + } else if (typeof state.sortKeys === 'function') { + // Custom sort function + objectKeyList.sort(state.sortKeys); + } else if (state.sortKeys) { + // Something is wrong + throw new exception('sortKeys must be a boolean or a function'); + } + + for (index = 0, length = objectKeyList.length; index < length; index += 1) { + pairBuffer = ''; + + if (!compact || _result !== '') { + pairBuffer += generateNextLine(state, level); + } + + objectKey = objectKeyList[index]; + objectValue = object[objectKey]; + + if (state.replacer) { + objectValue = state.replacer.call(object, objectKey, objectValue); + } + + if (!writeNode(state, level + 1, objectKey, true, true, true)) { + continue; // Skip this pair because of invalid key. + } + + explicitPair = (state.tag !== null && state.tag !== '?') || + (state.dump && state.dump.length > 1024); + + if (explicitPair) { + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + pairBuffer += '?'; + } else { + pairBuffer += '? '; + } + } + + pairBuffer += state.dump; + + if (explicitPair) { + pairBuffer += generateNextLine(state, level); + } + + if (!writeNode(state, level + 1, objectValue, true, explicitPair)) { + continue; // Skip this pair because of invalid value. + } + + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + pairBuffer += ':'; + } else { + pairBuffer += ': '; + } + + pairBuffer += state.dump; + + // Both key and value are valid. + _result += pairBuffer; + } + + state.tag = _tag; + state.dump = _result || '{}'; // Empty mapping if no valid pairs. +} + +function detectType(state, object, explicit) { + var _result, typeList, index, length, type, style; + + typeList = explicit ? state.explicitTypes : state.implicitTypes; + + for (index = 0, length = typeList.length; index < length; index += 1) { + type = typeList[index]; + + if ((type.instanceOf || type.predicate) && + (!type.instanceOf || ((typeof object === 'object') && (object instanceof type.instanceOf))) && + (!type.predicate || type.predicate(object))) { + + if (explicit) { + if (type.multi && type.representName) { + state.tag = type.representName(object); + } else { + state.tag = type.tag; + } + } else { + state.tag = '?'; + } + + if (type.represent) { + style = state.styleMap[type.tag] || type.defaultStyle; + + if (_toString.call(type.represent) === '[object Function]') { + _result = type.represent(object, style); + } else if (_hasOwnProperty.call(type.represent, style)) { + _result = type.represent[style](object, style); + } else { + throw new exception('!<' + type.tag + '> tag resolver accepts not "' + style + '" style'); + } + + state.dump = _result; + } + + return true; + } + } + + return false; +} + +// Serializes `object` and writes it to global `result`. +// Returns true on success, or false on invalid object. +// +function writeNode(state, level, object, block, compact, iskey, isblockseq) { + state.tag = null; + state.dump = object; + + if (!detectType(state, object, false)) { + detectType(state, object, true); + } + + var type = _toString.call(state.dump); + var inblock = block; + var tagStr; + + if (block) { + block = (state.flowLevel < 0 || state.flowLevel > level); + } + + var objectOrArray = type === '[object Object]' || type === '[object Array]', + duplicateIndex, + duplicate; + + if (objectOrArray) { + duplicateIndex = state.duplicates.indexOf(object); + duplicate = duplicateIndex !== -1; + } + + if ((state.tag !== null && state.tag !== '?') || duplicate || (state.indent !== 2 && level > 0)) { + compact = false; + } + + if (duplicate && state.usedDuplicates[duplicateIndex]) { + state.dump = '*ref_' + duplicateIndex; + } else { + if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) { + state.usedDuplicates[duplicateIndex] = true; + } + if (type === '[object Object]') { + if (block && (Object.keys(state.dump).length !== 0)) { + writeBlockMapping(state, level, state.dump, compact); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + state.dump; + } + } else { + writeFlowMapping(state, level, state.dump); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + ' ' + state.dump; + } + } + } else if (type === '[object Array]') { + if (block && (state.dump.length !== 0)) { + if (state.noArrayIndent && !isblockseq && level > 0) { + writeBlockSequence(state, level - 1, state.dump, compact); + } else { + writeBlockSequence(state, level, state.dump, compact); + } + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + state.dump; + } + } else { + writeFlowSequence(state, level, state.dump); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + ' ' + state.dump; + } + } + } else if (type === '[object String]') { + if (state.tag !== '?') { + writeScalar(state, state.dump, level, iskey, inblock); + } + } else if (type === '[object Undefined]') { + return false; + } else { + if (state.skipInvalid) return false; + throw new exception('unacceptable kind of an object to dump ' + type); + } + + if (state.tag !== null && state.tag !== '?') { + // Need to encode all characters except those allowed by the spec: + // + // [35] ns-dec-digit ::= [#x30-#x39] /* 0-9 */ + // [36] ns-hex-digit ::= ns-dec-digit + // | [#x41-#x46] /* A-F */ | [#x61-#x66] /* a-f */ + // [37] ns-ascii-letter ::= [#x41-#x5A] /* A-Z */ | [#x61-#x7A] /* a-z */ + // [38] ns-word-char ::= ns-dec-digit | ns-ascii-letter | “-” + // [39] ns-uri-char ::= “%” ns-hex-digit ns-hex-digit | ns-word-char | “#” + // | “;” | “/” | “?” | “:” | “@” | “&” | “=” | “+” | “$” | “,” + // | “_” | “.” | “!” | “~” | “*” | “'” | “(” | “)” | “[” | “]” + // + // Also need to encode '!' because it has special meaning (end of tag prefix). + // + tagStr = encodeURI( + state.tag[0] === '!' ? state.tag.slice(1) : state.tag + ).replace(/!/g, '%21'); + + if (state.tag[0] === '!') { + tagStr = '!' + tagStr; + } else if (tagStr.slice(0, 18) === 'tag:yaml.org,2002:') { + tagStr = '!!' + tagStr.slice(18); + } else { + tagStr = '!<' + tagStr + '>'; + } + + state.dump = tagStr + ' ' + state.dump; + } + } + + return true; +} + +function getDuplicateReferences(object, state) { + var objects = [], + duplicatesIndexes = [], + index, + length; + + inspectNode(object, objects, duplicatesIndexes); + + for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) { + state.duplicates.push(objects[duplicatesIndexes[index]]); + } + state.usedDuplicates = new Array(length); +} + +function inspectNode(object, objects, duplicatesIndexes) { + var objectKeyList, + index, + length; + + if (object !== null && typeof object === 'object') { + index = objects.indexOf(object); + if (index !== -1) { + if (duplicatesIndexes.indexOf(index) === -1) { + duplicatesIndexes.push(index); + } + } else { + objects.push(object); + + if (Array.isArray(object)) { + for (index = 0, length = object.length; index < length; index += 1) { + inspectNode(object[index], objects, duplicatesIndexes); + } + } else { + objectKeyList = Object.keys(object); + + for (index = 0, length = objectKeyList.length; index < length; index += 1) { + inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes); + } + } + } + } +} + +function dump$1(input, options) { + options = options || {}; + + var state = new State(options); + + if (!state.noRefs) getDuplicateReferences(input, state); + + var value = input; + + if (state.replacer) { + value = state.replacer.call({ '': value }, '', value); + } + + if (writeNode(state, 0, value, true, true)) return state.dump + '\n'; + + return ''; +} + +var dump_1 = dump$1; + +var dumper = { + dump: dump_1 +}; + +function renamed(from, to) { + return function () { + throw new Error('Function yaml.' + from + ' is removed in js-yaml 4. ' + + 'Use yaml.' + to + ' instead, which is now safe by default.'); + }; +} + + +var Type = type; +var Schema = schema; +var FAILSAFE_SCHEMA = failsafe; +var JSON_SCHEMA = json; +var CORE_SCHEMA = core; +var DEFAULT_SCHEMA = _default; +var load = loader.load; +var loadAll = loader.loadAll; +var dump = dumper.dump; +var YAMLException = exception; + +// Re-export all types in case user wants to create custom schema +var types = { + binary: binary, + float: float, + map: map, + null: _null, + pairs: pairs, + set: set, + timestamp: timestamp, + bool: bool, + int: int, + merge: merge, + omap: omap, + seq: seq, + str: str +}; + +// Removed functions from JS-YAML 3.0.x +var safeLoad = renamed('safeLoad', 'load'); +var safeLoadAll = renamed('safeLoadAll', 'loadAll'); +var safeDump = renamed('safeDump', 'dump'); + +var jsYaml = { + Type: Type, + Schema: Schema, + FAILSAFE_SCHEMA: FAILSAFE_SCHEMA, + JSON_SCHEMA: JSON_SCHEMA, + CORE_SCHEMA: CORE_SCHEMA, + DEFAULT_SCHEMA: DEFAULT_SCHEMA, + load: load, + loadAll: loadAll, + dump: dump, + YAMLException: YAMLException, + types: types, + safeLoad: safeLoad, + safeLoadAll: safeLoadAll, + safeDump: safeDump +}; + +const {defineComponent:_defineComponent$w} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$w,resolveComponent:_resolveComponent$w,withCtx:_withCtx$w,createVNode:_createVNode$w,unref:_unref$q,openBlock:_openBlock$w,createBlock:_createBlock$w} = await importShared('vue'); +const _sfc_main$w = /* @__PURE__ */ _defineComponent$w({ + __name: "ShowYamlDialog", + props: { + content: { + type: String, + required: true + }, + placeholder: String + }, + emits: ["close", "copyToClipboard"], + setup(__props, { emit: __emit }) { + const props = __props; + const readOnlyEditorOptions = { + readOnly: true, + enableBasicAutocompletion: true, + enableSnippets: true, + enableLiveAutocompletion: true, + showLineNumbers: true, + tabSize: 2 + }; + const emit = __emit; + return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent$w("v-card-title"); + const _component_v_card_text = _resolveComponent$w("v-card-text"); + const _component_v_spacer = _resolveComponent$w("v-spacer"); + const _component_v_btn = _resolveComponent$w("v-btn"); + const _component_v_card_actions = _resolveComponent$w("v-card-actions"); + const _component_v_card = _resolveComponent$w("v-card"); + const _component_v_dialog = _resolveComponent$w("v-dialog"); + return _openBlock$w(), _createBlock$w(_component_v_dialog, { + "model-value": true, + "max-width": "40rem", + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => emit("close")) + }, { + default: _withCtx$w(() => [ + _createVNode$w(_component_v_card, null, { + default: _withCtx$w(() => [ + _createVNode$w(_component_v_card_title, { class: "headline" }, { + default: _withCtx$w(() => _cache[3] || (_cache[3] = [ + _createTextVNode$w("YAML 配置") + ])), + _: 1 + }), + _createVNode$w(_component_v_card_text, { style: { "max-height": "600px", "overflow-y": "auto" } }, { + default: _withCtx$w(() => [ + _createVNode$w(_unref$q(VAceEditor), { + value: props.content, + lang: "yaml", + theme: "monokai", + options: readOnlyEditorOptions, + placeholder: __props.placeholder, + style: { "height": "30rem", "width": "100%", "margin-bottom": "16px" } + }, null, 8, ["value", "placeholder"]) + ]), + _: 1 + }), + _createVNode$w(_component_v_card_actions, null, { + default: _withCtx$w(() => [ + _createVNode$w(_component_v_spacer), + _createVNode$w(_component_v_btn, { + color: "primary", + onClick: _cache[0] || (_cache[0] = ($event) => emit("copyToClipboard", __props.content)) + }, { + default: _withCtx$w(() => _cache[4] || (_cache[4] = [ + _createTextVNode$w("复制") + ])), + _: 1 + }), + _createVNode$w(_component_v_btn, { + color: "primary", + onClick: _cache[1] || (_cache[1] = ($event) => emit("close")) + }, { + default: _withCtx$w(() => _cache[5] || (_cache[5] = [ + _createTextVNode$w("关闭") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$v} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$v,resolveComponent:_resolveComponent$v,withCtx:_withCtx$v,createVNode:_createVNode$v,createElementVNode:_createElementVNode$o,renderList:_renderList$a,Fragment:_Fragment$b,openBlock:_openBlock$v,createElementBlock:_createElementBlock$c,toDisplayString:_toDisplayString$s,createBlock:_createBlock$v,createCommentVNode:_createCommentVNode$o} = await importShared('vue'); + +const _hoisted_1$l = { class: "d-flex align-center justify-space-between mb-2" }; +const _hoisted_2$f = { + key: 0, + class: "text-caption text-grey text-center py-2" +}; +const {ref: ref$l,toRaw: toRaw$9,onMounted: onMounted$1} = await importShared('vue'); + +const _sfc_main$v = /* @__PURE__ */ _defineComponent$v({ + __name: "DataVisibilityDialog", + props: { + meta: { + type: Object, + required: true + }, + api: { + type: Object, + required: true + }, + endpoint: { + type: String, + required: true + }, + region: String, + presetIdentifiers: { + type: Array, + required: true + } + }, + emits: ["refresh", "show-snackbar", "show-error", "close"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const loading = ref$l(false); + const invisibleTo = ref$l(structuredClone(toRaw$9(props.meta?.invisible_to) || [])); + const selectedPresets = ref$l([]); + const customExpressions = ref$l([]); + const expressionTemplate = (id) => `identifier == '${id}'`; + const expressionRegex = /^identifier == '(.+)'$/; + onMounted$1(() => { + parseInvisibleTo(); + }); + function parseInvisibleTo() { + const presets = []; + const custom = []; + const currentInvisible = invisibleTo.value || []; + currentInvisible.forEach((expr) => { + const match = expr.match(expressionRegex); + if (match && props.presetIdentifiers.includes(match[1])) { + presets.push(match[1]); + } else { + custom.push(expr); + } + }); + selectedPresets.value = presets; + customExpressions.value = custom; + } + function addCustomExpression() { + customExpressions.value.push(""); + } + function removeCustomExpression(index) { + customExpressions.value.splice(index, 1); + } + async function updateDataVisibility() { + let newInvisibleTo; + newInvisibleTo = [ + ...selectedPresets.value.map((id) => expressionTemplate(id)), + ...customExpressions.value.filter((e) => e.trim() !== "") + ]; + invisibleTo.value = newInvisibleTo; + loading.value = true; + try { + const meta = structuredClone(toRaw$9(props.meta)); + meta.invisible_to = invisibleTo.value; + const result = await props.api.patch(props.endpoint, meta); + if (props.region) emit("refresh", [props.region]); + if (result?.success) { + emit("show-snackbar", { + show: true, + message: "可见性配置更新成功", + color: "success" + }); + emit("close"); + } else { + emit("show-error", "更新可见性配置失败: " + (result.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "更新可见性配置失败", + color: "error" + }); + } + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "更新可见性配置失败"); + } + } finally { + loading.value = false; + } + } + return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent$v("v-card-title"); + const _component_v_alert = _resolveComponent$v("v-alert"); + const _component_v_chip = _resolveComponent$v("v-chip"); + const _component_v_chip_group = _resolveComponent$v("v-chip-group"); + const _component_v_divider = _resolveComponent$v("v-divider"); + const _component_v_btn = _resolveComponent$v("v-btn"); + const _component_v_text_field = _resolveComponent$v("v-text-field"); + const _component_v_card_text = _resolveComponent$v("v-card-text"); + const _component_v_spacer = _resolveComponent$v("v-spacer"); + const _component_v_card_actions = _resolveComponent$v("v-card-actions"); + const _component_v_card = _resolveComponent$v("v-card"); + const _component_v_dialog = _resolveComponent$v("v-dialog"); + return _openBlock$v(), _createBlock$v(_component_v_dialog, { "max-width": "40rem" }, { + default: _withCtx$v(() => [ + _createVNode$v(_component_v_card, null, { + default: _withCtx$v(() => [ + _createVNode$v(_component_v_card_title, null, { + default: _withCtx$v(() => _cache[2] || (_cache[2] = [ + _createTextVNode$v(" 限制可见性 ") + ])), + _: 1 + }), + _createVNode$v(_component_v_card_text, null, { + default: _withCtx$v(() => [ + _createVNode$v(_component_v_alert, { + type: "info", + variant: "tonal", + class: "mb-4", + density: "compact" + }, { + default: _withCtx$v(() => _cache[3] || (_cache[3] = [ + _createTextVNode$v(" 配置数据项对哪些设备不可见。勾选预设设备或输入自定义表达式 (simpleeval)。 ") + ])), + _: 1 + }), + _cache[6] || (_cache[6] = _createElementVNode$o("div", { class: "text-subtitle-1 mb-2" }, "预设设备", -1)), + _createVNode$v(_component_v_chip_group, { + modelValue: selectedPresets.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedPresets.value = $event), + column: "", + multiple: "", + filter: "", + "selected-class": "text-primary" + }, { + default: _withCtx$v(() => [ + (_openBlock$v(true), _createElementBlock$c(_Fragment$b, null, _renderList$a(__props.presetIdentifiers, (id) => { + return _openBlock$v(), _createBlock$v(_component_v_chip, { + key: id, + value: id, + variant: "outlined", + filter: "" + }, { + default: _withCtx$v(() => [ + _createTextVNode$v(_toDisplayString$s(id), 1) + ]), + _: 2 + }, 1032, ["value"]); + }), 128)) + ]), + _: 1 + }, 8, ["modelValue"]), + _createVNode$v(_component_v_divider, { class: "my-4" }), + _createElementVNode$o("div", _hoisted_1$l, [ + _cache[5] || (_cache[5] = _createElementVNode$o("div", { class: "text-subtitle-1" }, "自定义表达式", -1)), + _createVNode$v(_component_v_btn, { + size: "small", + variant: "text", + color: "primary", + "prepend-icon": "mdi-plus", + onClick: addCustomExpression + }, { + default: _withCtx$v(() => _cache[4] || (_cache[4] = [ + _createTextVNode$v(" 添加 ") + ])), + _: 1 + }) + ]), + (_openBlock$v(true), _createElementBlock$c(_Fragment$b, null, _renderList$a(customExpressions.value.keys(), (index) => { + return _openBlock$v(), _createElementBlock$c("div", { + key: index, + class: "d-flex align-center mb-2" + }, [ + _createVNode$v(_component_v_text_field, { + modelValue: customExpressions.value[index], + "onUpdate:modelValue": ($event) => customExpressions.value[index] = $event, + label: "表达式", + variant: "outlined", + density: "compact", + "hide-details": "", + class: "flex-grow-1" + }, null, 8, ["modelValue", "onUpdate:modelValue"]), + _createVNode$v(_component_v_btn, { + icon: "mdi-delete", + variant: "text", + color: "error", + size: "small", + class: "ml-2", + onClick: ($event) => removeCustomExpression(index) + }, null, 8, ["onClick"]) + ]); + }), 128)), + customExpressions.value.length === 0 ? (_openBlock$v(), _createElementBlock$c("div", _hoisted_2$f, " 无自定义表达式 ")) : _createCommentVNode$o("", true) + ]), + _: 1 + }), + _createVNode$v(_component_v_card_actions, null, { + default: _withCtx$v(() => [ + _createVNode$v(_component_v_spacer), + _createVNode$v(_component_v_btn, { + color: "secondary", + onClick: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close")) + }, { + default: _withCtx$v(() => _cache[7] || (_cache[7] = [ + _createTextVNode$v("取消") + ])), + _: 1 + }), + _createVNode$v(_component_v_btn, { + color: "primary", + loading: loading.value, + onClick: updateDataVisibility + }, { + default: _withCtx$v(() => _cache[8] || (_cache[8] = [ + _createTextVNode$v("保存") + ])), + _: 1 + }, 8, ["loading"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const itemsPerPageOptions = [ + { title: "5", value: 5 }, + { title: "10", value: 10 }, + { title: "20", value: 20 }, + { title: "50", value: 50 }, + { title: "All", value: -1 } +]; +const defaultMetadata = { + source: "Manual", + disabled: false, + invisible_to: [], + remark: "", + time_modified: 0, + patched: false +}; +const defaultRule = { + type: "DOMAIN-SUFFIX", + payload: "", + action: "DIRECT", + priority: 0, + meta: { ...defaultMetadata }, + rule_string: "DOMAIN-SUFFIX,,DIRECT" +}; +const defaultProxy = { + name: "", + type: "ss", + server: "", + port: 443, + udp: false, + tfo: false, + mptcp: false, + tls: false, + "skip-cert-verify": false, + alpn: [], + "ws-opts": { + path: "/", + headers: {}, + "v2ray-http-upgrade": false, + "v2ray-http-upgrade-fast-open": false + }, + "http-opts": { method: "GET", path: ["/"], headers: {} }, + "h2-opts": { host: [], path: "/" }, + "grpc-opts": { "grpc-service-name": "" }, + smux: { + enabled: false, + protocol: "h2mux", + padding: false, + statistic: false, + "only-tcp": false, + "brutal-opts": { + enabled: false + } + }, + rescind: false +}; +const defaultProxyGroup = { + name: "", + type: "select", + url: "https://www.gstatic.com/generate_204", + lazy: true, + "disable-udp": false, + "include-all": false, + "include-all-proxies": false, + "include-all-providers": false, + "expected-status": "*", + hidden: false, + "max-failed-times": 5 +}; +const defaultHost = { + domain: "", + value: [], + using_cloudflare: false, + meta: { ...defaultMetadata } +}; +const defaultRuleProvider = { + type: "http", + interval: 600, + behavior: "classical", + format: "yaml", + "size-limit": 0, + payload: [] +}; + +const {defineComponent:_defineComponent$u} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$u,resolveComponent:_resolveComponent$u,withCtx:_withCtx$u,createVNode:_createVNode$u,mergeProps:_mergeProps$i,unref:_unref$p,openBlock:_openBlock$u,createBlock:_createBlock$u,createCommentVNode:_createCommentVNode$n,toDisplayString:_toDisplayString$r} = await importShared('vue'); +const _sfc_main$u = /* @__PURE__ */ _defineComponent$u({ + __name: "RuleActionMenu", + props: { + rule: { + type: Object, + required: true + }, + hideVisibility: { + type: Boolean, + default: false + } + }, + emits: ["edit", "delete", "changeStatus", "editVisibility"], + setup(__props, { emit: __emit }) { + const emit = __emit; + return (_ctx, _cache) => { + const _component_v_icon = _resolveComponent$u("v-icon"); + const _component_v_btn = _resolveComponent$u("v-btn"); + const _component_v_tooltip = _resolveComponent$u("v-tooltip"); + const _component_v_list_item_title = _resolveComponent$u("v-list-item-title"); + const _component_v_list_item = _resolveComponent$u("v-list-item"); + const _component_v_list = _resolveComponent$u("v-list"); + const _component_v_menu = _resolveComponent$u("v-menu"); + return _openBlock$u(), _createBlock$u(_component_v_menu, { "min-width": "120" }, { + activator: _withCtx$u(({ props }) => [ + _createVNode$u(_component_v_btn, _mergeProps$i({ + color: "secondary", + icon: "", + size: "small", + variant: "text" + }, props), { + default: _withCtx$u(() => [ + _createVNode$u(_component_v_icon, null, { + default: _withCtx$u(() => _cache[4] || (_cache[4] = [ + _createTextVNode$u("mdi-dots-vertical") + ])), + _: 1 + }) + ]), + _: 2 + }, 1040), + _unref$p(isSystemRule)(__props.rule) ? (_openBlock$u(), _createBlock$u(_component_v_tooltip, { + key: 0, + activator: "parent", + location: "top" + }, { + default: _withCtx$u(() => _cache[5] || (_cache[5] = [ + _createTextVNode$u(" 根据规则集自动添加 ") + ])), + _: 1 + })) : _createCommentVNode$n("", true) + ]), + default: _withCtx$u(() => [ + _createVNode$u(_component_v_list, { density: "compact" }, { + default: _withCtx$u(() => [ + _createVNode$u(_component_v_list_item, { + onClick: _cache[0] || (_cache[0] = ($event) => emit("changeStatus", !__props.rule.meta?.disabled)) + }, { + prepend: _withCtx$u(() => [ + _createVNode$u(_component_v_icon, { + size: "small", + color: __props.rule.meta?.disabled ? "success" : "grey" + }, { + default: _withCtx$u(() => [ + _createTextVNode$u(_toDisplayString$r(__props.rule.meta?.disabled ? "mdi-play-circle-outline" : "mdi-stop-circle-outline"), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + default: _withCtx$u(() => [ + _createVNode$u(_component_v_list_item_title, null, { + default: _withCtx$u(() => [ + _createTextVNode$u(_toDisplayString$r(__props.rule.meta?.disabled ? "启用" : "禁用"), 1) + ]), + _: 1 + }) + ]), + _: 1 + }), + !_unref$p(isSystemRule)(__props.rule) ? (_openBlock$u(), _createBlock$u(_component_v_list_item, { + key: 0, + onClick: _cache[1] || (_cache[1] = ($event) => emit("edit")) + }, { + prepend: _withCtx$u(() => [ + _createVNode$u(_component_v_icon, { + size: "small", + color: "primary" + }, { + default: _withCtx$u(() => _cache[6] || (_cache[6] = [ + _createTextVNode$u("mdi-file-edit-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$u(() => [ + _createVNode$u(_component_v_list_item_title, null, { + default: _withCtx$u(() => _cache[7] || (_cache[7] = [ + _createTextVNode$u("编辑") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$n("", true), + !__props.hideVisibility ? (_openBlock$u(), _createBlock$u(_component_v_list_item, { + key: 1, + onClick: _cache[2] || (_cache[2] = ($event) => emit("editVisibility")) + }, { + prepend: _withCtx$u(() => [ + _createVNode$u(_component_v_icon, { + size: "small", + color: "warning" + }, { + default: _withCtx$u(() => _cache[8] || (_cache[8] = [ + _createTextVNode$u("mdi-eye-off-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$u(() => [ + _createVNode$u(_component_v_list_item_title, null, { + default: _withCtx$u(() => _cache[9] || (_cache[9] = [ + _createTextVNode$u("隐藏") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$n("", true), + !_unref$p(isSystemRule)(__props.rule) ? (_openBlock$u(), _createBlock$u(_component_v_list_item, { + key: 2, + onClick: _cache[3] || (_cache[3] = ($event) => emit("delete")) + }, { + prepend: _withCtx$u(() => [ + _createVNode$u(_component_v_icon, { + size: "small", + color: "error" + }, { + default: _withCtx$u(() => _cache[10] || (_cache[10] = [ + _createTextVNode$u("mdi-trash-can-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$u(() => [ + _createVNode$u(_component_v_list_item_title, null, { + default: _withCtx$u(() => _cache[11] || (_cache[11] = [ + _createTextVNode$u("删除") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$n("", true) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$t} = await importShared('vue'); + +const {resolveComponent:_resolveComponent$t,createVNode:_createVNode$t,withCtx:_withCtx$t,Teleport:_Teleport$1,openBlock:_openBlock$t,createBlock:_createBlock$t,createCommentVNode:_createCommentVNode$m,unref:_unref$o,toDisplayString:_toDisplayString$q,createTextVNode:_createTextVNode$t,createElementVNode:_createElementVNode$n,Fragment:_Fragment$a,createElementBlock:_createElementBlock$b} = await importShared('vue'); + +const _hoisted_1$k = ["colspan"]; +const {ref: ref$k} = await importShared('vue'); +const ruleset$1 = "ruleset"; +const _sfc_main$t = /* @__PURE__ */ _defineComponent$t({ + __name: "RulesetRulesTable", + props: { + sortedRules: { + type: Array, + required: true + }, + page: { + type: Number, + required: true + }, + itemsPerPage: { + type: Number, + required: true + }, + rulesetPrefix: { + type: String, + required: true + }, + searchRule: String, + group: { + type: Boolean, + default: false + } + }, + emits: ["edit", "delete", "delete-batch", "reorder", "change-status", "change-status-batch", "editVisibility"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const headersRuleset = [ + { title: "", key: "handler", sortable: false, width: "1rem" }, + { title: "优先级", key: "priority", sortable: true, width: "3.5rem" }, + { title: "类型", key: "type", sortable: true }, + { title: "内容", key: "payload", sortable: true }, + { title: "出站", key: "action", sortable: true }, + { title: "规则集合", key: "name", value: "action", sortable: true }, + { title: "日期", key: "time_modified", sortable: true }, + { title: "", key: "status", sortable: false, width: "1rem" }, + { title: "", key: "actions", sortable: false, width: "1rem" } + ]; + const groupHeaders = [ + { title: "优先级", key: "priority", sortable: true, width: "3.5rem" }, + { title: "类型", key: "type", sortable: true }, + { title: "内容", key: "payload", sortable: true }, + { title: "日期", key: "time_modified", sortable: true }, + { title: "", key: "status", sortable: false, width: "1rem" }, + { title: "", key: "actions", sortable: false, width: "1rem" } + ]; + const groupBy = ref$k([ + { + key: "action" + } + ]); + const dragEnabled = ref$k(false); + const dragItem = ref$k(null); + const hoveredPriority = ref$k(-1); + const selected = ref$k([]); + function dragStart(event, priority) { + const item = props.sortedRules.find((r) => r.priority === priority); + if (!item) { + event.preventDefault?.(); + return; + } + dragItem.value = item; + if (event.dataTransfer) { + event.dataTransfer.effectAllowed = "move"; + } + } + function dragOver(event, priority) { + event.preventDefault(); + if (event.dataTransfer) { + event.dataTransfer.dropEffect = "move"; + } + hoveredPriority.value = priority; + } + function drop(event, targetPriority) { + if (dragItem.value && dragItem.value.priority !== targetPriority) { + emit("reorder", targetPriority, dragItem.value.priority, ruleset$1); + } + dragItem.value = null; + hoveredPriority.value = -1; + } + function dragEnd() { + dragItem.value = null; + hoveredPriority.value = -1; + } + function editRule(priority) { + emit("edit", priority, ruleset$1); + } + function deleteRule(priority) { + emit("delete", priority, ruleset$1); + } + function deleteSelected() { + if (selected.value.length > 0) { + emit("delete-batch", selected.value, ruleset$1); + selected.value = []; + } + } + function updateStatus(item, disabled) { + emit("change-status", item.priority, disabled, ruleset$1); + } + function changeBatchStatus(disabled) { + if (selected.value.length > 0) { + emit("change-status-batch", selected.value, disabled, ruleset$1); + selected.value = []; + } + } + const rowProps = (data) => { + const item = data.item; + return { + class: { + "drop-over": item.priority === hoveredPriority.value, + "dragging-item": dragItem.value?.priority === item.priority, + "list-row": true, + "text-grey": item.meta?.disabled + }, + draggable: dragEnabled.value, + onDragstart: (e) => dragStart(e, item.priority), + onDragover: (e) => dragOver(e, item.priority), + onDrop: (e) => drop(e, item.priority), + onDragend: dragEnd + }; + }; + return (_ctx, _cache) => { + const _component_v_btn = _resolveComponent$t("v-btn"); + const _component_v_btn_group = _resolveComponent$t("v-btn-group"); + const _component_v_chip = _resolveComponent$t("v-chip"); + const _component_v_icon = _resolveComponent$t("v-icon"); + const _component_v_data_table = _resolveComponent$t("v-data-table"); + return _openBlock$t(), _createElementBlock$b(_Fragment$a, null, [ + selected.value.length > 0 ? (_openBlock$t(), _createBlock$t(_Teleport$1, { + key: 0, + to: "#ruleset-rules-table-batch-actions" + }, [ + _createVNode$t(_component_v_btn_group, { + rounded: "", + variant: "tonal" + }, { + default: _withCtx$t(() => [ + _createVNode$t(_component_v_btn, { + color: "success", + "prepend-icon": "mdi-check", + size: "small", + onClick: _cache[0] || (_cache[0] = ($event) => changeBatchStatus(false)) + }), + _createVNode$t(_component_v_btn, { + color: "warning", + "prepend-icon": "mdi-close", + size: "small", + onClick: _cache[1] || (_cache[1] = ($event) => changeBatchStatus(true)) + }), + _createVNode$t(_component_v_btn, { + color: "error", + "prepend-icon": "mdi-trash-can-outline", + size: "small", + onClick: deleteSelected + }) + ]), + _: 1 + }) + ])) : _createCommentVNode$m("", true), + __props.group ? (_openBlock$t(), _createBlock$t(_component_v_data_table, { + key: 1, + modelValue: selected.value, + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => selected.value = $event), + "fixed-header": "", + class: "px-4", + headers: groupHeaders, + items: __props.sortedRules, + "group-by": groupBy.value, + search: __props.searchRule, + page: __props.page, + "items-per-page": __props.itemsPerPage, + "items-per-page-options": _unref$o(itemsPerPageOptions), + "item-key": "priority", + "item-value": "priority", + "show-select": "", + density: "compact", + "hide-default-footer": "" + }, { + "group-header": _withCtx$t(({ item, columns, toggleGroup, isGroupOpen }) => [ + _createElementVNode$n("tr", null, [ + _createElementVNode$n("td", { + colspan: columns.length + }, [ + _createVNode$t(_component_v_btn, { + icon: isGroupOpen(item) ? "mdi-chevron-down" : "mdi-chevron-right", + size: "small", + variant: "text", + onClick: ($event) => toggleGroup(item) + }, null, 8, ["icon", "onClick"]), + _createVNode$t(_component_v_chip, { + color: _unref$o(getActionColor)(item.value), + size: "small", + label: "" + }, { + default: _withCtx$t(() => [ + _createTextVNode$t(_toDisplayString$q(item.value), 1) + ]), + _: 2 + }, 1032, ["color"]) + ], 8, _hoisted_1$k) + ]) + ]), + "item.priority": _withCtx$t(({ item }) => [ + _createVNode$t(_component_v_chip, { + size: "x-small", + variant: "tonal", + color: "secondary", + class: "font-weight-bold" + }, { + default: _withCtx$t(() => [ + _createTextVNode$t(_toDisplayString$q(item.priority), 1) + ]), + _: 2 + }, 1024) + ]), + "item.type": _withCtx$t(({ item }) => [ + _createVNode$t(_component_v_chip, { + color: _unref$o(getRuleTypeColor)(item.type), + size: "small", + label: "", + variant: "tonal" + }, { + default: _withCtx$t(() => [ + _createTextVNode$t(_toDisplayString$q(item.type), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.payload": _withCtx$t(({ value }) => [ + _createElementVNode$n("small", null, _toDisplayString$q(value), 1) + ]), + "item.time_modified": _withCtx$t(({ item }) => [ + _createElementVNode$n("small", null, _toDisplayString$q(item.meta?.time_modified ? _unref$o(timestampToDate)(item.meta.time_modified) : ""), 1) + ]), + "item.status": _withCtx$t(({ item }) => [ + _createVNode$t(_component_v_icon, { + color: item.meta.disabled ? "grey" : "success" + }, { + default: _withCtx$t(() => [ + _createTextVNode$t(_toDisplayString$q(item.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.actions": _withCtx$t(({ item }) => [ + _createVNode$t(_sfc_main$u, { + rule: item, + "hide-visibility": "", + onEdit: ($event) => editRule(item.priority), + onDelete: ($event) => deleteRule(item.priority), + onChangeStatus: (disabled) => updateStatus(item, disabled) + }, null, 8, ["rule", "onEdit", "onDelete", "onChangeStatus"]) + ]), + _: 1 + }, 8, ["modelValue", "items", "group-by", "search", "page", "items-per-page", "items-per-page-options"])) : (_openBlock$t(), _createBlock$t(_component_v_data_table, { + key: 2, + modelValue: selected.value, + "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => selected.value = $event), + "fixed-header": "", + class: "px-4", + headers: headersRuleset, + items: __props.sortedRules, + search: __props.searchRule, + page: __props.page, + "items-per-page": __props.itemsPerPage, + "items-per-page-options": _unref$o(itemsPerPageOptions), + "item-key": "priority", + "item-value": "priority", + "show-select": "", + density: "compact", + "hide-default-footer": "", + "row-props": rowProps + }, { + "item.handler": _withCtx$t(({}) => [ + _createVNode$t(_component_v_icon, { + class: "drag-handle mr-1", + onMouseenter: _cache[3] || (_cache[3] = ($event) => dragEnabled.value = true), + onMouseleave: _cache[4] || (_cache[4] = ($event) => dragEnabled.value = false) + }, { + default: _withCtx$t(() => _cache[6] || (_cache[6] = [ + _createTextVNode$t("mdi-drag-horizontal-variant ") + ])), + _: 1 + }) + ]), + "item.priority": _withCtx$t(({ item }) => [ + _createVNode$t(_component_v_chip, { + size: "x-small", + variant: "tonal", + color: "secondary", + class: "font-weight-bold" + }, { + default: _withCtx$t(() => [ + _createTextVNode$t(_toDisplayString$q(item.priority), 1) + ]), + _: 2 + }, 1024) + ]), + "item.type": _withCtx$t(({ item }) => [ + _createVNode$t(_component_v_chip, { + color: _unref$o(getRuleTypeColor)(item.type), + size: "small", + label: "", + variant: "tonal" + }, { + default: _withCtx$t(() => [ + _createTextVNode$t(_toDisplayString$q(item.type), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.payload": _withCtx$t(({ value }) => [ + _createElementVNode$n("small", null, _toDisplayString$q(value), 1) + ]), + "item.action": _withCtx$t(({ item }) => [ + _createVNode$t(_component_v_chip, { + color: _unref$o(getActionColor)(item.action), + size: "small", + variant: "outlined", + pill: "" + }, { + default: _withCtx$t(() => [ + _createTextVNode$t(_toDisplayString$q(item.action), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.name": _withCtx$t(({ item }) => [ + _createElementVNode$n("small", null, _toDisplayString$q(__props.rulesetPrefix) + _toDisplayString$q(item.action), 1) + ]), + "item.time_modified": _withCtx$t(({ item }) => [ + _createElementVNode$n("small", null, _toDisplayString$q(item.meta?.time_modified ? _unref$o(timestampToDate)(item.meta.time_modified) : ""), 1) + ]), + "item.status": _withCtx$t(({ item }) => [ + _createVNode$t(_component_v_icon, { + color: item.meta.disabled ? "grey" : "success" + }, { + default: _withCtx$t(() => [ + _createTextVNode$t(_toDisplayString$q(item.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.actions": _withCtx$t(({ item }) => [ + _createVNode$t(_sfc_main$u, { + rule: item, + "hide-visibility": "", + onEdit: ($event) => editRule(item.priority), + onDelete: ($event) => deleteRule(item.priority), + onChangeStatus: (disabled) => updateStatus(item, disabled) + }, null, 8, ["rule", "onEdit", "onDelete", "onChangeStatus"]) + ]), + _: 1 + }, 8, ["modelValue", "items", "search", "page", "items-per-page", "items-per-page-options"])) + ], 64); + }; + } +}); + +const {defineComponent:_defineComponent$s} = await importShared('vue'); + +const {toDisplayString:_toDisplayString$p,createTextVNode:_createTextVNode$s,resolveComponent:_resolveComponent$s,withCtx:_withCtx$s,createVNode:_createVNode$s,createElementVNode:_createElementVNode$m,unref:_unref$n,openBlock:_openBlock$s,createBlock:_createBlock$s} = await importShared('vue'); + +const _hoisted_1$j = { class: "d-flex justify-space-between align-center px-4 pt-3" }; +const _hoisted_2$e = ["title"]; +const _sfc_main$s = /* @__PURE__ */ _defineComponent$s({ + __name: "RuleCard", + props: { + rule: { + type: Object, + required: true + }, + ruleset: { + type: Object, + required: true + } + }, + emits: ["edit", "delete", "change-status"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + function editRule(priority) { + emit("edit", priority, props.ruleset); + } + function deleteRule(priority) { + emit("delete", priority, props.ruleset); + } + function updateStatus(disabled) { + emit("change-status", props.rule.priority, disabled, props.ruleset); + } + return (_ctx, _cache) => { + const _component_v_chip = _resolveComponent$s("v-chip"); + const _component_v_col = _resolveComponent$s("v-col"); + const _component_v_row = _resolveComponent$s("v-row"); + const _component_v_card_text = _resolveComponent$s("v-card-text"); + const _component_v_divider = _resolveComponent$s("v-divider"); + const _component_v_icon = _resolveComponent$s("v-icon"); + const _component_v_spacer = _resolveComponent$s("v-spacer"); + const _component_v_card_actions = _resolveComponent$s("v-card-actions"); + const _component_v_card = _resolveComponent$s("v-card"); + return _openBlock$s(), _createBlock$s(_component_v_card, { + rounded: "lg", + elevation: "2", + class: "rule-card h-100 transition-swing", + variant: "tonal" + }, { + default: _withCtx$s(() => [ + _createElementVNode$m("div", _hoisted_1$j, [ + _createVNode$s(_component_v_chip, { + variant: "flat", + color: "secondary", + class: "font-weight-bold mr-2", + size: "small" + }, { + default: _withCtx$s(() => [ + _createTextVNode$s(_toDisplayString$p(__props.rule.priority), 1) + ]), + _: 1 + }) + ]), + _createVNode$s(_component_v_card_text, { class: "pt-2 pb-4" }, { + default: _withCtx$s(() => [ + _createVNode$s(_component_v_row, { + "no-gutters": "", + class: "mb-2 align-center" + }, { + default: _withCtx$s(() => [ + _createVNode$s(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$s(() => _cache[2] || (_cache[2] = [ + _createTextVNode$s("类型") + ])), + _: 1 + }), + _createVNode$s(_component_v_col, { cols: "9" }, { + default: _withCtx$s(() => [ + _createVNode$s(_component_v_chip, { + color: _unref$n(getRuleTypeColor)(__props.rule.type), + size: "x-small", + label: "", + variant: "tonal", + class: "font-weight-medium" + }, { + default: _withCtx$s(() => [ + _createTextVNode$s(_toDisplayString$p(__props.rule.type), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$s(_component_v_row, { + "no-gutters": "", + class: "mb-2 align-center" + }, { + default: _withCtx$s(() => [ + _createVNode$s(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$s(() => _cache[3] || (_cache[3] = [ + _createTextVNode$s("内容") + ])), + _: 1 + }), + _createVNode$s(_component_v_col, { + cols: "9", + class: "text-body-2 text-truncate font-weight-bold" + }, { + default: _withCtx$s(() => [ + _createElementVNode$m("span", { + title: __props.rule.payload + }, _toDisplayString$p(__props.rule.payload), 9, _hoisted_2$e) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$s(_component_v_row, { + "no-gutters": "", + class: "align-center" + }, { + default: _withCtx$s(() => [ + _createVNode$s(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$s(() => _cache[4] || (_cache[4] = [ + _createTextVNode$s("出站") + ])), + _: 1 + }), + _createVNode$s(_component_v_col, { cols: "9" }, { + default: _withCtx$s(() => [ + _createVNode$s(_component_v_chip, { + color: _unref$n(getActionColor)(__props.rule.action), + size: "x-small", + variant: "outlined", + class: "font-weight-medium" + }, { + default: _withCtx$s(() => [ + _createTextVNode$s(_toDisplayString$p(__props.rule.action), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$s(_component_v_divider), + _createVNode$s(_component_v_card_actions, null, { + default: _withCtx$s(() => [ + _createVNode$s(_component_v_icon, { + color: __props.rule.meta.disabled ? "grey" : "success" + }, { + default: _withCtx$s(() => [ + _createTextVNode$s(_toDisplayString$p(__props.rule.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 1 + }, 8, ["color"]), + _createVNode$s(_component_v_spacer), + _createVNode$s(_sfc_main$u, { + rule: __props.rule, + onEdit: _cache[0] || (_cache[0] = ($event) => editRule(__props.rule.priority)), + onDelete: _cache[1] || (_cache[1] = ($event) => deleteRule(__props.rule.priority)), + onChangeStatus: updateStatus + }, null, 8, ["rule"]) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const RuleCard = /* @__PURE__ */ _export_sfc(_sfc_main$s, [["__scopeId", "data-v-da4a3497"]]); + +/** + * lodash (Custom Build) + * Build: `lodash modularize exports="npm" -o ./` + * Copyright jQuery Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ + +/** Used as the `TypeError` message for "Functions" methods. */ +var FUNC_ERROR_TEXT = 'Expected a function'; + +/** Used as references for various `Number` constants. */ +var NAN = 0 / 0; + +/** `Object#toString` result references. */ +var symbolTag = '[object Symbol]'; + +/** Used to match leading and trailing whitespace. */ +var reTrim = /^\s+|\s+$/g; + +/** Used to detect bad signed hexadecimal string values. */ +var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; + +/** Used to detect binary string values. */ +var reIsBinary = /^0b[01]+$/i; + +/** Used to detect octal string values. */ +var reIsOctal = /^0o[0-7]+$/i; + +/** Built-in method references without a dependency on `root`. */ +var freeParseInt = parseInt; + +/** Detect free variable `global` from Node.js. */ +var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; + +/** Detect free variable `self`. */ +var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + +/** Used as a reference to the global object. */ +var root = freeGlobal || freeSelf || Function('return this')(); + +/** Used for built-in method references. */ +var objectProto = Object.prototype; + +/** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ +var objectToString = objectProto.toString; + +/* Built-in method references for those with the same name as other `lodash` methods. */ +var nativeMax = Math.max, + nativeMin = Math.min; + +/** + * Gets the timestamp of the number of milliseconds that have elapsed since + * the Unix epoch (1 January 1970 00:00:00 UTC). + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Date + * @returns {number} Returns the timestamp. + * @example + * + * _.defer(function(stamp) { + * console.log(_.now() - stamp); + * }, _.now()); + * // => Logs the number of milliseconds it took for the deferred invocation. + */ +var now = function() { + return root.Date.now(); +}; + +/** + * Creates a debounced function that delays invoking `func` until after `wait` + * milliseconds have elapsed since the last time the debounced function was + * invoked. The debounced function comes with a `cancel` method to cancel + * delayed `func` invocations and a `flush` method to immediately invoke them. + * Provide `options` to indicate whether `func` should be invoked on the + * leading and/or trailing edge of the `wait` timeout. The `func` is invoked + * with the last arguments provided to the debounced function. Subsequent + * calls to the debounced function return the result of the last `func` + * invocation. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is + * invoked on the trailing edge of the timeout only if the debounced function + * is invoked more than once during the `wait` timeout. + * + * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred + * until to the next tick, similar to `setTimeout` with a timeout of `0`. + * + * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) + * for details over the differences between `_.debounce` and `_.throttle`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to debounce. + * @param {number} [wait=0] The number of milliseconds to delay. + * @param {Object} [options={}] The options object. + * @param {boolean} [options.leading=false] + * Specify invoking on the leading edge of the timeout. + * @param {number} [options.maxWait] + * The maximum time `func` is allowed to be delayed before it's invoked. + * @param {boolean} [options.trailing=true] + * Specify invoking on the trailing edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * // Avoid costly calculations while the window size is in flux. + * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); + * + * // Invoke `sendMail` when clicked, debouncing subsequent calls. + * jQuery(element).on('click', _.debounce(sendMail, 300, { + * 'leading': true, + * 'trailing': false + * })); + * + * // Ensure `batchLog` is invoked once after 1 second of debounced calls. + * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); + * var source = new EventSource('/stream'); + * jQuery(source).on('message', debounced); + * + * // Cancel the trailing debounced invocation. + * jQuery(window).on('popstate', debounced.cancel); + */ +function debounce(func, wait, options) { + var lastArgs, + lastThis, + maxWait, + result, + timerId, + lastCallTime, + lastInvokeTime = 0, + leading = false, + maxing = false, + trailing = true; + + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + wait = toNumber(wait) || 0; + if (isObject(options)) { + leading = !!options.leading; + maxing = 'maxWait' in options; + maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; + trailing = 'trailing' in options ? !!options.trailing : trailing; + } + + function invokeFunc(time) { + var args = lastArgs, + thisArg = lastThis; + + lastArgs = lastThis = undefined; + lastInvokeTime = time; + result = func.apply(thisArg, args); + return result; + } + + function leadingEdge(time) { + // Reset any `maxWait` timer. + lastInvokeTime = time; + // Start the timer for the trailing edge. + timerId = setTimeout(timerExpired, wait); + // Invoke the leading edge. + return leading ? invokeFunc(time) : result; + } + + function remainingWait(time) { + var timeSinceLastCall = time - lastCallTime, + timeSinceLastInvoke = time - lastInvokeTime, + result = wait - timeSinceLastCall; + + return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result; + } + + function shouldInvoke(time) { + var timeSinceLastCall = time - lastCallTime, + timeSinceLastInvoke = time - lastInvokeTime; + + // Either this is the first call, activity has stopped and we're at the + // trailing edge, the system time has gone backwards and we're treating + // it as the trailing edge, or we've hit the `maxWait` limit. + return (lastCallTime === undefined || (timeSinceLastCall >= wait) || + (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); + } + + function timerExpired() { + var time = now(); + if (shouldInvoke(time)) { + return trailingEdge(time); + } + // Restart the timer. + timerId = setTimeout(timerExpired, remainingWait(time)); + } + + function trailingEdge(time) { + timerId = undefined; + + // Only invoke if we have `lastArgs` which means `func` has been + // debounced at least once. + if (trailing && lastArgs) { + return invokeFunc(time); + } + lastArgs = lastThis = undefined; + return result; + } + + function cancel() { + if (timerId !== undefined) { + clearTimeout(timerId); + } + lastInvokeTime = 0; + lastArgs = lastCallTime = lastThis = timerId = undefined; + } + + function flush() { + return timerId === undefined ? result : trailingEdge(now()); + } + + function debounced() { + var time = now(), + isInvoking = shouldInvoke(time); + + lastArgs = arguments; + lastThis = this; + lastCallTime = time; + + if (isInvoking) { + if (timerId === undefined) { + return leadingEdge(lastCallTime); + } + if (maxing) { + // Handle invocations in a tight loop. + timerId = setTimeout(timerExpired, wait); + return invokeFunc(lastCallTime); + } + } + if (timerId === undefined) { + timerId = setTimeout(timerExpired, wait); + } + return result; + } + debounced.cancel = cancel; + debounced.flush = flush; + return debounced; +} + +/** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ +function isObject(value) { + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} + +/** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} + +/** + * Checks if `value` is classified as a `Symbol` primitive or object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. + * @example + * + * _.isSymbol(Symbol.iterator); + * // => true + * + * _.isSymbol('abc'); + * // => false + */ +function isSymbol(value) { + return typeof value == 'symbol' || + (isObjectLike(value) && objectToString.call(value) == symbolTag); +} + +/** + * Converts `value` to a number. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to process. + * @returns {number} Returns the number. + * @example + * + * _.toNumber(3.2); + * // => 3.2 + * + * _.toNumber(Number.MIN_VALUE); + * // => 5e-324 + * + * _.toNumber(Infinity); + * // => Infinity + * + * _.toNumber('3.2'); + * // => 3.2 + */ +function toNumber(value) { + if (typeof value == 'number') { + return value; + } + if (isSymbol(value)) { + return NAN; + } + if (isObject(value)) { + var other = typeof value.valueOf == 'function' ? value.valueOf() : value; + value = isObject(other) ? (other + '') : other; + } + if (typeof value != 'string') { + return value === 0 ? value : +value; + } + value = value.replace(reTrim, ''); + var isBinary = reIsBinary.test(value); + return (isBinary || reIsOctal.test(value)) + ? freeParseInt(value.slice(2), isBinary ? 2 : 8) + : (reIsBadHex.test(value) ? NAN : +value); +} + +var lodash_debounce = debounce; + +const debounce$1 = /*@__PURE__*/getDefaultExportFromCjs(lodash_debounce); + +const {defineComponent:_defineComponent$r} = await importShared('vue'); + +const {toDisplayString:_toDisplayString$o,createTextVNode:_createTextVNode$r,resolveComponent:_resolveComponent$r,withCtx:_withCtx$r,createVNode:_createVNode$r,openBlock:_openBlock$r,createBlock:_createBlock$r,createCommentVNode:_createCommentVNode$l,withModifiers:_withModifiers$4} = await importShared('vue'); + +const {ref: ref$j,computed: computed$8,toRaw: toRaw$8} = await importShared('vue'); +const _sfc_main$r = /* @__PURE__ */ _defineComponent$r({ + __name: "RuleDialog", + props: { + // 父组件传递的规则数据 + initialRule: { + type: Object, + required: true + }, + isAdding: { + type: Boolean, + default: true + }, + editingType: { + type: String, + default: "top" + }, + ruleProviderNames: { + type: Array, + default: () => [] + }, + geoRules: { + type: Object, + default: () => ({ geoip: [], geosite: [] }) + }, + customOutbounds: { + type: Array, + default: () => [] + }, + api: { + type: Object, + required: true + } + }, + emits: ["close", "refresh", "show-snackbar", "show-error"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const rule = ref$j(structuredClone(toRaw$8(props.initialRule))); + const loading = ref$j(false); + const ruleForm = ref$j(null); + const geoSearch = ref$j(""); + const geoIPSearch = ref$j(""); + const geoFilterLoading = ref$j(false); + const filteredGeoItems = ref$j([]); + const ruleTypes = computed$8(() => { + const allTypes = [ + "DOMAIN", + "DOMAIN-SUFFIX", + "DOMAIN-KEYWORD", + "DOMAIN-REGEX", + "DOMAIN-WILDCARD", + "GEOSITE", + "GEOIP", + "IP-CIDR", + "IP-CIDR6", + "IP-SUFFIX", + "IP-ASN", + "SRC-GEOIP", + "SRC-IP-ASN", + "SRC-IP-CIDR", + "SRC-IP-SUFFIX", + "DST-PORT", + "SRC-PORT", + "IN-PORT", + "IN-TYPE", + "IN-USER", + "IN-NAME", + "PROCESS-PATH", + "PROCESS-PATH-REGEX", + "PROCESS-NAME", + "PROCESS-NAME-REGEX", + "UID", + "NETWORK", + "DSCP", + "RULE-SET", + "AND", + "OR", + "NOT", + "SUB-RULE", + "MATCH" + ]; + if (props.editingType === "ruleset") { + return allTypes.filter((type) => !["SUB-RULE", "RULE-SET"].includes(type)); + } + return allTypes; + }); + const showAdditionalParams = computed$8(() => { + return ["IP-CIDR", "IP-CIDR6", "IP-ASN", "GEOIP"].includes(rule.value.type); + }); + const onGeoSearch = (val) => { + geoSearch.value = val; + performGeoSiteFilter(val); + }; + const onGeoIPSearch = (val) => { + geoIPSearch.value = val; + performGeoIPFilter(val); + }; + const performGeoSiteFilter = debounce$1((val) => { + if (!val) { + filteredGeoItems.value = []; + geoFilterLoading.value = false; + return; + } + geoFilterLoading.value = true; + filteredGeoItems.value = props.geoRules.geosite.filter( + (item) => item.toLowerCase().includes(val.toLowerCase()) + ); + geoFilterLoading.value = false; + }, 100); + const performGeoIPFilter = debounce$1((val) => { + if (!val) { + filteredGeoItems.value = []; + geoFilterLoading.value = false; + return; + } + geoFilterLoading.value = true; + filteredGeoItems.value = props.geoRules.geoip.filter( + (item) => item.toLowerCase().includes(val.toLowerCase()) + ); + geoFilterLoading.value = false; + }, 200); + const onGeoSiteBlur = () => { + if (!filteredGeoItems.value.includes(geoSearch.value)) { + rule.value.payload = geoSearch.value; + } + }; + const onGeoIPBlur = () => { + if (!filteredGeoItems.value.includes(geoIPSearch.value)) { + rule.value.payload = geoIPSearch.value; + } + }; + const actions = computed$8(() => [ + "DIRECT", + "REJECT", + "REJECT-DROP", + "PASS", + "COMPATIBLE", + ...props.customOutbounds.map((outbound) => outbound) + ]); + const additionalParamOptions = ref$j([ + { title: "无", value: "" }, + { title: "no-resolve", value: "no-resolve" }, + { title: "src", value: "src" } + ]); + const payloadRules = computed$8(() => { + return [ + (v) => { + if (rule.value.type === "MATCH") { + return true; + } + return !!v || "内容不能为空"; + } + ]; + }); + async function saveRule() { + const { valid } = await ruleForm.value.validate(); + if (!valid) return; + loading.value = true; + try { + if (rule.value?.payload) { + rule.value.payload = rule.value.payload.trim(); + } + const requestData = { ...rule.value }; + const priority = props.isAdding ? "" : `/${props.initialRule.priority}`; + const method = props.isAdding ? "post" : "patch"; + const result = await props.api[method]( + `/plugin/ClashRuleProvider/rules/${props.editingType}${priority}`, + requestData + ); + if (!result.success) { + emit("show-error", "保存规则失败: " + (result.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "保存规则合失败", + color: "error" + }); + return; + } + emit("close"); + emit("refresh", ["top", "ruleset"]); + emit("show-snackbar", { + show: true, + message: props.isAdding ? "规则添加成功" : "规则更新成功", + color: "success" + }); + } catch (err) { + if (err instanceof Error) emit("show-error", "保存规则失败: " + (err.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "保存规则失败", + color: "error" + }); + } finally { + loading.value = false; + } + } + return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent$r("v-card-title"); + const _component_v_select = _resolveComponent$r("v-select"); + const _component_v_autocomplete = _resolveComponent$r("v-autocomplete"); + const _component_v_combobox = _resolveComponent$r("v-combobox"); + const _component_v_text_field = _resolveComponent$r("v-text-field"); + const _component_v_card_text = _resolveComponent$r("v-card-text"); + const _component_v_spacer = _resolveComponent$r("v-spacer"); + const _component_v_btn = _resolveComponent$r("v-btn"); + const _component_v_card_actions = _resolveComponent$r("v-card-actions"); + const _component_v_card = _resolveComponent$r("v-card"); + const _component_v_form = _resolveComponent$r("v-form"); + const _component_v_dialog = _resolveComponent$r("v-dialog"); + return _openBlock$r(), _createBlock$r(_component_v_dialog, { "max-width": "40rem" }, { + default: _withCtx$r(() => [ + _createVNode$r(_component_v_form, { + ref_key: "ruleForm", + ref: ruleForm, + onSubmit: _withModifiers$4(saveRule, ["prevent"]) + }, { + default: _withCtx$r(() => [ + _createVNode$r(_component_v_card, null, { + default: _withCtx$r(() => [ + _createVNode$r(_component_v_card_title, null, { + default: _withCtx$r(() => [ + _createTextVNode$r(_toDisplayString$o(__props.isAdding ? "添加规则" : "编辑规则"), 1) + ]), + _: 1 + }), + _createVNode$r(_component_v_card_text, null, { + default: _withCtx$r(() => [ + _createVNode$r(_component_v_select, { + modelValue: rule.value.type, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => rule.value.type = $event), + items: ruleTypes.value, + label: "规则类型", + required: "", + class: "mb-4" + }, null, 8, ["modelValue", "items"]), + rule.value.type === "RULE-SET" ? (_openBlock$r(), _createBlock$r(_component_v_select, { + key: 0, + modelValue: rule.value.payload, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => rule.value.payload = $event), + items: props.ruleProviderNames, + label: "选择规则集", + required: "", + rules: [(v) => !!v || "请选择一个有效的规则集"], + class: "mb-4" + }, null, 8, ["modelValue", "items", "rules"])) : rule.value.type === "GEOSITE" ? (_openBlock$r(), _createBlock$r(_component_v_autocomplete, { + key: 1, + modelValue: rule.value.payload, + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => rule.value.payload = $event), + search: geoSearch.value, + items: filteredGeoItems.value, + loading: geoFilterLoading.value, + "hide-no-data": "", + "hide-selected": "", + label: "内容", + "no-filter": "", + solo: "", + "custom-filter": () => true, + clearable: "", + class: "mb-4", + rules: payloadRules.value, + "onUpdate:search": onGeoSearch, + onBlur: onGeoSiteBlur + }, null, 8, ["modelValue", "search", "items", "loading", "rules"])) : rule.value.type === "GEOIP" ? (_openBlock$r(), _createBlock$r(_component_v_autocomplete, { + key: 2, + modelValue: rule.value.payload, + "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => rule.value.payload = $event), + search: geoIPSearch.value, + items: filteredGeoItems.value, + loading: geoFilterLoading.value, + "hide-no-data": "", + "hide-selected": "", + label: "内容", + "no-filter": "", + solo: "", + "custom-filter": () => true, + clearable: "", + class: "mb-4", + rules: payloadRules.value, + "onUpdate:search": onGeoIPSearch, + onBlur: onGeoIPBlur + }, null, 8, ["modelValue", "search", "items", "loading", "rules"])) : rule.value.type === "AND" || rule.value.type === "OR" || rule.value.type === "NOT" ? (_openBlock$r(), _createBlock$r(_component_v_combobox, { + key: 3, + modelValue: rule.value.conditions, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => rule.value.conditions = $event), + label: "条件", + multiple: "", + chips: "", + hint: "「(DOMAIN,baidu.com)」,「(NETWORK,TCP)」", + clearable: "", + required: "", + class: "mb-4" + }, null, 8, ["modelValue"])) : rule.value.type === "SUB-RULE" ? (_openBlock$r(), _createBlock$r(_component_v_text_field, { + key: 4, + modelValue: rule.value.condition, + "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => rule.value.condition = $event), + label: "条件", + required: "", + placeholder: "(AND,(DOMAIN,baidu.com),(NETWORK,TCP))", + rules: payloadRules.value, + class: "mb-4" + }, null, 8, ["modelValue", "rules"])) : (_openBlock$r(), _createBlock$r(_component_v_text_field, { + key: 5, + modelValue: rule.value.payload, + "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => rule.value.payload = $event), + label: "内容", + required: "", + rules: payloadRules.value, + class: "mb-4" + }, null, 8, ["modelValue", "rules"])), + rule.value.type === "SUB-RULE" ? (_openBlock$r(), _createBlock$r(_component_v_text_field, { + key: 6, + modelValue: rule.value.action, + "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => rule.value.action = $event), + label: "子规则", + required: "", + class: "mb-4" + }, null, 8, ["modelValue"])) : (_openBlock$r(), _createBlock$r(_component_v_select, { + key: 7, + modelValue: rule.value.action, + "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => rule.value.action = $event), + items: actions.value, + label: "出站", + required: "", + class: "mb-4" + }, null, 8, ["modelValue", "items"])), + showAdditionalParams.value ? (_openBlock$r(), _createBlock$r(_component_v_select, { + key: 8, + modelValue: rule.value.additional_params, + "onUpdate:modelValue": _cache[9] || (_cache[9] = ($event) => rule.value.additional_params = $event), + label: "附加参数", + items: additionalParamOptions.value, + clearable: "", + hint: "可选参数", + "persistent-hint": "", + class: "mb-4" + }, null, 8, ["modelValue", "items"])) : _createCommentVNode$l("", true), + _createVNode$r(_component_v_text_field, { + modelValue: rule.value.priority, + "onUpdate:modelValue": _cache[10] || (_cache[10] = ($event) => rule.value.priority = $event), + modelModifiers: { number: true }, + type: "number", + label: "优先级", + hint: "数字越小优先级越高", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$r(_component_v_card_actions, null, { + default: _withCtx$r(() => [ + _createVNode$r(_component_v_spacer), + _createVNode$r(_component_v_btn, { + color: "secondary", + onClick: _cache[11] || (_cache[11] = ($event) => emit("close")) + }, { + default: _withCtx$r(() => _cache[12] || (_cache[12] = [ + _createTextVNode$r("取消") + ])), + _: 1 + }), + _createVNode$r(_component_v_btn, { + color: "primary", + type: "submit", + loading: loading.value + }, { + default: _withCtx$r(() => _cache[13] || (_cache[13] = [ + _createTextVNode$r("保存 ") + ])), + _: 1 + }, 8, ["loading"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 512) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$q} = await importShared('vue'); + +const {resolveComponent:_resolveComponent$q,createVNode:_createVNode$q,withCtx:_withCtx$q,createElementVNode:_createElementVNode$l,renderList:_renderList$9,Fragment:_Fragment$9,openBlock:_openBlock$q,createElementBlock:_createElementBlock$a,createBlock:_createBlock$q,unref:_unref$m,toDisplayString:_toDisplayString$n,createTextVNode:_createTextVNode$q,mergeProps:_mergeProps$h,createCommentVNode:_createCommentVNode$k} = await importShared('vue'); + +const _hoisted_1$i = { class: "mb-2 position-relative" }; +const _hoisted_2$d = { class: "pa-4" }; +const _hoisted_3$c = { class: "d-none d-sm-flex clash-data-table" }; +const _hoisted_4$b = { class: "d-sm-none" }; +const _hoisted_5$8 = { + class: "pa-4", + style: { "min-height": "4rem" } +}; +const {computed: computed$7,ref: ref$i,toRaw: toRaw$7} = await importShared('vue'); +const _sfc_main$q = /* @__PURE__ */ _defineComponent$q({ + __name: "RulesetRulesTab", + props: { + rules: {}, + rulesetPrefix: {}, + api: {}, + ruleProviderNames: {}, + geoRules: {}, + customOutbounds: {} + }, + emits: ["refresh", "show-snackbar", "show-error"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const searchRulesetRule = ref$i(""); + const pageRuleset = ref$i(1); + const itemsPerPageRuleset = ref$i(10); + const loading = ref$i(false); + const group = ref$i(false); + const ruleDialogVisible = ref$i(false); + const editingPriority = ref$i(null); + const currentRule = ref$i({ ...defaultRule }); + const editingType = ref$i("ruleset"); + const filteredRulesetRules = computed$7(() => { + if (!searchRulesetRule.value) return props.rules; + const keyword = searchRulesetRule.value.toLowerCase(); + return props.rules.filter( + (item) => Object.values(item).some((val) => String(val).toLowerCase().includes(keyword)) + ); + }); + const pageCountRuleset = computed$7(() => { + if (itemsPerPageRuleset.value === -1) { + return 1; + } + return Math.ceil(filteredRulesetRules.value.length / itemsPerPageRuleset.value); + }); + const paginatedRulesetRules = computed$7(() => { + const start = (pageRuleset.value - 1) * itemsPerPageRuleset.value; + const end = start + itemsPerPageRuleset.value; + return filteredRulesetRules.value.slice(start, end); + }); + function openAddRuleDialog() { + editingPriority.value = null; + const nextPriority = props.rules.length > 0 ? props.rules[props.rules.length - 1].priority + 1 : 0; + currentRule.value = { ...defaultRule }; + currentRule.value.priority = nextPriority; + ruleDialogVisible.value = true; + } + function editRule(priority) { + const rule = props.rules.find((r) => r.priority === priority); + if (rule) { + editingPriority.value = priority; + currentRule.value = structuredClone(toRaw$7(rule)); + ruleDialogVisible.value = true; + } + } + async function deleteRule(priority) { + loading.value = true; + try { + await props.api.delete(`/plugin/ClashRuleProvider/rules/ruleset/${priority}`); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "删除规则失败"); + } + } finally { + loading.value = false; + } + } + async function deleteRules(priorities) { + loading.value = true; + try { + await props.api.delete("/plugin/ClashRuleProvider/rules/ruleset", { + data: { rules_priority: priorities } + }); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "批量删除规则失败"); + } + } finally { + loading.value = false; + } + } + async function handleReorderRule(targetPriority, movedPriority) { + loading.value = true; + try { + await props.api.put(`/plugin/ClashRuleProvider/reorder-rules/ruleset/${targetPriority}`, { + moved_priority: movedPriority + }); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "重排序失败"); + } + } finally { + loading.value = false; + } + } + async function handleStatusChange(priority, disabled) { + loading.value = true; + try { + await props.api.post(`/plugin/ClashRuleProvider/rules/ruleset/metadata/disabled`, { + [priority]: disabled + }); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "更新规则状态失败"); + } + } finally { + loading.value = false; + } + } + async function handleBatchStatusChange(priorities, disabled) { + loading.value = true; + try { + const payload = priorities.reduce((acc, p) => ({ ...acc, [p]: disabled }), {}); + await props.api.post(`/plugin/ClashRuleProvider/rules/ruleset/metadata/disabled`, payload); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "批量更新规则状态失败"); + } + } finally { + loading.value = false; + } + } + function closeRuleDialog() { + ruleDialogVisible.value = false; + } + return (_ctx, _cache) => { + const _component_v_progress_circular = _resolveComponent$q("v-progress-circular"); + const _component_v_overlay = _resolveComponent$q("v-overlay"); + const _component_v_text_field = _resolveComponent$q("v-text-field"); + const _component_v_col = _resolveComponent$q("v-col"); + const _component_v_btn = _resolveComponent$q("v-btn"); + const _component_v_btn_group = _resolveComponent$q("v-btn-group"); + const _component_v_row = _resolveComponent$q("v-row"); + const _component_v_pagination = _resolveComponent$q("v-pagination"); + const _component_v_list_item_title = _resolveComponent$q("v-list-item-title"); + const _component_v_list_item = _resolveComponent$q("v-list-item"); + const _component_v_list = _resolveComponent$q("v-list"); + const _component_v_menu = _resolveComponent$q("v-menu"); + const _component_v_divider = _resolveComponent$q("v-divider"); + return _openBlock$q(), _createElementBlock$a("div", _hoisted_1$i, [ + _createVNode$q(_component_v_overlay, { + modelValue: loading.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => loading.value = $event), + contained: "", + class: "align-center justify-center" + }, { + default: _withCtx$q(() => [ + _createVNode$q(_component_v_progress_circular, { + indeterminate: "", + color: "primary" + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createElementVNode$l("div", _hoisted_2$d, [ + _createVNode$q(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$q(() => [ + _createVNode$q(_component_v_col, { + cols: "10", + sm: "6", + class: "d-flex justify-start" + }, { + default: _withCtx$q(() => [ + _createVNode$q(_component_v_text_field, { + modelValue: searchRulesetRule.value, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => searchRulesetRule.value = $event), + label: "搜索规则", + clearable: "", + density: "compact", + variant: "solo-filled", + "hide-details": "", + class: "search-field", + "prepend-inner-icon": "mdi-magnify", + flat: "", + rounded: "pill", + "single-line": "", + disabled: loading.value + }, null, 8, ["modelValue", "disabled"]) + ]), + _: 1 + }), + _createVNode$q(_component_v_col, { + cols: "2", + sm: "6", + class: "d-flex justify-end" + }, { + default: _withCtx$q(() => [ + _createVNode$q(_component_v_btn_group, { + variant: "outlined", + rounded: "", + divided: "" + }, { + default: _withCtx$q(() => [ + _createVNode$q(_component_v_btn, { + class: "d-none d-sm-flex", + icon: group.value ? "mdi-format-list-bulleted" : "mdi-format-list-group", + disabled: loading.value, + onClick: _cache[2] || (_cache[2] = ($event) => group.value = !group.value) + }, null, 8, ["icon", "disabled"]), + _createVNode$q(_component_v_btn, { + icon: "mdi-plus", + disabled: loading.value, + onClick: openAddRuleDialog + }, null, 8, ["disabled"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createElementVNode$l("div", _hoisted_3$c, [ + _createVNode$q(_sfc_main$t, { + group: group.value, + "sorted-rules": _ctx.rules, + page: pageRuleset.value, + "items-per-page": itemsPerPageRuleset.value, + "ruleset-prefix": _ctx.rulesetPrefix, + "search-rule": searchRulesetRule.value, + onEdit: editRule, + onDelete: deleteRule, + onDeleteBatch: deleteRules, + onReorder: handleReorderRule, + onChangeStatus: handleStatusChange, + onChangeStatusBatch: handleBatchStatusChange + }, null, 8, ["group", "sorted-rules", "page", "items-per-page", "ruleset-prefix", "search-rule"]) + ]), + _createElementVNode$l("div", _hoisted_4$b, [ + _createVNode$q(_component_v_row, null, { + default: _withCtx$q(() => [ + (_openBlock$q(true), _createElementBlock$a(_Fragment$9, null, _renderList$9(paginatedRulesetRules.value, (item) => { + return _openBlock$q(), _createBlock$q(_component_v_col, { + key: item.priority, + cols: "12" + }, { + default: _withCtx$q(() => [ + _createVNode$q(RuleCard, { + ruleset: "ruleset", + rule: item, + onDelete: deleteRule, + onEdit: editRule, + onChangeStatus: handleStatusChange + }, null, 8, ["rule"]) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + _: 1 + }) + ]), + _createElementVNode$l("div", _hoisted_5$8, [ + _createVNode$q(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$q(() => [ + _createVNode$q(_component_v_col, { + cols: "2", + md: "2" + }, { + default: _withCtx$q(() => _cache[9] || (_cache[9] = [ + _createElementVNode$l("div", { id: "ruleset-rules-table-batch-actions" }, null, -1) + ])), + _: 1 + }), + _createVNode$q(_component_v_col, { + cols: "8", + md: "8", + class: "d-flex justify-center" + }, { + default: _withCtx$q(() => [ + _createVNode$q(_component_v_pagination, { + modelValue: pageRuleset.value, + "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => pageRuleset.value = $event), + length: pageCountRuleset.value, + "total-visible": "5", + rounded: "circle", + class: "d-none d-sm-flex my-0", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]), + _createVNode$q(_component_v_pagination, { + modelValue: pageRuleset.value, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => pageRuleset.value = $event), + length: pageCountRuleset.value, + "total-visible": "0", + rounded: "circle", + class: "d-sm-none my-0", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]) + ]), + _: 1 + }), + _createVNode$q(_component_v_col, { + cols: "2", + md: "2", + class: "d-flex justify-end" + }, { + default: _withCtx$q(() => [ + _createVNode$q(_component_v_menu, null, { + activator: _withCtx$q(({ props: props2 }) => [ + _createVNode$q(_component_v_btn, _mergeProps$h(props2, { + icon: "", + rounded: "circle", + variant: "tonal", + disabled: loading.value + }), { + default: _withCtx$q(() => [ + _createTextVNode$q(_toDisplayString$n(_unref$m(pageTitle)(itemsPerPageRuleset.value)), 1) + ]), + _: 2 + }, 1040, ["disabled"]) + ]), + default: _withCtx$q(() => [ + _createVNode$q(_component_v_list, null, { + default: _withCtx$q(() => [ + (_openBlock$q(true), _createElementBlock$a(_Fragment$9, null, _renderList$9(_unref$m(itemsPerPageOptions), (item, index) => { + return _openBlock$q(), _createBlock$q(_component_v_list_item, { + key: index, + value: item.value, + onClick: ($event) => itemsPerPageRuleset.value = item.value + }, { + default: _withCtx$q(() => [ + _createVNode$q(_component_v_list_item_title, null, { + default: _withCtx$q(() => [ + _createTextVNode$q(_toDisplayString$n(item.title), 1) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1032, ["value", "onClick"]); + }), 128)) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createVNode$q(_component_v_divider), + _cache[10] || (_cache[10] = _createElementVNode$l("div", { class: "text-caption text-grey mt-2" }, "* 对规则集的修改会在 Clash 中立即生效。", -1)), + ruleDialogVisible.value ? (_openBlock$q(), _createBlock$q(_sfc_main$r, { + key: 0, + modelValue: ruleDialogVisible.value, + "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => ruleDialogVisible.value = $event), + "initial-rule": currentRule.value, + "is-adding": editingPriority.value === null, + "editing-type": editingType.value, + "rule-provider-names": _ctx.ruleProviderNames, + "geo-rules": _ctx.geoRules, + "custom-outbounds": _ctx.customOutbounds, + api: _ctx.api, + onRefresh: _cache[6] || (_cache[6] = (v) => emit("refresh", v)), + onShowSnackbar: _cache[7] || (_cache[7] = (val) => emit("show-snackbar", val)), + onShowError: _cache[8] || (_cache[8] = (msg) => emit("show-error", msg)), + onClose: closeRuleDialog + }, null, 8, ["modelValue", "initial-rule", "is-adding", "editing-type", "rule-provider-names", "geo-rules", "custom-outbounds", "api"])) : _createCommentVNode$k("", true) + ]); + }; + } +}); + +const {defineComponent:_defineComponent$p} = await importShared('vue'); + +const {unref:_unref$l,resolveComponent:_resolveComponent$p,createVNode:_createVNode$p,withCtx:_withCtx$p,Teleport:_Teleport,openBlock:_openBlock$p,createBlock:_createBlock$p,createCommentVNode:_createCommentVNode$j,createTextVNode:_createTextVNode$p,toDisplayString:_toDisplayString$m,createElementVNode:_createElementVNode$k,mergeProps:_mergeProps$g} = await importShared('vue'); + +const _hoisted_1$h = { class: "d-flex align-center" }; +const {ref: ref$h} = await importShared('vue'); +const ruleset = "top"; +const _sfc_main$p = /* @__PURE__ */ _defineComponent$p({ + __name: "TopRulesTable", + props: { + sortedRules: { + type: Array, + required: true + }, + page: { + type: Number, + required: true + }, + itemsPerPage: { + type: Number, + required: true + }, + searchRule: String + }, + emits: ["edit", "delete", "delete-batch", "reorder", "change-status", "change-status-batch", "editVisibility"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const headers = ref$h([ + { title: "", key: "handler", sortable: false, width: "1rem" }, + { title: "优先级", key: "priority", sortable: true, width: "3.5rem" }, + { title: "类型", key: "type", sortable: true }, + { title: "内容", key: "payload", sortable: true }, + { title: "出站", key: "action", sortable: false }, + { title: "日期", key: "time_modified", sortable: true }, + { title: "", key: "status", sortable: false, width: "1rem" }, + { title: "", key: "actions", sortable: false, width: "1rem" } + ]); + const dragEnabled = ref$h(false); + const hoveredPriority = ref$h(-1); + const dragItem = ref$h(null); + const selected = ref$h([]); + function dragStart(event, priority) { + const item = props.sortedRules.find((r) => r.priority === priority); + if (!item) { + event.preventDefault?.(); + return; + } + dragItem.value = item; + if (event.dataTransfer) { + event.dataTransfer.effectAllowed = "move"; + } + } + function dragOver(event, priority) { + event.preventDefault(); + hoveredPriority.value = priority; + } + function drop(event, targetPriority) { + if (dragItem.value && dragItem.value.priority !== targetPriority) { + emit("reorder", targetPriority, dragItem.value.priority, ruleset); + } + dragItem.value = null; + hoveredPriority.value = -1; + } + function dragEnd() { + dragItem.value = null; + hoveredPriority.value = -1; + } + function editRule(priority) { + emit("edit", priority, ruleset); + } + function deleteRule(priority) { + emit("delete", priority, ruleset); + } + function deleteSelected() { + if (selected.value.length > 0) { + emit("delete-batch", selected.value, ruleset); + selected.value = []; + } + } + function updateStatus(item, disabled) { + emit("change-status", item.priority, disabled, ruleset); + } + function changeBatchStatus(disabled) { + if (selected.value.length > 0) { + emit("change-status-batch", selected.value, disabled, ruleset); + selected.value = []; + } + } + const rowProps = (data) => { + const item = data.item; + return { + class: { + "drop-over": item.priority === hoveredPriority.value, + "dragging-item": dragItem.value?.priority === item.priority, + "list-row": true, + "text-grey": item.meta?.disabled + }, + draggable: dragEnabled.value, + onDragstart: (e) => dragStart(e, item.priority), + onDragover: (e) => dragOver(e, item.priority), + onDrop: (e) => drop(e, item.priority), + onDragend: dragEnd + }; + }; + return (_ctx, _cache) => { + const _component_v_btn = _resolveComponent$p("v-btn"); + const _component_v_btn_group = _resolveComponent$p("v-btn-group"); + const _component_v_icon = _resolveComponent$p("v-icon"); + const _component_v_chip = _resolveComponent$p("v-chip"); + const _component_v_tooltip = _resolveComponent$p("v-tooltip"); + const _component_v_data_table = _resolveComponent$p("v-data-table"); + return _openBlock$p(), _createBlock$p(_component_v_data_table, { + modelValue: selected.value, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => selected.value = $event), + class: "px-4", + headers: headers.value, + search: __props.searchRule, + items: __props.sortedRules, + page: __props.page, + "items-per-page": __props.itemsPerPage, + "items-per-page-options": _unref$l(itemsPerPageOptions), + "item-key": "priority", + "item-value": "priority", + "show-select": "", + density: "compact", + "hide-default-footer": "", + "fixed-header": "", + "row-props": rowProps + }, { + top: _withCtx$p(() => [ + selected.value.length > 0 ? (_openBlock$p(), _createBlock$p(_Teleport, { + key: 0, + to: "#top-rules-table-batch-actions" + }, [ + _createVNode$p(_component_v_btn_group, { + rounded: "", + variant: "tonal" + }, { + default: _withCtx$p(() => [ + _createVNode$p(_component_v_btn, { + color: "success", + "prepend-icon": "mdi-check", + size: "small", + onClick: _cache[0] || (_cache[0] = ($event) => changeBatchStatus(false)) + }), + _createVNode$p(_component_v_btn, { + color: "warning", + "prepend-icon": "mdi-close", + size: "small", + onClick: _cache[1] || (_cache[1] = ($event) => changeBatchStatus(true)) + }), + _createVNode$p(_component_v_btn, { + color: "error", + "prepend-icon": "mdi-trash-can-outline", + size: "small", + onClick: deleteSelected + }) + ]), + _: 1 + }) + ])) : _createCommentVNode$j("", true) + ]), + "item.handler": _withCtx$p(({}) => [ + _createVNode$p(_component_v_icon, { + class: "drag-handle", + onMouseenter: _cache[2] || (_cache[2] = ($event) => dragEnabled.value = true), + onMouseleave: _cache[3] || (_cache[3] = ($event) => dragEnabled.value = false) + }, { + default: _withCtx$p(() => _cache[5] || (_cache[5] = [ + _createTextVNode$p("mdi-drag-horizontal-variant ") + ])), + _: 1 + }) + ]), + "item.priority": _withCtx$p(({ item }) => [ + _createVNode$p(_component_v_chip, { + size: "x-small", + variant: "tonal", + color: "secondary", + class: "font-weight-bold" + }, { + default: _withCtx$p(() => [ + _createTextVNode$p(_toDisplayString$m(item.priority), 1) + ]), + _: 2 + }, 1024) + ]), + "item.type": _withCtx$p(({ item }) => [ + _createVNode$p(_component_v_chip, { + color: _unref$l(getRuleTypeColor)(item.type), + size: "small", + label: "", + variant: "tonal" + }, { + default: _withCtx$p(() => [ + _createTextVNode$p(_toDisplayString$m(item.type), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.payload": _withCtx$p(({ value }) => [ + _createElementVNode$k("small", null, _toDisplayString$m(value), 1) + ]), + "item.action": _withCtx$p(({ item }) => [ + _createVNode$p(_component_v_chip, { + color: _unref$l(getActionColor)(item.action), + size: "small", + variant: "outlined", + pill: "" + }, { + default: _withCtx$p(() => [ + _createTextVNode$p(_toDisplayString$m(item.action), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.time_modified": _withCtx$p(({ item }) => [ + _createElementVNode$k("small", null, _toDisplayString$m(item.meta?.time_modified ? _unref$l(timestampToDate)(item.meta.time_modified) : ""), 1) + ]), + "item.status": _withCtx$p(({ item }) => [ + _createElementVNode$k("div", _hoisted_1$h, [ + _createVNode$p(_component_v_icon, { + color: item.meta.disabled ? "grey" : "success", + class: "mr-1" + }, { + default: _withCtx$p(() => [ + _createTextVNode$p(_toDisplayString$m(item.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 2 + }, 1032, ["color"]), + item.meta.invisible_to && item.meta.invisible_to.length > 0 ? (_openBlock$p(), _createBlock$p(_component_v_tooltip, { + key: 0, + text: "已配置可见性限制", + location: "top" + }, { + activator: _withCtx$p(({ props: props2 }) => [ + _createVNode$p(_component_v_icon, _mergeProps$g(props2, { + size: "small", + color: "warning" + }), { + default: _withCtx$p(() => _cache[6] || (_cache[6] = [ + _createTextVNode$p(" mdi-eye-off-outline ") + ])), + _: 2 + }, 1040) + ]), + _: 1 + })) : _createCommentVNode$j("", true) + ]) + ]), + "item.actions": _withCtx$p(({ item }) => [ + _createVNode$p(_sfc_main$u, { + rule: item, + onEdit: ($event) => editRule(item.priority), + onDelete: ($event) => deleteRule(item.priority), + onChangeStatus: (disabled) => updateStatus(item, disabled), + onEditVisibility: ($event) => emit("editVisibility", item.priority, ruleset) + }, null, 8, ["rule", "onEdit", "onDelete", "onChangeStatus", "onEditVisibility"]) + ]), + _: 1 + }, 8, ["modelValue", "headers", "search", "items", "page", "items-per-page", "items-per-page-options"]); + }; + } +}); + +const {defineComponent:_defineComponent$o} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$o,resolveComponent:_resolveComponent$o,withCtx:_withCtx$o,createVNode:_createVNode$o,unref:_unref$k,createElementVNode:_createElementVNode$j,openBlock:_openBlock$o,createBlock:_createBlock$o} = await importShared('vue'); + +const {ref: ref$g} = await importShared('vue'); +const _sfc_main$o = /* @__PURE__ */ _defineComponent$o({ + __name: "ImportRuleDialog", + props: { + modelValue: { type: Boolean }, + api: {} + }, + emits: ["update:modelValue", "refresh", "show-snackbar", "show-error"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const editorOptions = { + enableBasicAutocompletion: true, + enableSnippets: true, + enableLiveAutocompletion: true, + showLineNumbers: true, + tabSize: 2 + }; + const rulesPlaceholder = ref$g( + `rules: + - DOMAIN,gemini.google.com,Openai` + ); + const importRuleTypes = ["YAML"]; + const importRuleLoading = ref$g(false); + const importRules = ref$g({ + type: "YAML", + payload: "" + }); + function close() { + emit("update:modelValue", false); + } + async function importRule() { + try { + importRuleLoading.value = true; + const requestData = { + vehicle: importRules.value.type, + payload: importRules.value.payload + }; + const result = await props.api.post("/plugin/ClashRuleProvider/import", requestData); + if (!result.success) { + emit("show-error", "规则导入失败: " + (result.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "规则导入失败", + color: "error" + }); + return; + } + close(); + emit("refresh"); + emit("show-snackbar", { + show: true, + message: "规则导入成功", + color: "success" + }); + } catch (err) { + if (err instanceof Error) { + emit("show-error", "导入规则失败: " + (err.message || "未知错误")); + } + emit("show-snackbar", { + show: true, + message: "导入规则失败", + color: "error" + }); + } finally { + importRuleLoading.value = false; + } + } + return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent$o("v-card-title"); + const _component_v_select = _resolveComponent$o("v-select"); + const _component_v_alert = _resolveComponent$o("v-alert"); + const _component_v_card_text = _resolveComponent$o("v-card-text"); + const _component_v_spacer = _resolveComponent$o("v-spacer"); + const _component_v_btn = _resolveComponent$o("v-btn"); + const _component_v_card_actions = _resolveComponent$o("v-card-actions"); + const _component_v_card = _resolveComponent$o("v-card"); + const _component_v_dialog = _resolveComponent$o("v-dialog"); + return _openBlock$o(), _createBlock$o(_component_v_dialog, { + "model-value": _ctx.modelValue, + "max-width": "40rem", + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => emit("update:modelValue", $event)) + }, { + default: _withCtx$o(() => [ + _createVNode$o(_component_v_card, null, { + default: _withCtx$o(() => [ + _createVNode$o(_component_v_card_title, null, { + default: _withCtx$o(() => _cache[3] || (_cache[3] = [ + _createTextVNode$o("导入规则") + ])), + _: 1 + }), + _createVNode$o(_component_v_card_text, { style: { "max-height": "900px", "overflow-y": "auto" } }, { + default: _withCtx$o(() => [ + _createVNode$o(_component_v_select, { + modelValue: importRules.value.type, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => importRules.value.type = $event), + items: importRuleTypes, + label: "内容格式", + required: "", + class: "mb-4" + }, null, 8, ["modelValue"]), + _createVNode$o(_unref$k(VAceEditor), { + value: importRules.value.payload, + "onUpdate:value": _cache[1] || (_cache[1] = ($event) => importRules.value.payload = $event), + lang: "yaml", + theme: "monokai", + options: editorOptions, + placeholder: rulesPlaceholder.value, + style: { "height": "30rem", "width": "100%", "margin-bottom": "16px" } + }, null, 8, ["value", "placeholder"]), + _createVNode$o(_component_v_alert, { + type: "info", + dense: "", + class: "mb-4", + variant: "tonal" + }, { + default: _withCtx$o(() => _cache[4] || (_cache[4] = [ + _createTextVNode$o(" 请输入 Clash 规则中的 "), + _createElementVNode$j("strong", null, "rules", -1), + _createTextVNode$o(" 字段,例如:"), + _createElementVNode$j("br", null, null, -1), + _createElementVNode$j("code", null, [ + _createTextVNode$o("rules:"), + _createElementVNode$j("br"), + _createTextVNode$o("- DOMAIN,gemini.google.com,Openai") + ], -1) + ])), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$o(_component_v_card_actions, null, { + default: _withCtx$o(() => [ + _createVNode$o(_component_v_spacer), + _createVNode$o(_component_v_btn, { + color: "secondary", + onClick: close + }, { + default: _withCtx$o(() => _cache[5] || (_cache[5] = [ + _createTextVNode$o("取消") + ])), + _: 1 + }), + _createVNode$o(_component_v_btn, { + color: "primary", + loading: importRuleLoading.value, + onClick: importRule + }, { + default: _withCtx$o(() => _cache[6] || (_cache[6] = [ + _createTextVNode$o("导入 ") + ])), + _: 1 + }, 8, ["loading"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["model-value"]); + }; + } +}); + +const {defineComponent:_defineComponent$n} = await importShared('vue'); + +const {resolveComponent:_resolveComponent$n,createVNode:_createVNode$n,withCtx:_withCtx$n,createElementVNode:_createElementVNode$i,renderList:_renderList$8,Fragment:_Fragment$8,openBlock:_openBlock$n,createElementBlock:_createElementBlock$9,createBlock:_createBlock$n,unref:_unref$j,toDisplayString:_toDisplayString$l,createTextVNode:_createTextVNode$n,mergeProps:_mergeProps$f,createCommentVNode:_createCommentVNode$i} = await importShared('vue'); + +const _hoisted_1$g = { class: "mb-2 position-relative" }; +const _hoisted_2$c = { class: "pa-4" }; +const _hoisted_3$b = { class: "d-none d-sm-flex clash-data-table" }; +const _hoisted_4$a = { class: "d-sm-none" }; +const _hoisted_5$7 = { + class: "pa-4", + style: { "min-height": "4rem" } +}; +const {computed: computed$6,ref: ref$f,toRaw: toRaw$6} = await importShared('vue'); +const _sfc_main$n = /* @__PURE__ */ _defineComponent$n({ + __name: "TopRulesTab", + props: { + rules: {}, + api: {}, + ruleProviderNames: {}, + geoRules: {}, + customOutbounds: {} + }, + emits: ["refresh", "show-snackbar", "show-error", "edit-visibility"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const searchTopRule = ref$f(""); + const page = ref$f(1); + const itemsPerPage = ref$f(10); + const loading = ref$f(false); + const ruleDialogVisible = ref$f(false); + const importRuleDialog = ref$f(false); + const editingPriority = ref$f(null); + const currentRule = ref$f({ ...defaultRule }); + const editingType = ref$f("top"); + const filteredRules = computed$6(() => { + if (!searchTopRule.value) return props.rules; + const keyword = searchTopRule.value.toLowerCase(); + return props.rules.filter( + (item) => Object.values(item).some((val) => String(val).toLowerCase().includes(keyword)) + ); + }); + const pageCount = computed$6(() => { + if (itemsPerPage.value === -1) { + return 1; + } + return Math.ceil(filteredRules.value.length / itemsPerPage.value); + }); + const paginatedTopRules = computed$6(() => { + const start = (page.value - 1) * itemsPerPage.value; + const end = start + itemsPerPage.value; + return filteredRules.value.slice(start, end); + }); + function openImportRuleDialog() { + importRuleDialog.value = true; + } + function openAddRuleDialog() { + editingPriority.value = null; + const nextPriority = props.rules.length > 0 ? props.rules[props.rules.length - 1].priority + 1 : 0; + currentRule.value = { ...defaultRule }; + currentRule.value.priority = nextPriority; + ruleDialogVisible.value = true; + } + function closeRuleDialog() { + ruleDialogVisible.value = false; + } + function editRule(priority) { + const rule = props.rules.find((r) => r.priority === priority); + if (rule) { + editingPriority.value = priority; + currentRule.value = structuredClone(toRaw$6(rule)); + ruleDialogVisible.value = true; + } + } + async function deleteRule(priority) { + loading.value = true; + try { + await props.api.delete(`/plugin/ClashRuleProvider/rules/top/${priority}`); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "删除规则失败"); + } + } finally { + loading.value = false; + } + } + async function deleteRules(priorities) { + loading.value = true; + try { + await props.api.delete("/plugin/ClashRuleProvider/rules/top", { + data: { rules_priority: priorities } + }); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "批量删除规则失败"); + } + } finally { + loading.value = false; + } + } + async function handleReorderRule(targetPriority, movedPriority) { + loading.value = true; + try { + await props.api.put(`/plugin/ClashRuleProvider/reorder-rules/top/${targetPriority}`, { + moved_priority: movedPriority + }); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "重排序失败"); + } + } finally { + loading.value = false; + } + } + async function handleStatusChange(priority, disabled) { + loading.value = true; + try { + await props.api.post(`/plugin/ClashRuleProvider/rules/top/metadata/disabled`, { + [priority]: disabled + }); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "更新规则状态失败"); + } + } finally { + loading.value = false; + } + } + async function handleBatchStatusChange(priorities, disabled) { + loading.value = true; + try { + const payload = priorities.reduce((acc, p) => ({ ...acc, [p]: disabled }), {}); + await props.api.post(`/plugin/ClashRuleProvider/rules/top/metadata/disabled`, payload); + emit("refresh", ["top", "ruleset"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "批量更新规则状态失败"); + } + } finally { + loading.value = false; + } + } + function editVisibility(priority, type) { + const rule = props.rules.find((r) => r.priority === priority); + if (!rule) { + emit("show-error", "Rule not found"); + return; + } + emit( + "edit-visibility", + rule.meta, + `/plugin/ClashRuleProvider/rules/${type}/${priority}/meta`, + type + ); + } + return (_ctx, _cache) => { + const _component_v_progress_circular = _resolveComponent$n("v-progress-circular"); + const _component_v_overlay = _resolveComponent$n("v-overlay"); + const _component_v_text_field = _resolveComponent$n("v-text-field"); + const _component_v_col = _resolveComponent$n("v-col"); + const _component_v_btn = _resolveComponent$n("v-btn"); + const _component_v_btn_group = _resolveComponent$n("v-btn-group"); + const _component_v_row = _resolveComponent$n("v-row"); + const _component_v_pagination = _resolveComponent$n("v-pagination"); + const _component_v_list_item_title = _resolveComponent$n("v-list-item-title"); + const _component_v_list_item = _resolveComponent$n("v-list-item"); + const _component_v_list = _resolveComponent$n("v-list"); + const _component_v_menu = _resolveComponent$n("v-menu"); + const _component_v_divider = _resolveComponent$n("v-divider"); + return _openBlock$n(), _createElementBlock$9("div", _hoisted_1$g, [ + _createVNode$n(_component_v_overlay, { + modelValue: loading.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => loading.value = $event), + contained: "", + class: "align-center justify-center" + }, { + default: _withCtx$n(() => [ + _createVNode$n(_component_v_progress_circular, { + indeterminate: "", + color: "primary" + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createElementVNode$i("div", _hoisted_2$c, [ + _createVNode$n(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$n(() => [ + _createVNode$n(_component_v_col, { + cols: "8", + sm: "6", + class: "d-flex justify-start" + }, { + default: _withCtx$n(() => [ + _createVNode$n(_component_v_text_field, { + modelValue: searchTopRule.value, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => searchTopRule.value = $event), + label: "搜索规则", + clearable: "", + density: "compact", + variant: "solo-filled", + "hide-details": "", + class: "search-field", + "prepend-inner-icon": "mdi-magnify", + flat: "", + rounded: "pill", + "single-line": "", + disabled: loading.value + }, null, 8, ["modelValue", "disabled"]) + ]), + _: 1 + }), + _createVNode$n(_component_v_col, { + cols: "4", + sm: "6", + class: "d-flex justify-end" + }, { + default: _withCtx$n(() => [ + _createVNode$n(_component_v_btn_group, { + divided: "", + variant: "outlined", + rounded: "" + }, { + default: _withCtx$n(() => [ + _createVNode$n(_component_v_btn, { + icon: "mdi-import", + disabled: loading.value, + onClick: openImportRuleDialog + }, null, 8, ["disabled"]), + _createVNode$n(_component_v_btn, { + icon: "mdi-plus", + disabled: loading.value, + onClick: openAddRuleDialog + }, null, 8, ["disabled"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createElementVNode$i("div", _hoisted_3$b, [ + _createVNode$n(_sfc_main$p, { + "sorted-rules": _ctx.rules, + page: page.value, + "items-per-page": itemsPerPage.value, + "search-rule": searchTopRule.value, + onEdit: editRule, + onDelete: deleteRule, + onDeleteBatch: deleteRules, + onReorder: handleReorderRule, + onChangeStatus: handleStatusChange, + onChangeStatusBatch: handleBatchStatusChange, + onEditVisibility: editVisibility + }, null, 8, ["sorted-rules", "page", "items-per-page", "search-rule"]) + ]), + _createElementVNode$i("div", _hoisted_4$a, [ + _createVNode$n(_component_v_row, null, { + default: _withCtx$n(() => [ + (_openBlock$n(true), _createElementBlock$9(_Fragment$8, null, _renderList$8(paginatedTopRules.value, (item) => { + return _openBlock$n(), _createBlock$n(_component_v_col, { + key: item.priority, + cols: "12" + }, { + default: _withCtx$n(() => [ + _createVNode$n(RuleCard, { + ruleset: "top", + rule: item, + onDelete: deleteRule, + onEdit: editRule, + onChangeStatus: handleStatusChange + }, null, 8, ["rule"]) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + _: 1 + }) + ]), + _createElementVNode$i("div", _hoisted_5$7, [ + _createVNode$n(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$n(() => [ + _createVNode$n(_component_v_col, { + cols: "2", + md: "2" + }, { + default: _withCtx$n(() => _cache[12] || (_cache[12] = [ + _createElementVNode$i("div", { id: "top-rules-table-batch-actions" }, null, -1) + ])), + _: 1 + }), + _createVNode$n(_component_v_col, { + cols: "8", + md: "8", + class: "d-flex justify-center" + }, { + default: _withCtx$n(() => [ + _createVNode$n(_component_v_pagination, { + modelValue: page.value, + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => page.value = $event), + length: pageCount.value, + "total-visible": "5", + rounded: "circle", + class: "d-none d-sm-flex my-0", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]), + _createVNode$n(_component_v_pagination, { + modelValue: page.value, + "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => page.value = $event), + length: pageCount.value, + "total-visible": "0", + rounded: "circle", + class: "d-sm-none my-0", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]) + ]), + _: 1 + }), + _createVNode$n(_component_v_col, { + cols: "2", + md: "2", + class: "d-flex justify-end" + }, { + default: _withCtx$n(() => [ + _createVNode$n(_component_v_menu, null, { + activator: _withCtx$n(({ props: props2 }) => [ + _createVNode$n(_component_v_btn, _mergeProps$f(props2, { + icon: "", + rounded: "circle", + variant: "tonal", + disabled: loading.value + }), { + default: _withCtx$n(() => [ + _createTextVNode$n(_toDisplayString$l(_unref$j(pageTitle)(itemsPerPage.value)), 1) + ]), + _: 2 + }, 1040, ["disabled"]) + ]), + default: _withCtx$n(() => [ + _createVNode$n(_component_v_list, null, { + default: _withCtx$n(() => [ + (_openBlock$n(true), _createElementBlock$9(_Fragment$8, null, _renderList$8(_unref$j(itemsPerPageOptions), (item, index) => { + return _openBlock$n(), _createBlock$n(_component_v_list_item, { + key: index, + value: item.value, + onClick: ($event) => itemsPerPage.value = item.value + }, { + default: _withCtx$n(() => [ + _createVNode$n(_component_v_list_item_title, null, { + default: _withCtx$n(() => [ + _createTextVNode$n(_toDisplayString$l(item.title), 1) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1032, ["value", "onClick"]); + }), 128)) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createVNode$n(_component_v_divider), + _cache[13] || (_cache[13] = _createElementVNode$i("div", { class: "text-caption text-grey mt-2" }, " *置顶规则用于管理来自规则集的匹配规则,这些规则会动态更新。 ", -1)), + _cache[14] || (_cache[14] = _createElementVNode$i("div", { class: "text-caption text-grey mt-2" }, "*对置顶规则的修改只有Clash更新配置后才会生效。", -1)), + ruleDialogVisible.value ? (_openBlock$n(), _createBlock$n(_sfc_main$r, { + key: 0, + modelValue: ruleDialogVisible.value, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => ruleDialogVisible.value = $event), + "initial-rule": currentRule.value, + "is-adding": editingPriority.value === null, + "editing-type": editingType.value, + "rule-provider-names": _ctx.ruleProviderNames, + "geo-rules": _ctx.geoRules, + "custom-outbounds": _ctx.customOutbounds, + api: _ctx.api, + onRefresh: _cache[5] || (_cache[5] = (v) => emit("refresh", v)), + onShowSnackbar: _cache[6] || (_cache[6] = (val) => emit("show-snackbar", val)), + onShowError: _cache[7] || (_cache[7] = (msg) => emit("show-error", msg)), + onClose: closeRuleDialog + }, null, 8, ["modelValue", "initial-rule", "is-adding", "editing-type", "rule-provider-names", "geo-rules", "custom-outbounds", "api"])) : _createCommentVNode$i("", true), + importRuleDialog.value ? (_openBlock$n(), _createBlock$n(_sfc_main$o, { + key: 1, + modelValue: importRuleDialog.value, + "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => importRuleDialog.value = $event), + api: _ctx.api, + onRefresh: _cache[9] || (_cache[9] = ($event) => emit("refresh", ["top"])), + onShowSnackbar: _cache[10] || (_cache[10] = (val) => emit("show-snackbar", val)), + onShowError: _cache[11] || (_cache[11] = (msg) => emit("show-error", msg)) + }, null, 8, ["modelValue", "api"])) : _createCommentVNode$i("", true) + ]); + }; + } +}); + +const {defineComponent:_defineComponent$m} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$m,resolveComponent:_resolveComponent$m,withCtx:_withCtx$m,createVNode:_createVNode$m,mergeProps:_mergeProps$e,unref:_unref$i,toDisplayString:_toDisplayString$k,openBlock:_openBlock$m,createBlock:_createBlock$m,createCommentVNode:_createCommentVNode$h} = await importShared('vue'); +const _sfc_main$m = /* @__PURE__ */ _defineComponent$m({ + __name: "ProxyGroupActionMenu", + props: { + proxyGroup: { + type: Object, + required: true + } + }, + emits: ["showYaml", "edit", "delete", "deletePatch", "changeStatus", "editVisibility"], + setup(__props, { emit: __emit }) { + const emit = __emit; + return (_ctx, _cache) => { + const _component_v_icon = _resolveComponent$m("v-icon"); + const _component_v_btn = _resolveComponent$m("v-btn"); + const _component_v_list_item_title = _resolveComponent$m("v-list-item-title"); + const _component_v_list_item = _resolveComponent$m("v-list-item"); + const _component_v_list = _resolveComponent$m("v-list"); + const _component_v_menu = _resolveComponent$m("v-menu"); + return _openBlock$m(), _createBlock$m(_component_v_menu, { "min-width": "120" }, { + activator: _withCtx$m(({ props }) => [ + _createVNode$m(_component_v_btn, _mergeProps$e({ + color: "secondary", + icon: "", + size: "small", + variant: "text" + }, props), { + default: _withCtx$m(() => [ + _createVNode$m(_component_v_icon, null, { + default: _withCtx$m(() => _cache[6] || (_cache[6] = [ + _createTextVNode$m("mdi-dots-vertical") + ])), + _: 1 + }) + ]), + _: 2 + }, 1040) + ]), + default: _withCtx$m(() => [ + _createVNode$m(_component_v_list, { density: "compact" }, { + default: _withCtx$m(() => [ + _unref$i(isManual)(__props.proxyGroup.meta.source) ? (_openBlock$m(), _createBlock$m(_component_v_list_item, { + key: 0, + onClick: _cache[0] || (_cache[0] = ($event) => emit("changeStatus", !__props.proxyGroup.meta.disabled)) + }, { + prepend: _withCtx$m(() => [ + _createVNode$m(_component_v_icon, { + size: "small", + color: __props.proxyGroup.meta.disabled ? "success" : "grey" + }, { + default: _withCtx$m(() => [ + _createTextVNode$m(_toDisplayString$k(__props.proxyGroup.meta.disabled ? "mdi-play-circle-outline" : "mdi-stop-circle-outline"), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + default: _withCtx$m(() => [ + _createVNode$m(_component_v_list_item_title, null, { + default: _withCtx$m(() => [ + _createTextVNode$m(_toDisplayString$k(__props.proxyGroup.meta.disabled ? "启用" : "禁用"), 1) + ]), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$h("", true), + _createVNode$m(_component_v_list_item, { + onClick: _cache[1] || (_cache[1] = ($event) => emit("showYaml")) + }, { + prepend: _withCtx$m(() => [ + _createVNode$m(_component_v_icon, { + size: "small", + color: "info" + }, { + default: _withCtx$m(() => _cache[7] || (_cache[7] = [ + _createTextVNode$m("mdi-code-json") + ])), + _: 1 + }) + ]), + default: _withCtx$m(() => [ + _createVNode$m(_component_v_list_item_title, null, { + default: _withCtx$m(() => _cache[8] || (_cache[8] = [ + _createTextVNode$m("查看") + ])), + _: 1 + }) + ]), + _: 1 + }), + _unref$i(isManual)(__props.proxyGroup.meta.source) || _unref$i(isRegion)(__props.proxyGroup.meta.source) ? (_openBlock$m(), _createBlock$m(_component_v_list_item, { + key: 1, + onClick: _cache[2] || (_cache[2] = ($event) => emit("edit")) + }, { + prepend: _withCtx$m(() => [ + _createVNode$m(_component_v_icon, { + size: "small", + color: "primary" + }, { + default: _withCtx$m(() => _cache[9] || (_cache[9] = [ + _createTextVNode$m("mdi-file-edit-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$m(() => [ + _createVNode$m(_component_v_list_item_title, null, { + default: _withCtx$m(() => _cache[10] || (_cache[10] = [ + _createTextVNode$m("编辑") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$h("", true), + _unref$i(isManual)(__props.proxyGroup.meta.source) ? (_openBlock$m(), _createBlock$m(_component_v_list_item, { + key: 2, + onClick: _cache[3] || (_cache[3] = ($event) => emit("editVisibility")) + }, { + prepend: _withCtx$m(() => [ + _createVNode$m(_component_v_icon, { + size: "small", + color: "warning" + }, { + default: _withCtx$m(() => _cache[11] || (_cache[11] = [ + _createTextVNode$m("mdi-eye-off-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$m(() => [ + _createVNode$m(_component_v_list_item_title, null, { + default: _withCtx$m(() => _cache[12] || (_cache[12] = [ + _createTextVNode$m("隐藏") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$h("", true), + __props.proxyGroup.meta.patched ? (_openBlock$m(), _createBlock$m(_component_v_list_item, { + key: 3, + onClick: _cache[4] || (_cache[4] = ($event) => emit("deletePatch")) + }, { + prepend: _withCtx$m(() => [ + _createVNode$m(_component_v_icon, { + size: "small", + color: "error" + }, { + default: _withCtx$m(() => _cache[13] || (_cache[13] = [ + _createTextVNode$m("mdi-close-box-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$m(() => [ + _createVNode$m(_component_v_list_item_title, null, { + default: _withCtx$m(() => _cache[14] || (_cache[14] = [ + _createTextVNode$m("删除补丁") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$h("", true), + _unref$i(isManual)(__props.proxyGroup.meta.source) ? (_openBlock$m(), _createBlock$m(_component_v_list_item, { + key: 4, + onClick: _cache[5] || (_cache[5] = ($event) => emit("delete")) + }, { + prepend: _withCtx$m(() => [ + _createVNode$m(_component_v_icon, { + size: "small", + color: "error" + }, { + default: _withCtx$m(() => _cache[15] || (_cache[15] = [ + _createTextVNode$m("mdi-trash-can-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$m(() => [ + _createVNode$m(_component_v_list_item_title, null, { + default: _withCtx$m(() => _cache[16] || (_cache[16] = [ + _createTextVNode$m("删除") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$h("", true) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$l} = await importShared('vue'); + +const {unref:_unref$h,toDisplayString:_toDisplayString$j,createTextVNode:_createTextVNode$l,resolveComponent:_resolveComponent$l,withCtx:_withCtx$l,createVNode:_createVNode$l,mergeProps:_mergeProps$d,openBlock:_openBlock$l,createBlock:_createBlock$l,createCommentVNode:_createCommentVNode$g,createElementVNode:_createElementVNode$h} = await importShared('vue'); + +const _hoisted_1$f = { class: "d-flex align-center" }; +const {ref: ref$e} = await importShared('vue'); +const _sfc_main$l = /* @__PURE__ */ _defineComponent$l({ + __name: "ProxyGroupsTable", + props: { + proxyGroups: { + type: Array, + required: true + }, + page: { + type: Number, + required: true + }, + itemsPerPage: { + type: Number, + required: true + }, + search: String + }, + emits: ["copyToClipboard", "showYaml", "editProxyGroup", "deleteProxyGroup", "deletePatch", "changeStatus", "editVisibility"], + setup(__props, { emit: __emit }) { + const emit = __emit; + const proxyGroupHeaders = ref$e([ + { title: "名称", key: "name", sortable: true }, + { title: "类型", key: "type", sortable: false }, + { title: "来源", key: "source", sortable: false }, + { title: "", key: "status", sortable: false, width: "1.5rem" }, + { title: "", key: "actions", sortable: false, width: "1rem" } + ]); + return (_ctx, _cache) => { + const _component_v_chip = _resolveComponent$l("v-chip"); + const _component_v_icon = _resolveComponent$l("v-icon"); + const _component_v_tooltip = _resolveComponent$l("v-tooltip"); + const _component_v_data_table = _resolveComponent$l("v-data-table"); + return _openBlock$l(), _createBlock$l(_component_v_data_table, { + class: "px-4", + headers: proxyGroupHeaders.value, + search: __props.search, + items: __props.proxyGroups, + page: __props.page, + "items-per-page": __props.itemsPerPage, + "items-per-page-options": _unref$h(itemsPerPageOptions), + density: "compact", + "hide-default-footer": "", + "fixed-header": "", + "item-key": "name" + }, { + "item.name": _withCtx$l(({ item }) => [ + _createVNode$l(_component_v_chip, { + size: "small", + pill: "", + color: "secondary" + }, { + default: _withCtx$l(() => [ + _createTextVNode$l(_toDisplayString$j(item.data.name), 1) + ]), + _: 2 + }, 1024) + ]), + "item.type": _withCtx$l(({ item }) => [ + _createVNode$l(_component_v_chip, { + color: _unref$h(getProxyGroupTypeColor)(item.data.type), + size: "small", + label: "", + variant: "tonal" + }, { + default: _withCtx$l(() => [ + _createTextVNode$l(_toDisplayString$j(item.data.type), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.source": _withCtx$l(({ item }) => [ + _createVNode$l(_component_v_chip, { + size: "small", + color: _unref$h(getSourceColor)(item.meta.source), + variant: "outlined" + }, { + default: _withCtx$l(() => [ + _createTextVNode$l(_toDisplayString$j(item.meta.source), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.status": _withCtx$l(({ item }) => [ + _createElementVNode$h("div", _hoisted_1$f, [ + _createVNode$l(_component_v_icon, { + color: item.meta.disabled ? "grey" : "success", + class: "mr-1" + }, { + default: _withCtx$l(() => [ + _createTextVNode$l(_toDisplayString$j(item.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 2 + }, 1032, ["color"]), + item.meta.invisible_to && item.meta.invisible_to.length > 0 ? (_openBlock$l(), _createBlock$l(_component_v_tooltip, { + key: 0, + text: "已配置可见性限制", + location: "top" + }, { + activator: _withCtx$l(({ props }) => [ + _createVNode$l(_component_v_icon, _mergeProps$d(props, { + size: "small", + color: "warning" + }), { + default: _withCtx$l(() => _cache[0] || (_cache[0] = [ + _createTextVNode$l(" mdi-eye-off-outline ") + ])), + _: 2 + }, 1040) + ]), + _: 1 + })) : _createCommentVNode$g("", true), + item.meta.patched ? (_openBlock$l(), _createBlock$l(_component_v_tooltip, { + key: 1, + text: "已应用补丁", + location: "top" + }, { + activator: _withCtx$l(({ props }) => [ + _createVNode$l(_component_v_icon, _mergeProps$d(props, { + size: "small", + color: "info" + }), { + default: _withCtx$l(() => _cache[1] || (_cache[1] = [ + _createTextVNode$l(" mdi-auto-fix ") + ])), + _: 2 + }, 1040) + ]), + _: 1 + })) : _createCommentVNode$g("", true) + ]) + ]), + "item.actions": _withCtx$l(({ item }) => [ + _createVNode$l(_sfc_main$m, { + "proxy-group": item, + onChangeStatus: (disabled) => emit("changeStatus", item.data.name, disabled), + onShowYaml: ($event) => emit("showYaml", item.data), + onEdit: ($event) => emit("editProxyGroup", item.data.name), + onDelete: ($event) => emit("deleteProxyGroup", item.data.name), + onDeletePatch: ($event) => emit("deletePatch", item.data.name), + onEditVisibility: ($event) => emit("editVisibility", item.data.name) + }, null, 8, ["proxy-group", "onChangeStatus", "onShowYaml", "onEdit", "onDelete", "onDeletePatch", "onEditVisibility"]) + ]), + _: 1 + }, 8, ["headers", "search", "items", "page", "items-per-page", "items-per-page-options"]); + }; + } +}); + +const {defineComponent:_defineComponent$k} = await importShared('vue'); + +const {toDisplayString:_toDisplayString$i,createElementVNode:_createElementVNode$g,unref:_unref$g,createTextVNode:_createTextVNode$k,resolveComponent:_resolveComponent$k,withCtx:_withCtx$k,createVNode:_createVNode$k,openBlock:_openBlock$k,createBlock:_createBlock$k} = await importShared('vue'); + +const _hoisted_1$e = { class: "d-flex justify-space-between align-center px-4 pt-3" }; +const _hoisted_2$b = ["title"]; +const _sfc_main$k = /* @__PURE__ */ _defineComponent$k({ + __name: "ProxyGroupCard", + props: { + proxyGroupData: { + type: Object, + required: true + } + }, + emits: ["showYaml", "editProxyGroup", "deleteProxyGroup", "deletePatch", "changeStatus"], + setup(__props, { emit: __emit }) { + const emit = __emit; + return (_ctx, _cache) => { + const _component_v_chip = _resolveComponent$k("v-chip"); + const _component_v_col = _resolveComponent$k("v-col"); + const _component_v_row = _resolveComponent$k("v-row"); + const _component_v_card_text = _resolveComponent$k("v-card-text"); + const _component_v_divider = _resolveComponent$k("v-divider"); + const _component_v_icon = _resolveComponent$k("v-icon"); + const _component_v_spacer = _resolveComponent$k("v-spacer"); + const _component_v_card_actions = _resolveComponent$k("v-card-actions"); + const _component_v_card = _resolveComponent$k("v-card"); + return _openBlock$k(), _createBlock$k(_component_v_card, { + rounded: "lg", + elevation: "2", + class: "proxy-group-card h-100 transition-swing", + variant: "tonal" + }, { + default: _withCtx$k(() => [ + _createElementVNode$g("div", _hoisted_1$e, [ + _createElementVNode$g("span", { + class: "font-weight-bold text-truncate", + title: __props.proxyGroupData.data.name + }, _toDisplayString$i(__props.proxyGroupData.data.name), 9, _hoisted_2$b), + _createVNode$k(_component_v_chip, { + size: "small", + color: _unref$g(getSourceColor)(__props.proxyGroupData.meta.source), + variant: "outlined" + }, { + default: _withCtx$k(() => [ + _createTextVNode$k(_toDisplayString$i(__props.proxyGroupData.meta.source), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _createVNode$k(_component_v_card_text, { class: "pt-2 pb-4" }, { + default: _withCtx$k(() => [ + _createVNode$k(_component_v_row, { + "no-gutters": "", + class: "align-center" + }, { + default: _withCtx$k(() => [ + _createVNode$k(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$k(() => _cache[5] || (_cache[5] = [ + _createTextVNode$k("类型") + ])), + _: 1 + }), + _createVNode$k(_component_v_col, { cols: "9" }, { + default: _withCtx$k(() => [ + _createVNode$k(_component_v_chip, { + color: _unref$g(getProxyGroupTypeColor)(__props.proxyGroupData.data.type), + size: "x-small", + label: "", + variant: "tonal", + class: "font-weight-medium" + }, { + default: _withCtx$k(() => [ + _createTextVNode$k(_toDisplayString$i(__props.proxyGroupData.data.type), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$k(_component_v_divider), + _createVNode$k(_component_v_card_actions, null, { + default: _withCtx$k(() => [ + _createVNode$k(_component_v_icon, { + color: __props.proxyGroupData.meta.disabled ? "grey" : "success" + }, { + default: _withCtx$k(() => [ + _createTextVNode$k(_toDisplayString$i(__props.proxyGroupData.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 1 + }, 8, ["color"]), + _createVNode$k(_component_v_spacer), + _createVNode$k(_sfc_main$m, { + "proxy-group": __props.proxyGroupData, + onChangeStatus: _cache[0] || (_cache[0] = (disabled) => emit("changeStatus", __props.proxyGroupData.data.name, disabled)), + onShowYaml: _cache[1] || (_cache[1] = ($event) => emit("showYaml", __props.proxyGroupData.data)), + onEdit: _cache[2] || (_cache[2] = ($event) => emit("editProxyGroup", __props.proxyGroupData.data.name)), + onDelete: _cache[3] || (_cache[3] = ($event) => emit("deleteProxyGroup", __props.proxyGroupData.data.name)), + onDeletePatch: _cache[4] || (_cache[4] = ($event) => emit("deletePatch", __props.proxyGroupData.data.name)) + }, null, 8, ["proxy-group"]) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const ProxyGroupCard = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["__scopeId", "data-v-ef6241d5"]]); + +const {defineComponent:_defineComponent$j} = await importShared('vue'); + +const {toDisplayString:_toDisplayString$h,createTextVNode:_createTextVNode$j,resolveComponent:_resolveComponent$j,withCtx:_withCtx$j,createVNode:_createVNode$j,openBlock:_openBlock$j,createBlock:_createBlock$j,createCommentVNode:_createCommentVNode$f,createElementVNode:_createElementVNode$f,withModifiers:_withModifiers$3} = await importShared('vue'); + +const {ref: ref$d,computed: computed$5,toRaw: toRaw$5} = await importShared('vue'); +const _sfc_main$j = /* @__PURE__ */ _defineComponent$j({ + __name: "ProxyGroupsDialog", + props: { + initialValue: { + type: Object, + default: null + }, + isAdding: { + type: Boolean, + default: true + }, + proxyProviders: { + type: Array, + default: () => [] + }, + customOutbounds: { + type: Array, + default: () => [] + }, + api: { + type: Object, + required: true + } + }, + emits: ["close", "refresh", "show-snackbar", "show-error"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const proxyGroup = ref$d( + props.initialValue ? structuredClone(toRaw$5(props.initialValue.data)) : { ...defaultProxyGroup } + ); + const proxyGroupTypes = ref$d(["select", "url-test", "fallback", "load-balance", "smart"]); + const form = ref$d(null); + const loading = ref$d(false); + const proxyProviderNames = computed$5(() => Object.keys(props.proxyProviders)); + const strategyTypes = ref$d(["round-robin", "consistent-hashing", "sticky-sessions"]); + const smartStrategyTypes = ref$d(["round-robin", "sticky-sessions"]); + const actions = computed$5(() => [ + "DIRECT", + "REJECT", + "REJECT-DROP", + "PASS", + "COMPATIBLE", + ...props.customOutbounds.map((outbound) => outbound) + ]); + const urlRules = [ + (v) => { + return !v || isValidUrl(v) || "请输入一个有效的URL地址"; + } + ]; + async function saveProxyGroup() { + if (!form.value) return; + const { valid } = await form.value.validate(); + if (!valid) return; + const name = encodeURIComponent( + props.isAdding ? proxyGroup.value.name : props.initialValue?.data.name || "" + ); + const action = props.isAdding ? "添加代理组" : "更新代理组"; + loading.value = true; + try { + const path = props.isAdding ? "" : `/${name}`; + const method = props.isAdding ? "post" : "patch"; + const cleanedProxyGroup = JSON.parse(JSON.stringify(toRaw$5(proxyGroup.value))); + Object.keys(cleanedProxyGroup).forEach((key) => { + if (cleanedProxyGroup[key] === "") { + cleanedProxyGroup[key] = null; + } + }); + const requestData = props.isAdding ? cleanedProxyGroup : { + source: props.initialValue?.meta.source, + proxy_group: cleanedProxyGroup + }; + const result = await props.api[method]( + `/plugin/ClashRuleProvider/proxy-groups${path}`, + requestData + ); + if (!result.success) { + emit("show-error", action + "失败: " + (result.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: action + "失败", + color: "error" + }); + return; + } + emit("show-snackbar", { + show: true, + message: action + "成功", + color: "success" + }); + emit("refresh"); + emit("close"); + } catch (err) { + if (err instanceof Error) { + emit("show-error", action + "失败: " + (err.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: action + "失败", + color: "error" + }); + } + } finally { + loading.value = false; + } + } + return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent$j("v-card-title"); + const _component_v_text_field = _resolveComponent$j("v-text-field"); + const _component_v_col = _resolveComponent$j("v-col"); + const _component_v_select = _resolveComponent$j("v-select"); + const _component_v_row = _resolveComponent$j("v-row"); + const _component_v_icon = _resolveComponent$j("v-icon"); + const _component_v_switch = _resolveComponent$j("v-switch"); + const _component_v_card_text = _resolveComponent$j("v-card-text"); + const _component_v_alert = _resolveComponent$j("v-alert"); + const _component_v_spacer = _resolveComponent$j("v-spacer"); + const _component_v_btn = _resolveComponent$j("v-btn"); + const _component_v_card_actions = _resolveComponent$j("v-card-actions"); + const _component_v_card = _resolveComponent$j("v-card"); + const _component_v_form = _resolveComponent$j("v-form"); + const _component_v_dialog = _resolveComponent$j("v-dialog"); + return _openBlock$j(), _createBlock$j(_component_v_dialog, { "max-width": "40rem" }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_form, { + ref_key: "form", + ref: form, + onSubmit: _withModifiers$3(saveProxyGroup, ["prevent"]) + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_card, null, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_card_title, null, { + default: _withCtx$j(() => [ + _createTextVNode$j(_toDisplayString$h(props.isAdding ? "添加代理组" : "编辑代理组"), 1) + ]), + _: 1 + }), + _createVNode$j(_component_v_card_text, { + style: { "overflow-y": "auto" }, + "max-height": "60rem" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_row, null, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value.name, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => proxyGroup.value.name = $event), + label: "name", + required: "", + hint: "策略组的名字", + rules: [(v) => !!v || "Name不能为空"], + class: "mb-4" + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_select, { + modelValue: proxyGroup.value.type, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => proxyGroup.value.type = $event), + label: "type", + items: proxyGroupTypes.value, + required: "", + hint: "策略组的类型,smart组需要内核支持", + class: "mb-4" + }, null, 8, ["modelValue", "items"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$j(_component_v_select, { + modelValue: proxyGroup.value.proxies, + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => proxyGroup.value.proxies = $event), + label: "proxies", + items: actions.value, + multiple: "", + chips: "", + clearable: "", + hint: "引入出站代理或其他策略组", + class: "mb-4" + }, null, 8, ["modelValue", "items"]), + _createVNode$j(_component_v_select, { + modelValue: proxyGroup.value.use, + "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => proxyGroup.value.use = $event), + label: "use", + items: proxyProviderNames.value, + multiple: "", + chips: "", + clearable: "", + hint: "引入代理集合", + class: "mb-4" + }, null, 8, ["modelValue", "items"]), + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value.url, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => proxyGroup.value.url = $event), + label: "url", + hint: "健康检查测试地址", + rules: urlRules, + clearable: "", + class: "mb-4" + }, null, 8, ["modelValue"]), + proxyGroup.value.type === "url-test" ? (_openBlock$j(), _createBlock$j(_component_v_text_field, { + key: 0, + modelValue: proxyGroup.value.tolerance, + "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => proxyGroup.value.tolerance = $event), + modelModifiers: { number: true }, + label: "tolerance (ms)", + variant: "outlined", + type: "number", + min: "10", + hint: "节点切换容差", + rules: [(v) => v >= 0 || "容差需不小于0"], + class: "mb-4" + }, null, 8, ["modelValue", "rules"])) : _createCommentVNode$f("", true), + proxyGroup.value.type === "load-balance" ? (_openBlock$j(), _createBlock$j(_component_v_select, { + key: 1, + modelValue: proxyGroup.value.strategy, + "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => proxyGroup.value.strategy = $event), + label: "strategy", + items: strategyTypes.value, + hint: "负载均衡策略", + class: "mb-4" + }, null, 8, ["modelValue", "items"])) : _createCommentVNode$f("", true), + proxyGroup.value.type === "smart" ? (_openBlock$j(), _createBlock$j(_component_v_select, { + key: 2, + modelValue: proxyGroup.value.strategy, + "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => proxyGroup.value.strategy = $event), + label: "strategy", + items: smartStrategyTypes.value, + hint: "负载均衡策略", + class: "mb-4" + }, null, 8, ["modelValue", "items"])) : _createCommentVNode$f("", true), + proxyGroup.value.type === "smart" ? (_openBlock$j(), _createBlock$j(_component_v_row, { key: 3 }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value["policy-priority"], + "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => proxyGroup.value["policy-priority"] = $event), + label: "policy-priority", + hint: "优先级", + clearable: "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value["sample-rate"], + "onUpdate:modelValue": _cache[9] || (_cache[9] = ($event) => proxyGroup.value["sample-rate"] = $event), + modelModifiers: { number: true }, + label: "sample-rate", + type: "number", + hint: "数据采集率", + clearable: "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$f("", true), + _createVNode$j(_component_v_row, null, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value.filter, + "onUpdate:modelValue": _cache[10] || (_cache[10] = ($event) => proxyGroup.value.filter = $event), + label: "filter", + hint: "筛选满足关键词或正则表达式的节点", + clearable: "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value["exclude-filter"], + "onUpdate:modelValue": _cache[11] || (_cache[11] = ($event) => proxyGroup.value["exclude-filter"] = $event), + label: "exclude-filter", + hint: "排除满足关键词或正则表达式的节点", + clearable: "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value["exclude-type"], + "onUpdate:modelValue": _cache[12] || (_cache[12] = ($event) => proxyGroup.value["exclude-type"] = $event), + label: "exclude-type", + hint: "不支持正则表达式,通过 | 分割", + clearable: "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value["expected-status"], + "onUpdate:modelValue": _cache[13] || (_cache[13] = ($event) => proxyGroup.value["expected-status"] = $event), + label: "expected-status", + hint: "健康检查时期望的 HTTP 响应状态码", + clearable: "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$j(_component_v_row, null, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_col, { + cols: "12", + md: "12" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value.icon, + "onUpdate:modelValue": _cache[14] || (_cache[14] = ($event) => proxyGroup.value.icon = $event), + label: "icon", + clearable: "", + hint: "在 api 返回icon所输入的字符串" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$j(_component_v_row, null, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value.interval, + "onUpdate:modelValue": _cache[15] || (_cache[15] = ($event) => proxyGroup.value.interval = $event), + modelModifiers: { number: true }, + label: "interval", + variant: "outlined", + type: "number", + min: "0", + clearable: "", + suffix: "s", + hint: "健康检查间隔,如不为 0 则启用定时测试", + rules: [(v) => v === null || v === void 0 || v > -1 || "检查间隔需不小于0"] + }, { + "prepend-inner": _withCtx$j(() => [ + _createVNode$j(_component_v_icon, { color: "warning" }, { + default: _withCtx$j(() => _cache[26] || (_cache[26] = [ + _createTextVNode$j("mdi-timer") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value.timeout, + "onUpdate:modelValue": _cache[16] || (_cache[16] = ($event) => proxyGroup.value.timeout = $event), + modelModifiers: { number: true }, + label: "timeout", + variant: "outlined", + type: "number", + min: "1", + hint: "请求的超时时间", + suffix: "ms", + clearable: "", + rules: [(v) => v === null || v === void 0 || v > 0 || "超时时间必须大于0"] + }, { + "prepend-inner": _withCtx$j(() => [ + _createVNode$j(_component_v_icon, { color: "warning" }, { + default: _withCtx$j(() => _cache[27] || (_cache[27] = [ + _createTextVNode$j("mdi-timer") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$j(_component_v_row, null, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_text_field, { + modelValue: proxyGroup.value["max-failed-times"], + "onUpdate:modelValue": _cache[17] || (_cache[17] = ($event) => proxyGroup.value["max-failed-times"] = $event), + modelModifiers: { number: true }, + label: "max-failed-times", + variant: "outlined", + type: "number", + min: "0", + clearable: "", + hint: "最大失败次数", + rules: [(v) => v >= 0 || "最大失败次数必须大于等于0"] + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_switch, { + modelValue: proxyGroup.value["lazy"], + "onUpdate:modelValue": _cache[18] || (_cache[18] = ($event) => proxyGroup.value["lazy"] = $event), + label: "lazy", + inset: "", + hint: "未选择到当前策略组时,不进行测试", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_switch, { + modelValue: proxyGroup.value["disable-udp"], + "onUpdate:modelValue": _cache[19] || (_cache[19] = ($event) => proxyGroup.value["disable-udp"] = $event), + label: "disable-udp", + inset: "", + hint: "禁用该策略组的UDP", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_switch, { + modelValue: proxyGroup.value.hidden, + "onUpdate:modelValue": _cache[20] || (_cache[20] = ($event) => proxyGroup.value.hidden = $event), + label: "hidden", + inset: "", + hint: "在 api 返回hidden状态", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$j(_component_v_row, null, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_switch, { + modelValue: proxyGroup.value["include-all"], + "onUpdate:modelValue": _cache[21] || (_cache[21] = ($event) => proxyGroup.value["include-all"] = $event), + label: "include-all", + inset: "", + hint: "引入所有出站代理以及代理集合", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_switch, { + modelValue: proxyGroup.value["include-all-proxies"], + "onUpdate:modelValue": _cache[22] || (_cache[22] = ($event) => proxyGroup.value["include-all-proxies"] = $event), + label: "include-all-proxies", + inset: "", + hint: "引入所有出站代理", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + proxyGroup.value.type === "smart" ? (_openBlock$j(), _createBlock$j(_component_v_row, { key: 4 }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_switch, { + modelValue: proxyGroup.value["uselightgbm"], + "onUpdate:modelValue": _cache[23] || (_cache[23] = ($event) => proxyGroup.value["uselightgbm"] = $event), + label: "uselightgbm", + inset: "", + hint: "使用LightGBM进行权重预测", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$j(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_switch, { + modelValue: proxyGroup.value["collectdata"], + "onUpdate:modelValue": _cache[24] || (_cache[24] = ($event) => proxyGroup.value["collectdata"] = $event), + label: "collectdata", + inset: "", + hint: "收集数据进行模型训练", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$f("", true) + ]), + _: 1 + }), + _createVNode$j(_component_v_alert, { + type: "info", + variant: "tonal" + }, { + default: _withCtx$j(() => _cache[28] || (_cache[28] = [ + _createTextVNode$j(" 参考"), + _createElementVNode$f("a", { + href: "https://wiki.metacubex.one/config/proxy-groups/", + target: "_blank", + style: { "text-decoration": "underline" } + }, "Docs", -1) + ])), + _: 1 + }), + _createVNode$j(_component_v_card_actions, null, { + default: _withCtx$j(() => [ + _createVNode$j(_component_v_spacer), + _createVNode$j(_component_v_btn, { + color: "secondary", + onClick: _cache[25] || (_cache[25] = ($event) => emit("close")) + }, { + default: _withCtx$j(() => _cache[29] || (_cache[29] = [ + _createTextVNode$j("取消") + ])), + _: 1 + }), + _createVNode$j(_component_v_btn, { + color: "primary", + type: "submit", + loading: loading.value + }, { + default: _withCtx$j(() => _cache[30] || (_cache[30] = [ + _createTextVNode$j("保存 ") + ])), + _: 1 + }, 8, ["loading"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 512) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$i} = await importShared('vue'); + +const {resolveComponent:_resolveComponent$i,createVNode:_createVNode$i,withCtx:_withCtx$i,createElementVNode:_createElementVNode$e,renderList:_renderList$7,Fragment:_Fragment$7,openBlock:_openBlock$i,createElementBlock:_createElementBlock$8,createBlock:_createBlock$i,unref:_unref$f,toDisplayString:_toDisplayString$g,createTextVNode:_createTextVNode$i,mergeProps:_mergeProps$c,createCommentVNode:_createCommentVNode$e} = await importShared('vue'); + +const _hoisted_1$d = { class: "mb-2 position-relative" }; +const _hoisted_2$a = { class: "pa-4" }; +const _hoisted_3$a = { class: "d-none d-sm-flex clash-data-table" }; +const _hoisted_4$9 = { class: "d-sm-none" }; +const _hoisted_5$6 = { + class: "pa-4", + style: { "min-height": "4rem" } +}; +const {computed: computed$4,ref: ref$c} = await importShared('vue'); +const _sfc_main$i = /* @__PURE__ */ _defineComponent$i({ + __name: "ProxyGroupsTab", + props: { + proxyGroups: {}, + proxyProviders: {}, + customOutbounds: {}, + api: {} + }, + emits: ["refresh", "show-snackbar", "show-error", "show-yaml", "copy-to-clipboard", "edit-visibility"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const searchProxyGroups = ref$c(""); + const pageProxyGroup = ref$c(1); + const itemsPerPageProxyGroup = ref$c(10); + const loading = ref$c(false); + const proxyGroupDialogVisible = ref$c(false); + const editingProxyGroupName = ref$c(null); + const currentProxyGroup = ref$c(null); + const filteredProxyGroups = computed$4(() => { + if (!searchProxyGroups.value) return props.proxyGroups; + const keyword = searchProxyGroups.value.toLowerCase(); + return props.proxyGroups.filter( + (item) => Object.values(item).some((val) => String(val).toLowerCase().includes(keyword)) + ); + }); + const paginatedProxyGroups = computed$4(() => { + const start = (pageProxyGroup.value - 1) * itemsPerPageProxyGroup.value; + const end = start + itemsPerPageProxyGroup.value; + return filteredProxyGroups.value.slice(start, end); + }); + const pageCountProxyGroups = computed$4(() => { + if (itemsPerPageProxyGroup.value === -1) { + return 1; + } + return Math.ceil(filteredProxyGroups.value.length / itemsPerPageProxyGroup.value); + }); + function openAddProxyGroupDialog() { + editingProxyGroupName.value = null; + currentProxyGroup.value = null; + proxyGroupDialogVisible.value = true; + } + function editProxyGroup(name) { + const proxyGroupData = props.proxyGroups.find((p) => p.data.name === name); + if (proxyGroupData) { + editingProxyGroupName.value = name; + currentProxyGroup.value = { ...proxyGroupData }; + proxyGroupDialogVisible.value = true; + } + } + async function deleteProxyGroup(name) { + loading.value = true; + try { + const n = encodeURIComponent(name); + await props.api.delete(`/plugin/ClashRuleProvider/proxy-groups/${n}`); + emit("refresh", ["proxy-groups", "clash-outbounds"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "删除规则失败"); + } + } finally { + loading.value = false; + } + } + async function deletePatch(name) { + loading.value = true; + try { + const n = encodeURIComponent(name); + await props.api.delete(`/plugin/ClashRuleProvider/proxy-groups/${n}/patch`); + emit("refresh", ["proxy-groups", "clash-outbounds"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "删除补丁失败"); + } + } finally { + loading.value = false; + } + } + async function handleStatusChange(name, disabled) { + loading.value = true; + try { + const group = props.proxyGroups.find((g) => g.data.name === name); + if (!group) { + emit("show-error", "Proxy group not found"); + return; + } + const n = encodeURIComponent(name); + const newMeta = { ...group.meta, disabled }; + await props.api.patch(`/plugin/ClashRuleProvider/proxy-groups/${n}/meta`, newMeta); + emit("refresh", ["proxy-groups", "clash-outbounds"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "更新代理组状态失败"); + } + } finally { + loading.value = false; + } + } + function editVisibility(name) { + const group = props.proxyGroups.find((g) => g.data.name === name); + if (!group) { + emit("show-error", "Proxy group not found"); + return; + } + const n = encodeURIComponent(name); + emit( + "edit-visibility", + group.meta, + `/plugin/ClashRuleProvider/proxy-groups/${n}/meta`, + "proxy-groups" + ); + } + function closeProxyGroupsDialog() { + currentProxyGroup.value = null; + proxyGroupDialogVisible.value = false; + } + return (_ctx, _cache) => { + const _component_v_progress_circular = _resolveComponent$i("v-progress-circular"); + const _component_v_overlay = _resolveComponent$i("v-overlay"); + const _component_v_text_field = _resolveComponent$i("v-text-field"); + const _component_v_col = _resolveComponent$i("v-col"); + const _component_v_btn = _resolveComponent$i("v-btn"); + const _component_v_btn_group = _resolveComponent$i("v-btn-group"); + const _component_v_row = _resolveComponent$i("v-row"); + const _component_v_pagination = _resolveComponent$i("v-pagination"); + const _component_v_list_item_title = _resolveComponent$i("v-list-item-title"); + const _component_v_list_item = _resolveComponent$i("v-list-item"); + const _component_v_list = _resolveComponent$i("v-list"); + const _component_v_menu = _resolveComponent$i("v-menu"); + const _component_v_divider = _resolveComponent$i("v-divider"); + return _openBlock$i(), _createElementBlock$8("div", _hoisted_1$d, [ + _createVNode$i(_component_v_overlay, { + modelValue: loading.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => loading.value = $event), + contained: "", + class: "align-center justify-center" + }, { + default: _withCtx$i(() => [ + _createVNode$i(_component_v_progress_circular, { + indeterminate: "", + color: "primary" + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createElementVNode$e("div", _hoisted_2$a, [ + _createVNode$i(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$i(() => [ + _createVNode$i(_component_v_col, { + cols: "10", + sm: "6", + class: "d-flex justify-start" + }, { + default: _withCtx$i(() => [ + _createVNode$i(_component_v_text_field, { + modelValue: searchProxyGroups.value, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => searchProxyGroups.value = $event), + label: "搜索代理组", + clearable: "", + density: "compact", + variant: "solo-filled", + "hide-details": "", + class: "search-field", + "prepend-inner-icon": "mdi-magnify", + flat: "", + rounded: "pill", + "single-line": "", + disabled: loading.value + }, null, 8, ["modelValue", "disabled"]) + ]), + _: 1 + }), + _createVNode$i(_component_v_col, { + cols: "2", + sm: "6", + class: "d-flex justify-end" + }, { + default: _withCtx$i(() => [ + _createVNode$i(_component_v_btn_group, { + variant: "outlined", + rounded: "" + }, { + default: _withCtx$i(() => [ + _createVNode$i(_component_v_btn, { + icon: "mdi-plus", + disabled: loading.value, + onClick: openAddProxyGroupDialog + }, null, 8, ["disabled"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createElementVNode$e("div", _hoisted_3$a, [ + _createVNode$i(_sfc_main$l, { + "items-per-page": itemsPerPageProxyGroup.value, + page: pageProxyGroup.value, + "proxy-groups": _ctx.proxyGroups, + search: searchProxyGroups.value, + onCopyToClipboard: _cache[2] || (_cache[2] = (t) => emit("copy-to-clipboard", t)), + onShowYaml: _cache[3] || (_cache[3] = (o) => emit("show-yaml", o)), + onEditProxyGroup: editProxyGroup, + onDeleteProxyGroup: deleteProxyGroup, + onDeletePatch: deletePatch, + onChangeStatus: handleStatusChange, + onEditVisibility: editVisibility + }, null, 8, ["items-per-page", "page", "proxy-groups", "search"]) + ]), + _createElementVNode$e("div", _hoisted_4$9, [ + _createVNode$i(_component_v_row, null, { + default: _withCtx$i(() => [ + (_openBlock$i(true), _createElementBlock$8(_Fragment$7, null, _renderList$7(paginatedProxyGroups.value, (item) => { + return _openBlock$i(), _createBlock$i(_component_v_col, { + key: item.data.name, + cols: "12" + }, { + default: _withCtx$i(() => [ + _createVNode$i(ProxyGroupCard, { + "proxy-group-data": item, + onEditProxyGroup: editProxyGroup, + onDeleteProxyGroup: deleteProxyGroup, + onDeletePatch: deletePatch, + onShowYaml: _cache[4] || (_cache[4] = (o) => emit("show-yaml", o)), + onChangeStatus: handleStatusChange + }, null, 8, ["proxy-group-data"]) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + _: 1 + }) + ]), + _createElementVNode$e("div", _hoisted_5$6, [ + _createVNode$i(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$i(() => [ + _createVNode$i(_component_v_col, { + cols: "2", + md: "1" + }), + _createVNode$i(_component_v_col, { + cols: "8", + md: "10", + class: "d-flex justify-center" + }, { + default: _withCtx$i(() => [ + _createVNode$i(_component_v_pagination, { + modelValue: pageProxyGroup.value, + "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => pageProxyGroup.value = $event), + length: pageCountProxyGroups.value, + "total-visible": "5", + class: "d-none d-sm-flex my-0", + rounded: "circle", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]), + _createVNode$i(_component_v_pagination, { + modelValue: pageProxyGroup.value, + "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => pageProxyGroup.value = $event), + length: pageCountProxyGroups.value, + "total-visible": "0", + class: "d-sm-none my-0", + rounded: "circle", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]) + ]), + _: 1 + }), + _createVNode$i(_component_v_col, { + cols: "2", + md: "1", + class: "d-flex justify-end" + }, { + default: _withCtx$i(() => [ + _createVNode$i(_component_v_menu, null, { + activator: _withCtx$i(({ props: props2 }) => [ + _createVNode$i(_component_v_btn, _mergeProps$c(props2, { + icon: "", + rounded: "circle", + variant: "tonal", + disabled: loading.value + }), { + default: _withCtx$i(() => [ + _createTextVNode$i(_toDisplayString$g(_unref$f(pageTitle)(itemsPerPageProxyGroup.value)), 1) + ]), + _: 2 + }, 1040, ["disabled"]) + ]), + default: _withCtx$i(() => [ + _createVNode$i(_component_v_list, null, { + default: _withCtx$i(() => [ + (_openBlock$i(true), _createElementBlock$8(_Fragment$7, null, _renderList$7(_unref$f(itemsPerPageOptions), (item, index) => { + return _openBlock$i(), _createBlock$i(_component_v_list_item, { + key: index, + value: item.value, + onClick: ($event) => itemsPerPageProxyGroup.value = item.value + }, { + default: _withCtx$i(() => [ + _createVNode$i(_component_v_list_item_title, null, { + default: _withCtx$i(() => [ + _createTextVNode$i(_toDisplayString$g(item.title), 1) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1032, ["value", "onClick"]); + }), 128)) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createVNode$i(_component_v_divider), + proxyGroupDialogVisible.value ? (_openBlock$i(), _createBlock$i(_sfc_main$j, { + key: 0, + modelValue: proxyGroupDialogVisible.value, + "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => proxyGroupDialogVisible.value = $event), + "initial-value": currentProxyGroup.value, + "is-adding": editingProxyGroupName.value === null, + "proxy-providers": _ctx.proxyProviders, + "custom-outbounds": _ctx.customOutbounds, + api: _ctx.api, + onClose: closeProxyGroupsDialog, + onRefresh: _cache[8] || (_cache[8] = ($event) => emit("refresh", ["clash-outbounds", "proxy-groups"])), + onShowSnackbar: _cache[9] || (_cache[9] = (val) => emit("show-snackbar", val)), + onShowError: _cache[10] || (_cache[10] = (msg) => emit("show-error", msg)) + }, null, 8, ["modelValue", "initial-value", "is-adding", "proxy-providers", "custom-outbounds", "api"])) : _createCommentVNode$e("", true) + ]); + }; + } +}); + +const {defineComponent:_defineComponent$h} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$h,resolveComponent:_resolveComponent$h,withCtx:_withCtx$h,createVNode:_createVNode$h,mergeProps:_mergeProps$b,unref:_unref$e,toDisplayString:_toDisplayString$f,openBlock:_openBlock$h,createBlock:_createBlock$h,createCommentVNode:_createCommentVNode$d} = await importShared('vue'); +const _sfc_main$h = /* @__PURE__ */ _defineComponent$h({ + __name: "ProxyActionMenu", + props: { + proxy: { + type: Object, + required: true + } + }, + emits: ["showYaml", "edit", "delete", "deletePatch", "changeStatus", "copyToClipboard", "editVisibility"], + setup(__props, { emit: __emit }) { + const emit = __emit; + return (_ctx, _cache) => { + const _component_v_icon = _resolveComponent$h("v-icon"); + const _component_v_btn = _resolveComponent$h("v-btn"); + const _component_v_list_item_title = _resolveComponent$h("v-list-item-title"); + const _component_v_list_item = _resolveComponent$h("v-list-item"); + const _component_v_list = _resolveComponent$h("v-list"); + const _component_v_menu = _resolveComponent$h("v-menu"); + return _openBlock$h(), _createBlock$h(_component_v_menu, { "min-width": "120" }, { + activator: _withCtx$h(({ props }) => [ + _createVNode$h(_component_v_btn, _mergeProps$b({ + color: "secondary", + icon: "", + size: "small", + variant: "text" + }, props), { + default: _withCtx$h(() => [ + _createVNode$h(_component_v_icon, null, { + default: _withCtx$h(() => _cache[7] || (_cache[7] = [ + _createTextVNode$h("mdi-dots-vertical") + ])), + _: 1 + }) + ]), + _: 2 + }, 1040) + ]), + default: _withCtx$h(() => [ + _createVNode$h(_component_v_list, { density: "compact" }, { + default: _withCtx$h(() => [ + _unref$e(isManual)(__props.proxy.meta.source) ? (_openBlock$h(), _createBlock$h(_component_v_list_item, { + key: 0, + onClick: _cache[0] || (_cache[0] = ($event) => emit("changeStatus", !__props.proxy.meta.disabled)) + }, { + prepend: _withCtx$h(() => [ + _createVNode$h(_component_v_icon, { + size: "small", + color: __props.proxy.meta.disabled ? "success" : "grey" + }, { + default: _withCtx$h(() => [ + _createTextVNode$h(_toDisplayString$f(__props.proxy.meta.disabled ? "mdi-play-circle-outline" : "mdi-stop-circle-outline"), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + default: _withCtx$h(() => [ + _createVNode$h(_component_v_list_item_title, null, { + default: _withCtx$h(() => [ + _createTextVNode$h(_toDisplayString$f(__props.proxy.meta.disabled ? "启用" : "禁用"), 1) + ]), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$d("", true), + _createVNode$h(_component_v_list_item, { + onClick: _cache[1] || (_cache[1] = ($event) => emit("showYaml")) + }, { + prepend: _withCtx$h(() => [ + _createVNode$h(_component_v_icon, { + size: "small", + color: "info" + }, { + default: _withCtx$h(() => _cache[8] || (_cache[8] = [ + _createTextVNode$h("mdi-code-json") + ])), + _: 1 + }) + ]), + default: _withCtx$h(() => [ + _createVNode$h(_component_v_list_item_title, null, { + default: _withCtx$h(() => _cache[9] || (_cache[9] = [ + _createTextVNode$h("查看") + ])), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$h(_component_v_list_item, { + onClick: _cache[2] || (_cache[2] = ($event) => emit("edit")) + }, { + prepend: _withCtx$h(() => [ + _createVNode$h(_component_v_icon, { + size: "small", + color: "primary" + }, { + default: _withCtx$h(() => _cache[10] || (_cache[10] = [ + _createTextVNode$h("mdi-file-edit-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$h(() => [ + _createVNode$h(_component_v_list_item_title, null, { + default: _withCtx$h(() => _cache[11] || (_cache[11] = [ + _createTextVNode$h("编辑") + ])), + _: 1 + }) + ]), + _: 1 + }), + _unref$e(isManual)(__props.proxy.meta.source) ? (_openBlock$h(), _createBlock$h(_component_v_list_item, { + key: 1, + onClick: _cache[3] || (_cache[3] = ($event) => emit("editVisibility")) + }, { + prepend: _withCtx$h(() => [ + _createVNode$h(_component_v_icon, { + size: "small", + color: "warning" + }, { + default: _withCtx$h(() => _cache[12] || (_cache[12] = [ + _createTextVNode$h("mdi-eye-off-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$h(() => [ + _createVNode$h(_component_v_list_item_title, null, { + default: _withCtx$h(() => _cache[13] || (_cache[13] = [ + _createTextVNode$h("隐藏") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$d("", true), + __props.proxy.v2ray_link ? (_openBlock$h(), _createBlock$h(_component_v_list_item, { + key: 2, + onClick: _cache[4] || (_cache[4] = ($event) => emit("copyToClipboard", __props.proxy.v2ray_link)) + }, { + prepend: _withCtx$h(() => [ + _createVNode$h(_component_v_icon, { + size: "small", + color: "secondary" + }, { + default: _withCtx$h(() => _cache[14] || (_cache[14] = [ + _createTextVNode$h("mdi-link") + ])), + _: 1 + }) + ]), + default: _withCtx$h(() => [ + _createVNode$h(_component_v_list_item_title, null, { + default: _withCtx$h(() => _cache[15] || (_cache[15] = [ + _createTextVNode$h("复制链接") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$d("", true), + __props.proxy.meta.patched ? (_openBlock$h(), _createBlock$h(_component_v_list_item, { + key: 3, + onClick: _cache[5] || (_cache[5] = ($event) => emit("deletePatch")) + }, { + prepend: _withCtx$h(() => [ + _createVNode$h(_component_v_icon, { + size: "small", + color: "error" + }, { + default: _withCtx$h(() => _cache[16] || (_cache[16] = [ + _createTextVNode$h("mdi-close-box-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$h(() => [ + _createVNode$h(_component_v_list_item_title, null, { + default: _withCtx$h(() => _cache[17] || (_cache[17] = [ + _createTextVNode$h("删除补丁") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$d("", true), + _createVNode$h(_component_v_list_item, { + disabled: !(_unref$e(isManual)(__props.proxy.meta.source) || _unref$e(isInvalid)(__props.proxy.meta.source)), + onClick: _cache[6] || (_cache[6] = ($event) => emit("delete")) + }, { + prepend: _withCtx$h(() => [ + _createVNode$h(_component_v_icon, { + size: "small", + color: "error" + }, { + default: _withCtx$h(() => _cache[18] || (_cache[18] = [ + _createTextVNode$h("mdi-trash-can-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$h(() => [ + _createVNode$h(_component_v_list_item_title, null, { + default: _withCtx$h(() => _cache[19] || (_cache[19] = [ + _createTextVNode$h("删除") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["disabled"]) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$g} = await importShared('vue'); + +const {unref:_unref$d,toDisplayString:_toDisplayString$e,createTextVNode:_createTextVNode$g,resolveComponent:_resolveComponent$g,withCtx:_withCtx$g,createVNode:_createVNode$g,openBlock:_openBlock$g,createBlock:_createBlock$g,createCommentVNode:_createCommentVNode$c,createElementVNode:_createElementVNode$d,mergeProps:_mergeProps$a} = await importShared('vue'); + +const _hoisted_1$c = { class: "d-flex align-center" }; +const {ref: ref$b} = await importShared('vue'); +const _sfc_main$g = /* @__PURE__ */ _defineComponent$g({ + __name: "ProxiesTable", + props: { + proxies: { + type: Array, + required: true + }, + page: { + type: Number, + required: true + }, + itemsPerPage: { + type: Number, + required: true + }, + search: String + }, + emits: ["copyToClipboard", "showYaml", "editProxy", "deleteProxy", "deletePatch", "changeStatus", "editVisibility"], + setup(__props, { emit: __emit }) { + const emit = __emit; + const headers = ref$b([ + { title: "名称", key: "name", sortable: true }, + { title: "类型", key: "type", sortable: false }, + { title: "服务器", key: "server", sortable: false }, + { title: "端口", key: "port", sortable: false }, + { title: "来源", key: "source", sortable: false }, + { title: "", key: "status", sortable: false, width: "1.5rem" }, + { title: "", key: "actions", sortable: false, width: "1rem" } + ]); + return (_ctx, _cache) => { + const _component_v_chip = _resolveComponent$g("v-chip"); + const _component_v_icon = _resolveComponent$g("v-icon"); + const _component_v_btn = _resolveComponent$g("v-btn"); + const _component_v_tooltip = _resolveComponent$g("v-tooltip"); + const _component_v_data_table = _resolveComponent$g("v-data-table"); + return _openBlock$g(), _createBlock$g(_component_v_data_table, { + class: "px-4", + headers: headers.value, + search: __props.search, + items: __props.proxies, + page: __props.page, + "items-per-page": __props.itemsPerPage, + "items-per-page-options": _unref$d(itemsPerPageOptions), + "item-key": "name", + density: "compact", + "hide-default-footer": "", + "fixed-header": "" + }, { + "item.name": _withCtx$g(({ item }) => [ + _createVNode$g(_component_v_chip, { + size: "small", + pill: "", + color: "secondary" + }, { + default: _withCtx$g(() => [ + _createTextVNode$g(_toDisplayString$e(item.data.name), 1) + ]), + _: 2 + }, 1024), + item.v2ray_link ? (_openBlock$g(), _createBlock$g(_component_v_btn, { + key: 0, + icon: "", + size: "small", + color: "secondary", + variant: "text", + onClick: ($event) => emit("copyToClipboard", item.v2ray_link) + }, { + default: _withCtx$g(() => [ + _createVNode$g(_component_v_icon, null, { + default: _withCtx$g(() => _cache[1] || (_cache[1] = [ + _createTextVNode$g("mdi-link") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick"])) : _createCommentVNode$c("", true) + ]), + "item.type": _withCtx$g(({ item }) => [ + _createVNode$g(_component_v_chip, { + color: _unref$d(getProxyColor)(item.data.type), + size: "small", + label: "", + variant: "tonal" + }, { + default: _withCtx$g(() => [ + _createTextVNode$g(_toDisplayString$e(item.data.type), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.server": _withCtx$g(({ item }) => [ + _createElementVNode$d("small", null, _toDisplayString$e(item.data.server), 1) + ]), + "item.port": _withCtx$g(({ item }) => [ + _createVNode$g(_component_v_chip, { + size: "x-small", + label: "", + variant: "tonal", + color: "primary" + }, { + default: _withCtx$g(() => [ + _createTextVNode$g(_toDisplayString$e(item.data.port), 1) + ]), + _: 2 + }, 1024) + ]), + "item.source": _withCtx$g(({ item }) => [ + _createVNode$g(_component_v_chip, { + color: _unref$d(getSourceColor)(item.meta.source), + size: "small", + variant: "outlined" + }, { + default: _withCtx$g(() => [ + _createTextVNode$g(_toDisplayString$e(item.meta.source), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.status": _withCtx$g(({ item }) => [ + _createElementVNode$d("div", _hoisted_1$c, [ + _createVNode$g(_component_v_icon, { + color: item.meta.disabled ? "grey" : "success", + class: "mr-1" + }, { + default: _withCtx$g(() => [ + _createTextVNode$g(_toDisplayString$e(item.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 2 + }, 1032, ["color"]), + item.meta.invisible_to && item.meta.invisible_to.length > 0 ? (_openBlock$g(), _createBlock$g(_component_v_tooltip, { + key: 0, + text: "已配置可见性限制", + location: "top" + }, { + activator: _withCtx$g(({ props }) => [ + _createVNode$g(_component_v_icon, _mergeProps$a(props, { + size: "small", + color: "warning" + }), { + default: _withCtx$g(() => _cache[2] || (_cache[2] = [ + _createTextVNode$g(" mdi-eye-off-outline ") + ])), + _: 2 + }, 1040) + ]), + _: 1 + })) : _createCommentVNode$c("", true), + item.meta.patched ? (_openBlock$g(), _createBlock$g(_component_v_tooltip, { + key: 1, + text: "已应用补丁", + location: "top" + }, { + activator: _withCtx$g(({ props }) => [ + _createVNode$g(_component_v_icon, _mergeProps$a(props, { + size: "small", + color: "info" + }), { + default: _withCtx$g(() => _cache[3] || (_cache[3] = [ + _createTextVNode$g(" mdi-auto-fix ") + ])), + _: 2 + }, 1040) + ]), + _: 1 + })) : _createCommentVNode$c("", true) + ]) + ]), + "item.actions": _withCtx$g(({ item }) => [ + _createVNode$g(_sfc_main$h, { + proxy: item, + onChangeStatus: (disabled) => emit("changeStatus", item.data.name, disabled), + onShowYaml: ($event) => emit("showYaml", item.data), + onEdit: ($event) => emit("editProxy", item), + onDelete: ($event) => emit("deleteProxy", item.data.name), + onDeletePatch: ($event) => emit("deletePatch", item.data.name), + onCopyToClipboard: _cache[0] || (_cache[0] = (text) => emit("copyToClipboard", text)), + onEditVisibility: ($event) => emit("editVisibility", item.data.name) + }, null, 8, ["proxy", "onChangeStatus", "onShowYaml", "onEdit", "onDelete", "onDeletePatch", "onEditVisibility"]) + ]), + _: 1 + }, 8, ["headers", "search", "items", "page", "items-per-page", "items-per-page-options"]); + }; + } +}); + +const {defineComponent:_defineComponent$f} = await importShared('vue'); + +const {toDisplayString:_toDisplayString$d,createElementVNode:_createElementVNode$c,unref:_unref$c,createTextVNode:_createTextVNode$f,resolveComponent:_resolveComponent$f,withCtx:_withCtx$f,createVNode:_createVNode$f,openBlock:_openBlock$f,createBlock:_createBlock$f} = await importShared('vue'); + +const _hoisted_1$b = { class: "d-flex justify-space-between align-center px-4 pt-3" }; +const _hoisted_2$9 = ["title"]; +const _hoisted_3$9 = ["title"]; +const _sfc_main$f = /* @__PURE__ */ _defineComponent$f({ + __name: "ProxyCard", + props: { + proxyData: { + type: Object, + required: true + } + }, + emits: ["copyToClipboard", "showYaml", "editProxy", "deleteProxy", "deletePatch", "changeStatus"], + setup(__props, { emit: __emit }) { + const emit = __emit; + return (_ctx, _cache) => { + const _component_v_chip = _resolveComponent$f("v-chip"); + const _component_v_col = _resolveComponent$f("v-col"); + const _component_v_row = _resolveComponent$f("v-row"); + const _component_v_card_text = _resolveComponent$f("v-card-text"); + const _component_v_divider = _resolveComponent$f("v-divider"); + const _component_v_icon = _resolveComponent$f("v-icon"); + const _component_v_spacer = _resolveComponent$f("v-spacer"); + const _component_v_card_actions = _resolveComponent$f("v-card-actions"); + const _component_v_card = _resolveComponent$f("v-card"); + return _openBlock$f(), _createBlock$f(_component_v_card, { + rounded: "lg", + elevation: "2", + class: "proxy-card h-100 transition-swing", + variant: "tonal" + }, { + default: _withCtx$f(() => [ + _createElementVNode$c("div", _hoisted_1$b, [ + _createElementVNode$c("span", { + class: "font-weight-bold text-truncate", + title: __props.proxyData.data.name + }, _toDisplayString$d(__props.proxyData.data.name), 9, _hoisted_2$9), + _createVNode$f(_component_v_chip, { + size: "small", + color: _unref$c(getSourceColor)(__props.proxyData.meta.source), + variant: "outlined" + }, { + default: _withCtx$f(() => [ + _createTextVNode$f(_toDisplayString$d(__props.proxyData.meta.source), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _createVNode$f(_component_v_card_text, { class: "pt-2 pb-4" }, { + default: _withCtx$f(() => [ + _createVNode$f(_component_v_row, { + "no-gutters": "", + class: "mb-2 align-center" + }, { + default: _withCtx$f(() => [ + _createVNode$f(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$f(() => _cache[6] || (_cache[6] = [ + _createTextVNode$f("类型") + ])), + _: 1 + }), + _createVNode$f(_component_v_col, { cols: "9" }, { + default: _withCtx$f(() => [ + _createVNode$f(_component_v_chip, { + color: _unref$c(getProxyColor)(__props.proxyData.data.type), + size: "x-small", + label: "", + variant: "tonal", + class: "font-weight-medium" + }, { + default: _withCtx$f(() => [ + _createTextVNode$f(_toDisplayString$d(__props.proxyData.data.type), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$f(_component_v_row, { + "no-gutters": "", + class: "align-center" + }, { + default: _withCtx$f(() => [ + _createVNode$f(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$f(() => _cache[7] || (_cache[7] = [ + _createTextVNode$f("服务器") + ])), + _: 1 + }), + _createVNode$f(_component_v_col, { + cols: "9", + class: "text-body-2 text-truncate font-weight-medium" + }, { + default: _withCtx$f(() => [ + _createElementVNode$c("span", { + title: __props.proxyData.data.server + }, _toDisplayString$d(__props.proxyData.data.server), 9, _hoisted_3$9) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$f(_component_v_divider), + _createVNode$f(_component_v_card_actions, null, { + default: _withCtx$f(() => [ + _createVNode$f(_component_v_icon, { + color: __props.proxyData.meta.disabled ? "grey" : "success" + }, { + default: _withCtx$f(() => [ + _createTextVNode$f(_toDisplayString$d(__props.proxyData.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 1 + }, 8, ["color"]), + _createVNode$f(_component_v_spacer), + _createVNode$f(_sfc_main$h, { + proxy: __props.proxyData, + onChangeStatus: _cache[0] || (_cache[0] = (disabled) => emit("changeStatus", __props.proxyData.data.name, disabled)), + onShowYaml: _cache[1] || (_cache[1] = ($event) => emit("showYaml", __props.proxyData.data)), + onEdit: _cache[2] || (_cache[2] = ($event) => emit("editProxy", __props.proxyData)), + onDelete: _cache[3] || (_cache[3] = ($event) => emit("deleteProxy", __props.proxyData.data.name)), + onDeletePatch: _cache[4] || (_cache[4] = ($event) => emit("deletePatch", __props.proxyData.data.name)), + onCopyToClipboard: _cache[5] || (_cache[5] = (text) => emit("copyToClipboard", text)) + }, null, 8, ["proxy"]) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const ProxyCard = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["__scopeId", "data-v-ca5a79a2"]]); + +const {defineComponent:_defineComponent$e} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$e,resolveComponent:_resolveComponent$e,withCtx:_withCtx$e,createVNode:_createVNode$e,openBlock:_openBlock$e,createBlock:_createBlock$e,createCommentVNode:_createCommentVNode$b,createElementBlock:_createElementBlock$7,withModifiers:_withModifiers$2,createElementVNode:_createElementVNode$b} = await importShared('vue'); + +const _hoisted_1$a = { key: 0 }; +const _hoisted_2$8 = { + key: 0, + class: "mt-2" +}; +const _hoisted_3$8 = { + key: 0, + class: "mt-2" +}; +const _hoisted_4$8 = { + key: 0, + class: "mt-2" +}; +const _hoisted_5$5 = { + key: 0, + class: "mt-2" +}; +const _hoisted_6$2 = { + key: 0, + class: "mt-2" +}; +const _hoisted_7$2 = { key: 0 }; +const {ref: ref$a,toRaw: toRaw$4} = await importShared('vue'); + +const _sfc_main$e = /* @__PURE__ */ _defineComponent$e({ + __name: "ProxiesDialog", + props: { + proxyData: { + type: Object, + required: true + }, + api: { + type: Object, + required: true + } + }, + emits: ["close", "refresh", "show-snackbar", "show-error"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const proxyForm = ref$a(null); + const loading = ref$a(false); + const tab = ref$a("general"); + const proxy = ref$a(structuredClone(toRaw$4(props.proxyData.data))); + const wsHeaderString = ref$a(""); + const httpHeaderString = ref$a(""); + const jsonValidator = (value) => { + if (!value) return true; + try { + JSON.parse(value); + return true; + } catch (e) { + return "无效的JSON格式"; + } + }; + const proxyTypes = [ + "ss", + "ssr", + "vmess", + "vless", + "trojan", + "http", + "snell", + "tuic", + "hysteria", + "hysteria2" + ]; + const initNestedObjects = () => { + if (!proxy.value.smux) { + proxy.value.smux = { enabled: false, protocol: "h2mux" }; + } + if (!proxy.value.smux["brutal-opts"]) { + proxy.value.smux["brutal-opts"] = { enabled: false }; + } + if (!proxy.value["ws-opts"]) { + proxy.value["ws-opts"] = { + path: "/", + "v2ray-http-upgrade": false, + "v2ray-http-upgrade-fast-open": false + }; + } + if (!proxy.value["http-opts"]) { + proxy.value["http-opts"] = { path: ["/"], method: "GET" }; + } + if (!proxy.value["h2-opts"]) { + proxy.value["h2-opts"] = { path: "/", host: [] }; + } + if (!proxy.value["grpc-opts"]) { + proxy.value["grpc-opts"] = { "grpc-service-name": "" }; + } + if (!proxy.value.alpn) { + proxy.value.alpn = []; + } + if (proxy.value["ws-opts"]?.headers) { + wsHeaderString.value = JSON.stringify(proxy.value["ws-opts"].headers, null, 2); + } + if (proxy.value["http-opts"]?.headers) { + httpHeaderString.value = JSON.stringify(proxy.value["http-opts"].headers, null, 2); + } + }; + initNestedObjects(); + const parseHeaders = () => { + if (proxy.value.network === "ws") { + try { + if (proxy.value["ws-opts"]) { + proxy.value["ws-opts"].headers = JSON.parse(wsHeaderString.value || "{}"); + } + } catch (e) { + console.error("Invalid JSON format for ws headers:", e); + if (proxy.value["ws-opts"]) { + proxy.value["ws-opts"].headers = {}; + } + } + } + if (proxy.value.network === "http") { + try { + if (proxy.value["http-opts"]) { + proxy.value["http-opts"].headers = JSON.parse(httpHeaderString.value || "{}"); + } + } catch (e) { + console.error("Invalid JSON format for http headers:", e); + if (proxy.value["http-opts"]) { + proxy.value["http-opts"].headers = {}; + } + } + } + }; + const handleSave = async () => { + const { valid } = await proxyForm.value.validate(); + if (valid) { + parseHeaders(); + const finalProxy = { ...proxy.value }; + if (finalProxy.network !== "ws") delete finalProxy["ws-opts"]; + if (finalProxy.network !== "http") delete finalProxy["http-opts"]; + if (finalProxy.network !== "h2") delete finalProxy["h2-opts"]; + if (finalProxy.network !== "grpc") delete finalProxy["grpc-opts"]; + if (!finalProxy.smux?.enabled) { + delete finalProxy.smux; + } else if (!finalProxy.smux["brutal-opts"]?.enabled) { + delete finalProxy.smux["brutal-opts"]; + } + if (!finalProxy.tls) { + delete finalProxy.servername; + delete finalProxy.fingerprint; + delete finalProxy.alpn; + delete finalProxy["skip-cert-verify"]; + delete finalProxy["client-fingerprint"]; + delete finalProxy.sni; + } else { + if (["vmess", "vless"].includes(finalProxy.type)) { + delete finalProxy.sni; + } else { + delete finalProxy.servername; + } + if (!["vmess", "vless", "trojan"].includes(finalProxy.type)) { + delete finalProxy["client-fingerprint"]; + } + } + await saveProxy(finalProxy); + } + }; + const saveProxy = async (proxy2) => { + loading.value = true; + try { + const requestData = { + source: props.proxyData?.meta.source, + proxy: proxy2 + }; + const name = encodeURIComponent(props.proxyData.data.name); + const result = await props.api.patch(`/plugin/ClashRuleProvider/proxies/${name}`, requestData); + if (!result.success) { + emit("show-error", "保存出站代理失败: " + (result.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "保存出站代理失败", + color: "error" + }); + return; + } + emit("refresh"); + emit("show-snackbar", { + show: true, + message: "出站代理更新成功", + color: "success" + }); + emit("close"); + } catch (err) { + if (err instanceof Error) emit("show-error", "保存 Proxy 失败: " + (err.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "保存代理失败", + color: "error" + }); + } finally { + loading.value = false; + } + }; + return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent$e("v-card-title"); + const _component_v_tab = _resolveComponent$e("v-tab"); + const _component_v_tabs = _resolveComponent$e("v-tabs"); + const _component_v_text_field = _resolveComponent$e("v-text-field"); + const _component_v_col = _resolveComponent$e("v-col"); + const _component_v_select = _resolveComponent$e("v-select"); + const _component_v_switch = _resolveComponent$e("v-switch"); + const _component_v_row = _resolveComponent$e("v-row"); + const _component_v_container = _resolveComponent$e("v-container"); + const _component_v_window_item = _resolveComponent$e("v-window-item"); + const _component_v_combobox = _resolveComponent$e("v-combobox"); + const _component_v_expand_transition = _resolveComponent$e("v-expand-transition"); + const _component_v_textarea = _resolveComponent$e("v-textarea"); + const _component_v_card_text = _resolveComponent$e("v-card-text"); + const _component_v_card = _resolveComponent$e("v-card"); + const _component_v_window = _resolveComponent$e("v-window"); + const _component_v_form = _resolveComponent$e("v-form"); + const _component_v_alert = _resolveComponent$e("v-alert"); + const _component_v_spacer = _resolveComponent$e("v-spacer"); + const _component_v_btn = _resolveComponent$e("v-btn"); + const _component_v_card_actions = _resolveComponent$e("v-card-actions"); + const _component_v_dialog = _resolveComponent$e("v-dialog"); + return _openBlock$e(), _createBlock$e(_component_v_dialog, { + "max-width": "50rem", + persistent: "" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_card, null, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_card_title, null, { + default: _withCtx$e(() => _cache[45] || (_cache[45] = [ + _createTextVNode$e("编辑代理") + ])), + _: 1 + }), + _createVNode$e(_component_v_card_text, { class: "pa-2" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_form, { + ref_key: "proxyForm", + ref: proxyForm, + onSubmit: _withModifiers$2(handleSave, ["prevent"]) + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_tabs, { + modelValue: tab.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => tab.value = $event), + "background-color": "primary", + dark: "", + grow: "" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_tab, { value: "general" }, { + default: _withCtx$e(() => _cache[46] || (_cache[46] = [ + _createTextVNode$e("通用") + ])), + _: 1 + }), + _createVNode$e(_component_v_tab, { value: "tls" }, { + default: _withCtx$e(() => _cache[47] || (_cache[47] = [ + _createTextVNode$e("TLS") + ])), + _: 1 + }), + _createVNode$e(_component_v_tab, { value: "transport" }, { + default: _withCtx$e(() => _cache[48] || (_cache[48] = [ + _createTextVNode$e("传输层") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createVNode$e(_component_v_window, { + modelValue: tab.value, + "onUpdate:modelValue": _cache[43] || (_cache[43] = ($event) => tab.value = $event), + class: "pt-4" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_window_item, { value: "general" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_container, { fluid: "" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_row, { dense: "" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value.name, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => proxy.value.name = $event), + disabled: "", + label: "名称 (name)", + rules: [(v) => !!v || "名称不能为空"], + hint: "代理名称", + variant: "outlined" + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_select, { + modelValue: proxy.value.type, + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => proxy.value.type = $event), + label: "类型 (type)", + items: proxyTypes, + rules: [(v) => !!v || "类型不能为空"], + hint: "代理协议类型", + variant: "outlined" + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value.server, + "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => proxy.value.server = $event), + label: "服务器 (server)", + rules: [(v) => !!v || "服务器地址不能为空"], + hint: "代理服务器地址 (域名/IP)", + variant: "outlined" + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value.port, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => proxy.value.port = $event), + modelModifiers: { number: true }, + label: "端口 (port)", + type: "number", + rules: [(v) => !!v || "端口不能为空"], + hint: "代理服务器端口", + variant: "outlined" + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_select, { + modelValue: proxy.value["ip-version"], + "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => proxy.value["ip-version"] = $event), + label: "IP版本 (ip-version)", + items: ["dual", "ipv4", "ipv6", "ipv4-prefer", "ipv6-prefer"], + hint: "出站使用的IP版本", + clearable: "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value["interface-name"], + "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => proxy.value["interface-name"] = $event), + label: "网络接口 (interface-name)", + hint: "指定出站网络接口", + clearable: "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value["routing-mark"], + "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => proxy.value["routing-mark"] = $event), + modelModifiers: { number: true }, + label: "路由标记 (routing-mark)", + type: "number", + hint: "为出站连接设置路由标记", + clearable: "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value["dialer-proxy"], + "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => proxy.value["dialer-proxy"] = $event), + label: "拨号代理 (dialer-proxy)", + hint: "指定当前代理通过哪个代理建立连接", + clearable: "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "4", + sm: "4" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value.udp, + "onUpdate:modelValue": _cache[9] || (_cache[9] = ($event) => proxy.value.udp = $event), + label: "UDP", + hint: "是否允许UDP", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "4", + sm: "4" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value.tfo, + "onUpdate:modelValue": _cache[10] || (_cache[10] = ($event) => proxy.value.tfo = $event), + label: "TFO", + hint: "启用 TCP Fast Open", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "4", + sm: "4" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value.mptcp, + "onUpdate:modelValue": _cache[11] || (_cache[11] = ($event) => proxy.value.mptcp = $event), + label: "MPTCP", + hint: "启用 Multi-Path TCP", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$e(_component_v_window_item, { value: "tls" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_container, { fluid: "" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_row, { dense: "" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_col, { cols: "12" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value.tls, + "onUpdate:modelValue": _cache[12] || (_cache[12] = ($event) => proxy.value.tls = $event), + label: "启用 TLS", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$e(_component_v_expand_transition, null, { + default: _withCtx$e(() => [ + proxy.value.tls ? (_openBlock$e(), _createElementBlock$7("div", _hoisted_1$a, [ + _createVNode$e(_component_v_row, { dense: "" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + ["vmess", "vless"].includes(proxy.value.type) ? (_openBlock$e(), _createBlock$e(_component_v_text_field, { + key: 0, + modelValue: proxy.value.servername, + "onUpdate:modelValue": _cache[13] || (_cache[13] = ($event) => proxy.value.servername = $event), + label: "服务器名称 (servername)", + hint: "TLS服务器名称(SNI)", + clearable: "", + "persistent-hint": "", + variant: "outlined" + }, null, 8, ["modelValue"])) : (_openBlock$e(), _createBlock$e(_component_v_text_field, { + key: 1, + modelValue: proxy.value.sni, + "onUpdate:modelValue": _cache[14] || (_cache[14] = ($event) => proxy.value.sni = $event), + label: "SNI", + hint: "TLS服务器名称(SNI)", + clearable: "", + "persistent-hint": "", + variant: "outlined" + }, null, 8, ["modelValue"])) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value.fingerprint, + "onUpdate:modelValue": _cache[15] || (_cache[15] = ($event) => proxy.value.fingerprint = $event), + label: "指纹 (fingerprint)", + hint: "证书指纹", + clearable: "", + "persistent-hint": "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { cols: "12" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_combobox, { + modelValue: proxy.value.alpn, + "onUpdate:modelValue": _cache[16] || (_cache[16] = ($event) => proxy.value.alpn = $event), + label: "ALPN", + hint: "应用层协议协商", + multiple: "", + chips: "", + clearable: "", + "deletable-chips": "", + "persistent-hint": "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + ["vmess", "vless", "trojan"].includes(proxy.value.type) ? (_openBlock$e(), _createBlock$e(_component_v_col, { + key: 0, + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_select, { + modelValue: proxy.value["client-fingerprint"], + "onUpdate:modelValue": _cache[17] || (_cache[17] = ($event) => proxy.value["client-fingerprint"] = $event), + label: "客户端指纹 (client-fingerprint)", + items: [ + "chrome", + "firefox", + "safari", + "ios", + "android", + "edge", + "360", + "qq", + "random" + ], + hint: "uTLS客户端指紋", + clearable: "", + "persistent-hint": "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + })) : _createCommentVNode$b("", true), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6", + class: "d-flex align-center" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value["skip-cert-verify"], + "onUpdate:modelValue": _cache[18] || (_cache[18] = ($event) => proxy.value["skip-cert-verify"] = $event), + label: "跳过证书验证", + inset: "", + "persistent-hint": "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ])) : _createCommentVNode$b("", true) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$e(_component_v_window_item, { value: "transport" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_container, { fluid: "" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_row, null, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_col, { cols: "12" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_select, { + modelValue: proxy.value.network, + "onUpdate:modelValue": _cache[19] || (_cache[19] = ($event) => proxy.value.network = $event), + label: "网络 (network)", + items: ["http", "h2", "grpc", "ws"], + hint: "传输层协议", + clearable: "", + "persistent-hint": "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$e(_component_v_expand_transition, null, { + default: _withCtx$e(() => [ + proxy.value.network === "ws" && proxy.value["ws-opts"] ? (_openBlock$e(), _createElementBlock$7("div", _hoisted_2$8, [ + _createVNode$e(_component_v_card, { variant: "tonal" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { + default: _withCtx$e(() => _cache[49] || (_cache[49] = [ + _createTextVNode$e("WebSocket 选项") + ])), + _: 1 + }), + _createVNode$e(_component_v_card_text, null, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_row, null, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value["ws-opts"]["v2ray-http-upgrade"], + "onUpdate:modelValue": _cache[20] || (_cache[20] = ($event) => proxy.value["ws-opts"]["v2ray-http-upgrade"] = $event), + label: "v2ray-http-upgrade", + hint: "启用 http upgrade", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value["ws-opts"]["v2ray-http-upgrade-fast-open"], + "onUpdate:modelValue": _cache[21] || (_cache[21] = ($event) => proxy.value["ws-opts"]["v2ray-http-upgrade-fast-open"] = $event), + label: "v2ray-http-upgrade-fast-open", + hint: "启用 http upgrade 的 fast open", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value["ws-opts"].path, + "onUpdate:modelValue": _cache[22] || (_cache[22] = ($event) => proxy.value["ws-opts"].path = $event), + label: "路径 (path)", + hint: "WebSocket请求路径", + variant: "outlined", + class: "mb-2" + }, null, 8, ["modelValue"]), + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value["ws-opts"]["max-early-data"], + "onUpdate:modelValue": _cache[23] || (_cache[23] = ($event) => proxy.value["ws-opts"]["max-early-data"] = $event), + modelModifiers: { number: true }, + label: "max-early-data", + type: "number", + hint: "Early Data 首包长度阈值", + variant: "outlined", + class: "mb-2", + clearable: "" + }, null, 8, ["modelValue"]), + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value["ws-opts"]["early-data-header-name"], + "onUpdate:modelValue": _cache[24] || (_cache[24] = ($event) => proxy.value["ws-opts"]["early-data-header-name"] = $event), + label: "early-data-header-name", + variant: "outlined", + class: "mb-2", + clearable: "" + }, null, 8, ["modelValue"]), + _createVNode$e(_component_v_textarea, { + modelValue: wsHeaderString.value, + "onUpdate:modelValue": _cache[25] || (_cache[25] = ($event) => wsHeaderString.value = $event), + label: "请求头 (headers)", + hint: '请输入JSON格式字符串, 例如: {"Host":"example.com"}', + variant: "outlined", + rows: "3", + rules: [jsonValidator] + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ])) : _createCommentVNode$b("", true) + ]), + _: 1 + }), + _createVNode$e(_component_v_expand_transition, null, { + default: _withCtx$e(() => [ + proxy.value.network === "http" && proxy.value["http-opts"] ? (_openBlock$e(), _createElementBlock$7("div", _hoisted_3$8, [ + _createVNode$e(_component_v_card, { variant: "tonal" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { + default: _withCtx$e(() => _cache[50] || (_cache[50] = [ + _createTextVNode$e("HTTP 选项") + ])), + _: 1 + }), + _createVNode$e(_component_v_card_text, null, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value["http-opts"].method, + "onUpdate:modelValue": _cache[26] || (_cache[26] = ($event) => proxy.value["http-opts"].method = $event), + label: "方法 (method)", + hint: "HTTP请求方法", + variant: "outlined", + class: "mb-2" + }, null, 8, ["modelValue"]), + _createVNode$e(_component_v_combobox, { + modelValue: proxy.value["http-opts"].path, + "onUpdate:modelValue": _cache[27] || (_cache[27] = ($event) => proxy.value["http-opts"].path = $event), + label: "路径 (path)", + hint: "HTTP请求路径", + multiple: "", + chips: "", + clearable: "", + "deletable-chips": "", + variant: "outlined", + class: "mb-2" + }, null, 8, ["modelValue"]), + _createVNode$e(_component_v_textarea, { + modelValue: httpHeaderString.value, + "onUpdate:modelValue": _cache[28] || (_cache[28] = ($event) => httpHeaderString.value = $event), + label: "请求头 (headers)", + hint: '请输入JSON格式字符串, 例如: {"Host":"example.com"}', + variant: "outlined", + rows: "3", + rules: [jsonValidator] + }, null, 8, ["modelValue", "rules"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ])) : _createCommentVNode$b("", true) + ]), + _: 1 + }), + _createVNode$e(_component_v_expand_transition, null, { + default: _withCtx$e(() => [ + proxy.value.network === "h2" && proxy.value["h2-opts"] ? (_openBlock$e(), _createElementBlock$7("div", _hoisted_4$8, [ + _createVNode$e(_component_v_card, { variant: "tonal" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { + default: _withCtx$e(() => _cache[51] || (_cache[51] = [ + _createTextVNode$e("H2 选项") + ])), + _: 1 + }), + _createVNode$e(_component_v_card_text, null, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value["h2-opts"].path, + "onUpdate:modelValue": _cache[29] || (_cache[29] = ($event) => proxy.value["h2-opts"].path = $event), + label: "路径 (path)", + hint: "H2请求路径", + variant: "outlined", + class: "mb-2" + }, null, 8, ["modelValue"]), + _createVNode$e(_component_v_combobox, { + modelValue: proxy.value["h2-opts"].host, + "onUpdate:modelValue": _cache[30] || (_cache[30] = ($event) => proxy.value["h2-opts"].host = $event), + label: "主机 (host)", + hint: "主机域名列表", + multiple: "", + chips: "", + clearable: "", + "deletable-chips": "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ])) : _createCommentVNode$b("", true) + ]), + _: 1 + }), + _createVNode$e(_component_v_expand_transition, null, { + default: _withCtx$e(() => [ + proxy.value.network === "grpc" && proxy.value["grpc-opts"] ? (_openBlock$e(), _createElementBlock$7("div", _hoisted_5$5, [ + _createVNode$e(_component_v_card, { variant: "tonal" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { + default: _withCtx$e(() => _cache[52] || (_cache[52] = [ + _createTextVNode$e("gRPC 选项") + ])), + _: 1 + }), + _createVNode$e(_component_v_card_text, null, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value["grpc-opts"]["grpc-service-name"], + "onUpdate:modelValue": _cache[31] || (_cache[31] = ($event) => proxy.value["grpc-opts"]["grpc-service-name"] = $event), + label: "服务名称 (grpc-service-name)", + hint: "gRPC服务名称", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ])) : _createCommentVNode$b("", true) + ]), + _: 1 + }), + _createVNode$e(_component_v_expand_transition, null, { + default: _withCtx$e(() => [ + (!proxy.value.network || proxy.value.network === "tcp") && proxy.value.smux ? (_openBlock$e(), _createElementBlock$7("div", _hoisted_6$2, [ + _createVNode$e(_component_v_card, { variant: "tonal" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_card_title, { class: "text-subtitle-1 py-2" }, { + default: _withCtx$e(() => _cache[53] || (_cache[53] = [ + _createTextVNode$e("Smux 选项") + ])), + _: 1 + }), + _createVNode$e(_component_v_card_text, null, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_row, { dense: "" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_col, { cols: "12" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value.smux.enabled, + "onUpdate:modelValue": _cache[32] || (_cache[32] = ($event) => proxy.value.smux.enabled = $event), + label: "启用 Smux", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$e(_component_v_expand_transition, null, { + default: _withCtx$e(() => [ + proxy.value.smux.enabled && proxy.value.smux["brutal-opts"] ? (_openBlock$e(), _createElementBlock$7("div", _hoisted_7$2, [ + _createVNode$e(_component_v_row, { dense: "" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_select, { + modelValue: proxy.value.smux.protocol, + "onUpdate:modelValue": _cache[33] || (_cache[33] = ($event) => proxy.value.smux.protocol = $event), + label: "协议 (protocol)", + items: ["smux", "yamux", "h2mux"], + hint: "Smux协议类型", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value.smux["max-connections"], + "onUpdate:modelValue": _cache[34] || (_cache[34] = ($event) => proxy.value.smux["max-connections"] = $event), + modelModifiers: { number: true }, + label: "最大连接数 (max-connections)", + type: "number", + hint: "最大复用连接数", + clearable: "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value.smux["min-streams"], + "onUpdate:modelValue": _cache[35] || (_cache[35] = ($event) => proxy.value.smux["min-streams"] = $event), + modelModifiers: { number: true }, + label: "最小流数 (min-streams)", + type: "number", + hint: "每个连接的最小流数", + clearable: "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value.smux["max-streams"], + "onUpdate:modelValue": _cache[36] || (_cache[36] = ($event) => proxy.value.smux["max-streams"] = $event), + modelModifiers: { number: true }, + label: "最大流数 (max-streams)", + type: "number", + hint: "每个连接的最大流数", + clearable: "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "4", + sm: "4" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value.smux.padding, + "onUpdate:modelValue": _cache[37] || (_cache[37] = ($event) => proxy.value.smux.padding = $event), + label: "Padding", + hint: "启用Padding", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "4", + sm: "4" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value.smux.statistic, + "onUpdate:modelValue": _cache[38] || (_cache[38] = ($event) => proxy.value.smux.statistic = $event), + label: "Statistic", + hint: "启用统计", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "4", + sm: "4" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value.smux["only-tcp"], + "onUpdate:modelValue": _cache[39] || (_cache[39] = ($event) => proxy.value.smux["only-tcp"] = $event), + label: "Only TCP", + hint: "仅用于TCP", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { cols: "12" }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_switch, { + modelValue: proxy.value.smux["brutal-opts"].enabled, + "onUpdate:modelValue": _cache[40] || (_cache[40] = ($event) => proxy.value.smux["brutal-opts"].enabled = $event), + label: "启用 Brutal 优化", + inset: "", + color: "primary" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$e(_component_v_expand_transition, null, { + default: _withCtx$e(() => [ + proxy.value.smux["brutal-opts"].enabled ? (_openBlock$e(), _createBlock$e(_component_v_row, { + key: 0, + dense: "" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value.smux["brutal-opts"].up, + "onUpdate:modelValue": _cache[41] || (_cache[41] = ($event) => proxy.value.smux["brutal-opts"].up = $event), + modelModifiers: { number: true }, + label: "上行带宽 (up)", + hint: "上行带宽, 默认以 Mbps 为单位", + clearable: "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode$e(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_text_field, { + modelValue: proxy.value.smux["brutal-opts"].down, + "onUpdate:modelValue": _cache[42] || (_cache[42] = ($event) => proxy.value.smux["brutal-opts"].down = $event), + modelModifiers: { number: true }, + label: "下行带宽 (down)", + hint: "下行带宽, 默认以 Mbps 为单位", + clearable: "", + variant: "outlined" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$b("", true) + ]), + _: 1 + }) + ])) : _createCommentVNode$b("", true) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ])) : _createCommentVNode$b("", true) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }, 512), + _createVNode$e(_component_v_alert, { + type: "info", + variant: "tonal" + }, { + default: _withCtx$e(() => _cache[54] || (_cache[54] = [ + _createTextVNode$e(" 参考"), + _createElementVNode$b("a", { + href: "https://wiki.metacubex.one/config/proxies/", + target: "_blank", + style: { "text-decoration": "underline" } + }, "Docs", -1), + _createTextVNode$e(", 覆写某些选项可能导致代理不可用。 ") + ])), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$e(_component_v_card_actions, null, { + default: _withCtx$e(() => [ + _createVNode$e(_component_v_spacer), + _createVNode$e(_component_v_btn, { + onClick: _cache[44] || (_cache[44] = ($event) => emit("close")) + }, { + default: _withCtx$e(() => _cache[55] || (_cache[55] = [ + _createTextVNode$e("取消") + ])), + _: 1 + }), + _createVNode$e(_component_v_btn, { + color: "primary", + loading: loading.value, + disabled: loading.value, + onClick: handleSave + }, { + default: _withCtx$e(() => _cache[56] || (_cache[56] = [ + _createTextVNode$e("保存") + ])), + _: 1 + }, 8, ["loading", "disabled"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$d} = await importShared('vue'); + +const {resolveComponent:_resolveComponent$d,createVNode:_createVNode$d,withCtx:_withCtx$d,createElementVNode:_createElementVNode$a,renderList:_renderList$6,Fragment:_Fragment$6,openBlock:_openBlock$d,createElementBlock:_createElementBlock$6,createBlock:_createBlock$d,unref:_unref$b,toDisplayString:_toDisplayString$c,createTextVNode:_createTextVNode$d,mergeProps:_mergeProps$9,createCommentVNode:_createCommentVNode$a} = await importShared('vue'); + +const _hoisted_1$9 = { class: "mb-2 position-relative" }; +const _hoisted_2$7 = { class: "pa-4" }; +const _hoisted_3$7 = { class: "d-none d-sm-flex clash-data-table" }; +const _hoisted_4$7 = { class: "d-sm-none" }; +const _hoisted_5$4 = { + class: "pa-4", + style: { "min-height": "4rem" } +}; +const {ref: ref$9,computed: computed$3} = await importShared('vue'); +const _sfc_main$d = /* @__PURE__ */ _defineComponent$d({ + __name: "ProxiesTab", + props: { + proxies: {}, + api: {} + }, + emits: ["refresh", "show-snackbar", "show-error", "show-yaml", "copy-to-clipboard", "edit-visibility"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const editorOptions = { + enableBasicAutocompletion: true, + enableSnippets: true, + enableLiveAutocompletion: true, + showLineNumbers: true, + tabSize: 2 + }; + const proxiesPlaceholder = ref$9( + `proxies: + - name: "ss node" + type: "ss"` + ); + const importProxiesTypes = ["YAML", "LINK"]; + const searchProxies = ref$9(""); + const pageProxies = ref$9(1); + const itemsPerPageProxies = ref$9(10); + const loading = ref$9(false); + const filteredExtraProxies = computed$3(() => { + if (!searchProxies.value) return props.proxies; + const keyword = searchProxies.value.toLowerCase(); + return props.proxies.filter( + (item) => Object.values(item).some((val) => String(val).toLowerCase().includes(keyword)) + ); + }); + const paginatedExtraProxies = computed$3(() => { + const start = (pageProxies.value - 1) * itemsPerPageProxies.value; + const end = start + itemsPerPageProxies.value; + return filteredExtraProxies.value.slice(start, end); + }); + const pageCountProxies = computed$3(() => { + if (itemsPerPageProxies.value === -1) { + return 1; + } + return Math.ceil(props.proxies.length / itemsPerPageProxies.value); + }); + const importExtraProxiesPlaceholderText = computed$3(() => { + return importProxies.value.type === "YAML" ? "proxies: []" : "vless://xxxx"; + }); + const importExtraProxiesDialog = ref$9(false); + const importProxiesLoading = ref$9(false); + const importProxies = ref$9({ + type: "YAML", + payload: "" + }); + function openImportProxiesDialog() { + importProxies.value = { + type: "YAML", + payload: "" + }; + importExtraProxiesDialog.value = true; + } + async function importExtraProxies() { + try { + importProxiesLoading.value = true; + const requestData = { + vehicle: importProxies.value.type, + payload: importProxies.value.payload + }; + const result = await props.api.put("/plugin/ClashRuleProvider/proxies", requestData); + if (!result.success) { + emit("show-error", "节点导入失败: " + (result.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "节点导入失败", + color: "error" + }); + return; + } + importExtraProxiesDialog.value = false; + emit("refresh", ["proxies", "clash-outbounds"]); + emit("show-snackbar", { + show: true, + message: "节点导入成功", + color: "success" + }); + } catch (err) { + if (err instanceof Error) emit("show-error", "节点导入失败: " + (err.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "节点导入失败", + color: "error" + }); + } finally { + importProxiesLoading.value = false; + } + } + const proxiesDialogVisible = ref$9(false); + const editingProxy = ref$9({ + meta: { ...defaultMetadata }, + data: { ...defaultProxy }, + name: defaultProxy.name + }); + function openProxiesDialog(proxyData) { + editingProxy.value = proxyData; + proxiesDialogVisible.value = true; + } + function closeProxyDialog() { + proxiesDialogVisible.value = false; + } + async function deleteProxy(name) { + loading.value = true; + try { + await props.api.delete(`/plugin/ClashRuleProvider/proxies/${name}`); + emit("refresh", ["proxies", "clash-outbounds"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "删除规则失败"); + } + } finally { + loading.value = false; + } + } + async function deletePatch(name) { + loading.value = true; + try { + const n = encodeURIComponent(name); + await props.api.delete(`/plugin/ClashRuleProvider/proxies/${n}/patch`); + emit("refresh", ["proxies", "clash-outbounds"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "删除补丁失败"); + } + } finally { + loading.value = false; + } + } + async function handleStatusChange(name, disabled) { + loading.value = true; + try { + const proxy = props.proxies.find((p) => p.data.name === name); + if (!proxy) { + emit("show-error", "Proxy not found"); + return; + } + const n = encodeURIComponent(name); + const newMeta = { ...proxy.meta, disabled }; + await props.api.patch(`/plugin/ClashRuleProvider/proxies/${n}/meta`, newMeta); + emit("refresh", ["proxies", "clash-outbounds"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "更新代理状态失败"); + } + } finally { + loading.value = false; + } + } + function editVisibility(name) { + const proxy = props.proxies.find((p) => p.data.name === name); + if (!proxy) { + emit("show-error", "Proxy not found"); + return; + } + const n = encodeURIComponent(name); + emit("edit-visibility", proxy.meta, `/plugin/ClashRuleProvider/proxies/${n}/meta`, "proxies"); + } + return (_ctx, _cache) => { + const _component_v_progress_circular = _resolveComponent$d("v-progress-circular"); + const _component_v_overlay = _resolveComponent$d("v-overlay"); + const _component_v_text_field = _resolveComponent$d("v-text-field"); + const _component_v_col = _resolveComponent$d("v-col"); + const _component_v_btn = _resolveComponent$d("v-btn"); + const _component_v_btn_group = _resolveComponent$d("v-btn-group"); + const _component_v_row = _resolveComponent$d("v-row"); + const _component_v_pagination = _resolveComponent$d("v-pagination"); + const _component_v_list_item_title = _resolveComponent$d("v-list-item-title"); + const _component_v_list_item = _resolveComponent$d("v-list-item"); + const _component_v_list = _resolveComponent$d("v-list"); + const _component_v_menu = _resolveComponent$d("v-menu"); + const _component_v_divider = _resolveComponent$d("v-divider"); + const _component_v_card_title = _resolveComponent$d("v-card-title"); + const _component_v_select = _resolveComponent$d("v-select"); + const _component_v_textarea = _resolveComponent$d("v-textarea"); + const _component_v_alert = _resolveComponent$d("v-alert"); + const _component_v_card_text = _resolveComponent$d("v-card-text"); + const _component_v_spacer = _resolveComponent$d("v-spacer"); + const _component_v_card_actions = _resolveComponent$d("v-card-actions"); + const _component_v_card = _resolveComponent$d("v-card"); + const _component_v_dialog = _resolveComponent$d("v-dialog"); + return _openBlock$d(), _createElementBlock$6("div", _hoisted_1$9, [ + _createVNode$d(_component_v_overlay, { + modelValue: loading.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => loading.value = $event), + contained: "", + class: "align-center justify-center" + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_progress_circular, { + indeterminate: "", + color: "primary" + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createElementVNode$a("div", _hoisted_2$7, [ + _createVNode$d(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_col, { + cols: "10", + sm: "6", + class: "d-flex justify-start" + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_text_field, { + modelValue: searchProxies.value, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => searchProxies.value = $event), + label: "搜索出站代理", + clearable: "", + density: "compact", + variant: "solo-filled", + "hide-details": "", + class: "search-field", + "prepend-inner-icon": "mdi-magnify", + flat: "", + rounded: "pill", + "single-line": "", + disabled: loading.value + }, null, 8, ["modelValue", "disabled"]) + ]), + _: 1 + }), + _createVNode$d(_component_v_col, { + cols: "2", + sm: "6", + class: "d-flex justify-end" + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_btn_group, { + variant: "outlined", + rounded: "" + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_btn, { + icon: "mdi-import", + disabled: loading.value, + onClick: openImportProxiesDialog + }, null, 8, ["disabled"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createElementVNode$a("div", _hoisted_3$7, [ + _createVNode$d(_sfc_main$g, { + "items-per-page": itemsPerPageProxies.value, + page: pageProxies.value, + proxies: _ctx.proxies, + onCopyToClipboard: _cache[2] || (_cache[2] = (t) => emit("copy-to-clipboard", t)), + onShowYaml: _cache[3] || (_cache[3] = (o) => emit("show-yaml", o)), + onEditProxy: openProxiesDialog, + onDeleteProxy: deleteProxy, + onDeletePatch: deletePatch, + onChangeStatus: handleStatusChange, + onEditVisibility: editVisibility + }, null, 8, ["items-per-page", "page", "proxies"]) + ]), + _createElementVNode$a("div", _hoisted_4$7, [ + _createVNode$d(_component_v_row, null, { + default: _withCtx$d(() => [ + (_openBlock$d(true), _createElementBlock$6(_Fragment$6, null, _renderList$6(paginatedExtraProxies.value, (item) => { + return _openBlock$d(), _createBlock$d(_component_v_col, { + key: item.data.name, + cols: "12" + }, { + default: _withCtx$d(() => [ + _createVNode$d(ProxyCard, { + "proxy-data": item, + onCopyToClipboard: _cache[4] || (_cache[4] = (t) => emit("copy-to-clipboard", t)), + onShowYaml: _cache[5] || (_cache[5] = (o) => emit("show-yaml", o)), + onEditProxy: openProxiesDialog, + onDeleteProxy: deleteProxy, + onDeletePatch: deletePatch, + onChangeStatus: handleStatusChange + }, null, 8, ["proxy-data"]) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + _: 1 + }) + ]), + _createElementVNode$a("div", _hoisted_5$4, [ + _createVNode$d(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_col, { + cols: "2", + md: "1" + }), + _createVNode$d(_component_v_col, { + cols: "8", + md: "10", + class: "d-flex justify-center" + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_pagination, { + modelValue: pageProxies.value, + "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => pageProxies.value = $event), + length: pageCountProxies.value, + "total-visible": "5", + class: "d-none d-sm-flex my-0", + rounded: "circle", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]), + _createVNode$d(_component_v_pagination, { + modelValue: pageProxies.value, + "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => pageProxies.value = $event), + length: pageCountProxies.value, + "total-visible": "0", + class: "d-sm-none my-0", + rounded: "circle", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]) + ]), + _: 1 + }), + _createVNode$d(_component_v_col, { + cols: "2", + md: "1", + class: "d-flex justify-end" + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_menu, null, { + activator: _withCtx$d(({ props: props2 }) => [ + _createVNode$d(_component_v_btn, _mergeProps$9(props2, { + icon: "", + rounded: "circle", + variant: "tonal", + disabled: loading.value + }), { + default: _withCtx$d(() => [ + _createTextVNode$d(_toDisplayString$c(_unref$b(pageTitle)(itemsPerPageProxies.value)), 1) + ]), + _: 2 + }, 1040, ["disabled"]) + ]), + default: _withCtx$d(() => [ + _createVNode$d(_component_v_list, null, { + default: _withCtx$d(() => [ + (_openBlock$d(true), _createElementBlock$6(_Fragment$6, null, _renderList$6(_unref$b(itemsPerPageOptions), (item, index) => { + return _openBlock$d(), _createBlock$d(_component_v_list_item, { + key: index, + value: item.value, + onClick: ($event) => itemsPerPageProxies.value = item.value + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_list_item_title, null, { + default: _withCtx$d(() => [ + _createTextVNode$d(_toDisplayString$c(item.title), 1) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1032, ["value", "onClick"]); + }), 128)) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createVNode$d(_component_v_divider), + _createVNode$d(_component_v_dialog, { + modelValue: importExtraProxiesDialog.value, + "onUpdate:modelValue": _cache[12] || (_cache[12] = ($event) => importExtraProxiesDialog.value = $event), + "max-width": "40rem" + }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_card, null, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_card_title, null, { + default: _withCtx$d(() => _cache[17] || (_cache[17] = [ + _createTextVNode$d("导入节点") + ])), + _: 1 + }), + _createVNode$d(_component_v_card_text, { style: { "max-height": "900px", "overflow-y": "auto" } }, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_select, { + modelValue: importProxies.value.type, + "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => importProxies.value.type = $event), + items: importProxiesTypes, + label: "内容格式", + required: "", + class: "mb-4" + }, null, 8, ["modelValue"]), + importProxies.value.type === "YAML" ? (_openBlock$d(), _createBlock$d(_unref$b(VAceEditor), { + key: 0, + value: importProxies.value.payload, + "onUpdate:value": _cache[9] || (_cache[9] = ($event) => importProxies.value.payload = $event), + lang: "yaml", + theme: "monokai", + options: editorOptions, + placeholder: proxiesPlaceholder.value, + style: { "height": "30rem", "width": "100%", "margin-bottom": "16px" } + }, null, 8, ["value", "placeholder"])) : (_openBlock$d(), _createBlock$d(_component_v_textarea, { + key: 1, + modelValue: importProxies.value.payload, + "onUpdate:modelValue": _cache[10] || (_cache[10] = ($event) => importProxies.value.payload = $event), + label: "内容", + required: "", + placeholder: importExtraProxiesPlaceholderText.value, + class: "mb-4", + rows: "4", + "auto-grow": "" + }, null, 8, ["modelValue", "placeholder"])), + importProxies.value.type === "YAML" ? (_openBlock$d(), _createBlock$d(_component_v_alert, { + key: 2, + type: "info", + dense: "", + variant: "tonal" + }, { + default: _withCtx$d(() => _cache[18] || (_cache[18] = [ + _createTextVNode$d(" 请输入 Clash 规则中的 "), + _createElementVNode$a("strong", null, "proxies", -1), + _createTextVNode$d(" 字段,例如:"), + _createElementVNode$a("br", null, null, -1), + _createElementVNode$a("pre", { style: { "white-space": "pre-wrap", "font-family": "monospace", "margin": "0" } }, [ + _createTextVNode$d(""), + _createElementVNode$a("code", null, 'proxies:\n - name: "ss node"\n type: "ss"') + ], -1) + ])), + _: 1 + })) : _createCommentVNode$a("", true), + importProxies.value.type === "LINK" ? (_openBlock$d(), _createBlock$d(_component_v_alert, { + key: 3, + type: "info", + dense: "", + variant: "tonal" + }, { + default: _withCtx$d(() => _cache[19] || (_cache[19] = [ + _createTextVNode$d(" 请输入 V2RayN 格式的分享链接,例如:"), + _createElementVNode$a("br", null, null, -1), + _createElementVNode$a("code", null, "vmess://xxxx", -1), + _createElementVNode$a("br", null, null, -1), + _createElementVNode$a("code", null, "ss://xxxx", -1) + ])), + _: 1 + })) : _createCommentVNode$a("", true) + ]), + _: 1 + }), + _createVNode$d(_component_v_card_actions, null, { + default: _withCtx$d(() => [ + _createVNode$d(_component_v_spacer), + _createVNode$d(_component_v_btn, { + color: "secondary", + onClick: _cache[11] || (_cache[11] = ($event) => importExtraProxiesDialog.value = false) + }, { + default: _withCtx$d(() => _cache[20] || (_cache[20] = [ + _createTextVNode$d("取消") + ])), + _: 1 + }), + _createVNode$d(_component_v_btn, { + color: "primary", + loading: importProxiesLoading.value, + onClick: importExtraProxies + }, { + default: _withCtx$d(() => _cache[21] || (_cache[21] = [ + _createTextVNode$d(" 导入 ") + ])), + _: 1 + }, 8, ["loading"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]), + proxiesDialogVisible.value ? (_openBlock$d(), _createBlock$d(_sfc_main$e, { + key: 0, + modelValue: proxiesDialogVisible.value, + "onUpdate:modelValue": _cache[13] || (_cache[13] = ($event) => proxiesDialogVisible.value = $event), + "proxy-data": editingProxy.value, + api: _ctx.api, + onRefresh: _cache[14] || (_cache[14] = ($event) => emit("refresh", ["proxies"])), + onClose: closeProxyDialog, + onShowSnackbar: _cache[15] || (_cache[15] = (val) => emit("show-snackbar", val)), + onShowError: _cache[16] || (_cache[16] = (msg) => emit("show-error", msg)) + }, null, 8, ["modelValue", "proxy-data", "api"])) : _createCommentVNode$a("", true) + ]); + }; + } +}); + +const {defineComponent:_defineComponent$c} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$c,resolveComponent:_resolveComponent$c,withCtx:_withCtx$c,createVNode:_createVNode$c,unref:_unref$a,toDisplayString:_toDisplayString$b,createElementVNode:_createElementVNode$9,openBlock:_openBlock$c,createBlock:_createBlock$c,createCommentVNode:_createCommentVNode$9} = await importShared('vue'); + +const _hoisted_1$8 = { class: "card-header pa-4" }; +const _hoisted_2$6 = { class: "d-flex align-center overflow-hidden" }; +const _hoisted_3$6 = { class: "d-flex flex-column overflow-hidden" }; +const _hoisted_4$6 = { class: "text-subtitle-1 font-weight-bold text-truncate" }; +const _hoisted_5$3 = { class: "text-caption text-medium-emphasis text-truncate" }; +const _hoisted_6$1 = { + class: "d-flex flex-wrap gap-2 mb-4", + style: { "gap": "8px" } +}; +const _hoisted_7$1 = { class: "stats-grid mb-4" }; +const _hoisted_8$1 = { class: "stat-item" }; +const _hoisted_9 = { class: "text-body-2 font-weight-bold" }; +const _hoisted_10 = { class: "stat-item text-right" }; +const _hoisted_11 = { class: "text-body-2 font-weight-bold" }; +const _hoisted_12 = { class: "d-flex justify-space-between text-caption text-medium-emphasis" }; +const _hoisted_13 = { class: "card-actions px-4 py-2 d-flex align-center bg-surface-variant-lighten" }; +const _hoisted_14 = { class: "d-flex align-center" }; +const {ref: ref$8} = await importShared('vue'); + +const _sfc_main$c = /* @__PURE__ */ _defineComponent$c({ + __name: "SubscriptionCard", + props: { + info: {}, + url: {}, + api: {} + }, + emits: ["show-error", "show-snackbar", "refresh", "copy-to-clipboard", "start-loading", "end-loading"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const loading = ref$8(false); + async function updateSubscription() { + loading.value = true; + emit("start-loading"); + try { + await props.api.put("plugin/ClashRuleProvider/refresh", { + url: props.url + }); + emit("show-snackbar", { + show: true, + message: "订阅更新成功", + color: "success" + }); + emit("refresh", [ + "status", + "clash-outbounds", + "rule-providers", + "proxy-groups", + "proxies", + "proxy-providers" + ]); + } catch (err) { + if (err instanceof Error) emit("show-error", "订阅更新失败: " + (err.message || "未知错误")); + } finally { + loading.value = false; + emit("end-loading"); + } + } + async function toggleSubscription(val) { + emit("start-loading"); + try { + await props.api.post("plugin/ClashRuleProvider/subscription-info", { + url: props.url, + enabled: val + }); + emit("show-snackbar", { + show: true, + message: "设置成功", + color: "success" + }); + emit("refresh", ["status"]); + } catch (err) { + if (err instanceof Error) emit("show-error", "设置自动更新失败: " + (err.message || "未知错误")); + emit("refresh", ["status"]); + } finally { + emit("end-loading"); + } + } + return (_ctx, _cache) => { + const _component_v_icon = _resolveComponent$c("v-icon"); + const _component_v_avatar = _resolveComponent$c("v-avatar"); + const _component_v_spacer = _resolveComponent$c("v-spacer"); + const _component_v_divider = _resolveComponent$c("v-divider"); + const _component_v_chip = _resolveComponent$c("v-chip"); + const _component_v_progress_linear = _resolveComponent$c("v-progress-linear"); + const _component_v_card_text = _resolveComponent$c("v-card-text"); + const _component_v_tooltip = _resolveComponent$c("v-tooltip"); + const _component_v_btn = _resolveComponent$c("v-btn"); + const _component_v_switch = _resolveComponent$c("v-switch"); + const _component_v_card = _resolveComponent$c("v-card"); + return _openBlock$c(), _createBlock$c(_component_v_card, { + class: "subscription-card mb-4", + elevation: "0", + border: "" + }, { + default: _withCtx$c(() => [ + _createElementVNode$9("div", _hoisted_1$8, [ + _createElementVNode$9("div", _hoisted_2$6, [ + _createVNode$c(_component_v_avatar, { + color: "primary", + variant: "tonal", + rounded: "lg", + class: "mr-3" + }, { + default: _withCtx$c(() => [ + _createVNode$c(_component_v_icon, null, { + default: _withCtx$c(() => _cache[0] || (_cache[0] = [ + _createTextVNode$c("mdi-rss") + ])), + _: 1 + }) + ]), + _: 1 + }), + _createElementVNode$9("div", _hoisted_3$6, [ + _createElementVNode$9("div", _hoisted_4$6, _toDisplayString$b(_unref$a(extractDomain)(_ctx.url)), 1), + _createElementVNode$9("div", _hoisted_5$3, _toDisplayString$b(_ctx.url), 1) + ]) + ]), + _createVNode$c(_component_v_spacer) + ]), + _createVNode$c(_component_v_divider), + _createVNode$c(_component_v_card_text, { class: "pa-4" }, { + default: _withCtx$c(() => [ + _createElementVNode$9("div", _hoisted_6$1, [ + _ctx.info.proxy_num != null ? (_openBlock$c(), _createBlock$c(_component_v_chip, { + key: 0, + size: "small", + color: "primary", + variant: "flat", + class: "font-weight-medium" + }, { + default: _withCtx$c(() => [ + _createVNode$c(_component_v_icon, { + start: "", + size: "small" + }, { + default: _withCtx$c(() => _cache[1] || (_cache[1] = [ + _createTextVNode$c("mdi-server-network") + ])), + _: 1 + }), + _createTextVNode$c(" " + _toDisplayString$b(_ctx.info.proxy_num) + " 节点 ", 1) + ]), + _: 1 + })) : _createCommentVNode$9("", true), + _ctx.info.last_update ? (_openBlock$c(), _createBlock$c(_component_v_chip, { + key: 1, + size: "small", + color: "secondary", + variant: "tonal" + }, { + default: _withCtx$c(() => [ + _createVNode$c(_component_v_icon, { + start: "", + size: "small" + }, { + default: _withCtx$c(() => _cache[2] || (_cache[2] = [ + _createTextVNode$c("mdi-clock-outline") + ])), + _: 1 + }), + _createTextVNode$c(" " + _toDisplayString$b(_unref$a(formatTimestamp)(_ctx.info.last_update)), 1) + ]), + _: 1 + })) : _createCommentVNode$9("", true), + _ctx.info.expire ? (_openBlock$c(), _createBlock$c(_component_v_chip, { + key: 2, + size: "small", + color: _unref$a(getExpireColor)(_ctx.info.expire), + variant: "tonal" + }, { + default: _withCtx$c(() => [ + _createVNode$c(_component_v_icon, { + start: "", + size: "small" + }, { + default: _withCtx$c(() => _cache[3] || (_cache[3] = [ + _createTextVNode$c("mdi-calendar-clock") + ])), + _: 1 + }), + _createTextVNode$c(" 到期:" + _toDisplayString$b(_unref$a(formatTimestamp)(_ctx.info.expire)), 1) + ]), + _: 1 + }, 8, ["color"])) : _createCommentVNode$9("", true) + ]), + _createElementVNode$9("div", _hoisted_7$1, [ + _createElementVNode$9("div", _hoisted_8$1, [ + _cache[4] || (_cache[4] = _createElementVNode$9("div", { class: "text-caption text-medium-emphasis" }, "已用", -1)), + _createElementVNode$9("div", _hoisted_9, _toDisplayString$b(_unref$a(formatBytes)(_ctx.info.download + _ctx.info.upload)), 1) + ]), + _createElementVNode$9("div", _hoisted_10, [ + _cache[5] || (_cache[5] = _createElementVNode$9("div", { class: "text-caption text-medium-emphasis" }, "剩余", -1)), + _createElementVNode$9("div", _hoisted_11, _toDisplayString$b(_unref$a(formatBytes)(_ctx.info.total - _ctx.info.download - _ctx.info.upload)), 1) + ]) + ]), + _createVNode$c(_component_v_progress_linear, { + "model-value": _unref$a(getUsedPercentageFloor)(_ctx.info), + color: _unref$a(getUsageColor)(_unref$a(getUsedPercentageFloor)(_ctx.info)), + height: "8", + rounded: "", + class: "mb-2" + }, null, 8, ["model-value", "color"]), + _createElementVNode$9("div", _hoisted_12, [ + _createElementVNode$9("span", null, "↓ " + _toDisplayString$b(_unref$a(formatBytes)(_ctx.info.download)), 1), + _createElementVNode$9("span", null, "↑ " + _toDisplayString$b(_unref$a(formatBytes)(_ctx.info.upload)), 1), + _createElementVNode$9("span", null, "Total: " + _toDisplayString$b(_unref$a(formatBytes)(_ctx.info.total)), 1) + ]) + ]), + _: 1 + }), + _createVNode$c(_component_v_divider), + _createElementVNode$9("div", _hoisted_13, [ + _createVNode$c(_component_v_btn, { + icon: "", + size: "small", + variant: "text", + color: "primary", + loading: loading.value, + onClick: updateSubscription + }, { + default: _withCtx$c(() => [ + _createVNode$c(_component_v_icon, null, { + default: _withCtx$c(() => _cache[6] || (_cache[6] = [ + _createTextVNode$c("mdi-refresh") + ])), + _: 1 + }), + _createVNode$c(_component_v_tooltip, { + activator: "parent", + location: "top" + }, { + default: _withCtx$c(() => _cache[7] || (_cache[7] = [ + _createTextVNode$c("刷新") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["loading"]), + _createVNode$c(_component_v_spacer), + _createElementVNode$9("div", _hoisted_14, [ + _cache[8] || (_cache[8] = _createElementVNode$9("span", { class: "text-caption mr-2 text-medium-emphasis" }, "自动更新", -1)), + _createVNode$c(_component_v_switch, { + "model-value": _ctx.info.enabled, + "hide-details": "", + density: "compact", + color: "primary", + inset: "", + "onUpdate:modelValue": toggleSubscription + }, null, 8, ["model-value"]) + ]) + ]) + ]), + _: 1 + }); + }; + } +}); + +const SubscriptionCard = /* @__PURE__ */ _export_sfc(_sfc_main$c, [["__scopeId", "data-v-97c0f367"]]); + +const {defineComponent:_defineComponent$b} = await importShared('vue'); + +const {resolveComponent:_resolveComponent$b,createVNode:_createVNode$b,withCtx:_withCtx$b,createTextVNode:_createTextVNode$b,createElementVNode:_createElementVNode$8,openBlock:_openBlock$b,createBlock:_createBlock$b,createCommentVNode:_createCommentVNode$8,renderList:_renderList$5,Fragment:_Fragment$5,createElementBlock:_createElementBlock$5} = await importShared('vue'); + +const _hoisted_1$7 = { class: "mb-2 position-relative" }; +const {ref: ref$7} = await importShared('vue'); + +const _sfc_main$b = /* @__PURE__ */ _defineComponent$b({ + __name: "SubscriptionTab", + props: { + subscriptionsInfo: {}, + api: {} + }, + emits: ["show-error", "show-snackbar", "refresh", "copy-to-clipboard", "switch"], + setup(__props, { emit: __emit }) { + const emit = __emit; + const loading = ref$7(false); + return (_ctx, _cache) => { + const _component_v_progress_circular = _resolveComponent$b("v-progress-circular"); + const _component_v_overlay = _resolveComponent$b("v-overlay"); + const _component_v_icon = _resolveComponent$b("v-icon"); + const _component_v_btn = _resolveComponent$b("v-btn"); + const _component_v_card_text = _resolveComponent$b("v-card-text"); + const _component_v_card = _resolveComponent$b("v-card"); + const _component_v_row = _resolveComponent$b("v-row"); + const _component_v_col = _resolveComponent$b("v-col"); + return _openBlock$b(), _createElementBlock$5("div", _hoisted_1$7, [ + _createVNode$b(_component_v_overlay, { + modelValue: loading.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => loading.value = $event), + contained: "", + class: "align-center justify-center" + }, { + default: _withCtx$b(() => [ + _createVNode$b(_component_v_progress_circular, { + indeterminate: "", + color: "primary" + }) + ]), + _: 1 + }, 8, ["modelValue"]), + Object.keys(_ctx.subscriptionsInfo).length === 0 ? (_openBlock$b(), _createBlock$b(_component_v_row, { + key: 0, + class: "pa-6 justify-center" + }, { + default: _withCtx$b(() => [ + _createVNode$b(_component_v_card, { + class: "mx-auto text-center py-8 px-4", + "max-width": "400", + elevation: "10", + rounded: "xl", + style: { "background": "linear-gradient(135deg, #d6c355 0%, #fda085 100%)" } + }, { + default: _withCtx$b(() => [ + _createVNode$b(_component_v_card_text, { class: "d-flex flex-column align-center" }, { + default: _withCtx$b(() => [ + _createVNode$b(_component_v_icon, { + size: "64", + color: "white", + class: "mb-4 bounce" + }, { + default: _withCtx$b(() => _cache[8] || (_cache[8] = [ + _createTextVNode$b(" mdi-emoticon-happy-outline ") + ])), + _: 1 + }), + _cache[10] || (_cache[10] = _createElementVNode$8("h2", { class: "text-h6 font-weight-bold white--text mb-2" }, "还没有订阅呢 🎉", -1)), + _cache[11] || (_cache[11] = _createElementVNode$8("p", { class: "white--text mb-4" }, "试试添加一个订阅吧!", -1)), + _createVNode$b(_component_v_btn, { + color: "info", + dark: "", + rounded: "", + elevation: "6", + onClick: _cache[1] || (_cache[1] = ($event) => emit("switch")) + }, { + default: _withCtx$b(() => _cache[9] || (_cache[9] = [ + _createTextVNode$b(" 去配置 🚀 ") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$8("", true), + _createVNode$b(_component_v_row, { class: "pa-4" }, { + default: _withCtx$b(() => [ + (_openBlock$b(true), _createElementBlock$5(_Fragment$5, null, _renderList$5(_ctx.subscriptionsInfo, (info, url) => { + return _openBlock$b(), _createBlock$b(_component_v_col, { + key: url, + cols: "12", + md: "6" + }, { + default: _withCtx$b(() => [ + _createVNode$b(SubscriptionCard, { + info, + url: String(url), + api: _ctx.api, + onRefresh: _cache[2] || (_cache[2] = (r) => emit("refresh", r)), + onShowSnackbar: _cache[3] || (_cache[3] = (val) => emit("show-snackbar", val)), + onShowError: _cache[4] || (_cache[4] = (msg) => emit("show-error", msg)), + onCopyToClipboard: _cache[5] || (_cache[5] = (t) => emit("copy-to-clipboard", t)), + onStartLoading: _cache[6] || (_cache[6] = ($event) => loading.value = true), + onEndLoading: _cache[7] || (_cache[7] = ($event) => loading.value = false) + }, null, 8, ["info", "url", "api"]) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + _: 1 + }) + ]); + }; + } +}); + +const SubscriptionTab = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["__scopeId", "data-v-6a1d5a83"]]); + +const {defineComponent:_defineComponent$a} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$a,resolveComponent:_resolveComponent$a,withCtx:_withCtx$a,createVNode:_createVNode$a,mergeProps:_mergeProps$8,unref:_unref$9,toDisplayString:_toDisplayString$a,openBlock:_openBlock$a,createBlock:_createBlock$a,createCommentVNode:_createCommentVNode$7} = await importShared('vue'); +const _sfc_main$a = /* @__PURE__ */ _defineComponent$a({ + __name: "RuleProviderActionMenu", + props: { + ruleProvider: { + type: Object, + required: true + } + }, + emits: ["showYaml", "edit", "delete", "changeStatus", "editVisibility"], + setup(__props, { emit: __emit }) { + const emit = __emit; + return (_ctx, _cache) => { + const _component_v_icon = _resolveComponent$a("v-icon"); + const _component_v_btn = _resolveComponent$a("v-btn"); + const _component_v_list_item_title = _resolveComponent$a("v-list-item-title"); + const _component_v_list_item = _resolveComponent$a("v-list-item"); + const _component_v_list = _resolveComponent$a("v-list"); + const _component_v_menu = _resolveComponent$a("v-menu"); + return _openBlock$a(), _createBlock$a(_component_v_menu, { "min-width": "120" }, { + activator: _withCtx$a(({ props }) => [ + _createVNode$a(_component_v_btn, _mergeProps$8({ + color: "secondary", + icon: "", + size: "small", + variant: "text" + }, props), { + default: _withCtx$a(() => [ + _createVNode$a(_component_v_icon, null, { + default: _withCtx$a(() => _cache[5] || (_cache[5] = [ + _createTextVNode$a("mdi-dots-vertical") + ])), + _: 1 + }) + ]), + _: 2 + }, 1040) + ]), + default: _withCtx$a(() => [ + _createVNode$a(_component_v_list, { density: "compact" }, { + default: _withCtx$a(() => [ + _unref$9(isManual)(__props.ruleProvider.meta.source) ? (_openBlock$a(), _createBlock$a(_component_v_list_item, { + key: 0, + onClick: _cache[0] || (_cache[0] = ($event) => emit("changeStatus", !__props.ruleProvider.meta.disabled)) + }, { + prepend: _withCtx$a(() => [ + _createVNode$a(_component_v_icon, { + size: "small", + color: __props.ruleProvider.meta.disabled ? "success" : "grey" + }, { + default: _withCtx$a(() => [ + _createTextVNode$a(_toDisplayString$a(__props.ruleProvider.meta.disabled ? "mdi-play-circle-outline" : "mdi-stop-circle-outline"), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + default: _withCtx$a(() => [ + _createVNode$a(_component_v_list_item_title, null, { + default: _withCtx$a(() => [ + _createTextVNode$a(_toDisplayString$a(__props.ruleProvider.meta.disabled ? "启用" : "禁用"), 1) + ]), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$7("", true), + _unref$9(isManual)(__props.ruleProvider.meta.source) ? (_openBlock$a(), _createBlock$a(_component_v_list_item, { + key: 1, + onClick: _cache[1] || (_cache[1] = ($event) => emit("edit")) + }, { + prepend: _withCtx$a(() => [ + _createVNode$a(_component_v_icon, { + size: "small", + color: "primary" + }, { + default: _withCtx$a(() => _cache[6] || (_cache[6] = [ + _createTextVNode$a("mdi-file-edit-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$a(() => [ + _createVNode$a(_component_v_list_item_title, null, { + default: _withCtx$a(() => _cache[7] || (_cache[7] = [ + _createTextVNode$a("编辑") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$7("", true), + _createVNode$a(_component_v_list_item, { + onClick: _cache[2] || (_cache[2] = ($event) => emit("showYaml")) + }, { + prepend: _withCtx$a(() => [ + _createVNode$a(_component_v_icon, { + size: "small", + color: "info" + }, { + default: _withCtx$a(() => _cache[8] || (_cache[8] = [ + _createTextVNode$a("mdi-code-json") + ])), + _: 1 + }) + ]), + default: _withCtx$a(() => [ + _createVNode$a(_component_v_list_item_title, null, { + default: _withCtx$a(() => _cache[9] || (_cache[9] = [ + _createTextVNode$a("查看") + ])), + _: 1 + }) + ]), + _: 1 + }), + _unref$9(isManual)(__props.ruleProvider.meta.source) ? (_openBlock$a(), _createBlock$a(_component_v_list_item, { + key: 2, + onClick: _cache[3] || (_cache[3] = ($event) => emit("editVisibility")) + }, { + prepend: _withCtx$a(() => [ + _createVNode$a(_component_v_icon, { + size: "small", + color: "warning" + }, { + default: _withCtx$a(() => _cache[10] || (_cache[10] = [ + _createTextVNode$a("mdi-eye-off-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$a(() => [ + _createVNode$a(_component_v_list_item_title, null, { + default: _withCtx$a(() => _cache[11] || (_cache[11] = [ + _createTextVNode$a("隐藏") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$7("", true), + _unref$9(isManual)(__props.ruleProvider.meta.source) ? (_openBlock$a(), _createBlock$a(_component_v_list_item, { + key: 3, + onClick: _cache[4] || (_cache[4] = ($event) => emit("delete")) + }, { + prepend: _withCtx$a(() => [ + _createVNode$a(_component_v_icon, { + size: "small", + color: "error" + }, { + default: _withCtx$a(() => _cache[12] || (_cache[12] = [ + _createTextVNode$a("mdi-trash-can-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$a(() => [ + _createVNode$a(_component_v_list_item_title, null, { + default: _withCtx$a(() => _cache[13] || (_cache[13] = [ + _createTextVNode$a("删除") + ])), + _: 1 + }) + ]), + _: 1 + })) : _createCommentVNode$7("", true) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$9} = await importShared('vue'); + +const {unref:_unref$8,toDisplayString:_toDisplayString$9,createTextVNode:_createTextVNode$9,resolveComponent:_resolveComponent$9,withCtx:_withCtx$9,createVNode:_createVNode$9,openBlock:_openBlock$9,createBlock:_createBlock$9,createCommentVNode:_createCommentVNode$6,mergeProps:_mergeProps$7,createElementVNode:_createElementVNode$7} = await importShared('vue'); + +const _hoisted_1$6 = { class: "d-flex align-center" }; +const {ref: ref$6} = await importShared('vue'); +const _sfc_main$9 = /* @__PURE__ */ _defineComponent$9({ + __name: "RuleProvidersTable", + props: { + ruleProviders: { + type: Array, + required: true + }, + page: { + type: Number, + required: true + }, + itemsPerPage: { + type: Number, + required: true + }, + search: String + }, + emits: ["editRuleProvider", "deleteRuleProvider", "showYaml", "changeStatus", "editVisibility"], + setup(__props, { emit: __emit }) { + const emit = __emit; + const headersRuleProviders = ref$6([ + { title: "名称", key: "name", sortable: true }, + { title: "类型", key: "type", sortable: true }, + { title: "行为", key: "behavior", sortable: false }, + { title: "格式", key: "format", sortable: false }, + { title: "来源", key: "source", sortable: false }, + { title: "", key: "status", sortable: false, width: "1rem" }, + { title: "", key: "actions", sortable: false, width: "1rem" } + ]); + return (_ctx, _cache) => { + const _component_v_chip = _resolveComponent$9("v-chip"); + const _component_v_icon = _resolveComponent$9("v-icon"); + const _component_v_tooltip = _resolveComponent$9("v-tooltip"); + const _component_v_data_table = _resolveComponent$9("v-data-table"); + return _openBlock$9(), _createBlock$9(_component_v_data_table, { + headers: headersRuleProviders.value, + items: __props.ruleProviders, + search: __props.search, + page: __props.page, + "items-per-page": __props.itemsPerPage, + "items-per-page-options": _unref$8(itemsPerPageOptions), + "item-key": "name", + class: "px-4", + density: "compact", + "hide-default-footer": "", + "fixed-header": "" + }, { + "item.name": _withCtx$9(({ item }) => [ + _createVNode$9(_component_v_chip, { + size: "small", + pill: "", + color: "secondary" + }, { + default: _withCtx$9(() => [ + _createTextVNode$9(_toDisplayString$9(item.name), 1) + ]), + _: 2 + }, 1024) + ]), + "item.type": _withCtx$9(({ item }) => [ + _createVNode$9(_component_v_chip, { + size: "small", + label: "", + variant: "tonal", + color: "primary" + }, { + default: _withCtx$9(() => [ + _createTextVNode$9(_toDisplayString$9(item.data.type), 1) + ]), + _: 2 + }, 1024) + ]), + "item.behavior": _withCtx$9(({ item }) => [ + item.data?.behavior ? (_openBlock$9(), _createBlock$9(_component_v_chip, { + key: 0, + color: _unref$8(getBehaviorColor)(item.data.behavior), + size: "small", + label: "", + variant: "tonal" + }, { + default: _withCtx$9(() => [ + _createTextVNode$9(_toDisplayString$9(item.data.behavior), 1) + ]), + _: 2 + }, 1032, ["color"])) : _createCommentVNode$6("", true) + ]), + "item.format": _withCtx$9(({ item }) => [ + _createVNode$9(_component_v_chip, { + color: _unref$8(getFormatColor)(item.data.format), + size: "small", + label: "", + variant: "tonal" + }, { + default: _withCtx$9(() => [ + _createTextVNode$9(_toDisplayString$9(item.data.format), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.source": _withCtx$9(({ item }) => [ + _createVNode$9(_component_v_chip, { + color: _unref$8(getSourceColor)(item.meta.source), + size: "small", + variant: "outlined" + }, { + default: _withCtx$9(() => [ + _createTextVNode$9(_toDisplayString$9(item.meta.source), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.status": _withCtx$9(({ item }) => [ + _createElementVNode$7("div", _hoisted_1$6, [ + _createVNode$9(_component_v_icon, { + color: item.meta.disabled ? "grey" : "success", + class: "mr-1" + }, { + default: _withCtx$9(() => [ + _createTextVNode$9(_toDisplayString$9(item.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 2 + }, 1032, ["color"]), + item.meta.invisible_to && item.meta.invisible_to.length > 0 ? (_openBlock$9(), _createBlock$9(_component_v_tooltip, { + key: 0, + text: "已配置可见性限制", + location: "top" + }, { + activator: _withCtx$9(({ props }) => [ + _createVNode$9(_component_v_icon, _mergeProps$7(props, { + size: "small", + color: "warning" + }), { + default: _withCtx$9(() => _cache[0] || (_cache[0] = [ + _createTextVNode$9(" mdi-eye-off-outline ") + ])), + _: 2 + }, 1040) + ]), + _: 1 + })) : _createCommentVNode$6("", true) + ]) + ]), + "item.actions": _withCtx$9(({ item }) => [ + _createVNode$9(_sfc_main$a, { + "rule-provider": item, + onChangeStatus: (disabled) => emit("changeStatus", item.name, disabled), + onEdit: ($event) => emit("editRuleProvider", item.name), + onShowYaml: ($event) => emit("showYaml", item.data), + onDelete: ($event) => emit("deleteRuleProvider", item.name), + onEditVisibility: ($event) => emit("editVisibility", item.name) + }, null, 8, ["rule-provider", "onChangeStatus", "onEdit", "onShowYaml", "onDelete", "onEditVisibility"]), + !_unref$8(isManual)(item.meta.source) ? (_openBlock$9(), _createBlock$9(_component_v_tooltip, { + key: 0, + activator: "parent", + location: "top" + }, { + default: _withCtx$9(() => _cache[1] || (_cache[1] = [ + _createTextVNode$9(" 非手动添加 ") + ])), + _: 1 + })) : _createCommentVNode$6("", true) + ]), + _: 1 + }, 8, ["headers", "items", "search", "page", "items-per-page", "items-per-page-options"]); + }; + } +}); + +const {defineComponent:_defineComponent$8} = await importShared('vue'); + +const {toDisplayString:_toDisplayString$8,createElementVNode:_createElementVNode$6,unref:_unref$7,createTextVNode:_createTextVNode$8,resolveComponent:_resolveComponent$8,withCtx:_withCtx$8,createVNode:_createVNode$8,openBlock:_openBlock$8,createBlock:_createBlock$8,createCommentVNode:_createCommentVNode$5} = await importShared('vue'); + +const _hoisted_1$5 = { class: "d-flex justify-space-between align-center px-4 pt-3" }; +const _hoisted_2$5 = ["title"]; +const _hoisted_3$5 = { class: "text-body-2 font-weight-medium" }; +const _hoisted_4$5 = ["title"]; +const _sfc_main$8 = /* @__PURE__ */ _defineComponent$8({ + __name: "RuleProviderCard", + props: { + ruleProviderData: { + type: Object, + required: true + } + }, + emits: ["editRuleProvider", "deleteRuleProvider", "showYaml", "changeStatus"], + setup(__props, { emit: __emit }) { + const emit = __emit; + return (_ctx, _cache) => { + const _component_v_chip = _resolveComponent$8("v-chip"); + const _component_v_col = _resolveComponent$8("v-col"); + const _component_v_row = _resolveComponent$8("v-row"); + const _component_v_card_text = _resolveComponent$8("v-card-text"); + const _component_v_divider = _resolveComponent$8("v-divider"); + const _component_v_icon = _resolveComponent$8("v-icon"); + const _component_v_spacer = _resolveComponent$8("v-spacer"); + const _component_v_card_actions = _resolveComponent$8("v-card-actions"); + const _component_v_card = _resolveComponent$8("v-card"); + return _openBlock$8(), _createBlock$8(_component_v_card, { + rounded: "lg", + elevation: "2", + class: "rule-provider-card h-100 transition-swing", + variant: "tonal" + }, { + default: _withCtx$8(() => [ + _createElementVNode$6("div", _hoisted_1$5, [ + _createElementVNode$6("span", { + class: "font-weight-bold text-truncate", + title: __props.ruleProviderData.name + }, _toDisplayString$8(__props.ruleProviderData.name), 9, _hoisted_2$5), + _createVNode$8(_component_v_chip, { + size: "small", + color: _unref$7(getSourceColor)(__props.ruleProviderData.meta.source), + variant: "outlined" + }, { + default: _withCtx$8(() => [ + _createTextVNode$8(_toDisplayString$8(__props.ruleProviderData.meta.source), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _createVNode$8(_component_v_card_text, { class: "pt-2 pb-4" }, { + default: _withCtx$8(() => [ + _createVNode$8(_component_v_row, { + "no-gutters": "", + class: "mb-2 align-center" + }, { + default: _withCtx$8(() => [ + _createVNode$8(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$8(() => _cache[4] || (_cache[4] = [ + _createTextVNode$8("类型") + ])), + _: 1 + }), + _createVNode$8(_component_v_col, { cols: "9" }, { + default: _withCtx$8(() => [ + _createElementVNode$6("span", _hoisted_3$5, _toDisplayString$8(__props.ruleProviderData.data.type), 1) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$8(_component_v_row, { + "no-gutters": "", + class: "mb-2 align-center" + }, { + default: _withCtx$8(() => [ + _createVNode$8(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$8(() => _cache[5] || (_cache[5] = [ + _createTextVNode$8("行为") + ])), + _: 1 + }), + _createVNode$8(_component_v_col, { cols: "9" }, { + default: _withCtx$8(() => [ + __props.ruleProviderData.data?.behavior ? (_openBlock$8(), _createBlock$8(_component_v_chip, { + key: 0, + color: _unref$7(getBehaviorColor)(__props.ruleProviderData.data.behavior), + size: "x-small", + label: "", + variant: "tonal", + class: "font-weight-medium" + }, { + default: _withCtx$8(() => [ + _createTextVNode$8(_toDisplayString$8(__props.ruleProviderData.data.behavior), 1) + ]), + _: 1 + }, 8, ["color"])) : _createCommentVNode$5("", true) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$8(_component_v_row, { + "no-gutters": "", + class: "align-center" + }, { + default: _withCtx$8(() => [ + _createVNode$8(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$8(() => _cache[6] || (_cache[6] = [ + _createTextVNode$8("内容") + ])), + _: 1 + }), + _createVNode$8(_component_v_col, { + cols: "9", + class: "text-body-2 text-truncate font-weight-medium" + }, { + default: _withCtx$8(() => [ + _createElementVNode$6("span", { + title: __props.ruleProviderData.data.format + }, _toDisplayString$8(__props.ruleProviderData.data.format), 9, _hoisted_4$5) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$8(_component_v_divider), + _createVNode$8(_component_v_card_actions, null, { + default: _withCtx$8(() => [ + _createVNode$8(_component_v_icon, { + color: __props.ruleProviderData.meta.disabled ? "grey" : "success" + }, { + default: _withCtx$8(() => [ + _createTextVNode$8(_toDisplayString$8(__props.ruleProviderData.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 1 + }, 8, ["color"]), + _createVNode$8(_component_v_spacer), + _createVNode$8(_sfc_main$a, { + "rule-provider": __props.ruleProviderData, + onChangeStatus: _cache[0] || (_cache[0] = (disabled) => emit("changeStatus", __props.ruleProviderData.name, disabled)), + onEdit: _cache[1] || (_cache[1] = ($event) => emit("editRuleProvider", __props.ruleProviderData.name)), + onShowYaml: _cache[2] || (_cache[2] = ($event) => emit("showYaml", __props.ruleProviderData.data)), + onDelete: _cache[3] || (_cache[3] = ($event) => emit("deleteRuleProvider", __props.ruleProviderData.name)) + }, null, 8, ["rule-provider"]) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const RuleProviderCard = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-24eb2895"]]); + +const {defineComponent:_defineComponent$7} = await importShared('vue'); + +const {toDisplayString:_toDisplayString$7,createTextVNode:_createTextVNode$7,resolveComponent:_resolveComponent$7,withCtx:_withCtx$7,createVNode:_createVNode$7,unref:_unref$6,openBlock:_openBlock$7,createBlock:_createBlock$7,createCommentVNode:_createCommentVNode$4,mergeProps:_mergeProps$6,withModifiers:_withModifiers$1} = await importShared('vue'); + +const {ref: ref$5,toRaw: toRaw$3} = await importShared('vue'); +const _sfc_main$7 = /* @__PURE__ */ _defineComponent$7({ + __name: "RuleProviderDialog", + props: { + initialValue: { + type: Object, + default: null + }, + isAdding: { + type: Boolean, + default: true + }, + api: { + type: Object, + required: true + } + }, + emits: ["close", "refresh", "show-snackbar", "show-error"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const ruleProviderTypes = ["http", "file", "inline"]; + const ruleProviderBehaviorTypes = ["domain", "ipcidr", "classical"]; + const ruleProviderFormatTypes = ["yaml", "text", "mrs"]; + const saveRuleProviderLoading = ref$5(false); + const newRuleProvider = ref$5( + props.initialValue ? structuredClone(toRaw$3(props.initialValue)) : { + meta: { ...defaultMetadata }, + data: { ...defaultRuleProvider }, + name: "" + } + ); + const ruleProvidersForm = ref$5(null); + async function saveRuleProvider() { + if (!ruleProvidersForm.value) return; + const { valid } = await ruleProvidersForm.value.validate(); + if (!valid) return; + try { + saveRuleProviderLoading.value = true; + const name = encodeURIComponent( + props.isAdding ? newRuleProvider.value.name : props.initialValue?.name || "" + ); + const requestData = props.isAdding ? newRuleProvider.value.data : newRuleProvider.value; + const method = props.isAdding ? "post" : "patch"; + const result = await props.api[method]( + `/plugin/ClashRuleProvider/rule-providers/${name}`, + requestData + ); + if (!result.success) { + emit("show-error", "保存规则集合失败: " + (result.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "保存规则集合失败", + color: "error" + }); + return; + } + emit("show-snackbar", { + show: true, + message: props.isAdding ? "规则集合添加成功" : "规则集合更新成功", + color: "success" + }); + emit("refresh"); + emit("close"); + } catch (err) { + if (err instanceof Error) { + emit("show-error", "保存规则集合失败: " + (err.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "保存规则集合失败", + color: "error" + }); + } + } finally { + saveRuleProviderLoading.value = false; + } + } + return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent$7("v-card-title"); + const _component_v_text_field = _resolveComponent$7("v-text-field"); + const _component_v_select = _resolveComponent$7("v-select"); + const _component_v_chip = _resolveComponent$7("v-chip"); + const _component_v_combobox = _resolveComponent$7("v-combobox"); + const _component_v_card_text = _resolveComponent$7("v-card-text"); + const _component_v_spacer = _resolveComponent$7("v-spacer"); + const _component_v_btn = _resolveComponent$7("v-btn"); + const _component_v_card_actions = _resolveComponent$7("v-card-actions"); + const _component_v_card = _resolveComponent$7("v-card"); + const _component_v_form = _resolveComponent$7("v-form"); + const _component_v_dialog = _resolveComponent$7("v-dialog"); + return _openBlock$7(), _createBlock$7(_component_v_dialog, { + "max-width": "40rem", + "model-value": true, + persistent: "" + }, { + default: _withCtx$7(() => [ + _createVNode$7(_component_v_form, { + ref_key: "ruleProvidersForm", + ref: ruleProvidersForm, + onSubmit: _withModifiers$1(saveRuleProvider, ["prevent"]) + }, { + default: _withCtx$7(() => [ + _createVNode$7(_component_v_card, null, { + default: _withCtx$7(() => [ + _createVNode$7(_component_v_card_title, null, { + default: _withCtx$7(() => [ + _createTextVNode$7(_toDisplayString$7(__props.isAdding ? "添加规则集合" : "编辑规则集合"), 1) + ]), + _: 1 + }), + _createVNode$7(_component_v_card_text, null, { + default: _withCtx$7(() => [ + _createVNode$7(_component_v_text_field, { + modelValue: newRuleProvider.value.name, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => newRuleProvider.value.name = $event), + label: "name", + required: "", + rules: [(v) => !!v || "名称不能为空"], + class: "mb-4" + }, null, 8, ["modelValue", "rules"]), + _createVNode$7(_component_v_select, { + modelValue: newRuleProvider.value.data.type, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => newRuleProvider.value.data.type = $event), + items: ruleProviderTypes, + label: "type", + required: "", + rules: [(v) => !!v || "类型不能为空"], + class: "mb-4" + }, null, 8, ["modelValue", "rules"]), + newRuleProvider.value.data.type === "http" ? (_openBlock$7(), _createBlock$7(_component_v_text_field, { + key: 0, + modelValue: newRuleProvider.value.data.url, + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => newRuleProvider.value.data.url = $event), + label: "url", + required: "", + rules: [(v) => !!v || "URL 不能为空", (v) => _unref$6(isValidUrl)(v) || "请输入有效的 URL"], + class: "mb-4", + hint: "当类型为 http 时必须配置" + }, null, 8, ["modelValue", "rules"])) : _createCommentVNode$4("", true), + newRuleProvider.value.data.type === "file" ? (_openBlock$7(), _createBlock$7(_component_v_text_field, { + key: 1, + modelValue: newRuleProvider.value.data.path, + "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => newRuleProvider.value.data.path = $event), + label: "path", + required: "", + rules: [(v) => !!v || "当类型为文件时,路径不能为空"], + class: "mb-4", + hint: "文件路径,不填写时会使用 url 的 MD5 作为文件名" + }, null, 8, ["modelValue", "rules"])) : _createCommentVNode$4("", true), + _createVNode$7(_component_v_text_field, { + modelValue: newRuleProvider.value.data.interval, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => newRuleProvider.value.data.interval = $event), + modelModifiers: { number: true }, + label: "interval", + class: "mb-4", + type: "number", + min: "0", + suffix: "s", + hint: "Provider 的更新间隔", + clearable: "", + rules: [(v) => v === null || v === void 0 || v >= 0 || "更新间隔不能为负数"] + }, null, 8, ["modelValue", "rules"]), + _createVNode$7(_component_v_select, { + modelValue: newRuleProvider.value.data.behavior, + "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => newRuleProvider.value.data.behavior = $event), + items: ruleProviderBehaviorTypes, + label: "behavior", + class: "mb-4", + hint: "对应不同格式的 rule-provider 文件" + }, null, 8, ["modelValue"]), + _createVNode$7(_component_v_select, { + modelValue: newRuleProvider.value.data.format, + "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => newRuleProvider.value.data.format = $event), + items: ruleProviderFormatTypes, + label: "format", + class: "mb-4", + hint: "mrs目前 behavior 仅支持 domain/ipcidr" + }, null, 8, ["modelValue"]), + _createVNode$7(_component_v_text_field, { + modelValue: newRuleProvider.value.data["size-limit"], + "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => newRuleProvider.value.data["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 === void 0 || v >= 0 || "大小限制不能为负数"] + }, null, 8, ["modelValue", "rules"]), + newRuleProvider.value.data.type === "inline" ? (_openBlock$7(), _createBlock$7(_component_v_combobox, { + key: 2, + modelValue: newRuleProvider.value.data.payload, + "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => newRuleProvider.value.data.payload = $event), + multiple: "", + chips: "", + "closable-chips": "", + clearable: "", + label: "payload", + required: "", + rules: [(v) => !!v || "当类型为 inline 时,内容不能为空"], + class: "mb-4", + hint: "当类型为 inline 时才有效,按回车确认输入", + row: "" + }, { + chip: _withCtx$7(({ props: props2, item }) => [ + _createVNode$7(_component_v_chip, _mergeProps$6(props2, { + closable: "", + size: "small" + }), { + default: _withCtx$7(() => [ + _createTextVNode$7(_toDisplayString$7(item.value), 1) + ]), + _: 2 + }, 1040) + ]), + _: 1 + }, 8, ["modelValue", "rules"])) : _createCommentVNode$4("", true) + ]), + _: 1 + }), + _createVNode$7(_component_v_card_actions, null, { + default: _withCtx$7(() => [ + _createVNode$7(_component_v_spacer), + _createVNode$7(_component_v_btn, { + color: "secondary", + onClick: _cache[9] || (_cache[9] = ($event) => emit("close")) + }, { + default: _withCtx$7(() => _cache[10] || (_cache[10] = [ + _createTextVNode$7("取消") + ])), + _: 1 + }), + _createVNode$7(_component_v_btn, { + color: "primary", + type: "submit", + loading: saveRuleProviderLoading.value + }, { + default: _withCtx$7(() => _cache[11] || (_cache[11] = [ + _createTextVNode$7("保存 ") + ])), + _: 1 + }, 8, ["loading"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 512) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$6} = await importShared('vue'); + +const {resolveComponent:_resolveComponent$6,createVNode:_createVNode$6,withCtx:_withCtx$6,createElementVNode:_createElementVNode$5,renderList:_renderList$4,Fragment:_Fragment$4,openBlock:_openBlock$6,createElementBlock:_createElementBlock$4,createBlock:_createBlock$6,unref:_unref$5,toDisplayString:_toDisplayString$6,createTextVNode:_createTextVNode$6,mergeProps:_mergeProps$5,createCommentVNode:_createCommentVNode$3} = await importShared('vue'); + +const _hoisted_1$4 = { class: "mb-2 position-relative" }; +const _hoisted_2$4 = { class: "pa-4" }; +const _hoisted_3$4 = { class: "d-none d-sm-flex clash-data-table" }; +const _hoisted_4$4 = { class: "d-sm-none" }; +const _hoisted_5$2 = { + class: "pa-4", + style: { "min-height": "4rem" } +}; +const {ref: ref$4,computed: computed$2,toRaw: toRaw$2} = await importShared('vue'); +const _sfc_main$6 = /* @__PURE__ */ _defineComponent$6({ + __name: "RuleProvidersTab", + props: { + ruleProviders: {}, + api: {} + }, + emits: ["refresh", "show-snackbar", "show-error", "show-yaml", "edit-visibility"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const searchRuleProviders = ref$4(""); + const pageRulProviders = ref$4(1); + const itemsPerPageRuleProviders = ref$4(10); + const ruleProviderDialogVisible = ref$4(false); + const editingRuleProvider = ref$4(null); + const editingRuleProviderName = ref$4(null); + const loading = ref$4(false); + const filteredExtraRuleProviders = computed$2(() => { + if (!searchRuleProviders.value) return props.ruleProviders; + const keyword = searchRuleProviders.value.toLowerCase(); + return props.ruleProviders.filter( + (item) => Object.values(item).some((val) => String(val).toLowerCase().includes(keyword)) + ); + }); + const paginatedExtraRuleProviders = computed$2(() => { + const start = (pageRulProviders.value - 1) * itemsPerPageRuleProviders.value; + const end = start + itemsPerPageRuleProviders.value; + return filteredExtraRuleProviders.value.slice(start, end); + }); + const pageCountExtraRuleProviders = computed$2(() => { + if (itemsPerPageRuleProviders.value === -1) { + return 1; + } + return Math.ceil(props.ruleProviders.length / itemsPerPageRuleProviders.value); + }); + function openAddRuleProviderDialog() { + editingRuleProviderName.value = null; + editingRuleProvider.value = null; + ruleProviderDialogVisible.value = true; + } + function editRuleProvider(name) { + const ruleProvider = props.ruleProviders.find((r) => r.name === name); + if (ruleProvider) { + editingRuleProviderName.value = name; + editingRuleProvider.value = structuredClone(toRaw$2(ruleProvider)); + ruleProviderDialogVisible.value = true; + } + } + async function deleteRuleProvider(name) { + loading.value = true; + try { + const n = encodeURIComponent(name); + await props.api.delete(`/plugin/ClashRuleProvider/rule-providers/${n}`); + emit("refresh", ["rule-providers"]); + } catch (err) { + if (err instanceof Error) emit("show-error", err.message || "删除规则集合失败"); + } finally { + loading.value = false; + } + } + async function handleStatusChange(name, disabled) { + loading.value = true; + try { + const provider = props.ruleProviders.find((p) => p.name === name); + if (!provider) { + emit("show-error", "Rule provider not found"); + return; + } + const n = encodeURIComponent(name); + const newMeta = { ...provider.meta, disabled }; + await props.api.patch(`/plugin/ClashRuleProvider/rule-providers/${n}/meta`, newMeta); + emit("refresh", ["rule-providers"]); + } catch (err) { + if (err instanceof Error) { + emit("show-error", err.message || "更新规则集合状态失败"); + } + } finally { + loading.value = false; + } + } + function editVisibility(name) { + const provider = props.ruleProviders.find((p) => p.name === name); + if (!provider) { + emit("show-error", "Rule provider not found"); + return; + } + const n = encodeURIComponent(name); + emit( + "edit-visibility", + provider.meta, + `/plugin/ClashRuleProvider/rule-providers/${n}/meta`, + "rule-providers" + ); + } + function closeRuleProviderDialog() { + editingRuleProviderName.value = null; + ruleProviderDialogVisible.value = false; + } + return (_ctx, _cache) => { + const _component_v_progress_circular = _resolveComponent$6("v-progress-circular"); + const _component_v_overlay = _resolveComponent$6("v-overlay"); + const _component_v_text_field = _resolveComponent$6("v-text-field"); + const _component_v_col = _resolveComponent$6("v-col"); + const _component_v_btn = _resolveComponent$6("v-btn"); + const _component_v_btn_group = _resolveComponent$6("v-btn-group"); + const _component_v_row = _resolveComponent$6("v-row"); + const _component_v_pagination = _resolveComponent$6("v-pagination"); + const _component_v_list_item_title = _resolveComponent$6("v-list-item-title"); + const _component_v_list_item = _resolveComponent$6("v-list-item"); + const _component_v_list = _resolveComponent$6("v-list"); + const _component_v_menu = _resolveComponent$6("v-menu"); + const _component_v_divider = _resolveComponent$6("v-divider"); + return _openBlock$6(), _createElementBlock$4("div", _hoisted_1$4, [ + _createVNode$6(_component_v_overlay, { + modelValue: loading.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => loading.value = $event), + contained: "", + class: "align-center justify-center" + }, { + default: _withCtx$6(() => [ + _createVNode$6(_component_v_progress_circular, { + indeterminate: "", + color: "primary" + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createElementVNode$5("div", _hoisted_2$4, [ + _createVNode$6(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$6(() => [ + _createVNode$6(_component_v_col, { + cols: "10", + sm: "6", + class: "d-flex justify-start" + }, { + default: _withCtx$6(() => [ + _createVNode$6(_component_v_text_field, { + modelValue: searchRuleProviders.value, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => searchRuleProviders.value = $event), + label: "搜索规则集合", + clearable: "", + density: "compact", + variant: "solo-filled", + "hide-details": "", + class: "search-field", + "prepend-inner-icon": "mdi-magnify", + flat: "", + rounded: "pill", + "single-line": "", + disabled: loading.value + }, null, 8, ["modelValue", "disabled"]) + ]), + _: 1 + }), + _createVNode$6(_component_v_col, { + cols: "2", + sm: "6", + class: "d-flex justify-end" + }, { + default: _withCtx$6(() => [ + _createVNode$6(_component_v_btn_group, { + variant: "outlined", + rounded: "" + }, { + default: _withCtx$6(() => [ + _createVNode$6(_component_v_btn, { + icon: "mdi-plus", + disabled: loading.value, + onClick: openAddRuleProviderDialog + }, null, 8, ["disabled"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createElementVNode$5("div", _hoisted_3$4, [ + _createVNode$6(_sfc_main$9, { + "items-per-page": itemsPerPageRuleProviders.value, + page: pageRulProviders.value, + "rule-providers": filteredExtraRuleProviders.value, + onEditRuleProvider: editRuleProvider, + onDeleteRuleProvider: deleteRuleProvider, + onShowYaml: _cache[2] || (_cache[2] = (o) => emit("show-yaml", o)), + onChangeStatus: handleStatusChange, + onEditVisibility: editVisibility + }, null, 8, ["items-per-page", "page", "rule-providers"]) + ]), + _createElementVNode$5("div", _hoisted_4$4, [ + _createVNode$6(_component_v_row, null, { + default: _withCtx$6(() => [ + (_openBlock$6(true), _createElementBlock$4(_Fragment$4, null, _renderList$4(paginatedExtraRuleProviders.value, (item) => { + return _openBlock$6(), _createBlock$6(_component_v_col, { + key: item.name, + cols: "12" + }, { + default: _withCtx$6(() => [ + _createVNode$6(RuleProviderCard, { + "rule-provider-data": item, + onEditRuleProvider: editRuleProvider, + onDeleteRuleProvider: deleteRuleProvider, + onShowYaml: _cache[3] || (_cache[3] = (o) => emit("show-yaml", o)), + onChangeStatus: handleStatusChange + }, null, 8, ["rule-provider-data"]) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + _: 1 + }) + ]), + _createElementVNode$5("div", _hoisted_5$2, [ + _createVNode$6(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$6(() => [ + _createVNode$6(_component_v_col, { + cols: "2", + md: "1" + }), + _createVNode$6(_component_v_col, { + cols: "8", + md: "10", + class: "d-flex justify-center" + }, { + default: _withCtx$6(() => [ + _createVNode$6(_component_v_pagination, { + modelValue: pageRulProviders.value, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => pageRulProviders.value = $event), + length: pageCountExtraRuleProviders.value, + "total-visible": "5", + rounded: "circle", + class: "d-none d-sm-flex my-0", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]), + _createVNode$6(_component_v_pagination, { + modelValue: pageRulProviders.value, + "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => pageRulProviders.value = $event), + length: pageCountExtraRuleProviders.value, + "total-visible": "0", + rounded: "circle", + class: "d-sm-none my-0", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]) + ]), + _: 1 + }), + _createVNode$6(_component_v_col, { + cols: "2", + md: "1", + class: "d-flex justify-end" + }, { + default: _withCtx$6(() => [ + _createVNode$6(_component_v_menu, null, { + activator: _withCtx$6(({ props: props2 }) => [ + _createVNode$6(_component_v_btn, _mergeProps$5(props2, { + icon: "", + rounded: "circle", + variant: "tonal", + disabled: loading.value + }), { + default: _withCtx$6(() => [ + _createTextVNode$6(_toDisplayString$6(_unref$5(pageTitle)(itemsPerPageRuleProviders.value)), 1) + ]), + _: 2 + }, 1040, ["disabled"]) + ]), + default: _withCtx$6(() => [ + _createVNode$6(_component_v_list, null, { + default: _withCtx$6(() => [ + (_openBlock$6(true), _createElementBlock$4(_Fragment$4, null, _renderList$4(_unref$5(itemsPerPageOptions), (item, index) => { + return _openBlock$6(), _createBlock$6(_component_v_list_item, { + key: index, + value: item.value, + onClick: ($event) => itemsPerPageRuleProviders.value = item.value + }, { + default: _withCtx$6(() => [ + _createVNode$6(_component_v_list_item_title, null, { + default: _withCtx$6(() => [ + _createTextVNode$6(_toDisplayString$6(item.title), 1) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1032, ["value", "onClick"]); + }), 128)) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createVNode$6(_component_v_divider), + ruleProviderDialogVisible.value ? (_openBlock$6(), _createBlock$6(_sfc_main$7, { + key: 0, + modelValue: ruleProviderDialogVisible.value, + "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => ruleProviderDialogVisible.value = $event), + "initial-value": editingRuleProvider.value, + "is-adding": editingRuleProviderName.value === null, + api: _ctx.api, + onClose: closeRuleProviderDialog, + onRefresh: _cache[7] || (_cache[7] = ($event) => emit("refresh", ["rule-providers"])), + onShowSnackbar: _cache[8] || (_cache[8] = (val) => emit("show-snackbar", val)), + onShowError: _cache[9] || (_cache[9] = (msg) => emit("show-error", msg)) + }, null, 8, ["modelValue", "initial-value", "is-adding", "api"])) : _createCommentVNode$3("", true) + ]); + }; + } +}); + +const {defineComponent:_defineComponent$5} = await importShared('vue'); + +const {unref:_unref$4,toDisplayString:_toDisplayString$5,createTextVNode:_createTextVNode$5,resolveComponent:_resolveComponent$5,withCtx:_withCtx$5,createVNode:_createVNode$5,renderList:_renderList$3,Fragment:_Fragment$3,openBlock:_openBlock$5,createElementBlock:_createElementBlock$3,createBlock:_createBlock$5,mergeProps:_mergeProps$4} = await importShared('vue'); + +const {ref: ref$3} = await importShared('vue'); +const _sfc_main$5 = /* @__PURE__ */ _defineComponent$5({ + __name: "HostsTable", + props: { + hosts: { + type: Array, + required: true + }, + search: { + type: String, + default: "" + }, + page: { + type: Number, + required: true + }, + itemsPerPage: { + type: Number, + required: true + } + }, + emits: ["edit", "delete"], + setup(__props) { + const headersHosts = ref$3([ + { title: "域名", key: "domain", sortable: true }, + { title: "IP", key: "value", sortable: false }, + { title: "Cloudflare CDN", key: "using_cloudflare", sortable: false }, + { title: "", key: "actions", sortable: false, width: "1rem" } + ]); + return (_ctx, _cache) => { + const _component_v_chip = _resolveComponent$5("v-chip"); + const _component_v_icon = _resolveComponent$5("v-icon"); + const _component_v_btn = _resolveComponent$5("v-btn"); + const _component_v_list_item_title = _resolveComponent$5("v-list-item-title"); + const _component_v_list_item = _resolveComponent$5("v-list-item"); + const _component_v_list = _resolveComponent$5("v-list"); + const _component_v_menu = _resolveComponent$5("v-menu"); + const _component_v_data_table = _resolveComponent$5("v-data-table"); + return _openBlock$5(), _createBlock$5(_component_v_data_table, { + headers: headersHosts.value, + items: __props.hosts, + search: __props.search, + page: __props.page, + "items-per-page": __props.itemsPerPage, + "items-per-page-options": _unref$4(itemsPerPageOptions), + class: "px-4", + density: "compact", + "hide-default-footer": "", + "fixed-header": "", + "item-key": "domain" + }, { + "item.domain": _withCtx$5(({ item }) => [ + _createVNode$5(_component_v_chip, { + size: "small", + pill: "", + color: "secondary" + }, { + default: _withCtx$5(() => [ + _createTextVNode$5(_toDisplayString$5(item.domain), 1) + ]), + _: 2 + }, 1024) + ]), + "item.value": _withCtx$5(({ item }) => [ + (_openBlock$5(true), _createElementBlock$3(_Fragment$3, null, _renderList$3(item.value, (ip) => { + return _openBlock$5(), _createBlock$5(_component_v_chip, { + key: ip, + size: "small", + class: "ma-1", + variant: "tonal" + }, { + default: _withCtx$5(() => [ + _createTextVNode$5(_toDisplayString$5(ip), 1) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + "item.using_cloudflare": _withCtx$5(({ item }) => [ + _createVNode$5(_component_v_chip, { + color: _unref$4(getBoolColor)(item.using_cloudflare), + size: "small", + variant: "tonal" + }, { + default: _withCtx$5(() => [ + _createTextVNode$5(_toDisplayString$5(item.using_cloudflare ? "是" : "否"), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + "item.actions": _withCtx$5(({ item }) => [ + _createVNode$5(_component_v_menu, { "min-width": "120" }, { + activator: _withCtx$5(({ props }) => [ + _createVNode$5(_component_v_btn, _mergeProps$4({ + color: "secondary", + icon: "", + size: "small", + variant: "text" + }, props), { + default: _withCtx$5(() => [ + _createVNode$5(_component_v_icon, null, { + default: _withCtx$5(() => _cache[0] || (_cache[0] = [ + _createTextVNode$5("mdi-dots-vertical") + ])), + _: 1 + }) + ]), + _: 2 + }, 1040) + ]), + default: _withCtx$5(() => [ + _createVNode$5(_component_v_list, { density: "compact" }, { + default: _withCtx$5(() => [ + _createVNode$5(_component_v_list_item, { + onClick: ($event) => _ctx.$emit("edit", item.domain) + }, { + prepend: _withCtx$5(() => [ + _createVNode$5(_component_v_icon, { + size: "small", + color: "primary" + }, { + default: _withCtx$5(() => _cache[1] || (_cache[1] = [ + _createTextVNode$5("mdi-file-edit-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$5(() => [ + _createVNode$5(_component_v_list_item_title, null, { + default: _withCtx$5(() => _cache[2] || (_cache[2] = [ + _createTextVNode$5("编辑") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick"]), + _createVNode$5(_component_v_list_item, { + onClick: ($event) => _ctx.$emit("delete", item.domain) + }, { + prepend: _withCtx$5(() => [ + _createVNode$5(_component_v_icon, { + size: "small", + color: "error" + }, { + default: _withCtx$5(() => _cache[3] || (_cache[3] = [ + _createTextVNode$5("mdi-trash-can-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$5(() => [ + _createVNode$5(_component_v_list_item_title, null, { + default: _withCtx$5(() => _cache[4] || (_cache[4] = [ + _createTextVNode$5("删除") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick"]) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1024) + ]), + _: 1 + }, 8, ["headers", "items", "search", "page", "items-per-page", "items-per-page-options"]); + }; + } +}); + +const {defineComponent:_defineComponent$4} = await importShared('vue'); + +const {toDisplayString:_toDisplayString$4,createTextVNode:_createTextVNode$4,resolveComponent:_resolveComponent$4,withCtx:_withCtx$4,createVNode:_createVNode$4,unref:_unref$3,mergeProps:_mergeProps$3,openBlock:_openBlock$4,createBlock:_createBlock$4,createCommentVNode:_createCommentVNode$2,createElementVNode:_createElementVNode$4,withModifiers:_withModifiers} = await importShared('vue'); + +const {ref: ref$2,toRaw: toRaw$1} = await importShared('vue'); +const _sfc_main$4 = /* @__PURE__ */ _defineComponent$4({ + __name: "HostDialog", + props: { + initialValue: { + type: Object, + default: () => ({ ...defaultHost }) + }, + isAdding: { + type: Boolean, + default: true + }, + bestCloudflareIPs: { + type: Array, + default: () => [] + }, + api: { + type: Object, + required: true + } + }, + emits: ["close", "refresh", "show-snackbar", "show-error"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const hostForm = ref$2(null); + const saveHostLoading = ref$2(false); + const newHost = ref$2( + props.isAdding ? { ...defaultHost } : structuredClone(toRaw$1(props.initialValue)) + ); + async function saveHost() { + const { valid } = await hostForm.value.validate(); + if (!valid) return; + try { + saveHostLoading.value = true; + newHost.value.domain = newHost.value.domain.trim(); + const targetDomain = props.isAdding ? newHost.value.domain : props.initialValue.domain; + const result = await props.api.post("/plugin/ClashRuleProvider/hosts", { + domain: targetDomain, + host: newHost.value + }); + if (!result.success) { + emit("show-error", "保存 Host 失败: " + (result.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "保存 Host 失败", + color: "error" + }); + return; + } + emit("close"); + emit("refresh"); + emit("show-snackbar", { + show: true, + message: props.isAdding ? "Host 添加成功" : "Host 更新成功", + color: "success" + }); + } catch (err) { + emit("show-error", "保存 Host 失败: " + (err.message || "未知错误")); + emit("show-snackbar", { + show: true, + message: "保存 Host 失败", + color: "error" + }); + } finally { + saveHostLoading.value = false; + } + } + return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent$4("v-card-title"); + const _component_v_text_field = _resolveComponent$4("v-text-field"); + const _component_v_chip = _resolveComponent$4("v-chip"); + const _component_v_combobox = _resolveComponent$4("v-combobox"); + const _component_v_switch = _resolveComponent$4("v-switch"); + const _component_v_col = _resolveComponent$4("v-col"); + const _component_v_row = _resolveComponent$4("v-row"); + const _component_v_card_text = _resolveComponent$4("v-card-text"); + const _component_v_alert = _resolveComponent$4("v-alert"); + const _component_v_spacer = _resolveComponent$4("v-spacer"); + const _component_v_btn = _resolveComponent$4("v-btn"); + const _component_v_card_actions = _resolveComponent$4("v-card-actions"); + const _component_v_card = _resolveComponent$4("v-card"); + const _component_v_form = _resolveComponent$4("v-form"); + const _component_v_dialog = _resolveComponent$4("v-dialog"); + return _openBlock$4(), _createBlock$4(_component_v_dialog, { "max-width": "40rem" }, { + default: _withCtx$4(() => [ + _createVNode$4(_component_v_form, { + ref_key: "hostForm", + ref: hostForm, + onSubmit: _withModifiers(saveHost, ["prevent"]) + }, { + default: _withCtx$4(() => [ + _createVNode$4(_component_v_card, null, { + default: _withCtx$4(() => [ + _createVNode$4(_component_v_card_title, null, { + default: _withCtx$4(() => [ + _createTextVNode$4(_toDisplayString$4(__props.isAdding ? "添加 Host" : "编辑 Host"), 1) + ]), + _: 1 + }), + _createVNode$4(_component_v_card_text, null, { + default: _withCtx$4(() => [ + _createVNode$4(_component_v_text_field, { + modelValue: newHost.value.domain, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => newHost.value.domain = $event), + label: "域名", + required: "", + rules: [(v) => !!v || "域名不能为空"], + class: "mb-4" + }, null, 8, ["modelValue", "rules"]), + !newHost.value.using_cloudflare ? (_openBlock$4(), _createBlock$4(_component_v_combobox, { + key: 0, + modelValue: newHost.value.value, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => newHost.value.value = $event), + multiple: "", + chips: "", + "closable-chips": "", + clearable: "", + label: "IP", + required: "", + rules: [_unref$3(validateIPs)], + class: "mb-4", + hint: "一个或多个 IP 地址" + }, { + chip: _withCtx$4(({ props: props2, item }) => [ + _createVNode$4(_component_v_chip, _mergeProps$3(props2, { + closable: "", + size: "small" + }), { + default: _withCtx$4(() => [ + _createTextVNode$4(_toDisplayString$4(item.value), 1) + ]), + _: 2 + }, 1040) + ]), + _: 1 + }, 8, ["modelValue", "rules"])) : _createCommentVNode$2("", true), + _createVNode$4(_component_v_row, null, { + default: _withCtx$4(() => [ + _createVNode$4(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx$4(() => [ + _createVNode$4(_component_v_switch, { + modelValue: newHost.value.using_cloudflare, + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => newHost.value.using_cloudflare = $event), + label: "使用 Cloudflare CDN", + inset: "", + hint: "设置为 CF 优选 IPs", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + __props.bestCloudflareIPs.length === 0 && newHost.value.using_cloudflare ? (_openBlock$4(), _createBlock$4(_component_v_alert, { + key: 0, + type: "warning", + text: "请在「高级选项」配置 Cloudflare CDN 优选 IPs", + variant: "outlined", + class: "mb-2" + })) : _createCommentVNode$2("", true), + _createVNode$4(_component_v_alert, { + type: "info", + variant: "tonal" + }, { + default: _withCtx$4(() => _cache[4] || (_cache[4] = [ + _createTextVNode$4(" 支持"), + _createElementVNode$4("a", { + href: "https://wiki.metacubex.one/handbook/syntax/#_8", + target: "_blank" + }, "域名通配符", -1) + ])), + _: 1 + }), + _createVNode$4(_component_v_card_actions, null, { + default: _withCtx$4(() => [ + _createVNode$4(_component_v_spacer), + _createVNode$4(_component_v_btn, { + color: "secondary", + onClick: _cache[3] || (_cache[3] = ($event) => emit("close")) + }, { + default: _withCtx$4(() => _cache[5] || (_cache[5] = [ + _createTextVNode$4("取消") + ])), + _: 1 + }), + _createVNode$4(_component_v_btn, { + color: "primary", + type: "submit", + loading: saveHostLoading.value + }, { + default: _withCtx$4(() => _cache[6] || (_cache[6] = [ + _createTextVNode$4("保存 ") + ])), + _: 1 + }, 8, ["loading"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 512) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent$3} = await importShared('vue'); + +const {toDisplayString:_toDisplayString$3,createElementVNode:_createElementVNode$3,createTextVNode:_createTextVNode$3,resolveComponent:_resolveComponent$3,withCtx:_withCtx$3,createVNode:_createVNode$3,unref:_unref$2,renderList:_renderList$2,Fragment:_Fragment$2,openBlock:_openBlock$3,createElementBlock:_createElementBlock$2,createBlock:_createBlock$3,mergeProps:_mergeProps$2} = await importShared('vue'); + +const _hoisted_1$3 = { class: "d-flex justify-space-between align-center px-4 pt-3" }; +const _hoisted_2$3 = { class: "d-flex align-center text-truncate" }; +const _hoisted_3$3 = ["title"]; +const _hoisted_4$3 = { class: "d-flex flex-wrap gap-1" }; +const _sfc_main$3 = /* @__PURE__ */ _defineComponent$3({ + __name: "HostCard", + props: { + hostData: { + type: Object, + required: true + }, + bestCloudflareIPs: { + type: Array, + required: true + } + }, + emits: ["editHost", "deleteHost"], + setup(__props, { emit: __emit }) { + const emit = __emit; + return (_ctx, _cache) => { + const _component_v_col = _resolveComponent$3("v-col"); + const _component_v_chip = _resolveComponent$3("v-chip"); + const _component_v_row = _resolveComponent$3("v-row"); + const _component_v_card_text = _resolveComponent$3("v-card-text"); + const _component_v_divider = _resolveComponent$3("v-divider"); + const _component_v_icon = _resolveComponent$3("v-icon"); + const _component_v_spacer = _resolveComponent$3("v-spacer"); + const _component_v_btn = _resolveComponent$3("v-btn"); + const _component_v_list_item_title = _resolveComponent$3("v-list-item-title"); + const _component_v_list_item = _resolveComponent$3("v-list-item"); + const _component_v_list = _resolveComponent$3("v-list"); + const _component_v_menu = _resolveComponent$3("v-menu"); + const _component_v_card_actions = _resolveComponent$3("v-card-actions"); + const _component_v_card = _resolveComponent$3("v-card"); + return _openBlock$3(), _createBlock$3(_component_v_card, { + rounded: "lg", + elevation: "2", + class: "host-card h-100 transition-swing", + variant: "tonal" + }, { + default: _withCtx$3(() => [ + _createElementVNode$3("div", _hoisted_1$3, [ + _createElementVNode$3("div", _hoisted_2$3, [ + _createElementVNode$3("span", { + class: "font-weight-bold text-truncate", + title: __props.hostData.domain + }, _toDisplayString$3(__props.hostData.domain), 9, _hoisted_3$3) + ]) + ]), + _createVNode$3(_component_v_card_text, { class: "pt-2 pb-4" }, { + default: _withCtx$3(() => [ + _createVNode$3(_component_v_row, { + "no-gutters": "", + class: "mb-2 align-center" + }, { + default: _withCtx$3(() => [ + _createVNode$3(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$3(() => _cache[2] || (_cache[2] = [ + _createTextVNode$3("类型") + ])), + _: 1 + }), + _createVNode$3(_component_v_col, { cols: "9" }, { + default: _withCtx$3(() => [ + _createVNode$3(_component_v_chip, { + color: _unref$2(getBoolColor)(__props.hostData.using_cloudflare), + size: "x-small", + label: "", + variant: "tonal", + class: "font-weight-medium" + }, { + default: _withCtx$3(() => [ + _createTextVNode$3(_toDisplayString$3(__props.hostData.using_cloudflare ? "Cloudflare" : "hosts"), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$3(_component_v_row, { + "no-gutters": "", + class: "align-center" + }, { + default: _withCtx$3(() => [ + _createVNode$3(_component_v_col, { + cols: "3", + class: "text-caption text-medium-emphasis" + }, { + default: _withCtx$3(() => _cache[3] || (_cache[3] = [ + _createTextVNode$3("IP") + ])), + _: 1 + }), + _createVNode$3(_component_v_col, { cols: "9" }, { + default: _withCtx$3(() => [ + _createElementVNode$3("div", _hoisted_4$3, [ + (_openBlock$3(true), _createElementBlock$2(_Fragment$2, null, _renderList$2(__props.hostData.using_cloudflare ? __props.bestCloudflareIPs : __props.hostData.value, (ip) => { + return _openBlock$3(), _createBlock$3(_component_v_chip, { + key: ip, + size: "x-small", + class: "mr-1 mb-1", + variant: "outlined" + }, { + default: _withCtx$3(() => [ + _createTextVNode$3(_toDisplayString$3(ip), 1) + ]), + _: 2 + }, 1024); + }), 128)) + ]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$3(_component_v_divider), + _createVNode$3(_component_v_card_actions, null, { + default: _withCtx$3(() => [ + _createVNode$3(_component_v_icon, { + color: __props.hostData.meta.disabled ? "grey" : "success" + }, { + default: _withCtx$3(() => [ + _createTextVNode$3(_toDisplayString$3(__props.hostData.meta.disabled ? "mdi-close-circle-outline" : "mdi-check-circle-outline"), 1) + ]), + _: 1 + }, 8, ["color"]), + _createVNode$3(_component_v_spacer), + _createVNode$3(_component_v_menu, { "min-width": "140" }, { + activator: _withCtx$3(({ props }) => [ + _createVNode$3(_component_v_btn, _mergeProps$2({ + color: "secondary", + icon: "", + size: "small", + variant: "text" + }, props), { + default: _withCtx$3(() => [ + _createVNode$3(_component_v_icon, null, { + default: _withCtx$3(() => _cache[4] || (_cache[4] = [ + _createTextVNode$3("mdi-dots-vertical") + ])), + _: 1 + }) + ]), + _: 2 + }, 1040) + ]), + default: _withCtx$3(() => [ + _createVNode$3(_component_v_list, { density: "compact" }, { + default: _withCtx$3(() => [ + _createVNode$3(_component_v_list_item, { + onClick: _cache[0] || (_cache[0] = ($event) => emit("editHost", __props.hostData.domain)) + }, { + prepend: _withCtx$3(() => [ + _createVNode$3(_component_v_icon, { + size: "small", + color: "primary" + }, { + default: _withCtx$3(() => _cache[5] || (_cache[5] = [ + _createTextVNode$3("mdi-file-edit-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$3(() => [ + _createVNode$3(_component_v_list_item_title, null, { + default: _withCtx$3(() => _cache[6] || (_cache[6] = [ + _createTextVNode$3("编辑") + ])), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$3(_component_v_list_item, { + onClick: _cache[1] || (_cache[1] = ($event) => emit("deleteHost", __props.hostData.domain)) + }, { + prepend: _withCtx$3(() => [ + _createVNode$3(_component_v_icon, { + size: "small", + color: "error" + }, { + default: _withCtx$3(() => _cache[7] || (_cache[7] = [ + _createTextVNode$3("mdi-trash-can-outline") + ])), + _: 1 + }) + ]), + default: _withCtx$3(() => [ + _createVNode$3(_component_v_list_item_title, null, { + default: _withCtx$3(() => _cache[8] || (_cache[8] = [ + _createTextVNode$3("删除") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const HostCard = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-a5d6e0e6"]]); + +const {defineComponent:_defineComponent$2} = await importShared('vue'); + +const {resolveComponent:_resolveComponent$2,createVNode:_createVNode$2,withCtx:_withCtx$2,createElementVNode:_createElementVNode$2,renderList:_renderList$1,Fragment:_Fragment$1,openBlock:_openBlock$2,createElementBlock:_createElementBlock$1,createBlock:_createBlock$2,unref:_unref$1,toDisplayString:_toDisplayString$2,createTextVNode:_createTextVNode$2,mergeProps:_mergeProps$1,createCommentVNode:_createCommentVNode$1} = await importShared('vue'); + +const _hoisted_1$2 = { class: "mb-2 position-relative" }; +const _hoisted_2$2 = { class: "pa-4" }; +const _hoisted_3$2 = { class: "d-none d-sm-flex clash-data-table" }; +const _hoisted_4$2 = { class: "d-sm-none" }; +const _hoisted_5$1 = { + class: "pa-4", + style: { "min-height": "4rem" } +}; +const {ref: ref$1,computed: computed$1,toRaw} = await importShared('vue'); +const _sfc_main$2 = /* @__PURE__ */ _defineComponent$2({ + __name: "HostsTab", + props: { + hosts: {}, + bestCloudflareIPs: {}, + api: {} + }, + emits: ["refresh", "show-snackbar", "show-error"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const searchHosts = ref$1(""); + const pageHosts = ref$1(1); + const itemsPerPageHosts = ref$1(10); + const hostDialogVisible = ref$1(false); + const currentHost = ref$1({ ...defaultHost }); + const isAdding = ref$1(true); + const loading = ref$1(false); + const filteredHosts = computed$1(() => { + if (!searchHosts.value) return props.hosts; + const keyword = searchHosts.value.toLowerCase(); + return props.hosts.filter( + (item) => Object.values(item).some((val) => String(val).toLowerCase().includes(keyword)) + ); + }); + const paginatedHosts = computed$1(() => { + const start = (pageHosts.value - 1) * itemsPerPageHosts.value; + const end = start + itemsPerPageHosts.value; + return filteredHosts.value.slice(start, end); + }); + const pageCountHosts = computed$1(() => { + if (itemsPerPageHosts.value === -1) { + return 1; + } + return Math.ceil(props.hosts.length / itemsPerPageHosts.value); + }); + function openAddHostDialog() { + currentHost.value = { ...defaultHost }; + isAdding.value = true; + hostDialogVisible.value = true; + } + function editHost(domain) { + const hostItem = props.hosts.find((r) => r.domain === domain); + if (hostItem) { + currentHost.value = structuredClone(toRaw(hostItem)); + isAdding.value = false; + hostDialogVisible.value = true; + } + } + async function deleteHost(name) { + loading.value = true; + try { + await props.api.delete(`/plugin/ClashRuleProvider/hosts/${encodeURIComponent(name)}`); + emit("refresh"); + } catch (err) { + emit("show-error", err.message || "删除 host 失败"); + } finally { + loading.value = false; + } + } + return (_ctx, _cache) => { + const _component_v_progress_circular = _resolveComponent$2("v-progress-circular"); + const _component_v_overlay = _resolveComponent$2("v-overlay"); + const _component_v_text_field = _resolveComponent$2("v-text-field"); + const _component_v_col = _resolveComponent$2("v-col"); + const _component_v_btn = _resolveComponent$2("v-btn"); + const _component_v_btn_group = _resolveComponent$2("v-btn-group"); + const _component_v_row = _resolveComponent$2("v-row"); + const _component_v_pagination = _resolveComponent$2("v-pagination"); + const _component_v_list_item_title = _resolveComponent$2("v-list-item-title"); + const _component_v_list_item = _resolveComponent$2("v-list-item"); + const _component_v_list = _resolveComponent$2("v-list"); + const _component_v_menu = _resolveComponent$2("v-menu"); + const _component_v_divider = _resolveComponent$2("v-divider"); + return _openBlock$2(), _createElementBlock$1(_Fragment$1, null, [ + _createElementVNode$2("div", _hoisted_1$2, [ + _createVNode$2(_component_v_overlay, { + modelValue: loading.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => loading.value = $event), + contained: "", + class: "align-center justify-center" + }, { + default: _withCtx$2(() => [ + _createVNode$2(_component_v_progress_circular, { + indeterminate: "", + color: "primary" + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createElementVNode$2("div", _hoisted_2$2, [ + _createVNode$2(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$2(() => [ + _createVNode$2(_component_v_col, { + cols: "10", + sm: "6", + class: "d-flex justify-start" + }, { + default: _withCtx$2(() => [ + _createVNode$2(_component_v_text_field, { + modelValue: searchHosts.value, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => searchHosts.value = $event), + label: "搜索Hosts", + clearable: "", + density: "compact", + variant: "solo-filled", + "hide-details": "", + class: "search-field", + "prepend-inner-icon": "mdi-magnify", + flat: "", + rounded: "pill", + "single-line": "", + disabled: loading.value + }, null, 8, ["modelValue", "disabled"]) + ]), + _: 1 + }), + _createVNode$2(_component_v_col, { + cols: "2", + sm: "6", + class: "d-flex justify-end" + }, { + default: _withCtx$2(() => [ + _createVNode$2(_component_v_btn_group, { + variant: "outlined", + rounded: "" + }, { + default: _withCtx$2(() => [ + _createVNode$2(_component_v_btn, { + icon: "mdi-plus", + disabled: loading.value, + onClick: openAddHostDialog + }, null, 8, ["disabled"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createElementVNode$2("div", _hoisted_3$2, [ + _createVNode$2(_sfc_main$5, { + hosts: _ctx.hosts, + search: searchHosts.value, + page: pageHosts.value, + "items-per-page": itemsPerPageHosts.value, + onEdit: editHost, + onDelete: deleteHost + }, null, 8, ["hosts", "search", "page", "items-per-page"]) + ]), + _createElementVNode$2("div", _hoisted_4$2, [ + _createVNode$2(_component_v_row, null, { + default: _withCtx$2(() => [ + (_openBlock$2(true), _createElementBlock$1(_Fragment$1, null, _renderList$1(paginatedHosts.value, (item) => { + return _openBlock$2(), _createBlock$2(_component_v_col, { + key: item.domain, + cols: "12" + }, { + default: _withCtx$2(() => [ + _createVNode$2(HostCard, { + "host-data": item, + "best-cloudflare-i-ps": _ctx.bestCloudflareIPs, + onEditHost: editHost, + onDeleteHost: deleteHost + }, null, 8, ["host-data", "best-cloudflare-i-ps"]) + ]), + _: 2 + }, 1024); + }), 128)) + ]), + _: 1 + }) + ]), + _createElementVNode$2("div", _hoisted_5$1, [ + _createVNode$2(_component_v_row, { + align: "center", + "no-gutters": "" + }, { + default: _withCtx$2(() => [ + _createVNode$2(_component_v_col, { + cols: "2", + md: "1" + }), + _createVNode$2(_component_v_col, { + cols: "8", + md: "10", + class: "d-flex justify-center" + }, { + default: _withCtx$2(() => [ + _createVNode$2(_component_v_pagination, { + modelValue: pageHosts.value, + "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => pageHosts.value = $event), + length: pageCountHosts.value, + "total-visible": "5", + rounded: "circle", + class: "d-none d-sm-flex my-0", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]), + _createVNode$2(_component_v_pagination, { + modelValue: pageHosts.value, + "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => pageHosts.value = $event), + length: pageCountHosts.value, + "total-visible": "0", + rounded: "circle", + class: "d-sm-none my-0", + disabled: loading.value + }, null, 8, ["modelValue", "length", "disabled"]) + ]), + _: 1 + }), + _createVNode$2(_component_v_col, { + cols: "2", + md: "1", + class: "d-flex justify-end" + }, { + default: _withCtx$2(() => [ + _createVNode$2(_component_v_menu, null, { + activator: _withCtx$2(({ props: props2 }) => [ + _createVNode$2(_component_v_btn, _mergeProps$1(props2, { + icon: "", + rounded: "circle", + variant: "tonal", + disabled: loading.value + }), { + default: _withCtx$2(() => [ + _createTextVNode$2(_toDisplayString$2(_unref$1(pageTitle)(itemsPerPageHosts.value)), 1) + ]), + _: 2 + }, 1040, ["disabled"]) + ]), + default: _withCtx$2(() => [ + _createVNode$2(_component_v_list, null, { + default: _withCtx$2(() => [ + (_openBlock$2(true), _createElementBlock$1(_Fragment$1, null, _renderList$1(_unref$1(itemsPerPageOptions), (item, index) => { + return _openBlock$2(), _createBlock$2(_component_v_list_item, { + key: index, + value: item.value, + onClick: ($event) => itemsPerPageHosts.value = item.value + }, { + default: _withCtx$2(() => [ + _createVNode$2(_component_v_list_item_title, null, { + default: _withCtx$2(() => [ + _createTextVNode$2(_toDisplayString$2(item.title), 1) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1032, ["value", "onClick"]); + }), 128)) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _createVNode$2(_component_v_divider) + ]), + hostDialogVisible.value ? (_openBlock$2(), _createBlock$2(_sfc_main$4, { + key: 0, + modelValue: hostDialogVisible.value, + "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => hostDialogVisible.value = $event), + "initial-value": currentHost.value, + "is-adding": isAdding.value, + "best-cloudflare-i-ps": _ctx.bestCloudflareIPs, + api: _ctx.api, + onRefresh: _cache[5] || (_cache[5] = ($event) => emit("refresh")), + onShowSnackbar: _cache[6] || (_cache[6] = (v) => emit("show-snackbar", v)), + onShowError: _cache[7] || (_cache[7] = (v) => emit("show-error", v)), + onClose: _cache[8] || (_cache[8] = ($event) => hostDialogVisible.value = false) + }, null, 8, ["modelValue", "initial-value", "is-adding", "best-cloudflare-i-ps", "api"])) : _createCommentVNode$1("", true) + ], 64); + }; + } +}); + +const {defineComponent:_defineComponent$1} = await importShared('vue'); + +const {createTextVNode:_createTextVNode$1,resolveComponent:_resolveComponent$1,withCtx:_withCtx$1,createVNode:_createVNode$1,toDisplayString:_toDisplayString$1,createElementVNode:_createElementVNode$1,openBlock:_openBlock$1,createBlock:_createBlock$1} = await importShared('vue'); + +const _hoisted_1$1 = { class: "text-h6 mt-2 font-weight-bold" }; +const _hoisted_2$1 = { class: "text-h6 mt-2 font-weight-bold" }; +const _hoisted_3$1 = { class: "text-h6 mt-2 font-weight-bold" }; +const _hoisted_4$1 = { class: "text-h6 mt-2 font-weight-bold" }; +const _hoisted_5 = { class: "text-h6 mt-2 font-weight-bold" }; +const _hoisted_6 = { class: "text-h6 mt-2 font-weight-bold" }; +const _hoisted_7 = { class: "text-h6 mt-2 font-weight-bold" }; +const _hoisted_8 = { class: "text-h6 mt-2 font-weight-bold" }; +const _sfc_main$1 = /* @__PURE__ */ _defineComponent$1({ + __name: "StatisticsPanel", + props: { + rulesetRulesCount: {}, + topRulesCount: {}, + proxyGroupsCount: {}, + extraProxiesCount: {}, + extraRuleProvidersCount: {}, + hostsCount: {}, + geositeCount: {}, + lastUpdated: {} + }, + setup(__props) { + return (_ctx, _cache) => { + const _component_v_icon = _resolveComponent$1("v-icon"); + const _component_v_card = _resolveComponent$1("v-card"); + const _component_v_col = _resolveComponent$1("v-col"); + const _component_v_row = _resolveComponent$1("v-row"); + return _openBlock$1(), _createBlock$1(_component_v_row, { dense: "" }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card, { + class: "pa-4 d-flex flex-column align-center", + rounded: "xl" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_icon, { + size: "40", + color: "primary" + }, { + default: _withCtx$1(() => _cache[0] || (_cache[0] = [ + _createTextVNode$1("mdi-format-list-bulleted") + ])), + _: 1 + }), + _createElementVNode$1("div", _hoisted_1$1, _toDisplayString$1(_ctx.rulesetRulesCount), 1), + _cache[1] || (_cache[1] = _createElementVNode$1("div", { class: "text-subtitle-2 grey--text" }, "规则集规则", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card, { + class: "pa-4 d-flex flex-column align-center", + rounded: "xl" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_icon, { + size: "40", + color: "info" + }, { + default: _withCtx$1(() => _cache[2] || (_cache[2] = [ + _createTextVNode$1("mdi-pin") + ])), + _: 1 + }), + _createElementVNode$1("div", _hoisted_2$1, _toDisplayString$1(_ctx.topRulesCount), 1), + _cache[3] || (_cache[3] = _createElementVNode$1("div", { class: "text-subtitle-2 grey--text" }, "置顶规则", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card, { + class: "pa-4 d-flex flex-column align-center", + rounded: "xl" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_icon, { + size: "40", + color: "success" + }, { + default: _withCtx$1(() => _cache[4] || (_cache[4] = [ + _createTextVNode$1("mdi-source-branch") + ])), + _: 1 + }), + _createElementVNode$1("div", _hoisted_3$1, _toDisplayString$1(_ctx.proxyGroupsCount), 1), + _cache[5] || (_cache[5] = _createElementVNode$1("div", { class: "text-subtitle-2 grey--text" }, "代理组", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card, { + class: "pa-4 d-flex flex-column align-center", + rounded: "xl" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_icon, { + size: "40", + color: "error" + }, { + default: _withCtx$1(() => _cache[6] || (_cache[6] = [ + _createTextVNode$1("mdi-rocket-launch") + ])), + _: 1 + }), + _createElementVNode$1("div", _hoisted_4$1, _toDisplayString$1(_ctx.extraProxiesCount), 1), + _cache[7] || (_cache[7] = _createElementVNode$1("div", { class: "text-subtitle-2 grey--text" }, "出站代理", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card, { + class: "pa-4 d-flex flex-column align-center", + rounded: "xl" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_icon, { + size: "40", + color: "warning" + }, { + default: _withCtx$1(() => _cache[8] || (_cache[8] = [ + _createTextVNode$1("mdi-folder-multiple") + ])), + _: 1 + }), + _createElementVNode$1("div", _hoisted_5, _toDisplayString$1(_ctx.extraRuleProvidersCount), 1), + _cache[9] || (_cache[9] = _createElementVNode$1("div", { class: "text-subtitle-2 grey--text" }, "规则集合", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card, { + class: "pa-4 d-flex flex-column align-center", + rounded: "xl" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_icon, { + size: "40", + color: "secondary" + }, { + default: _withCtx$1(() => _cache[10] || (_cache[10] = [ + _createTextVNode$1("mdi-lan") + ])), + _: 1 + }), + _createElementVNode$1("div", _hoisted_6, _toDisplayString$1(_ctx.hostsCount), 1), + _cache[11] || (_cache[11] = _createElementVNode$1("div", { class: "text-subtitle-2 grey--text" }, "Hosts", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card, { + class: "pa-4 d-flex flex-column align-center", + rounded: "xl" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_icon, { + size: "40", + color: "info" + }, { + default: _withCtx$1(() => _cache[12] || (_cache[12] = [ + _createTextVNode$1("mdi-earth") + ])), + _: 1 + }), + _createElementVNode$1("div", _hoisted_7, _toDisplayString$1(_ctx.geositeCount), 1), + _cache[13] || (_cache[13] = _createElementVNode$1("div", { class: "text-subtitle-2 grey--text" }, "Geosite", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode$1(_component_v_col, { + cols: "6", + md: "3" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_card, { + class: "pa-4 d-flex flex-column align-center", + rounded: "xl" + }, { + default: _withCtx$1(() => [ + _createVNode$1(_component_v_icon, { + size: "40", + color: "success" + }, { + default: _withCtx$1(() => _cache[14] || (_cache[14] = [ + _createTextVNode$1("mdi-clock-time-four-outline") + ])), + _: 1 + }), + _createElementVNode$1("div", _hoisted_8, _toDisplayString$1(_ctx.lastUpdated), 1), + _cache[15] || (_cache[15] = _createElementVNode$1("div", { class: "text-subtitle-2 grey--text" }, "最后更新", -1)) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }); + }; + } +}); + +const {defineComponent:_defineComponent} = await importShared('vue'); + +const {toDisplayString:_toDisplayString,createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,openBlock:_openBlock,createBlock:_createBlock,createCommentVNode:_createCommentVNode,unref:_unref,createElementVNode:_createElementVNode,createVNode:_createVNode,createElementBlock:_createElementBlock,mergeProps:_mergeProps,renderList:_renderList,Fragment:_Fragment} = await importShared('vue'); + +const _hoisted_1 = { class: "plugin-page" }; +const _hoisted_2 = ["src"]; +const _hoisted_3 = { key: 1 }; +const _hoisted_4 = { key: 0 }; +const {ref,onMounted,computed} = await importShared('vue'); +const _sfc_main = /* @__PURE__ */ _defineComponent({ + __name: "Page", + props: { + model: { + type: Object, + default: () => { + } + }, + api: { + type: Object, + default: () => { + } + } + }, + emits: ["action", "switch", "close"], + setup(__props, { emit: __emit }) { + const props = __props; + const emit = __emit; + const activeTab = ref(0); + const snackbar = ref({ + show: false, + message: "", + color: "success" + }); + const customOutbounds = ref([]); + const subUrl = ref(""); + const proxyGroups = ref([]); + const proxyProviders = ref([]); + const proxies = ref([]); + const presetIdentifiers = ref([]); + const expand = ref(false); + const loading = ref(true); + const error = ref(false); + const errorMsg = ref(""); + const rules = ref([]); + const rulesetRules = ref([]); + const ruleProviders = ref([]); + const hosts = ref([]); + const status = ref("running"); + const rulesetPrefix = ref("📂<="); + const geoRules = ref({ + geoip: [], + geosite: [] + }); + const lastUpdated = ref(""); + const showYamlDialog = ref(false); + const displayedYaml = ref(""); + const visibilityDialogVisible = ref(false); + const currentVisibilityMeta = ref({ ...defaultMetadata }); + const currentVisibilityEndpoint = ref(""); + const currentVisibilityRegion = ref(""); + const sortedRules = computed( + () => [...rules.value].sort((a, b) => a.priority - b.priority) + ); + const sortedRulesetRules = computed( + () => [...rulesetRules.value].sort((a, b) => a.priority - b.priority) + ); + const ruleProviderNames = computed(() => { + return ruleProviders.value.map((provider) => provider.name); + }); + const subscriptionsInfo = ref({}); + const bestCloudflareIPs = ref([]); + function copyToClipboard(text) { + navigator.clipboard.writeText(text).then(() => { + snackbar.value = { + show: true, + message: "已复制到剪贴板", + color: "success" + }; + }).catch(() => { + snackbar.value = { + show: true, + message: "复制失败", + color: "error" + }; + }); + } + function copyPluginLink() { + const url = `${window.location.origin}/#/plugins?tab=installed&id=ClashRuleProvider`; + copyToClipboard(url); + } + function generateIdentifierUrl(identifier) { + if (!subUrl.value) return ""; + try { + const url = new URL(subUrl.value, window.location.origin); + url.searchParams.set("identifier", identifier); + return url.toString(); + } catch (e) { + console.error("Failed to parse URL", e); + return subUrl.value; + } + } + function showYaml(obj) { + displayedYaml.value = jsYaml.dump(obj); + showYamlDialog.value = true; + } + function showError(Msg) { + error.value = true; + errorMsg.value = Msg; + } + function handleEditVisibility(meta, endpoint, region) { + currentVisibilityMeta.value = meta; + currentVisibilityEndpoint.value = endpoint; + currentVisibilityRegion.value = region; + visibilityDialogVisible.value = true; + } + async function refreshStatus() { + const state = await props.api.get("/plugin/ClashRuleProvider/status"); + status.value = state?.data?.state ? "running" : "disabled"; + subUrl.value = state?.data?.sub_url || ""; + if (state?.data?.subscription_info) { + subscriptionsInfo.value = state.data.subscription_info; + } + bestCloudflareIPs.value = state?.data?.best_cf_ip || []; + rulesetPrefix.value = state?.data?.ruleset_prefix || "📂<="; + geoRules.value = state?.data?.geoRules ?? geoRules.value; + } + async function refreshTopRules() { + const response = await props.api.get("/plugin/ClashRuleProvider/rules/top"); + rules.value = response?.data || []; + } + async function refreshRulesetRules() { + const response = await props.api.get("/plugin/ClashRuleProvider/rules/ruleset"); + rulesetRules.value = response?.data || []; + } + async function refreshOutbounds() { + const outboundsResponse = await props.api.get("/plugin/ClashRuleProvider/clash-outbound"); + customOutbounds.value = outboundsResponse?.data || []; + } + async function refreshExtraRuleProviders() { + const providersResponse = await props.api.get("/plugin/ClashRuleProvider/rule-providers"); + ruleProviders.value = providersResponse?.data || []; + } + async function refreshProxyGroups() { + const proxyGroupsResponse = await props.api.get("/plugin/ClashRuleProvider/proxy-groups"); + proxyGroups.value = proxyGroupsResponse?.data || []; + } + async function refreshExtraProxies() { + const extraProxiesResponse = await props.api.get("/plugin/ClashRuleProvider/proxies"); + proxies.value = extraProxiesResponse?.data || []; + } + async function refreshHosts() { + const hostsResponse = await props.api.get("/plugin/ClashRuleProvider/hosts"); + hosts.value = hostsResponse?.data || []; + } + async function refreshProxyProviders() { + const proxyProvidersResponse = await props.api.get("/plugin/ClashRuleProvider/proxy-providers"); + proxyProviders.value = proxyProvidersResponse?.data || []; + } + async function refreshDataOf(region) { + switch (region) { + case "status": + return refreshStatus(); + case "top": + return refreshTopRules(); + case "ruleset": + return refreshRulesetRules(); + case "clash-outbounds": + return refreshOutbounds(); + case "rule-providers": + return refreshExtraRuleProviders(); + case "proxy-groups": + return refreshProxyGroups(); + case "proxies": + return refreshExtraProxies(); + case "hosts": + return refreshHosts(); + case "proxy-providers": + return refreshProxyProviders(); + default: + throw new Error("Unknown region: " + region); + } + } + async function refreshAllRegions(regions) { + try { + await Promise.all(regions.map(refreshDataOf)); + } catch (err) { + console.error("获取数据失败:", err); + if (err instanceof Error) { + showError(err.message || "获取数据失败"); + } + status.value = "error"; + } finally { + lastUpdated.value = (/* @__PURE__ */ new Date()).toLocaleString(); + } + } + async function refreshData() { + loading.value = true; + error.value = false; + errorMsg.value = ""; + try { + const [ + state, + response, + response_ruleset, + outboundsResponse, + providersResponse, + proxyGroupsResponse, + extraProxiesResponse, + hostsResponse, + proxyProvidersResponse + ] = await Promise.all([ + props.api.get("/plugin/ClashRuleProvider/status"), + props.api.get("/plugin/ClashRuleProvider/rules/top"), + props.api.get("/plugin/ClashRuleProvider/rules/ruleset"), + props.api.get("/plugin/ClashRuleProvider/clash-outbound"), + props.api.get("/plugin/ClashRuleProvider/rule-providers"), + props.api.get("/plugin/ClashRuleProvider/proxy-groups"), + props.api.get("/plugin/ClashRuleProvider/proxies"), + props.api.get("/plugin/ClashRuleProvider/hosts"), + props.api.get("/plugin/ClashRuleProvider/proxy-providers") + ]); + status.value = state?.data?.state ? "running" : "disabled"; + subUrl.value = state?.data?.sub_url || ""; + if (state?.data?.subscription_info) { + subscriptionsInfo.value = state.data.subscription_info; + } + bestCloudflareIPs.value = state?.data?.best_cf_ip || []; + rulesetPrefix.value = state?.data?.ruleset_prefix || "📂<="; + geoRules.value = state?.data?.geoRules ?? geoRules.value; + presetIdentifiers.value = state?.data?.preset_identifiers || []; + rules.value = response?.data || []; + rulesetRules.value = response_ruleset?.data || []; + customOutbounds.value = outboundsResponse?.data || []; + ruleProviders.value = providersResponse?.data || []; + proxyGroups.value = proxyGroupsResponse?.data || []; + proxies.value = extraProxiesResponse?.data || []; + hosts.value = hostsResponse?.data || []; + proxyProviders.value = proxyProvidersResponse?.data || []; + lastUpdated.value = (/* @__PURE__ */ new Date()).toLocaleString(); + } catch (err) { + console.error("获取数据失败:", err); + if (err instanceof Error) { + showError(err.message || "获取数据失败"); + } + status.value = "error"; + } finally { + loading.value = false; + } + } + function notifySwitch() { + emit("switch"); + } + function notifyClose() { + emit("close"); + } + onMounted(() => { + refreshData(); + }); + return (_ctx, _cache) => { + const _component_v_alert = _resolveComponent("v-alert"); + const _component_v_icon = _resolveComponent("v-icon"); + const _component_v_chip = _resolveComponent("v-chip"); + const _component_v_card_title = _resolveComponent("v-card-title"); + const _component_v_btn = _resolveComponent("v-btn"); + const _component_v_card_item = _resolveComponent("v-card-item"); + const _component_v_skeleton_loader = _resolveComponent("v-skeleton-loader"); + const _component_v_tab = _resolveComponent("v-tab"); + const _component_v_tabs = _resolveComponent("v-tabs"); + const _component_v_window_item = _resolveComponent("v-window-item"); + const _component_v_window = _resolveComponent("v-window"); + const _component_v_card_text = _resolveComponent("v-card-text"); + const _component_v_expand_transition = _resolveComponent("v-expand-transition"); + const _component_v_list_item_title = _resolveComponent("v-list-item-title"); + const _component_v_list_item = _resolveComponent("v-list-item"); + const _component_v_list = _resolveComponent("v-list"); + const _component_v_menu = _resolveComponent("v-menu"); + 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_card = _resolveComponent("v-card"); + return _openBlock(), _createElementBlock("div", _hoisted_1, [ + _createVNode(_component_v_card, null, { + default: _withCtx(() => [ + error.value ? (_openBlock(), _createBlock(_component_v_alert, { + key: 0, + modelValue: error.value, + "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => error.value = $event), + type: "error", + class: "mb-4", + closable: "" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(errorMsg.value), 1) + ]), + _: 1 + }, 8, ["modelValue"])) : _createCommentVNode("", true), + _createVNode(_component_v_card_item, null, { + append: _withCtx(() => [ + _createVNode(_component_v_btn, { + icon: "", + color: "primary", + variant: "text", + onClick: notifyClose + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[18] || (_cache[18] = [ + _createTextVNode("mdi-close") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + default: _withCtx(() => [ + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { + class: "mr-2", + size: "24" + }, { + default: _withCtx(() => [ + _createElementVNode("img", { + src: `/api/v1/plugin/file/clashruleprovider/dist${_unref(MetaLogo)}`, + alt: "icon", + style: { "width": "100%", "height": "100%" } + }, null, 8, _hoisted_2) + ]), + _: 1 + }), + _cache[17] || (_cache[17] = _createTextVNode(" Clash Rule Provider ")), + _createVNode(_component_v_chip, { + size: "small", + color: status.value === "running" ? "success" : "warning", + onClick: copyPluginLink + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(status.value), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_card_text, null, { + default: _withCtx(() => [ + loading.value ? (_openBlock(), _createBlock(_component_v_skeleton_loader, { + key: 0, + type: "card" + })) : (_openBlock(), _createElementBlock("div", _hoisted_3, [ + _createVNode(_component_v_tabs, { + modelValue: activeTab.value, + "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => activeTab.value = $event), + "background-color": "primary", + dark: "" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_tab, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[19] || (_cache[19] = [ + _createTextVNode("mdi-format-list-bulleted") + ])), + _: 1 + }), + _cache[20] || (_cache[20] = _createTextVNode(" 规则集规则 ")) + ]), + _: 1 + }), + _createVNode(_component_v_tab, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[21] || (_cache[21] = [ + _createTextVNode("mdi-pin") + ])), + _: 1 + }), + _cache[22] || (_cache[22] = _createTextVNode(" 置顶规则 ")) + ]), + _: 1 + }), + _createVNode(_component_v_tab, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[23] || (_cache[23] = [ + _createTextVNode("mdi-source-branch") + ])), + _: 1 + }), + _cache[24] || (_cache[24] = _createTextVNode(" 代理组 ")) + ]), + _: 1 + }), + _createVNode(_component_v_tab, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[25] || (_cache[25] = [ + _createTextVNode("mdi-rocket-launch") + ])), + _: 1 + }), + _cache[26] || (_cache[26] = _createTextVNode(" 出站代理 ")) + ]), + _: 1 + }), + _createVNode(_component_v_tab, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[27] || (_cache[27] = [ + _createTextVNode("mdi-folder-multiple") + ])), + _: 1 + }), + _cache[28] || (_cache[28] = _createTextVNode(" 规则集合 ")) + ]), + _: 1 + }), + _createVNode(_component_v_tab, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[29] || (_cache[29] = [ + _createTextVNode("mdi-lan") + ])), + _: 1 + }), + _cache[30] || (_cache[30] = _createTextVNode(" Hosts ")) + ]), + _: 1 + }), + _createVNode(_component_v_tab, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { start: "" }, { + default: _withCtx(() => _cache[31] || (_cache[31] = [ + _createTextVNode("mdi-cloud-sync") + ])), + _: 1 + }), + _cache[32] || (_cache[32] = _createTextVNode(" 订阅状态 ")) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createVNode(_component_v_window, { + modelValue: activeTab.value, + "onUpdate:modelValue": _cache[10] || (_cache[10] = ($event) => activeTab.value = $event) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_window_item, null, { + default: _withCtx(() => [ + _createVNode(_sfc_main$q, { + rules: sortedRulesetRules.value, + "ruleset-prefix": rulesetPrefix.value, + api: __props.api, + "rule-provider-names": ruleProviderNames.value, + "geo-rules": geoRules.value, + "custom-outbounds": customOutbounds.value, + onRefresh: refreshAllRegions, + onShowSnackbar: _cache[2] || (_cache[2] = (val) => snackbar.value = val), + onShowError: showError + }, null, 8, ["rules", "ruleset-prefix", "api", "rule-provider-names", "geo-rules", "custom-outbounds"]) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, null, { + default: _withCtx(() => [ + _createVNode(_sfc_main$n, { + rules: sortedRules.value, + api: __props.api, + "rule-provider-names": ruleProviderNames.value, + "geo-rules": geoRules.value, + "custom-outbounds": customOutbounds.value, + onRefresh: refreshAllRegions, + onShowSnackbar: _cache[3] || (_cache[3] = (val) => snackbar.value = val), + onShowError: showError, + onEditVisibility: handleEditVisibility + }, null, 8, ["rules", "api", "rule-provider-names", "geo-rules", "custom-outbounds"]) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, null, { + default: _withCtx(() => [ + _createVNode(_sfc_main$i, { + "proxy-groups": proxyGroups.value, + "proxy-providers": proxyProviders.value, + "custom-outbounds": customOutbounds.value, + api: __props.api, + onRefresh: refreshAllRegions, + onShowSnackbar: _cache[4] || (_cache[4] = (val) => snackbar.value = val), + onShowError: showError, + onShowYaml: showYaml, + onCopyToClipboard: copyToClipboard, + onEditVisibility: handleEditVisibility + }, null, 8, ["proxy-groups", "proxy-providers", "custom-outbounds", "api"]) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, null, { + default: _withCtx(() => [ + _createVNode(_sfc_main$d, { + proxies: proxies.value, + api: __props.api, + onRefresh: refreshAllRegions, + onShowSnackbar: _cache[5] || (_cache[5] = (val) => snackbar.value = val), + onShowError: showError, + onShowYaml: showYaml, + onCopyToClipboard: copyToClipboard, + onEditVisibility: handleEditVisibility + }, null, 8, ["proxies", "api"]) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, null, { + default: _withCtx(() => [ + _createVNode(_sfc_main$6, { + "rule-providers": ruleProviders.value, + api: __props.api, + onRefresh: refreshAllRegions, + onShowSnackbar: _cache[6] || (_cache[6] = (val) => snackbar.value = val), + onShowError: showError, + onShowYaml: showYaml, + onEditVisibility: handleEditVisibility + }, null, 8, ["rule-providers", "api"]) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, null, { + default: _withCtx(() => [ + _createVNode(_sfc_main$2, { + hosts: hosts.value, + "best-cloudflare-i-ps": bestCloudflareIPs.value, + api: __props.api, + onRefresh: _cache[7] || (_cache[7] = ($event) => refreshAllRegions(["hosts"])), + onShowSnackbar: _cache[8] || (_cache[8] = (val) => snackbar.value = val), + onShowError: showError + }, null, 8, ["hosts", "best-cloudflare-i-ps", "api"]) + ]), + _: 1 + }), + _createVNode(_component_v_window_item, null, { + default: _withCtx(() => [ + _createVNode(SubscriptionTab, { + "subscriptions-info": subscriptionsInfo.value, + api: __props.api, + onRefresh: refreshAllRegions, + onShowSnackbar: _cache[9] || (_cache[9] = (val) => snackbar.value = val), + onShowError: showError, + onCopyToClipboard: copyToClipboard, + onSwitch: notifySwitch + }, null, 8, ["subscriptions-info", "api"]) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ])) + ]), + _: 1 + }), + _createVNode(_component_v_expand_transition, null, { + default: _withCtx(() => [ + expand.value ? (_openBlock(), _createElementBlock("div", _hoisted_4, [ + _createVNode(_sfc_main$1, { + "ruleset-rules-count": sortedRulesetRules.value.length, + "top-rules-count": sortedRules.value.length, + "proxy-groups-count": proxyGroups.value.length, + "extra-proxies-count": proxies.value.length, + "extra-rule-providers-count": ruleProviders.value.length, + "hosts-count": hosts.value.length, + "geosite-count": geoRules.value.geosite.length, + "last-updated": lastUpdated.value + }, null, 8, ["ruleset-rules-count", "top-rules-count", "proxy-groups-count", "extra-proxies-count", "extra-rule-providers-count", "hosts-count", "geosite-count", "last-updated"]) + ])) : _createCommentVNode("", true) + ]), + _: 1 + }), + _createVNode(_component_v_card_actions, null, { + default: _withCtx(() => [ + _createVNode(_component_v_btn, { + color: "primary", + loading: loading.value, + onClick: refreshData + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[33] || (_cache[33] = [ + _createTextVNode("mdi-refresh") + ])), + _: 1 + }), + _cache[34] || (_cache[34] = _createTextVNode(" 刷新数据 ")) + ]), + _: 1 + }, 8, ["loading"]), + presetIdentifiers.value.length > 0 ? (_openBlock(), _createBlock(_component_v_menu, { key: 0 }, { + activator: _withCtx(({ props: props2 }) => [ + _createVNode(_component_v_btn, _mergeProps({ color: "info" }, props2), { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[35] || (_cache[35] = [ + _createTextVNode("mdi-link-variant") + ])), + _: 1 + }), + _cache[36] || (_cache[36] = _createTextVNode(" 生成链接 ")) + ]), + _: 2 + }, 1040) + ]), + default: _withCtx(() => [ + _createVNode(_component_v_list, null, { + default: _withCtx(() => [ + _createVNode(_component_v_list_item, { + href: subUrl.value, + target: "_blank" + }, { + prepend: _withCtx(() => [ + _createVNode(_component_v_icon, { icon: "mdi-link-variant" }) + ]), + default: _withCtx(() => [ + _createVNode(_component_v_list_item_title, null, { + default: _withCtx(() => _cache[37] || (_cache[37] = [ + _createTextVNode("默认") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["href"]), + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(presetIdentifiers.value, (id) => { + return _openBlock(), _createBlock(_component_v_list_item, { + key: id, + href: generateIdentifierUrl(id), + target: "_blank" + }, { + prepend: _withCtx(() => [ + _createVNode(_component_v_icon, { icon: "mdi-devices" }) + ]), + default: _withCtx(() => [ + _createVNode(_component_v_list_item_title, null, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(id), 1) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1032, ["href"]); + }), 128)) + ]), + _: 1 + }) + ]), + _: 1 + })) : (_openBlock(), _createBlock(_component_v_btn, { + key: 1, + color: "info", + href: subUrl.value, + target: "_blank" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[38] || (_cache[38] = [ + _createTextVNode("mdi-link-variant") + ])), + _: 1 + }), + _cache[39] || (_cache[39] = _createTextVNode(" 生成链接 ")) + ]), + _: 1 + }, 8, ["href"])), + _createVNode(_component_v_btn, { + color: "success", + onClick: _cache[11] || (_cache[11] = ($event) => expand.value = !expand.value) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[40] || (_cache[40] = [ + _createTextVNode("mdi-chart-bar") + ])), + _: 1 + }), + _cache[41] || (_cache[41] = _createTextVNode(" 统计信息 ")) + ]), + _: 1 + }), + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + color: "primary", + onClick: notifySwitch + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[42] || (_cache[42] = [ + _createTextVNode("mdi-cog") + ])), + _: 1 + }), + _cache[43] || (_cache[43] = _createTextVNode(" 配置 ")) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_snackbar, { + modelValue: snackbar.value.show, + "onUpdate:modelValue": _cache[12] || (_cache[12] = ($event) => snackbar.value.show = $event), + color: snackbar.value.color, + location: "bottom", + class: "mb-2" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(snackbar.value.message), 1) + ]), + _: 1 + }, 8, ["modelValue", "color"]) + ]), + _: 1 + }), + showYamlDialog.value ? (_openBlock(), _createBlock(_sfc_main$w, { + key: 0, + content: displayedYaml.value, + onCopyToClipboard: copyToClipboard, + onClose: _cache[13] || (_cache[13] = ($event) => showYamlDialog.value = false) + }, null, 8, ["content"])) : _createCommentVNode("", true), + visibilityDialogVisible.value ? (_openBlock(), _createBlock(_sfc_main$v, { + key: 1, + modelValue: visibilityDialogVisible.value, + "onUpdate:modelValue": _cache[14] || (_cache[14] = ($event) => visibilityDialogVisible.value = $event), + meta: currentVisibilityMeta.value, + endpoint: currentVisibilityEndpoint.value, + region: currentVisibilityRegion.value, + api: __props.api, + "preset-identifiers": presetIdentifiers.value, + onRefresh: refreshAllRegions, + onShowSnackbar: _cache[15] || (_cache[15] = (val) => snackbar.value = val), + onShowError: showError, + onClose: _cache[16] || (_cache[16] = ($event) => visibilityDialogVisible.value = false) + }, null, 8, ["modelValue", "meta", "endpoint", "region", "api", "preset-identifiers"])) : _createCommentVNode("", true) + ]); + }; + } +}); + +const PageComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-ab912b83"]]); + +export { PageComponent as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-D32QZFxh.js b/plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-D32QZFxh.js new file mode 100644 index 0000000..47fc77e --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-D32QZFxh.js @@ -0,0 +1,208 @@ +const isValidUrl = (urlString) => { + if (!urlString) return false; + try { + const url = new URL(urlString); + return url.protocol === "http:" || url.protocol === "https:"; + } catch (e) { + return false; + } +}; +function isValidIP(ip) { + const ipv4Regex = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; + const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(([0-9a-fA-F]{1,4}:){1,7}|:):([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})$/; + return ipv4Regex.test(ip) || ipv6Regex.test(ip); +} +function validateIPs(ips) { + if (ips.length === 0) { + return `至少需要一个 IP 地址`; + } + for (const ip of ips) { + if (!isValidIP(ip)) { + return `无效的 IP 地址: ${ip}`; + } + } + return true; +} +function getUsageColor(percentage) { + return percentage > 90 ? "error" : percentage > 70 ? "warning" : "success"; +} +function getBehaviorColor(action) { + const colors = { + classical: "success", + domain: "error", + ipcidr: "error" + }; + return colors[action] || "primary"; +} +function getFormatColor(action) { + const colors = { + yaml: "success", + text: "warning", + mrs: "info" + }; + return colors[action] || "secondary"; +} +function getRuleTypeColor(type) { + const colors = { + DOMAIN: "primary", + "DOMAIN-SUFFIX": "primary", + "DOMAIN-KEYWORD": "primary", + "DOMAIN-REGEX": "primary", + "DOMAIN-WILDCARD": "primary", + GEOSITE: "info", + GEOIP: "info", + "IP-CIDR": "warning", + "IP-CIDR6": "warning", + "IP-SUFFIX": "warning", + "IP-ASN": "warning", + "SRC-GEOIP": "info", + "SRC-IP-ASN": "warning", + "SRC-IP-CIDR": "warning", + "SRC-IP-SUFFIX": "warning", + "DST-PORT": "success", + "SRC-PORT": "success", + "IN-PORT": "success", + "IN-TYPE": "success", + "IN-USER": "success", + "IN-NAME": "success", + "PROCESS-PATH": "error", + "PROCESS-PATH-REGEX": "error", + "PROCESS-NAME": "error", + "PROCESS-NAME-REGEX": "error", + UID: "secondary", + NETWORK: "secondary", + DSCP: "secondary", + "RULE-SET": "deep-purple", + AND: "deep-orange", + OR: "deep-orange", + NOT: "deep-orange", + "SUB-RULE": "deep-orange", + MATCH: "teal" + }; + return colors[type] || "grey"; +} +function getSourceColor(source) { + const colors = { + Auto: "success", + Manual: "info" + }; + return colors[source] || "primary"; +} +function getActionColor(action) { + const colors = { + DIRECT: "success", + REJECT: "error", + "REJECT-DROP": "error", + PASS: "warning", + COMPATIBLE: "info" + }; + return colors[action] || "primary"; +} +function getProxyGroupTypeColor(action) { + const colors = { + "url-test": "success", + fallback: "error", + "load-balance": "primary", + select: "info" + }; + return colors[action] || "warning"; +} +function getProxyColor(action) { + const colors = { + ss: "success", + ssr: "success", + trojan: "error", + vmess: "primary", + vless: "primary", + hysteria: "info", + hysteria2: "info", + anytls: "warning" + }; + return colors[action] || "secondary"; +} +function getBoolColor(value) { + if (value) { + return "primary"; + } + return "success"; +} +function isSystemRule(rule) { + return rule.meta.source?.startsWith("Auto"); +} +function isManual(source) { + return source === "Manual"; +} +function isInvalid(source) { + return source === "Invalid"; +} +function isRegion(source) { + return source === "Auto"; +} +function pageTitle(itemPerPageValue) { + if (itemPerPageValue < 0) { + return "♾️"; + } + return `${itemPerPageValue}`; +} +function formatBytes(bytes) { + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; +} +function formatTimestamp(timestamp) { + if (!timestamp) return "N/A"; + const date = new Date(timestamp * 1e3); + return date.toLocaleDateString("zh-CN"); +} +function timestampToDate(timestamp) { + if (!timestamp) return "N/A"; + const date = new Date(timestamp * 1e3); + return date.toLocaleString("zh-CN", { + // 'en-GB' 表示使用英国格式(YYYY-MM-DD HH:mm:ss) + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false + // 24小时制 + }); +} +function getExpireColor(timestamp) { + if (!timestamp) return "grey"; + const secondsLeft = timestamp - Math.floor(Date.now() / 1e3); + const daysLeft = secondsLeft / 86400; + return daysLeft < 7 ? "error" : daysLeft < 30 ? "warning" : "success"; +} +function extractDomain(url) { + try { + const hostname = new URL(url).hostname; + const parts = hostname.split("."); + if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname) || hostname.includes(":")) { + return hostname; + } + if (parts.length <= 2) { + return hostname; + } + return parts.slice(-2).join("."); + } catch { + return url; + } +} +function getUsedPercentageFloor(data) { + const used = data.upload + data.download; + return data.total > 0 ? Math.floor(used / data.total * 100) : 0; +} + +const _export_sfc = (sfc, props) => { + const target = sfc.__vccOpts || sfc; + for (const [key, val] of props) { + target[key] = val; + } + return target; +}; + +export { _export_sfc as _, getActionColor as a, isManual as b, isRegion as c, getSourceColor as d, getProxyGroupTypeColor as e, isValidUrl as f, getRuleTypeColor as g, isInvalid as h, isSystemRule as i, getProxyColor as j, extractDomain as k, formatTimestamp as l, getExpireColor as m, formatBytes as n, getUsageColor as o, pageTitle as p, getUsedPercentageFloor as q, getFormatColor as r, getBehaviorColor as s, timestampToDate as t, getBoolColor as u, validateIPs as v }; diff --git a/plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-pcqpp-6-.js b/plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-pcqpp-6-.js deleted file mode 100644 index 3da658f..0000000 --- a/plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-pcqpp-6-.js +++ /dev/null @@ -1,9 +0,0 @@ -const _export_sfc = (sfc, props) => { - const target = sfc.__vccOpts || sfc; - for (const [key, val] of props) { - target[key] = val; - } - return target; -}; - -export { _export_sfc as _ }; diff --git a/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js b/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js index a04be33..22fc133 100644 --- a/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js +++ b/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js @@ -2,14 +2,14 @@ const currentImports = {}; const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); let moduleMap = { "./Page":()=>{ - dynamicLoadingCss(["__federation_expose_Page-Dx-0nC8K.css"], false, './Page'); - return __federation_import('./__federation_expose_Page-CUYOswsP.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, + dynamicLoadingCss(["__federation_expose_Page-BLBLx7jX.css"], false, './Page'); + return __federation_import('./__federation_expose_Page-KIk8A7rq.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, "./Config":()=>{ - dynamicLoadingCss(["__federation_expose_Config-D7x82s8Y.css"], false, './Config'); - return __federation_import('./__federation_expose_Config-C8YPPEsk.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, + dynamicLoadingCss(["__federation_expose_Config-CwbjkOP2.css"], false, './Config'); + return __federation_import('./__federation_expose_Config-CY46uj5g.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, "./Dashboard":()=>{ - dynamicLoadingCss(["__federation_expose_Dashboard-vS9Qm2ZB.css"], false, './Dashboard'); - return __federation_import('./__federation_expose_Dashboard-BDSt5WaH.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},}; + dynamicLoadingCss(["__federation_expose_Dashboard-CFBdUa27.css"], false, './Dashboard'); + return __federation_import('./__federation_expose_Dashboard-CybypqLB.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},}; const seen = {}; const dynamicLoadingCss = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => { const metaUrl = import.meta.url; diff --git a/plugins.v2/clashruleprovider/dist/assets/theme-monokai-Bn79mBHh.js b/plugins.v2/clashruleprovider/dist/assets/theme-monokai-CF_yROe-.js similarity index 98% rename from plugins.v2/clashruleprovider/dist/assets/theme-monokai-Bn79mBHh.js rename to plugins.v2/clashruleprovider/dist/assets/theme-monokai-CF_yROe-.js index aca61f9..119e567 100644 --- a/plugins.v2/clashruleprovider/dist/assets/theme-monokai-Bn79mBHh.js +++ b/plugins.v2/clashruleprovider/dist/assets/theme-monokai-CF_yROe-.js @@ -713,6 +713,37 @@ var ace$2 = {exports: {}}; exports.importCssStylsheet = function (uri, doc) { exports.buildDom(["link", { rel: "stylesheet", href: uri }], exports.getDocumentHead(doc)); }; + exports.$fixPositionBug = function (el) { + var rect = el.getBoundingClientRect(); + if (el.style.left) { + var target = parseFloat(el.style.left); + var result = +rect.left; + if (Math.abs(target - result) > 1) { + el.style.left = 2 * target - result + "px"; + } + } + if (el.style.right) { + var target = parseFloat(el.style.right); + var result = window.innerWidth - rect.right; + if (Math.abs(target - result) > 1) { + el.style.right = 2 * target - result + "px"; + } + } + if (el.style.top) { + var target = parseFloat(el.style.top); + var result = +rect.top; + if (Math.abs(target - result) > 1) { + el.style.top = 2 * target - result + "px"; + } + } + if (el.style.bottom) { + var target = parseFloat(el.style.bottom); + var result = window.innerHeight - rect.bottom; + if (Math.abs(target - result) > 1) { + el.style.bottom = 2 * target - result + "px"; + } + } + }; exports.scrollbarWidth = function (doc) { var inner = exports.createElement("ace_inner"); inner.style.width = "100%"; @@ -1319,7 +1350,7 @@ var ace$2 = {exports: {}}; reportErrorIfPathIsNotConfigured = function () { }; } }; - exports.version = "1.43.2"; + exports.version = "1.43.5"; }); @@ -2072,6 +2103,7 @@ var ace$2 = {exports: {}}; this.text = dom.createElement("textarea"); this.text.className = "ace_text-input"; this.text.setAttribute("wrap", "off"); + this.text.setAttribute("autocomplete", "off"); this.text.setAttribute("autocorrect", "off"); this.text.setAttribute("autocapitalize", "off"); this.text.setAttribute("spellcheck", "false"); @@ -2858,7 +2890,7 @@ var ace$2 = {exports: {}}; anchor = this.$clickSelection.start; } else { - var orientedRange = calcRangeOrientation(this.$clickSelection, cursor); + var orientedRange = calcRangeOrientation(this.$clickSelection, cursor, editor.session); cursor = orientedRange.cursor; anchor = orientedRange.anchor; } @@ -2889,7 +2921,7 @@ var ace$2 = {exports: {}}; anchor = range.start; } else { - var orientedRange = calcRangeOrientation(this.$clickSelection, cursor); + var orientedRange = calcRangeOrientation(this.$clickSelection, cursor, editor.session); cursor = orientedRange.cursor; anchor = orientedRange.anchor; } @@ -3003,11 +3035,11 @@ var ace$2 = {exports: {}}; function calcDistance(ax, ay, bx, by) { return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2)); } - function calcRangeOrientation(range, cursor) { + function calcRangeOrientation(range, cursor, session) { if (range.start.row == range.end.row) var cmp = 2 * cursor.column - range.start.column - range.end.column; else if (range.start.row == range.end.row - 1 && !range.start.column && !range.end.column) - var cmp = cursor.column - 4; + var cmp = 3 * cursor.column - 2 * session.getLine(range.start.row).length; else var cmp = 2 * cursor.row - range.start.row - range.end.row; if (cmp < 0) @@ -3018,6 +3050,71 @@ var ace$2 = {exports: {}}; }); + ace.define("ace/mouse/mouse_event",["require","exports","module","ace/lib/event","ace/lib/useragent"], function(require, exports, module){ var event = require("../lib/event"); + var useragent = require("../lib/useragent"); + var MouseEvent = /** @class */ (function () { + function MouseEvent(domEvent, editor) { this.speed; this.wheelX; this.wheelY; + this.domEvent = domEvent; + this.editor = editor; + this.x = this.clientX = domEvent.clientX; + this.y = this.clientY = domEvent.clientY; + this.$pos = null; + this.$inSelection = null; + this.propagationStopped = false; + this.defaultPrevented = false; + } + MouseEvent.prototype.stopPropagation = function () { + event.stopPropagation(this.domEvent); + this.propagationStopped = true; + }; + MouseEvent.prototype.preventDefault = function () { + event.preventDefault(this.domEvent); + this.defaultPrevented = true; + }; + MouseEvent.prototype.stop = function () { + this.stopPropagation(); + this.preventDefault(); + }; + MouseEvent.prototype.getDocumentPosition = function () { + if (this.$pos) + return this.$pos; + this.$pos = this.editor.renderer.screenToTextCoordinates(this.clientX, this.clientY); + return this.$pos; + }; + MouseEvent.prototype.getGutterRow = function () { + var documentRow = this.getDocumentPosition().row; + var screenRow = this.editor.session.documentToScreenRow(documentRow, 0); + var screenTopRow = this.editor.session.documentToScreenRow(this.editor.renderer.$gutterLayer.$lines.get(0).row, 0); + return screenRow - screenTopRow; + }; + MouseEvent.prototype.inSelection = function () { + if (this.$inSelection !== null) + return this.$inSelection; + var editor = this.editor; + var selectionRange = editor.getSelectionRange(); + if (selectionRange.isEmpty()) + this.$inSelection = false; + else { + var pos = this.getDocumentPosition(); + this.$inSelection = selectionRange.contains(pos.row, pos.column); + } + return this.$inSelection; + }; + MouseEvent.prototype.getButton = function () { + return event.getButton(this.domEvent); + }; + MouseEvent.prototype.getShiftKey = function () { + return this.domEvent.shiftKey; + }; + MouseEvent.prototype.getAccelKey = function () { + return useragent.isMac ? this.domEvent.metaKey : this.domEvent.ctrlKey; + }; + return MouseEvent; + }()); + exports.MouseEvent = MouseEvent; + + }); + ace.define("ace/lib/scroll",["require","exports","module"], function(require, exports, module){exports.preventParentScroll = function preventParentScroll(event) { event.stopPropagation(); var target = event.currentTarget; @@ -3090,8 +3187,20 @@ var ace$2 = {exports: {}}; dom.addCssClass(this.getElement(), className); }; Tooltip.prototype.setTheme = function (theme) { - this.$element.className = CLASSNAME + " " + - (theme.isDark ? "ace_dark " : "") + (theme.cssClass || ""); + if (this.theme) { + this.theme.isDark && dom.removeCssClass(this.getElement(), "ace_dark"); + this.theme.cssClass && dom.removeCssClass(this.getElement(), this.theme.cssClass); + } + if (theme.isDark) { + dom.addCssClass(this.getElement(), "ace_dark"); + } + if (theme.cssClass) { + dom.addCssClass(this.getElement(), theme.cssClass); + } + this.theme = { + isDark: theme.isDark, + cssClass: theme.cssClass + }; }; Tooltip.prototype.show = function (text, x, y) { if (text != null) @@ -3218,12 +3327,18 @@ var ace$2 = {exports: {}}; HoverTooltip.prototype.addToEditor = function (editor) { editor.on("mousemove", this.onMouseMove); editor.on("mousedown", this.hide); - editor.renderer.getMouseEventTarget().addEventListener("mouseout", this.onMouseOut, true); + var target = editor.renderer.getMouseEventTarget(); + if (target && typeof target.removeEventListener === "function") { + target.addEventListener("mouseout", this.onMouseOut, true); + } }; HoverTooltip.prototype.removeFromEditor = function (editor) { editor.off("mousemove", this.onMouseMove); editor.off("mousedown", this.hide); - editor.renderer.getMouseEventTarget().removeEventListener("mouseout", this.onMouseOut, true); + var target = editor.renderer.getMouseEventTarget(); + if (target && typeof target.removeEventListener === "function") { + target.removeEventListener("mouseout", this.onMouseOut, true); + } if (this.timeout) { clearTimeout(this.timeout); this.timeout = null; @@ -3278,7 +3393,6 @@ var ace$2 = {exports: {}}; this.$gatherData = value; }; HoverTooltip.prototype.showForRange = function (editor, range, domNode, startingEvent) { - var MARGIN = 10; if (startingEvent && startingEvent != this.lastEvent) return; if (this.isOpen && document.activeElement == this.getElement()) @@ -3290,7 +3404,6 @@ var ace$2 = {exports: {}}; this.setTheme(renderer.theme); } this.isOpen = true; - this.addMarker(range, editor.session); this.range = Range.fromPoints(range.start, range.end); var position = renderer.textToScreenCoordinates(range.start.row, range.start.column); var rect = renderer.scroller.getBoundingClientRect(); @@ -3301,17 +3414,27 @@ var ace$2 = {exports: {}}; element.appendChild(domNode); element.style.maxHeight = ""; element.style.display = "block"; - var labelHeight = element.clientHeight; - var labelWidth = element.clientWidth; - var spaceBelow = window.innerHeight - position.pageY - renderer.lineHeight; - var isAbove = true; - if (position.pageY - labelHeight < 0 && position.pageY < spaceBelow) { - isAbove = false; - } - element.style.maxHeight = (isAbove ? position.pageY : spaceBelow) - MARGIN + "px"; - element.style.top = isAbove ? "" : position.pageY + renderer.lineHeight + "px"; - element.style.bottom = isAbove ? window.innerHeight - position.pageY + "px" : ""; - element.style.left = Math.min(position.pageX, window.innerWidth - labelWidth - MARGIN) + "px"; + this.$setPosition(editor, position, true, range); + dom.$fixPositionBug(element); + }; + HoverTooltip.prototype.$setPosition = function (editor, position, withMarker, range) { + var MARGIN = 10; + withMarker && this.addMarker(range, editor.session); + var renderer = editor.renderer; + var element = this.getElement(); + var labelHeight = element.offsetHeight; + var labelWidth = element.offsetWidth; + var anchorTop = position.pageY; + var anchorLeft = position.pageX; + var spaceBelow = window.innerHeight - anchorTop - renderer.lineHeight; + var isAbove = this.$shouldPlaceAbove(labelHeight, anchorTop, spaceBelow - MARGIN); + element.style.maxHeight = (isAbove ? anchorTop : spaceBelow) - MARGIN + "px"; + element.style.top = isAbove ? "" : anchorTop + renderer.lineHeight + "px"; + element.style.bottom = isAbove ? window.innerHeight - anchorTop + "px" : ""; + element.style.left = Math.min(anchorLeft, window.innerWidth - labelWidth - MARGIN) + "px"; + }; + HoverTooltip.prototype.$shouldPlaceAbove = function (labelHeight, anchorTop, spaceBelow) { + return !(anchorTop - labelHeight < 0 && anchorTop < spaceBelow); }; HoverTooltip.prototype.addMarker = function (range, session) { if (this.marker) { @@ -3321,6 +3444,11 @@ var ace$2 = {exports: {}}; this.marker = session && session.addMarker(range, "ace_highlight-marker", "text"); }; HoverTooltip.prototype.hide = function (e) { + if (e && this.$fromKeyboard && e.type == "keydown") { + if (e.code == "Escape") { + return; + } + } if (!e && document.activeElement == this.getElement()) return; if (e && e.target && (e.type != "keydown" || e.ctrlKey || e.metaKey) && this.$element.contains(e.target)) @@ -3331,6 +3459,7 @@ var ace$2 = {exports: {}}; this.timeout = null; this.addMarker(null); if (this.isOpen) { + this.$fromKeyboard = false; this.$removeCloseEvents(); this.getElement().style.display = "none"; this.isOpen = false; @@ -3368,7 +3497,7 @@ var ace$2 = {exports: {}}; }); - ace.define("ace/mouse/default_gutter_handler",["require","exports","module","ace/lib/dom","ace/lib/event","ace/tooltip","ace/config"], function(require, exports, module){ var __extends = (this && this.__extends) || (function () { + ace.define("ace/mouse/default_gutter_handler",["require","exports","module","ace/lib/dom","ace/mouse/mouse_event","ace/tooltip","ace/config","ace/range"], function(require, exports, module){ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || @@ -3395,17 +3524,19 @@ var ace$2 = {exports: {}}; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var dom = require("../lib/dom"); - var event = require("../lib/event"); - var Tooltip = require("../tooltip").Tooltip; + var MouseEvent = require("./mouse_event").MouseEvent; + var HoverTooltip = require("../tooltip").HoverTooltip; var nls = require("../config").nls; - var GUTTER_TOOLTIP_LEFT_OFFSET = 5; - var GUTTER_TOOLTIP_TOP_OFFSET = 3; - exports.GUTTER_TOOLTIP_LEFT_OFFSET = GUTTER_TOOLTIP_LEFT_OFFSET; - exports.GUTTER_TOOLTIP_TOP_OFFSET = GUTTER_TOOLTIP_TOP_OFFSET; + var Range = require("../range").Range; function GutterHandler(mouseHandler) { var editor = mouseHandler.editor; var gutter = editor.renderer.$gutterLayer; - var tooltip = new GutterTooltip(editor, true); + mouseHandler.$tooltip = new GutterTooltip(editor); + mouseHandler.$tooltip.addToEditor(editor); + mouseHandler.$tooltip.setDataProvider(function (e, editor) { + var row = e.getDocumentPosition().row; + mouseHandler.$tooltip.showTooltip(row); + }); mouseHandler.editor.setDefaultHandler("guttermousedown", function (e) { if (!editor.isFocused() || e.getButton() != 0) return; @@ -3427,87 +3558,11 @@ var ace$2 = {exports: {}}; mouseHandler.captureMouse(e); return e.preventDefault(); }); - var tooltipTimeout, mouseEvent; - function showTooltip() { - var row = mouseEvent.getDocumentPosition().row; - var maxRow = editor.session.getLength(); - if (row == maxRow) { - var screenRow = editor.renderer.pixelToScreenCoordinates(0, mouseEvent.y).row; - var pos = mouseEvent.$pos; - if (screenRow > editor.session.documentToScreenRow(pos.row, pos.column)) - return hideTooltip(); - } - tooltip.showTooltip(row); - if (!tooltip.isOpen) - return; - editor.on("mousewheel", hideTooltip); - editor.on("changeSession", hideTooltip); - window.addEventListener("keydown", hideTooltip, true); - if (mouseHandler.$tooltipFollowsMouse) { - moveTooltip(mouseEvent); - } - else { - var gutterRow = mouseEvent.getGutterRow(); - var gutterCell = gutter.$lines.get(gutterRow); - if (gutterCell) { - var gutterElement = gutterCell.element.querySelector(".ace_gutter_annotation"); - var rect = gutterElement.getBoundingClientRect(); - var style = tooltip.getElement().style; - style.left = (rect.right - GUTTER_TOOLTIP_LEFT_OFFSET) + "px"; - style.top = (rect.bottom - GUTTER_TOOLTIP_TOP_OFFSET) + "px"; - } - else { - moveTooltip(mouseEvent); - } - } - } - function hideTooltip(e) { - if (e && e.type === "keydown" && (e.ctrlKey || e.metaKey)) - return; - if (e && e.type === "mouseout" && (!e.relatedTarget || tooltip.getElement().contains(e.relatedTarget))) - return; - if (tooltipTimeout) - tooltipTimeout = clearTimeout(tooltipTimeout); - if (tooltip.isOpen) { - tooltip.hideTooltip(); - editor.off("mousewheel", hideTooltip); - editor.off("changeSession", hideTooltip); - window.removeEventListener("keydown", hideTooltip, true); - } - } - function moveTooltip(e) { - tooltip.setPosition(e.x, e.y); - } - mouseHandler.editor.setDefaultHandler("guttermousemove", function (e) { - var target = e.domEvent.target || e.domEvent.srcElement; - if (dom.hasCssClass(target, "ace_fold-widget") || dom.hasCssClass(target, "ace_custom-widget")) - return hideTooltip(); - if (tooltip.isOpen && mouseHandler.$tooltipFollowsMouse) - moveTooltip(e); - mouseEvent = e; - if (tooltipTimeout) - return; - tooltipTimeout = setTimeout(function () { - tooltipTimeout = null; - if (mouseEvent && !mouseHandler.isMousePressed) - showTooltip(); - }, 50); - }); - event.addListener(editor.renderer.$gutter, "mouseout", function (e) { - mouseEvent = null; - if (!tooltip.isOpen) - return; - tooltipTimeout = setTimeout(function () { - tooltipTimeout = null; - hideTooltip(e); - }, 50); - }, editor); } exports.GutterHandler = GutterHandler; var GutterTooltip = /** @class */ (function (_super) { __extends(GutterTooltip, _super); - function GutterTooltip(editor, isHover) { - if (isHover === void 0) { isHover = false; } + function GutterTooltip(editor) { var _this = _super.call(this, editor.container) || this; _this.id = "gt" + (++GutterTooltip.$uid); _this.editor = editor; @@ -3516,35 +3571,37 @@ var ace$2 = {exports: {}}; el.setAttribute("role", "tooltip"); el.setAttribute("id", _this.id); el.style.pointerEvents = "auto"; - if (isHover) { - _this.onMouseOut = _this.onMouseOut.bind(_this); - el.addEventListener("mouseout", _this.onMouseOut); - } + _this.idleTime = 50; + _this.onDomMouseMove = _this.onDomMouseMove.bind(_this); + _this.onDomMouseOut = _this.onDomMouseOut.bind(_this); + _this.setClassName("ace_gutter-tooltip"); return _this; } - GutterTooltip.prototype.onMouseOut = function (e) { - if (!this.isOpen) - return; - if (!e.relatedTarget || this.getElement().contains(e.relatedTarget)) - return; - if (e && e.currentTarget.contains(e.relatedTarget)) - return; - this.hideTooltip(); + GutterTooltip.prototype.onDomMouseMove = function (domEvent) { + var aceEvent = new MouseEvent(domEvent, this.editor); + this.onMouseMove(aceEvent, this.editor); }; - GutterTooltip.prototype.setPosition = function (x, y) { - var windowWidth = window.innerWidth || document.documentElement.clientWidth; - var windowHeight = window.innerHeight || document.documentElement.clientHeight; - var width = this.getWidth(); - var height = this.getHeight(); - x += 15; - y += 15; - if (x + width > windowWidth) { - x -= (x + width) - windowWidth; + GutterTooltip.prototype.onDomMouseOut = function (domEvent) { + var aceEvent = new MouseEvent(domEvent, this.editor); + this.onMouseOut(aceEvent); + }; + GutterTooltip.prototype.addToEditor = function (editor) { + var gutter = editor.renderer.$gutter; + gutter.addEventListener("mousemove", this.onDomMouseMove); + gutter.addEventListener("mouseout", this.onDomMouseOut); + _super.prototype.addToEditor.call(this, editor); + }; + GutterTooltip.prototype.removeFromEditor = function (editor) { + var gutter = editor.renderer.$gutter; + gutter.removeEventListener("mousemove", this.onDomMouseMove); + gutter.removeEventListener("mouseout", this.onDomMouseOut); + _super.prototype.removeFromEditor.call(this, editor); + }; + GutterTooltip.prototype.destroy = function () { + if (this.editor) { + this.removeFromEditor(this.editor); } - if (y + height > windowHeight) { - y -= 20 + height; - } - Tooltip.prototype.setPosition.call(this, x, y); + _super.prototype.destroy.call(this); }; Object.defineProperty(GutterTooltip, "annotationLabels", { get: function () { @@ -3610,7 +3667,7 @@ var ace$2 = {exports: {}}; } } if (annotation.displayText.length === 0) - return this.hideTooltip(); + return this.hide(); var annotationMessages = { error: [], security: [], warning: [], info: [], hint: [] }; var iconClassName = gutter.$useSvgGutterIcons ? "ace_icon_svg" : "ace_icon"; for (var i = 0; i < annotation.displayText.length; i++) { @@ -3625,26 +3682,42 @@ var ace$2 = {exports: {}}; lineElement.appendChild(dom.createElement("br")); annotationMessages[annotation.type[i].replace("_fold", "")].push(lineElement); } - var tooltipElement = this.getElement(); - dom.removeChildren(tooltipElement); + var tooltipElement = dom.createElement("span"); annotationMessages.error.forEach(function (el) { return tooltipElement.appendChild(el); }); annotationMessages.security.forEach(function (el) { return tooltipElement.appendChild(el); }); annotationMessages.warning.forEach(function (el) { return tooltipElement.appendChild(el); }); annotationMessages.info.forEach(function (el) { return tooltipElement.appendChild(el); }); annotationMessages.hint.forEach(function (el) { return tooltipElement.appendChild(el); }); tooltipElement.setAttribute("aria-live", "polite"); - if (!this.isOpen) { - this.setTheme(this.editor.renderer.theme); - this.setClassName("ace_gutter-tooltip"); - } var annotationNode = this.$findLinkedAnnotationNode(row); if (annotationNode) { annotationNode.setAttribute("aria-describedby", this.id); } - this.show(); + var range = Range.fromPoints({ row: row, column: 0 }, { row: row, column: 0 }); + this.showForRange(this.editor, range, tooltipElement); this.visibleTooltipRow = row; this.editor._signal("showGutterTooltip", this); }; + GutterTooltip.prototype.$setPosition = function (editor, _ignoredPosition, _withMarker, range) { + var gutterCell = this.$findCellByRow(range.start.row); + if (!gutterCell) + return; + var el = gutterCell && gutterCell.element; + var anchorEl = el && (el.querySelector(".ace_gutter_annotation")); + if (!anchorEl) + return; + var r = anchorEl.getBoundingClientRect(); + if (!r) + return; + var position = { + pageX: r.right, + pageY: r.top + }; + return _super.prototype.$setPosition.call(this, editor, position, false, range); + }; + GutterTooltip.prototype.$shouldPlaceAbove = function (labelHeight, anchorTop, spaceBelow) { + return spaceBelow < labelHeight; + }; GutterTooltip.prototype.$findLinkedAnnotationNode = function (row) { var cell = this.$findCellByRow(row); if (cell) { @@ -3657,12 +3730,11 @@ var ace$2 = {exports: {}}; GutterTooltip.prototype.$findCellByRow = function (row) { return this.editor.renderer.$gutterLayer.$lines.cells.find(function (el) { return el.row === row; }); }; - GutterTooltip.prototype.hideTooltip = function () { + GutterTooltip.prototype.hide = function (e) { if (!this.isOpen) { return; } this.$element.removeAttribute("aria-live"); - this.hide(); if (this.visibleTooltipRow != undefined) { var annotationNode = this.$findLinkedAnnotationNode(this.visibleTooltipRow); if (annotationNode) { @@ -3671,6 +3743,7 @@ var ace$2 = {exports: {}}; } this.visibleTooltipRow = undefined; this.editor._signal("hideGutterTooltip", this); + _super.prototype.hide.call(this, e); }; GutterTooltip.annotationsToSummaryString = function (annotations) { var e_1, _a; @@ -3694,78 +3767,19 @@ var ace$2 = {exports: {}}; } return summary.join(", "); }; + GutterTooltip.prototype.isOutsideOfText = function (e) { + var editor = e.editor; + var rect = editor.renderer.$gutter.getBoundingClientRect(); + return !(e.clientX >= rect.left && e.clientX <= rect.right && + e.clientY >= rect.top && e.clientY <= rect.bottom); + }; return GutterTooltip; - }(Tooltip)); + }(HoverTooltip)); GutterTooltip.$uid = 0; exports.GutterTooltip = GutterTooltip; }); - ace.define("ace/mouse/mouse_event",["require","exports","module","ace/lib/event","ace/lib/useragent"], function(require, exports, module){ var event = require("../lib/event"); - var useragent = require("../lib/useragent"); - var MouseEvent = /** @class */ (function () { - function MouseEvent(domEvent, editor) { this.speed; this.wheelX; this.wheelY; - this.domEvent = domEvent; - this.editor = editor; - this.x = this.clientX = domEvent.clientX; - this.y = this.clientY = domEvent.clientY; - this.$pos = null; - this.$inSelection = null; - this.propagationStopped = false; - this.defaultPrevented = false; - } - MouseEvent.prototype.stopPropagation = function () { - event.stopPropagation(this.domEvent); - this.propagationStopped = true; - }; - MouseEvent.prototype.preventDefault = function () { - event.preventDefault(this.domEvent); - this.defaultPrevented = true; - }; - MouseEvent.prototype.stop = function () { - this.stopPropagation(); - this.preventDefault(); - }; - MouseEvent.prototype.getDocumentPosition = function () { - if (this.$pos) - return this.$pos; - this.$pos = this.editor.renderer.screenToTextCoordinates(this.clientX, this.clientY); - return this.$pos; - }; - MouseEvent.prototype.getGutterRow = function () { - var documentRow = this.getDocumentPosition().row; - var screenRow = this.editor.session.documentToScreenRow(documentRow, 0); - var screenTopRow = this.editor.session.documentToScreenRow(this.editor.renderer.$gutterLayer.$lines.get(0).row, 0); - return screenRow - screenTopRow; - }; - MouseEvent.prototype.inSelection = function () { - if (this.$inSelection !== null) - return this.$inSelection; - var editor = this.editor; - var selectionRange = editor.getSelectionRange(); - if (selectionRange.isEmpty()) - this.$inSelection = false; - else { - var pos = this.getDocumentPosition(); - this.$inSelection = selectionRange.contains(pos.row, pos.column); - } - return this.$inSelection; - }; - MouseEvent.prototype.getButton = function () { - return event.getButton(this.domEvent); - }; - MouseEvent.prototype.getShiftKey = function () { - return this.domEvent.shiftKey; - }; - MouseEvent.prototype.getAccelKey = function () { - return useragent.isMac ? this.domEvent.metaKey : this.domEvent.ctrlKey; - }; - return MouseEvent; - }()); - exports.MouseEvent = MouseEvent; - - }); - ace.define("ace/mouse/dragdrop_handler",["require","exports","module","ace/lib/dom","ace/lib/event","ace/lib/useragent"], function(require, exports, module){ var dom = require("../lib/dom"); var event = require("../lib/event"); var useragent = require("../lib/useragent"); @@ -4574,6 +4588,8 @@ var ace$2 = {exports: {}}; MouseHandler.prototype.destroy = function () { if (this.releaseMouse) this.releaseMouse(); + if (this.$tooltip) + this.$tooltip.destroy(); }; return MouseHandler; }()); @@ -4583,7 +4599,6 @@ var ace$2 = {exports: {}}; dragDelay: { initialValue: (useragent.isMac ? 150 : 0) }, dragEnabled: { initialValue: true }, focusTimeout: { initialValue: 0 }, - tooltipFollowsMouse: { initialValue: true } }); exports.MouseHandler = MouseHandler; @@ -13724,8 +13739,7 @@ var ace$2 = {exports: {}}; }); - ace.define("ace/keyboard/gutter_handler",["require","exports","module","ace/lib/keys","ace/mouse/default_gutter_handler"], function(require, exports, module){ var keys = require('../lib/keys'); - var GutterTooltip = require("../mouse/default_gutter_handler").GutterTooltip; + ace.define("ace/keyboard/gutter_handler",["require","exports","module","ace/lib/keys"], function(require, exports, module){ var keys = require('../lib/keys'); var GutterKeyboardHandler = /** @class */ (function () { function GutterKeyboardHandler(editor) { this.editor = editor; @@ -13734,7 +13748,7 @@ var ace$2 = {exports: {}}; this.lines = editor.renderer.$gutterLayer.$lines; this.activeRowIndex = null; this.activeLane = null; - this.annotationTooltip = new GutterTooltip(this.editor); + this.annotationTooltip = this.editor.$mouseHandler.$tooltip; } GutterKeyboardHandler.prototype.addListener = function () { this.element.addEventListener("keydown", this.$onGutterKeyDown.bind(this)); @@ -13750,7 +13764,7 @@ var ace$2 = {exports: {}}; if (this.annotationTooltip.isOpen) { e.preventDefault(); if (e.keyCode === keys["escape"]) - this.annotationTooltip.hideTooltip(); + this.annotationTooltip.hide(); return; } if (e.target === this.element) { @@ -13869,12 +13883,8 @@ var ace$2 = {exports: {}}; } return; case "annotation": - var gutterElement = this.lines.cells[this.activeRowIndex].element.childNodes[2]; - var rect = gutterElement.getBoundingClientRect(); - var style = this.annotationTooltip.getElement().style; - style.left = rect.right + "px"; - style.top = rect.bottom + "px"; this.annotationTooltip.showTooltip(this.$rowIndexToRow(this.activeRowIndex)); + this.annotationTooltip.$fromKeyboard = true; break; } return; @@ -13893,7 +13903,7 @@ var ace$2 = {exports: {}}; } } if (this.annotationTooltip.isOpen) - this.annotationTooltip.hideTooltip(); + this.annotationTooltip.hide(); return; }; GutterKeyboardHandler.prototype.$isFoldWidgetVisible = function (index) { @@ -16178,7 +16188,6 @@ var ace$2 = {exports: {}}; dragDelay: "$mouseHandler", dragEnabled: "$mouseHandler", focusTimeout: "$mouseHandler", - tooltipFollowsMouse: "$mouseHandler", firstLineNumber: "session", overwrite: "session", newLineMode: "session", @@ -16328,6 +16337,7 @@ var ace$2 = {exports: {}}; var nls = require("../config").nls; var Gutter = /** @class */ (function () { function Gutter(parentEl) { + this.$showCursorMarker = null; this.element = dom.createElement("div"); this.element.className = "ace_layer ace_gutter-layer"; parentEl.appendChild(this.element); @@ -16448,6 +16458,8 @@ var ace$2 = {exports: {}}; } this._signal("afterRender"); this.$updateGutterWidth(config); + if (this.$showCursorMarker && this.$highlightGutterLine) + this.$updateCursorMarker(); }; Gutter.prototype.$updateGutterWidth = function (config) { var session = this.session; @@ -16476,6 +16488,8 @@ var ace$2 = {exports: {}}; this.$cursorRow = position.row; }; Gutter.prototype.updateLineHighlight = function () { + if (this.$showCursorMarker) + this.$updateCursorMarker(); if (!this.$highlightGutterLine) return; var row = this.session.selection.cursor.row; @@ -16502,6 +16516,26 @@ var ace$2 = {exports: {}}; } } }; + Gutter.prototype.$updateCursorMarker = function () { + if (!this.session) + return; + var session = this.session; + if (!this.$highlightElement) { + this.$highlightElement = dom.createElement("div"); + this.$highlightElement.className = "ace_gutter-cursor"; + this.$highlightElement.style.pointerEvents = "none"; + this.element.appendChild(this.$highlightElement); + } + var pos = session.selection.cursor; + var config = this.config; + var lines = this.$lines; + var screenTop = config.firstRowScreen * config.lineHeight; + var screenPage = Math.floor(screenTop / lines.canvasHeight); + var lineTop = session.documentToScreenRow(pos) * config.lineHeight; + var top = lineTop - (screenPage * lines.canvasHeight); + dom.setStyle(this.$highlightElement.style, "height", config.lineHeight + "px"); + dom.setStyle(this.$highlightElement.style, "top", top + "px"); + }; Gutter.prototype.scrollLines = function (config) { var oldConfig = this.config; this.config = config; @@ -16745,6 +16779,10 @@ var ace$2 = {exports: {}}; }; Gutter.prototype.setHighlightGutterLine = function (highlightGutterLine) { this.$highlightGutterLine = highlightGutterLine; + if (!highlightGutterLine && this.$highlightElement) { + this.$highlightElement.remove(); + this.$highlightElement = null; + } }; Gutter.prototype.setShowLineNumbers = function (show) { this.$renderer = !show && { @@ -16786,8 +16824,24 @@ var ace$2 = {exports: {}}; }; Gutter.prototype.$getGutterCell = function (row) { var cells = this.$lines.cells; - var visibileRow = this.session.documentToScreenRow(row, 0); - return cells[row - this.config.firstRowScreen - (row - visibileRow)]; + var min = 0; + var max = cells.length - 1; + if (row < cells[0].row || row > cells[max].row) + return; + while (min <= max) { + var mid = Math.floor((min + max) / 2); + var cell = cells[mid]; + if (cell.row > row) { + max = mid - 1; + } + else if (cell.row < row) { + min = mid + 1; + } + else { + return cell; + } + } + return cell; }; Gutter.prototype.$addCustomWidget = function (row, _a, cell) { var className = _a.className, label = _a.label, title = _a.title, callbacks = _a.callbacks; @@ -16850,7 +16904,7 @@ var ace$2 = {exports: {}}; }()); Gutter.prototype.$fixedWidth = false; Gutter.prototype.$highlightGutterLine = true; - Gutter.prototype.$renderer = ""; + Gutter.prototype.$renderer = undefined; Gutter.prototype.$showLineNumbers = true; Gutter.prototype.$showFoldWidgets = true; oop.implement(Gutter.prototype, EventEmitter); @@ -19856,6 +19910,15 @@ var ace$2 = {exports: {}}; : "padding" in (_self.theme || {}) ? 4 : _self.$padding; if (_self.$padding && padding != _self.$padding) _self.setPadding(padding); + if (_self.$gutterLayer) { + var showGutterCursor = module["$showGutterCursorMarker"]; + if (showGutterCursor && !_self.$gutterLayer.$showCursorMarker) { + _self.$gutterLayer.$showCursorMarker = "theme"; + } + else if (!showGutterCursor && _self.$gutterLayer.$showCursorMarker == "theme") { + _self.$gutterLayer.$showCursorMarker = null; + } + } _self.$theme = module.cssClass; _self.theme = module; dom.addCssClass(_self.container, module.cssClass); diff --git a/plugins.v2/clashruleprovider/helper/clashrulemanager.py b/plugins.v2/clashruleprovider/helper/clashrulemanager.py index 7870c32..93fdaaf 100644 --- a/plugins.v2/clashruleprovider/helper/clashrulemanager.py +++ b/plugins.v2/clashruleprovider/helper/clashrulemanager.py @@ -1,18 +1,10 @@ -import time - -from dataclasses import dataclass, field from typing import Any, Callable, Dict, List, Optional, Union, Iterator -from .clashruleparser import ClashRuleParser -from ..models.rule import Action, RoutingRuleType, MatchRule, ClashRule, LogicRule, SubRule +from pydantic import TypeAdapter, ValidationError - -@dataclass -class RuleItem: - """Clash rule item""" - rule: Union[ClashRule, LogicRule, MatchRule, SubRule] - remark: str = field(default="") - time_modified: float = field(default=0) +from ..models.metadata import Metadata +from ..models.rule import Action, RoutingRuleType, MatchRule, ClashRule, LogicRule +from ..models.ruleitem import RuleItem, RuleData class ClashRuleManager: @@ -21,20 +13,17 @@ class ClashRuleManager: self.rules: List[RuleItem] = [] def import_rules(self, rules_list: List[Dict[str, Any]]): - self.rules = [] + self.rules.clear() for r in rules_list: - rule = ClashRuleParser.parse_rule_line(r['rule']) - if rule is None: + try: + rule = RuleItem.model_validate(r) + except ValidationError: continue - remark = r.get('remark', '') - time_modified = r.get('time_modified', time.time()) - self.rules.append(RuleItem(rule=rule, remark=remark, time_modified=time_modified)) + self.rules.append(rule) def export_rules(self) -> List[Dict[str, str]]: - rules_list = [] - for rule in self.rules: - rules_list.append({'rule': str(rule.rule), 'remark': rule.remark, 'time_modified': rule.time_modified}) - return rules_list + adapter = TypeAdapter(list[RuleItem]) + return adapter.dump_python(self.rules, mode='json') def append_rules(self, clash_rules: List[RuleItem]): self.rules.extend(clash_rules) @@ -64,6 +53,15 @@ class ClashRuleManager: return self.rules.pop(priority) return None + def remove_rules_at_priorities(self, priorities: list[int]) -> list[RuleItem]: + """Remove rules at specific priorities""" + removed = [] + # Sort priorities in descending order to avoid index shift issues during removal + for priority in sorted(priorities, reverse=True): + if 0 <= priority < len(self.rules): + removed.append(self.rules.pop(priority)) + return removed + def remove_rules_by_lambda(self, condition: Callable[[RuleItem], bool]): """Remove rules by lambda""" initial_count = len(self.rules) @@ -101,7 +99,7 @@ class ClashRuleManager: return any(r.rule == clash_rule for r in self.rules) def has_rule_item(self, clash_rule: RuleItem) -> bool: - return any(clash_rule.remark == r.remark and r.rule == clash_rule.rule for r in self.rules) + return any(clash_rule.meta.source == r.meta.source and r.rule == clash_rule.rule for r in self.rules) def reorder_rules(self, moved_priority: int, target_priority: int) -> RuleItem: """Reorder the rules""" @@ -113,13 +111,27 @@ class ClashRuleManager: self.rules.insert(target_priority, rule) return rule - def to_list(self) -> List[Dict[str, Any]]: + def update_rules_at_priorities(self, priorities: dict[int, bool]) -> list[RuleItem]: + """Disable rules""" + updated = [] + for priority, disabled in priorities.items(): + if 0 <= priority < len(self.rules): + self.rules[priority].meta.disabled = disabled + updated.append(self.rules[priority]) + return updated + + def update_rule_meta_at_priority(self, priority: int, meta: Metadata) -> bool: + """Update rule metadata at priority""" + if 0 <= priority < len(self.rules): + self.rules[priority].meta = meta + return True + return False + + def to_list(self) -> list[RuleData]: """Convert parsed rules to a list""" - result = [] + result: list[RuleData] = [] for priority, rule_item in enumerate(self.rules): - rule_dict = {'remark': rule_item.remark, 'time_modified': rule_item.time_modified,'priority': priority, - **rule_item.rule.to_dict()} - result.append(rule_dict) + result.append(RuleData.from_rule_item(rule_item, priority)) return result def clear(self): diff --git a/plugins.v2/clashruleprovider/helper/clashruleparser.py b/plugins.v2/clashruleprovider/helper/clashruleparser.py index 473e3c4..56538ca 100644 --- a/plugins.v2/clashruleprovider/helper/clashruleparser.py +++ b/plugins.v2/clashruleprovider/helper/clashruleparser.py @@ -9,21 +9,25 @@ from ..models.rule import RuleType, Action, RoutingRuleType, MatchRule, ClashRul class ClashRuleParser: """Parser for Clash routing rules""" + @staticmethod + def parse(line: str) -> RuleType: + """Parse a single rule line""" + # Handle logic rules (AND, OR, NOT) + if line.startswith(('AND,', 'OR,', 'NOT,')): + return ClashRuleParser._parse_logic_rule(line) + elif line.startswith('MATCH'): + return ClashRuleParser._parse_match_rule(line) + elif line.startswith('SUB-RULE'): + return ClashRuleParser._parse_sub_rule(line) + # Handle regular rules + return ClashRuleParser._parse_regular_rule(line) + @staticmethod def parse_rule_line(line: str) -> Optional[RuleType]: """Parse a single rule line""" line = line.strip() try: - # Handle logic rules (AND, OR, NOT) - if line.startswith(('AND,', 'OR,', 'NOT,')): - return ClashRuleParser._parse_logic_rule(line) - elif line.startswith('MATCH'): - return ClashRuleParser._parse_match_rule(line) - elif line.startswith('SUB-RULE'): - return ClashRuleParser._parse_sub_rule(line) - # Handle regular rules - return ClashRuleParser._parse_regular_rule(line) - + return ClashRuleParser.parse(line) except (ValidationError, TypeError, ValueError, RecursionError): return None @@ -221,7 +225,7 @@ class ClashRuleParser: """ Parse conditions within logic rules, supporting nested logic. The examples of conditions_str: - - (DOMAIN,baidu.com)` + - (DOMAIN,baidu.com) - (AND,(DOMAIN,baidu.com),(NETWORK,TCP)) """ @@ -288,11 +292,6 @@ class ClashRuleParser: raise ValueError(f"Invalid rule format: {content}") return conditions - - @staticmethod - def action_string(action: Union[Action, str]) -> str: - return action.value if isinstance(action, Action) else action - @staticmethod def parse_rules(rules_text: str) -> List[Union[ClashRule, LogicRule, MatchRule]]: """Parse multiple rules from text, preserving order and priority""" diff --git a/plugins.v2/clashruleprovider/helper/configconverter.py b/plugins.v2/clashruleprovider/helper/configconverter.py index 6967405..8686768 100644 --- a/plugins.v2/clashruleprovider/helper/configconverter.py +++ b/plugins.v2/clashruleprovider/helper/configconverter.py @@ -2,7 +2,7 @@ import base64 import importlib import json import os -from typing import List, Dict, Any, Optional, Union +from typing import Dict, Any, Optional, Union from urllib.parse import quote from .converters import BaseConverter @@ -54,8 +54,8 @@ class Converter: print(f"Could not load converter for {module_name}: {e}") return converters - def convert_line(self, line: str, names: Optional[Dict[str, int]] = None, skip_exception: bool = True - ) -> Optional[Dict[str, Any]]: + def convert_line(self, line: str, names: dict[str, int] | None = None, skip_exception: bool = True, + logger: Any = None) -> dict[str, Any] | None: """ Parses a single subscription link and converts it to a proxy dictionary. """ @@ -73,12 +73,15 @@ class Converter: try: return converter.convert(line, names) except Exception as e: + if logger: + logger.error(f"Error converting line {line}: {e}") if not skip_exception: raise ValueError(f"{scheme.upper()} parse error: {e}") from e return None return None - def convert_v2ray(self, v2ray_link: Union[list, bytes], skip_exception: bool = True) -> List[Dict[str, Any]]: + def convert_v2ray(self, v2ray_link: Union[list, bytes], skip_exception: bool = True, + logger: Any = None) -> dict[str, dict[str, Any]]: """ Converts a base64 encoded V2Ray subscription content or a list of links into a list of proxy dictionaries. @@ -89,15 +92,15 @@ class Converter: else: lines = v2ray_link - proxies = [] + proxies: dict[str, dict[str, Any]] = {} names = {} for line in lines: line = line.strip() if not line: continue - proxy = self.convert_line(line, names, skip_exception=skip_exception) + proxy = self.convert_line(line, names, skip_exception=skip_exception, logger=logger) if proxy: - proxies.append(proxy) + proxies[line] = proxy elif not skip_exception: raise ValueError("Failed to convert one of the links in the subscription.") return proxies diff --git a/plugins.v2/clashruleprovider/helper/converters/hysteria.py b/plugins.v2/clashruleprovider/helper/converters/hysteria.py index b813dd2..065fe0f 100644 --- a/plugins.v2/clashruleprovider/helper/converters/hysteria.py +++ b/plugins.v2/clashruleprovider/helper/converters/hysteria.py @@ -17,6 +17,7 @@ class HysteriaConverter(BaseConverter): "type": "hysteria", "server": parsed.hostname, "port": parsed.port, + "udp": True } auth_str = query.get("auth") diff --git a/plugins.v2/clashruleprovider/helper/converters/hysteria2.py b/plugins.v2/clashruleprovider/helper/converters/hysteria2.py index c19a5a3..b4b7c03 100644 --- a/plugins.v2/clashruleprovider/helper/converters/hysteria2.py +++ b/plugins.v2/clashruleprovider/helper/converters/hysteria2.py @@ -34,6 +34,7 @@ class Hysteria2Converter(BaseConverter): "skip-cert-verify": StringUtils.to_bool(query.get("insecure", "false")), "down": query.get("down"), "up": query.get("up"), + "udp": True } if "pinSHA256" in query: proxy["fingerprint"] = query.get("pinSHA256") diff --git a/plugins.v2/clashruleprovider/helper/dataupgrader/upgrader_2_1_0.py b/plugins.v2/clashruleprovider/helper/dataupgrader/upgrader_2_1_0.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins.v2/clashruleprovider/helper/dataupgrader/v_2_1_0.py b/plugins.v2/clashruleprovider/helper/dataupgrader/v_2_1_0.py new file mode 100644 index 0000000..874378b --- /dev/null +++ b/plugins.v2/clashruleprovider/helper/dataupgrader/v_2_1_0.py @@ -0,0 +1,114 @@ +import copy +import time +from typing import Any + +import jsonpatch +from pydantic import ValidationError + +from app.db.plugindata_oper import PluginDataOper + +from ..configconverter import Converter +from ..utilsprovider import UtilsProvider +from ...models.proxygroups import ProxyGroupData +from ...models.proxy import Proxy, ProxyData +from ...models.ruleproviders import RuleProviderData +from ...models.types import DataSource, DataKey +from ...models.datapatch import PatchItem +from ...models.metadata import Metadata + + +def _overwrite_proxy(proxy: dict[str, Any], overwritten_proxies: dict[str, Any]) -> dict[str, Any]: + if proxy["name"] in overwritten_proxies: + for key in ['base', 'tls', 'network']: + if overlay := overwritten_proxies[proxy["name"]].get(key): + proxy.update(copy.deepcopy(overlay)) + return proxy + + +def upgrade(plugin_id: str): + data_oper = PluginDataOper() + + # Upgrade proxy groups + proxy_groups = data_oper.get_data(plugin_id, "proxy_groups") or [] + new_pg, invalid_pg, names = [], [], set() + + for pg in proxy_groups: + try: + obj = ProxyGroupData(meta=Metadata(source=DataSource.MANUAL), data=pg, name=pg["name"]) + if obj.name not in names: + new_pg.append(obj.model_dump(by_alias=True, exclude_none=True)) + names.add(obj.name) + except ValidationError: + invalid_pg.append(pg) + + data_oper.save(plugin_id, DataKey.PROXY_GROUPS, new_pg) + data_oper.save(plugin_id, "proxy_groups", invalid_pg) + + # Upgrade rule providers + rule_providers = data_oper.get_data(plugin_id, "extra_rule_providers") or {} + new_rp, invalid_rp = [], [] + + for name, rp in rule_providers.items(): + try: + obj = RuleProviderData(meta=Metadata(source=DataSource.MANUAL), name=name, data=rp) + new_rp.append(obj.model_dump(by_alias=True, exclude_none=True)) + except ValidationError: + invalid_rp.append(rp) + + data_oper.save(plugin_id, DataKey.RULE_PROVIDERS, new_rp) + data_oper.save(plugin_id, "extra_rule_providers", invalid_rp) + + # Upgrade proxies + proxies = data_oper.get_data(plugin_id, DataKey.PROXIES) or [] + new_proxies, invalid_proxies = [], [] + all_proxies = [] + names = set() + converter = Converter() + + for proxy in proxies: + try: + raw = None + if isinstance(proxy, str): + proxy_dict, raw = converter.convert_line(proxy), proxy + elif isinstance(proxy, dict): + proxy_dict = UtilsProvider.filter_empty(proxy, empty=['', None]) + else: + continue + + obj = Proxy.model_validate(proxy_dict) + if obj.name in names: continue + + p_data = ProxyData(data=obj, name=obj.name, meta=Metadata(source=DataSource.MANUAL), raw=raw) + new_proxies.append(p_data.model_dump(by_alias=True, exclude_none=True)) + all_proxies.append(p_data.data) + names.add(p_data.name) + except Exception: + invalid_proxies.append(proxy) + + data_oper.save(plugin_id, DataKey.PROXIES, new_proxies) + data_oper.save(plugin_id, "extra_proxies", invalid_proxies) + + # Create proxy patches + data_patch = {} + overwritten = data_oper.get_data(plugin_id, "overwritten_proxies") or {} + for name in overwritten: + if proxy := next((p for p in all_proxies if p.name == name), None): + src = proxy.model_dump(by_alias=True) + # Create a deep copy for dst to avoid modifying src in place if _overwrite_proxy mutates + dst = _overwrite_proxy(copy.deepcopy(src), overwritten) + if patch := jsonpatch.make_patch(src, dst).to_string(): + data_patch[name] = PatchItem(patch=patch).model_dump(by_alias=True, exclude_none=True) + + data_oper.save(plugin_id, DataKey.PROXY_PATCH, data_patch) + data_oper.save(plugin_id, DataKey.ACL4SSR, []) + + # Upgrade rules + for key in [DataKey.TOP_RULES, DataKey.RULESET_RULES]: + if rules := data_oper.get_data(plugin_id, key): + for rule in rules: + rule["meta"] = Metadata( + source=rule.get("remark") or DataSource.MANUAL, + time_modified=rule.get("time_modified") or time.time() + ).model_dump() + data_oper.save(plugin_id, key, rules) + data_oper.save(plugin_id, DataKey.DATA_VERSION, "2.1.0") diff --git a/plugins.v2/clashruleprovider/helper/proxiesmanager.py b/plugins.v2/clashruleprovider/helper/proxiesmanager.py index 841847f..d6289c8 100644 --- a/plugins.v2/clashruleprovider/helper/proxiesmanager.py +++ b/plugins.v2/clashruleprovider/helper/proxiesmanager.py @@ -1,105 +1,110 @@ import copy -from dataclasses import dataclass -from typing import Callable, Dict, List, Optional, Union, Any, Iterator +from typing import Callable, Dict, List, Optional, Any, Iterator, Union -from ..models.proxy import Proxy, ProxyType +from ..models.metadata import Metadata +from ..models.types import DataSource +from ..models.proxy import Proxy, ProxyData -@dataclass -class ProxyItem: - proxy: ProxyType - remark: str = "" - raw: Optional[Union[str, Dict[str, Any]]] = None - class ProxyManager: """Proxy Manager""" def __init__(self): - self.proxies: Dict[str,ProxyItem] = {} + self._proxies: Dict[str, ProxyData] = {} - def add(self, proxy: ProxyType, remark: str = "", raw: Optional[str|Dict[str, Any]] = None): + def add(self, proxy: Proxy, source: DataSource, remark: str = "", raw: str | dict[str, Any] | None = None): """Add a proxy to the proxy manager. """ - if proxy.name not in self.proxies: - self.proxies[proxy.name] = ProxyItem(proxy, remark, raw=copy.deepcopy(raw)) + if proxy.name not in self._proxies: + meta = Metadata(source=source, remark=remark) + self._proxies[proxy.name] = ProxyData(data=proxy, name=proxy.name, meta=meta, raw=copy.deepcopy(raw)) else: raise ValueError(f"Proxy with name {proxy.name!r} already exists.") - def add_proxy_dict(self, proxy_dict: Dict[str, Any], remark: str = "", raw: Optional[str] = None): + def update(self, name: str, proxy: Proxy): + if name not in self._proxies: + raise ValueError(f"Key '{name}' not found") + src = self._proxies[name] + src.data = proxy + + def add_proxy_data(self, proxy_data: dict[str, Any]): + pd = ProxyData.model_validate(proxy_data) + if pd.data.name not in self._proxies: + self._proxies[pd.data.name] = pd + else: + raise ValueError(f"Proxy with name {pd.data.name!r} already exists.") + + def add_proxy_dict(self, proxy_dict: Dict[str, Any], source: DataSource, remark: str = "", raw: str | None = None): """ Add a proxy to the proxies list. :param proxy_dict: Proxy dict with proxy name as key - :param remark: Proxy remark + :param source: Proxy source + :param remark: Remark :param raw: Proxy raw :raises: ValueError if proxy name already exists """ proxy = Proxy.model_validate(proxy_dict) raw = raw or proxy_dict - self.add(proxy.root, remark=remark, raw=raw) - - def add_from_list(self, proxies: List[Dict[str, Any]], remark: str = "", skip_existing: bool = False): - """Add proxies from the proxies list. """ - proxies_list = [] - for proxy in proxies: - p = Proxy.model_validate(proxy) - proxies_list.append(ProxyItem(p.root, remark, raw=proxy)) - - for proxy_item in proxies_list: - try: - self.add(proxy_item.proxy, remark=remark, raw=proxy_item.raw) - except ValueError: - if skip_existing: - continue - raise + self.add(proxy, source=source, remark=remark, raw=raw) def get_all_proxies(self) -> List[Dict[str, Any]]: proxies = [] - for proxy_item in self.proxies.values(): - proxy_dict = proxy_item.proxy.model_dump(by_alias=True, exclude_none=True) + for proxy_item in self._proxies.values(): + proxy_dict = proxy_item.data.model_dump(by_alias=True, exclude_none=True) proxies.append(proxy_dict) return proxies def remove_proxy(self, name): - if name in self.proxies: - del self.proxies[name] + if name in self._proxies: + del self._proxies[name] - def remove_proxies_by_condition(self, condition: Callable[[ProxyItem], bool]) -> int: + def remove_proxies_by_condition(self, condition: Callable[[ProxyData], bool]) -> int: """ Removes proxies from the manager based on a given condition. - :param condition: A callable that takes a ProxyItem and returns True if the proxy should be removed. + :param condition: A callable that takes a ProxyData and returns True if the proxy should be removed. :return: The number of proxies removed. """ - initial_count = len(self.proxies) - self.proxies = { + initial_count = len(self._proxies) + self._proxies = { name: item - for name, item in self.proxies.items() + for name, item in self._proxies.items() if not condition(item) } - return initial_count - len(self.proxies) + return initial_count - len(self._proxies) - def filter_proxies_by_condition(self, condition: Callable[[ProxyItem], bool]) -> List[ProxyItem]: - return [proxy for proxy in self.proxies.values() if condition(proxy)] + def filter_proxies_by_condition(self, condition: Callable[[ProxyData], bool]) -> List[ProxyData]: + return [proxy for proxy in self._proxies.values() if condition(proxy)] def clear(self): - self.proxies.clear() + self._proxies.clear() - def export_raw(self, condition: Optional[Callable[[ProxyItem], bool]] = None) -> List[str|Dict[str, Any]]: + def export_raw(self, condition: Optional[Callable[[ProxyData], bool]] = None) -> List[Union[str, Dict[str, Any]]]: proxies = [] - for proxy in self.proxies.values(): + for proxy in self._proxies.values(): if condition and not condition(proxy): continue if proxy.raw: proxies.append(copy.deepcopy(proxy.raw)) else: - proxies.append(proxy.proxy.model_dump(by_alias=True, exclude_none=True)) + proxies.append(proxy.data.model_dump(by_alias=True, exclude_none=True)) return proxies def proxy_names(self) -> Iterator[str]: - return iter(self.proxies) + return iter(self._proxies) + + def set_proxy_meta(self, name: str, meta: Metadata): + if name not in self._proxies: + raise ValueError(f"Key '{name}' not found") + self._proxies[name].meta = meta def __len__(self) -> int: - return len(self.proxies) + return len(self._proxies) - def __iter__(self) -> Iterator[ProxyItem]: - return iter(self.proxies.values()) + def __iter__(self) -> Iterator[ProxyData]: + return iter(self._proxies.values()) def __contains__(self, name: str) -> bool: - return name in self.proxies + return name in self._proxies + + def __getitem__(self, name: str) -> ProxyData: + if name not in self._proxies: + raise KeyError(f"Key '{name}' not found") + return self._proxies[name] diff --git a/plugins.v2/clashruleprovider/models/__init__.py b/plugins.v2/clashruleprovider/models/__init__.py index 54aa114..30241d7 100644 --- a/plugins.v2/clashruleprovider/models/__init__.py +++ b/plugins.v2/clashruleprovider/models/__init__.py @@ -1,3 +1,6 @@ from .proxy import * +from .hosts import * +from .ruleitem import * from .ruleproviders import * from .proxygroups import * +from .proxyproviders import * diff --git a/plugins.v2/clashruleprovider/models/api.py b/plugins.v2/clashruleprovider/models/api.py index 9e09f28..5b77ee3 100644 --- a/plugins.v2/clashruleprovider/models/api.py +++ b/plugins.v2/clashruleprovider/models/api.py @@ -1,48 +1,71 @@ -from typing import List, Optional, Union, Literal +from typing import List -from pydantic import BaseModel, Field, ConfigDict +from pydantic import BaseModel, Field, RootModel +from simpleeval import simple_eval -from .rule import RoutingRuleType, Action, AdditionalParam -from .ruleproviders import RuleProvider - -class RuleData(BaseModel): - priority: int - type: RoutingRuleType - payload: Optional[str] = None - action: Union['Action', str] - additional_params: Optional[AdditionalParam] = None - conditions: Optional[List[str]] = None - condition: Optional[str] = None - - model_config = ConfigDict( - use_enum_values=True - ) class ClashApi(BaseModel): url: str secret: str + class Connectivity(BaseModel): clash_apis: List[ClashApi] = Field(default_factory=list) sub_links: List[str] = Field(default_factory=list) -class Subscription(BaseModel): + +class SubscriptionSetting(BaseModel): url: str + enabled: bool -class RuleProviderData(BaseModel): - name: str - rule_provider: RuleProvider -class SubscriptionInfo(BaseModel): +class DataUsage(BaseModel): + upload: int = 0 + download: int = 0 + total: int = 0 + expire: int = 0 + + @property + def header(self) -> str: + return f'upload={self.upload}; download={self.download}; total={self.total}; expire={self.expire};' + + +class SubscriptionInfo(DataUsage): + last_update: int = Field(default=0) + proxy_num: int = Field(default=0) + enabled: bool = True + + def update(self, setting: SubscriptionSetting): + self.enabled = setting.enabled + + +class SubscriptionsInfo(RootModel[dict[str, SubscriptionInfo]]): + root: dict[str, SubscriptionInfo] = Field(default_factory=dict) + + def update(self, urls: list[str]): + if not urls: + return + + self.root.clear() + for url in urls: + self.root[url] = self.root.get(url, SubscriptionInfo()) + + def get(self, url: str) -> SubscriptionInfo: + return self.root.get(url, SubscriptionInfo()) + + def __setitem__(self, key: str, value: SubscriptionInfo): + self.root[key] = value + + def set(self, setting: SubscriptionSetting): + if setting.url in self.root: + self.root[setting.url].update(setting) + + +class ConfigRequest(BaseModel): url: str - field: Literal['name', 'enabled'] - value: Union[bool, str] + client_host: str + identifier: str | None = None + user_agent : str | None = None -class Host(BaseModel): - domain: str - value: List[str] - using_cloudflare: bool - -class HostData(BaseModel): - domain: str - value: Optional[Host] = None + def resolve(self, expr) -> bool: + return bool(simple_eval(expr=expr, names=self.model_dump())) diff --git a/plugins.v2/clashruleprovider/models/configuration.py b/plugins.v2/clashruleprovider/models/configuration.py new file mode 100644 index 0000000..66738be --- /dev/null +++ b/plugins.v2/clashruleprovider/models/configuration.py @@ -0,0 +1,226 @@ +from typing import Any, Literal + +from pydantic import BaseModel, Field, model_validator, field_validator, field_serializer, PrivateAttr + +from app.log import logger + +from .proxy import Proxy +from .proxygroups import ProxyGroup +from .proxyproviders import ProxyProvider +from .proxy.tlsmixin import ClientFingerprint +from .ruleproviders import RuleProvider +from .rule import RuleType, Action, RoutingRuleType +from ..helper.clashruleparser import ClashRuleParser + + +class ExternalControllerCors(BaseModel): + allow_origins: list[str] = Field(default_factory=lambda: ["*"], alias="allow-origins") + allow_credentials: bool = Field(default=True, alias="allow-credentials") + + +class Profile(BaseModel): + store_selected: bool = Field(default=False, alias="store-selected") + store_fake_ip: bool = Field(default=False, alias="store-fake-ip") + + +class NTP(BaseModel): + enable: bool = Field(default=False) + Server: str = Field(default="time.apple.com") + port: int = Field(default=123) + write_to_system: bool = Field(default=False, alias="write-to-system") + + +class Experimental(BaseModel): + quic_go_disable_gso: bool = Field(default=False, alias="quic-go-disable-gso") + quic_go_disable_ecn: bool = Field(default=True, alias="quic-go-disable-ecn") + dialer_ip4p_convert: bool = Field(default=False, alias="dialer-ip4p-convert") + + +class ClashConfig(BaseModel): + _raw_proxies: dict[str, str] = PrivateAttr(default_factory=dict) + + dns: dict[str, Any] | None = Field(default=None) + hosts: dict[str, list[str] | str] | None = Field(default=None) + allow_lan: bool | None = Field(default=None, alias="allow-lan") + bind_address: str = Field(default="*", alias="bind-address") + lan_allowed_ips: list[str] = Field(default_factory=lambda: ["0.0.0.0/0", "::/0"], alias="lan-allowed-ips") + lan_disallowed_ips: list[str] = Field(default_factory=list, alias="lan-disallowed-ips") + authentication: list[str] = Field(default_factory=list) + skip_auth_prefixes: list[str] = Field(default_factory=list, alias="skip-auth-prefixes") + mode: Literal["rule", "global", "direct"] = Field(default="rule") + log_level: Literal["silent", "error", "warning", "info", "debug"] = Field(default="info", alias="log-level") + ipv6: bool = Field(default=True) + keep_alive_interval: int = Field(default=0, alias="keep-alive-interval") + keep_alive_idle: int = Field(default=0, alias="keep-alive-idle") + disable_keep_alive: bool = Field(default=False, alias="disable-keep-alive") + find_process_mode: Literal["strict", "always", "off"] = Field(default="strict", alias="find-process-mode") + external_controller: str | None = Field(default=None, alias="external-controller") + external_controller_cors: ExternalControllerCors = Field(default_factory=ExternalControllerCors, + alias="external-controller-cors") + external_controller_unix: str | None = Field(default=None, alias="external-controller-unix") + external_controller_pipe: str | None = Field(default=None, alias="external-controller-pipe") + external_controller_tls: str | None = Field(default=None, alias="external-controller-tls") + secret: str | None = Field(default=None) + external_ui: str | None = Field(default=None, alias="external-ui") + external_ui_name: str | None = Field(default=None, alias="external-ui-name") + external_ui_url: str | None = Field(default=None, alias="external-ui-url") + profile: Profile = Field(default_factory=Profile) + unified_delay: bool = Field(default=True, alias="unified-delay") + tcp_concurrent: bool = Field(default=True, alias="tcp-concurrent") + interface_name: str | None = Field(default=None, alias="interface-name") + routing_mark: int | None = Field(default=None, alias="routing-mark") + tls: dict[str, Any] | None = Field(default=None, alias="tls") + global_client_fingerprint: ClientFingerprint | None = Field(default=ClientFingerprint.chrome, + alias="global-client-fingerprint") + geodata_mode: bool | None = Field(default=None, alias="geodata-mode") + geodata_loader: Literal["memconservative", "standard"] = Field(default="memconservative", alias="geodata-loader") + geo_auto_update: bool = Field(default=False, alias="geo-auto-update") + geo_update_interval: int = Field(default=24, alias="geo-update-interval") + global_ua: str = Field(default="clash.meta", alias="global-ua") + etag_support: bool = Field(default=True, alias="etag-support") + sniffer: dict[str, Any] | None = None + listeners: list[dict[str, Any]] | None = Field(default=None) + port: int = Field(default=0, description="HTTP(S) proxy port") + socks_port: int = Field(default=0, alias="socks-port") + mixed_port: int = Field(default=0, alias="mixed-port") + redir_port: int = Field(default=0, alias="redir-port") + tproxy_port: int = Field(default=0, alias="tproxy-port") + tun: dict[str, Any] | None = Field(default=None) + sub_rules: dict[str, Any] | None = Field(default=None, alias="sub-rules") + tunnels: list[dict[str, Any] | str] | None = Field(default=None) + ntp: NTP | None = Field(default=None) + experimental: Experimental | None = Field(default=None) + proxies: list[Proxy] = Field(default_factory=list) + proxy_providers: dict[str, ProxyProvider] = Field(default_factory=dict, alias="proxy-providers") + proxy_groups: list[ProxyGroup] = Field(default_factory=list, alias="proxy-groups") + rules: list[RuleType] = Field(default_factory=list) + rule_providers: dict[str, RuleProvider] = Field(default_factory=dict, alias="rule-providers") + + @model_validator(mode="before") + @classmethod + def fill_none_with_default(cls, values: dict): + fill_none_fields = {"proxies", "proxy_providers", "proxy_groups", "rules", "rule_providers"} + for field_name in fill_none_fields: + field = cls.model_fields[field_name] + factory = field.default_factory + if not factory: + continue + keys = {field_name} + if field.alias: + keys.add(field.alias) + + for key in keys: + if key in values and values[key] is None: + values[key] = factory() + return values + + @field_serializer("proxies") + def serialize_proxies(self, v: list[Proxy], _info): + serialized_proxies = [] + seen_names = set() + for proxy in v: + if proxy.name in seen_names: + logger.warning(f"Skipping duplicate proxy: {proxy.name}") + continue + seen_names.add(proxy.name) + serialized_proxies.append(proxy.model_dump(by_alias=True, exclude_none=True, mode="json")) + return serialized_proxies + + @field_serializer("proxy_groups") + def serialize_proxy_groups(self, v: list[ProxyGroup], _info): + valid_outbounds = {a.value for a in Action} + valid_outbounds.add("GLOBAL") + if self.proxies: + valid_outbounds.update(p.name for p in self.proxies) + if v: + valid_outbounds.update(pg.name for pg in v) + + serialized_groups = [] + seen_names = set() + for group in v: + if group.name in seen_names: + logger.warning(f"Skipping duplicate proxy group: {group.name}") + continue + seen_names.add(group.name) + + group_data = group.model_dump(by_alias=True, exclude_none=True, mode="json") + if "proxies" in group_data and group_data["proxies"]: + original_proxies = group_data["proxies"] + group_data["proxies"] = [ + p for p in original_proxies if p in valid_outbounds + ] + removed = set(original_proxies) - set(group_data["proxies"]) + if removed: + logger.warning(f"Proxy group {group.name} removed missing proxies: {removed}") + serialized_groups.append(group_data) + + return serialized_groups + + @field_validator("rules", mode="before") + @classmethod + def validate_rules(cls, v): + if isinstance(v, list): + rules = [] + for item in v: + if isinstance(item, str): + rules.append(ClashRuleParser.parse(item)) + else: + rules.append(item) + return rules + return v + + @field_serializer("rules") + def serialize_rules(self, v: list[RuleType], _info): + valid_rules = [] + valid_outbounds = set(self.outbounds) + valid_actions = {a.value for a in Action} + + for rule in v: + if rule.rule_type == RoutingRuleType.SUB_RULE: + if self.sub_rules and rule.action in self.sub_rules: + valid_rules.append(rule) + else: + logger.warning(f"Skipping rule with missing sub-rule action: {rule}") + continue + + if rule.rule_type == RoutingRuleType.RULE_SET: + if rule.payload not in self.rule_providers: + logger.warning(f"Skipping rule with missing rule-provider: {rule}") + continue + + action_str = str(rule.action) + if action_str in valid_actions or action_str in valid_outbounds: + valid_rules.append(rule) + else: + logger.warning(f"Skipping rule with invalid outbound: {rule}") + + return [str(rule) for rule in valid_rules] + + @property + def outbounds(self) -> list[str]: + outbounds = [] + if self.proxies: + outbounds.extend(p.name for p in self.proxies) + if self.proxy_groups: + outbounds.extend(pg.name for pg in self.proxy_groups) + return outbounds + + @property + def node_num(self) -> int: + return len(self.proxies) + + @property + def raw_proxies(self) -> dict[str, str]: + return self._raw_proxies + + @raw_proxies.setter + def raw_proxies(self, value: dict[str, str]): + self._raw_proxies = value + + def merge(self, other: 'ClashConfig') -> 'ClashConfig': + self.proxies += other.proxies + self.proxy_groups += other.proxy_groups + self.rules += other.rules + self.rule_providers |= other.rule_providers + self.proxy_providers |= other.proxy_providers + return self diff --git a/plugins.v2/clashruleprovider/models/datamodel.py b/plugins.v2/clashruleprovider/models/datamodel.py new file mode 100644 index 0000000..819c6da --- /dev/null +++ b/plugins.v2/clashruleprovider/models/datamodel.py @@ -0,0 +1,31 @@ +from pydantic import BaseModel, Field + +from .api import SubscriptionsInfo +from .configuration import ClashConfig +from .datapatch import DataPatch +from .hosts import Hosts +from .proxy import Proxies +from .proxygroups import ProxyGroups +from .ruleproviders import RuleProviders, RuleProvider +from .types import DataKey + + +class GeoRules(BaseModel): + geoip: list[str] = Field(default_factory=list) + geosite: list[str] = Field(default_factory=list) + + +class PersistState(BaseModel): + proxies: Proxies = Field(alias=DataKey.PROXIES, default_factory=Proxies) + proxy_groups: ProxyGroups = Field(alias=DataKey.PROXY_GROUPS, default_factory=ProxyGroups) + subscription_info: SubscriptionsInfo = Field(alias=DataKey.SUB_INFO, default_factory=SubscriptionsInfo) + rule_provider: dict[str, RuleProvider] = Field(alias=DataKey.AUTO_RULE_PROVIDERS, default_factory=dict) + rule_providers: RuleProviders = Field(alias=DataKey.RULE_PROVIDERS, default_factory=RuleProviders) + ruleset_names: dict[str, str] = Field(alias=DataKey.RULESET_NAMES, default_factory=dict) + acl4ssr_providers: RuleProviders = Field(alias=DataKey.ACL4SSR, default_factory=RuleProviders) + sub_configs: dict[str, ClashConfig] = Field(alias=DataKey.SUB_CONFIGS, default_factory=dict) + hosts: Hosts = Field(alias=DataKey.HOSTS, default_factory=Hosts) + proxy_group_patch: DataPatch = Field(alias=DataKey.PROXY_GROUP_PATCH, default_factory=DataPatch) + proxy_patch: DataPatch = Field(alias=DataKey.PROXY_PATCH, default_factory=DataPatch) + geo_rules: GeoRules = Field(alias=DataKey.GEO_RULES, default_factory=GeoRules) + rule_provider_patch: DataPatch = Field(alias=DataKey.RULE_PROVIDER_PATCH, default_factory=DataPatch) diff --git a/plugins.v2/clashruleprovider/models/datapatch.py b/plugins.v2/clashruleprovider/models/datapatch.py new file mode 100644 index 0000000..a84dc57 --- /dev/null +++ b/plugins.v2/clashruleprovider/models/datapatch.py @@ -0,0 +1,32 @@ +from pydantic import BaseModel, Field, RootModel + + +class PatchItem(BaseModel): + lifecycle: int = Field(default=3) + patch: str + + +class DataPatch(RootModel[dict[str, PatchItem]]): + """DataPatch model for storing patch items.""" + root: dict[str, PatchItem] = Field(default_factory=dict, description="Dictionary of patch items.") + + def update_patch(self, alive_keys: list[str] | set[str], lifespan: int = 3): + outdated_keys = [] + for key in list(self.root.keys()): + if key not in alive_keys: + self.root[key].lifecycle -= 1 + if self.root[key].lifecycle == 0: + outdated_keys.append(key) + else: + self.root[key].lifecycle = lifespan + for key in outdated_keys: + del self.root[key] + + def __setitem__(self, key: str, value: PatchItem): + self.root[key] = value + + def __contains__(self, key: str) -> bool: + return key in self.root + + def __getitem__(self, key: str) -> PatchItem: + return self.root[key] diff --git a/plugins.v2/clashruleprovider/models/generics.py b/plugins.v2/clashruleprovider/models/generics.py new file mode 100644 index 0000000..426fb8c --- /dev/null +++ b/plugins.v2/clashruleprovider/models/generics.py @@ -0,0 +1,93 @@ +from typing import TypeVar, Generic, Iterator, Any +from pydantic import BaseModel, RootModel, Field, model_validator +from .metadata import Metadata + + +# Specific data payload model +T = TypeVar("T") + +class ResourceItem(BaseModel, Generic[T]): + """Generic resource item model""" + name: str = Field(..., description="Resource name") + data: T = Field(..., description="Resource data payload") + meta: Metadata = Field(default_factory=Metadata, description="Resource metadata") + + +# Subclasses of ResourceItem +R = TypeVar("R", bound=ResourceItem) + +class ResourceList(RootModel[list[R]], Generic[R]): + """ + Generic configuration list base class + """ + root: list[R] = Field(default_factory=list) + + @model_validator(mode='after') + def validate_unique_names(self) -> 'ResourceList[R]': + names = [item.name for item in self.root] + if len(names) != len(set(names)): + raise ValueError("names must be unique") + return self + + def __iter__(self) -> Iterator[R]: + return iter(self.root) + + def __len__(self) -> int: + return len(self.root) + + def __contains__(self, name: str) -> bool: + """Check if a configuration with the specified name exists""" + return any(item.name == name for item in self.root) + + def get(self, name: str) -> R | None: + """Get the configuration item by name""" + for item in self.root: + if item.name == name: + return item + return None + + def add(self, item: R): + """Add a configuration item, raise an exception if the name is duplicated""" + if item.name in self: + raise ValueError(f"name {item.name!r} already exists") + self.root.insert(0, item) + + def remove(self, name: str): + """Remove the configuration item by name""" + self.root = [item for item in self.root if item.name != name] + + def pop(self, name: str) -> R | None: + """Remove and return the configuration item with the specified name""" + for i, item in enumerate(self.root) : + if item.name == name: + return self.root.pop(i) + return None + + def update(self, name: str, item: R): + """Update the configuration item with the specified name""" + for i, existing_item in enumerate(self.root): + if existing_item.name == name: + item.meta = self.root[i].meta + self.root[i] = item + return + + def update_data(self, name: str, data: Any) -> bool: + """Update only the data payload of the configuration item with the specified name""" + item = self.get(name) + if item: + item.data = data + return True + return False + + def set_meta(self, name: str, meta: Metadata) -> bool: + """Set metadata for the specified configuration item""" + item = self.get(name) + if item: + item.meta = meta + return True + return False + + @property + def names(self) -> list[str]: + """Return a list of names for all configuration items""" + return [item.name for item in self.root] diff --git a/plugins.v2/clashruleprovider/models/hosts.py b/plugins.v2/clashruleprovider/models/hosts.py new file mode 100644 index 0000000..a27453d --- /dev/null +++ b/plugins.v2/clashruleprovider/models/hosts.py @@ -0,0 +1,33 @@ +from pydantic import Field, RootModel, BaseModel + +from .metadata import Metadata + + +class HostData(BaseModel): + domain: str + value: list[str] + using_cloudflare: bool + meta: Metadata = Field(default_factory=Metadata) + + +class Hosts(RootModel[list[HostData]]): + root: list[HostData] = Field(default_factory=list) + + def __len__(self) -> int: + return len(self.root) + + def update(self, domain: str, data: HostData): + self.root = [host for host in self.root if host.domain != domain] + self.root.append(data) + + def delete(self, domain: str): + self.root = [host for host in self.root if host.domain != domain] + + def to_dict(self, cloudflare: list[str]) -> dict[str, list[str]]: + hosts = {} + for host in self.root: + if host.using_cloudflare: + hosts[host.domain] = cloudflare + else: + hosts[host.domain] = host.value + return hosts diff --git a/plugins.v2/clashruleprovider/models/metadata.py b/plugins.v2/clashruleprovider/models/metadata.py new file mode 100644 index 0000000..c26d1c3 --- /dev/null +++ b/plugins.v2/clashruleprovider/models/metadata.py @@ -0,0 +1,25 @@ +import time + +from pydantic import BaseModel, Field + +from .api import ConfigRequest +from .types import DataSource + + +class Metadata(BaseModel): + """Metadata model for Clash items""" + # source of the item + source: DataSource = Field(default=DataSource.MANUAL) + # whether the item is disabled + disabled: bool = Field(default=False) + # roles that cannot see the item + invisible_to: list[str] = Field(default_factory=list) + # additional remarks + remark: str = Field(default="") + # last modified time + time_modified: float = Field(default_factory=lambda: time.time()) + # whether the item has been patched + patched: bool = Field(default=False) + + def available(self, param: ConfigRequest | None = None) -> bool: + return not self.disabled and (param is None or not any(param.resolve(expr) for expr in self.invisible_to)) diff --git a/plugins.v2/clashruleprovider/models/proxy/__init__.py b/plugins.v2/clashruleprovider/models/proxy/__init__.py index d2cce51..ccf7ae6 100644 --- a/plugins.v2/clashruleprovider/models/proxy/__init__.py +++ b/plugins.v2/clashruleprovider/models/proxy/__init__.py @@ -1,6 +1,7 @@ -from typing import Union +import jsonpatch +from typing import Union, Any -from pydantic import Field, RootModel +from pydantic import Field, RootModel, model_validator from .anytlsproxy import AnyTLSProxy from .directproxy import DirectProxy @@ -22,6 +23,7 @@ from .tuicproxy import TuicProxy from .vlessproxy import VlessProxy from .vmessproxy import VmessProxy from .wireguardproxy import WireGuardProxy +from ..generics import ResourceItem, ResourceList ProxyType = Union[ AnyTLSProxy, @@ -46,3 +48,31 @@ ProxyType = Union[ class Proxy(RootModel[ProxyType]): root: ProxyType = Field(..., discriminator="type") + + @property + def name(self) -> str: + return self.root.name + + def __getattr__(self, item): + return getattr(self.root, item) + + def patch(self, patch: str) -> 'Proxy': + src = self.model_dump(mode='json', by_alias=True) + patched = jsonpatch.apply_patch(src, patch=patch, in_place=True) + return Proxy.model_validate(patched) + + +class ProxyData(ResourceItem[Proxy]): + raw: Union[str, dict[str, Any], None] = None + v2ray_link: str | None = None + + @model_validator(mode="after") + def validate_name_consistency(self): + if self.name != self.data.name: + raise ValueError(f"name ({self.name}) must equal data.name ({self.data.name})") + return self + + +class Proxies(ResourceList[ProxyData]): + """Proxies Collection""" + pass diff --git a/plugins.v2/clashruleprovider/models/proxy/hysteria2proxy.py b/plugins.v2/clashruleprovider/models/proxy/hysteria2proxy.py index 309ee28..0f8e9ef 100644 --- a/plugins.v2/clashruleprovider/models/proxy/hysteria2proxy.py +++ b/plugins.v2/clashruleprovider/models/proxy/hysteria2proxy.py @@ -10,8 +10,8 @@ class Hysteria2Proxy(ProxyBase): password: Optional[str] = None obfs: Optional[Literal['salamander']] = None obfs_password: Optional[str] = Field(None, alias='obfs-password') - up: Optional[str] = None - down: Optional[str] = None + up: Optional[int | str] = None + down: Optional[int | str] = None hop_interval: Optional[int] = Field(None, alias='hop-interval') ca: Optional[str] = None ca_str: Optional[str] = Field(None, alias='ca-str') diff --git a/plugins.v2/clashruleprovider/models/proxy/hysteriaproxy.py b/plugins.v2/clashruleprovider/models/proxy/hysteriaproxy.py index 1285061..b4f0b6c 100644 --- a/plugins.v2/clashruleprovider/models/proxy/hysteriaproxy.py +++ b/plugins.v2/clashruleprovider/models/proxy/hysteriaproxy.py @@ -11,10 +11,8 @@ class HysteriaProxy(ProxyBase): auth: Optional[str] = None protocol: Optional[Literal['udp','wechat-video', 'faketcp']] = None up: Optional[str] = None - down: Optional[str] = None - up_speed: Optional[int] = Field(None, alias='up-speed') - down_speed: Optional[int] = Field(None, alias='down-speed') - obfs: Optional[str] = None + down: Optional[int | str] = None + obfs: Optional[int | str] = None obfs_protocol: Optional[str] = Field(None, alias='obfs-protocol') recv_window_conn: Optional[int] = Field(None, alias='recv-window-conn') recv_window: Optional[int] = Field(None, alias='recv-window') diff --git a/plugins.v2/clashruleprovider/models/proxy/tlsmixin.py b/plugins.v2/clashruleprovider/models/proxy/tlsmixin.py index 5ed7d58..0612b8c 100644 --- a/plugins.v2/clashruleprovider/models/proxy/tlsmixin.py +++ b/plugins.v2/clashruleprovider/models/proxy/tlsmixin.py @@ -1,8 +1,21 @@ -from typing import List, Optional, Literal +from enum import StrEnum +from typing import List, Optional from pydantic import BaseModel, Field +class ClientFingerprint(StrEnum): + chrome = 'chrome' + firefox = 'firefox' + safari = 'safari' + ios = 'ios' + android = 'android' + edge = 'edge' + n360 = '360' + qq = 'qq' + random = 'random' + + class RealityOpts(BaseModel): public_key: str = Field(..., alias='public-key') short_id: Optional[str] = Field(None, alias='short-id') @@ -23,6 +36,6 @@ class TLSMixin(BaseModel): fingerprint: Optional[str] = None alpn: Optional[List[str]] = None skip_cert_verify: Optional[bool] = Field(None, alias='skip-cert-verify') - client_fingerprint: Optional[Literal['chrome', 'firefox', 'safari', 'ios', 'android', 'edge', '360', 'qq', 'random']] = Field(None, alias='client-fingerprint') + client_fingerprint: Optional[ClientFingerprint] = Field(None, alias='client-fingerprint') reality_opts: Optional[RealityOpts] = Field(None, alias='reality-opts') ech_opts: Optional[EchOpts] = Field(None, alias='ech-opts') diff --git a/plugins.v2/clashruleprovider/models/proxygroups.py b/plugins.v2/clashruleprovider/models/proxygroups.py index ecae1c9..c64970d 100644 --- a/plugins.v2/clashruleprovider/models/proxygroups.py +++ b/plugins.v2/clashruleprovider/models/proxygroups.py @@ -1,7 +1,10 @@ +import jsonpatch import re from typing import List, Optional, Union, Literal -from pydantic import BaseModel, Field, field_validator, RootModel +from pydantic import BaseModel, Field, field_validator, RootModel, model_validator + +from .generics import ResourceItem, ResourceList class ProxyGroupBase(BaseModel): @@ -12,44 +15,45 @@ class ProxyGroupBase(BaseModel): name: str = Field(..., description="The name of the proxy group.") # Proxy and provider references - proxies: Optional[List[str]] = Field(None, description="References to outbound proxies or other proxy groups.") - use: Optional[List[str]] = Field(None, description="References to proxy provider sets.") + proxies: Optional[List[str]] = Field(default=None, + description="References to outbound proxies or other proxy groups.") + use: Optional[List[str]] = Field(default=None, description="References to proxy provider sets.") # Health check fields - url: Optional[str] = Field(None, description="Health check test address.") - interval: Optional[int] = Field(None, description="Health check interval in seconds.") - lazy: Optional[bool] = Field(True, description="If not selected, no health checks are performed.") - timeout: Optional[int] = Field(None, description="Health check timeout in milliseconds.") - max_failed_times: Optional[int] = Field(5, description="Maximum number of failures before a forced health check.", - alias="max-failed-times") - expected_status: Optional[str] = Field('*', - description="Expected HTTP response status code for health checks.", - alias="expected-status") + url: Optional[str] = Field(default="https://www.gstatic.com/generate_204", description="Health check test address.") + interval: Optional[int] = Field(default=300, description="Health check interval in seconds.") + lazy: Optional[bool] = Field(default=True, description="If not selected, no health checks are performed.") + timeout: Optional[int] = Field(default=5000, description="Health check timeout in milliseconds.") + max_failed_times: Optional[int] = Field(default=5, alias="max-failed-times", + description="Maximum number of failures before a forced health check.") + expected_status: Optional[str] = Field(default='*', alias="expected-status", + description="Expected HTTP response status code for health checks.") # Network and routing fields - disable_udp: Optional[bool] = Field(False, description="Disables UDP for this proxy group.", alias="disable-udp") - interface_name: Optional[str] = Field(None, description="DEPRECATED. Specifies the outbound interface.", + disable_udp: Optional[bool] = Field(default=False, description="Disables UDP for this proxy group.", + alias="disable-udp") + interface_name: Optional[str] = Field(default=None, description="DEPRECATED. Specifies the outbound interface.", alias="interface-name") - routing_mark: Optional[int] = Field(None, description="DEPRECATED. The routing mark for outbound connections.", - alias="routing-mark") + routing_mark: Optional[int] = Field(default=None, alias="routing-mark", + description="DEPRECATED. The routing mark for outbound connections.") # Dynamic proxy inclusion - include_all: Optional[bool] = Field(False, description="Includes all outbound proxies and proxy sets.", + include_all: Optional[bool] = Field(default=False, description="Includes all outbound proxies and proxy sets.", alias="include-all") - include_all_proxies: Optional[bool] = Field(False, description="Includes all outbound proxies.", + include_all_proxies: Optional[bool] = Field(default=False, description="Includes all outbound proxies.", alias="include-all-proxies") - include_all_providers: Optional[bool] = Field(False, description="Includes all proxy provider sets.", + include_all_providers: Optional[bool] = Field(default=False, description="Includes all proxy provider sets.", alias="include-all-providers") # Filtering - filter: Optional[str] = Field(None, description="Regex to filter nodes from providers.") - exclude_filter: Optional[str] = Field(None, description="Regex to exclude nodes.", alias="exclude-filter") - exclude_type: Optional[str] = Field(None, description="Exclude nodes by adapter type, separated by '|'.", + filter: Optional[str] = Field(default=None, description="Regex to filter nodes from providers.") + exclude_filter: Optional[str] = Field(default=None, description="Regex to exclude nodes.", alias="exclude-filter") + exclude_type: Optional[str] = Field(default=None, description="Exclude nodes by adapter type, separated by '|'.", alias="exclude-type") # UI fields - hidden: Optional[bool] = Field(False, description="Hides the proxy group in the API.") - icon: Optional[str] = Field(None, description="Icon string for the proxy group, for UI use.") + hidden: Optional[bool] = Field(default=False, description="Hides the proxy group in the API.") + icon: Optional[str] = Field(default=None, description="Icon string for the proxy group, for UI use.") @field_validator('expected_status') @classmethod @@ -72,44 +76,50 @@ class ProxyGroupBase(BaseModel): class SelectGroup(ProxyGroupBase): - type: Literal['select'] + type: Literal['select'] = "select" class RelayGroup(ProxyGroupBase): - type: Literal['relay'] + type: Literal['relay'] = "relay" class FallbackGroup(ProxyGroupBase): - type: Literal['fallback'] + type: Literal['fallback'] = "fallback" class UrlTestGroup(ProxyGroupBase): - type: Literal['url-test'] - tolerance: Optional[int] = Field(None, description="proxies switch tolerance, measured in milliseconds (ms).") + type: Literal['url-test'] = "url-test" + tolerance: Optional[int] = Field(default=None, description="proxies switch tolerance, measured in milliseconds (ms).") class LoadBalanceGroup(ProxyGroupBase): - type: Literal['load-balance'] + type: Literal['load-balance'] = "load-balance" strategy: Optional[Literal['round-robin', 'consistent-hashing', 'sticky-sessions']] = Field( - 'round-robin', - description="Load balancing strategy." + default='round-robin', description="Load balancing strategy." ) class SmartGroup(ProxyGroupBase): - type: Literal['smart'] - uselightgbm: bool = Field(..., description="Use LightGBM model predict weight.") - collectdata: bool = Field(..., description="Collect datas for model training.") - policy_priority: Optional[str] = Field("1", + type: Literal['smart'] = "smart" + uselightgbm: bool = Field(default=False, description="Use LightGBM model predict weight.") + collectdata: bool = Field(default=False, description="Collect datas for model training.") + policy_priority: Optional[str] = Field(default=None, description="<1 means lower priority, >1 means higher priority, " "the default is 1, pattern support regex and string.", alias="policy-priority") strategy: Optional[Literal['round-robin', 'sticky-sessions']] = Field( - 'sticky-sessions', - description="Load balancing strategy." + default='sticky-sessions', description="Load balancing strategy." ) - sample_rate: Optional[int] = Field(1, description="Data acquisition rate.", alias="sample-rate") + sample_rate: Optional[int] = Field(default=1, description="Data acquisition rate.", alias="sample-rate") + @field_validator('policy_priority', mode='before') + @classmethod + def validate_policy_priority(cls, v): + if v is None or v == "": + return None + if not isinstance(v, str): + raise ValueError('policy_priority must be a string') + return v # Discriminated Union ProxyGroupType = Union[SelectGroup, RelayGroup, FallbackGroup, UrlTestGroup, LoadBalanceGroup, SmartGroup] @@ -117,3 +127,37 @@ ProxyGroupType = Union[SelectGroup, RelayGroup, FallbackGroup, UrlTestGroup, Loa class ProxyGroup(RootModel[ProxyGroupType]): root: ProxyGroupType = Field(..., discriminator='type') + + @property + def name(self) -> str: + return self.root.name + + @property + def proxies(self) -> list[str]: + if self.root.proxies: + return self.root.proxies + return [] + + def __getattr__(self, item): + return getattr(self.root, item) + + def patch(self, patch: str) -> 'ProxyGroup': + src = self.model_dump(mode="json", by_alias=True) + patched = jsonpatch.apply_patch(src, patch=patch, in_place=True) + return ProxyGroup.model_validate(patched) + + +class ProxyGroupData(ResourceItem[ProxyGroup]): + """Proxy Group Data""" + + @model_validator(mode="after") + def validate_name_consistency(self): + data_name = self.data.name + if self.name != data_name: + raise ValueError(f"name ({self.name}) must equal data.name ({data_name})") + return self + + +class ProxyGroups(ResourceList[ProxyGroupData]): + """Proxy Groups Collection""" + pass diff --git a/plugins.v2/clashruleprovider/models/proxyproviders.py b/plugins.v2/clashruleprovider/models/proxyproviders.py new file mode 100644 index 0000000..4aa47f0 --- /dev/null +++ b/plugins.v2/clashruleprovider/models/proxyproviders.py @@ -0,0 +1,130 @@ +from typing import Any, Optional + +from pydantic import BaseModel, Field, field_validator, ConfigDict + +from .generics import ResourceItem, ResourceList +from .types import VehicleType + + +class OverrideProxyName(BaseModel): + """代理名称覆盖配置""" + pattern: str | None = Field(None, description="正则表达式模式") + target: str = Field(..., description="替换目标") + + +class Override(BaseModel): + """代理配置覆盖""" + tfo: bool | None = Field(None, description="TCP Fast Open") + mptcp: bool | None = Field(None, description="Multipath TCP") + udp: bool | None = Field(None, description="UDP支持") + udp_over_tcp: bool | None = Field(None, alias="udp-over-tcp", description="UDP over TCP") + up: str | None = Field(None, description="上传速度限制") + dialer_proxy: str | None = Field(None, alias="dialer-proxy", description="拨号代理") + skip_cert_verify: bool | None = Field(None, alias="skip-cert-verify", description="跳过证书验证") + interface_name: Optional[str] = Field(None, alias="interface-name", description="网络接口名称") + routing_mark: int | None = Field(None, alias="routing-mark", description="路由标记") + ip_version: str | None = Field(None, alias="ip-version", description="IP版本偏好") + additional_prefix: str | None = Field(None, alias="additional-prefix", description="名称前缀") + additional_suffix: str | None = Field(None, alias="additional-suffix", description="名称后缀") + proxy_name: list[OverrideProxyName] | None = Field(None, alias="proxy-name", description="代理名称替换规则") + + +class HealthCheck(BaseModel): + """健康检查配置""" + enable: bool = Field(..., description="启用健康检查") + url: str = Field(..., description="健康检查URL") + interval: int = Field(300, description="检查间隔(秒)") + timeout: int | None = Field(None, description="超时时间(毫秒)") + lazy: bool = Field(True, description="懒加载模式") + expected_status: str | None = Field(None, alias="expected-status", description="期望的HTTP状态码") + + @field_validator('interval') + @classmethod + def validate_interval(cls, v): + if v <= 0: + raise ValueError("间隔时间必须大于0") + return v + + @field_validator('timeout') + @classmethod + def validate_timeout(cls, v): + if v is not None and v <= 0: + raise ValueError("超时时间必须大于0") + return v + + +class ProxyProvider(BaseModel): + """Proxy Provider""" + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + ) + + type: VehicleType = Field(..., description="Provider类型") + path: str | None = Field(default=None, description="本地文件路径") + url: str | None = Field(default=None, description="远程URL") + proxy: str | None = Field(default=None, description="使用的代理") + interval: int | None = Field(default=None, description="更新间隔(秒)") + filter: str | None = Field(default=None, description="过滤正则表达式") + exclude_filter: str | None = Field(default=None, alias="exclude-filter", description="排除过滤正则表达式") + exclude_type: str | None = Field(default=None, alias="exclude-type", description="排除的代理类型") + dialer_proxy: str | None = Field(default=None, alias="dialer-proxy", description="拨号代理") + size_limit: int | None = Field(default=None, alias="size-limit", description="文件大小限制(字节)") + payload: list[dict[str, Any]] | None = Field(default=None, description="内联代理配置") + health_check: HealthCheck | None = Field(default=None, alias="health-check", description="健康检查配置") + override: Override | None = Field(default=None, description="配置覆盖") + header: dict[str, list[str]] | None = Field(default=None, description="HTTP请求头") + + @field_validator('interval') + @classmethod + def validate_interval(cls, v): + if v is not None and v <= 0: + raise ValueError("间隔时间必须大于0") + return v + + @field_validator('size_limit') + @classmethod + def validate_size_limit(cls, v): + if v is not None and v < 0: + raise ValueError("文件大小限制不能为负数") + return v + + @field_validator('exclude_type') + @classmethod + def validate_exclude_type(cls, v): + if v is not None: + types = [t.strip() for t in v.split('|')] + if not all(types): + raise ValueError("排除类型不能为空") + return v + + @field_validator('url') + @classmethod + def validate_url_dependency(cls, v, info): + if info.data.get('type') == VehicleType.HTTP and not v: + raise ValueError("HTTP类型的provider必须提供URL") + return v + + @field_validator('path') + @classmethod + def validate_path_dependency(cls, v, info): + if info.data.get('type') == VehicleType.FILE and not v: + raise ValueError("FILE类型的provider必须提供路径") + return v + + @field_validator('payload') + @classmethod + def validate_payload_dependency(cls, v, info): + if info.data.get('type') == VehicleType.INLINE and not v: + raise ValueError("INLINE类型的provider必须提供payload") + return v + + +class ProxyProviderData(ResourceItem[ProxyProvider]): + """Proxy Provider Data""" + pass + + +class ProxyProviders(ResourceList[ProxyProviderData]): + """Proxy Provider Collection""" + pass diff --git a/plugins.v2/clashruleprovider/models/rule/__init__.py b/plugins.v2/clashruleprovider/models/rule/__init__.py index 9ca842c..62d22a4 100644 --- a/plugins.v2/clashruleprovider/models/rule/__init__.py +++ b/plugins.v2/clashruleprovider/models/rule/__init__.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import Enum, StrEnum from typing import Any, List, Optional, Union, Dict, Literal from pydantic import BaseModel, field_validator, ValidationInfo @@ -57,7 +57,7 @@ class RoutingRuleType(Enum): MATCH = "MATCH" -class Action(Enum): +class Action(StrEnum): """Enumeration of rule actions""" DIRECT = "DIRECT" REJECT = "REJECT" @@ -65,9 +65,6 @@ class Action(Enum): PASS = "PASS" COMPATIBLE = "COMPATIBLE" - def __str__(self) -> str: - return self.value - class RuleBase(BaseModel): rule_type: RoutingRuleType @@ -101,7 +98,7 @@ class ClashRule(RuleBase): 'payload': self.payload, 'action': self.action.value if isinstance(self.action, Action) else self.action, 'additional_params': self.additional_params.value if self.additional_params else None, - 'raw': self.raw_rule + 'rule_string': str(self) } def __str__(self) -> str: @@ -131,7 +128,7 @@ class LogicRule(RuleBase): return f"{self.rule_type.value},({conditions_str})" def to_dict(self) -> Dict[str, Any]: - conditions = [] + conditions: list[str] = [] for condition in self.conditions: conditions.append(condition.condition_string()) @@ -139,7 +136,7 @@ class LogicRule(RuleBase): 'type': self.rule_type.value, 'conditions': conditions, 'action': self.action.value if isinstance(self.action, Action) else self.action, - 'raw': self.raw_rule + 'rule_string': str(self) } @field_validator('conditions', mode='after') @@ -166,7 +163,7 @@ class SubRule(RuleBase): 'type': self.rule_type.value, 'condition': f"({self.condition.condition_string()})", 'action': self.action, - 'raw': self.raw_rule + 'rule_string': str(self) } def __str__(self) -> str: @@ -185,7 +182,7 @@ class MatchRule(RuleBase): return { 'type': 'MATCH', 'action': self.action.value if isinstance(self.action, Action) else self.action, - 'raw': self.raw_rule + 'rule_string': str(self) } def __str__(self) -> str: diff --git a/plugins.v2/clashruleprovider/models/ruleitem.py b/plugins.v2/clashruleprovider/models/ruleitem.py new file mode 100644 index 0000000..12174f0 --- /dev/null +++ b/plugins.v2/clashruleprovider/models/ruleitem.py @@ -0,0 +1,40 @@ +from pydantic import BaseModel, Field, field_validator, field_serializer + +from .metadata import Metadata +from .rule import RuleType +from .rule import RoutingRuleType, Action, AdditionalParam +from ..helper.clashruleparser import ClashRuleParser + + +class RuleItem(BaseModel): + """Clash rule item""" + rule: RuleType + meta: Metadata = Field(default_factory=Metadata) + + @field_serializer("rule") + def serialize_rule(self, v: RuleType, _info): + return str(v) + + @field_validator("rule", mode="before") + @classmethod + def validate_rule(cls, v): + if isinstance(v, str): + return ClashRuleParser.parse(v) + return v + + +class RuleData(BaseModel): + priority: int + rule_string: str + type: RoutingRuleType + payload: str | None = None + action: Action | str + additional_params: AdditionalParam | None = None + conditions: list[str] | None = None + condition: str | None = None + meta: Metadata = Field(default_factory=Metadata) + + @classmethod + def from_rule_item(cls, item: RuleItem, priority: int) -> 'RuleData': + fields = item.rule.to_dict() + return cls(priority=priority, meta=item.meta, **fields) diff --git a/plugins.v2/clashruleprovider/models/ruleproviders.py b/plugins.v2/clashruleprovider/models/ruleproviders.py index fc2c589..02e4a0b 100644 --- a/plugins.v2/clashruleprovider/models/ruleproviders.py +++ b/plugins.v2/clashruleprovider/models/ruleproviders.py @@ -1,20 +1,29 @@ -from typing import List, Optional, Literal, Dict +from typing import List, Optional, Literal -from pydantic import BaseModel, Field, model_validator, HttpUrl, RootModel +from pydantic import BaseModel, ConfigDict, Field, model_validator, HttpUrl + +from .generics import ResourceItem, ResourceList +from .types import VehicleType 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.") + """Rule Provider""" + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + ) + + type: VehicleType = Field(..., description="Provider type") + url: Optional[HttpUrl] = Field(default=None, description="Must be configured if the type is http") + path: Optional[str] = Field(default=None, description="Optional, file path, must be unique.") + interval: Optional[int] = Field(default=None, ge=0, description="The update interval for the provider, in seconds.") + proxy: Optional[str] = Field(default=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)", - alias="size-limit") - payload: Optional[List[str]] = Field(None, description="Content, only effective when type is inline") + size_limit: int = Field(default=0, ge=0, alias="size-limit", + description="The maximum size of downloadable files in bytes (0 for no limit)") + payload: Optional[List[str]] = Field(default=None, description="Content, only effective when type is inline") @model_validator(mode="before") @classmethod @@ -55,5 +64,11 @@ class RuleProvider(BaseModel): return values -class RuleProviders(RootModel[Dict[str, RuleProvider]]): - root: Dict[str, RuleProvider] +class RuleProviderData(ResourceItem[RuleProvider]): + """Rule Provider Data""" + pass + + +class RuleProviders(ResourceList[RuleProviderData]): + """Rule Providers Collection""" + pass diff --git a/plugins.v2/clashruleprovider/models/types.py b/plugins.v2/clashruleprovider/models/types.py new file mode 100644 index 0000000..c4433f9 --- /dev/null +++ b/plugins.v2/clashruleprovider/models/types.py @@ -0,0 +1,57 @@ +from enum import StrEnum +from typing import TypeVar, Protocol + + +class DataSource(StrEnum): + MANUAL = "Manual" + ACL4SSR = "Acl4SSR" + TEMPLATE = "Template" + SUB = "Subscription" + AUTO = "Auto" + + +class VehicleType(StrEnum): + FILE = "file" + HTTP = "http" + INLINE = "inline" + + +class DataKey(StrEnum): + """Plugin data key""" + PROXY_PATCH = "proxy_patch" + PROXY_GROUPS = "proxy-groups" + PROXIES = "proxies" + INVALID_PROXIES = "extra_proxies" + SUB_INFO = "subscription_info" + HOSTS = "hosts" + ACL4SSR = "acl4ssr_providers" + RULE_PROVIDERS = "rule-providers" + DATA_VERSION = "data_version" + SUB_CONFIGS = "clash_configs" + PROXY_GROUP_PATCH = "proxy_group_patch" + RULESET_NAMES = "ruleset_names" + AUTO_RULE_PROVIDERS = "rule_provider" + GEO_RULES = "geo_rules" + TOP_RULES = "top_rules" + RULESET_RULES = "ruleset_rules" + RULE_PROVIDER_PATCH = "rule_provider_patch" + RAW_PROXIES = "raw_proxies" + + +class RuleSet(StrEnum): + TOP = "top" + RULESET = "ruleset" + + +class ClashKey(StrEnum): + PROXIES = "proxies" + PROXY_GROUPS = "proxy-groups" + NAME = "name" + RULES = "rules" + + +T = TypeVar("T") + +class SupportsPatch(Protocol[T]): + def patch(self, patch: str) -> T: + ... diff --git a/plugins.v2/clashruleprovider/requirements.txt b/plugins.v2/clashruleprovider/requirements.txt index ff583c4..1aa324a 100644 --- a/plugins.v2/clashruleprovider/requirements.txt +++ b/plugins.v2/clashruleprovider/requirements.txt @@ -1,3 +1,5 @@ websockets -sse_starlette~=2.3.6 -PyYAML~=6.0.2 \ No newline at end of file +sse_starlette~=3.1.1 +PyYAML~=6.0.2 +jsonpatch~=1.33 +simpleeval~=1.0.3 \ No newline at end of file diff --git a/plugins.v2/clashruleprovider/services.py b/plugins.v2/clashruleprovider/services.py index 8e4fae2..5763bb1 100644 --- a/plugins.v2/clashruleprovider/services.py +++ b/plugins.v2/clashruleprovider/services.py @@ -1,5 +1,4 @@ import asyncio -import copy import hashlib import json import pytz @@ -7,127 +6,187 @@ import re import time import yaml from datetime import datetime, timedelta -from typing import Any, Dict, List, Optional, Literal, Tuple, overload +from typing import Any, Dict, List, Optional, Tuple, Iterable, TypeVar +import jsonpatch from fastapi import HTTPException from apscheduler.schedulers.asyncio import AsyncIOScheduler +from pydantic import HttpUrl from app.core.cache import cached from app.core.config import settings from app.log import logger from app.utils.http import AsyncRequestUtils -from .base import _ClashRuleProviderBase as Crp -from .config import PluginConfig -from .helper.clashrulemanager import RuleItem, ClashRuleManager -from .helper.clashruleparser import ClashRuleParser, RoutingRuleType, Action, ClashRule +from .base import Constant +from .helper.clashruleparser import ClashRuleParser, RoutingRuleType, Action from .helper.configconverter import Converter from .helper.utilsprovider import UtilsProvider -from .models import ProxyBase, TLSMixin, NetworkMixin, ProxyGroup, Proxy -from .models.api import RuleData, ClashApi, RuleProviderData, SubscriptionInfo, HostData +from .models import ProxyGroup, Proxy, RuleProvider, RuleProviderData, ProxyData, HostData, VehicleType, SelectGroup, \ + ProxyGroupData, RuleItem, RuleData, Metadata, RuleProviders +from .models.api import ClashApi, SubscriptionSetting, DataUsage, SubscriptionInfo, ConfigRequest +from .models.configuration import ClashConfig +from .models.datapatch import PatchItem, DataPatch +from .models.rule import RuleType +from .models.types import DataSource, DataKey, RuleSet, ClashKey, SupportsPatch from .state import PluginState -from .store import PluginStore +T = TypeVar("T") + class ClashRuleProviderService: def __init__( self, plugin_id: str, - config: PluginConfig, state: PluginState, - store: PluginStore, scheduler: Optional[AsyncIOScheduler] = None ): self.plugin_id = plugin_id - self.config = config self.state = state - self.store = store self.scheduler = scheduler def save_rules(self): - self.store.save_data(Crp.KEY_TOP_RULES, self.state.top_rules_manager.export_rules()) - self.store.save_data(Crp.KEY_RULESET_RULES, self.state.ruleset_rules_manager.export_rules()) + self.state.save_data(DataKey.TOP_RULES, self.state.top_rules_manager.export_rules()) + self.state.save_data(DataKey.RULESET_RULES, self.state.ruleset_rules_manager.export_rules()) def load_rules(self): - def process_rules(raw_rules: List[str | dict[str, Any]], manager: ClashRuleManager, key: str): - raw_rules = raw_rules or [] - rules = [self._upgrade_rule(r) if isinstance(r, str) else r for r in raw_rules] - manager.import_rules(rules) - if any((isinstance(r, str) or 'time_modified' not in r) for r in raw_rules): - self.store.save_data(key, manager.export_rules()) + self.state.top_rules_manager.import_rules(self.state.get_data(DataKey.TOP_RULES) or []) + self.state.ruleset_rules_manager.import_rules(self.state.get_data(DataKey.RULESET_RULES) or []) - process_rules(self.store.get_data(Crp.KEY_TOP_RULES), self.state.top_rules_manager, Crp.KEY_TOP_RULES) - process_rules(self.store.get_data(Crp.KEY_RULESET_RULES), self.state.ruleset_rules_manager, - Crp.KEY_RULESET_RULES) + def _make_proxy_patch(self, src: Proxy, dst: Proxy): + src_dict = src.model_dump(mode="json", by_alias=True, exclude_none=True) + dst_dict = dst.model_dump(mode="json", by_alias=True, exclude_none=True) + patch = jsonpatch.make_patch(src_dict, dst_dict) + patches = self.state.proxy_patch + patches[src.name] = PatchItem(patch=patch.to_string(), lifecycle=Constant.PATCH_LIFESPAN) + self.state.proxy_patch = patches - def _upgrade_rule(self, rule_string: str) -> Dict[str, str]: - rule = ClashRuleParser.parse_rule_line(rule_string) - remark = 'Manual' - if isinstance(rule, ClashRule) and rule.rule_type == RoutingRuleType.RULE_SET and rule.payload.startswith( - self.config.ruleset_prefix): - remark = 'Auto' - return {'rule': rule_string, 'remark': remark, 'time_modified': f"{int(time.time())}"} + def _apply_patch(self, item: SupportsPatch[T], name: str, patch: DataPatch) -> T: + try: + if name in patch: + return item.patch(patch[name].patch) + except Exception as err: + logger.error(f"Failed to apply patch for {name}: {repr(err)}") + return item - def save_proxies(self): - proxies = self.state.proxies_manager.export_raw(condition=lambda proxy: proxy.remark == 'Manual') - self.store.save_data(Crp.KEY_PROXIES, proxies) + def _apply_patches(self, items: list[Any], patches: DataPatch) -> list[Any]: + for item in items: + item.data = self._apply_patch(item.data, item.name, patches) + item.meta.patched = item.name in patches + return items - def load_proxies(self): - proxies = self.store.get_data(Crp.KEY_PROXIES) or [] - initial_len = len(proxies) - proxies.extend(self.state.extra_proxies) - invalid_proxies = [] - converter = Converter() - for proxy in proxies: - try: - if isinstance(proxy, dict): - proxy = UtilsProvider.filter_empty(proxy, empty=['', None]) - self.state.proxies_manager.add_proxy_dict(proxy, remark='Manual') - elif isinstance(proxy, str): - proxy_dict = converter.convert_line(proxy) - if proxy_dict: - self.state.proxies_manager.add_proxy_dict(proxy_dict, remark='Manual', raw=proxy) - except Exception as e: - logger.error(f"Failed to load proxy {proxy}: {e}") - invalid_proxies.append(proxy) - if len(self.state.extra_proxies) != len(invalid_proxies): - self.state.extra_proxies = invalid_proxies - self.store.save_data('extra_proxies', self.state.extra_proxies) - if len(self.state.proxies_manager) > initial_len: - self.save_proxies() + def _apply_patch_to_config(self, conf: ClashConfig) -> ClashConfig: + conf.proxies = [self._apply_patch(proxy, proxy.name, self.state.proxy_patch) for proxy in conf.proxies] + conf.proxy_groups = [self._apply_patch(pg, pg.name, self.state.proxy_group_patch) for pg in conf.proxy_groups] + return conf - def overwrite_proxy(self, proxy: Dict[str, Any]): - proxy_base = ProxyBase.model_validate(proxy) - tls = TLSMixin.model_validate(proxy) - network = NetworkMixin.model_validate(proxy) - overwrite_config = { - 'base': proxy_base.model_dump(by_alias=True, exclude_none=True), - 'tls': tls.model_dump(by_alias=True, exclude_none=True), - 'network': network.model_dump(by_alias=True, exclude_none=True), - 'lifetime': Crp.OVERWRITTEN_PROXIES_LIFETIME - } - self.state.overwritten_proxies[proxy_base.name] = overwrite_config - self.store.save_data('overwritten_proxies', self.state.overwritten_proxies) + def _merge_subscriptions(self, config: ClashConfig): + subscriptions_config = self.state.config.subscriptions_config + subscription_info = self.state.subscription_info - def remove_overwritten_proxy(self, proxy_name: str): - self.state.overwritten_proxies.pop(proxy_name, None) - self.store.save_data('overwritten_proxies', self.state.overwritten_proxies) + for conf in subscriptions_config: + if not subscription_info.get(conf.url).enabled: + continue + sub_config = self.state.get_sub_config(conf.url) + config.merge(sub_config) - def overwrite_region_group(self, region_group: ProxyGroup): - overwrite_config = {k: v for k, v in region_group.model_dump(by_alias=True, exclude_none=True).items() if - k not in {Crp.KEY_NAME, Crp.KEY_PROXIES, 'use'}} - self.state.overwritten_region_groups[region_group.root.name] = overwrite_config - self._group_by_region.cache_clear() - self.store.save_data('overwritten_region_groups', self.state.overwritten_region_groups) + def _filter_available_items(self, items: Iterable[Any], param: ConfigRequest) -> list[Any]: + return [item.data for item in items if item.meta.available(param)] + + def _process_auto_rule_providers(self, config: ClashConfig): + auto_rule_provider = {} + ruleset_names = self.state.ruleset_names + + for r in self.state.ruleset_rules_manager.rules: + rule = r.rule + rule_provider_name = f'{self.state.config.ruleset_prefix}{rule.action}' + if rule_provider_name not in auto_rule_provider: + path_name = hashlib.sha256(rule.action.encode('utf-8')).hexdigest()[:10] + ruleset_names[path_name] = rule_provider_name + sub_url = (f"{self.state.config.movie_pilot_url}/api/v1/plugin/{self.plugin_id}/ruleset?" + f"name={path_name}&apikey={self.state.config.apikey or settings.API_TOKEN}") + auto_rule_provider[rule_provider_name] = RuleProvider( + type=VehicleType.HTTP, behavior="classical", url=HttpUrl(sub_url), path=f"./CRP/{path_name}.yaml", + interval=3600, format="yaml" + ) + config.rule_providers = config.rule_providers | auto_rule_provider + self.state.rule_provider = auto_rule_provider + self.state.ruleset_names = ruleset_names + + def _process_rules(self, config: ClashConfig, param: ConfigRequest): + top_rules: list[RuleType] = [] + acl4ssr_providers_map: dict[str, RuleProvider] = {} + acl4ssr_data = self.state.acl4ssr_providers + + for r in self.state.top_rules_manager: + if not r.meta.available(param): + continue + rule = r.rule + if rule.rule_type == RoutingRuleType.RULE_SET: + if rule.payload in acl4ssr_data: + acl4ssr_providers_map[rule.payload] = acl4ssr_data.get(rule.payload).data + top_rules.append(rule) + config.rule_providers = config.rule_providers | acl4ssr_providers_map + config.rules = top_rules + config.rules + + def _cleanup_ruleset_names(self, config: ClashConfig): + ruleset_names = self.state.ruleset_names + key_to_delete = [key for key, item in ruleset_names.items() if item not in config.rule_providers] + for key in key_to_delete: + del ruleset_names[key] + self.state.ruleset_names = ruleset_names + + def _check_cycles(self, config: ClashConfig): + proxy_graph = self._build_graph(config) + cycles = UtilsProvider.find_cycles(proxy_graph) + if cycles: + logger.warn("发现代理组回环:") + for cycle in cycles: + logger.warn(" -> ".join(cycle)) + + def _make_proxy_group_patch(self, src: ProxyGroup, dst: ProxyGroup): + src_dict = src.model_dump(mode="json", by_alias=True, exclude_none=True) + dst_dict = dst.model_dump(mode="json", by_alias=True, exclude_none=True) + patch = jsonpatch.make_patch(src_dict, dst_dict) + + # Flatten list patches to full replace to avoid index shift issues + new_ops = [] + replaced_paths = set() + list_fields = ["/proxies", "/use"] + + for op in patch.patch: + path = op["path"] + matched_list = next((f for f in list_fields if path == f or path.startswith(f + '/')), None) + if matched_list: + if matched_list not in replaced_paths: + field_name = matched_list.strip('/') + val = dst_dict.get(field_name) + if val is None: + # Removed in dst + new_ops.append({"op": "remove", "path": matched_list}) + elif field_name not in src_dict: + # Not in src, added in dst + new_ops.append({"op": "add", "path": matched_list, "value": val}) + else: + # In src and dst, replacing + new_ops.append({"op": "replace", "path": matched_list, "value": val}) + replaced_paths.add(matched_list) + else: + new_ops.append(op) + + patch.patch = new_ops + pg_patches = self.state.proxy_group_patch + pg_patches[src.name] = PatchItem(patch=patch.to_string(), lifecycle=Constant.PATCH_LIFESPAN) + self.state.proxy_group_patch = pg_patches def organize_and_save_rules(self): self.sync_ruleset() self.save_rules() def ruleset(self, ruleset: str) -> List[str]: - if not ruleset.startswith(self.config.ruleset_prefix): + if not ruleset.startswith(self.state.config.ruleset_prefix): return [] - action = ruleset[len(self.config.ruleset_prefix):] + action = ruleset[len(self.state.config.ruleset_prefix):] try: final_action = Action(action.upper()) except ValueError: @@ -142,32 +201,33 @@ class ClashRuleProviderService: manager.remove_rules_by_lambda( lambda r: r.rule.rule_type == RoutingRuleType.RULE_SET and - r.remark == 'Auto' and - r.rule.payload != f"{self.config.ruleset_prefix}{ClashRuleParser.action_string(r.rule.action)}" + r.meta.source == DataSource.AUTO and + r.rule.payload != f"{self.state.config.ruleset_prefix}{r.rule.action}" ) rules_existed = manager.filter_rules_by_condition( - lambda r: r.remark == 'Auto' and r.rule.rule_type == RoutingRuleType.RULE_SET + lambda r: r.meta.source == DataSource.AUTO and r.rule.rule_type == RoutingRuleType.RULE_SET ) - actions_existed = {ClashRuleParser.action_string(r.rule.action) for r in rules_existed} + actions_existed = {r.rule.action for r in rules_existed} - for r in self.state.ruleset_rules_manager.rules: - action_str = ClashRuleParser.action_string(r.rule.action) - outbounds.add(action_str) - if action_str not in actions_existed: - new_outbounds.add(action_str) + for r in self.state.ruleset_rules_manager: + if r.meta.disabled: + continue + outbounds.add(r.rule.action) + if r.rule.action not in actions_existed: + new_outbounds.add(r.rule.action) manager.remove_rules_by_lambda( lambda r: r.rule.rule_type == RoutingRuleType.RULE_SET and - r.remark == 'Auto' and - (ClashRuleParser.action_string(r.rule.action) not in outbounds) + r.meta.source == DataSource.AUTO and + (r.rule.action not in outbounds) ) for outbound in new_outbounds: clash_rule = ClashRuleParser.parse_rule_line( - f"RULE-SET,{self.config.ruleset_prefix}{outbound},{outbound}") + f"RULE-SET,{self.state.config.ruleset_prefix}{outbound},{outbound}") if clash_rule is None: continue - rule = RuleItem(rule=clash_rule, remark='Auto') + rule = RuleItem(rule=clash_rule, meta=Metadata(source=DataSource.AUTO)) if not manager.has_rule_item(rule): manager.insert_rule_at_priority(rule, 0) @@ -176,184 +236,106 @@ class ClashRuleProviderService: for rule in rules: clash_rule = ClashRuleParser.parse_rule_line(rule) if clash_rule: - clash_rules.append(RuleItem(rule=clash_rule, remark='Manual')) + clash_rules.append(RuleItem(rule=clash_rule, meta=Metadata(source=DataSource.MANUAL))) self.state.top_rules_manager.append_rules(clash_rules) - self.store.save_data(Crp.KEY_TOP_RULES, self.state.top_rules_manager.export_rules()) + self.state.save_data(DataKey.TOP_RULES, self.state.top_rules_manager.export_rules()) - def clash_outbound(self) -> List[Dict[str, Any]]: - outbound = [{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in self.value_from_sub_conf('proxy-groups')] - if self.state.clash_template_dict: - outbound.extend( - [{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in - self.state.clash_template_dict.get(Crp.KEY_PROXY_GROUPS, [])]) - if self.config.group_by_region or self.config.group_by_country: - outbound.extend([{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in self.proxy_groups_by_region()]) - outbound.extend([{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in self.state.proxy_groups]) - outbound.extend([{Crp.KEY_NAME: pg.get(Crp.KEY_NAME)} for pg in self.get_proxies()]) + def clash_outbound(self) -> list[str]: + outbound = [pg_data.data.name for pg_data in self.state.proxy_groups_from_subs()] + if self.state.clash_template: + outbound.extend(pg.name for pg in self.state.clash_template.proxy_groups) + if self.state.config.group_by_region or self.state.config.group_by_country: + outbound.extend(pg.name for pg in self.proxy_groups_by_region()) + outbound.extend(pg.data.name for pg in self.state.proxy_groups) + outbound.extend(pg.data.name for pg in self.get_proxies()) return outbound - def rule_providers(self) -> List[Dict[str, Any]]: - providers_list = [] - sub_providers = self.dict_from_sub_conf('rule-providers') - hostnames = [UtilsProvider.get_url_domain(url) for url in sub_providers] - provider_sources = ( - self.state.rule_providers, - *sub_providers.values(), - self.state.clash_template_dict.get('rule-providers', {}), - self.state.acl4ssr_providers - ) - source_names = ('Manual', *hostnames, 'Template', 'Acl4ssr') - for i, provider_dict in enumerate(provider_sources): - for name, value in provider_dict.items(): - provider = copy.deepcopy(value) - provider[Crp.KEY_NAME] = name - provider['source'] = source_names[i] - providers_list.append(provider) - return providers_list + def delete_proxy(self, name: str) -> Tuple[bool, str]: + proxies = self.state.proxies + deleted = proxies.pop(name) + if deleted: + self.state.proxies = proxies + return True, "代理删除成功" + return False, f"代理 {name!r} 不存在" - def all_proxy_providers(self) -> Dict[str, Any]: - proxy_providers = self.value_from_sub_conf('proxy-providers') - proxy_providers.update(self.state.clash_template_dict.get('proxy-providers', {})) - return proxy_providers + def delete_proxy_patch(self, name: str) -> tuple[bool, str]: + patches = self.state.proxy_patch + if name in patches: + del patches.root[name] + self.state.proxy_patch = patches + return True, "补丁已删除" + return False, "补丁不存在" - def get_all_proxies_with_details(self) -> List[Dict[str, Any]]: - proxies = self.get_proxies(regex='^Manual$', flat=False) - proxies.extend(self.get_proxies(regex='^Template$', flat=False)) - proxies.extend(self.get_proxies(regex='^Sub:', flat=False)) - ret = [] - for proxy in proxies: - remark = proxy['remark'] - i = remark.rfind('-') - source = remark[remark.rfind(':') + 1:(len(remark) if i == -1 else i)] - if isinstance(proxy['raw'], str): - proxy_link = proxy['raw'] - else: - try: - proxy_link = Converter.convert_to_share_link(proxy['proxy']) - except Exception as e: - logger.warn(f"Failed to convert proxy link: {repr(e)}") - proxy_link = None - proxy['proxy']['source'] = source - proxy['proxy']['v2ray_link'] = proxy_link - proxy['proxy']['overwritten'] = proxy['proxy'][Crp.KEY_NAME] in self.state.overwritten_proxies - ret.append(proxy['proxy']) - ret.extend([{'source': 'Invalid', 'v2ray_link': None, **proxy} for proxy in self.state.extra_proxies]) - return ret - - def delete_proxy(self, name: str): - extra_proxies = [p for p in self.state.extra_proxies if p.get(Crp.KEY_NAME) != name] - if len(extra_proxies) != len(self.state.extra_proxies): - self.state.extra_proxies = extra_proxies - self.store.save_data('extra_proxies', self.state.extra_proxies) - return - self.state.proxies_manager.remove_proxy(name) - self.save_proxies() - - def import_proxies(self, params: Dict[str, Any]) -> Tuple[bool, str]: - extra_proxies = ClashRuleProviderService.parse_proxies_from_input(params) - if not extra_proxies: - return False, "无可用节点或输入格式错误" + def import_proxies(self, vehicle: str, payload: str) -> tuple[bool, str]: + proxies = [] + if vehicle == 'LINK': + links = payload.strip().splitlines() + proxies = list(Converter().convert_v2ray(links, skip_exception=True, logger=logger).items()) + elif vehicle == 'YAML': + try: + imported = yaml.load(payload, Loader=yaml.SafeLoader) + if not isinstance(imported, dict): + return False, "无效的输入" + except yaml.YAMLError as err: + logger.error(f"Failed to import rules: {repr(err)}") + return False, 'YAML 格式错误' + proxies = [(None, p) for p in (imported.get(DataKey.PROXIES) or [])] + if not proxies: + return False, "无可用节点" success_count = 0 error_messages = '' success = True - for proxy_item in extra_proxies: + ps = self.state.proxies + for item in proxies: try: - self.state.proxies_manager.add_proxy_dict( - proxy_item['proxy'], 'Manual', raw=proxy_item['raw'] - ) + proxy = Proxy.model_validate(item[1]) + meta = Metadata(source=DataSource.MANUAL) + pd = ProxyData(data=proxy, name=proxy.name, meta=meta, raw=item[0], v2ray_link=item[0]) + if not pd.v2ray_link: + try: + pd.v2ray_link = Converter.convert_to_share_link(item[1]) + except Exception as err: + logger.debug(f"Failed to convert proxy link: {repr(err)}") + ps.add(pd) success_count += 1 except Exception as err: success = False error_messages += f"{err}\n" - message = f"导入 {success_count}/{len(extra_proxies)} 个代理节点. \n{error_messages}" - self.save_proxies() + message = f"导入 {success_count}/{len(proxies)} 个代理节点. \n{error_messages}" + self.state.proxies = ps return success, message - def update_proxy(self, name: str, params: Dict[str, Any]) -> Tuple[bool, str]: - proxy_dict = params - previous_name = name - if previous_name not in self.state.proxies_manager: - return False, f"The proxy name {previous_name} does not exist" - if proxy_dict.get('rescind'): - self.remove_overwritten_proxy(previous_name) - return True, '' - try: - Proxy.model_validate(proxy_dict) - if proxy_dict[Crp.KEY_NAME] != previous_name: - return False, "Proxy name is not allowed to be overwritten" - self.overwrite_proxy(proxy_dict) - except Exception as e: - logger.error(f"Failed to overwrite proxy: {repr(e)}") - return False, "覆写代理失败" + def update_proxy(self, previous_name: str, source: str, proxy: Proxy) -> tuple[bool, str]: + if source == DataSource.MANUAL: + proxies = self.state.proxies + proxies.update(previous_name, ProxyData(data=proxy, name=proxy.name, meta=Metadata())) + self.state.proxies = proxies + return True, "代理更新成功" + if previous_name != proxy.name: + return False, "请勿修改代理名称" + proxies = list(self.state.proxies_from_subs()) + src = next((g for g in proxies if g.name == previous_name), None) + if src is None: + return False, f"代理组 {previous_name!r} ({source}) 不存在" + self._make_proxy_patch(src.data, proxy) + return True, "代理更新成功" + + def update_proxy_meta(self, name: str, meta: Metadata) -> tuple[bool, str]: + proxies = self.state.proxies + if name not in proxies: + return False, f"The proxy name {name} does not exist" + proxies.set_meta(name, meta) + self.state.proxies = proxies return True, '' - @staticmethod - def parse_proxies_from_input(params: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]: - extra_proxies: List = [] - if params.get('type') == 'YAML': - try: - imported_proxies = yaml.load(params["payload"], Loader=yaml.SafeLoader) - if not imported_proxies or not isinstance(imported_proxies, dict): - logger.error(f"Failed to load YAML payload: {repr(params)}") - return None - if Crp.KEY_PROXIES not in imported_proxies: - logger.error("No field 'proxies' found") - return None - extra_proxies = [{'proxy': proxy, 'raw': None} for proxy in imported_proxies.get(Crp.KEY_PROXIES, [])] - except Exception as err: - logger.error(f"Failed to load YAML payload: {params['payload']}: {repr(err)}") - return None - elif params.get('type') == 'LINK': - try: - links = params['payload'].strip().splitlines() - for link in links: - proxy = Converter().convert_line(link, skip_exception=True) - if proxy: - extra_proxies.append({'proxy': proxy, 'raw': link}) - except Exception as err: - logger.error(f"Failed to load LINK payload: {repr(params)}: {repr(err)}") - return extra_proxies - - def get_all_proxy_groups_with_source(self) -> List[Dict[str, Any]]: - proxy_groups = [] - sub = self.dict_from_sub_conf('proxy-groups') - hostnames = [UtilsProvider.get_url_domain(url) or '' for url in sub] - sub_proxy_groups = sub.values() - sources = ('Manual', 'Template', *hostnames, 'Region') - groups = (self.state.proxy_groups, self.state.clash_template_dict.get(Crp.KEY_PROXY_GROUPS, []), - *sub_proxy_groups, self.proxy_groups_by_region()) - for i, group in enumerate(groups): - for proxy_group in group: - proxy_group_copy = copy.deepcopy(proxy_group) - proxy_group_copy['source'] = sources[i] - proxy_groups.append(proxy_group_copy) - return proxy_groups - - def get_proxies(self, regex: Optional[str] = None, flat: bool = True) -> List[Dict[str, Any]]: - def _overwrite_proxy(_proxy: Dict[str, Any]) -> Dict[str, Any]: - if _proxy[Crp.KEY_NAME] in self.state.overwritten_proxies: - for key in ['base', 'tls', 'network']: - _proxy.update(copy.deepcopy(self.state.overwritten_proxies[_proxy[Crp.KEY_NAME]].get(key)) or {}) - return _proxy - - if regex is None: - proxy_items = list(self.state.proxies_manager) - else: - compiled = re.compile(regex) - proxy_items = self.state.proxies_manager.filter_proxies_by_condition( - lambda item: bool(compiled.match(item.remark)) - ) - - result = [] - for p in proxy_items: - if any(keyword in p.proxy.name for keyword in self.config.filter_keywords): - continue - proxy_dict = _overwrite_proxy(p.proxy.model_dump(by_alias=True, exclude_none=True)) - if flat: - result.append(proxy_dict) - else: - result.append({'proxy': proxy_dict, 'raw': p.raw, 'remark': p.remark}) - return result + def get_proxies(self, patched: bool = True) -> list[ProxyData]: + proxies = self.state.all_proxies + proxies = list( + filter(lambda p: not any(keyword in p.data.name for keyword in self.state.config.filter_keywords), proxies) + ) + if not patched: + return proxies + return self._apply_patches(proxies, self.state.proxy_patch) @cached(maxsize=1, ttl=86400, skip_empty=True) def _get_countries_data(self) -> List[Dict[str, str]]: @@ -365,45 +347,45 @@ class ClashRuleProviderService: logger.error(f"加载国家/地区文件错误:{e}") return [] - def proxy_groups_by_region(self) -> List[Dict[str, Any]]: + def proxy_groups_by_region(self) -> list[ProxyGroupData]: countries = self._get_countries_data() - all_proxies = self.get_proxies() - return self._group_by_region(countries, all_proxies, self.config.group_by_region, - self.config.group_by_country, - self.state.overwritten_region_groups) + proxies = self.get_proxies() + return self._group_by_region( + countries, proxies, self.state.config.group_by_region, self.state.config.group_by_country + ) - @cached(maxsize=1, ttl=86400) - def _group_by_region(self, countries: List[Dict[str, str]], all_proxies: List[Dict[str, Any]], - group_by_region: bool, group_by_country: bool, - overwritten_groups: Dict[str, Any]) -> List[Dict[str, Any]]: + @cached(maxsize=2, ttl=86400) + def _group_by_region(self, countries: list[dict[str, str]], proxies: list[ProxyData], group_by_continent: bool, + group_by_country: bool) -> list[ProxyGroupData]: continent_groups = {} country_groups = {} continent_map = { '欧洲': 'Europe', '亚洲': 'Asia', '大洋洲': 'Oceania', '非洲': 'Africa', '北美洲': 'NorthAmerica', '南美洲': 'SouthAmerica' } - proxy_groups: List[Dict[str, Any]] = [] + proxy_groups: list[ProxyGroup] = [] hk = next((c for c in countries if c['abbr'] == 'HK'), {}) tw = next((c for c in countries if c['abbr'] == 'TW'), {}) - for proxy_node in all_proxies: - country = ClashRuleProviderService._country_from_node(countries, proxy_node[Crp.KEY_NAME]) + for proxy_data in proxies: + proxy_node = proxy_data.data + country = ClashRuleProviderService._country_from_node(countries, proxy_node.name) if not country: continue if country.get("abbr") == "CN": - if any(key in proxy_node[Crp.KEY_NAME] for key in ("🇭🇰", "HK", "香港")): + if any(key in proxy_node.name for key in ("🇭🇰", "HK", "香港")): country = hk - if any(key in proxy_node[Crp.KEY_NAME] for key in ("🇹🇼", "TW", "台湾")): + if any(key in proxy_node.name for key in ("🇹🇼", "TW", "台湾")): country = tw - continent = continent_map.get(country['continent']) - if continent and group_by_region: - continent_groups.setdefault(continent, []).append(proxy_node[Crp.KEY_NAME]) + continent = continent_map.get(country["continent"]) + if continent and group_by_continent: + continent_groups.setdefault(continent, []).append(proxy_node.name) if group_by_country: country_groups.setdefault(f"{country.get('emoji')} {country.get('chinese')}", []).append( - proxy_node[Crp.KEY_NAME]) + proxy_node.name) for continent, nodes in continent_groups.items(): if nodes: - proxy_groups.append({Crp.KEY_NAME: continent, 'type': 'select', Crp.KEY_PROXIES: nodes}) + proxy_groups.append(ProxyGroup(root=SelectGroup(name=continent, proxies=nodes))) excluded = ('中国', '香港', 'CN', 'HK', '🇨🇳', '🇭🇰') for continent_node in continent_groups.get('Asia', []): @@ -411,22 +393,16 @@ class ClashRuleProviderService: continue continent_groups.setdefault('AsiaExceptChina', []).append(continent_node) if continent_groups.get('AsiaExceptChina'): - proxy_group = {Crp.KEY_NAME: 'AsiaExceptChina', 'type': 'select', - Crp.KEY_PROXIES: continent_groups['AsiaExceptChina']} - proxy_groups.append(proxy_group) + pg = SelectGroup(name="AsiaExceptChina", proxies=continent_groups['AsiaExceptChina']) + proxy_groups.append(ProxyGroup(root=pg)) for country, nodes in country_groups.items(): if len(nodes): - proxy_group = {Crp.KEY_NAME: country, 'type': 'select', Crp.KEY_PROXIES: nodes} - proxy_groups.append(proxy_group) + proxy_groups.append(ProxyGroup(root=SelectGroup(name=country, proxies=nodes))) country_group = list(country_groups.keys()) if country_group: - proxy_groups.append({Crp.KEY_NAME: '🏴‍☠️国家分组', 'type': 'select', Crp.KEY_PROXIES: country_group}) - - for pg in proxy_groups: - if pg[Crp.KEY_NAME] in overwritten_groups: - pg.update(overwritten_groups[pg[Crp.KEY_NAME]]) - - return proxy_groups + proxy_groups.append(ProxyGroup(root=SelectGroup(name="🏴‍☠️国家分组", proxies=country_group))) + ret = [ProxyGroupData(name=p.name, data=p, meta=Metadata(source=DataSource.AUTO)) for p in proxy_groups] + return ret @staticmethod def _country_from_node(countries: List[Dict[str, str]], node_name: str) -> Optional[Dict[str, str]]: @@ -442,112 +418,31 @@ class ClashRuleProviderService: return None @staticmethod - def _extend_with_name_checking(to_list: List[Dict[str, Any]], from_list: List[Dict[str, Any]] - ) -> List[Dict[str, Any]]: - """ - 去除同名元素合并列表 - """ - for item in from_list: - if any(p.get(Crp.KEY_NAME) == item.get(Crp.KEY_NAME, '') for p in to_list): - logger.warn(f"Item named {item.get(Crp.KEY_NAME)!r} already exists. Skipping...") - continue - to_list.append(item) - return to_list - - @staticmethod - def _remove_invalid_outbounds(proxies: List[Dict[str, Any]], proxy_groups: List[Dict[str, Any]] - ) -> List[Dict[str, Any]]: - """ - 从代理组中移除无效的出站 - """ - outbounds = {proxy.get(Crp.KEY_NAME) for proxy in proxies if proxy.get(Crp.KEY_NAME)} | \ - {proxy_group.get(Crp.KEY_NAME) for proxy_group in proxy_groups if proxy_group.get(Crp.KEY_NAME)} | \ - {action.value for action in Action} - outbounds.add('GLOBAL') - for proxy_group in proxy_groups: - ps = [] - if proxy_group.get(Crp.KEY_PROXIES): - for proxy in proxy_group.get(Crp.KEY_PROXIES, []): - if proxy in outbounds: - ps.append(proxy) - else: - logger.warn(f"Proxy {proxy!r} in {proxy_group.get(Crp.KEY_NAME)!r} doesn't exist. Skipping...") - proxy_group[Crp.KEY_PROXIES] = ps - return proxy_groups - - @staticmethod - def _remove_invalid_proxy_providers(providers: Dict[str, Any], proxy_groups: List[Dict[str, Any]] - ) -> List[Dict[str, Any]]: - provider_names = providers.keys() - for proxy_group in proxy_groups: - ps = [] - if proxy_group.get('use'): - for provider in proxy_group.get('use', []): - if provider in provider_names: - ps.append(provider) - else: - logger.warn(f"Proxy provider {provider!r} in {proxy_group.get(Crp.KEY_NAME)!r} doesn't exist. " - f"Skipping...") - proxy_group['use'] = ps - return proxy_groups - - @staticmethod - def _build_graph(config: Dict[str, Any]) -> Dict[str, Any]: + def _build_graph(config: ClashConfig) -> Dict[str, Any]: """构建代理组有向图""" graph = {} - groups = config.get(Crp.KEY_PROXY_GROUPS, []) - group_names = {g[Crp.KEY_NAME] for g in groups} + groups = config.proxy_groups + group_names = {g.name for g in groups} for group in groups: - name = group[Crp.KEY_NAME] - proxies = group.get(Crp.KEY_PROXIES, []) - graph[name] = [p for p in proxies if p in group_names] + proxies = group.proxies + graph[group.name] = [p for p in proxies if p in group_names] return graph - @overload - def value_from_sub_conf(self, key: Literal['rules', 'proxies', 'proxy-groups']) -> list: ... - - @overload - def value_from_sub_conf(self, key: Literal['rule-providers', 'proxy-providers']) -> dict: ... - - def value_from_sub_conf(self, - key: Literal['rules', 'rule-providers', 'proxies', 'proxy-groups', 'proxy-providers']): - default = copy.deepcopy(Crp.DEFAULT_CLASH_CONF[key]) - for conf in self.config.subscriptions_config: - url = conf.url - sub_config = self.state.clash_configs.get(url, {}) - if isinstance(default, dict): - default.update(sub_config.get(key, {})) - elif isinstance(default, list): - default.extend(sub_config.get(key, [])) - return default - - def dict_from_sub_conf(self, - key: Literal['rules', 'rule-providers', 'proxies', 'proxy-groups', 'proxy-providers'] - ) -> Dict[str, Any]: - result = {} - for conf in self.config.subscriptions_config: - url = conf.url - sub_config = self.state.clash_configs.get(url, {}) - result[url] = sub_config.get(key, copy.deepcopy(Crp.DEFAULT_CLASH_CONF[key])) - return result - async def fetch_clash_data(self, endpoint: str) -> Dict: - headers = {"Authorization": f"Bearer {self.config.dashboard_secret}"} - url = f"{self.config.dashboard_url}/{endpoint}" + headers = {"Authorization": f"Bearer {self.state.config.dashboard_secret}"} + url = f"{self.state.config.dashboard_url}/{endpoint}" response = await AsyncRequestUtils().get_json(url, headers=headers, timeout=10) if response is None: raise HTTPException(status_code=502, detail=f"Failed to fetch {endpoint}") return response - def get_subscription_user_info(self) -> Dict[str, int]: - sub_info = {'upload': 0, 'download': 0, 'total': 0, 'expire': 0} - for info in self.state.subscription_info.values(): - if not info: - continue - sub_info['upload'] += info.get('upload', 0) - sub_info['download'] += info.get('download', 0) - sub_info['total'] += info.get('total', 0) - sub_info['expire'] = max(sub_info['expire'], info.get('expire') or 0) + def get_subscription_user_info(self) -> DataUsage: + sub_info = DataUsage() + for info in self.state.subscription_info.root.values(): + sub_info.upload += info.upload + sub_info.download += info.download + sub_info.total += info.total + sub_info.expire = max(sub_info.expire, info.expire) return sub_info @staticmethod @@ -566,147 +461,71 @@ class ClashRuleProviderService: logger.warn(f"[{ruleset}] {api_url} 刷新失败") def add_notification_job(self, ruleset_names: List[str]): - if not self.config.enabled or not self.scheduler: + if not self.state.config.enabled or not self.scheduler: return for ruleset in ruleset_names: if ruleset in self.state.rule_provider: self.scheduler.add_job( ClashRuleProviderService.async_notify_clash, "date", run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + - timedelta(seconds=self.config.refresh_delay), - args=(ruleset, self.config.dashboard_url, - self.config.dashboard_secret), + timedelta(seconds=self.state.config.refresh_delay), + args=(ruleset, self.state.config.dashboard_url, + self.state.config.dashboard_secret), id=f'CRP-notify-clash{ruleset}', replace_existing=True, - misfire_grace_time=Crp.MISFIRE_GRACE_TIME + misfire_grace_time=Constant.MISFIRE_GRACE_TIME ) - def clash_config(self) -> Optional[Dict[str, Any]]: - if not self.state.clash_template_dict: - config: Dict[str, Any] = copy.deepcopy(Crp.DEFAULT_CLASH_CONF) + def build_clash_config(self, param: ConfigRequest) -> ClashConfig | None: + if not self.state.clash_template: + config = ClashConfig() else: - config: Dict[str, Any] = copy.deepcopy(self.state.clash_template_dict) + config = self.state.clash_template.model_copy(deep=True) - for key, default in Crp.DEFAULT_CLASH_CONF.items(): - if isinstance(default, dict): - assert key in {'rule-providers', 'proxy-providers'} - UtilsProvider.update_with_checking(self.value_from_sub_conf(key), config.get(key, {})) - elif isinstance(default, list): - assert key in {'rules', 'proxies', 'proxy-groups'} - self._extend_with_name_checking(config.get(key, []), self.value_from_sub_conf(key)) - proxies = [] - for proxy in self.get_proxies(): - if any(p.get(Crp.KEY_NAME) == proxy.get(Crp.KEY_NAME, '') for p in proxies): - logger.warn(f"Proxy named {proxy.get(Crp.KEY_NAME)!r} already exists. Skipping...") - continue - proxies.append(proxy) - if proxies: - config[Crp.KEY_PROXIES] = proxies - self.sync_ruleset() - # 添加代理组 - proxy_groups = copy.deepcopy(self.state.proxy_groups) - if proxy_groups: - config[Crp.KEY_PROXY_GROUPS] = self._extend_with_name_checking(config[Crp.KEY_PROXY_GROUPS], - proxy_groups) - # 添加按大洲代理组 - if self.config.group_by_region or self.config.group_by_country: - groups_by_region = self.proxy_groups_by_region() - if groups_by_region: - config[Crp.KEY_PROXY_GROUPS] = self._extend_with_name_checking(config[Crp.KEY_PROXY_GROUPS], - groups_by_region) - # 移除无效出站, 避免配置错误 - config[Crp.KEY_PROXY_GROUPS] = self._remove_invalid_outbounds(config.get(Crp.KEY_PROXIES, []), - config.get(Crp.KEY_PROXY_GROUPS, [])) - config[Crp.KEY_PROXY_GROUPS] = self._remove_invalid_proxy_providers( - self.all_proxy_providers(), - config.get(Crp.KEY_PROXY_GROUPS, []) + # Merge subscriptions + self._merge_subscriptions(config) + + # Add proxies + config.proxies += self._filter_available_items(self.state.proxies, param) + config.proxies = list( + filter(lambda p: not any(kw in p.name for kw in self.state.config.filter_keywords), config.proxies) ) - top_rules = [] - outbound_names = list(x.get(Crp.KEY_NAME) for x in self.clash_outbound()) + # Add proxy groups + config.proxy_groups += self._filter_available_items(self.state.proxy_groups, param) - # 添加 extra rule providers - if self.state.rule_providers: - config['rule-providers'].update(self.state.rule_providers) + # Add region groups + if self.state.config.group_by_region or self.state.config.group_by_country: + config.proxy_groups += [pg.data for pg in self.proxy_groups_by_region()] - # 通过 ruleset rules 添加 rule-providers - self.state.rule_provider = {} - for r in self.state.ruleset_rules_manager.rules: - rule = r.rule - action_str = ClashRuleParser.action_string(rule.action) - rule_provider_name = f'{self.config.ruleset_prefix}{action_str}' - if rule_provider_name not in self.state.rule_provider: - path_name = hashlib.sha256(action_str.encode('utf-8')).hexdigest()[:10] - self.state.ruleset_names[path_name] = rule_provider_name - sub_url = (f"{self.config.movie_pilot_url}/api/v1/plugin/ClashRuleProvider/ruleset?" - f"name={path_name}&apikey={self.config.apikey or settings.API_TOKEN}") - self.state.rule_provider[rule_provider_name] = {"behavior": "classical", - "format": "yaml", - "interval": 3600, - "path": f"./CRP/{path_name}.yaml", - "type": "http", - "url": sub_url} - config['rule-providers'].update(self.state.rule_provider) - # 添加规则 - for r in self.state.top_rules_manager: - rule = r.rule - if (not isinstance(rule.action, Action) and rule.action not in outbound_names and - rule.rule_type != RoutingRuleType.SUB_RULE): - logger.warn(f"出站 {rule.action} 不存在, 跳过 {rule.raw_rule}") - continue - if rule.rule_type == RoutingRuleType.RULE_SET: - # 添加ACL4SSR Rules - if rule.payload in self.state.acl4ssr_providers: - config['rule-providers'][rule.payload] = self.state.acl4ssr_providers[rule.payload] - if rule.payload not in config.get('rule-providers', {}): - logger.warn(f"规则集合 {rule.payload!r} 不存在, 跳过 {rule.raw_rule!r}") - continue - top_rules.append(str(rule)) - for raw_rule in config.get("rules", []): - rule = ClashRuleParser.parse_rule_line(raw_rule) - if not rule: - logger.warn(f"无效的规则 {raw_rule!r}, 跳过") - continue + # Add rule providers (Load once) + current_rule_providers = self.state.rule_providers + rule_providers = {} + for rp_data in current_rule_providers: + if rp_data.meta.available(param): + rule_providers[rp_data.name] = rp_data.data + config.rule_providers = config.rule_providers | rule_providers - if (not isinstance(rule.action, Action) and rule.action not in outbound_names and - rule.rule_type != RoutingRuleType.SUB_RULE): - logger.warn(f"出站 {rule.action!r} 不存在, 跳过 {rule.raw_rule!r}") - continue - top_rules.append(str(rule)) - config["rules"] = top_rules + # Apply patches + config = self._apply_patch_to_config(config) - # 添加 Hosts - if self.state.hosts: - config.setdefault('hosts', {}) - new_hosts = { - item['domain']: item.get('value', []) if not item.get( - 'using_cloudflare') else self.config.best_cf_ip - for item in self.state.hosts if item.get('domain') - } - config["hosts"] = {**config["hosts"], **new_hosts} + # Sync and add auto rule providers + self.sync_ruleset() + self._process_auto_rule_providers(config) - if self.state.rule_provider: - config['rule-providers'] = config.get('rule-providers') or {} - config['rule-providers'].update(self.state.rule_provider) + # Add rules (including ACL4SSR) + self._process_rules(config, param) - key_to_delete = [] - for key, item in self.state.ruleset_names.items(): - if item not in config.get('rule-providers', {}): - key_to_delete.append(key) - for key in key_to_delete: - del self.state.ruleset_names[key] - if not config.get("rule-providers"): - del config["rule-providers"] + # Add Hosts + hosts = self.state.hosts.to_dict(self.state.config.best_cf_ip) + if hosts: + config.hosts = config.hosts or {} + config.hosts = config.hosts | hosts - # 对代理组进行回环检测 - proxy_graph = self._build_graph(config) - cycles = UtilsProvider.find_cycles(proxy_graph) - # 警告但不处理 - if cycles: - logger.warn("发现代理组回环:") - for cycle in cycles: - logger.warn(" -> ".join(cycle)) + # Cleanup ruleset names + self._cleanup_ruleset_names(config) + + # Cycle check + self._check_cycles(config) - self.store.save_data('ruleset_names', self.state.ruleset_names) - self.store.save_data('rule_provider', self.state.rule_provider) return config def delete_proxy_group(self, name: str) -> Tuple[bool, str]: @@ -714,62 +533,121 @@ class ClashRuleProviderService: Deletes a proxy group by name and saves the state. Returns True if a group was deleted, False otherwise. """ - initial_len = len(self.state.proxy_groups) - self.state.proxy_groups = [item for item in self.state.proxy_groups if item.get(Crp.KEY_NAME) != name] - if len(self.state.proxy_groups) < initial_len: - self.store.save_data('proxy_groups', self.state.proxy_groups) - return True, '' - return False, '' + pgs = self.state.proxy_groups + deleted = pgs.pop(name) + if deleted: + self.state.proxy_groups = pgs + return True, "代理组删除成功" + return False, f"代理组 {name!r} 不存在" - def add_proxy_group(self, item: ProxyGroup) -> Tuple[bool, str]: + def delete_proxy_group_patch(self, name: str) -> tuple[bool, str]: + patches = self.state.proxy_group_patch + if name in patches: + del patches.root[name] + self.state.proxy_group_patch = patches + return True, "补丁已删除" + return False, "补丁不存在" + + def update_proxy_group_meta(self, name: str, meta: Metadata) -> tuple[bool, str]: + pgs = self.state.proxy_groups + res = pgs.set_meta(name, meta) + if res: + self.state.proxy_groups = pgs + return True, "" + return False, f"代理组 {name!r} 不存在" + + def add_proxy_group(self, proxy_group: ProxyGroup) -> tuple[bool, str]: """ Adds a new proxy group, saves the state, and returns status. """ - if any(x.get(Crp.KEY_NAME) == item.root.name for x in self.state.proxy_groups): - return False, f"The proxy group name {item.root.name} already exists" try: - proxy_group = ProxyGroup.model_validate(item) + pgs = self.state.proxy_groups + pgs.add(ProxyGroupData(data=proxy_group, name=proxy_group.name, meta=Metadata(source=DataSource.MANUAL))) + self.state.proxy_groups = pgs except Exception as e: - logger.error(f"Failed to parse proxy group: {repr(e)}") - return False, "Failed to parse proxy group" - self.state.proxy_groups.append(proxy_group.model_dump(by_alias=True, exclude_none=True)) - self.store.save_data('proxy_groups', self.state.proxy_groups) - return True, "Proxy group added successfully." + logger.error(f"Failed to add proxy group: {repr(e)}") + return False, "代理组添加失败" + return True, "代理组添加成功" - def update_proxy_group(self, previous_name: str, item: ProxyGroup) -> Tuple[bool, str]: - proxy_group = item.root - region_groups = {g[Crp.KEY_NAME] for g in self.proxy_groups_by_region()} - if previous_name in region_groups: - self.overwrite_region_group(item) - return True, '' + def get_proxy_groups(self, patched = True) -> list[ProxyGroupData]: + pgs = self.state.all_proxy_groups + pgs += self.proxy_groups_by_region() + if not patched: + return pgs + return self._apply_patches(pgs, self.state.proxy_group_patch) - index = next((i for i, x in enumerate(self.state.proxy_groups) if x.get(Crp.KEY_NAME) == previous_name), None) - if index is None: - return False, f"Proxy group {previous_name!r} does not exist" + def update_proxy_group(self, previous_name: str, source: str, proxy_group: ProxyGroup) -> tuple[bool, str]: + if source == DataSource.MANUAL: + pgs = self.state.proxy_groups + pgs.update(previous_name, ProxyGroupData(data=proxy_group, name=proxy_group.name, meta=Metadata())) + self.state.proxy_groups = pgs + return True, "代理组更新成功" + if previous_name != proxy_group.name: + return False, "请勿修改代理组名称" + pgs = self.proxy_groups_by_region() + src = next((g for g in pgs if g.name == previous_name), None) + if src is None: + return False, f"代理组 {previous_name!r} ({source}) 不存在" + self._make_proxy_group_patch(src.data, proxy_group) + return True, "代理组更新成功" - new_name_index = next((i for i, x in enumerate(self.state.proxy_groups) if - x.get(Crp.KEY_NAME) == proxy_group.name), None) - if new_name_index is not None and new_name_index != index: - return False, f"The proxy group name {proxy_group.name} already exists" - self.state.proxy_groups[index] = proxy_group.model_dump(by_alias=True, exclude_none=True) - self.store.save_data('proxy_groups', self.state.proxy_groups) - return True, '' - - def update_rule_provider(self, name: str, rule_provider_data: RuleProviderData) -> Tuple[bool, str]: + def update_rule_provider(self, name: str, rule_provider: RuleProviderData) -> Tuple[bool, str]: """ - Updates a rule provider, saves the state, and returns status. + Updates a rule provider. """ - new_name = rule_provider_data.name - if name != new_name: - self.state.rule_providers.pop(name, None) - self.state.rule_providers[new_name] = rule_provider_data.rule_provider.model_dump(by_alias=True, - exclude_none=True) - self.store.save_data('extra_rule_providers', self.state.rule_providers) - return True, "Rule provider updated successfully." + rps = self.state.rule_providers + if name not in rps: + return False, f"规则集 {name!r} 不存在" + rps.update(name, rule_provider) + self.state.rule_providers = rps + return True, "规则集更新成功" - def delete_rule_provider(self, name: str): - self.state.rule_providers.pop(name, None) - self.store.save_data('extra_rule_providers', self.state.rule_providers) + def update_rule_providers_meta(self, name: str, meta: Metadata) -> tuple[bool, str]: + rps = self.state.rule_providers + if name in rps: + res = rps.set_meta(name, meta) + if res: + self.state.rule_providers = rps + return True, "" + + arps = self.state.acl4ssr_providers + if name in arps: + res = arps.set_meta(name, meta) + if res: + self.state.acl4ssr_providers = arps + return True, "" + return False, f"规则集 {name!r} 不存在" + + def update_rule_meta(self, rule_type: RuleSet, priority: int, meta: Metadata) -> tuple[bool, str]: + manager = self.state.get_rule_manager(rule_type) + rule = manager.get_rule_at_priority(priority) + if not rule: + return False, "规则不存在" + res = manager.update_rule_meta_at_priority(priority, meta) + if res: + if rule_type == RuleSet.RULESET: + self.add_notification_job([f"{self.state.config.ruleset_prefix}{rule.rule.action}"]) + self.organize_and_save_rules() + return True, "" + return False, "更新规则元数据失败" + + def delete_rule_provider(self, name: str) -> tuple[bool, str]: + rps = self.state.rule_providers + deleted = rps.pop(name) + if deleted: + self.state.rule_providers = rps + return True, f"规则集删除成功" + return False, f"规则集 {name!r} 不存在" + + def add_rule_provider(self, name: str, rule_provider: RuleProvider) -> tuple[bool, str]: + try: + rps = self.state.rule_providers + rps.add(RuleProviderData(data=rule_provider, name=name, meta=Metadata(source=DataSource.MANUAL))) + self.state.rule_providers = rps + except Exception as e: + logger.error(f"Failed to add rule provider: {repr(e)}") + return False, "规则集添加失败" + return True, "规则集添加成功" async def test_connectivity(self, clash_apis: List[ClashApi], sub_links: List[str]) -> Tuple[bool, str]: tasks = [] @@ -785,7 +663,7 @@ class ClashRuleProviderService: for sub_link in sub_links: task = asyncio.create_task( AsyncRequestUtils( - accept_type="text/html", proxies=settings.PROXY if self.config.proxy else None, + accept_type="text/html", proxies=settings.PROXY if self.state.config.proxy else None, timeout=5).get(sub_link) ) urls.append(sub_link) @@ -798,56 +676,53 @@ class ClashRuleProviderService: def get_status(self) -> Dict[str, Any]: data = { - "state": self.config.enabled, - "ruleset_prefix": self.config.ruleset_prefix, - "best_cf_ip": self.config.best_cf_ip, + "state": self.state.config.enabled, + "ruleset_prefix": self.state.config.ruleset_prefix, + "preset_identifiers": self.state.config.identifiers, + "best_cf_ip": self.state.config.best_cf_ip, "geoRules": self.state.geo_rules, "subscription_info": self.state.subscription_info, - "sub_url": f"{self.config.movie_pilot_url}/api/v1/plugin/{self.plugin_id}/config?" - f"apikey={self.config.apikey or settings.API_TOKEN}" + "sub_url": f"{self.state.config.movie_pilot_url}/api/v1/plugin/{self.plugin_id}/config?" + f"apikey={self.state.config.apikey or settings.API_TOKEN}" } return data - def get_rules(self, rule_type: str) -> List[Dict[str, Any]]: - manager = self.state.ruleset_rules_manager \ - if rule_type == 'ruleset' else self.state.top_rules_manager + def get_rules(self, ruleset: RuleSet) -> list[RuleData]: + manager = self.state.get_rule_manager(ruleset) return manager.to_list() - def reorder_rules(self, rule_type: str, moved_priority: int, target_priority: int) -> Tuple[bool, str]: + def reorder_rules(self, rule_type: RuleSet, moved_priority: int, target_priority: int) -> tuple[bool, str]: + manager = self.state.get_rule_manager(rule_type) try: - if rule_type == 'ruleset': - rule = self.state.ruleset_rules_manager.reorder_rules(moved_priority, target_priority) + rule = manager.reorder_rules(moved_priority, target_priority) + if rule_type == RuleSet.RULESET: self.add_notification_job( - [f"{self.config.ruleset_prefix}{rule.rule.action}"]) - else: - self.state.top_rules_manager.reorder_rules(moved_priority, target_priority) + [f"{self.state.config.ruleset_prefix}{rule.rule.action}"]) except Exception as e: logger.info(f"Failed to reorder rules: {repr(e)}") return False, "规则移动失败" self.organize_and_save_rules() return True, "" - def update_rule(self, rule_type: str, priority: int, rule_data: RuleData) -> Tuple[bool, str]: + def update_rule(self, rule_type: RuleSet, priority: int, rule_data: RuleData) -> tuple[bool, str]: try: dst_priority = rule_data.priority src_priority = priority - clash_rule = ClashRuleParser.parse_rule_dict(rule_data.model_dump(exclude_none=True)) + clash_rule = ClashRuleParser.parse_rule_dict(rule_data.model_dump(mode='json', exclude_none=True)) if not clash_rule: return False, f"无效的规则: {rule_data!r}" - if rule_type == 'ruleset': - manager = self.state.ruleset_rules_manager - original_rule = manager.get_rule_at_priority(src_priority) - rule_item = RuleItem(rule=clash_rule, remark=original_rule.remark, time_modified=time.time()) + manager = self.state.get_rule_manager(rule_type) + original_rule = manager.get_rule_at_priority(src_priority) + meta = Metadata(source=original_rule.meta.source, time_modified=time.time()) + rule_item = RuleItem(rule=clash_rule, meta=meta) + if rule_type == RuleSet.RULESET: res = manager.update_rule_at_priority(rule_item, src_priority, dst_priority) if res: - ruleset_to_notify = [f"{self.config.ruleset_prefix}{clash_rule.action}"] + ruleset_to_notify = [f"{self.state.config.ruleset_prefix}{clash_rule.action}"] if rule_data.action != original_rule.rule.action: - ruleset_to_notify.append(f"{self.config.ruleset_prefix}{original_rule.rule.action}") + ruleset_to_notify.append(f"{self.state.config.ruleset_prefix}{original_rule.rule.action}") self.add_notification_job(ruleset_to_notify) else: - manager = self.state.top_rules_manager - original_rule = manager.get_rule_at_priority(src_priority) - rule_item = RuleItem(rule=clash_rule, remark=original_rule.remark, time_modified=time.time()) res = manager.update_rule_at_priority(rule_item, src_priority, dst_priority) except Exception as err: logger.info(f"Failed to update rules: {repr(err)}") @@ -855,16 +730,17 @@ class ClashRuleProviderService: self.organize_and_save_rules() return res, "" - def add_rule(self, rule_type: str, rule_data: RuleData) -> Tuple[bool, str]: + def add_rule(self, rule_type: RuleSet, rule_data: RuleData) -> tuple[bool, str]: try: priority = rule_data.priority - clash_rule = ClashRuleParser.parse_rule_dict(rule_data.model_dump(exclude_none=True)) + clash_rule = ClashRuleParser.parse_rule_dict(rule_data.model_dump(mode='json', exclude_none=True)) if not clash_rule: - return False, f"无效的输入规则: {rule_data.model_dump(exclude_none=True)}" - rule_item = RuleItem(rule=clash_rule, remark='Manual', time_modified=time.time()) - if rule_type == 'ruleset': + return False, f"无效的输入规则: {rule_data.model_dump(mode='json', exclude_none=True)}" + meta = Metadata(source=DataSource.MANUAL, time_modified=time.time()) + rule_item = RuleItem(rule=clash_rule, meta=meta) + if rule_type == RuleSet.RULESET: self.state.ruleset_rules_manager.insert_rule_at_priority(rule_item, priority) - self.add_notification_job([f"{self.config.ruleset_prefix}{clash_rule.action}"]) + self.add_notification_job([f"{self.state.config.ruleset_prefix}{clash_rule.action}"]) else: self.state.top_rules_manager.insert_rule_at_priority(rule_item, priority) except Exception as err: @@ -873,28 +749,43 @@ class ClashRuleProviderService: self.organize_and_save_rules() return True, "" - def delete_rule(self, rule_type: str, priority: int): - if rule_type == 'ruleset': - res = self.state.ruleset_rules_manager.remove_rule_at_priority(priority) + def delete_rule(self, ruleset: RuleSet, priority: int): + manager = self.state.get_rule_manager(ruleset) + res = manager.remove_rule_at_priority(priority) + if ruleset == RuleSet.RULESET: if res: - rule = res.rule - action_str = ClashRuleParser.action_string(rule.action) - self.add_notification_job([f"{self.config.ruleset_prefix}{action_str}"]) - else: - self.state.top_rules_manager.remove_rule_at_priority(priority) + self.add_notification_job([f"{self.state.config.ruleset_prefix}{res.rule.action}"]) self.organize_and_save_rules() - def import_rules(self, params: Dict[str, Any]) -> Tuple[bool, str]: + def delete_rules(self, ruleset: RuleSet, priorities: list[int]): + manager = self.state.get_rule_manager(ruleset) + removed = manager.remove_rules_at_priorities(priorities) + if ruleset == RuleSet.RULESET: + if removed: + actions = {r.rule.action for r in removed} + self.add_notification_job([f"{self.state.config.ruleset_prefix}{action}" for action in actions]) + self.organize_and_save_rules() + + def set_rules_status(self, ruleset: RuleSet, priorities: dict[int, bool]): + manager = self.state.get_rule_manager(ruleset) + updated = manager.update_rules_at_priorities(priorities) + if ruleset == RuleSet.RULESET: + if updated: + actions = {r.rule.action for r in updated} + self.add_notification_job([f"{self.state.config.ruleset_prefix}{action}" for action in actions]) + self.organize_and_save_rules() + + def import_rules(self, vehicle: str, payload: str) -> tuple[bool, str]: rules: List[str] = [] - if params.get('type') == 'YAML': + if vehicle == 'YAML': try: - imported_rules = yaml.load(params["payload"], Loader=yaml.SafeLoader) + imported_rules = yaml.load(payload, Loader=yaml.SafeLoader) if not isinstance(imported_rules, dict): return False, "无效的输入" - rules = imported_rules.get("rules", []) except yaml.YAMLError as err: logger.error(f"Failed to import rules: {repr(err)}") return False, 'YAML 格式错误' + rules = imported_rules.get(ClashKey.RULES, []) self.append_top_rules(rules) return True, "" @@ -906,71 +797,56 @@ class ClashRuleProviderService: res = yaml.dump({"payload": rules}, allow_unicode=True) return res - def get_hosts(self) -> List[Dict[str, Any]]: - return self.state.hosts + def update_hosts(self, domain: str, host: HostData) -> tuple[bool, str]: + hosts = self.state.hosts + hosts.update(domain, host) + self.state.hosts = hosts + return True, f"Host for domain {host.domain} updated successfully." - def update_hosts(self, param: HostData) -> Tuple[bool, str]: - if not param.value: - return False, "无效的参数" - value = param.value.model_dump(exclude_none=True) - for i, host in enumerate(self.state.hosts): - if host.get('domain') == param.domain: - self.state.hosts[i] = {**host, **value} - self.store.save_data('hosts', self.state.hosts) - logger.info(f"Host for domain {param.domain} updated successfully.") - return True, "" - self.state.hosts.append(value) - self.store.save_data('hosts', self.state.hosts) - return True, "" - - def delete_host(self, param: HostData) -> Tuple[bool, str]: - original_len = len(self.state.hosts) - self.state.hosts = [host for host in self.state.hosts if host.get('domain') != param.domain] - self.store.save_data('hosts', self.state.hosts) - - if len(self.state.hosts) < original_len: + def delete_host(self, domain: str) -> tuple[bool, str]: + hosts = self.state.hosts + original_len = len(hosts) + hosts.delete(domain) + if len(hosts) < original_len: + self.state.hosts = hosts return True, '' else: - return False, f'Host for domain {param.domain} not found.' + return False, f'Host for domain {domain} not found.' async def refresh_subscription(self, url: str) -> Tuple[bool, str]: - sub_conf = next((conf for conf in self.config.subscriptions_config if conf.url == url), None) + sub_conf = next((conf for conf in self.state.config.subscriptions_config if conf.url == url), None) if not sub_conf: return False, f"Configuration for {url} not found." - config, info = await self.async_get_subscription(url, sub_conf.model_dump()) + config, info = await self.async_get_subscription(url) if not config: return False, f"订阅链接 {url} 更新失败" - self.state.clash_configs[url] = config - self.__sync_sub_proxies(url, config) - self.state.subscription_info[url] = {**info, - 'enabled': self.state.subscription_info.get(url, {}).get( - 'enabled', True)} - self.store.save_data('clash_configs', self.state.clash_configs) - self.store.save_data('subscription_info', self.state.subscription_info) + sub_configs = self.state.sub_configs + sub_configs[url] = config + self.state.sub_configs = sub_configs + + sub_info_map = self.state.subscription_info + info.enabled = sub_info_map.get(url).enabled + sub_info_map[url] = info + self.state.subscription_info = sub_info_map return True, "订阅更新成功" - def update_subscription_info(self, sub_info: SubscriptionInfo): - self.state.subscription_info[sub_info.url][sub_info.field] = sub_info.value - self.store.save_data('subscription_info', self.state.subscription_info) + def update_subscription_info(self, sub_setting: SubscriptionSetting): + sub_info = self.state.subscription_info + sub_info.set(sub_setting) + self.state.subscription_info = sub_info - def add_proxies_to_manager(self, proxies: List[Dict[str, Any]], remark: str, raw: Optional[str] = None): - for proxy in proxies: - try: - self.state.proxies_manager.add_proxy_dict(proxy, remark=remark, raw=raw) - except Exception as e: - logger.error(f"Failed to add proxies: {e}") - - async def async_get_subscription(self, url: str, conf: Dict[str, Any] - ) -> Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]: + async def async_get_subscription(self, url: str) -> tuple[ClashConfig | None, SubscriptionInfo | None]: if not url: return None, None logger.info(f"正在刷新 {UtilsProvider.get_url_domain(url)} ...") ret = None - for _ in range(self.config.retry_times): - ret = await AsyncRequestUtils(accept_type="text/html", timeout=self.config.timeout, - proxies=settings.PROXY if self.config.proxy else None - ).get_res(url) + raw_proxies = {} + for _ in range(self.state.config.retry_times): + ret = await AsyncRequestUtils( + accept_type="text/html", timeout=self.state.config.timeout, ua="clash.meta", + proxies=settings.PROXY if self.state.config.proxy else None + ).get_res(url) if ret: break if not ret: @@ -983,111 +859,116 @@ class ClashRuleProviderService: proxies = Converter().convert_v2ray(content) if not proxies: raise ValueError("Unknown content type") - rs = {Crp.KEY_PROXIES: proxies, - Crp.KEY_PROXY_GROUPS: [ - {Crp.KEY_NAME: "All Proxies", 'type': 'select', 'include-all-proxies': True}]} - + rs = { + ClashKey.PROXIES: proxies.values(), + ClashKey.PROXY_GROUPS: [ + {ClashKey.NAME: "All Proxies", 'type': 'select', 'include-all-proxies': True} + ] + } + raw_proxies = {p['name']: link for link, p in proxies.items()} if not isinstance(rs, dict): raise ValueError("Subscription content is not a valid dictionary.") rs: dict[str, Any] = rs - logger.info(f"已刷新: {UtilsProvider.get_url_domain(url)}. 节点数量: {len(rs.get(Crp.KEY_PROXIES, []))}") - for key, default in Crp.DEFAULT_CLASH_CONF.items(): - rs.setdefault(key, copy.deepcopy(default)) - if not conf.get(key, False): - rs[key] = copy.deepcopy(default) + logger.info(f"已刷新: {UtilsProvider.get_url_domain(url)}. 节点数量: {len(rs.get(ClashKey.PROXIES, []))}") + conf = ClashConfig.model_validate(rs) except Exception as e: logger.error(f"解析配置出错: {e}") return None, None - - sub_info = {'last_update': int(time.time()), 'proxy_num': len(rs.get(Crp.KEY_PROXIES, []))} + info = {"last_update": int(time.time()), "proxy_num": conf.node_num} if 'Subscription-Userinfo' in ret.headers: matches = re.findall(r'(\w+)=(\d+)', ret.headers['Subscription-Userinfo']) variables = {key: int(value) for key, value in matches} - sub_info.update(variables) - return rs, sub_info + info.update(variables) + sub_info = SubscriptionInfo(**info) + conf.raw_proxies = raw_proxies + return conf, sub_info async def async_refresh_subscriptions(self) -> Dict[str, bool]: res = {} - for sub_conf in self.config.subscriptions_config: + sub_info_map = self.state.subscription_info + sub_configs_map = self.state.sub_configs + + for sub_conf in self.state.config.subscriptions_config: url = sub_conf.url - if not self.state.subscription_info.get(url, {}).get('enabled'): + if not sub_info_map.get(url).enabled: continue - conf, sub_info = await self.async_get_subscription(url, conf=sub_conf.model_dump()) + conf, sub_info = await self.async_get_subscription(url) if not conf: res[url] = False continue - self.state.subscription_info[url] = {**sub_info, 'enabled': True} + sub_info_map[url] = sub_info res[url] = True - self.state.clash_configs[url] = conf - self.__sync_sub_proxies(url, conf) - self.store.save_data('subscription_info', self.state.subscription_info) - self.store.save_data('clash_configs', self.state.clash_configs) + sub_configs_map[url] = conf + self.state.subscription_info = sub_info_map + self.state.sub_configs = sub_configs_map return res - def __sync_sub_proxies(self, url: str, conf: Dict[str, Any]): - remark = f"Sub:{UtilsProvider.get_url_domain(url)}-{abs(hash(url))}" - self.state.proxies_manager.remove_proxies_by_condition(lambda p: p.remark == remark) - self.add_proxies_to_manager(conf.get(Crp.KEY_PROXIES, []), remark) - async def async_refresh_acl4ssr(self): logger.info("正在刷新 ACL4SSR ...") paths = ['Clash/Providers', 'Clash/Providers/Ruleset'] - api_url = f"{Crp.ACL4SSR_API}/contents/%s" + api_url = f"{Constant.ACL4SSR_API}/contents/%s" branch = 'master' - new_providers = {} + new_providers = [] + names = set() for path in paths: response = await AsyncRequestUtils().get_res(api_url % path, headers=settings.GITHUB_HEADERS, params={'ref': branch}) if not response: continue files = response.json() - yaml_files = [f for f in files if f["type"] == "file" and f[Crp.KEY_NAME].endswith((".yaml", ".yml"))] + yaml_files = [f for f in files if f["type"] == "file" and f[ClashKey.NAME].endswith((".yaml", ".yml"))] for f in yaml_files: - name = f"{self.config.acl4ssr_prefix}{f[Crp.KEY_NAME][:f[Crp.KEY_NAME].rfind('.')]}" + name = f"{self.state.config.acl4ssr_prefix}{f[ClashKey.NAME][:f[ClashKey.NAME].rfind('.')]}" + if name in names: + continue file_path = f"./ACL4SSR/{f['name']}" - provider = {'type': 'http', 'path': file_path, 'url': f["download_url"], 'interval': 600, - 'behavior': 'classical', 'format': 'yaml', 'size-limit': 0} - if name not in new_providers: - new_providers[name] = provider - self.state.acl4ssr_providers = new_providers - self.store.save_data('acl4ssr_providers', self.state.acl4ssr_providers) + provider = RuleProvider( + type=VehicleType.HTTP, path=file_path, url=f["download_url"], interval=600, behavior="classical", + format="yaml" + ) + meta = Metadata(source=DataSource.ACL4SSR) + new_providers.append(RuleProviderData(name=name, data=provider, meta=meta)) + names.add(name) + + self.state.acl4ssr_providers = RuleProviders.model_validate(new_providers) logger.info(f"ACL4SSR 规则集刷新完成. 规则集数量: {len(self.state.acl4ssr_providers)}") async def async_refresh_geo_dat(self): logger.info("正在刷新 Geo Rules ...") branch = 'meta' - api_url = f"{Crp.METACUBEX_RULE_DAT_API}/contents/geo" + api_url = f"{Constant.METACUBEX_RULE_DAT_API}/contents/geo" resp = await AsyncRequestUtils().get_res(api_url, headers=settings.GITHUB_HEADERS, params={'ref': branch}) if not resp: return + geo_rules = self.state.geo_rules for path in resp.json(): - if path["type"] == "dir" and path["name"] in self.state.geo_rules: + if path["type"] == "dir" and path["name"] in geo_rules.model_fields: tree_sha = path["sha"] - url = f"{Crp.METACUBEX_RULE_DAT_API}/git/trees/{tree_sha}" + url = f"{Constant.METACUBEX_RULE_DAT_API}/git/trees/{tree_sha}" res = await AsyncRequestUtils().get_res(url, headers=settings.GITHUB_HEADERS, params={'ref': branch}) if not res: continue tree = res.json() yaml_files = [item["path"][:item["path"].rfind('.')] for item in tree["tree"] if item["type"] == "blob" and item['path'].endswith((".yaml", ".yml"))] - self.state.geo_rules[path["name"]] = yaml_files - self.store.save_data('geo_rules', self.state.geo_rules) - logger.info( - f"Geo Rules 更新完成. 规则数量: {', '.join([f'{k}({len(v)})' for k, v in self.state.geo_rules.items()])}") + setattr(geo_rules, path["name"], yaml_files) + self.state.geo_rules = geo_rules + logger.info(f"Geo Rules 更新完成. 规则数量: " + f"geoip({len(self.state.geo_rules.geoip)}), geosite({len(self.state.geo_rules.geosite)})") - def check_proxies_lifetime(self): - for proxy in self.state.proxies_manager: - proxy_name = proxy.proxy.name - if proxy_name in self.state.overwritten_proxies: - self.state.overwritten_proxies[proxy_name][ - 'lifetime'] = Crp.OVERWRITTEN_PROXIES_LIFETIME - outdated_proxies = [] - for proxy_name, data in self.state.overwritten_proxies.items(): - if proxy_name not in self.state.proxies_manager: - data['lifetime'] = data.get('lifetime', Crp.OVERWRITTEN_PROXIES_LIFETIME) - 1 - if data['lifetime'] < 0: - outdated_proxies.append(proxy_name) - for proxy_name in outdated_proxies: - del self.state.overwritten_proxies[proxy_name] - self.store.save_data('overwritten_proxies', self.state.overwritten_proxies) + def check_patch_lifetime(self): + pp = self.state.proxy_patch + proxies = self.state.all_proxies + pp.update_patch({g.name for g in proxies}, lifespan=Constant.PATCH_LIFESPAN) + self.state.proxy_patch = pp + + groups = self.proxy_groups_by_region() + self.state.all_proxy_groups + pgp = self.state.proxy_group_patch + pgp.update_patch({g.name for g in groups}, lifespan=Constant.PATCH_LIFESPAN) + self.state.proxy_group_patch = pgp + + rpp = self.state.rule_provider_patch + rule_providers = self.state.all_rule_providers + rpp.update_patch({g.name for g in rule_providers}, lifespan=Constant.PATCH_LIFESPAN) + self.state.rule_provider_patch = rpp diff --git a/plugins.v2/clashruleprovider/state.py b/plugins.v2/clashruleprovider/state.py index 369bbd9..5f41741 100644 --- a/plugins.v2/clashruleprovider/state.py +++ b/plugins.v2/clashruleprovider/state.py @@ -1,33 +1,283 @@ -from dataclasses import dataclass, field -from typing import Any, Dict, List +from itertools import chain +from typing import Any, Generator, Callable +from pydantic import TypeAdapter + +from app.core.cache import Cache +from app.db.plugindata_oper import PluginDataOper + +from .config import PluginConfig from .helper.clashrulemanager import ClashRuleManager -from .helper.proxiesmanager import ProxyManager +from .helper.utilsprovider import UtilsProvider +from .models import RuleProviderData, ProxyProviderData, ProxyGroupData, Hosts, ProxyGroups, RuleProviders, \ + RuleProvider, Metadata, Proxies, ProxyData +from .models.configuration import ClashConfig +from .models.types import DataSource, RuleSet, DataKey +from .models.datapatch import DataPatch +from .models.api import SubscriptionsInfo +from .models.datamodel import GeoRules, PersistState -@dataclass class PluginState: """ - A dataclass to hold all the runtime state of the ClashRuleProvider plugin. + A DAL to manage the runtime state of ClashRuleProvider. """ - # Rule and Proxy Managers - top_rules_manager: ClashRuleManager = field(default_factory=ClashRuleManager) - ruleset_rules_manager: ClashRuleManager = field(default_factory=ClashRuleManager) - proxies_manager: ProxyManager = field(default_factory=ProxyManager) + def __init__(self, plugin_id: str, config: PluginConfig = None): + self.plugin_id = plugin_id + self.config = config or PluginConfig() + self.plugin_data = PluginDataOper() + self.cache = Cache(maxsize=256, ttl=self.config.cache_ttl) + self.cache_region = f"app.plugins.{self.plugin_id.lower()}" - # Loaded from saved data - proxy_groups: List[Dict[str, Any]] = field(default_factory=list) - extra_proxies: List[Dict[str, Any]] = field(default_factory=list) - subscription_info: Dict[str, Any] = field(default_factory=dict) - rule_provider: Dict[str, Any] = field(default_factory=dict) - rule_providers: Dict[str, Any] = field(default_factory=dict) - ruleset_names: Dict[str, str] = field(default_factory=dict) - acl4ssr_providers: Dict[str, Any] = field(default_factory=dict) - clash_configs: Dict[str, Any] = field(default_factory=dict) - hosts: List[Dict[str, Any]] = field(default_factory=list) - overwritten_region_groups: Dict[str, Any] = field(default_factory=dict) - overwritten_proxies: Dict[str, Any] = field(default_factory=dict) - clash_template_dict: Dict[str, Any] = field(default_factory=dict) + # Build schemas from PersistState model + self._schemas: dict[str, tuple[TypeAdapter, Callable[[], Any]]] = {} + for _, field in PersistState.model_fields.items(): + alias = field.alias + if alias: + self._schemas[alias] = (TypeAdapter(field.annotation), field.default_factory) - # Volatile state (generated at runtime) - geo_rules: Dict[str, List[str]] = field(default_factory=lambda: {'geoip': [], 'geosite': []}) + # Rule and Proxy Managers (Runtime) + self.top_rules_manager: ClashRuleManager = ClashRuleManager() + self.ruleset_rules_manager: ClashRuleManager = ClashRuleManager() + + # Runtime variables (not persisted directly or persisted via config) + self.clash_template: ClashConfig = ClashConfig() + + def _get_val(self, key: str) -> Any: + # Check cache + if self.cache.exists(key, region=self.cache_region): + return self.cache.get(key, region=self.cache_region) + + data = self.plugin_data.get_data(self.plugin_id, key) + adapter, default_factory = self._schemas.get(key, (None, None)) + + if data is None: + if default_factory: + val = default_factory() + self.cache.set(key, val, region=self.cache_region) + return val + return None + + if adapter: + val = adapter.validate_python(data) + else: + val = data + + self.cache.set(key, val, region=self.cache_region) + return val + + def _set_val(self, key: str, value: Any): + adapter, _ = self._schemas.get(key, (None, None)) + if adapter: + data = adapter.dump_python(value, mode="json", by_alias=True, exclude_none=True) + else: + data = value + self.plugin_data.save(self.plugin_id, key, data) + self.cache.set(key, value, region=self.cache_region) + + @property + def proxies(self) -> Proxies: + return self._get_val(DataKey.PROXIES) + + @proxies.setter + def proxies(self, value: Proxies): + self._set_val(DataKey.PROXIES, value) + + @property + def proxy_groups(self) -> ProxyGroups: + return self._get_val(DataKey.PROXY_GROUPS) + + @proxy_groups.setter + def proxy_groups(self, value: ProxyGroups): + self._set_val(DataKey.PROXY_GROUPS, value) + + @property + def subscription_info(self) -> SubscriptionsInfo: + return self._get_val(DataKey.SUB_INFO) + + @subscription_info.setter + def subscription_info(self, value: SubscriptionsInfo): + self._set_val(DataKey.SUB_INFO, value) + + @property + def rule_provider(self) -> dict[str, RuleProvider]: + return self._get_val(DataKey.AUTO_RULE_PROVIDERS) + + @rule_provider.setter + def rule_provider(self, value: dict[str, RuleProvider]): + self._set_val(DataKey.AUTO_RULE_PROVIDERS, value) + + @property + def rule_providers(self) -> RuleProviders: + return self._get_val(DataKey.RULE_PROVIDERS) + + @rule_providers.setter + def rule_providers(self, value: RuleProviders): + self._set_val(DataKey.RULE_PROVIDERS, value) + + @property + def ruleset_names(self) -> dict[str, str]: + return self._get_val(DataKey.RULESET_NAMES) + + @ruleset_names.setter + def ruleset_names(self, value: dict[str, str]): + self._set_val(DataKey.RULESET_NAMES, value) + + @property + def acl4ssr_providers(self) -> RuleProviders: + return self._get_val(DataKey.ACL4SSR) + + @acl4ssr_providers.setter + def acl4ssr_providers(self, value: RuleProviders): + self._set_val(DataKey.ACL4SSR, value) + + @property + def sub_configs(self) -> dict[str, ClashConfig]: + sub_conf = self._get_val(DataKey.SUB_CONFIGS) + return sub_conf + + @sub_configs.setter + def sub_configs(self, value: dict[str, ClashConfig]): + self._set_val(DataKey.SUB_CONFIGS, value) + + @property + def hosts(self) -> Hosts: + return self._get_val(DataKey.HOSTS) + + @hosts.setter + def hosts(self, value: Hosts): + self._set_val(DataKey.HOSTS, value) + + @property + def proxy_group_patch(self) -> DataPatch: + return self._get_val(DataKey.PROXY_GROUP_PATCH) + + @proxy_group_patch.setter + def proxy_group_patch(self, value: DataPatch): + self._set_val(DataKey.PROXY_GROUP_PATCH, value) + + @property + def proxy_patch(self) -> DataPatch: + return self._get_val(DataKey.PROXY_PATCH) + + @proxy_patch.setter + def proxy_patch(self, value: DataPatch): + self._set_val(DataKey.PROXY_PATCH, value) + + @property + def rule_provider_patch(self) -> DataPatch: + return self._get_val(DataKey.RULE_PROVIDER_PATCH) + + @rule_provider_patch.setter + def rule_provider_patch(self, value: DataPatch): + self._set_val(DataKey.RULE_PROVIDER_PATCH, value) + + @property + def geo_rules(self) -> GeoRules: + return self._get_val(DataKey.GEO_RULES) + + @geo_rules.setter + def geo_rules(self, value: GeoRules): + self._set_val(DataKey.GEO_RULES, value) + + def get_data(self, key: str) -> Any: + return self.plugin_data.get_data(self.plugin_id, key) + + def save_data(self, key: str, value: Any): + self.plugin_data.save(self.plugin_id, key, value) + + def get_rule_manager(self, ruleset: RuleSet) -> ClashRuleManager: + if ruleset == RuleSet.RULESET: + return self.ruleset_rules_manager + return self.top_rules_manager + + def get_sub_config(self, url: str) -> ClashConfig: + conf = self.sub_configs.get(url) + if conf is None: + return ClashConfig() + ret = ClashConfig() + sub_options = self.config.get_sub_conf(url) + for field_name in sub_options.model_fields.keys(): + if getattr(sub_options, field_name) is True and field_name in ret.model_fields: + setattr(ret, field_name, getattr(conf, field_name)) + return ret + + def set_rule_providers(self, rule_providers: dict[str, dict[str, Any]]): + self.rule_provider.clear() + for name, rp in rule_providers.items(): + self.rule_providers[name] = RuleProvider(**rp) + + def rule_providers_from_subs(self) -> Generator[RuleProviderData, None, None]: + for url, conf in self.sub_configs.items(): + if self.config.get_sub_conf(url).rule_providers: + for name, rp in conf.rule_providers.items(): + meta = Metadata(source=DataSource.SUB, remark=UtilsProvider.get_url_domain(url)) + yield RuleProviderData(name=name, data=rp, meta=meta) + + def rule_providers_from_template(self) -> Generator[RuleProviderData, None, None]: + for name, rp in self.clash_template.rule_providers.items(): + yield RuleProviderData(meta=Metadata(source=DataSource.TEMPLATE), name=name, data=rp) + + def proxy_providers_from_subs(self) -> Generator[ProxyProviderData, None, None]: + for url, conf in self.sub_configs.items(): + if self.config.get_sub_conf(url).proxy_providers: + for name, pp in conf.proxy_providers.items(): + meta = Metadata(source=DataSource.SUB, remark=UtilsProvider.get_url_domain(url)) + yield ProxyProviderData(meta=meta, name=name, data=pp) + + def proxy_providers_from_template(self) -> Generator[ProxyProviderData, None, None]: + for name, pp in self.clash_template.proxy_providers.items(): + yield ProxyProviderData(meta=Metadata(source=DataSource.TEMPLATE), name=name, data=pp) + + def proxy_groups_from_subs(self) -> Generator[ProxyGroupData, None, None]: + for url, conf in self.sub_configs.items(): + if self.config.get_sub_conf(url).proxy_groups: + for pg in conf.proxy_groups: + meta = Metadata(source=DataSource.SUB, remark=UtilsProvider.get_url_domain(url)) + yield ProxyGroupData(meta=meta, data=pg, name=pg.name) + + def proxy_groups_from_template(self) -> Generator[ProxyGroupData, None, None]: + for pg in self.clash_template.proxy_groups: + yield ProxyGroupData(meta=Metadata(source=DataSource.TEMPLATE), data=pg, name=pg.name) + + def proxies_from_subs(self) -> Generator[ProxyData, None, None]: + for url, conf in self.sub_configs.items(): + for p in conf.proxies: + meta = Metadata(source=DataSource.SUB, remark=UtilsProvider.get_url_domain(url)) + yield ProxyData(meta=meta, data=p, name=p.name, v2ray_link=conf.raw_proxies.get(p.name)) + + def proxies_from_template(self) -> Generator[ProxyData, None, None]: + for p in self.clash_template.proxies: + yield ProxyData(meta=Metadata(source=DataSource.TEMPLATE), data=p, name=p.name) + + @property + def all_rule_providers(self) -> list[RuleProviderData]: + return list(chain( + self.rule_providers, + self.rule_providers_from_template(), + self.rule_providers_from_subs(), + self.acl4ssr_providers + )) + + @property + def all_proxy_providers(self) -> list[ProxyProviderData]: + return list(chain( + self.proxy_providers_from_subs(), + self.proxy_providers_from_template() + )) + + @property + def all_proxy_groups(self) -> list[ProxyGroupData]: + return list(chain( + self.proxy_groups, + self.proxy_groups_from_subs(), + self.proxy_groups_from_template() + )) + + @property + def all_proxies(self) -> list[ProxyData]: + return list(chain( + self.proxies, + self.proxies_from_subs(), + self.proxies_from_template() + )) diff --git a/plugins.v2/clashruleprovider/store.py b/plugins.v2/clashruleprovider/store.py deleted file mode 100755 index 4e07100..0000000 --- a/plugins.v2/clashruleprovider/store.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any, Optional - -from app.db.plugindata_oper import PluginDataOper - - -class PluginStore: - """数据持久化""" - def __init__(self, plugin_id: str): - self.plugin_id = plugin_id - self.plugin_data = PluginDataOper() - - def get_data(self, key: Optional[str] = None) -> Any: - return self.plugin_data.get_data(self.plugin_id, key) - - def save_data(self, key: str, value: Any): - self.plugin_data.save(self.plugin_id, key, value) - - def del_data(self, key: str) -> Any: - self.plugin_data.del_data(self.plugin_id, key)