Improve device information UX
This commit is contained in:
+40
@@ -43,6 +43,18 @@ const els = {
|
||||
deviceName: document.querySelector("#deviceName"),
|
||||
deviceMeta: document.querySelector("#deviceMeta"),
|
||||
deviceBadge: document.querySelector("#deviceBadge"),
|
||||
informationStatus: document.querySelector("#informationStatus"),
|
||||
infoHostname: document.querySelector("#infoHostname"),
|
||||
infoModel: document.querySelector("#infoModel"),
|
||||
infoOpenWrt: document.querySelector("#infoOpenWrt"),
|
||||
infoTarget: document.querySelector("#infoTarget"),
|
||||
infoSystem: document.querySelector("#infoSystem"),
|
||||
infoKernel: document.querySelector("#infoKernel"),
|
||||
infoCreatedAt: document.querySelector("#infoCreatedAt"),
|
||||
infoLastSeenAt: document.querySelector("#infoLastSeenAt"),
|
||||
infoDeviceId: document.querySelector("#infoDeviceId"),
|
||||
infoBoardName: document.querySelector("#infoBoardName"),
|
||||
infoRootfs: document.querySelector("#infoRootfs"),
|
||||
healthSummary: document.querySelector("#healthSummary"),
|
||||
lastSeen: document.querySelector("#lastSeen"),
|
||||
loadAvg: document.querySelector("#loadAvg"),
|
||||
@@ -424,11 +436,30 @@ function renderDeviceDetail(device) {
|
||||
els.fleetGroup.value = device.group || "";
|
||||
els.fleetTags.value = Array.isArray(device.tags) ? device.tags.join(", ") : "";
|
||||
els.inventoryJson.textContent = JSON.stringify(device.inventory || {}, null, 2);
|
||||
renderDeviceInformation(device);
|
||||
renderHealthSummary(device);
|
||||
renderClients(device);
|
||||
renderInterfaceCounters(device);
|
||||
}
|
||||
|
||||
function renderDeviceInformation(device) {
|
||||
const board = device.inventory && device.inventory.board ? device.inventory.board : {};
|
||||
const release = board.release || {};
|
||||
els.informationStatus.textContent = device.online ? "На связи" : "Не на связи";
|
||||
els.informationStatus.className = `badge ${device.online ? "online" : "offline"}`;
|
||||
els.infoHostname.textContent = deviceDisplayName(device);
|
||||
els.infoModel.textContent = board.model || "-";
|
||||
els.infoOpenWrt.textContent = release.description || device.openwrt_version || "-";
|
||||
els.infoTarget.textContent = release.target || "-";
|
||||
els.infoSystem.textContent = board.system || "-";
|
||||
els.infoKernel.textContent = board.kernel || "-";
|
||||
els.infoCreatedAt.textContent = formatDate(device.created_at);
|
||||
els.infoLastSeenAt.textContent = formatDate(device.last_seen_at);
|
||||
els.infoDeviceId.textContent = device.id || "-";
|
||||
els.infoBoardName.textContent = board.board_name || "-";
|
||||
els.infoRootfs.textContent = board.rootfs_type || "-";
|
||||
}
|
||||
|
||||
function formatMemory(memory) {
|
||||
if (!memory || !memory.total_kb) return "-";
|
||||
return `${kbToMb(memory.used_kb)} / ${kbToMb(memory.total_kb)} MB`;
|
||||
@@ -1224,6 +1255,15 @@ els.copyCommandOutputBtn.addEventListener("click", async () => {
|
||||
setStatus("Output copied");
|
||||
});
|
||||
|
||||
for (const button of document.querySelectorAll(".copy-info-btn")) {
|
||||
button.addEventListener("click", async () => {
|
||||
const source = document.querySelector(`#${button.dataset.copyInfo}`);
|
||||
if (!source) return;
|
||||
await navigator.clipboard.writeText(source.textContent);
|
||||
setStatus("Значение скопировано");
|
||||
});
|
||||
}
|
||||
|
||||
for (const button of document.querySelectorAll(".preset-btn")) {
|
||||
button.addEventListener("click", () => {
|
||||
sendPresetCommand(button.dataset.preset, button.dataset.action).catch((error) => setStatus(error.message));
|
||||
|
||||
+87
-8
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>OpenWrt RMM</title>
|
||||
<link rel="stylesheet" href="/styles.css?v=4">
|
||||
<link rel="stylesheet" href="/styles.css?v=5">
|
||||
</head>
|
||||
<body>
|
||||
<main id="loginView" class="login-view">
|
||||
@@ -173,23 +173,95 @@
|
||||
<div id="healthSummary" class="health-summary"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel device-information-panel" data-device-tab="information">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>Сведения об устройстве</h2>
|
||||
<p class="section-subtitle">Основная информация, полученная от роутера</p>
|
||||
</div>
|
||||
<span id="informationStatus" class="badge">-</span>
|
||||
</div>
|
||||
<div class="information-grid">
|
||||
<div class="information-item">
|
||||
<span>Имя устройства</span>
|
||||
<strong id="infoHostname">-</strong>
|
||||
</div>
|
||||
<div class="information-item">
|
||||
<span>Модель</span>
|
||||
<strong id="infoModel">-</strong>
|
||||
</div>
|
||||
<div class="information-item">
|
||||
<span>Версия OpenWrt</span>
|
||||
<strong id="infoOpenWrt">-</strong>
|
||||
</div>
|
||||
<div class="information-item">
|
||||
<span>Платформа</span>
|
||||
<strong id="infoTarget">-</strong>
|
||||
</div>
|
||||
<div class="information-item">
|
||||
<span>Система</span>
|
||||
<strong id="infoSystem">-</strong>
|
||||
</div>
|
||||
<div class="information-item">
|
||||
<span>Версия ядра</span>
|
||||
<strong id="infoKernel">-</strong>
|
||||
</div>
|
||||
<div class="information-item">
|
||||
<span>Зарегистрирован</span>
|
||||
<strong id="infoCreatedAt">-</strong>
|
||||
</div>
|
||||
<div class="information-item">
|
||||
<span>Последний контакт</span>
|
||||
<strong id="infoLastSeenAt">-</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel" data-device-tab="information">
|
||||
<div class="section-title">
|
||||
<h2>Группа и теги</h2>
|
||||
<button id="saveFleetBtn" type="button">Сохранить</button>
|
||||
<div>
|
||||
<h2>Организация</h2>
|
||||
<p class="section-subtitle">Группа и теги помогают находить и фильтровать объекты</p>
|
||||
</div>
|
||||
<button id="saveFleetBtn" class="primary" type="button">Сохранить</button>
|
||||
</div>
|
||||
<div class="fleet-form">
|
||||
<label>
|
||||
Group
|
||||
<input id="fleetGroup" type="text" placeholder="core, lab, home">
|
||||
Группа
|
||||
<input id="fleetGroup" type="text" placeholder="Офис, дом, филиал">
|
||||
</label>
|
||||
<label>
|
||||
Tags
|
||||
<input id="fleetTags" type="text" placeholder="edge, vpn, test">
|
||||
Теги через запятую
|
||||
<input id="fleetTags" type="text" placeholder="VPN, тестовый, резервный">
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel" data-device-tab="information">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>Технические реквизиты</h2>
|
||||
<p class="section-subtitle">Используются при диагностике и обращении в поддержку</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="technical-details">
|
||||
<div>
|
||||
<span>ID устройства</span>
|
||||
<code id="infoDeviceId">-</code>
|
||||
<button class="copy-info-btn" data-copy-info="infoDeviceId" type="button">Копировать</button>
|
||||
</div>
|
||||
<div>
|
||||
<span>Board name</span>
|
||||
<code id="infoBoardName">-</code>
|
||||
<button class="copy-info-btn" data-copy-info="infoBoardName" type="button">Копировать</button>
|
||||
</div>
|
||||
<div>
|
||||
<span>Root filesystem</span>
|
||||
<code id="infoRootfs">-</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="metrics-grid" data-device-tab="overview">
|
||||
<div class="metric">
|
||||
<span>Последний контакт</span>
|
||||
@@ -229,6 +301,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="expert-notice" data-device-tab="expert">
|
||||
<div>
|
||||
<strong>Экспертный режим</strong>
|
||||
<span>Здесь находятся низкоуровневые команды и настройки OpenWrt. Ошибочное изменение может нарушить связь с роутером.</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="two-column" data-device-tab="expert">
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
@@ -552,6 +631,6 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/app.js?v=11" defer></script>
|
||||
<script src="/app.js?v=12" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+111
-1
@@ -1134,6 +1134,100 @@ select {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.information-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.information-item {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-height: 82px;
|
||||
border-right: 1px solid var(--line);
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: var(--surface-raised);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.information-item:nth-child(4n) {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.information-item:nth-last-child(-n + 4) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.information-item span,
|
||||
.technical-details span {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.information-item strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.technical-details {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.technical-details > div {
|
||||
display: grid;
|
||||
grid-template-columns: 160px minmax(0, 1fr) auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
min-height: 48px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.technical-details > div:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.technical-details code {
|
||||
overflow: hidden;
|
||||
color: var(--text);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.copy-info-btn {
|
||||
min-height: 30px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.expert-notice {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
border: 1px solid #715d31;
|
||||
border-radius: 8px;
|
||||
background: #342e22;
|
||||
margin-bottom: 14px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.expert-notice div {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.expert-notice strong {
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
.expert-notice span {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.metric {
|
||||
min-width: 0;
|
||||
border: 1px solid var(--line);
|
||||
@@ -1400,7 +1494,23 @@ select {
|
||||
}
|
||||
|
||||
.client-toolbar,
|
||||
.network-health {
|
||||
.network-health,
|
||||
.information-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.information-item,
|
||||
.information-item:nth-child(4n),
|
||||
.information-item:nth-last-child(-n + 4) {
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.information-item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.technical-details > div {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user