|
@@ -1,4 +1,4 @@
|
|
|
-export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'WS';
|
|
|
|
|
|
|
+export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';
|
|
|
export type ParamLocation =
|
|
export type ParamLocation =
|
|
|
| 'path'
|
|
| 'path'
|
|
|
| 'query'
|
|
| 'query'
|
|
@@ -28,6 +28,7 @@ export interface EndpointParam {
|
|
|
defaultValue?: string | number | boolean;
|
|
defaultValue?: string | number | boolean;
|
|
|
minLength?: number;
|
|
minLength?: number;
|
|
|
pattern?: string;
|
|
pattern?: string;
|
|
|
|
|
+ enum?: readonly (string | number | boolean)[];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export interface Endpoint {
|
|
export interface Endpoint {
|
|
@@ -45,6 +46,9 @@ export interface Endpoint {
|
|
|
bodyRequiredOneOf?: string[];
|
|
bodyRequiredOneOf?: string[];
|
|
|
responseSchema?: string;
|
|
responseSchema?: string;
|
|
|
responseSchemaArray?: boolean;
|
|
responseSchemaArray?: boolean;
|
|
|
|
|
+ responseObjectSchema?: Record<string, unknown>;
|
|
|
|
|
+ responses?: Record<string, Record<string, unknown>>;
|
|
|
|
|
+ security?: readonly Record<string, readonly string[]>[];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export interface SubscriptionHeader {
|
|
export interface SubscriptionHeader {
|
|
@@ -172,6 +176,12 @@ const subBalancerBodyParams: EndpointParam[] = [
|
|
|
},
|
|
},
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
|
|
+const subscriptionHeadResponses = {
|
|
|
|
|
+ '200': { description: 'Subscription is available. Headers match GET; no response body.' },
|
|
|
|
|
+ '404': { description: 'No enabled client matches the subscription ID.' },
|
|
|
|
|
+ '500': { description: 'Subscription generation failed.' },
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
export const sections: readonly Section[] = [
|
|
export const sections: readonly Section[] = [
|
|
|
{
|
|
{
|
|
|
id: 'authentication',
|
|
id: 'authentication',
|
|
@@ -556,7 +566,7 @@ export const sections: readonly Section[] = [
|
|
|
method: 'GET',
|
|
method: 'GET',
|
|
|
path: '/panel/api/server/getNewUUID',
|
|
path: '/panel/api/server/getNewUUID',
|
|
|
summary: 'Generate a fresh UUID v4. Convenience helper for client IDs.',
|
|
summary: 'Generate a fresh UUID v4. Convenience helper for client IDs.',
|
|
|
- response: '{\n "success": true,\n "obj": "550e8400-e29b-41d4-a716-446655440000"\n}',
|
|
|
|
|
|
|
+ responseSchema: 'NewUUIDResponse',
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
method: 'GET',
|
|
method: 'GET',
|
|
@@ -584,18 +594,14 @@ export const sections: readonly Section[] = [
|
|
|
{
|
|
{
|
|
|
method: 'GET',
|
|
method: 'GET',
|
|
|
path: '/panel/api/server/getNewmldsa65',
|
|
path: '/panel/api/server/getNewmldsa65',
|
|
|
- summary:
|
|
|
|
|
- 'Generate a new ML-DSA-65 keypair (post-quantum signature). Returns {privateKey, publicKey, seed}.',
|
|
|
|
|
- response:
|
|
|
|
|
- '{\n "success": true,\n "obj": {\n "privateKey": "mdsa65priv...",\n "publicKey": "mdsa65pub...",\n "seed": "random-seed..."\n }\n}',
|
|
|
|
|
|
|
+ summary: 'Generate a new ML-DSA-65 keypair. Returns {seed, verify}.',
|
|
|
|
|
+ responseSchema: 'MLDSA65Response',
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
method: 'GET',
|
|
method: 'GET',
|
|
|
path: '/panel/api/server/getNewmlkem768',
|
|
path: '/panel/api/server/getNewmlkem768',
|
|
|
- summary:
|
|
|
|
|
- 'Generate a new ML-KEM-768 keypair (post-quantum KEM). Returns {clientKey, serverKey}.',
|
|
|
|
|
- response:
|
|
|
|
|
- '{\n "success": true,\n "obj": {\n "clientKey": "mlkem768-client...",\n "serverKey": "mlkem768-server..."\n }\n}',
|
|
|
|
|
|
|
+ summary: 'Generate a new ML-KEM-768 keypair. Returns {seed, client}.',
|
|
|
|
|
+ responseSchema: 'MLKEM768Response',
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
method: 'GET',
|
|
method: 'GET',
|
|
@@ -703,8 +709,9 @@ export const sections: readonly Section[] = [
|
|
|
},
|
|
},
|
|
|
],
|
|
],
|
|
|
body: 'level=info&syslog=false',
|
|
body: 'level=info&syslog=false',
|
|
|
|
|
+ responseObjectSchema: { type: 'array', items: { type: 'string' } },
|
|
|
response:
|
|
response:
|
|
|
- '{\n "success": true,\n "obj": "2025/01/01 12:00:00 [INFO] Server started\\n2025/01/01 12:00:01 [INFO] Xray is running"\n}',
|
|
|
|
|
|
|
+ '{\n "success": true,\n "obj": [\n "2025/01/01 12:00:00 [INFO] Server started",\n "2025/01/01 12:00:01 [INFO] Xray is running"\n ]\n}',
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
@@ -742,8 +749,8 @@ export const sections: readonly Section[] = [
|
|
|
},
|
|
},
|
|
|
],
|
|
],
|
|
|
body: 'filter=error&showDirect=false&showBlocked=true&showProxy=true',
|
|
body: 'filter=error&showDirect=false&showBlocked=true&showProxy=true',
|
|
|
- response:
|
|
|
|
|
- '{\n "success": true,\n "obj": "2025/01/01 12:00:00 rejected vless proxy example.com reason: no valid user\\n2025/01/01 12:00:01 direct freedom ok"\n}',
|
|
|
|
|
|
|
+ responseSchema: 'LogEntry',
|
|
|
|
|
+ responseSchemaArray: true,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
@@ -966,41 +973,132 @@ export const sections: readonly Section[] = [
|
|
|
in: 'query',
|
|
in: 'query',
|
|
|
type: 'number',
|
|
type: 'number',
|
|
|
desc: '1-indexed page number. Defaults to 1.',
|
|
desc: '1-indexed page number. Defaults to 1.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ defaultValue: 1,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
name: 'pageSize',
|
|
name: 'pageSize',
|
|
|
in: 'query',
|
|
in: 'query',
|
|
|
type: 'number',
|
|
type: 'number',
|
|
|
desc: 'Rows per page. Defaults to 25, capped at 200.',
|
|
desc: 'Rows per page. Defaults to 25, capped at 200.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ defaultValue: 25,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
name: 'search',
|
|
name: 'search',
|
|
|
in: 'query',
|
|
in: 'query',
|
|
|
type: 'string',
|
|
type: 'string',
|
|
|
- desc: 'Case-insensitive substring match on email / subId / comment.',
|
|
|
|
|
|
|
+ desc: 'Case-insensitive substring match on email, subId, comment, UUID, password, auth or Telegram ID.',
|
|
|
|
|
+ optional: true,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
name: 'filter',
|
|
name: 'filter',
|
|
|
in: 'query',
|
|
in: 'query',
|
|
|
type: 'string',
|
|
type: 'string',
|
|
|
- desc: 'Status bucket: online | active | deactive | depleted | expiring.',
|
|
|
|
|
|
|
+ desc: 'CSV status buckets: online, active, deactive, depleted or expiring. Values are ORed.',
|
|
|
|
|
+ optional: true,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
name: 'protocol',
|
|
name: 'protocol',
|
|
|
in: 'query',
|
|
in: 'query',
|
|
|
type: 'string',
|
|
type: 'string',
|
|
|
- desc: 'Match clients attached to at least one inbound of this protocol (vless, vmess, trojan, shadowsocks, ...).',
|
|
|
|
|
|
|
+ desc: 'CSV inbound protocols: vmess, vless, trojan, shadowsocks, wireguard, hysteria, http, mixed, tunnel, tun, mtproto or amneziawg. Values are ORed.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'inbound',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ desc: 'CSV positive inbound IDs. Values are ORed; invalid or non-positive IDs are ignored.',
|
|
|
|
|
+ optional: true,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
name: 'sort',
|
|
name: 'sort',
|
|
|
in: 'query',
|
|
in: 'query',
|
|
|
type: 'string',
|
|
type: 'string',
|
|
|
- desc: 'Sort key: enable | email | inboundIds | traffic | remaining | expiryTime.',
|
|
|
|
|
|
|
+ desc: 'Sort key. An omitted or unknown value falls back to client ID ascending.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ enum: [
|
|
|
|
|
+ 'enable',
|
|
|
|
|
+ 'email',
|
|
|
|
|
+ 'inboundIds',
|
|
|
|
|
+ 'traffic',
|
|
|
|
|
+ 'remaining',
|
|
|
|
|
+ 'expiryTime',
|
|
|
|
|
+ 'createdAt',
|
|
|
|
|
+ 'updatedAt',
|
|
|
|
|
+ 'lastOnline',
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'order',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ desc: 'Sort direction. Only descend selects descending order; otherwise ascending.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ enum: ['ascend', 'descend'],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'expiryFrom',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'number',
|
|
|
|
|
+ desc: 'Inclusive minimum expiry time in Unix milliseconds. Zero or negative means unset.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'expiryTo',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'number',
|
|
|
|
|
+ desc: 'Inclusive maximum expiry time in Unix milliseconds. Zero or negative means unbounded.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'usageFrom',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'number',
|
|
|
|
|
+ desc: 'Inclusive minimum combined upload and download usage in bytes. Zero means unset.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'usageTo',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'number',
|
|
|
|
|
+ desc: 'Inclusive maximum combined upload and download usage in bytes. Zero means unbounded.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'autoRenew',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ desc: 'on selects clients with an interval or calendar-day reset; off selects clients without either.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ enum: ['on', 'off'],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'hasTgId',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ desc: 'yes selects clients with a non-zero Telegram ID; no selects clients without one.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ enum: ['yes', 'no'],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'hasComment',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ desc: 'yes selects clients with a non-blank comment; no selects clients without one.',
|
|
|
|
|
+ optional: true,
|
|
|
|
|
+ enum: ['yes', 'no'],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: 'group',
|
|
|
|
|
+ in: 'query',
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ desc: 'CSV group names, matched case-insensitively after trimming. Values are ORed.',
|
|
|
|
|
+ optional: true,
|
|
|
},
|
|
},
|
|
|
- { name: 'order', in: 'query', type: 'string', desc: 'ascend or descend.' },
|
|
|
|
|
],
|
|
],
|
|
|
- response:
|
|
|
|
|
- '{\n "success": true,\n "obj": {\n "items": [\n {\n "email": "[email protected]",\n "subId": "abcd1234",\n "enable": true,\n "totalGB": 53687091200,\n "expiryTime": 1735689600000,\n "limitIp": 0,\n "limitHwid": 0,\n "reset": 0,\n "inboundIds": [3, 5],\n "traffic": { "up": 1024, "down": 4096, "enable": true },\n "createdAt": 1735000000000,\n "updatedAt": 1735100000000\n }\n ],\n "total": 2000,\n "filtered": 47,\n "page": 1,\n "pageSize": 25,\n "summary": {\n "total": 2000,\n "active": 1850,\n "onlineCount": 1,\n "depletedCount": 0,\n "expiringCount": 0,\n "deactiveCount": 150,\n "online": ["[email protected]"],\n "depleted": [],\n "expiring": [],\n "deactive": ["[email protected]"]\n }\n }\n}',
|
|
|
|
|
|
|
+ responseSchema: 'ClientPageResponse',
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
method: 'GET',
|
|
method: 'GET',
|
|
@@ -2503,6 +2601,14 @@ export const sections: readonly Section[] = [
|
|
|
},
|
|
},
|
|
|
],
|
|
],
|
|
|
},
|
|
},
|
|
|
|
|
+ {
|
|
|
|
|
+ method: 'HEAD',
|
|
|
|
|
+ path: '/{subPath}:subid',
|
|
|
|
|
+ summary:
|
|
|
|
|
+ 'Return the same status and subscription metadata headers as GET without a response body.',
|
|
|
|
|
+ params: [{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }],
|
|
|
|
|
+ responses: subscriptionHeadResponses,
|
|
|
|
|
+ },
|
|
|
{
|
|
{
|
|
|
method: 'GET',
|
|
method: 'GET',
|
|
|
path: '/{jsonPath}:subid',
|
|
path: '/{jsonPath}:subid',
|
|
@@ -2510,6 +2616,14 @@ export const sections: readonly Section[] = [
|
|
|
'Return subscription as a JSON array of proxy configs (one per enabled client). Only when JSON subscription is enabled in settings. The path prefix is configured by subJsonPath.',
|
|
'Return subscription as a JSON array of proxy configs (one per enabled client). Only when JSON subscription is enabled in settings. The path prefix is configured by subJsonPath.',
|
|
|
params: [{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }],
|
|
params: [{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }],
|
|
|
},
|
|
},
|
|
|
|
|
+ {
|
|
|
|
|
+ method: 'HEAD',
|
|
|
|
|
+ path: '/{jsonPath}:subid',
|
|
|
|
|
+ summary:
|
|
|
|
|
+ 'Return the JSON subscription status and metadata headers without a body. Registered only when JSON subscriptions are enabled.',
|
|
|
|
|
+ params: [{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }],
|
|
|
|
|
+ responses: subscriptionHeadResponses,
|
|
|
|
|
+ },
|
|
|
{
|
|
{
|
|
|
method: 'GET',
|
|
method: 'GET',
|
|
|
path: '/{clashPath}:subid',
|
|
path: '/{clashPath}:subid',
|
|
@@ -2517,6 +2631,14 @@ export const sections: readonly Section[] = [
|
|
|
'Return subscription as a Clash/Mihomo-compatible YAML config, including configured global Clash routing rules. Only when Clash subscription is enabled in settings. The path prefix is configured by subClashPath.',
|
|
'Return subscription as a Clash/Mihomo-compatible YAML config, including configured global Clash routing rules. Only when Clash subscription is enabled in settings. The path prefix is configured by subClashPath.',
|
|
|
params: [{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }],
|
|
params: [{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }],
|
|
|
},
|
|
},
|
|
|
|
|
+ {
|
|
|
|
|
+ method: 'HEAD',
|
|
|
|
|
+ path: '/{clashPath}:subid',
|
|
|
|
|
+ summary:
|
|
|
|
|
+ 'Return the Clash subscription status and metadata headers without a body. Registered only when Clash subscriptions are enabled.',
|
|
|
|
|
+ params: [{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }],
|
|
|
|
|
+ responses: subscriptionHeadResponses,
|
|
|
|
|
+ },
|
|
|
],
|
|
],
|
|
|
},
|
|
},
|
|
|
|
|
|
|
@@ -2531,36 +2653,11 @@ export const sections: readonly Section[] = [
|
|
|
path: '/ws',
|
|
path: '/ws',
|
|
|
summary:
|
|
summary:
|
|
|
'Upgrade an HTTP connection to a WebSocket. Requires an authenticated session cookie (Bearer token auth is not supported here). Returns 101 Switching Protocols on success. The server then pushes JSON messages described below.',
|
|
'Upgrade an HTTP connection to a WebSocket. Requires an authenticated session cookie (Bearer token auth is not supported here). Returns 101 Switching Protocols on success. The server then pushes JSON messages described below.',
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- method: 'WS',
|
|
|
|
|
- path: '→ type: status',
|
|
|
|
|
- summary:
|
|
|
|
|
- 'Server health snapshot pushed every 2 seconds. Contains CPU, memory, swap, disk, network IO, load, and Xray state — same shape as <code>GET /panel/api/server/status</code>.',
|
|
|
|
|
- response:
|
|
|
|
|
- '{\n "type": "status",\n "data": { "cpu": 12.5, "mem": { "current": 2147483648, "total": 8589934592 }, "xray": { "state": "running" } }\n}',
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- method: 'WS',
|
|
|
|
|
- path: '→ type: xrayState',
|
|
|
|
|
- summary:
|
|
|
|
|
- 'Xray process state change. Fired when Xray starts, stops, or encounters an error.',
|
|
|
|
|
- response: '{\n "type": "xrayState",\n "data": "running"\n}',
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- method: 'WS',
|
|
|
|
|
- path: '→ type: notification',
|
|
|
|
|
- summary:
|
|
|
|
|
- 'In-panel toast notification. Fired on Xray stop/restart, DB import, panel restart, etc.',
|
|
|
|
|
- response:
|
|
|
|
|
- '{\n "type": "notification",\n "title": "Xray service restarted",\n "body": "Xray has been restarted successfully",\n "severity": "success"\n}',
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- method: 'WS',
|
|
|
|
|
- path: '→ type: invalidate',
|
|
|
|
|
- summary:
|
|
|
|
|
- 'Instructs the UI to re-fetch a resource. Fired when another admin session modifies data (e.g. toggling inbound enable).',
|
|
|
|
|
- response: '{\n "type": "invalidate",\n "resource": "inbounds"\n}',
|
|
|
|
|
|
|
+ responses: {
|
|
|
|
|
+ '101': { description: 'Switching Protocols. WebSocket messages use WebSocketEnvelope.' },
|
|
|
|
|
+ '401': { description: 'No authenticated panel session cookie.' },
|
|
|
|
|
+ },
|
|
|
|
|
+ security: [{ cookieAuth: [] }],
|
|
|
},
|
|
},
|
|
|
],
|
|
],
|
|
|
},
|
|
},
|