feat: add xhttp subscriptions, network monitoring, chart legend toggle and ticket sender labels

This commit is contained in:
xboard
2026-04-18 02:02:06 +08:00
parent d9833fab47
commit 1708b6564b
11 changed files with 221 additions and 26 deletions
@@ -168,12 +168,19 @@ class MachineController extends Controller
$params = $request->validate([ $params = $request->validate([
'machine_id' => 'required|integer|exists:v2_server_machine,id', 'machine_id' => 'required|integer|exists:v2_server_machine,id',
'limit' => 'nullable|integer|min:10|max:1440', 'limit' => 'nullable|integer|min:10|max:1440',
'range_hours' => 'nullable|integer|min:1|max:24',
]); ]);
$query = ServerMachineLoadHistory::query()
->where('machine_id', $params['machine_id']);
if (!empty($params['range_hours'])) {
$query->where('recorded_at', '>=', now()->subHours((int) $params['range_hours'])->timestamp);
}
$limit = (int) ($params['limit'] ?? 60); $limit = (int) ($params['limit'] ?? 60);
$history = ServerMachineLoadHistory::query() $history = $query
->where('machine_id', $params['machine_id'])
->orderByDesc('recorded_at') ->orderByDesc('recorded_at')
->limit($limit) ->limit($limit)
->get([ ->get([
@@ -182,6 +189,8 @@ class MachineController extends Controller
'mem_used', 'mem_used',
'disk_total', 'disk_total',
'disk_used', 'disk_used',
'net_in_speed',
'net_out_speed',
'recorded_at', 'recorded_at',
]) ])
->reverse() ->reverse()
@@ -225,6 +225,7 @@ class ManageController extends Controller
'ids' => 'required|array', 'ids' => 'required|array',
'ids.*' => 'integer', 'ids.*' => 'integer',
'show' => 'nullable|integer|in:0,1', 'show' => 'nullable|integer|in:0,1',
'enabled' => 'nullable|boolean',
]); ]);
$ids = $params['ids']; $ids = $params['ids'];
@@ -236,6 +237,9 @@ class ManageController extends Controller
if (array_key_exists('show', $params) && $params['show'] !== null) { if (array_key_exists('show', $params) && $params['show'] !== null) {
$update['show'] = (int) $params['show']; $update['show'] = (int) $params['show'];
} }
if (array_key_exists('enabled', $params) && $params['enabled'] !== null) {
$update['enabled'] = (bool) $params['enabled'];
}
if (empty($update)) { if (empty($update)) {
return $this->fail([400, '没有可更新的字段']); return $this->fail([400, '没有可更新的字段']);
@@ -50,32 +50,46 @@ class MachineController extends Controller
'swap.used' => 'nullable|integer|min:0', 'swap.used' => 'nullable|integer|min:0',
'disk.total' => 'nullable|integer|min:0', 'disk.total' => 'nullable|integer|min:0',
'disk.used' => 'nullable|integer|min:0', 'disk.used' => 'nullable|integer|min:0',
'net.in_speed' => 'nullable|numeric|min:0',
'net.out_speed' => 'nullable|numeric|min:0',
]); ]);
$machine = $this->authenticateMachine($request); $machine = $this->authenticateMachine($request);
$recordedAt = now()->timestamp; $recordedAt = now()->timestamp;
$machine->forceFill([ $loadStatus = [
'load_status' => [ 'cpu' => (float) $request->input('cpu'),
'cpu' => (float) $request->input('cpu'), 'mem' => [
'mem' => [ 'total' => (int) $request->input('mem.total'),
'total' => (int) $request->input('mem.total'), 'used' => (int) $request->input('mem.used'),
'used' => (int) $request->input('mem.used'),
],
'swap' => [
'total' => (int) $request->input('swap.total', 0),
'used' => (int) $request->input('swap.used', 0),
],
'disk' => [
'total' => (int) $request->input('disk.total', 0),
'used' => (int) $request->input('disk.used', 0),
],
'updated_at' => $recordedAt,
], ],
'swap' => [
'total' => (int) $request->input('swap.total', 0),
'used' => (int) $request->input('swap.used', 0),
],
'disk' => [
'total' => (int) $request->input('disk.total', 0),
'used' => (int) $request->input('disk.used', 0),
],
'updated_at' => $recordedAt,
];
$netInSpeed = $request->input('net.in_speed');
$netOutSpeed = $request->input('net.out_speed');
if ($netInSpeed !== null && $netOutSpeed !== null) {
$loadStatus['net'] = [
'in_speed' => (float) $netInSpeed,
'out_speed' => (float) $netOutSpeed,
];
}
$machine->forceFill([
'load_status' => $loadStatus,
'last_seen_at' => $recordedAt, 'last_seen_at' => $recordedAt,
])->save(); ])->save();
ServerMachineLoadHistory::create([ $historyData = [
'machine_id' => $machine->id, 'machine_id' => $machine->id,
'cpu' => (float) $request->input('cpu'), 'cpu' => (float) $request->input('cpu'),
'mem_total' => (int) $request->input('mem.total'), 'mem_total' => (int) $request->input('mem.total'),
@@ -83,7 +97,14 @@ class MachineController extends Controller
'disk_total' => (int) $request->input('disk.total', 0), 'disk_total' => (int) $request->input('disk.total', 0),
'disk_used' => (int) $request->input('disk.used', 0), 'disk_used' => (int) $request->input('disk.used', 0),
'recorded_at' => $recordedAt, 'recorded_at' => $recordedAt,
]); ];
if ($netInSpeed !== null && $netOutSpeed !== null) {
$historyData['net_in_speed'] = (float) $netInSpeed;
$historyData['net_out_speed'] = (float) $netOutSpeed;
}
ServerMachineLoadHistory::create($historyData);
// Time-based cleanup: keep 24h of data, runs on ~5% of requests // Time-based cleanup: keep 24h of data, runs on ~5% of requests
if (random_int(1, 20) === 1) { if (random_int(1, 20) === 1) {
+2
View File
@@ -17,6 +17,8 @@ class ServerMachineLoadHistory extends Model
'mem_used' => 'integer', 'mem_used' => 'integer',
'disk_total' => 'integer', 'disk_total' => 'integer',
'disk_used' => 'integer', 'disk_used' => 'integer',
'net_in_speed' => 'float',
'net_out_speed' => 'float',
'recorded_at' => 'integer', 'recorded_at' => 'integer',
'created_at' => 'timestamp', 'created_at' => 'timestamp',
'updated_at' => 'timestamp', 'updated_at' => 'timestamp',
+33
View File
@@ -36,6 +36,27 @@ class ClashMeta extends AbstractProtocol
'http' => '0.0.0', 'http' => '0.0.0',
'h2' => '0.0.0', 'h2' => '0.0.0',
'httpupgrade' => '0.0.0', 'httpupgrade' => '0.0.0',
'xhttp' => '0.0.0',
],
'strict' => true,
],
'*.vmess.protocol_settings.network' => [
'whitelist' => [
'tcp' => '0.0.0',
'ws' => '0.0.0',
'grpc' => '0.0.0',
'http' => '0.0.0',
'h2' => '0.0.0',
'httpupgrade' => '0.0.0',
],
'strict' => true,
],
'*.trojan.protocol_settings.network' => [
'whitelist' => [
'tcp' => '0.0.0',
'ws' => '0.0.0',
'grpc' => '0.0.0',
'httpupgrade' => '0.0.0',
], ],
'strict' => true, 'strict' => true,
], ],
@@ -468,6 +489,18 @@ class ClashMeta extends AbstractProtocol
if ($host = data_get($protocol_settings, 'network_settings.host')) if ($host = data_get($protocol_settings, 'network_settings.host'))
$array['ws-opts']['headers'] = ['Host' => $host]; $array['ws-opts']['headers'] = ['Host' => $host];
break; break;
case 'xhttp':
$array['network'] = 'xhttp';
$xhttpOpts = [];
if ($path = data_get($protocol_settings, 'network_settings.path'))
$xhttpOpts['path'] = $path;
if ($host = data_get($protocol_settings, 'network_settings.host'))
$xhttpOpts['host'] = $host;
if ($mode = data_get($protocol_settings, 'network_settings.mode'))
$xhttpOpts['mode'] = $mode;
if (!empty($xhttpOpts))
$array['xhttp-opts'] = $xhttpOpts;
break;
default: default:
break; break;
} }
+27 -3
View File
@@ -135,6 +135,17 @@ class General extends AbstractProtocol
$config['path'] = $path; $config['path'] = $path;
$config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']); $config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
break; break;
case 'xhttp':
$config['net'] = 'xhttp';
$config['type'] = 'xhttp';
if ($path = data_get($protocol_settings, 'network_settings.path'))
$config['path'] = $path;
$config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
if ($mode = data_get($protocol_settings, 'network_settings.mode', 'auto'))
$config['mode'] = $mode;
if ($extra = data_get($protocol_settings, 'network_settings.extra'))
$config['extra'] = is_array($extra) && !empty($extra) ? json_encode($extra) : null;
break;
default: default:
break; break;
} }
@@ -216,10 +227,13 @@ class General extends AbstractProtocol
$config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']); $config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
break; break;
case 'xhttp': case 'xhttp':
$config['path'] = data_get($protocol_settings, 'network_settings.path'); if ($path = data_get($protocol_settings, 'network_settings.path'))
$config['path'] = $path;
$config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']); $config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
$config['mode'] = data_get($protocol_settings, 'network_settings.mode', 'auto'); if ($mode = data_get($protocol_settings, 'network_settings.mode', 'auto'))
$config['extra'] = json_encode(data_get($protocol_settings, 'network_settings.extra')); $config['mode'] = $mode;
if ($extra = data_get($protocol_settings, 'network_settings.extra'))
$config['extra'] = is_array($extra) && !empty($extra) ? json_encode($extra) : null;
break; break;
} }
@@ -286,6 +300,16 @@ class General extends AbstractProtocol
$array['path'] = $path; $array['path'] = $path;
$array['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']); $array['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
break; break;
case 'xhttp':
$array['type'] = 'xhttp';
if ($path = data_get($protocol_settings, 'network_settings.path'))
$array['path'] = $path;
$array['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
if ($mode = data_get($protocol_settings, 'network_settings.mode', 'auto'))
$array['mode'] = $mode;
if ($extra = data_get($protocol_settings, 'network_settings.extra'))
$array['extra'] = is_array($extra) && !empty($extra) ? json_encode($extra) : null;
break;
default: default:
break; break;
} }
+32
View File
@@ -225,6 +225,20 @@ class Loon extends AbstractProtocol
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName')) if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
$config[] = "grpc-service-name={$serviceName}"; $config[] = "grpc-service-name={$serviceName}";
break; break;
case 'h2':
$config[] = 'transport=h2';
if ($path = data_get($protocol_settings, 'network_settings.path'))
$config[] = "path={$path}";
if ($host = data_get($protocol_settings, 'network_settings.host'))
$config[] = "host=" . (is_array($host) ? $host[0] : $host);
break;
case 'httpupgrade':
$config[] = 'transport=httpupgrade';
if ($path = data_get($protocol_settings, 'network_settings.path'))
$config[] = "path={$path}";
if ($host = data_get($protocol_settings, 'network_settings.host', $server['host']))
$config[] = "host={$host}";
break;
} }
$config = array_filter($config); $config = array_filter($config);
@@ -295,6 +309,24 @@ class Loon extends AbstractProtocol
$config[] = "grpc-service-name={$serviceName}"; $config[] = "grpc-service-name={$serviceName}";
} }
break; break;
case 'h2':
$config[] = "transport=h2";
if ($path = data_get($protocol_settings, 'network_settings.path')) {
$config[] = "path={$path}";
}
if ($host = data_get($protocol_settings, 'network_settings.host')) {
$config[] = "host=" . (is_array($host) ? $host[0] : $host);
}
break;
case 'httpupgrade':
$config[] = "transport=httpupgrade";
if ($path = data_get($protocol_settings, 'network_settings.path')) {
$config[] = "path={$path}";
}
if ($host = data_get($protocol_settings, 'network_settings.host', $server['host'])) {
$config[] = "host={$host}";
}
break;
default: default:
$config[] = "transport=tcp"; $config[] = "transport=tcp";
break; break;
+41 -2
View File
@@ -23,6 +23,10 @@ class Shadowrocket extends AbstractProtocol
protected $protocolRequirements = [ protected $protocolRequirements = [
'shadowrocket.hysteria.protocol_settings.version' => [2 => '1993'], 'shadowrocket.hysteria.protocol_settings.version' => [2 => '1993'],
'shadowrocket.anytls.base_version' => '2592', 'shadowrocket.anytls.base_version' => '2592',
'shadowrocket.trojan.protocol_settings.network' => [
'whitelist' => ['tcp', 'ws', 'grpc', 'h2', 'httpupgrade'],
'strict' => true,
],
]; ];
public function handle() public function handle()
@@ -147,6 +151,18 @@ class Shadowrocket extends AbstractProtocol
$config['peer'] = $host [0] ?? $server['host']; $config['peer'] = $host [0] ?? $server['host'];
} }
break; break;
case 'xhttp':
$config['obfs'] = "xhttp";
if ($path = data_get($protocol_settings, 'network_settings.path')) {
$config['path'] = $path;
}
if ($host = data_get($protocol_settings, 'network_settings.host', $server['host'])) {
$config['obfsParam'] = $host;
}
if ($mode = data_get($protocol_settings, 'network_settings.mode', 'auto')) {
$config['mode'] = $mode;
}
break;
} }
$query = http_build_query($config, '', '&', PHP_QUERY_RFC3986); $query = http_build_query($config, '', '&', PHP_QUERY_RFC3986);
$uri = "vmess://{$userinfo}?{$query}"; $uri = "vmess://{$userinfo}?{$query}";
@@ -282,8 +298,8 @@ class Shadowrocket extends AbstractProtocol
$params['sni'] = data_get($protocol_settings, 'reality_settings.server_name'); $params['sni'] = data_get($protocol_settings, 'reality_settings.server_name');
break; break;
default: // Standard TLS default: // Standard TLS
$params['allowInsecure'] = data_get($protocol_settings, 'allow_insecure'); $params['allowInsecure'] = (int) data_get($protocol_settings, 'tls_settings.allow_insecure');
if ($serverName = data_get($protocol_settings, 'server_name')) { if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
$params['peer'] = $serverName; $params['peer'] = $serverName;
} }
break; break;
@@ -299,6 +315,29 @@ class Shadowrocket extends AbstractProtocol
$path = data_get($protocol_settings, 'network_settings.path'); $path = data_get($protocol_settings, 'network_settings.path');
$params['plugin'] = "obfs-local;obfs=websocket;obfs-host={$host};obfs-uri={$path}"; $params['plugin'] = "obfs-local;obfs=websocket;obfs-host={$host};obfs-uri={$path}";
break; break;
case 'h2':
$params['obfs'] = 'h2';
if ($path = data_get($protocol_settings, 'network_settings.path'))
$params['path'] = $path;
if ($host = data_get($protocol_settings, 'network_settings.host', $server['host']))
$params['obfsParam'] = is_array($host) ? $host[0] : $host;
break;
case 'httpupgrade':
$params['obfs'] = 'httpupgrade';
if ($path = data_get($protocol_settings, 'network_settings.path'))
$params['path'] = $path;
if ($host = data_get($protocol_settings, 'network_settings.host', $server['host']))
$params['obfsParam'] = $host;
break;
case 'xhttp':
$params['obfs'] = 'xhttp';
if ($path = data_get($protocol_settings, 'network_settings.path'))
$params['path'] = $path;
if ($host = data_get($protocol_settings, 'network_settings.host', $server['host']))
$params['obfsParam'] = $host;
if ($mode = data_get($protocol_settings, 'network_settings.mode', 'auto'))
$params['mode'] = $mode;
break;
} }
$query = http_build_query($params); $query = http_build_query($params);
$addr = Helper::wrapIPv6($server['host']); $addr = Helper::wrapIPv6($server['host']);
+9
View File
@@ -40,16 +40,25 @@ class SingBox extends AbstractProtocol
], ],
'protocol_settings.tls_settings.ech.enabled' => [ 'protocol_settings.tls_settings.ech.enabled' => [
1 => '1.5.0' 1 => '1.5.0'
],
'protocol_settings.network' => [
'xhttp' => '9999.0.0'
] ]
], ],
'vmess' => [ 'vmess' => [
'protocol_settings.tls_settings.ech.enabled' => [ 'protocol_settings.tls_settings.ech.enabled' => [
1 => '1.5.0' 1 => '1.5.0'
],
'protocol_settings.network' => [
'xhttp' => '9999.0.0'
] ]
], ],
'trojan' => [ 'trojan' => [
'protocol_settings.tls_settings.ech.enabled' => [ 'protocol_settings.tls_settings.ech.enabled' => [
1 => '1.5.0' 1 => '1.5.0'
],
'protocol_settings.network' => [
'xhttp' => '9999.0.0'
] ]
], ],
'hysteria' => [ 'hysteria' => [
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('v2_server_machine_load_history', function (Blueprint $table) {
$table->double('net_in_speed')->nullable()->after('disk_used');
$table->double('net_out_speed')->nullable()->after('net_in_speed');
});
}
public function down(): void
{
Schema::table('v2_server_machine_load_history', function (Blueprint $table) {
$table->dropColumn(['net_in_speed', 'net_out_speed']);
});
}
};