Files
genieacs/ui/unifi-device-detail-page.ts
rodrigo d4e1b297ae implementação de mudança de interface
Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo.sousa.577@gmail.com>
2026-03-30 09:53:11 -03:00

280 lines
9.2 KiB
TypeScript

// --- BEGIN UNIFI THEME ---
// UniFi-style device detail page.
// This file REPLACES device-page.ts in the router without modifying the
// original, preserving upstream merge-friendliness.
// All TR-069 parameter rendering is delegated to the original device-page
// component; this wrapper adds the UniFi header/action-bar on top.
import { ClosureComponent, Children } from "mithril";
import { m } from "./components.ts";
import { icon } from "./tailwind-utility-components.ts";
import { device as deviceConfig } from "./config.ts";
import * as store from "./store.ts";
import * as notifications from "./notifications.ts";
import { queueTask } from "./task-queue.ts";
import Expression from "../lib/common/expression.ts";
import Path from "../lib/common/path.ts";
import {
statusBadge,
getDeviceStatus,
getDeviceName,
getDeviceModel,
getParam,
timeAgo,
} from "./unifi-components.ts";
export function init(
args: Record<string, unknown>,
): Promise<Record<string, unknown>> {
if (!window.authorizer.hasAccess("devices", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page"),
);
}
return Promise.resolve({
deviceId: args.id,
deviceFilter: new Expression.Binary(
"=",
new Expression.Parameter(Path.parse("DeviceID.ID")),
new Expression.Literal(args.id as string),
),
});
}
interface Attrs {
deviceId: string;
deviceFilter: Expression;
}
// ─── KV pair helper ────────────────────────────────────────────────────────
function kv(label: string, value: string | null): Children {
return m("div", [
m("div.unifi-detail-kv__label", label),
m(
`div.unifi-detail-kv__value${value == null ? ".empty" : ""}`,
value ?? "—",
),
]);
}
// ─── Action helper ─────────────────────────────────────────────────────────
function actionBtn(
label: string,
className: string,
disabled: boolean,
onClick: () => void,
): Children {
return m(
`button.unifi-btn.${className}`,
{ disabled, onclick: onClick },
label,
);
}
// ─── Component ─────────────────────────────────────────────────────────────
export const component: ClosureComponent<Attrs> = () => {
let actionsPending = false;
return {
view: (vnode) => {
const deviceId = vnode.attrs.deviceId;
document.title = `${deviceId} — Devices — GenieACS`;
const dev = store.fetch("devices", vnode.attrs.deviceFilter);
// While loading
if (!dev.value || !dev.value.length) {
if (!dev.fulfilling) {
return m(
"p",
{ style: "color:#e53935;font-weight:bold;" },
`No such device: ${deviceId}`,
);
}
return m("loading", { queries: [dev] }, m("div", { style: "height:100px" }));
}
const device = dev.value[0];
const status = getDeviceStatus(device);
const name = getDeviceName(device);
const model = getDeviceModel(device);
const lastInform = device["_lastInform"] as number | undefined;
const canWrite = window.authorizer.hasAccess("devices", 3);
// Key TR-069 fields
const serial =
getParam(device, "DeviceID.SerialNumber") ??
getParam(
device,
"InternetGatewayDevice.DeviceInfo.SerialNumber",
"Device.DeviceInfo.SerialNumber",
);
const firmware = getParam(
device,
"InternetGatewayDevice.DeviceInfo.SoftwareVersion",
"Device.DeviceInfo.SoftwareVersion",
);
const wanIp = getParam(
device,
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.ExternalIPAddress",
"Device.IP.Interface.1.IPv4Address.1.IPAddress",
);
const ssid = getParam(
device,
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID",
"Device.WiFi.SSID.1.SSID",
);
const hardwareVersion = getParam(
device,
"InternetGatewayDevice.DeviceInfo.HardwareVersion",
"Device.DeviceInfo.HardwareVersion",
);
const uptime = getParam(
device,
"InternetGatewayDevice.DeviceInfo.UpTime",
"Device.DeviceInfo.UpTime",
);
// ── Router icon ────────────────────────────────────────────────────
const routerIcon = m(
"div.unifi-device-detail-header__icon",
m(icon, { name: "nav-devices", class: "h-7 w-7" }),
);
// ── Header bar ─────────────────────────────────────────────────────
const header = m("div.unifi-device-detail-header", [
// Back link
m(
"a.unifi-back-link",
{ href: "#!/devices" },
m(icon, { name: "nav-back", class: "h-4 w-4" }),
" Back to Devices",
),
m("div.unifi-device-detail-header__top", [
routerIcon,
m("div.unifi-device-detail-header__info", [
m("h1.unifi-device-detail-header__name", name),
m("div.unifi-device-detail-header__meta", [
m(statusBadge, { status }),
model && m("span", model),
serial && m("span", `S/N: ${serial}`),
lastInform &&
m(
"span",
{ title: new Date(lastInform).toLocaleString() },
`Last seen: ${timeAgo(lastInform)}`,
),
]),
]),
// Actions
canWrite &&
m("div.unifi-device-detail-header__actions", [
actionBtn(
"Reboot",
"unifi-btn--secondary",
actionsPending,
() => {
actionsPending = true;
queueTask({ name: "reboot", device: deviceId });
notifications.push(
"success",
`Reboot task queued for ${name}`,
);
setTimeout(() => {
actionsPending = false;
m.redraw();
}, 2000);
},
),
actionBtn(
"Factory Reset",
"unifi-btn--danger",
actionsPending,
() => {
if (
!confirm(
`Factory reset ${name}? This will erase all configuration.`,
)
)
return;
actionsPending = true;
queueTask({ name: "factoryReset", device: deviceId });
notifications.push(
"success",
`Factory reset task queued for ${name}`,
);
setTimeout(() => {
actionsPending = false;
m.redraw();
}, 2000);
},
),
actionBtn(
"Refresh",
"unifi-btn--secondary",
actionsPending,
() => {
actionsPending = true;
store.setTimestamp(Date.now());
setTimeout(() => {
actionsPending = false;
m.redraw();
}, 1500);
},
),
]),
]),
// Key-value grid
m("div.unifi-device-detail-header__kv", [
kv("WAN IP", wanIp),
kv("WiFi SSID", ssid),
kv("Firmware", firmware),
kv("Hardware", hardwareVersion),
uptime != null &&
kv(
"Uptime",
(() => {
const s = Number(uptime);
if (isNaN(s)) return uptime;
const d = Math.floor(s / 86400);
const h = Math.floor((s % 86400) / 3600);
const m2 = Math.floor((s % 3600) / 60);
return [d && `${d}d`, h && `${h}h`, `${m2}m`]
.filter(Boolean)
.join(" ");
})(),
),
kv("Device ID", deviceId),
]),
]);
// ── Original device-page content (all configured panels) ────────────
const conf = deviceConfig;
const cmps = [];
for (const c of Object.values(conf)) {
cmps.push(
m.context(
{ device: dev.value[0], deviceQuery: dev },
store.evaluateExpression(c["type"], {}).value as string,
c as any,
),
);
}
const content = m("div.unifi-device-content", [
m("div.device-page", cmps),
]);
return m("div", [header, content]);
},
};
};
// --- END UNIFI THEME ---