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
+38 -1
View File
@@ -24,6 +24,7 @@ When rendering the template, the following variables are injected into the templ
* `{{ .sId }}`: Subscription ID (UUID).
* `{{ .enabled }}`: Whether the subscription/client is enabled (boolean).
* `{{ .isOnline }}`: Whether the subscription's client has a live connection right now (boolean). Computed from the panel's online-client tracking (local Xray plus any remote nodes) at render time.
* `{{ .download }}`: Formatted download traffic (e.g. "2.5 GB").
* `{{ .upload }}`: Formatted upload traffic.
* `{{ .total }}`: Formatted total traffic limit.
@@ -40,5 +41,41 @@ When rendering the template, the following variables are injected into the templ
* `{{ .subTitle }}`: The subscription title configured in the panel (Subscription → Information). Useful for page branding/headings. May be empty.
* `{{ .subSupportUrl }}`: The support URL configured in the panel. Useful for a "Contact support" link. May be empty.
* `{{ .links }}`: A list (slice) of string configurations (VMess, VLESS, etc. URLs). You can loop through them using `{{ range .links }} ... {{ end }}`.
* `{{ .emails }}`: A list (slice) of emails related to the subscription.
* `{{ .emails }}`: A list (slice) of client emails, parallel to `links` — the email at index *i* owns the link at index *i*. May contain duplicates when one client has several links.
* `{{ .announce }}`: The announcement text configured in the panel (Settings → Subscription → Announce). May be empty.
* `{{ .datepicker }}`: Current calendar format used by the panel (e.g. "gregorian" or "jalali").
## Live Status JSON (`?format=info`)
Every subscription URL also answers `GET <sub URL>?format=info` with the same view-model as JSON —
minus `links`, and with `emails` deduplicated — so a template can poll it and update usage or
online status live without reloading the page:
```json
{
"sId": "…",
"enabled": true,
"isOnline": true,
"used": "1.2 GB",
"remained": "8.8 GB",
"expire": 0,
"lastOnline": 1735680000000,
"…": "…"
}
```
Example polling snippet for a template:
```html
<span id="status"></span>
<script>
async function refreshStatus() {
const res = await fetch(window.location.pathname + '?format=info');
if (!res.ok) return;
const info = await res.json();
document.getElementById('status').textContent = info.isOnline ? 'Online' : 'Offline';
}
refreshStatus();
setInterval(refreshStatus, 10000);
</script>
```