Files
omc-servicedesk-monitor/data.html

211 lines
8.3 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>