mirror of
https://github.com/cedar2025/Xboard.git
synced 2025-02-21 23:38:14 -05:00
feat(admin): Add subscription template configuration and fix minor issues
This commit is contained in:
parent
30a140f2d8
commit
8098cf3ee2
@ -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;
|
||||
|
@ -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]);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
} else {
|
||||
$config = Yaml::parseFile($defaultConfig);
|
||||
|
||||
// 优先从 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 {
|
||||
$template = file_get_contents($defaultConfig);
|
||||
}
|
||||
}
|
||||
|
||||
$config = Yaml::parse($template);
|
||||
$proxy = [];
|
||||
$proxies = [];
|
||||
|
||||
|
@ -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);
|
||||
} else {
|
||||
$config = Yaml::parseFile($defaultConfig);
|
||||
|
||||
// 优先从 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 {
|
||||
$template = file_get_contents($defaultConfig);
|
||||
}
|
||||
}
|
||||
|
||||
$config = Yaml::parse($template);
|
||||
$proxy = [];
|
||||
$proxies = [];
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
} else {
|
||||
$config = Yaml::parseFile($defaultConfig);
|
||||
|
||||
// 优先从 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 {
|
||||
$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';
|
||||
|
@ -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");
|
||||
} else {
|
||||
$config = file_get_contents("$defaultConfig");
|
||||
// 优先从 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Subscription link
|
||||
|
2
public/assets/admin/assets/index.css
vendored
2
public/assets/admin/assets/index.css
vendored
File diff suppressed because one or more lines are too long
14
public/assets/admin/assets/index.js
vendored
14
public/assets/admin/assets/index.js
vendored
File diff suppressed because one or more lines are too long
912
public/assets/admin/assets/vendor.js
vendored
912
public/assets/admin/assets/vendor.js
vendored
File diff suppressed because one or more lines are too long
32
public/assets/admin/locales/en-US.js
vendored
32
public/assets/admin/locales/en-US.js
vendored
@ -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",
|
||||
|
32
public/assets/admin/locales/ko-KR.js
vendored
32
public/assets/admin/locales/ko-KR.js
vendored
@ -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 서버로 해석",
|
||||
|
32
public/assets/admin/locales/zh-CN.js
vendored
32
public/assets/admin/locales/zh-CN.js
vendored
@ -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服务器进行解析",
|
||||
|
Loading…
Reference in New Issue
Block a user