💋 Sync 2026-10-04 04:16:09

This commit is contained in:
github-actions[bot] committed 2026-10-04 04:16:09 +08:00
1 parent f6b2df73ec
commit f62992fd80
745 files changed
+212416 -1173

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Fluent dashboard for LuCI
LUCI_DEPENDS:=+luci-base +libiwinfo
LUCI_DESCRIPTION:=A fixed-card, theme-independent LuCI status dashboard with system, network, wireless, DHCP lease, and wireless client information.
LUCI_MAINTAINER:=LazuliKao
LUCI_MINIFY_CSS:=0
LUCI_MINIFY_JS:=0
PKG_NAME:=luci-mod-fluentdashboard
PKG_VERSION:=1.0.9
PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
PKG_BUILD_DEPENDS:=iwinfo
# call BuildPackage - OpenWrt buildroot signature
include $(TOPDIR)/feeds/luci/luci.mk
@@ -0,0 +1,241 @@
.fluent-dashboard {
--fluent-dashboard-border: var(--fluent-card-border, var(--inputborder-color, color-mix(in srgb, currentColor 16%, transparent)));
--fluent-dashboard-muted: var(--fluent-text-secondary, color-mix(in srgb, currentColor 68%, transparent));
--fluent-dashboard-surface: var(--fluent-card-bg, rgba(var(--primary-rgbbody, 255, 255, 255), 0.7));
--fluent-dashboard-shadow: var(--fluent-card-shadow, 0 .1rem .3rem var(--input-boxcolor, color-mix(in srgb, CanvasText 8%, transparent)));
color: var(--fluent-text, CanvasText);
container-type: inline-size;
}
.fluent-dashboard, .fluent-dashboard * {
box-sizing: border-box;
}
.fluent-dashboard__header {
color: highlighttext;
background: linear-gradient(120deg, Highlight, color-mix(in srgb, Highlight 68%, CanvasText));
border-radius: .75rem;
justify-content: space-between;
align-items: end;
gap: 1rem;
margin-block-end: 1rem;
padding: 1.25rem;
display: flex;
z-index: 0;
overflow: hidden;
position: relative;
}
.fluent-dashboard__header h1, .fluent-dashboard__header p {
color: inherit;
margin: 0;
}
.fluent-dashboard__header h1 {
font-size: clamp(1.5rem, 4vw, 2.25rem);
line-height: 1.1;
}
.fluent-dashboard__header h1 + p {
opacity: .82;
margin-block-start: .4rem;
}
.fluent-dashboard__updated {
opacity: .86;
flex: none;
font-size: .85rem;
}
.fluent-dashboard__grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
display: grid;
}
.fluent-dashboard__card {
min-width: 0;
color: inherit;
background: var(--fluent-dashboard-surface);
border: 1px solid var(--fluent-dashboard-border);
box-shadow: var(--fluent-dashboard-shadow);
border-radius: .75rem;
overflow: hidden;
}
.fluent-dashboard__card--wide {
grid-column: 1 / -1;
}
.fluent-dashboard__card-title {
color: inherit;
border-block-end: 1px solid var(--fluent-dashboard-border);
margin: 0;
padding: 1rem 1.1rem .85rem 1.35rem;
font-size: 1rem;
font-weight: 650;
line-height: 1.25;
position: relative;
}
.fluent-dashboard__card-title:before {
content: "";
background: highlight;
border-radius: 1rem;
width: .2rem;
position: absolute;
inset: 1rem auto .85rem .7rem;
}
.fluent-dashboard__card-content {
padding: 1rem 1.1rem;
}
.fluent-dashboard__details {
grid-template-columns: minmax(8rem, 36%) minmax(0, 1fr);
gap: .6rem 1rem;
margin: 0;
display: grid;
}
.fluent-dashboard__details-label {
color: var(--fluent-dashboard-muted);
}
.fluent-dashboard__details-value {
overflow-wrap: anywhere;
min-width: 0;
font-weight: 550;
}
.fluent-dashboard__meter + .fluent-dashboard__meter {
margin-block-start: 1.1rem;
}
.fluent-dashboard__meter-label {
justify-content: space-between;
gap: 1rem;
margin-block-end: .45rem;
font-size: .85rem;
display: flex;
}
.fluent-dashboard__meter-label span:last-child {
color: var(--fluent-dashboard-muted);
font-variant-numeric: tabular-nums;
}
.fluent-dashboard__meter-track {
background: color-mix(in srgb, currentColor 12%, transparent);
border-radius: 1rem;
height: .5rem;
overflow: hidden;
}
.fluent-dashboard__meter-value {
border-radius: inherit;
background: highlight;
height: 100%;
transition: width .2s;
display: block;
}
.fluent-dashboard__table-wrap {
max-width: 100%;
overflow-x: auto;
}
.fluent-dashboard__table {
width: 100%;
color: inherit;
border-collapse: collapse;
margin: 0;
}
.fluent-dashboard__table th, .fluent-dashboard__table td {
text-align: start;
vertical-align: middle;
border-block-end: 1px solid var(--fluent-dashboard-border);
padding: .7rem .65rem;
}
.fluent-dashboard__table th {
color: var(--fluent-dashboard-muted);
letter-spacing: .02em;
text-transform: uppercase;
font-size: .78rem;
font-weight: 650;
}
.fluent-dashboard__table tbody tr:last-child td {
border-block-end: 0;
}
.fluent-dashboard__status {
white-space: nowrap;
align-items: center;
gap: .35rem;
font-size: .82rem;
font-weight: 600;
display: inline-flex;
}
.fluent-dashboard__status:before {
content: "";
background: currentColor;
border-radius: 50%;
width: .5rem;
height: .5rem;
}
.fluent-dashboard__status--up {
color: color-mix(in srgb, LinkText 75%, Highlight);
}
.fluent-dashboard__status--down {
color: color-mix(in srgb, currentColor 58%, transparent);
}
.fluent-dashboard__table .fluent-dashboard__empty {
color: var(--fluent-dashboard-muted);
text-align: center;
padding-block: 1.5rem;
font-style: italic;
}
@container (width <= 44rem) {
.fluent-dashboard__grid {
grid-template-columns: minmax(0, 1fr);
}
.fluent-dashboard__card--wide {
grid-column: auto;
}
}
@media (max-width: 575px) {
.fluent-dashboard__header {
flex-direction: column;
align-items: start;
padding: 1rem;
}
.fluent-dashboard__details {
grid-template-columns: minmax(0, 1fr);
gap: .2rem;
}
.fluent-dashboard__details-value + .fluent-dashboard__details-label {
margin-block-start: .45rem;
}
.fluent-dashboard__card-content {
padding: .85rem;
}
}
@media (prefers-reduced-motion: reduce) {
.fluent-dashboard__meter-value {
transition: none;
}
}
@@ -0,0 +1,654 @@
"use strict";
"require view";
"require dom";
"require poll";
"require rpc";
"require network";
// WARN: generated by rsbuild - do not edit
// The require scope
var __webpack_require__ = {};
// webpack/runtime/define_property_getters
(() => {
__webpack_require__.d = (exports, getters, values) => {
var define = (defs, kind) => {
for(var key in defs) {
if(__webpack_require__.o(defs, key) && !__webpack_require__.o(exports, key)) {
Object.defineProperty(exports, key, { enumerable: true, [kind]: defs[key] });
}
}
};
define(getters, "get");
define(values, "value");
};
})();
// webpack/runtime/has_own_property
(() => {
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
})();
// webpack/runtime/make_namespace_object
(() => {
// define __esModule on exports
__webpack_require__.r = (exports) => {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
})();
var __webpack_exports__ = {};
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
main: () => (/* binding */ main)
});
;// CONCATENATED MODULE: ../node_modules/.pnpm/@lazulikao+luci-types@https_685a81b9ead15f36368ce6e6ebcf4a27/node_modules/@lazulikao/luci-types/src/jsx/jsx-factory.ts
const Fragment = Symbol.for("jsx.fragment");
function jsx_factory_e(e, t) {
let { children: n, ...r } = null === t || "object" != typeof t || Array.isArray(t) ? {} : t, o = function e(t, n = []) {
for (let r of t)null != r && "boolean" != typeof r && (Array.isArray(r) ? e(r, n) : n.push(r instanceof Node ? r : String(r)));
return n;
}(null == n ? [] : Array.isArray(n) ? n : [
n
]);
if (e === Fragment) {
let e = document.createDocumentFragment();
return e.append(...o), e;
}
if ("function" == typeof e) {
let t = Reflect.apply(e, void 0, [
{
...r,
children: o
}
]);
if (!(t instanceof Node)) throw TypeError("JSX components must return a DOM Node");
return t;
}
if ("string" != typeof e) throw TypeError("JSX element types must be tag names or component functions");
let f = {}, l = {
...r
};
for (let [e, t] of Object.entries(l))e.startsWith("on") && "function" == typeof t ? (f[e] = t, delete l[e]) : "boolean" == typeof t && (t ? l[e] = e : delete l[e]);
let s = Object.keys(l).length > 0 ? o.length > 1 ? E(e, l, o) : E(e, l, o[0]) : o.length > 1 ? E(e, {}, o) : E(e, {}, o[0]);
for (let [e, t] of Object.entries(f)){
let n = e.slice(2).toLowerCase();
s.addEventListener(n, t);
}
return s;
}
function jsx(t, n) {
return jsx_factory_e(t, n);
}
function jsxs(t, n) {
return jsx_factory_e(t, n);
}
function jsxDEV(t, n) {
return jsx_factory_e(t, n);
}
;// CONCATENATED MODULE: ../node_modules/.pnpm/@lazulikao+luci-types@https_685a81b9ead15f36368ce6e6ebcf4a27/node_modules/@lazulikao/luci-types/src/jsx/jsx-runtime.ts
;// CONCATENATED MODULE: ./web/fluentdashboard/model.ts
function model_t(t) {
return null === t || "object" != typeof t || Array.isArray(t) ? {} : t;
}
function model_e(t) {
return "string" == typeof t ? t : "";
}
function model_r(t) {
return "number" == typeof t && Number.isFinite(t) ? t : 0;
}
function model_o(e, i = "free") {
let n = model_t(e);
return {
total: model_r(n.total),
free: model_r(n[i] ?? n.free)
};
}
function normalizeSystemSnapshot(i, n, a) {
let l = model_t(i), m = model_t(n), u = model_t(l.release), f = model_t(a), s = Array.isArray(m.load) ? m.load.map(model_r) : [];
return {
hostname: model_e(l.hostname),
model: model_e(l.model),
architecture: model_e(l.system),
target: model_e(u.target),
firmware: model_e(u.description),
kernel: model_e(l.kernel),
luciVersion: [
model_e(f.branch),
model_e(f.revision)
].filter(Boolean).join(" "),
uptime: model_r(m.uptime),
localtime: model_r(m.localtime),
load: s,
memory: model_o(m.memory, "available"),
swap: model_o(m.swap),
root: model_o(m.root),
tmp: model_o(m.tmp)
};
}
function usedPercent(t) {
return t.total <= 0 ? 0 : Math.min(100, Math.max(0, (t.total - t.free) / t.total * 100));
}
function formatBytes(t) {
if (!Number.isFinite(t) || t <= 0) return "0 B";
let e = [
"B",
"KiB",
"MiB",
"GiB",
"TiB"
], r = Math.min(Math.floor(Math.log(t) / Math.log(1024)), e.length - 1), o = t / 1024 ** r;
return `${o >= 10 || 0 === r ? o.toFixed(0) : o.toFixed(1)} ${e[r]}`;
}
function formatDuration(t) {
if (!Number.isFinite(t) || t <= 0) return "-";
let e = Math.floor(t / 86400), r = Math.floor(t % 86400 / 3600), o = Math.floor(t % 3600 / 60);
return [
e > 0 ? `${e}d` : "",
`${r}h`,
`${o}m`
].filter(Boolean).join(" ");
}
function formatLoad(t) {
return 0 === t.length ? "-" : t.slice(0, 3).map((t)=>(t / 65535).toFixed(2)).join(" / ");
}
function formatLocalTime(t) {
return !Number.isFinite(t) || t <= 0 ? "-" : new Intl.DateTimeFormat(void 0, {
dateStyle: "medium",
timeStyle: "medium",
timeZone: "UTC"
}).format(new Date(1000 * t));
}
;// CONCATENATED MODULE: ./web/fluentdashboard/index.tsx
let fluentdashboard_l = rpc.declare({
object: "system",
method: "board"
}), fluentdashboard_o = rpc.declare({
object: "system",
method: "info"
}), c = rpc.declare({
object: "luci",
method: "getVersion"
}), fluentdashboard_u = rpc.declare({
object: "luci-rpc",
method: "getDHCPLeases",
expect: {
"": {}
}
});
function fluentdashboard_h(e) {
return null === e || "object" != typeof e || Array.isArray(e) ? {} : e;
}
function fluentdashboard_m(e) {
return "string" == typeof e ? e : "";
}
function p(e) {
return "number" == typeof e && Number.isFinite(e) ? e : 0;
}
async function fluentdashboard_f(e, t) {
try {
return await e;
} catch {
return t;
}
}
function y(e, t = !1) {
let r = e.getL3Device() ?? e.getDevice();
return {
name: e.getName(),
protocol: e.getI18n(),
up: e.isUp(),
addresses: t ? e.getIP6Addrs() : e.getIPAddrs(),
gateway: t ? e.getGateway6Addr() : e.getGatewayAddr(),
uptime: e.getUptime(),
device: r?.getName() ?? "",
rx: r?.getRXBytes() ?? 0,
tx: r?.getTXBytes() ?? 0
};
}
async function g() {
await fluentdashboard_f(network.flushCache(), {});
let [e, t, r, a, s, n, d, u] = await Promise.all([
fluentdashboard_f(fluentdashboard_l(), {}),
fluentdashboard_f(fluentdashboard_o(), {}),
fluentdashboard_f(c(), {}),
fluentdashboard_f(network.getWANNetworks(), []),
fluentdashboard_f(network.getWAN6Networks(), []),
fluentdashboard_f(network.getNetworks(), []),
fluentdashboard_f(network.getWifiDevices(), []),
fluentdashboard_f(network.getWifiNetworks(), [])
]), h = u.map((e)=>{
let t = d.find((t)=>t.getName() === e.getWifiDeviceName());
return {
radio: t?.getName() ?? e.getWifiDeviceName() ?? "",
hardware: t?.getI18n() ?? "",
up: e.isUp() && !t?.isDisabled(),
ssid: e.getActiveSSID() || e.getSSID() || e.getMeshID() || "",
mode: e.getActiveModeI18n() || e.getMode(),
channel: e.getChannel(),
frequency: e.getFrequency() ?? "",
encryption: e.getActiveEncryption()
};
});
for (let e of d)h.some((t)=>t.radio === e.getName()) || h.push({
radio: e.getName(),
hardware: e.getI18n(),
up: e.isUp() && !e.isDisabled(),
ssid: "",
mode: "",
channel: null,
frequency: "",
encryption: ""
});
return {
system: normalizeSystemSnapshot(e, t, r),
wan4: a.map((e)=>y(e)),
wan6: s.map((e)=>y(e, !0)),
interfaces: n.filter((e)=>"loopback" !== e.getName()).map((e)=>y(e)),
wireless: h,
updatedAt: new Date()
};
}
async function w() {
let [e, t, r] = await Promise.all([
fluentdashboard_f(fluentdashboard_u(), {}),
fluentdashboard_f(network.getWifiNetworks(), []),
fluentdashboard_f(network.getHostHints(), null)
]), a = await Promise.all(t.map(async (e)=>({
wifi: e,
peers: await fluentdashboard_f(e.getAssocList(), [])
}))), s = [];
for (let { wifi: e, peers: t } of a)for (let a of t){
let t = r?.getHostnameByMACAddr(a.mac) || r?.getIPAddrByMACAddr(a.mac) || r?.getIP6AddrByMACAddr(a.mac) || "";
s.push({
network: e.getActiveSSID() || e.getSSID() || e.getName(),
hostname: t,
mac: a.mac,
signal: a.signal,
noise: a.noise ?? null,
rxRate: a.rx?.rate ?? 0,
txRate: a.tx?.rate ?? 0
});
}
return {
leases: function(e) {
let t = fluentdashboard_h(e), r = [];
if (Array.isArray(t.dhcp_leases)) for (let e of t.dhcp_leases){
let t = fluentdashboard_h(e);
r.push({
family: "IPv4",
hostname: fluentdashboard_m(t.hostname),
address: fluentdashboard_m(t.ipaddr),
identifier: fluentdashboard_m(t.macaddr),
expires: !1 !== t.expires && p(t.expires)
});
}
if (Array.isArray(t.dhcp6_leases)) for (let e of t.dhcp6_leases){
let t = fluentdashboard_h(e), a = Array.isArray(t.ip6addrs) ? t.ip6addrs.filter((e)=>"string" == typeof e) : [];
r.push({
family: "IPv6",
hostname: fluentdashboard_m(t.hostname),
address: a.join(", ") || fluentdashboard_m(t.ip6addr),
identifier: fluentdashboard_m(t.duid),
expires: !1 !== t.expires && p(t.expires)
});
}
return r;
}(e),
clients: s
};
}
function b(e) {
return null == e || "" === e ? "-" : String(e);
}
function v(t) {
return jsx("span", {
class: `fluent-dashboard__status fluent-dashboard__status--${t ? "up" : "down"}`,
children: t ? _("Online") : _("Offline")
});
}
function A(r, a, s, n = !1) {
return jsxs("div", {
class: `fluent-dashboard__card${n ? " fluent-dashboard__card--wide" : ""}`,
"data-dashboard-section": r,
children: [
jsx("h2", {
class: "fluent-dashboard__card-title",
children: a
}),
jsx("div", {
class: "fluent-dashboard__card-content",
children: s
})
]
});
}
function S(r, a, s) {
return jsx("div", {
class: "fluent-dashboard__table-wrap",
children: jsxs("table", {
class: "fluent-dashboard__table",
children: [
jsx("thead", {
children: jsx("tr", {
children: r.map((t)=>jsx("th", {
children: t
}))
})
}),
jsx("tbody", {
children: a.length ? a.map((t)=>jsx("tr", {
children: t.map((t)=>jsx("td", {
children: t instanceof Node ? t : b(t)
}))
})) : jsx("tr", {
children: jsx("td", {
class: "fluent-dashboard__empty",
colSpan: r.length,
children: s
})
})
})
]
})
});
}
function x(t) {
return A("system", _("System"), jsx("div", {
class: "fluent-dashboard__details",
children: [
[
_("Hostname"),
t.hostname
],
[
_("Model"),
t.model
],
[
_("Architecture"),
t.architecture
],
[
_("Target Platform"),
t.target
],
[
_("Firmware Version"),
t.firmware
],
[
_("Kernel Version"),
t.kernel
],
[
_("LuCI Version"),
t.luciVersion
],
[
_("Local Time"),
formatLocalTime(t.localtime)
],
[
_("Uptime"),
formatDuration(t.uptime)
],
[
_("Load Average"),
formatLoad(t.load)
]
].flatMap(([t, r])=>[
jsx("div", {
class: "fluent-dashboard__details-label",
children: t
}),
jsx("div", {
class: "fluent-dashboard__details-value",
children: r instanceof Node ? r : b(r)
})
])
}));
}
function I(a) {
let s = [
[
_("Memory"),
a.memory
],
[
_("Swap"),
a.swap
],
[
_("Root storage"),
a.root
],
[
_("Temporary storage"),
a.tmp
]
];
return A("resources", _("Resources"), s.map(([a, s])=>{
let n, i;
return n = Math.max(0, s.total - s.free), i = usedPercent(s), jsxs("div", {
class: "fluent-dashboard__meter",
children: [
jsxs("div", {
class: "fluent-dashboard__meter-label",
children: [
jsx("span", {
children: a
}),
jsx("span", {
children: `${formatBytes(n)} / ${formatBytes(s.total)}`
})
]
}),
jsx("div", {
class: "fluent-dashboard__meter-track",
role: "progressbar",
"aria-valuemin": 0,
"aria-valuemax": 100,
"aria-valuenow": Math.round(i),
children: jsx("span", {
class: "fluent-dashboard__meter-value",
style: `width:${i.toFixed(1)}%`
})
})
]
});
}));
}
function N(e) {
let t = [
...e.wan4.map((e)=>[
"IPv4",
e
]),
...e.wan6.map((e)=>[
"IPv6",
e
])
];
return A("internet", _("Internet"), S([
_("Family"),
_("Interface"),
_("Status"),
_("Protocol"),
_("Address"),
_("Gateway"),
_("Uptime")
], t.map(([e, t])=>[
e,
t.name,
v(t.up),
t.protocol,
t.addresses.join(", "),
t.gateway,
formatDuration(t.uptime)
]), _("No Internet connection information is available.")), !0);
}
function D(e) {
return A("interfaces", _("Interfaces"), S([
_("Interface"),
_("Device"),
_("Status"),
_("Address"),
_("Received"),
_("Transmitted")
], e.map((e)=>[
e.name,
e.device,
v(e.up),
e.addresses.join(", "),
formatBytes(e.rx),
formatBytes(e.tx)
]), _("No network interfaces were found.")), !0);
}
function k(t) {
return A("wireless", _("Wireless"), S([
_("Radio"),
_("SSID"),
_("Status"),
_("Mode"),
_("Channel"),
_("Encryption")
], t.map((t)=>[
jsx("span", {
title: t.hardware,
children: b(t.radio)
}),
t.ssid,
v(t.up),
t.mode,
null === t.channel ? "-" : t.frequency ? `${t.channel} (${t.frequency} GHz)` : t.channel,
t.encryption
]), _("No wireless radios were found.")), !0);
}
function C(e) {
return A("leases", _("Active DHCP Leases"), S([
_("Family"),
_("Hostname"),
_("Address"),
_("MAC / DUID"),
_("Expires")
], e.map((e)=>{
var t;
return [
e.family,
e.hostname,
e.address,
e.identifier,
!1 === (t = e.expires) ? _("Unlimited") : t <= 0 ? _("Expired") : formatDuration(t)
];
}), _("There are no active DHCP leases.")), !0);
}
function P(e) {
return e > 0 ? `${(e / 1000).toFixed(1)} ${_("Mbit/s")}` : "-";
}
function M(e) {
return A("clients", _("Wireless Clients"), S([
_("Network"),
_("Host"),
_("MAC address"),
_("Signal"),
_("RX / TX Rate")
], e.map((e)=>[
e.network,
e.hostname,
e.mac,
null === e.noise ? `${e.signal} dBm` : `${e.signal} / ${e.noise} dBm`,
`${P(e.rxRate)} / ${P(e.txRate)}`
]), _("No wireless clients are connected.")), !0);
}
class $ extends L.view {
handleSave = null;
handleSaveApply = null;
handleReset = null;
root = null;
load() {
return Promise.all([
g(),
w()
]);
}
replaceSection(e, t) {
this.root?.querySelector(`[data-dashboard-section="${e}"]`)?.replaceWith(t);
}
async refreshSummary() {
if (!this.root?.isConnected) return;
let e = await g();
if (!this.root?.isConnected) return;
this.replaceSection("system", x(e.system)), this.replaceSection("resources", I(e.system)), this.replaceSection("internet", N(e)), this.replaceSection("interfaces", D(e.interfaces)), this.replaceSection("wireless", k(e.wireless));
let t = this.root.querySelector("[data-dashboard-updated]");
t && (t.textContent = e.updatedAt.toLocaleTimeString());
}
async refreshDetails() {
if (!this.root?.isConnected) return;
let e = await w();
this.root?.isConnected && (this.replaceSection("leases", C(e.leases)), this.replaceSection("clients", M(e.clients)));
}
render([r, a]) {
return document.getElementById("fluent-dashboard-styles") || document.head.appendChild(jsx("link", {
id: "fluent-dashboard-styles",
rel: "stylesheet",
href: L.resource("view/fluentdashboard/index.css")
})), this.root = jsxs("div", {
class: "fluent-dashboard",
children: [
jsxs("div", {
class: "fluent-dashboard__header",
children: [
jsxs("div", {
children: [
jsx("h1", {
children: _("Fluent Dashboard")
}),
jsx("p", {
children: _("A live overview of this OpenWrt device.")
})
]
}),
jsxs("p", {
class: "fluent-dashboard__updated",
children: [
_("Updated"),
" ",
jsx("time", {
"data-dashboard-updated": "",
children: r.updatedAt.toLocaleTimeString()
})
]
})
]
}),
jsxs("div", {
class: "fluent-dashboard__grid",
children: [
x(r.system),
I(r.system),
N(r),
D(r.interfaces),
k(r.wireless),
C(a.leases),
M(a.clients)
]
})
]
}), poll.add(()=>this.refreshSummary(), 5), poll.add(()=>this.refreshDetails(), 15), this.root;
}
}
const main = $;
return main;
@@ -0,0 +1,177 @@
msgid ""
msgstr ""
"Project-Id-Version: luci-mod-fluentdashboard\n"
"POT-Creation-Date: 2026-08-24 00:20+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "A live overview of this OpenWrt device."
msgstr ""
msgid "Active DHCP Leases"
msgstr ""
msgid "Address"
msgstr ""
msgid "Architecture"
msgstr ""
msgid "Channel"
msgstr ""
msgid "Device"
msgstr ""
msgid "Encryption"
msgstr ""
msgid "Expired"
msgstr ""
msgid "Expires"
msgstr ""
msgid "Family"
msgstr ""
msgid "Firmware Version"
msgstr ""
msgid "Fluent Dashboard"
msgstr ""
msgid "Gateway"
msgstr ""
msgid "Host"
msgstr ""
msgid "Hostname"
msgstr ""
msgid "Interface"
msgstr ""
msgid "Interfaces"
msgstr ""
msgid "Internet"
msgstr ""
msgid "Kernel Version"
msgstr ""
msgid "Load Average"
msgstr ""
msgid "Local Time"
msgstr ""
msgid "LuCI Version"
msgstr ""
msgid "MAC / DUID"
msgstr ""
msgid "MAC address"
msgstr ""
msgid "Mbit/s"
msgstr ""
msgid "Memory"
msgstr ""
msgid "Mode"
msgstr ""
msgid "Model"
msgstr ""
msgid "Network"
msgstr ""
msgid "No Internet connection information is available."
msgstr ""
msgid "No network interfaces were found."
msgstr ""
msgid "No wireless clients are connected."
msgstr ""
msgid "No wireless radios were found."
msgstr ""
msgid "Offline"
msgstr ""
msgid "Online"
msgstr ""
msgid "Protocol"
msgstr ""
msgid "Radio"
msgstr ""
msgid "Received"
msgstr ""
msgid "Resources"
msgstr ""
msgid "Root storage"
msgstr ""
msgid "RX / TX Rate"
msgstr ""
msgid "Signal"
msgstr ""
msgid "SSID"
msgstr ""
msgid "Status"
msgstr ""
msgid "Swap"
msgstr ""
msgid "System"
msgstr ""
msgid "Target Platform"
msgstr ""
msgid "Temporary storage"
msgstr ""
msgid "There are no active DHCP leases."
msgstr ""
msgid "Transmitted"
msgstr ""
msgid "Unlimited"
msgstr ""
msgid "Updated"
msgstr ""
msgid "Uptime"
msgstr ""
msgid "Wireless"
msgstr ""
msgid "Wireless Clients"
msgstr ""
+1
View File
@@ -0,0 +1 @@
zh_Hans
@@ -0,0 +1,177 @@
msgid ""
msgstr ""
"Project-Id-Version: luci-mod-fluentdashboard\n"
"POT-Creation-Date: 2026-08-24 00:20+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: LazuliKao\n"
"Language-Team: LuCI Fluent Dashboard\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "A live overview of this OpenWrt device."
msgstr "此 OpenWrt 设备的实时概览。"
msgid "Active DHCP Leases"
msgstr "活动的 DHCP 租约"
msgid "Address"
msgstr "地址"
msgid "Architecture"
msgstr "架构"
msgid "Channel"
msgstr "信道"
msgid "Device"
msgstr "设备"
msgid "Encryption"
msgstr "加密"
msgid "Expired"
msgstr "已过期"
msgid "Expires"
msgstr "到期时间"
msgid "Family"
msgstr "协议族"
msgid "Firmware Version"
msgstr "固件版本"
msgid "Fluent Dashboard"
msgstr "Fluent 状态概览"
msgid "Gateway"
msgstr "网关"
msgid "Host"
msgstr "主机"
msgid "Hostname"
msgstr "主机名"
msgid "Interface"
msgstr "接口"
msgid "Interfaces"
msgstr "接口"
msgid "Internet"
msgstr "互联网"
msgid "Kernel Version"
msgstr "内核版本"
msgid "Load Average"
msgstr "平均负载"
msgid "Local Time"
msgstr "本地时间"
msgid "LuCI Version"
msgstr "LuCI 版本"
msgid "MAC / DUID"
msgstr "MAC / DUID"
msgid "MAC address"
msgstr "MAC 地址"
msgid "Mbit/s"
msgstr "Mbit/s"
msgid "Memory"
msgstr "内存"
msgid "Mode"
msgstr "模式"
msgid "Model"
msgstr "型号"
msgid "Network"
msgstr "网络"
msgid "No Internet connection information is available."
msgstr "没有可用的互联网连接信息。"
msgid "No network interfaces were found."
msgstr "未找到网络接口。"
msgid "No wireless clients are connected."
msgstr "没有已连接的无线客户端。"
msgid "No wireless radios were found."
msgstr "未找到无线射频。"
msgid "Offline"
msgstr "离线"
msgid "Online"
msgstr "在线"
msgid "Protocol"
msgstr "协议"
msgid "Radio"
msgstr "射频"
msgid "Received"
msgstr "已接收"
msgid "Resources"
msgstr "资源"
msgid "Root storage"
msgstr "根存储"
msgid "RX / TX Rate"
msgstr "接收 / 发送速率"
msgid "Signal"
msgstr "信号"
msgid "SSID"
msgstr "SSID"
msgid "Status"
msgstr "状态"
msgid "Swap"
msgstr "交换空间"
msgid "System"
msgstr "系统"
msgid "Target Platform"
msgstr "目标平台"
msgid "Temporary storage"
msgstr "临时存储"
msgid "There are no active DHCP leases."
msgstr "没有活动的 DHCP 租约。"
msgid "Transmitted"
msgstr "已发送"
msgid "Unlimited"
msgstr "无限制"
msgid "Updated"
msgstr "更新时间"
msgid "Uptime"
msgstr "运行时间"
msgid "Wireless"
msgstr "无线"
msgid "Wireless Clients"
msgstr "无线客户端"
@@ -0,0 +1,13 @@
{
"admin/fluentdashboard": {
"title": "Fluent Dashboard",
"order": 1,
"action": {
"type": "view",
"path": "fluentdashboard/index"
},
"depends": {
"acl": [ "luci-mod-fluentdashboard-index" ]
}
}
}
@@ -0,0 +1,13 @@
{
"luci-mod-fluentdashboard-index": {
"description": "Grant access to the Fluent dashboard status display",
"read": {
"ubus": {
"iwinfo": [ "assoclist" ],
"luci": [ "getVersion" ],
"luci-rpc": [ "getDHCPLeases" ],
"system": [ "board", "info" ]
}
}
}
}