feat(sub): expose live online status and add ?format=info endpoint

Custom subscription templates only received the lastOnline timestamp, so
template authors had to fake an online indicator by comparing it against
the current time, and the page was a one-shot server render with no way
to refresh usage without reloading the whole HTML.

The template context (and window.__SUB_PAGE_DATA__) now carries isOnline,
computed from the panel's own online-client tracking (local xray plus
remote nodes) at render time. The subscription URL also answers
?format=info with the page view-model as JSON — minus the links, with
emails deduplicated — so templates can poll live status cheaply. The
shared view-model construction moved into buildSubPageData/subPageContext
so the HTML page, the SPA payload and the info JSON cannot drift apart.

Also documents the previously injected but undocumented announce
template variable.
This commit is contained in:
Sanaei
2026-07-23 23:57:29 +02:00
parent b319dd0c3a
commit cd674c8d4f
7 changed files with 312 additions and 38 deletions
+10 -1
View File
@@ -10981,7 +10981,7 @@
"tags": [
"Subscription Server"
],
"summary": "Return base64-encoded subscription links for all enabled clients matching the subscription ID. When the request has an Accept: text/html header or ?html=1, renders a styled info page instead. Default path: /sub/:subid.",
"summary": "Return base64-encoded subscription links for all enabled clients matching the subscription ID. When the request has an Accept: text/html header or ?html=1, renders a styled info page instead. With ?format=info, returns the page view-model as JSON (traffic, expiry, online status; no links) for live polling. Default path: /sub/:subid.",
"operationId": "get_subPath_subid",
"parameters": [
{
@@ -10993,6 +10993,15 @@
"type": "string"
}
},
{
"name": "format",
"in": "query",
"required": false,
"description": "Set to \"info\" to get the subscription status view-model as JSON instead of the links.",
"schema": {
"type": "string"
}
},
{
"name": "subPath",
"in": "path",
+2 -1
View File
@@ -1463,9 +1463,10 @@ export const sections: readonly Section[] = [
{
method: 'GET',
path: '/{subPath}:subid',
summary: 'Return base64-encoded subscription links for all enabled clients matching the subscription ID. When the request has an Accept: text/html header or ?html=1, renders a styled info page instead. Default path: /sub/:subid.',
summary: 'Return base64-encoded subscription links for all enabled clients matching the subscription ID. When the request has an Accept: text/html header or ?html=1, renders a styled info page instead. With ?format=info, returns the page view-model as JSON (traffic, expiry, online status; no links) for live polling. Default path: /sub/:subid.',
params: [
{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' },
{ name: 'format', in: 'query', type: 'string', optional: true, desc: 'Set to "info" to get the subscription status view-model as JSON instead of the links.' },
],
},
{