mirror of
https://github.com/d0zingcat/MoviePilot-Plugins.git
synced 2026-05-13 15:09:12 +00:00
Merge pull request #820 from wumode/clashruleprovider
This commit is contained in:
@@ -450,11 +450,12 @@
|
||||
"name": "Clash Rule Provider",
|
||||
"description": "随时为Clash添加一些额外的规则。",
|
||||
"labels": "工具",
|
||||
"version": "1.1.3",
|
||||
"version": "1.2.0",
|
||||
"icon": "Mihomo_Meta_A.png",
|
||||
"author": "wumode",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.2.0": "支持管理多个订阅; 支持导入配置模板和 V2Ray 链接; 优化界面",
|
||||
"v1.1.3": "添加仪表盘组件",
|
||||
"v1.1.1": "支持解析 V2ray 订阅",
|
||||
"v1.1.0": "支持规则集合; 添加ACL4SSR规则集; 配置说明",
|
||||
|
||||
@@ -2,6 +2,8 @@ import json
|
||||
import re
|
||||
from typing import Any, Optional, List, Dict, Tuple, Union
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import yaml
|
||||
import hashlib
|
||||
from datetime import datetime, timedelta
|
||||
@@ -13,7 +15,6 @@ from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
import httpx
|
||||
import asyncio
|
||||
import json
|
||||
from fastapi import HTTPException, Request, status, Body, Response
|
||||
import websockets
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
@@ -37,7 +38,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Mihomo_Meta_A.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.1.3"
|
||||
plugin_version = "1.2.0"
|
||||
# 插件作者
|
||||
plugin_author = "wumode"
|
||||
# 作者主页
|
||||
@@ -73,9 +74,11 @@ class ClashRuleProvider(_PluginBase):
|
||||
_discard_rules: bool = False
|
||||
_enable_acl4ssr: bool = False
|
||||
_dashboard_components: List[str] = []
|
||||
_clash_template_yaml = ''
|
||||
|
||||
# 插件数据
|
||||
_clash_config: Optional[Dict[str, Any]] = None
|
||||
# 综合多个订阅的配置
|
||||
# _clash_config: Optional[Dict[str, Any]] = None
|
||||
_top_rules: List[str] = []
|
||||
_ruleset_rules: List[str] = []
|
||||
_rule_provider: Dict[str, Any] = {}
|
||||
@@ -86,27 +89,30 @@ class ClashRuleProvider(_PluginBase):
|
||||
_extra_proxies = []
|
||||
_acl4ssr_providers: Dict[str, Any] = {}
|
||||
_acl4ssr_prefix: str = '🗂️=>'
|
||||
# 保存每个订阅文件的原始内容
|
||||
_clash_configs: Dict[str, Any] = {}
|
||||
|
||||
# protected variables
|
||||
_clash_rule_parser = None
|
||||
_ruleset_rule_parser = None
|
||||
_custom_rule_sets = None
|
||||
_clash_template: Optional[Dict[str, Any]] = None
|
||||
_scheduler: Optional[BackgroundScheduler] = None
|
||||
_countries: Optional[List[Dict[str, str]]] = None
|
||||
_proxy_groups_by_region: List[Dict[str, Any]] = []
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
self._clash_config = self.get_data("clash_config")
|
||||
# self._clash_config = self.get_data("clash_config")
|
||||
self._ruleset_rules = self.get_data("ruleset_rules")
|
||||
self._top_rules = self.get_data("top_rules")
|
||||
self._proxy_groups = self.get_data("proxy_groups") or []
|
||||
self._extra_proxies = self.get_data("extra_proxies") or []
|
||||
self._subscription_info = self.get_data("subscription_info") or \
|
||||
{"download": 0, "upload": 0, "total": 0, "expire": 0, "last_update": 0}
|
||||
self._subscription_info = self.get_data("subscription_info") or {}
|
||||
self._rule_provider = self.get_data("rule_provider") or {}
|
||||
self._extra_rule_providers = self.get_data("extra_rule_providers") or {}
|
||||
self._ruleset_names = self.get_data("ruleset_names") or {}
|
||||
self._acl4ssr_providers = self.get_data("acl4ssr_providers") or {}
|
||||
self._clash_configs = self.get_data("clash_configs") or {}
|
||||
if config:
|
||||
self._enabled = config.get("enabled")
|
||||
self._proxy = config.get("proxy")
|
||||
@@ -134,19 +140,30 @@ class ClashRuleProvider(_PluginBase):
|
||||
self._discard_rules = config.get("discard_rules") or False
|
||||
self._enable_acl4ssr = config.get("enable_acl4ssr") or False
|
||||
self._dashboard_components = config.get("dashboard_components") or []
|
||||
self._clash_template_yaml = config.get("clash_template") or ''
|
||||
self._clash_rule_parser = ClashRuleParser()
|
||||
self._ruleset_rule_parser = ClashRuleParser()
|
||||
if self._enabled:
|
||||
if self._clash_template_yaml:
|
||||
self._clash_template = {}
|
||||
try:
|
||||
self._clash_template = yaml.load(self._clash_template_yaml, Loader=yaml.SafeLoader)
|
||||
except yaml.YAMLError as exc:
|
||||
logger.error(f"Error loading clash template yaml: {exc}")
|
||||
if self._group_by_region:
|
||||
self._countries = ClashRuleProvider.__load_countries(
|
||||
f"{settings.ROOT_PATH}/app/plugins/clashruleprovider/countries.json")
|
||||
self._proxy_groups_by_region = ClashRuleProvider.__group_by_region(self._countries,
|
||||
self._clash_config.get('proxies'))
|
||||
self.all_proxies())
|
||||
self.__parse_config()
|
||||
# 清理不存在的 URL
|
||||
self._subscription_info = {url: self._subscription_info.get(url)
|
||||
for url in self._sub_links if self._subscription_info.get(url)}
|
||||
self._clash_configs = {url: self._clash_configs[url] for url in self._sub_links if self._clash_configs.get(url)}
|
||||
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
||||
self._scheduler.start()
|
||||
# 更新订阅
|
||||
self._scheduler.add_job(self.__refresh_subscription, "date",
|
||||
self._scheduler.add_job(self.refresh_subscriptions, "date",
|
||||
run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=2))
|
||||
# 更新acl4ssr
|
||||
if self._enable_acl4ssr:
|
||||
@@ -155,6 +172,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
else:
|
||||
self._acl4ssr_providers = {}
|
||||
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@@ -236,14 +254,6 @@ class ClashRuleProvider(_PluginBase):
|
||||
"summary": "删除一条规则",
|
||||
"description": "删除一条规则"
|
||||
},
|
||||
{
|
||||
"path": "/subscription",
|
||||
"endpoint": self.get_subscription,
|
||||
"methods": ["GET"],
|
||||
"auth": "bear",
|
||||
"summary": "获取原订阅链接",
|
||||
"description": "获取原订阅链接"
|
||||
},
|
||||
{
|
||||
"path": "/subscription",
|
||||
"endpoint": self.refresh_subscription,
|
||||
@@ -455,7 +465,6 @@ class ClashRuleProvider(_PluginBase):
|
||||
self.__insert_ruleset()
|
||||
self._top_rules = self._clash_rule_parser.to_list()
|
||||
self._ruleset_rules = self._ruleset_rule_parser.to_list()
|
||||
self.save_data('clash_config', self._clash_config)
|
||||
self.save_data('ruleset_rules', self._ruleset_rules)
|
||||
self.save_data('top_rules', self._top_rules)
|
||||
self.save_data('subscription_info', self._subscription_info)
|
||||
@@ -465,6 +474,7 @@ class ClashRuleProvider(_PluginBase):
|
||||
self.save_data('extra_proxies', self._extra_proxies)
|
||||
self.save_data('extra_rule_providers', self._extra_rule_providers)
|
||||
self.save_data('acl4ssr_providers', self._acl4ssr_providers)
|
||||
self.save_data('clash_configs', self._clash_configs)
|
||||
|
||||
def __parse_config(self):
|
||||
if self._top_rules is None:
|
||||
@@ -555,11 +565,12 @@ class ClashRuleProvider(_PluginBase):
|
||||
return Response(content=res, media_type="text/yaml")
|
||||
|
||||
def get_clash_outbound(self) -> schemas.Response:
|
||||
outbound = self.clash_outbound(self._clash_config)
|
||||
outbound = self.clash_outbound()
|
||||
return schemas.Response(success=True, message="", data={"outbound": outbound})
|
||||
|
||||
def get_status(self):
|
||||
rule_size = len(self._clash_config.get("rules", [])) if self._clash_config else 0
|
||||
first_config = self._clash_configs.get(self._sub_links[0], {}) if self._sub_links else {}
|
||||
rule_size = len(first_config.get("rules", []))
|
||||
return {"success": True, "message": "",
|
||||
"data": {"state": self._enabled,
|
||||
"ruleset_prefix": self._ruleset_prefix,
|
||||
@@ -574,10 +585,14 @@ class ClashRuleProvider(_PluginBase):
|
||||
if not config:
|
||||
return {'success': False, "message": ''}
|
||||
res = yaml.dump(config, allow_unicode=True)
|
||||
headers = {'Subscription-Userinfo': f'upload={self._subscription_info["upload"]}; '
|
||||
f'download={self._subscription_info["download"]}; '
|
||||
f'total={self._subscription_info["total"]}; '
|
||||
f'expire={self._subscription_info["expire"]}'}
|
||||
first_url = self._sub_links[0] if self._sub_links else None
|
||||
if not first_url:
|
||||
return {'success': False, "message": ''}
|
||||
sub_info = self._subscription_info.get(first_url, {})
|
||||
headers = {'Subscription-Userinfo': f'upload={sub_info.get("upload", 0)}; '
|
||||
f'download={sub_info.get("download", 0)}; '
|
||||
f'total={sub_info.get("total", 0)}; '
|
||||
f'expire={sub_info.get("expire", 0)}'}
|
||||
return Response(headers=headers, content=res, media_type="text/yaml")
|
||||
|
||||
def get_rules(self, rule_type: str) -> schemas.Response:
|
||||
@@ -658,20 +673,19 @@ class ClashRuleProvider(_PluginBase):
|
||||
res = self.add_rule_by_priority(params.get('rule_data'), self._clash_rule_parser)
|
||||
return schemas.Response(success=bool(res), message='')
|
||||
|
||||
def get_subscription(self) -> schemas.Response:
|
||||
if not self._sub_links:
|
||||
return schemas.Response(success=False, message=f"Invalid subscription links: {self._sub_links}")
|
||||
return schemas.Response(success=True, data={"url": self._sub_links[0]})
|
||||
|
||||
def refresh_subscription(self, params: Dict[str, Any]):
|
||||
if not self._enabled:
|
||||
return schemas.Response(success=False, message="")
|
||||
url = params.get('url')
|
||||
if not url:
|
||||
return schemas.Response(success=False, message="missing params")
|
||||
res = self.__refresh_subscription()
|
||||
if not res:
|
||||
return schemas.Response(success=False, message=f"订阅链接 {self._sub_links[0]} 更新失败")
|
||||
config, info = self.__get_subscription(url)
|
||||
if not config:
|
||||
return schemas.Response(success=False, message=f"订阅链接 {url} 更新失败")
|
||||
self._clash_configs[url] = config
|
||||
self._subscription_info[url] = info
|
||||
self.save_data('clash_configs', self._clash_configs)
|
||||
self.save_data('subscription_info', self._subscription_info)
|
||||
return schemas.Response(success=True, message='订阅更新成功')
|
||||
|
||||
def get_rule_providers(self) -> schemas.Response:
|
||||
@@ -741,11 +755,19 @@ class ClashRuleProvider(_PluginBase):
|
||||
try:
|
||||
imported_proxies = yaml.load(params["payload"], Loader=yaml.SafeLoader)
|
||||
extra_proxies = imported_proxies.get("proxies", [])
|
||||
except yaml.YAMLError as err:
|
||||
except Exception as err:
|
||||
return schemas.Response(success=False, message=f'YAML error: {err}')
|
||||
elif params.get('type') == 'LINK':
|
||||
try:
|
||||
links = params['payload'].strip().splitlines()
|
||||
extra_proxies = Converter.convert_v2ray(v2ray_link=links)
|
||||
except Exception as err:
|
||||
return schemas.Response(success=False, message=f'LINK error: {err}')
|
||||
if not extra_proxies:
|
||||
return schemas.Response(success=False, message='无可用节点')
|
||||
for proxy in extra_proxies:
|
||||
name = proxy.get('name')
|
||||
if not name or any(x.get('name') == name for x in self.clash_outbound(self._clash_config)):
|
||||
if not name or any(x.get('name') == name for x in self.clash_outbound()):
|
||||
logger.warning(f"The proxy name {proxy['name']} already exists. Skipping...")
|
||||
continue
|
||||
required_fields = {'name', 'type', 'server', 'port'}
|
||||
@@ -798,22 +820,20 @@ class ClashRuleProvider(_PluginBase):
|
||||
self.save_data('proxy_groups', self._proxy_groups)
|
||||
return schemas.Response(success=True, message='')
|
||||
|
||||
def clash_outbound(self, clash_config: Dict[str, Any]) -> Optional[List]:
|
||||
if not clash_config:
|
||||
return []
|
||||
outbound = [{'name': proxy_group.get("name")} for proxy_group in clash_config.get("proxy-groups")]
|
||||
outbound.extend([{'name': proxy.get("name")} for proxy in clash_config.get("proxies")])
|
||||
def clash_outbound(self) -> Optional[List]:
|
||||
first_config = self._clash_configs.get(self._sub_links[0], {}) if self._sub_links else {}
|
||||
outbound = [{'name': proxy_group.get("name")} for proxy_group in first_config.get("proxy-groups", [])]
|
||||
outbound.extend([{'name': proxy.get("name")} for proxy in first_config.get("proxies", [])])
|
||||
if self._group_by_region:
|
||||
outbound.extend([{'name': proxy_group.get("name")} for proxy_group in self._proxy_groups_by_region])
|
||||
outbound.extend([{'name': proxy.get("name")} for proxy in self._extra_proxies])
|
||||
outbound.extend([{'name': proxy_group.get("name")} for proxy_group in self._proxy_groups])
|
||||
return outbound
|
||||
|
||||
def rule_providers(self) -> Optional[Dict[str, Any]]:
|
||||
if not self._clash_config:
|
||||
return None
|
||||
def rule_providers(self) -> Dict[str, Any]:
|
||||
first_config = self._clash_configs.get(self._sub_links[0], {}) if self._sub_links else {}
|
||||
rule_providers = {}
|
||||
for key, value in self._clash_config.get('rule-providers', {}).items():
|
||||
for key, value in first_config.get('rule-providers', {}).items():
|
||||
rule_providers[key] = value
|
||||
for key, value in self._extra_rule_providers.items():
|
||||
rule_providers[key] = value
|
||||
@@ -921,21 +941,34 @@ class ClashRuleProvider(_PluginBase):
|
||||
return f"{days}天后过期" if days > 0 else "已过期"
|
||||
|
||||
def refresh_subscription_service(self):
|
||||
res = self.__refresh_subscription()
|
||||
if res:
|
||||
used = self._subscription_info['download'] + self._subscription_info['upload']
|
||||
remaining = self._subscription_info['total'] - used
|
||||
message = (f"订阅更新成功\n"
|
||||
f"已用流量: {ClashRuleProvider.format_bytes(used)}\n"
|
||||
f"剩余流量: {ClashRuleProvider.format_bytes(remaining)}\n"
|
||||
f"总量: {ClashRuleProvider.format_bytes(self._subscription_info['total'])}\n"
|
||||
f"过期时间: {ClashRuleProvider.format_expire_time(self._subscription_info['expire'])}")
|
||||
else:
|
||||
message = "订阅更新失败"
|
||||
res = self.refresh_subscriptions()
|
||||
messages = []
|
||||
for url, result in res.items():
|
||||
try:
|
||||
host_name = urlparse(url).hostname
|
||||
except ValueError:
|
||||
host_name = url
|
||||
message = f"1. 「 {host_name} 」\n"
|
||||
if result:
|
||||
sub_info = self._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
|
||||
info = (f"节点数量: {sub_info.get('proxy_num', 0)}\n"
|
||||
f"已用流量: {ClashRuleProvider.format_bytes(used)}\n"
|
||||
f"剩余流量: {ClashRuleProvider.format_bytes(remaining)}\n"
|
||||
f"总量: {ClashRuleProvider.format_bytes(sub_info.get('total', 0))}\n"
|
||||
f"过期时间: {ClashRuleProvider.format_expire_time(sub_info.get('expire', 0))}")
|
||||
else:
|
||||
info = ""
|
||||
message += f"订阅更新成功\n{info}"
|
||||
else:
|
||||
message += '订阅更新失败'
|
||||
messages.append(message)
|
||||
if self._notify:
|
||||
self.post_message(title=f"【{self.plugin_name}】",
|
||||
mtype=NotificationType.Plugin,
|
||||
text=f"{message}"
|
||||
text='\n'.join(messages)
|
||||
)
|
||||
|
||||
def __refresh_acl4ssr(self):
|
||||
@@ -962,11 +995,31 @@ class ClashRuleProvider(_PluginBase):
|
||||
self._acl4ssr_providers[name] = provider
|
||||
self.save_data('acl4ssr_providers', self._acl4ssr_providers)
|
||||
|
||||
def __refresh_subscription(self) -> bool:
|
||||
if not self._sub_links:
|
||||
logger.error(f"Invalid links: {self._sub_links}")
|
||||
return False
|
||||
url = self._sub_links[0]
|
||||
def refresh_subscriptions(self) -> Dict[str, bool]:
|
||||
"""
|
||||
更新全部订阅链接
|
||||
"""
|
||||
all_proxies = []
|
||||
res = {}
|
||||
for index, url in enumerate(self._sub_links):
|
||||
config, sub_info = self.__get_subscription(url)
|
||||
if not config:
|
||||
res[url] = False
|
||||
continue
|
||||
res[url] = True
|
||||
self._clash_configs[url] = config
|
||||
all_proxies.extend(config.get("proxies", []))
|
||||
self._subscription_info[url] = sub_info
|
||||
self.save_data('subscription_info', self._subscription_info)
|
||||
self.save_data('clash_configs', self._clash_configs)
|
||||
self._proxy_groups_by_region = ClashRuleProvider.__group_by_region(self._countries,
|
||||
all_proxies)
|
||||
return res
|
||||
|
||||
def __get_subscription(self, url: str) -> Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]:
|
||||
if not url:
|
||||
logger.error(f"Invalid links: {url}")
|
||||
return None, None
|
||||
logger.info(f"正在更新: {url}")
|
||||
ret = None
|
||||
for i in range(0, self._retry_times):
|
||||
@@ -976,9 +1029,9 @@ class ClashRuleProvider(_PluginBase):
|
||||
if ret:
|
||||
break
|
||||
if not ret:
|
||||
return False
|
||||
return None, None
|
||||
try:
|
||||
rs = yaml.load(ret.content, Loader=yaml.FullLoader)
|
||||
rs: Dict[str, Any] = yaml.load(ret.content, Loader=yaml.FullLoader)
|
||||
if type(rs) is str:
|
||||
all_proxies = {'name': "All Proxies", 'type': 'select', 'include-all-proxies': True}
|
||||
proxies = Converter.convert_v2ray(ret.content)
|
||||
@@ -988,27 +1041,27 @@ class ClashRuleProvider(_PluginBase):
|
||||
logger.info(f"已更新: {url}. 节点数量: {len(rs['proxies'])}")
|
||||
if rs.get('rules') is None:
|
||||
rs['rules'] = []
|
||||
if self._discard_rules:
|
||||
rs['rules'] = []
|
||||
self._clash_config = self.__remove_nodes_by_keywords(rs)
|
||||
rs = self.__remove_nodes_by_keywords(rs)
|
||||
except Exception as e:
|
||||
logger.error(f"解析配置出错: {e}")
|
||||
return False
|
||||
return None, None
|
||||
|
||||
sub_info = {'last_update': int(time.time()), 'proxy_num': len(rs.get('proxies', []))}
|
||||
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}
|
||||
self._subscription_info['download'] = variables['download']
|
||||
self._subscription_info['upload'] = variables['upload']
|
||||
self._subscription_info['total'] = variables['total']
|
||||
self._subscription_info['expire'] = variables['expire']
|
||||
self._subscription_info["last_update"] = int(time.time())
|
||||
self._proxy_groups_by_region = ClashRuleProvider.__group_by_region(self._countries,
|
||||
self._clash_config.get('proxies'))
|
||||
self.save_data('subscription_info', self._subscription_info)
|
||||
self.save_data('clash_config', self._clash_config)
|
||||
return True
|
||||
sub_info.update({
|
||||
'download': variables['download'],
|
||||
'upload': variables['upload'],
|
||||
'total': variables['total'],
|
||||
'expire': variables['expire']
|
||||
})
|
||||
return rs, sub_info
|
||||
|
||||
def notify_clash(self, ruleset: str):
|
||||
"""
|
||||
通知 Clash 刷新规则集
|
||||
"""
|
||||
url = f'{self._clash_dashboard_url}/providers/rules/{ruleset}'
|
||||
RequestUtils(content_type="application/json",
|
||||
headers={"authorization": f"Bearer {self._clash_dashboard_secret}"}
|
||||
@@ -1086,40 +1139,77 @@ class ClashRuleProvider(_PluginBase):
|
||||
proxy_group['proxies'] = [x for x in proxy_group.get('proxies', []) if x not in removed_proxies]
|
||||
return clash_config
|
||||
|
||||
def all_proxies(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
所有出站代理
|
||||
"""
|
||||
all_proxies = []
|
||||
for index, url in enumerate(self._sub_links):
|
||||
config = self._clash_configs.get(url, {})
|
||||
all_proxies.extend(config.get("proxies", []))
|
||||
return all_proxies
|
||||
|
||||
@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('name') == item.get('name', '') for p in to_list):
|
||||
logger.warn(f"Item named {item.get('name')} already exists. Skipping...")
|
||||
continue
|
||||
to_list.append(item)
|
||||
return to_list
|
||||
|
||||
def clash_config(self) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
整理clash配置,返回配置字典
|
||||
整理 clash 配置,返回配置字典
|
||||
"""
|
||||
if not self._clash_config:
|
||||
return None
|
||||
# 使用模板或第一个订阅
|
||||
first_config = self._clash_configs.get(self._sub_links[0], {}) if self._sub_links else {}
|
||||
proxies =[]
|
||||
if not self._clash_template:
|
||||
clash_config = copy.deepcopy(first_config)
|
||||
clash_config['proxy-groups'] = []
|
||||
clash_config['rule-providers'] = {}
|
||||
clash_config['rules'] = []
|
||||
else:
|
||||
clash_config = copy.deepcopy(self._clash_template)
|
||||
proxies.extend(self._clash_template.get('proxies'))
|
||||
clash_config['proxy-groups'] = ClashRuleProvider.extend_with_name_checking(clash_config.get('proxy-groups', []),
|
||||
first_config.get('proxy-groups', []),
|
||||
)
|
||||
clash_config['rules'] = clash_config.get('rules', [])
|
||||
if not self._discard_rules:
|
||||
clash_config['rules'] += first_config.get('rules', [])
|
||||
clash_config['rule-providers'] = first_config.get('rule-providers', {}).update(clash_config.get('rule-providers', {}))
|
||||
|
||||
|
||||
for proxy in (self._extra_proxies + self.all_proxies()) :
|
||||
if any(p.get('name') == proxy.get('name', '') for p in proxies):
|
||||
logger.warn(f"Proxy named {proxy.get('name')} already exists. Skipping...")
|
||||
continue
|
||||
proxies.append(proxy)
|
||||
if proxies:
|
||||
clash_config['proxies'] = proxies
|
||||
self.__insert_ruleset()
|
||||
self._top_rules = self._clash_rule_parser.to_list()
|
||||
clash_config = copy.deepcopy(self._clash_config)
|
||||
clash_config['rule-providers'] = clash_config.get('rule-providers') or {}
|
||||
# 添加代理组
|
||||
proxy_groups = copy.deepcopy(self._proxy_groups)
|
||||
if proxy_groups:
|
||||
if clash_config.get("proxy-groups"):
|
||||
clash_config['proxy-groups'].extend(proxy_groups)
|
||||
else:
|
||||
clash_config['proxy-groups'] = proxy_groups
|
||||
|
||||
# 添加额外节点
|
||||
if clash_config.get('proxies'):
|
||||
clash_config['proxies'].extend(self._extra_proxies)
|
||||
else:
|
||||
clash_config['proxies'] = copy.deepcopy(self._extra_proxies)
|
||||
clash_config['proxy-groups'] = ClashRuleProvider.extend_with_name_checking(clash_config['proxy-groups'],
|
||||
proxy_groups)
|
||||
|
||||
# 添加按大洲代理组
|
||||
if self._group_by_region:
|
||||
if self._proxy_groups_by_region:
|
||||
if clash_config.get('proxy-groups'):
|
||||
clash_config['proxy-groups'].extend(self._proxy_groups_by_region)
|
||||
else:
|
||||
clash_config['proxy-groups'] = copy.deepcopy(self._proxy_groups_by_region)
|
||||
clash_config['proxy-groups'] = ClashRuleProvider.extend_with_name_checking(clash_config['proxy-groups'],
|
||||
self._proxy_groups_by_region)
|
||||
|
||||
top_rules = []
|
||||
outbound_names = list(x.get("name") for x in self.clash_outbound(clash_config))
|
||||
outbound_names = list(x.get("name") for x in self.clash_outbound())
|
||||
|
||||
# 添加 extra rule providers
|
||||
if self._extra_rule_providers:
|
||||
|
||||
@@ -2,7 +2,7 @@ import re
|
||||
from typing import List, Dict, Any, Optional, Union, Callable, Literal
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from urllib.parse import urlparse, parse_qs, unquote, parse_qsl
|
||||
from urllib.parse import urlparse, parse_qs, unquote, parse_qsl, urlencode, urlunparse
|
||||
import json
|
||||
import base64
|
||||
import binascii
|
||||
@@ -684,12 +684,14 @@ class Converter:
|
||||
raise ValueError(f"invalid truth value {val!r}")
|
||||
|
||||
@staticmethod
|
||||
def convert_v2ray(buf: bytes):
|
||||
decoded = Converter.decode_base64(buf).decode("utf-8")
|
||||
lines = decoded.strip().splitlines()
|
||||
def convert_v2ray(v2ray_link: Union[list, bytes]) -> List[Dict[str, Any]]:
|
||||
if isinstance(v2ray_link, bytes):
|
||||
decoded = Converter.decode_base64(v2ray_link).decode("utf-8")
|
||||
lines = decoded.strip().splitlines()
|
||||
else:
|
||||
lines = v2ray_link
|
||||
proxies = []
|
||||
names = {}
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
@@ -800,7 +802,6 @@ class Converter:
|
||||
try:
|
||||
parsed = urlparse(line)
|
||||
query = parse_qs(parsed.query)
|
||||
|
||||
uuid = parsed.username or ""
|
||||
server = parsed.hostname or ""
|
||||
port = parsed.port or 443
|
||||
@@ -811,7 +812,6 @@ class Converter:
|
||||
network = query.get("type", [""])[0]
|
||||
path = query.get("path", [""])[0]
|
||||
host = query.get("host", [""])[0]
|
||||
|
||||
name = Converter.unique_name(names, unquote(parsed.fragment or f"{server}:{port}"))
|
||||
|
||||
proxy = {
|
||||
@@ -831,11 +831,30 @@ class Converter:
|
||||
|
||||
if network:
|
||||
proxy["network"] = network
|
||||
if network == "ws":
|
||||
proxy["ws-opts"] = {
|
||||
"path": path,
|
||||
"headers": {"Host": host}
|
||||
}
|
||||
if network in ["ws", "httpupgrade"]:
|
||||
headers = {}
|
||||
if host:
|
||||
headers["Host"] = host
|
||||
ws_opts:Dict[str, Any] = { "path": path, "headers": headers }
|
||||
try:
|
||||
parsed_path = urlparse(path)
|
||||
q = parse_qs(parsed_path.query)
|
||||
if "ed" in q:
|
||||
med = int(q["ed"][0])
|
||||
ws_opts["max-early-data"] = med
|
||||
ws_opts["early-data-header-name"] = q.get("eh", ["Sec-WebSocket-Protocol"])[0]
|
||||
q.pop("ed", None)
|
||||
new_query = urlencode(q, doseq=True)
|
||||
parsed = parsed._replace(query=new_query)
|
||||
path = urlunparse(parsed)
|
||||
elif "eh" in q:
|
||||
ws_opts["early-data-header-name"] = q["eh"][0]
|
||||
ws_opts["path"] = path
|
||||
except Exception:
|
||||
pass
|
||||
if network == "httpupgrade":
|
||||
ws_opts["v2ray-http-upgrade-fast-open"] = True
|
||||
proxy["ws-opts"] = ws_opts
|
||||
elif network == "grpc":
|
||||
proxy["grpc-opts"] = {
|
||||
"grpc-service-name": path
|
||||
@@ -850,11 +869,11 @@ class Converter:
|
||||
alpn = query.get("alpn", [""])[0]
|
||||
if alpn:
|
||||
proxy["alpn"] = alpn.split(",")
|
||||
|
||||
return proxy
|
||||
proxies.append(proxy)
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"VLESS parse error: {e}") from e
|
||||
|
||||
elif scheme == "trojan":
|
||||
try:
|
||||
parsed = urlparse(line)
|
||||
@@ -971,31 +990,56 @@ class Converter:
|
||||
raise ValueError(f"SOCKS5 parse error: {e}") from e
|
||||
elif scheme == "ss":
|
||||
try:
|
||||
parsed = urlparse(line)
|
||||
# 兼容 ss://base64 或 ss://base64#name
|
||||
if "#" in body:
|
||||
body, fragment = body.split("#", 1)
|
||||
name = Converter.unique_name(names, unquote(fragment))
|
||||
if parsed.fragment:
|
||||
name = Converter.unique_name(names, unquote(parsed.fragment))
|
||||
else:
|
||||
name = Converter.unique_name(names, "ss")
|
||||
|
||||
if "@" in body:
|
||||
userinfo, server = body.split("@", 1)
|
||||
else:
|
||||
decoded = Converter.decode_base64(body).decode()
|
||||
userinfo, server = decoded.rsplit("@", 1)
|
||||
|
||||
cipher, password = userinfo.split(":")
|
||||
server_host, server_port = server.split(":")
|
||||
|
||||
if parsed.port is None:
|
||||
base64_body = body.split("#")[0]
|
||||
parsed = urlparse(f"ss://{Converter.decode_base64(base64_body).decode('utf-8')}")
|
||||
cipher_raw = parsed.username
|
||||
cipher = cipher_raw
|
||||
password = parsed.password
|
||||
if not password:
|
||||
dc_buf = Converter.decode_base64(cipher_raw).decode('utf-8')
|
||||
if dc_buf.startswith("ss://"):
|
||||
dc_buf = dc_buf[len("ss://"):]
|
||||
dc_buf = Converter.decode_base64(dc_buf).decode('utf-8')
|
||||
cipher, password = dc_buf.split(":", 1)
|
||||
server = parsed.hostname
|
||||
port = parsed.port
|
||||
query = dict(parse_qsl(parsed.query))
|
||||
proxy = {
|
||||
"name": name,
|
||||
"type": "ss",
|
||||
"server": server_host,
|
||||
"port": server_port,
|
||||
"server": server,
|
||||
"port": port,
|
||||
"cipher": cipher,
|
||||
"password": password,
|
||||
"udp": True
|
||||
}
|
||||
plugin = query.get("plugin")
|
||||
if plugin and ";" in plugin:
|
||||
query_string = "pluginName=" + plugin.replace(";", "&")
|
||||
plugin_info = parse_qs(query_string)
|
||||
plugin_name = plugin_info.get("pluginName", [""])[0]
|
||||
|
||||
if "obfs" in plugin_name:
|
||||
proxy["plugin"] = "obfs"
|
||||
proxy["plugin-opts"] = {
|
||||
"mode": plugin_info.get("obfs", [""])[0],
|
||||
"host": plugin_info.get("obfs-host", [""])[0],
|
||||
}
|
||||
elif "v2ray-plugin" in plugin_name:
|
||||
proxy["plugin"] = "v2ray-plugin"
|
||||
proxy["plugin-opts"] = {
|
||||
"mode": plugin_info.get("mode", [""])[0],
|
||||
"host": plugin_info.get("host", [""])[0],
|
||||
"path": plugin_info.get("path", [""])[0],
|
||||
"tls": "tls" in plugin,
|
||||
}
|
||||
proxies.append(proxy)
|
||||
except Exception as e:
|
||||
raise ValueError(f"SS parse error: {e}") from e
|
||||
|
||||
4
plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-BDDunIfC.css
vendored
Normal file
4
plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-BDDunIfC.css
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
.plugin-config[data-v-107d3846] {
|
||||
margin: 0 auto;
|
||||
}
|
||||
1029
plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-r5zwvDwv.js
vendored
Normal file
1029
plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-r5zwvDwv.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,920 +0,0 @@
|
||||
import { importShared } from './__federation_fn_import-JrT3xvdd.js';
|
||||
import { _ as _export_sfc } from './_plugin-vue_export-helper-pcqpp-6-.js';
|
||||
|
||||
const {createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,createVNode:_createVNode,toDisplayString:_toDisplayString,openBlock:_openBlock,createBlock:_createBlock,createCommentVNode:_createCommentVNode,createElementVNode:_createElementVNode,mergeProps:_mergeProps,withModifiers:_withModifiers,createElementBlock:_createElementBlock} = await importShared('vue');
|
||||
|
||||
|
||||
const _hoisted_1 = { class: "plugin-config" };
|
||||
const _hoisted_2 = { class: "d-flex align-center" };
|
||||
const _hoisted_3 = { class: "font-weight-medium" };
|
||||
const _hoisted_4 = { class: "text-body-2" };
|
||||
|
||||
const {ref,reactive,onMounted,computed} = await importShared('vue');
|
||||
|
||||
|
||||
// Props
|
||||
|
||||
const _sfc_main = {
|
||||
__name: 'Config',
|
||||
props: {
|
||||
initialConfig: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
api: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
emits: ['save', 'close'],
|
||||
setup(__props, { emit: __emit }) {
|
||||
|
||||
const props = __props;
|
||||
|
||||
// 状态变量
|
||||
const form = ref(null);
|
||||
const isFormValid = ref(true);
|
||||
const error = ref(null);
|
||||
const saving = ref(false);
|
||||
const testing = ref(false);
|
||||
const showClashSecret = ref(false);
|
||||
const selectedCronOption = ref('6hours');
|
||||
const dashboardComponents = ['Clash Info', 'Traffic Stats'];
|
||||
|
||||
// Test result state
|
||||
const testResult = reactive({
|
||||
show: false,
|
||||
success: false,
|
||||
title: '',
|
||||
message: ''
|
||||
});
|
||||
|
||||
|
||||
// 默认配置
|
||||
const defaultConfig = {
|
||||
enabled: false,
|
||||
sub_links: [],
|
||||
filter_keywords: ["公益性", "高延迟", "域名", "官网", "重启", "过期时间", "系统代理"],
|
||||
clash_dashboard_url: '',
|
||||
clash_dashboard_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,
|
||||
refresh_delay: 5,
|
||||
discard_rules: false,
|
||||
enable_acl4ssr: false,
|
||||
dashboard_components: [],
|
||||
};
|
||||
|
||||
// 响应式配置对象
|
||||
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 isValidUrl = (urlString) => {
|
||||
if (!urlString) return false;
|
||||
try {
|
||||
const url = new URL(urlString);
|
||||
return url.protocol === 'http:' || url.protocol === 'https:';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function validateSubLinks(links) {
|
||||
if (!links || links.length === 0) {
|
||||
return '至少需要一个订阅链接'
|
||||
}
|
||||
|
||||
for (const link of links) {
|
||||
if (!isValidUrl(link)) {
|
||||
return `无效的订阅链接: ${link}`
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
async function testConnection() {
|
||||
testing.value = true;
|
||||
error.value = null;
|
||||
testResult.show = false;
|
||||
|
||||
try {
|
||||
// 验证必需的参数
|
||||
if (!config.clash_dashboard_url) {
|
||||
throw new Error('请先配置 Clash 面板 URL')
|
||||
}
|
||||
if (!config.clash_dashboard_secret) {
|
||||
throw new Error('请先配置 Clash 面板密钥')
|
||||
}
|
||||
if (!config.sub_links || config.sub_links.length === 0) {
|
||||
throw new Error('请先配置至少一个订阅链接')
|
||||
}
|
||||
if (!config.movie_pilot_url || config.movie_pilot_url.length === 0) {
|
||||
throw new Error('请先MoviePilot链接')
|
||||
}
|
||||
// 准备API请求参数
|
||||
const testParams = {
|
||||
clash_dashboard_url: config.clash_dashboard_url,
|
||||
clash_dashboard_secret: config.clash_dashboard_secret,
|
||||
sub_link: config.sub_links[0] // 使用第一个订阅链接进行测试
|
||||
};
|
||||
|
||||
// 调用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() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function extractDomain(url) {
|
||||
try {
|
||||
const domain = new URL(url).hostname;
|
||||
return domain.startsWith('www.') ? domain.substring(4) : domain
|
||||
} catch {
|
||||
return url // 如果解析失败,返回原始URL
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
function resetForm() {
|
||||
Object.keys(defaultConfig).forEach(key => {
|
||||
config[key] = defaultConfig[key];
|
||||
});
|
||||
selectedCronOption.value = '6hours';
|
||||
|
||||
if (form.value) {
|
||||
form.value.resetValidation();
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭组件
|
||||
function notifyClose() {
|
||||
emit('close');
|
||||
}
|
||||
|
||||
// 通知主应用切换到Page页面
|
||||
function notifySwitch() {
|
||||
emit('switch');
|
||||
}
|
||||
|
||||
return (_ctx, _cache) => {
|
||||
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_alert = _resolveComponent("v-alert");
|
||||
const _component_v_switch = _resolveComponent("v-switch");
|
||||
const _component_v_col = _resolveComponent("v-col");
|
||||
const _component_v_row = _resolveComponent("v-row");
|
||||
const _component_v_chip = _resolveComponent("v-chip");
|
||||
const _component_v_combobox = _resolveComponent("v-combobox");
|
||||
const _component_v_text_field = _resolveComponent("v-text-field");
|
||||
const _component_v_select = _resolveComponent("v-select");
|
||||
const _component_v_cron_field = _resolveComponent("v-cron-field");
|
||||
const _component_v_expansion_panel_title = _resolveComponent("v-expansion-panel-title");
|
||||
const _component_v_expansion_panel_text = _resolveComponent("v-expansion-panel-text");
|
||||
const _component_v_expansion_panel = _resolveComponent("v-expansion-panel");
|
||||
const _component_v_expansion_panels = _resolveComponent("v-expansion-panels");
|
||||
const _component_v_form = _resolveComponent("v-form");
|
||||
const _component_v_card_text = _resolveComponent("v-card-text");
|
||||
const _component_v_spacer = _resolveComponent("v-spacer");
|
||||
const _component_v_card_actions = _resolveComponent("v-card-actions");
|
||||
const _component_v_card = _resolveComponent("v-card");
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, [
|
||||
_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[23] || (_cache[23] = [
|
||||
_createTextVNode("mdi-close")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_card_title, null, {
|
||||
default: _withCtx(() => _cache[22] || (_cache[22] = [
|
||||
_createTextVNode("Clash Rule Provider 插件配置")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_card_text, { class: "overflow-y-auto" }, {
|
||||
default: _withCtx(() => [
|
||||
(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_form, {
|
||||
ref_key: "form",
|
||||
ref: form,
|
||||
modelValue: isFormValid.value,
|
||||
"onUpdate:modelValue": _cache[20] || (_cache[20] = $event => ((isFormValid).value = $event)),
|
||||
onSubmit: _withModifiers(saveConfig, ["prevent"])
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_cache[35] || (_cache[35] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "基本设置", -1)),
|
||||
_createVNode(_component_v_row, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_col, {
|
||||
cols: "12",
|
||||
md: "3"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_switch, {
|
||||
modelValue: config.enabled,
|
||||
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((config.enabled) = $event)),
|
||||
label: "启用插件",
|
||||
color: "primary",
|
||||
inset: "",
|
||||
hint: "启用后插件将开始监控和同步",
|
||||
density: "compact"
|
||||
}, 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: "",
|
||||
hint: "是否使用系统代理进行网络请求",
|
||||
density: "compact"
|
||||
}, 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: "",
|
||||
hint: "执行完成后发送通知消息",
|
||||
density: "compact"
|
||||
}, null, 8, ["modelValue"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_col, {
|
||||
cols: "12",
|
||||
md: "3"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_switch, {
|
||||
modelValue: config.auto_update_subscriptions,
|
||||
"onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ((config.auto_update_subscriptions) = $event)),
|
||||
label: "自动更新订阅",
|
||||
color: "primary",
|
||||
inset: "",
|
||||
hint: "定期自动更新Clash订阅配置",
|
||||
density: "compact"
|
||||
}, null, 8, ["modelValue"])
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_cache[36] || (_cache[36] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "订阅配置", -1)),
|
||||
_createVNode(_component_v_row, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_col, { cols: "12" }, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_combobox, {
|
||||
modelValue: config.sub_links,
|
||||
"onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ((config.sub_links) = $event)),
|
||||
label: "订阅链接",
|
||||
variant: "outlined",
|
||||
multiple: "",
|
||||
chips: "",
|
||||
"closable-chips": "",
|
||||
hint: "添加一个Clash订阅链接, 按回车确认输入",
|
||||
rules: [validateSubLinks]
|
||||
}, {
|
||||
chip: _withCtx(({ props, item }) => [
|
||||
_createVNode(_component_v_chip, _mergeProps(props, {
|
||||
closable: "",
|
||||
size: "small"
|
||||
}), {
|
||||
default: _withCtx(() => [
|
||||
_createTextVNode(_toDisplayString(extractDomain(item.value)), 1)
|
||||
]),
|
||||
_: 2
|
||||
}, 1040)
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue", "rules"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_col, { cols: "12" }, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_combobox, {
|
||||
modelValue: config.filter_keywords,
|
||||
"onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ((config.filter_keywords) = $event)),
|
||||
label: "节点过滤关键词",
|
||||
variant: "outlined",
|
||||
multiple: "",
|
||||
chips: "",
|
||||
"closable-chips": "",
|
||||
hint: "添加用于过滤节点的关键词"
|
||||
}, {
|
||||
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
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_cache[37] || (_cache[37] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "Clash 面板设置", -1)),
|
||||
_createVNode(_component_v_row, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_col, { cols: "12" }, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_text_field, {
|
||||
modelValue: config.clash_dashboard_url,
|
||||
"onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((config.clash_dashboard_url) = $event)),
|
||||
label: "Clash 面板 URL",
|
||||
variant: "outlined",
|
||||
placeholder: "http://localhost:9090",
|
||||
hint: "Clash 控制面板的访问地址",
|
||||
rules: [v => !v || isValidUrl(v) || '请输入有效的URL地址']
|
||||
}, {
|
||||
"prepend-inner": _withCtx(() => [
|
||||
_createVNode(_component_v_icon, { color: "primary" }, {
|
||||
default: _withCtx(() => _cache[24] || (_cache[24] = [
|
||||
_createTextVNode("mdi-web")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue", "rules"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_col, {
|
||||
cols: "12",
|
||||
md: "6"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_text_field, {
|
||||
modelValue: config.clash_dashboard_secret,
|
||||
"onUpdate:modelValue": _cache[7] || (_cache[7] = $event => ((config.clash_dashboard_secret) = $event)),
|
||||
label: "Clash 面板密钥",
|
||||
variant: "outlined",
|
||||
placeholder: "your-clash-secret",
|
||||
hint: "用于访问Clash API的密钥",
|
||||
"append-inner-icon": showClashSecret.value ? 'mdi-eye-off' : 'mdi-eye',
|
||||
type: showClashSecret.value ? 'text' : 'password',
|
||||
"onClick:appendInner": _cache[8] || (_cache[8] = $event => (showClashSecret.value = !showClashSecret.value))
|
||||
}, {
|
||||
"prepend-inner": _withCtx(() => [
|
||||
_createVNode(_component_v_icon, { color: "warning" }, {
|
||||
default: _withCtx(() => _cache[25] || (_cache[25] = [
|
||||
_createTextVNode("mdi-key")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue", "append-inner-icon", "type"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_col, {
|
||||
cols: "12",
|
||||
md: "6"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_select, {
|
||||
modelValue: config.dashboard_components,
|
||||
"onUpdate:modelValue": _cache[9] || (_cache[9] = $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[26] || (_cache[26] = [
|
||||
_createTextVNode("mdi-view-dashboard")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue"])
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_cache[38] || (_cache[38] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "MoviePilot 设置", -1)),
|
||||
_createVNode(_component_v_row, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_col, { cols: "12" }, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_text_field, {
|
||||
modelValue: config.movie_pilot_url,
|
||||
"onUpdate:modelValue": _cache[10] || (_cache[10] = $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[27] || (_cache[27] = [
|
||||
_createTextVNode("mdi-movie")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue", "rules"])
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_cache[39] || (_cache[39] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "执行设置", -1)),
|
||||
_createVNode(_component_v_row, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_col, { cols: "12" }, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_cron_field, {
|
||||
modelValue: config.cron_string,
|
||||
"onUpdate:modelValue": _cache[11] || (_cache[11] = $event => ((config.cron_string) = $event)),
|
||||
label: "执行周期",
|
||||
placeholder: "0 4 * * *",
|
||||
hint: "使用标准Cron表达式格式 (分 时 日 月 周)"
|
||||
}, {
|
||||
"prepend-inner": _withCtx(() => [
|
||||
_createVNode(_component_v_icon, { color: "info" }, {
|
||||
default: _withCtx(() => _cache[28] || (_cache[28] = [
|
||||
_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[12] || (_cache[12] = $event => ((config.timeout) = $event)),
|
||||
modelModifiers: { number: true },
|
||||
label: "超时时间 (秒)",
|
||||
variant: "outlined",
|
||||
type: "number",
|
||||
min: "1",
|
||||
max: "300",
|
||||
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[13] || (_cache[13] = $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[29] || (_cache[29] = [
|
||||
_createTextVNode("mdi-refresh")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue", "rules"])
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_expansion_panels, {
|
||||
variant: "accordion",
|
||||
class: "mt-4"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_expansion_panel, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_expansion_panel_title, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_icon, { class: "mr-2" }, {
|
||||
default: _withCtx(() => _cache[30] || (_cache[30] = [
|
||||
_createTextVNode("mdi-cog")
|
||||
])),
|
||||
_: 1
|
||||
}),
|
||||
_cache[31] || (_cache[31] = _createTextVNode(" 高级选项 "))
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_expansion_panel_text, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_row, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_col, {
|
||||
cols: "12",
|
||||
md: "4"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_switch, {
|
||||
modelValue: config.discard_rules,
|
||||
"onUpdate:modelValue": _cache[14] || (_cache[14] = $event => ((config.discard_rules) = $event)),
|
||||
label: "丢弃订阅规则",
|
||||
color: "primary",
|
||||
inset: "",
|
||||
hint: "不保留订阅配置文件的rules字段"
|
||||
}, null, 8, ["modelValue"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_col, {
|
||||
cols: "12",
|
||||
md: "4"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_switch, {
|
||||
modelValue: config.enable_acl4ssr,
|
||||
"onUpdate:modelValue": _cache[15] || (_cache[15] = $event => ((config.enable_acl4ssr) = $event)),
|
||||
label: "ACL4SSR规则集",
|
||||
color: "primary",
|
||||
inset: "",
|
||||
hint: "启用ACL4SSR规则集"
|
||||
}, null, 8, ["modelValue"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_col, {
|
||||
cols: "12",
|
||||
md: "4"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_switch, {
|
||||
modelValue: config.group_by_region,
|
||||
"onUpdate:modelValue": _cache[16] || (_cache[16] = $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",
|
||||
md: "4"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_text_field, {
|
||||
modelValue: config.ruleset_prefix,
|
||||
"onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((config.ruleset_prefix) = $event)),
|
||||
label: "规则集前缀",
|
||||
variant: "outlined",
|
||||
placeholder: "📂<=",
|
||||
rules: [v => !!v || '规则集前缀不能为空'],
|
||||
hint: "为生成的规则集添加前缀"
|
||||
}, {
|
||||
"prepend-inner": _withCtx(() => [
|
||||
_createVNode(_component_v_icon, { color: "info" }, {
|
||||
default: _withCtx(() => _cache[32] || (_cache[32] = [
|
||||
_createTextVNode("mdi-palette")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue", "rules"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_col, {
|
||||
cols: "12",
|
||||
md: "4"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_text_field, {
|
||||
modelValue: config.acl4ssr_prefix,
|
||||
"onUpdate:modelValue": _cache[18] || (_cache[18] = $event => ((config.acl4ssr_prefix) = $event)),
|
||||
label: "ACL4SSR 规则集前缀",
|
||||
variant: "outlined",
|
||||
placeholder: "🗂️=>",
|
||||
rules: [v => !!v || '规则集前缀不能为空'],
|
||||
hint: "ACL4SSR 规则集前缀"
|
||||
}, {
|
||||
"prepend-inner": _withCtx(() => [
|
||||
_createVNode(_component_v_icon, { color: "primary" }, {
|
||||
default: _withCtx(() => _cache[33] || (_cache[33] = [
|
||||
_createTextVNode("mdi-palette")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue", "rules"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_col, {
|
||||
cols: "12",
|
||||
md: "4"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_text_field, {
|
||||
modelValue: config.refresh_delay,
|
||||
"onUpdate:modelValue": _cache[19] || (_cache[19] = $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[34] || (_cache[34] = [
|
||||
_createTextVNode("mdi-clock-outline")
|
||||
])),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue", "rules"])
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["modelValue"])
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_alert, {
|
||||
type: "info",
|
||||
text: "",
|
||||
class: "mb-6",
|
||||
variant: "tonal"
|
||||
}, {
|
||||
default: _withCtx(() => _cache[40] || (_cache[40] = [
|
||||
_createTextVNode(" 配置说明参考: "),
|
||||
_createElementVNode("a", {
|
||||
href: "https://github.com/wumode/MoviePilot-Plugins/tree/main/plugins.v2/clashruleprovider/README.md",
|
||||
target: "_blank"
|
||||
}, "README", -1)
|
||||
])),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_card_actions, null, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_btn, {
|
||||
color: "primary",
|
||||
onClick: notifySwitch
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_icon, { left: "" }, {
|
||||
default: _withCtx(() => _cache[41] || (_cache[41] = [
|
||||
_createTextVNode("mdi-view-dashboard-edit")
|
||||
])),
|
||||
_: 1
|
||||
}),
|
||||
_cache[42] || (_cache[42] = _createTextVNode(" 规则 "))
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_btn, {
|
||||
color: "secondary",
|
||||
onClick: resetForm
|
||||
}, {
|
||||
default: _withCtx(() => _cache[43] || (_cache[43] = [
|
||||
_createTextVNode("重置")
|
||||
])),
|
||||
_: 1
|
||||
}),
|
||||
_createVNode(_component_v_btn, {
|
||||
color: "info",
|
||||
onClick: testConnection,
|
||||
loading: testing.value
|
||||
}, {
|
||||
default: _withCtx(() => _cache[44] || (_cache[44] = [
|
||||
_createTextVNode("测试连接")
|
||||
])),
|
||||
_: 1
|
||||
}, 8, ["loading"]),
|
||||
_createVNode(_component_v_spacer),
|
||||
_createVNode(_component_v_btn, {
|
||||
color: "primary",
|
||||
disabled: !isFormValid.value,
|
||||
onClick: saveConfig,
|
||||
loading: saving.value
|
||||
}, {
|
||||
default: _withCtx(() => _cache[45] || (_cache[45] = [
|
||||
_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[21] || (_cache[21] = $event => (testResult.show = false))
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createElementVNode("div", _hoisted_2, [
|
||||
_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_3, _toDisplayString(testResult.title), 1),
|
||||
_createElementVNode("div", _hoisted_4, _toDisplayString(testResult.message), 1)
|
||||
])
|
||||
])
|
||||
]),
|
||||
_: 1
|
||||
}, 8, ["type"]))
|
||||
: _createCommentVNode("", true)
|
||||
]),
|
||||
_: 1
|
||||
})
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
const ConfigComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-bd2044a8"]]);
|
||||
|
||||
export { ConfigComponent as default };
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
.plugin-config[data-v-bd2044a8] {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@@ -15118,6 +15118,9 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
maxRotation: 0,
|
||||
minRotation: 0,
|
||||
@@ -15135,6 +15138,9 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: "rgba(121, 114, 235, 0.1)"
|
||||
},
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
return formatBytes(value) + "/s";
|
||||
@@ -15169,6 +15175,9 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
maxRotation: 0,
|
||||
minRotation: 0,
|
||||
@@ -15185,6 +15194,9 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
|
||||
}
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
color: "rgba(121, 114, 235, 0.1)"
|
||||
},
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
@@ -15245,7 +15257,8 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer);
|
||||
}
|
||||
if (eventSource) eventSource.close();
|
||||
if (connectionsEvtSource) connectionsEvtSource.close();
|
||||
if (evtSource) evtSource.close();
|
||||
});
|
||||
const props = __props;
|
||||
return (_ctx, _cache) => {
|
||||
@@ -15425,7 +15438,10 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
|
||||
})
|
||||
]),
|
||||
_: 1
|
||||
})) : (_openBlock(), _createBlock(_component_v_card, { key: 1 }, {
|
||||
})) : (_openBlock(), _createBlock(_component_v_card, {
|
||||
key: 1,
|
||||
class: "h-100"
|
||||
}, {
|
||||
default: _withCtx(() => [
|
||||
_createVNode(_component_v_card_item, null, {
|
||||
default: _withCtx(() => [
|
||||
@@ -15444,13 +15460,13 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
|
||||
_createElementVNode("div", _hoisted_9, [
|
||||
clashInfo.value.version.meta ? (_openBlock(), _createBlock(_component_v_img, {
|
||||
key: 0,
|
||||
src: "https://raw.githubusercontent.com/MetaCubeX/mihomo/refs/heads/Meta/Meta.png",
|
||||
src: "/api/v1/system/img/0?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: "https://raw.githubusercontent.com/jxxghp/MoviePilot-Plugins/refs/heads/main/icons/Clash_A.png",
|
||||
src: "/api/v1/system/img/0?imgurl=https://raw.githubusercontent.com/jxxghp/MoviePilot-Plugins/refs/heads/main/icons/Clash_A.png",
|
||||
alt: "Logo",
|
||||
"max-height": "48",
|
||||
contain: ""
|
||||
@@ -15479,7 +15495,7 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
|
||||
]),
|
||||
_createElementVNode("div", _hoisted_14, [
|
||||
_cache[11] || (_cache[11] = _createElementVNode("span", null, "规则数量", -1)),
|
||||
_createElementVNode("span", null, _toDisplayString(clashInfo.value.rules.rules.length), 1)
|
||||
_createElementVNode("span", null, _toDisplayString(clashInfo.value.rules?.rules.length), 1)
|
||||
]),
|
||||
_createElementVNode("div", _hoisted_15, [
|
||||
_cache[12] || (_cache[12] = _createElementVNode("span", null, "规则集数量", -1)),
|
||||
@@ -15498,6 +15514,6 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
|
||||
}
|
||||
});
|
||||
|
||||
const DashboardComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-6c8bed46"]]);
|
||||
const DashboardComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9093476d"]]);
|
||||
|
||||
export { DashboardComponent as default };
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
.dashboard-widget[data-v-6c8bed46] {
|
||||
.dashboard-widget[data-v-9093476d] {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
|
||||
.plugin-page[data-v-eae2a1d4] {
|
||||
max-width: 1200px;
|
||||
.plugin-page[data-v-fd7b621d] {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 使卡片等宽并适应移动端 */
|
||||
.d-flex.flex-wrap[data-v-eae2a1d4] {
|
||||
.d-flex.flex-wrap[data-v-fd7b621d] {
|
||||
gap: 16px;
|
||||
}
|
||||
.url-display[data-v-eae2a1d4] {
|
||||
.url-display[data-v-fd7b621d] {
|
||||
word-break: break-all;
|
||||
padding: 8px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
@@ -17,19 +16,19 @@
|
||||
|
||||
/* 移动端堆叠布局 */
|
||||
@media (max-width: 768px) {
|
||||
.d-flex.flex-wrap[data-v-eae2a1d4] {
|
||||
.d-flex.flex-wrap[data-v-fd7b621d] {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add visual distinction between sections */
|
||||
.ruleset-section[data-v-eae2a1d4] {
|
||||
.ruleset-section[data-v-fd7b621d] {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.top-section[data-v-eae2a1d4] {
|
||||
.top-section[data-v-fd7b621d] {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
@@ -37,12 +36,15 @@
|
||||
}
|
||||
|
||||
/* Optional: Add different border colors to further distinguish */
|
||||
.ruleset-section[data-v-eae2a1d4] {
|
||||
.ruleset-section[data-v-fd7b621d] {
|
||||
border-left: 4px solid #2196F3; /* Blue accent */
|
||||
}
|
||||
.top-section[data-v-eae2a1d4] {
|
||||
.top-section[data-v-fd7b621d] {
|
||||
border-left: 4px solid #4CAF50; /* Green accent */
|
||||
}
|
||||
.drag-handle[data-v-eae2a1d4] {
|
||||
.drag-handle[data-v-fd7b621d] {
|
||||
cursor: move;
|
||||
}
|
||||
.gap-2[data-v-fd7b621d] {
|
||||
gap: 8px;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,14 +2,14 @@ const currentImports = {};
|
||||
const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']);
|
||||
let moduleMap = {
|
||||
"./Page":()=>{
|
||||
dynamicLoadingCss(["__federation_expose_Page-DQjiFgWw.css"], false, './Page');
|
||||
return __federation_import('./__federation_expose_Page--ZdI8TQS.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
dynamicLoadingCss(["__federation_expose_Page-DIAzDhN_.css"], false, './Page');
|
||||
return __federation_import('./__federation_expose_Page-gjHBCfnd.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
"./Config":()=>{
|
||||
dynamicLoadingCss(["__federation_expose_Config-xrxN2F1l.css"], false, './Config');
|
||||
return __federation_import('./__federation_expose_Config-sTz3W3yr.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
dynamicLoadingCss(["__federation_expose_Config-BDDunIfC.css"], false, './Config');
|
||||
return __federation_import('./__federation_expose_Config-r5zwvDwv.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
|
||||
"./Dashboard":()=>{
|
||||
dynamicLoadingCss(["__federation_expose_Dashboard-IipjE6HA.css"], false, './Dashboard');
|
||||
return __federation_import('./__federation_expose_Dashboard-DEFRy9WP.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},};
|
||||
dynamicLoadingCss(["__federation_expose_Dashboard-D6WU_Ejn.css"], false, './Dashboard');
|
||||
return __federation_import('./__federation_expose_Dashboard-BFVr4jq_.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;
|
||||
|
||||
Reference in New Issue
Block a user