Compare commits

...

7 Commits

Author SHA1 Message Date
xboard
d81974d8bc feat: add shadowsockstidalab、trojantidala api
Some checks are pending
Docker Build and Publish / build (push) Waiting to run
2025-01-18 21:36:18 +08:00
xboard
1298a6fbbc fix: migrate command again 2025-01-18 20:23:46 +08:00
xboard
e6c33776e5 update docs 2025-01-18 19:06:04 +08:00
xboard
9f95f015a0 fix: migrate command 2025-01-18 18:52:48 +08:00
xboard
9973db24d0 fix: telegram bind 2025-01-18 17:21:31 +08:00
xboard
81ef1b8909 update app.php 2025-01-18 17:21:05 +08:00
xboard
43faab4e9c feat: add plugin support 2025-01-18 17:12:07 +08:00
25 changed files with 1341 additions and 275 deletions

View File

@ -53,11 +53,12 @@ class MigrateFromV2b extends Command
],
'1.7.3' => [
'ALTER TABLE `v2_stat_order` RENAME TO `v2_stat`;',
"ALTER TABLE `v2_stat` CHANGE COLUMN order_amount order_total INT COMMENT '订单合计';",
"ALTER TABLE `v2_stat` CHANGE COLUMN order_amount paid_total INT COMMENT '订单合计';",
"ALTER TABLE `v2_stat` CHANGE COLUMN order_count paid_count INT COMMENT '邀请佣金';",
"ALTER TABLE `v2_stat` CHANGE COLUMN commission_amount commission_total INT COMMENT '佣金合计';",
"ALTER TABLE `v2_stat`
ADD COLUMN paid_count INT NULL,
ADD COLUMN paid_total INT NULL,
ADD COLUMN order_count INT NULL,
ADD COLUMN order_total INT NULL,
ADD COLUMN register_count INT NULL,
ADD COLUMN invite_count INT NULL,
ADD COLUMN transfer_used_total VARCHAR(32) NULL;

View File

@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers\V1\Server;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Models\ServerShadowsocks;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
/*
* Tidal Lab Shadowsocks
* Github: https://github.com/tokumeikoi/tidalab-ss
*/
class ShadowsocksTidalabController extends Controller
{
// 后端获取用户
public function user(Request $request)
{
ini_set('memory_limit', -1);
$server = $request->input('node_info');
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server->id), time(), 3600);
$users = ServerService::getAvailableUsers($server->group_ids);
$result = [];
foreach ($users as $user) {
array_push($result, [
'id' => $user->id,
'port' => $server->server_port,
'cipher' => $server->cipher,
'secret' => $user->uuid
]);
}
$eTag = sha1(json_encode($result));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
return response(null,304);
}
return response([
'data' => $result
])->header('ETag', "\"{$eTag}\"");
}
// 后端提交数据
public function submit(Request $request)
{
$server = $request->input('node_info');
$data = json_decode(request()->getContent(), true);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$formatData[$item['user_id']] = [$item['u'], $item['d']];
}
$userService->trafficFetch($server->toArray(), 'shadowsocks', $formatData);
return response([
'ret' => 1,
'msg' => 'ok'
]);
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers\V1\Server;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Models\ServerTrojan;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
/*
* Tidal Lab Trojan
* Github: https://github.com/tokumeikoi/tidalab-trojan
*/
class TrojanTidalabController extends Controller
{
const TROJAN_CONFIG = '{"run_type":"server","local_addr":"0.0.0.0","local_port":443,"remote_addr":"www.taobao.com","remote_port":80,"password":[],"ssl":{"cert":"server.crt","key":"server.key","sni":"domain.com"},"api":{"enabled":true,"api_addr":"127.0.0.1","api_port":10000}}';
// 后端获取用户
public function user(Request $request)
{
ini_set('memory_limit', -1);
$server = $request->input('node_info');
if ($server->type !== 'trojan') {
return $this->fail([400, '节点不存在']);
}
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
$users = ServerService::getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
$user->trojan_user = [
"password" => $user->uuid,
];
unset($user->uuid);
array_push($result, $user);
}
$eTag = sha1(json_encode($result));
if (strpos($request->header('If-None-Match'), $eTag) !== false) {
return response(null, 304);
}
return response([
'msg' => 'ok',
'data' => $result,
])->header('ETag', "\"{$eTag}\"");
}
// 后端提交数据
public function submit(Request $request)
{
$server = $request->input('node_info');
if ($server->type !== 'trojan') {
return $this->fail([400, '节点不存在']);
}
$data = json_decode(request()->getContent(), true);
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$formatData[$item['user_id']] = [$item['u'], $item['d']];
}
$userService->trafficFetch($server->toArray(), 'trojan', $formatData);
return response([
'ret' => 1,
'msg' => 'ok'
]);
}
// 后端获取配置
public function config(Request $request)
{
$server = $request->input('node_info');
if ($server->type !== 'trojan') {
return $this->fail([400, '节点不存在']);
}
$request->validate([
'node_id' => 'required',
'local_port' => 'required'
], [
'node_id.required' => '节点ID不能为空',
'local_port.required' => '本地端口不能为空'
]);
try {
$json = $this->getTrojanConfig($server, $request->input('local_port'));
} catch (\Exception $e) {
\Log::error($e);
return $this->fail([500, '配置获取失败']);
}
return (json_encode($json, JSON_UNESCAPED_UNICODE));
}
private function getTrojanConfig($server, int $localPort)
{
$protocolSettings = $server->protocol_settings;
$json = json_decode(self::TROJAN_CONFIG);
$json->local_port = $server->server_port;
$json->ssl->sni = data_get($protocolSettings, 'server_name', $server->host);
$json->ssl->cert = "/root/.cert/server.crt";
$json->ssl->key = "/root/.cert/server.key";
$json->api->api_port = $localPort;
return $json;
}
}

View File

@ -14,6 +14,7 @@ use App\Services\CouponService;
use App\Services\OrderService;
use App\Services\PaymentService;
use App\Services\PlanService;
use App\Services\Plugin\HookManager;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
@ -112,6 +113,7 @@ class OrderController extends Controller
if (!$order->save()) {
throw new ApiException(__('Failed to create order'));
}
HookManager::call('order.after_create', $order);
return $this->success($order->trade_no);
});

View File

@ -0,0 +1,195 @@
<?php
namespace App\Http\Controllers\V2\Admin;
use App\Http\Controllers\Controller;
use App\Models\Plugin;
use App\Services\Plugin\PluginManager;
use App\Services\Plugin\PluginConfigService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
class PluginController extends Controller
{
protected PluginManager $pluginManager;
protected PluginConfigService $configService;
public function __construct(
PluginManager $pluginManager,
PluginConfigService $configService
) {
$this->pluginManager = $pluginManager;
$this->configService = $configService;
}
/**
* 获取插件列表
*/
public function index()
{
$installedPlugins = Plugin::get()
->keyBy('code')
->toArray();
$pluginPath = base_path('plugins');
$plugins = [];
if (File::exists($pluginPath)) {
$directories = File::directories($pluginPath);
foreach ($directories as $directory) {
$pluginName = basename($directory);
$configFile = $directory . '/config.json';
if (File::exists($configFile)) {
$config = json_decode(File::get($configFile), true);
$installed = isset($installedPlugins[$pluginName]);
// 使用配置服务获取配置
$pluginConfig = $installed ? $this->configService->getConfig($pluginName) : ($config['config'] ?? []);
$plugins[] = [
'code' => $config['code'],
'name' => $config['name'],
'version' => $config['version'],
'description' => $config['description'],
'author' => $config['author'],
'is_installed' => $installed,
'is_enabled' => $installed ? $installedPlugins[$pluginName]['is_enabled'] : false,
'config' => $pluginConfig,
];
}
}
}
return response()->json([
'data' => $plugins
]);
}
/**
* 安装插件
*/
public function install(Request $request)
{
$request->validate([
'code' => 'required|string'
]);
try {
$this->pluginManager->install($request->input('code'));
return response()->json([
'message' => '插件安装成功'
]);
} catch (\Exception $e) {
return response()->json([
'message' => '插件安装失败:' . $e->getMessage()
], 400);
}
}
/**
* 卸载插件
*/
public function uninstall(Request $request)
{
$request->validate([
'code' => 'required|string'
]);
try {
$this->pluginManager->uninstall($request->input('code'));
return response()->json([
'message' => '插件卸载成功'
]);
} catch (\Exception $e) {
return response()->json([
'message' => '插件卸载失败:' . $e->getMessage()
], 400);
}
}
/**
* 启用插件
*/
public function enable(Request $request)
{
$request->validate([
'code' => 'required|string'
]);
try {
$this->pluginManager->enable($request->input('code'));
return response()->json([
'message' => '插件启用成功'
]);
} catch (\Exception $e) {
return response()->json([
'message' => '插件启用失败:' . $e->getMessage()
], 400);
}
}
/**
* 禁用插件
*/
public function disable(Request $request)
{
$request->validate([
'code' => 'required|string'
]);
try {
$this->pluginManager->disable($request->input('code'));
return response()->json([
'message' => '插件禁用成功'
]);
} catch (\Exception $e) {
return response()->json([
'message' => '插件禁用失败:' . $e->getMessage()
], 400);
}
}
/**
* 获取插件配置
*/
public function getConfig(Request $request)
{
$request->validate([
'code' => 'required|string'
]);
try {
$config = $this->configService->getConfig($request->input('code'));
return response()->json([
'data' => $config
]);
} catch (\Exception $e) {
return response()->json([
'message' => '获取配置失败:' . $e->getMessage()
], 400);
}
}
/**
* 更新插件配置
*/
public function updateConfig(Request $request)
{
$request->validate([
'code' => 'required|string',
'config' => 'required|array'
]);
try {
$this->configService->updateConfig(
$request->input('code'),
$request->input('config')
);
return response()->json([
'message' => '配置更新成功'
]);
} catch (\Exception $e) {
return response()->json([
'message' => '配置更新失败:' . $e->getMessage()
], 400);
}
}
}

View File

@ -41,7 +41,6 @@ class Server
],
'node_id' => 'required',
'node_type' => [
'required',
'nullable',
function ($attribute, $value, $fail) use ($request) {
if (!ServerModel::isValidType($value)) {

View File

@ -24,6 +24,21 @@ class ServerRoute
$route->post('alive', [UniProxyController::class, 'alive']);
$route->get('alivelist', [UniProxyController::class, 'alivelist']);
});
$router->group([
'prefix' => 'ShadowsocksTidalab',
'middleware' => 'server:shadowsocks'
], function ($route) {
$route->get('user', [ShadowsocksTidalabController::class, 'user']);
$route->post('submit', [ShadowsocksTidalabController::class, 'submit']);
});
$router->group([
'prefix' => 'TrojanTidalab',
'middleware' => 'server:trojan'
], function ($route) {
$route->get('config', [TrojanTidalabController::class, 'config']);
$route->get('user', [TrojanTidalabController::class, 'user']);
$route->post('submit', [TrojanTidalabController::class, 'submit']);
});
});
}
}

View File

@ -17,6 +17,7 @@ use App\Http\Controllers\V2\Admin\PaymentController;
use App\Http\Controllers\V2\Admin\SystemController;
use App\Http\Controllers\V2\Admin\ThemeController;
use Illuminate\Contracts\Routing\Registrar;
use Illuminate\Support\Facades\Route;
class AdminRoute
{
@ -202,6 +203,20 @@ class AdminRoute
$router->post('/saveThemeConfig', [ThemeController::class, 'saveThemeConfig']);
$router->post('/getThemeConfig', [ThemeController::class, 'getThemeConfig']);
});
// Plugin
$router->group([
'prefix' => 'plugin'
], function ($router) {
$router->get('/getPlugins', [\App\Http\Controllers\V2\Admin\PluginController::class, 'index']);
$router->post('install', [\App\Http\Controllers\V2\Admin\PluginController::class, 'install']);
$router->post('uninstall', [\App\Http\Controllers\V2\Admin\PluginController::class, 'uninstall']);
$router->post('enable', [\App\Http\Controllers\V2\Admin\PluginController::class, 'enable']);
$router->post('disable', [\App\Http\Controllers\V2\Admin\PluginController::class, 'disable']);
$router->get('config', [\App\Http\Controllers\V2\Admin\PluginController::class, 'getConfig']);
$router->post('config', [\App\Http\Controllers\V2\Admin\PluginController::class, 'updateConfig']);
});
});
}
}

16
app/Models/Plugin.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Plugin extends Model
{
protected $table = 'v2_plugins';
protected $guarded = [
'id',
'created_at',
'updated_at'
];
}

View File

@ -17,8 +17,19 @@ class Bind extends Telegram {
}
$subscribeUrl = $message->args[0];
$subscribeUrl = parse_url($subscribeUrl);
parse_str($subscribeUrl['query'], $query);
$token = $query['token'];
// 首先尝试从查询参数获取token
$token = null;
if (isset($subscribeUrl['query'])) {
parse_str($subscribeUrl['query'], $query);
$token = $query['token'] ?? null;
}
if (!$token && isset($subscribeUrl['path'])) {
$pathParts = explode('/', trim($subscribeUrl['path'], '/'));
$token = end($pathParts);
}
if (!$token) {
throw new ApiException('订阅地址无效');
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Providers;
use App\Models\Plugin;
use App\Services\Plugin\PluginManager;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\DB;
class PluginServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->scoped(PluginManager::class, function ($app) {
return new PluginManager();
});
}
public function boot(): void
{
if (!file_exists(base_path('plugins'))) {
mkdir(base_path('plugins'), 0755, true);
}
try {
$plugins = Plugin::query()
->where('is_enabled', true)
->get();
foreach ($plugins as $plugin) {
$manager = $this->app->make(PluginManager::class);
$manager->enable($plugin->code);
}
} catch (\Exception $e) {
\Log::error('Failed to load plugins: ' . $e->getMessage());
}
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Services\Plugin;
abstract class AbstractPlugin
{
protected array $config = [];
/**
* 插件启动时调用
*/
public function boot(): void
{
// 子类实现具体逻辑
}
/**
* 插件禁用时调用
*/
public function cleanup(): void
{
// 子类实现具体逻辑
}
/**
* 设置配置
*/
public function setConfig(array $config): void
{
$this->config = $config;
}
/**
* 获取配置
*/
public function getConfig(): array
{
return $this->config;
}
/**
* 注册事件监听器
*/
protected function listen(string $hook, callable $callback): void
{
HookManager::register($hook, $callback);
}
/**
* 移除事件监听器
*/
protected function removeListener(string $hook): void
{
HookManager::remove($hook);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Services\Plugin;
use Illuminate\Support\Facades\Event;
class HookManager
{
/**
* 触发钩子
*
* @param string $hook 钩子名称
* @param mixed $payload 传递给钩子的数据
* @return mixed
*/
public static function call(string $hook, mixed $payload = null): mixed
{
return Event::dispatch($hook, [$payload]);
}
/**
* 注册钩子监听器
*
* @param string $hook 钩子名称
* @param callable $callback 回调函数
* @return void
*/
public static function register(string $hook, callable $callback): void
{
Event::listen($hook, $callback);
}
/**
* 移除钩子监听器
*
* @param string $hook 钩子名称
* @return void
*/
public static function remove(string $hook): void
{
Event::forget($hook);
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace App\Services\Plugin;
use App\Models\Plugin;
use Illuminate\Support\Facades\File;
class PluginConfigService
{
/**
* 获取插件配置
*
* @param string $pluginCode
* @return array
*/
public function getConfig(string $pluginCode): array
{
$defaultConfig = $this->getDefaultConfig($pluginCode);
if (empty($defaultConfig)) {
return [];
}
$dbConfig = $this->getDbConfig($pluginCode);
$result = [];
foreach ($defaultConfig as $key => $item) {
$result[$key] = [
'type' => $item['type'],
'label' => $item['label'] ?? '',
'placeholder' => $item['placeholder'] ?? '',
'description' => $item['description'] ?? '',
'value' => $dbConfig[$key] ?? $item['default']
];
}
return $result;
}
/**
* 更新插件配置
*
* @param string $pluginCode
* @param array $config
* @return bool
*/
public function updateConfig(string $pluginCode, array $config): bool
{
$defaultConfig = $this->getDefaultConfig($pluginCode);
if (empty($defaultConfig)) {
throw new \Exception('插件配置结构不存在');
}
$values = [];
foreach ($config as $key => $value) {
if (!isset($defaultConfig[$key])) {
continue;
}
$values[$key] = $value;
}
Plugin::query()
->where('code', $pluginCode)
->update([
'config' => json_encode($values),
'updated_at' => now()
]);
return true;
}
/**
* 获取插件默认配置
*
* @param string $pluginCode
* @return array
*/
protected function getDefaultConfig(string $pluginCode): array
{
$configFile = base_path("plugins/{$pluginCode}/config.json");
if (!File::exists($configFile)) {
return [];
}
$config = json_decode(File::get($configFile), true);
return $config['config'] ?? [];
}
/**
* 获取数据库中的配置
*
* @param string $pluginCode
* @return array
*/
protected function getDbConfig(string $pluginCode): array
{
$plugin = Plugin::query()
->where('code', $pluginCode)
->first();
if (!$plugin || empty($plugin->config)) {
return [];
}
return json_decode($plugin->config, true);
}
}

View File

@ -0,0 +1,180 @@
<?php
namespace App\Services\Plugin;
use App\Models\Plugin;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
class PluginManager
{
protected string $pluginPath;
public function __construct()
{
$this->pluginPath = base_path('plugins');
}
/**
* 安装插件
*/
public function install(string $pluginCode): bool
{
$configFile = $this->pluginPath . '/' . $pluginCode . '/config.json';
if (!File::exists($configFile)) {
throw new \Exception('Plugin config file not found');
}
$config = json_decode(File::get($configFile), true);
if (!$this->validateConfig($config)) {
throw new \Exception('Invalid plugin config');
}
// 检查依赖
if (!$this->checkDependencies($config['require'] ?? [])) {
throw new \Exception('Dependencies not satisfied');
}
// 提取配置默认值
$defaultValues = [];
if (isset($config['config']) && is_array($config['config'])) {
foreach ($config['config'] as $key => $item) {
$defaultValues[$key] = $item['default'] ?? null;
}
}
// 注册到数据库
Plugin::create([
'code' => $pluginCode,
'name' => $config['name'],
'version' => $config['version'],
'is_enabled' => false,
'config' => json_encode($defaultValues),
'installed_at' => now(),
]);
return true;
}
/**
* 启用插件
*/
public function enable(string $pluginCode): bool
{
$plugin = $this->loadPlugin($pluginCode);
if (!$plugin) {
throw new \Exception('Plugin not found');
}
// 获取插件配置
$dbPlugin = Plugin::query()
->where('code', $pluginCode)
->first();
if ($dbPlugin && !empty($dbPlugin->config)) {
$plugin->setConfig(json_decode($dbPlugin->config, true));
}
// 更新数据库状态
Plugin::query()
->where('code', $pluginCode)
->update([
'is_enabled' => true,
'updated_at' => now(),
]);
// 加载路由
$routesFile = $this->pluginPath . '/' . $pluginCode . '/routes/web.php';
if (File::exists($routesFile)) {
require $routesFile;
}
// 初始化插件
if (method_exists($plugin, 'boot')) {
$plugin->boot();
}
return true;
}
/**
* 禁用插件
*/
public function disable(string $pluginCode): bool
{
$plugin = $this->loadPlugin($pluginCode);
if (!$plugin) {
throw new \Exception('Plugin not found');
}
// 更新数据库状态
Plugin::query()
->where('code', $pluginCode)
->update([
'is_enabled' => false,
'updated_at' => now(),
]);
// 清理插件
if (method_exists($plugin, 'cleanup')) {
$plugin->cleanup();
}
return true;
}
/**
* 卸载插件
*/
public function uninstall(string $pluginCode): bool
{
// 先禁用插件
$this->disable($pluginCode);
// 删除数据库记录
Plugin::query()->where('code', $pluginCode)->delete();
return true;
}
/**
* 加载插件实例
*/
protected function loadPlugin(string $pluginCode)
{
$pluginFile = $this->pluginPath . '/' . $pluginCode . '/Plugin.php';
if (!File::exists($pluginFile)) {
return null;
}
require_once $pluginFile;
$className = "Plugin\\{$pluginCode}\\Plugin";
return new $className();
}
/**
* 验证配置文件
*/
protected function validateConfig(array $config): bool
{
return isset($config['code'])
&& isset($config['version'])
&& isset($config['description'])
&& isset($config['author']);
}
/**
* 检查依赖关系
*/
protected function checkDependencies(array $requires): bool
{
foreach ($requires as $package => $version) {
if ($package === 'xboard') {
// 检查xboard版本
// 实现版本比较逻辑
}
}
return true;
}
}

View File

@ -176,6 +176,7 @@ return [
App\Providers\RouteServiceProvider::class,
App\Providers\SettingServiceProvider::class,
App\Providers\OctaneSchedulerProvider::class,
App\Providers\PluginServiceProvider::class,
],

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('v2_plugins', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('code')->unique();
$table->string('version', 50);
$table->boolean('is_enabled')->default(false);
$table->json('config')->nullable();
$table->timestamp('installed_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('v2_plugins');
}
};

View File

@ -29,7 +29,7 @@ docker compose run -it --rm \
-e ENABLE_SQLITE=true \
-e ENABLE_REDIS=true \
-e ADMIN_ACCOUNT=admin@demo.com \
web php artisan xboard:install && \
web php artisan xboard:install
```
- 自定义配置安装(高级用户)
```bash

2
plugins/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

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

@ -151,6 +151,7 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
"systemConfig": "System Configuration",
"themeConfig": "Theme Configuration",
"noticeManagement": "Notice Management",
"pluginManagement": "Plugin Management",
"paymentConfig": "Payment Configuration",
"knowledgeManagement": "Knowledge Management",
"nodeManagement": "Node Management",
@ -163,6 +164,58 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
"userManagement": "User Management",
"ticketManagement": "Ticket Management"
},
"plugin": {
"title": "Plugin Management",
"description": "Manage and configure system plugins",
"search": {
"placeholder": "Search plugin name or description..."
},
"category": {
"placeholder": "Select Category",
"all": "All",
"other": "Other"
},
"tabs": {
"all": "All Plugins",
"installed": "Installed",
"available": "Available"
},
"status": {
"enabled": "Enabled",
"disabled": "Disabled"
},
"button": {
"install": "Install",
"config": "Configure",
"enable": "Enable",
"disable": "Disable"
},
"uninstall": {
"title": "Uninstall Plugin",
"description": "Are you sure you want to uninstall this plugin? Plugin data will be cleared after uninstallation.",
"button": "Uninstall"
},
"config": {
"title": "Configuration",
"description": "Modify plugin configuration",
"save": "Save",
"cancel": "Cancel"
},
"author": "Author",
"messages": {
"installSuccess": "Plugin installed successfully",
"installError": "Failed to install plugin",
"uninstallSuccess": "Plugin uninstalled successfully",
"uninstallError": "Failed to uninstall plugin",
"enableSuccess": "Plugin enabled successfully",
"enableError": "Failed to enable plugin",
"disableSuccess": "Plugin disabled successfully",
"disableError": "Failed to disable plugin",
"configLoadError": "Failed to load plugin configuration",
"configSaveSuccess": "Configuration saved successfully",
"configSaveError": "Failed to save configuration"
}
},
"settings": {
"title": "System Settings",
"description": "Manage core system configurations, including site, security, subscription, invite commission, nodes, email, and notifications",

View File

@ -151,6 +151,7 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
"systemConfig": "시스템 설정",
"themeConfig": "테마 설정",
"noticeManagement": "공지사항 관리",
"pluginManagement": "플러그인 관리",
"paymentConfig": "결제 설정",
"knowledgeManagement": "지식 관리",
"nodeManagement": "노드 관리",
@ -163,6 +164,58 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
"userManagement": "사용자 관리",
"ticketManagement": "티켓 관리"
},
"plugin": {
"title": "플러그인 관리",
"description": "시스템 플러그인 관리 및 설정",
"search": {
"placeholder": "플러그인 이름 또는 설명 검색..."
},
"category": {
"placeholder": "카테고리 선택",
"all": "전체",
"other": "기타"
},
"tabs": {
"all": "전체 플러그인",
"installed": "설치됨",
"available": "사용 가능"
},
"status": {
"enabled": "활성화됨",
"disabled": "비활성화됨"
},
"button": {
"install": "설치",
"config": "설정",
"enable": "활성화",
"disable": "비활성화"
},
"uninstall": {
"title": "플러그인 제거",
"description": "이 플러그인을 제거하시겠습니까? 제거 후 플러그인 데이터가 삭제됩니다.",
"button": "제거"
},
"config": {
"title": "설정",
"description": "플러그인 설정 수정",
"save": "저장",
"cancel": "취소"
},
"author": "작성자",
"messages": {
"installSuccess": "플러그인이 성공적으로 설치되었습니다",
"installError": "플러그인 설치에 실패했습니다",
"uninstallSuccess": "플러그인이 성공적으로 제거되었습니다",
"uninstallError": "플러그인 제거에 실패했습니다",
"enableSuccess": "플러그인이 성공적으로 활성화되었습니다",
"enableError": "플러그인 활성화에 실패했습니다",
"disableSuccess": "플러그인이 성공적으로 비활성화되었습니다",
"disableError": "플러그인 비활성화에 실패했습니다",
"configLoadError": "플러그인 설정을 불러오는데 실패했습니다",
"configSaveSuccess": "설정이 성공적으로 저장되었습니다",
"configSaveError": "설정 저장에 실패했습니다"
}
},
"settings": {
"title": "시스템 설정",
"description": "사이트, 보안, 구독, 초대 수수료, 노드, 이메일 및 알림을 포함한 핵심 시스템 구성을 관리합니다",

View File

@ -150,6 +150,7 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
"systemManagement": "系统管理",
"systemConfig": "系统配置",
"themeConfig": "主题配置",
"pluginManagement": "插件管理",
"noticeManagement": "公告管理",
"paymentConfig": "支付配置",
"knowledgeManagement": "知识库管理",
@ -163,6 +164,58 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
"userManagement": "用户管理",
"ticketManagement": "工单管理"
},
"plugin": {
"title": "插件管理",
"description": "管理和配置系统插件",
"search": {
"placeholder": "搜索插件名称或描述..."
},
"category": {
"placeholder": "选择分类",
"all": "全部",
"other": "其他"
},
"tabs": {
"all": "全部插件",
"installed": "已安装",
"available": "可用插件"
},
"status": {
"enabled": "已启用",
"disabled": "已禁用"
},
"button": {
"install": "安装",
"config": "配置",
"enable": "启用",
"disable": "禁用"
},
"uninstall": {
"title": "卸载插件",
"description": "确定要卸载该插件吗?卸载后插件数据将被清除。",
"button": "卸载"
},
"config": {
"title": "配置",
"description": "修改插件配置",
"save": "保存",
"cancel": "取消"
},
"author": "作者",
"messages": {
"installSuccess": "插件安装成功",
"installError": "插件安装失败",
"uninstallSuccess": "插件卸载成功",
"uninstallError": "插件卸载失败",
"enableSuccess": "插件启用成功",
"enableError": "插件启用失败",
"disableSuccess": "插件禁用成功",
"disableError": "插件禁用失败",
"configLoadError": "加载插件配置失败",
"configSaveSuccess": "配置保存成功",
"configSaveError": "配置保存失败"
}
},
"settings": {
"title": "系统设置",
"description": "管理系统核心配置,包括站点、安全、订阅、邀请佣金、节点、邮件和通知等设置",
@ -1941,6 +1994,7 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
"dashboard": "仪表盘",
"systemManagement": "系统管理",
"systemConfig": "系统配置",
"pluginManagement": "插件管理",
"themeConfig": "主题配置",
"noticeManagement": "公告管理",
"paymentConfig": "支付配置",