feat(inbounds): apply remark template to Export all inbound links

Export-all now renders links through the subscription engine via a new GET /panel/api/inbounds/allLinks endpoint, so the configured remark template (name-only display part) is applied per client -- matching the client info/QR pages. Previously it generated links client-side with a hardcoded inbound-email remark.

Host-aware: managed Host endpoints win over the plain link, so HOST and per-host variants render; duplicate client JSON entries are deduped by email and the list is scoped to the logged-in user.
This commit is contained in:
MHSanaei
2026-06-27 11:22:45 +02:00
parent 535b89a352
commit 439245d42b
8 changed files with 158 additions and 15 deletions
+37
View File
@@ -2715,6 +2715,43 @@
}
}
},
"/panel/api/inbounds/allLinks": {
"get": {
"tags": [
"Inbounds"
],
"summary": "Return every protocol URL (vless://, vmess://, trojan://, ss://, hysteria://, mtproto) across all inbounds and all of their clients. Links are rendered through the subscription engine, so the configured remark template (name-only display part) is applied per client — the same output the client info/QR pages use. Protocols without a URL form (socks, http, mixed, wireguard, dokodemo, tunnel) contribute nothing. Used by the panels \"Export all inbound links\" action.",
"operationId": "get_panel_api_inbounds_allLinks",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"obj": {}
}
},
"example": {
"success": true,
"obj": [
"vless://uuid@host:443?security=reality&...#Germany-alice",
"vmess://eyJ2IjoyLC..."
]
}
}
}
}
}
}
},
"/panel/api/inbounds/get/{id}": {
"get": {
"tags": [
+8
View File
@@ -126,6 +126,14 @@ export const sections: readonly Section[] = [
responseSchema: 'InboundOption',
responseSchemaArray: true,
},
{
method: 'GET',
path: '/panel/api/inbounds/allLinks',
summary:
'Return every protocol URL (vless://, vmess://, trojan://, ss://, hysteria://, mtproto) across all inbounds and all of their clients. Links are rendered through the subscription engine, so the configured remark template (name-only display part) is applied per client — the same output the client info/QR pages use. Protocols without a URL form (socks, http, mixed, wireguard, dokodemo, tunnel) contribute nothing. Used by the panels "Export all inbound links" action.',
response:
'{\n "success": true,\n "obj": [\n "vless://uuid@host:443?security=reality&...#Germany-alice",\n "vmess://eyJ2IjoyLC..."\n ]\n}',
},
{
method: 'GET',
path: '/panel/api/inbounds/get/:id',
+4 -15
View File
@@ -292,21 +292,10 @@ export default function InboundsPage() {
}, [subSettings, openText, t]);
const exportAllLinks = useCallback(async () => {
const hydrated = await Promise.all(
dbInbounds.map((ib) => hydrateInbound(ib.id).then((r) => r ?? ib)),
);
const out: string[] = [];
for (const ib of hydrated) {
const projected = checkFallback(ib);
out.push(genInboundLinks({
inbound: inboundFromDb(projected),
remark: projected.remark,
hostOverride: hostOverrideFor(ib),
fallbackHostname: preferPublicHost(window.location.hostname, subSettings.publicHost),
}));
}
openText({ title: t('pages.inbounds.exportAllLinksTitle'), content: out.join('\r\n'), fileName: t('pages.inbounds.exportAllLinksFileName') });
}, [dbInbounds, hydrateInbound, checkFallback, hostOverrideFor, subSettings.publicHost, openText, t]);
const msg = await HttpUtil.get('/panel/api/inbounds/allLinks');
const links = msg?.success && Array.isArray(msg.obj) ? (msg.obj as string[]) : [];
openText({ title: t('pages.inbounds.exportAllLinksTitle'), content: links.join('\r\n'), fileName: t('pages.inbounds.exportAllLinksFileName') });
}, [openText, t]);
const exportAllSubs = useCallback(async () => {
const hydrated = await Promise.all(