diff --git a/admin.html b/admin.html
index 933245f..e2c6cb4 100644
--- a/admin.html
+++ b/admin.html
@@ -703,6 +703,10 @@ function statCard(label, value, color, icon, delay) {
`;
}
+function rowAttr(i) {
+ return `class="reveal" style="animation-delay:${Math.min(i, 10) * 0.025}s"`;
+}
+
async function loadTraffic() {
const t = await api("/admin/api/traffic");
const grid = document.getElementById("traffic-stat-grid");
@@ -713,8 +717,8 @@ async function loadTraffic() {
].map(([l, v, c, ic], i) => statCard(l, v, c, ic, i * 0.05)).join("");
const body = document.getElementById("traffic-body");
- body.innerHTML = t.per_subscription.length ? t.per_subscription.map((r) => `
-
| ${esc(r.username)} | ${esc(r.node_label)} | ${r.up_fmt} | ${r.down_fmt} | ${r.total_fmt} |
+ body.innerHTML = t.per_subscription.length ? t.per_subscription.map((r, i) => `
+
| ${esc(r.username)} | ${esc(r.node_label)} | ${r.up_fmt} | ${r.down_fmt} | ${r.total_fmt} |
|
`).join("") : 'Пока нет данных по трафику |
';
}
@@ -756,8 +760,8 @@ async function loadDashboard() {
const subs = await api("/admin/api/subscriptions");
const body = document.getElementById("recent-subs-body");
const recent = subs.slice(0, 8);
- body.innerHTML = recent.length ? recent.map((s) => `
- | ${s.username ? "@" + esc(s.username) : "tg" + s.tg_id} | ${esc(s.node_label)} | ${esc(s.plan_label)} |
+ body.innerHTML = recent.length ? recent.map((s, i) => `
+
| ${s.username ? "@" + esc(s.username) : "tg" + s.tg_id} | ${esc(s.node_label)} | ${esc(s.plan_label)} |
${fmtDate(s.expires_at)} | ${statusBadge(s.active, s.days_left)} |
`).join("") : 'Пока нет подписок |
';
}
@@ -765,8 +769,8 @@ async function loadDashboard() {
async function loadSubscriptions() {
const subs = await api("/admin/api/subscriptions");
const body = document.getElementById("subs-body");
- body.innerHTML = subs.length ? subs.map((s) => `
- | ${s.username ? "@" + esc(s.username) : "tg" + s.tg_id} | ${esc(s.node_label)} | ${esc(s.plan_label)} |
+ body.innerHTML = subs.length ? subs.map((s, i) => `
+
| ${s.username ? "@" + esc(s.username) : "tg" + s.tg_id} | ${esc(s.node_label)} | ${esc(s.plan_label)} |
${fmtDate(s.created_at)} | ${fmtDate(s.expires_at)} | ${statusBadge(s.active, s.days_left)} |
@@ -803,8 +807,8 @@ function switchUserTab(tab) {
}
function renderUcSubs(subs) {
const list = document.getElementById("uc-subs-list");
- list.innerHTML = subs.length ? subs.map((s) => `
-
+ list.innerHTML = subs.length ? subs.map((s, i) => `
+
${esc(s.node_label)} — ${esc(s.plan_label)}
до ${fmtDate(s.expires_at)} · ${statusBadge(s.active, s.days_left)}
@@ -822,8 +826,8 @@ async function grantSubscription() {
}
function renderDevices(devices) {
const list = document.getElementById("devices-list");
- list.innerHTML = devices.length ? devices.map((d) => `
-
+ list.innerHTML = devices.length ? devices.map((d, i) => `
+
${esc(d.device_model || d.device_os || "Неизвестное устройство")}
${esc(d.device_os || "")} · с ${fmtDate(d.first_seen)}
@@ -862,8 +866,8 @@ async function loadGifts() {
const codes = await api("/admin/api/gift-codes");
const body = document.getElementById("gifts-body");
- body.innerHTML = codes.length ? codes.map((c) => `
- | ${esc(c.node_label)} | ${esc(c.plan_label)} | ${fmtDate(c.created_at)} |
+ body.innerHTML = codes.length ? codes.map((c, i) => `
+ | ${esc(c.node_label)} | ${esc(c.plan_label)} | ${fmtDate(c.created_at)} |
${c.used_by ? 'использован' : 'свободен'} |
|
`).join("") : ' Пока нет гифт-кодов | ';
@@ -883,8 +887,8 @@ async function loadNodes() {
const nodes = await api("/admin/api/nodes");
nodesCache = nodes;
const body = document.getElementById("nodes-body");
- body.innerHTML = nodes.map((n) => `
-
+ body.innerHTML = nodes.map((n, i) => `
+
| ${esc(n.label)} | ${esc(n.address) || "—"}${n.port && n.address ? ":" + n.port : ""} |
${n.kind === "local" ? "локальная" : n.kind === "managed" ? "управляемая" : "внешняя"} |
${n.status === "pending" ? 'ожидает установки' : (n.enabled ? 'включена' : 'выключена')} |
|