Files
App/www/settings.js
HiepLM 77ea3cc361
Some checks failed
Test / test (push) Has been cancelled
Thêm phần 5. Monitoring và 6. System
2026-06-25 16:00:18 +07:00

149 lines
7.4 KiB
JavaScript

(() => {
const el = (id) => document.getElementById(id);
const t = (key, vars) => window.I18n?.t(key, vars) ?? key;
const statusEl = el("settingsStatusText");
const inputMissionRuns = el("settingsRetentionMissionRuns");
const inputSystemLog = el("settingsRetentionSystemLog");
const inputErrorLogs = el("settingsRetentionErrorLogs");
const selectLogLevel = el("settingsLogLevel");
const gridEl = el("settingsTileGrid");
const gridViewEl = el("settingsGridView");
const detailViewEl = el("settingsDetailView");
function setStatus(text, kind = "info") {
if (!statusEl) return;
statusEl.textContent = text || "";
statusEl.classList.toggle("mapsMirNote--ok", kind === "ok");
statusEl.classList.toggle("mapsMirNote--error", kind === "error");
}
const TILE_SVGS = {
mapping: `<svg width="42" height="42" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6l6-2 4 2 6-2v14l-6 2-4-2-6 2V6z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M10 4v14M14 6v14" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>`,
warning: `<svg width="42" height="42" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3l10 18H2L12 3z" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 9v5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="12" cy="17" r="1" fill="currentColor"/></svg>`,
gear: `<svg width="42" height="42" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 15.2a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4z" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M19.4 15a8.6 8.6 0 0 0 .1-2l2-1.2-2-3.4-2.3.7a8.5 8.5 0 0 0-1.7-1L15 5h-4l-.5 2.1a8.5 8.5 0 0 0-1.7 1l-2.3-.7-2 3.4L6.6 13a8.6 8.6 0 0 0 .1 2l-2 1.2 2 3.4 2.3-.7c.5.4 1.1.7 1.7 1L11 22h4l.5-2.1c.6-.3 1.2-.6 1.7-1l2.3.7 2-3.4-2-1.2z" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>`,
battery: `<svg width="42" height="42" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="7" width="16" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M21 10v4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M6 10h8v4H6z" fill="currentColor"/></svg>`,
calendar: `<svg width="42" height="42" viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="5" width="16" height="15" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M8 3v4M16 3v4M4 9h16" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>`,
wifi: `<svg width="42" height="42" viewBox="0 0 24 24" aria-hidden="true"><path d="M2 8c5-4 15-4 20 0" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M5 12c3.5-2.7 10.5-2.7 14 0" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M8.5 15.5c2-1.5 5-1.5 7 0" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="12" cy="19" r="1.3" fill="currentColor"/></svg>`,
};
const TILES = [
{ id: "mapping", title: "settings.tile.mapping", sub: "settings.tile.mappingSub", icon: "mapping", action: "coming_soon" },
{ id: "error_handling", title: "settings.tile.errorHandling", sub: "settings.tile.errorHandlingSub", icon: "warning", action: "coming_soon" },
{ id: "battery", title: "settings.tile.battery", sub: "settings.tile.batterySub", icon: "battery", action: "coming_soon" },
{ id: "wifi", title: "settings.tile.wifi", sub: "settings.tile.wifiSub", icon: "wifi", action: "coming_soon" },
{ id: "date_time", title: "settings.tile.dateTime", sub: "settings.tile.dateTimeSub", icon: "calendar", action: "coming_soon" },
{ id: "advanced", title: "settings.tile.advanced", sub: "settings.tile.advancedSub", icon: "gear", action: "open_logging_retention" },
];
function showGrid() {
if (gridViewEl) gridViewEl.hidden = false;
if (detailViewEl) detailViewEl.hidden = true;
setStatus("");
}
function showDetail() {
if (gridViewEl) gridViewEl.hidden = true;
if (detailViewEl) detailViewEl.hidden = false;
}
function renderGrid() {
if (!gridEl) return;
gridEl.innerHTML = TILES.map((tile) => {
const disabled = tile.action === "coming_soon";
return `
<button type="button" class="settingsMirTile${disabled ? " is-disabled" : ""}" data-settings-tile="${tile.id}" ${disabled ? "disabled" : ""}>
<div class="settingsMirTileIcon">${TILE_SVGS[tile.icon] || ""}</div>
<div class="settingsMirTileTitle">${t(tile.title)}</div>
<div class="settingsMirTileSub">${t(tile.sub)}</div>
</button>`;
}).join("");
}
async function apiJson(url, opts = {}) {
const res = await fetch(url, { credentials: "include", ...opts });
const text = await res.text();
let data = null;
try {
data = text ? JSON.parse(text) : null;
} catch {
data = null;
}
if (!res.ok) throw new Error((data && data.error) || text || res.statusText);
return data;
}
function toInt(v, fallback) {
const n = Number(v);
if (!Number.isFinite(n)) return fallback;
return Math.round(n);
}
async function refresh() {
setStatus(t("settings.loading"));
const data = await apiJson("/api/settings");
const s = data && typeof data === "object" ? data : {};
if (inputMissionRuns) inputMissionRuns.value = String(toInt(s.retention_mission_runs, 2000));
if (inputSystemLog) inputSystemLog.value = String(toInt(s.retention_system_log, 2000));
if (inputErrorLogs) inputErrorLogs.value = String(toInt(s.retention_error_logs, 200));
if (selectLogLevel) selectLogLevel.value = String(s.log_level || "info");
setStatus(t("settings.loaded"), "ok");
}
async function apply() {
const payload = {
retention_mission_runs: toInt(inputMissionRuns?.value, 2000),
retention_system_log: toInt(inputSystemLog?.value, 2000),
retention_error_logs: toInt(inputErrorLogs?.value, 200),
log_level: String(selectLogLevel?.value || "info"),
};
setStatus(t("settings.saving"));
const res = await apiJson("/api/settings", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const ok = res && typeof res === "object";
setStatus(ok ? t("settings.saved") : t("settings.saveFailed"), ok ? "ok" : "error");
await refresh();
}
function bind() {
renderGrid();
gridEl?.addEventListener("click", (evt) => {
const btn = evt.target.closest("[data-settings-tile]");
if (!btn?.dataset.settingsTile) return;
const tile = TILES.find((x) => x.id === btn.dataset.settingsTile);
if (!tile) return;
if (tile.action === "open_logging_retention") {
showDetail();
refresh().catch((e) => setStatus(e.message, "error"));
}
});
el("settingsRefreshBtn")?.addEventListener("click", () => refresh().catch((e) => setStatus(e.message, "error")));
el("settingsApplyBtn")?.addEventListener("click", () => apply().catch((e) => setStatus(e.message, "error")));
el("settingsBackBtn")?.addEventListener("click", () => showGrid());
}
function onPageShow() {
showGrid();
refresh().catch((e) => setStatus(e.message, "error"));
}
function onPageHide() {}
window.SettingsApp = { onPageShow, onPageHide, refresh };
function boot() {
bind();
}
if (window.AuthApp?.isReady()) boot();
else window.addEventListener("lm:auth-ready", boot, { once: true });
})();