implementação de mudança de interface
Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
1 parent
d445aa03cf
commit
d4e1b297ae
24 files changed
+1982
-47
No files matched your search
@@ -0,0 +1,279 @@
|
||||
// --- 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 ---
|
||||
Reference in new issue
Block a user