feat(admin): Add subscription template configuration and fix minor issues

This commit is contained in:
xboard 2025-02-16 18:05:03 +08:00
parent 30a140f2d8
commit 8098cf3ee2
14 changed files with 682 additions and 499 deletions

View File

@ -202,7 +202,7 @@ class XboardInstall extends Command
$this->info(Artisan::output());
$this->info('数据库导入完成');
$this->info('开始注册管理员账号');
if (!$this->registerAdmin($email, $password)) {
if (!self::registerAdmin($email, $password)) {
abort(500, '管理员账号注册失败,请重试');
}
$this->info('🎉:一切就绪');
@ -218,7 +218,7 @@ class XboardInstall extends Command
}
}
public function registerAdmin($email, $password)
public static function registerAdmin($email, $password)
{
$user = new User();
$user->email = $email;

View File

@ -10,6 +10,7 @@ use App\Services\TelegramService;
use App\Services\ThemeService;
use App\Utils\Dict;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
class ConfigController extends Controller
{
@ -63,6 +64,12 @@ class ConfigController extends Controller
return $this->success(true);
}
private function getTemplateContent(string $filename): string
{
$path = resource_path("rules/{$filename}");
return File::exists($path) ? File::get($path) : '';
}
public function fetch(Request $request)
{
$key = $request->input('key');
@ -162,6 +169,47 @@ class ConfigController extends Controller
'password_limit_enable' => (bool) admin_setting('password_limit_enable', 1),
'password_limit_count' => admin_setting('password_limit_count', 5),
'password_limit_expire' => admin_setting('password_limit_expire', 60)
],
'subscribe_template' => [
'subscribe_template_singbox' => (function () {
$template = admin_setting('subscribe_template_singbox');
if (!empty($template)) {
return is_array($template)
? json_encode($template, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
: $template;
}
$content = file_exists(base_path('resources/rules/custom.sing-box.json'))
? file_get_contents(base_path('resources/rules/custom.sing-box.json'))
: file_get_contents(base_path('resources/rules/default.sing-box.json'));
// 确保返回格式化的 JSON 字符串
return json_encode(json_decode($content), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
})(),
'subscribe_template_clash' => (string) (admin_setting('subscribe_template_clash') ?: (
file_exists(base_path('resources/rules/custom.clash.yaml'))
? file_get_contents(base_path('resources/rules/custom.clash.yaml'))
: file_get_contents(base_path('resources/rules/default.clash.yaml'))
)),
'subscribe_template_clashmeta' => (string) (admin_setting('subscribe_template_clashmeta') ?: (
file_exists(base_path('resources/rules/custom.clashmeta.yaml'))
? file_get_contents(base_path('resources/rules/custom.clashmeta.yaml'))
: (file_exists(base_path('resources/rules/custom.clash.yaml'))
? file_get_contents(base_path('resources/rules/custom.clash.yaml'))
: file_get_contents(base_path('resources/rules/default.clash.yaml')))
)),
'subscribe_template_stash' => (string) (admin_setting('subscribe_template_stash') ?: (
file_exists(base_path('resources/rules/custom.stash.yaml'))
? file_get_contents(base_path('resources/rules/custom.stash.yaml'))
: (file_exists(base_path('resources/rules/custom.clash.yaml'))
? file_get_contents(base_path('resources/rules/custom.clash.yaml'))
: file_get_contents(base_path('resources/rules/default.clash.yaml')))
)),
'subscribe_template_surge' => (string) (admin_setting('subscribe_template_surge') ?: (
file_exists(base_path('resources/rules/custom.surge.conf'))
? file_get_contents(base_path('resources/rules/custom.surge.conf'))
: file_get_contents(base_path('resources/rules/default.surge.conf'))
)),
]
];
if ($key && isset($data[$key])) {
@ -179,7 +227,7 @@ class ConfigController extends Controller
$data = $request->validated();
foreach ($data as $k => $v) {
if ($k == 'frontend_theme') {
$themeService = new ThemeService();
$themeService = app(ThemeService::class);
$themeService->switch($v);
}
admin_setting([$k => $v]);

View File

@ -95,7 +95,12 @@ class ConfigSave extends FormRequest
'password_limit_count' => 'integer',
'password_limit_expire' => 'integer',
'default_remind_expire' => 'boolean',
'default_remind_traffic' => 'boolean'
'default_remind_traffic' => 'boolean',
'subscribe_template_singbox' => 'nullable',
'subscribe_template_clash' => 'nullable',
'subscribe_template_clashmeta' => 'nullable',
'subscribe_template_stash' => 'nullable',
'subscribe_template_surge' => 'nullable',
];
/**
* Get the validation rules that apply to the request.

View File

@ -28,13 +28,20 @@ class Clash implements ProtocolInterface
$servers = $this->servers;
$user = $this->user;
$appName = admin_setting('app_name', 'XBoard');
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.clash.yaml';
if (\File::exists($customConfig)) {
$config = Yaml::parseFile($customConfig);
// 优先从 admin_setting 获取模板
$template = admin_setting('subscribe_template_clash');
if (empty($template)) {
$defaultConfig = base_path('resources/rules/default.clash.yaml');
$customConfig = base_path('resources/rules/custom.clash.yaml');
if (file_exists($customConfig)) {
$template = file_get_contents($customConfig);
} else {
$config = Yaml::parseFile($defaultConfig);
$template = file_get_contents($defaultConfig);
}
}
$config = Yaml::parse($template);
$proxy = [];
$proxies = [];

View File

@ -29,16 +29,23 @@ class ClashMeta implements ProtocolInterface
$servers = $this->servers;
$user = $this->user;
$appName = admin_setting('app_name', 'XBoard');
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
$customClashConfig = base_path() . '/resources/rules/custom.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.clashmeta.yaml';
if (\File::exists($customConfig)) {
$config = Yaml::parseFile($customConfig);
} elseif (\File::exists($customClashConfig)) {
$config = Yaml::parseFile($customClashConfig);
// 优先从 admin_setting 获取模板
$template = admin_setting('subscribe_template_clashmeta');
if (empty($template)) {
$defaultConfig = base_path('resources/rules/default.clash.yaml');
$customClashConfig = base_path('resources/rules/custom.clash.yaml');
$customConfig = base_path('resources/rules/custom.clashmeta.yaml');
if (file_exists($customConfig)) {
$template = file_get_contents($customConfig);
} elseif (file_exists($customClashConfig)) {
$template = file_get_contents($customClashConfig);
} else {
$config = Yaml::parseFile($defaultConfig);
$template = file_get_contents($defaultConfig);
}
}
$config = Yaml::parse($template);
$proxy = [];
$proxies = [];

View File

@ -39,6 +39,12 @@ class SingBox implements ProtocolInterface
protected function loadConfig()
{
// 优先从 admin_setting 获取模板
$template = admin_setting('subscribe_template_singbox');
if (!empty($template)) {
return is_array($template) ? $template : json_decode($template, true);
}
$defaultConfig = base_path('resources/rules/default.sing-box.json');
$customConfig = base_path('resources/rules/custom.sing-box.json');
$jsonData = file_exists($customConfig) ? file_get_contents($customConfig) : file_get_contents($defaultConfig);

View File

@ -28,17 +28,23 @@ class Stash implements ProtocolInterface
$servers = $this->servers;
$user = $this->user;
$appName = admin_setting('app_name', 'XBoard');
// 暂时使用clash配置文件后续根据Stash更新情况更新
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
$customClashConfig = base_path() . '/resources/rules/custom.clash.yaml';
$customStashConfig = base_path() . '/resources/rules/custom.stash.yaml';
if (\File::exists($customStashConfig)) {
$config = Yaml::parseFile($customStashConfig);
} elseif (\File::exists($customClashConfig)) {
$config = Yaml::parseFile($customClashConfig);
// 优先从 admin_setting 获取模板
$template = admin_setting('subscribe_template_stash');
if (empty($template)) {
$defaultConfig = base_path('resources/rules/default.clash.yaml');
$customClashConfig = base_path('resources/rules/custom.clash.yaml');
$customStashConfig = base_path('resources/rules/custom.stash.yaml');
if (file_exists($customStashConfig)) {
$template = file_get_contents($customStashConfig);
} elseif (file_exists($customClashConfig)) {
$template = file_get_contents($customClashConfig);
} else {
$config = Yaml::parseFile($defaultConfig);
$template = file_get_contents($defaultConfig);
}
}
$config = Yaml::parse($template);
$proxy = [];
$proxies = [];
@ -153,8 +159,8 @@ class Stash implements ProtocolInterface
switch (data_get($protocol_settings, 'network')) {
case 'tcp':
$array['network'] = data_get($protocol_settings, 'network_settings.header.type');
$array['http-opts']['path'] = data_get($protocol_settings, 'network_settings.header.request.path', ['/'])[0];
$array['network'] = data_get($protocol_settings, 'network_settings.header.type', 'tcp');
$array['http-opts']['path'] = data_get($protocol_settings, 'network_settings.header.request.path', ['/']);
break;
case 'ws':
$array['network'] = 'ws';

View File

@ -59,12 +59,16 @@ class Surge implements ProtocolInterface
}
}
$defaultConfig = base_path() . '/resources/rules/default.surge.conf';
$customConfig = base_path() . '/resources/rules/custom.surge.conf';
if (\File::exists($customConfig)) {
$config = file_get_contents("$customConfig");
// 优先从 admin_setting 获取模板
$config = admin_setting('subscribe_template_surge');
if (empty($config)) {
$defaultConfig = base_path('resources/rules/default.surge.conf');
$customConfig = base_path('resources/rules/custom.surge.conf');
if (file_exists($customConfig)) {
$config = file_get_contents($customConfig);
} else {
$config = file_get_contents("$defaultConfig");
$config = file_get_contents($defaultConfig);
}
}
// Subscription link

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -775,6 +775,30 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
"save_success": "Saved automatically",
"placeholder": "Please input",
"autoSaved": "Saved automatically"
},
"subscribe_template": {
"title": "Subscribe Templates",
"description": "Configure subscription templates for different clients",
"singbox": {
"title": "Sing-box Template",
"description": "Configure subscription template format for Sing-box"
},
"clash": {
"title": "Clash Template",
"description": "Configure subscription template format for Clash"
},
"clashmeta": {
"title": "Clash Meta Template",
"description": "Configure subscription template format for Clash Meta"
},
"stash": {
"title": "Stash Template",
"description": "Configure subscription template format for Stash"
},
"surge": {
"title": "Surge Template",
"description": "Configure subscription template format for Surge"
}
}
},
"group": {
@ -1029,7 +1053,13 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
"remarks": "Remarks",
"action": "Action",
"actions": "Actions",
"matchRules": "Match {{count}} rules"
"matchRules": "Match {{count}} rules",
"action_value": {
"title": "Action Value",
"dns": "DNS: {{value}}",
"block": "Block Access",
"direct": "Direct Connection"
}
},
"actions": {
"dns": "Resolve using specified DNS server",

View File

@ -773,6 +773,30 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
"save_success": "자동으로 저장됨",
"placeholder": "입력해주세요",
"autoSaved": "자동으로 저장됨"
},
"subscribe_template": {
"title": "구독 템플릿",
"description": "다양한 클라이언트의 구독 템플릿 설정",
"singbox": {
"title": "Sing-box 템플릿",
"description": "Sing-box의 구독 템플릿 형식 설정"
},
"clash": {
"title": "Clash 템플릿",
"description": "Clash의 구독 템플릿 형식 설정"
},
"clashmeta": {
"title": "Clash Meta 템플릿",
"description": "Clash Meta의 구독 템플릿 형식 설정"
},
"stash": {
"title": "Stash 템플릿",
"description": "Stash의 구독 템플릿 형식 설정"
},
"surge": {
"title": "Surge 템플릿",
"description": "Surge의 구독 템플릿 형식 설정"
}
}
},
"group": {
@ -1025,7 +1049,13 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
"remarks": "비고",
"action": "동작",
"actions": "작업",
"matchRules": "{{count}}개 규칙 일치"
"matchRules": "{{count}}개 규칙 일치",
"action_value": {
"title": "작업 값",
"dns": "DNS: {{value}}",
"block": "접근 차단",
"direct": "직접 연결"
}
},
"actions": {
"dns": "지정된 DNS 서버로 해석",

View File

@ -780,6 +780,30 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
"title": "节点管理",
"description": "管理所有节点,包括添加、删除、编辑等操作。"
}
},
"subscribe_template": {
"title": "订阅模板",
"description": "配置各个客户端的订阅模板",
"singbox": {
"title": "Sing-box 订阅模板",
"description": "配置 Sing-box 的订阅模板格式"
},
"clash": {
"title": "Clash 订阅模板",
"description": "配置 Clash 的订阅模板格式"
},
"clashmeta": {
"title": "Clash Meta 订阅模板",
"description": "配置 Clash Meta 的订阅模板格式"
},
"stash": {
"title": "Stash 订阅模板",
"description": "配置 Stash 的订阅模板格式"
},
"surge": {
"title": "Surge 配置模板",
"description": "配置 Surge 订阅模板,支持 Surge 配置文件格式"
}
}
},
"group": {
@ -1021,7 +1045,13 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
"remarks": "备注",
"action": "动作",
"actions": "操作",
"matchRules": "匹配{{count}}条规则"
"matchRules": "匹配{{count}}条规则",
"action_value": {
"title": "动作值",
"dns": "DNS: {{value}}",
"block": "阻止访问",
"direct": "直接连接"
}
},
"actions": {
"dns": "指定DNS服务器进行解析",