Feature: отдельная страница data.html, общие css и upload-core.js
Made-with: Cursor
This commit is contained in:
210
data.html
Normal file
210
data.html
Normal file
@@ -0,0 +1,210 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Источник данных — ServiceDesk Monitor</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@300;400;500;600&display=swap" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
|
||||
<link rel="stylesheet" href="css/dashboard.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="topbar">
|
||||
<div class="topbar-brand">
|
||||
<div class="brand-hex" aria-hidden="true"></div>
|
||||
<div>
|
||||
<div class="brand-text-main">ServiceDesk Monitor</div>
|
||||
<div class="brand-text-sub">OMC · Ситуационный центр</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="topbar-nav" aria-label="Разделы">
|
||||
<a href="index.html" class="nav-link">Мониторинг</a>
|
||||
<a href="data.html" class="nav-link active">Источник данных</a>
|
||||
</nav>
|
||||
<div class="topbar-seg">
|
||||
<span class="seg-label">Статус</span>
|
||||
<span class="seg-value"><span class="status-pip pip-green" aria-hidden="true"></span> АКТИВЕН</span>
|
||||
</div>
|
||||
<div class="topbar-seg">
|
||||
<span class="seg-label">В базе</span>
|
||||
<span class="seg-value" id="statusbar-count">—</span>
|
||||
</div>
|
||||
<div class="topbar-seg">
|
||||
<span class="seg-label">Дата системы</span>
|
||||
<span class="seg-value" id="current-date-display">—</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="section-head">
|
||||
<span class="section-head-label">Подсистема</span>
|
||||
<span class="section-head-title">Источник данных</span>
|
||||
<span class="section-head-rule"></span>
|
||||
<span class="section-head-info">CSV · JSON · Excel → PostgreSQL, ключ «Название»</span>
|
||||
</div>
|
||||
|
||||
<main class="main">
|
||||
<div class="data-stats-row">
|
||||
<div class="data-stat-card">
|
||||
<div class="ds-val" id="db-count">—</div>
|
||||
<div class="ds-lbl">Объектов в базе</div>
|
||||
<div class="ds-sub" id="db-count-sub">GET /api/incidents</div>
|
||||
</div>
|
||||
<div class="data-stat-card">
|
||||
<div class="ds-val" id="last-applied">—</div>
|
||||
<div class="ds-lbl">Сохранено при последней загрузке</div>
|
||||
<div class="ds-sub" id="last-upload-sub"></div>
|
||||
</div>
|
||||
<div class="data-stat-card">
|
||||
<div class="ds-val ds-warn" id="last-skipped">—</div>
|
||||
<div class="ds-lbl">Пропущено (нет «Название»)</div>
|
||||
<div class="ds-sub" id="last-total">Строк в файле: —</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="upload-panel">
|
||||
<span class="lbl">Загрузка файла</span>
|
||||
<button type="button" class="btn-upload" id="btn-upload">↑ Выбрать файл</button>
|
||||
<span class="upload-hint" id="upload-hint">CSV, JSON или Excel — те же колонки, что в экспорте ServiceDesk</span>
|
||||
<input type="file" id="file-input" accept=".csv,.json,.xlsx,.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel" hidden />
|
||||
</div>
|
||||
|
||||
<div id="success" class="msg msg-ok"></div>
|
||||
<div id="loading" class="msg msg-load"></div>
|
||||
<div id="error" class="msg msg-err"></div>
|
||||
|
||||
<div class="data-info-panel">
|
||||
<strong>Справка</strong>
|
||||
После разбора файл отправляется в API; для каждой строки ключ — целое число из поля «Название»: существующая запись обновляется, новая добавляется.
|
||||
Откройте <a href="index.html" style="color:var(--sky);font-weight:600;">мониторинг</a> для графиков и таблицы.
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/upload-core.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var el = function (id) { return document.getElementById(id); };
|
||||
|
||||
function pad(n) { return n < 10 ? "0" + n : String(n); }
|
||||
function formatSystemDate() {
|
||||
var d = new Date();
|
||||
return pad(d.getDate()) + "." + pad(d.getMonth() + 1) + "." + d.getFullYear() + " " + pad(d.getHours()) + ":" + pad(d.getMinutes());
|
||||
}
|
||||
|
||||
function refreshDbCount() {
|
||||
if (!window.OmcUpload) return;
|
||||
OmcUpload.fetchIncidents()
|
||||
.then(function (j) {
|
||||
if (!j || !j.rows) {
|
||||
el("db-count").textContent = "—";
|
||||
el("statusbar-count").textContent = "—";
|
||||
return;
|
||||
}
|
||||
var n = j.rows.length;
|
||||
el("db-count").textContent = n;
|
||||
el("statusbar-count").textContent = String(n);
|
||||
})
|
||||
.catch(function () {
|
||||
el("db-count").textContent = "—";
|
||||
el("statusbar-count").textContent = "—";
|
||||
});
|
||||
}
|
||||
|
||||
function readLastUpload() {
|
||||
try {
|
||||
var s = sessionStorage.getItem("omc_last_upload");
|
||||
if (!s) return null;
|
||||
return JSON.parse(s);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function showLastUpload() {
|
||||
var x = readLastUpload();
|
||||
if (!x) {
|
||||
el("last-applied").textContent = "—";
|
||||
el("last-skipped").textContent = "—";
|
||||
el("last-total").textContent = "Строк в файле: —";
|
||||
el("last-upload-sub").textContent = "";
|
||||
return;
|
||||
}
|
||||
el("last-applied").textContent = x.applied != null ? x.applied : "—";
|
||||
el("last-skipped").textContent = x.skipped != null ? x.skipped : "0";
|
||||
el("last-total").textContent = "Строк в файле: " + (x.total != null ? x.total : "—");
|
||||
if (x.at) {
|
||||
var d = new Date(x.at);
|
||||
el("last-upload-sub").textContent =
|
||||
"Время: " + pad(d.getDate()) + "." + pad(d.getMonth() + 1) + "." + d.getFullYear() + " " + pad(d.getHours()) + ":" + pad(d.getMinutes());
|
||||
}
|
||||
}
|
||||
|
||||
function handleFile(file) {
|
||||
if (!file) return;
|
||||
el("upload-hint").textContent = file.name;
|
||||
el("success").classList.remove("show");
|
||||
el("error").classList.remove("show");
|
||||
el("loading").classList.add("show");
|
||||
el("loading").textContent = "Разбор файла…";
|
||||
|
||||
OmcUpload.parseFileToRows(file)
|
||||
.then(function (rows) {
|
||||
if (!rows.length) throw new Error("Нет строк данных");
|
||||
el("loading").textContent = "Сохранение в базу данных…";
|
||||
return OmcUpload.syncRowsToServer(rows).then(function (j) {
|
||||
return { j: j, rows: rows };
|
||||
});
|
||||
})
|
||||
.then(function (pack) {
|
||||
var j = pack.j;
|
||||
var rows = pack.rows;
|
||||
sessionStorage.setItem(
|
||||
"omc_last_upload",
|
||||
JSON.stringify({
|
||||
applied: j.applied,
|
||||
skipped: j.skipped,
|
||||
total: rows.length,
|
||||
at: new Date().toISOString()
|
||||
})
|
||||
);
|
||||
refreshDbCount();
|
||||
showLastUpload();
|
||||
el("loading").classList.remove("show");
|
||||
el("error").classList.remove("show");
|
||||
el("success").classList.add("show");
|
||||
el("success").textContent =
|
||||
"✓ В базе сохранено: " + j.applied + " из " + rows.length + " строк" +
|
||||
(j.skipped ? " (пропущено без «Название»: " + j.skipped + ")" : "");
|
||||
})
|
||||
.catch(function (err) {
|
||||
el("loading").classList.remove("show");
|
||||
el("success").classList.remove("show");
|
||||
el("error").classList.add("show");
|
||||
el("error").textContent = "✗ " + (err && err.message ? err.message : String(err));
|
||||
});
|
||||
}
|
||||
|
||||
el("btn-upload").addEventListener("click", function () {
|
||||
el("file-input").click();
|
||||
});
|
||||
el("file-input").addEventListener("change", function () {
|
||||
var f = el("file-input").files[0];
|
||||
if (f) handleFile(f);
|
||||
});
|
||||
|
||||
function tickClock() {
|
||||
el("current-date-display").textContent = formatSystemDate();
|
||||
}
|
||||
tickClock();
|
||||
setInterval(tickClock, 30000);
|
||||
|
||||
refreshDbCount();
|
||||
showLastUpload();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user