211 lines
8.3 KiB
HTML
211 lines
8.3 KiB
HTML
<!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>
|