Grotti 2.0.0: eigenes Repo, Umbenennung von Farm.
This commit is contained in:
commit
b2e6443c68
69 files changed
+18719
No files matched your search
@@ -0,0 +1,183 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Aktivität</h1>
|
||||
<p class="tiny">Ereignisse von Grotti und den Druckern. Neueste oben.</p>
|
||||
</div>
|
||||
<div class="page-head-side">
|
||||
<button type="button" class="ghost" id="tl-refresh"><svg class="ico"><use href="#i-refresh"/></svg>Neu laden</button>
|
||||
<button type="button" id="tl-read"><svg class="ico"><use href="#i-check"/></svg>Alle gelesen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-bar" id="f-range" role="group" aria-label="Zeitraum">
|
||||
<button type="button" class="fchip on" data-range="day">Heute</button>
|
||||
<button type="button" class="fchip" data-range="week">7 Tage</button>
|
||||
<button type="button" class="fchip" data-range="all">Alle</button>
|
||||
</div>
|
||||
<div class="filter-bar" id="f-kind" role="group" aria-label="Art"></div>
|
||||
<div class="filter-bar" id="f-host" role="group" aria-label="Drucker"></div>
|
||||
|
||||
<p id="tl-note" class="tiny"></p>
|
||||
<div class="timeline" id="timeline">
|
||||
<div class="skeleton" style="height:4rem"></div>
|
||||
<div class="skeleton" style="height:4rem"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script>
|
||||
const KINDS = [
|
||||
["queue", "Warteschlange"],
|
||||
["ablauf", "Ablauf"],
|
||||
["start", "Start / Pause"],
|
||||
["fertig", "Fertig"],
|
||||
["fehler", "Fehler"],
|
||||
["abbruch", "Abbruch"],
|
||||
["offline", "Offline"],
|
||||
["spule", "Spule"],
|
||||
];
|
||||
const KIND_LABEL = Object.fromEntries(KINDS);
|
||||
const params = new URLSearchParams(location.search);
|
||||
const filter = {
|
||||
range: params.get("range") || (params.get("host") ? "week" : "day"),
|
||||
kinds: new Set((params.get("kind") || "").split(",").filter(Boolean)),
|
||||
host: params.get("host") || "",
|
||||
};
|
||||
let printers = [];
|
||||
|
||||
function syncUrl() {
|
||||
const q = new URLSearchParams();
|
||||
if (filter.range !== "day") q.set("range", filter.range);
|
||||
if (filter.kinds.size) q.set("kind", [...filter.kinds].join(","));
|
||||
if (filter.host) q.set("host", filter.host);
|
||||
const s = q.toString();
|
||||
history.replaceState(null, "", s ? "?" + s : location.pathname);
|
||||
}
|
||||
|
||||
function renderFilters() {
|
||||
document.querySelectorAll("#f-range .fchip").forEach((b) => b.classList.toggle("on", b.dataset.range === filter.range));
|
||||
document.getElementById("f-kind").innerHTML =
|
||||
`<button type="button" class="fchip ${filter.kinds.size ? "" : "on"}" data-kind="">Alle Arten</button>` +
|
||||
KINDS.map(([k, label]) =>
|
||||
`<button type="button" class="fchip ${filter.kinds.has(k) ? "on" : ""}" data-kind="${k}"><span class="tl-dot ${k}"></span>${label}</button>`
|
||||
).join("");
|
||||
document.getElementById("f-host").innerHTML =
|
||||
`<button type="button" class="fchip ${filter.host ? "" : "on"}" data-host="">Alle Drucker</button>` +
|
||||
printers.map((p) =>
|
||||
`<button type="button" class="fchip ${filter.host === p.host ? "on" : ""}" data-host="${escapeHtml(p.host)}">${escapeHtml(p.name)}</button>`
|
||||
).join("");
|
||||
}
|
||||
|
||||
function sinceIso() {
|
||||
if (filter.range === "all") return "";
|
||||
const d = new Date();
|
||||
if (filter.range === "day") d.setHours(0, 0, 0, 0);
|
||||
else d.setDate(d.getDate() - 7);
|
||||
return d.toISOString();
|
||||
}
|
||||
|
||||
function dayLabel(date) {
|
||||
const today = new Date(); today.setHours(0, 0, 0, 0);
|
||||
const that = new Date(date); that.setHours(0, 0, 0, 0);
|
||||
const diff = Math.round((today - that) / 86400000);
|
||||
if (diff === 0) return "Heute";
|
||||
if (diff === 1) return "Gestern";
|
||||
return that.toLocaleDateString("de-DE", { weekday: "long", day: "numeric", month: "long" });
|
||||
}
|
||||
|
||||
function itemHtml(ev) {
|
||||
const t = new Date(ev.created_at);
|
||||
const time = t.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
|
||||
let msg = ev.message || "";
|
||||
if (ev.printer && msg.startsWith(ev.printer)) msg = msg.slice(ev.printer.length).replace(/^[\s:·—-]+/, "");
|
||||
const who = ev.printer
|
||||
? `<a href="/#${encodeURIComponent(ev.host)}"><b>${escapeHtml(ev.printer)}</b></a>`
|
||||
: `<b>Grotti</b>`;
|
||||
const file = ev.filename && !msg.includes(ev.filename)
|
||||
? `<div class="tl-file">${icon("file", "ico-sm")} ${escapeHtml(ev.filename)}</div>` : "";
|
||||
return `<li class="tl-item ${ev.unread ? "unread" : ""}">
|
||||
<span class="tl-time" title="${escapeHtml(t.toLocaleString("de-DE"))}">${time}</span>
|
||||
<span class="tl-dot ${escapeHtml(ev.kind)}"></span>
|
||||
<div class="tl-body">
|
||||
<div class="tl-head">${who}<span class="tl-kind">${escapeHtml(KIND_LABEL[ev.kind] || ev.kind)}</span></div>
|
||||
<div class="tl-msg">${escapeHtml(msg)}</div>${file}
|
||||
</div>
|
||||
</li>`;
|
||||
}
|
||||
|
||||
function render(items) {
|
||||
const box = document.getElementById("timeline");
|
||||
const note = document.getElementById("tl-note");
|
||||
if (!items.length) {
|
||||
box.innerHTML = `<div class="card empty-state">${icon("activity")}<p>Keine Einträge${filter.range === "day" ? " heute" : ""}.</p>${
|
||||
filter.range !== "all" ? `<button type="button" class="ghost" data-range-jump="all">Alle anzeigen</button>` : ""}</div>`;
|
||||
note.textContent = "";
|
||||
return;
|
||||
}
|
||||
const groups = [];
|
||||
for (const ev of items) {
|
||||
const label = dayLabel(ev.created_at);
|
||||
if (!groups.length || groups[groups.length - 1].label !== label) groups.push({ label, items: [] });
|
||||
groups[groups.length - 1].items.push(ev);
|
||||
}
|
||||
box.innerHTML = groups.map((g) =>
|
||||
`<section class="tl-day"><h3>${escapeHtml(g.label)} · ${g.items.length}</h3><ol class="tl-list">${g.items.map(itemHtml).join("")}</ol></section>`
|
||||
).join("");
|
||||
note.textContent = items.length >= 300 ? "Die letzten 300 Einträge. Zeitraum oder Filter enger stellen für ältere." : "";
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const q = new URLSearchParams({ limit: "300" });
|
||||
const since = sinceIso();
|
||||
if (since) q.set("since", since);
|
||||
if (filter.kinds.size) q.set("kind", [...filter.kinds].join(","));
|
||||
if (filter.host) q.set("host", filter.host);
|
||||
try {
|
||||
const data = await api("/ereignisse?" + q.toString());
|
||||
render(data.items || []);
|
||||
} catch (err) {
|
||||
document.getElementById("timeline").innerHTML = `<div class="strip bad">${icon("alert")}<span>${escapeHtml(err.message)}</span></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
const range = e.target.closest("[data-range], [data-range-jump]");
|
||||
if (range) {
|
||||
filter.range = range.dataset.range || range.dataset.rangeJump;
|
||||
} else if (e.target.closest("#f-kind [data-kind]")) {
|
||||
const k = e.target.closest("[data-kind]").dataset.kind;
|
||||
if (!k) filter.kinds.clear();
|
||||
else if (filter.kinds.has(k)) filter.kinds.delete(k);
|
||||
else filter.kinds.add(k);
|
||||
} else if (e.target.closest("#f-host [data-host]")) {
|
||||
filter.host = e.target.closest("[data-host]").dataset.host;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
syncUrl();
|
||||
renderFilters();
|
||||
load();
|
||||
});
|
||||
|
||||
document.getElementById("tl-refresh").addEventListener("click", load);
|
||||
document.getElementById("tl-read").addEventListener("click", async () => {
|
||||
await api("/ereignisse/gelesen", { method: "POST", json: {} });
|
||||
toast("Alles als gelesen markiert.", "good");
|
||||
if (typeof refreshEvents === "function") refreshEvents();
|
||||
load();
|
||||
});
|
||||
window.onEventsRead = load;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
printers = await api("/drucker?live=0");
|
||||
} catch (_) {
|
||||
printers = [];
|
||||
}
|
||||
renderFilters();
|
||||
load();
|
||||
setInterval(load, 15000);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user