v0.26.1
This commit is contained in:
1 parent
ddc5c6593f
commit
b9d137eee1
63 files changed
+1232
-445
No files matched your search
+95
-15
@@ -1,7 +1,28 @@
|
||||
(() => {
|
||||
let inventory = null, offset = 0, lastTotal = 0, busy = false;
|
||||
let currentEntries = [], selectedIndex = null;
|
||||
let preferencesLoaded = false, preferenceSaveRunning = false, preferenceSaveQueued = false;
|
||||
let searchTimer = null;
|
||||
|
||||
const columns = [
|
||||
{ id: "time", label: "time", value: entry => entryTime(entry) },
|
||||
{ id: "event", label: "fileLogsEvent", value: entry => entry.event || "—" },
|
||||
{ id: "status", label: "status", value: entry => entryStatus(entry) },
|
||||
{ id: "client_ip", label: "clientIp", value: entry => entry.client_ip || entry.source_ip || "—" },
|
||||
{ id: "vhost", label: "vhost", value: entry => entry.vhost || "—" },
|
||||
{ id: "transport", label: "logTransport", value: entry => entry.transport ? `${entry.transport}${entry.http_version ? ` / HTTP ${entry.http_version}` : ""}` : "—" },
|
||||
{ id: "matched_by", label: "fileLogsMatchedBy", value: entry => entry.matched_by || "—" },
|
||||
{ id: "sni", label: "fileLogsSni", value: entry => entry.sni || entry.tls_sni || "—" },
|
||||
{ id: "authority", label: "fileLogsAuthority", value: entry => entry.authority || entry.host || "—" },
|
||||
{ id: "record", label: "fileLogsRecord", value: entry => entrySummary(entry) },
|
||||
{ id: "source", label: "fileLogsSource", value: entry => entry._source || "—" },
|
||||
];
|
||||
const defaultColumnIds = ["time", "event", "status", "client_ip", "vhost", "record", "source"];
|
||||
let visibleColumns = new Set(defaultColumnIds);
|
||||
|
||||
const sourceSelect = () => $("file-log-source");
|
||||
const currentLimit = () => Math.max(1, Math.min(500, Number($("file-log-limit").value) || 100));
|
||||
|
||||
function sourceOption(source) {
|
||||
const label = `${source.label} · ${source.format} · ${bytes(source.size || 0)}${source.enabled ? ` · ${t("enabled")}` : ""}`;
|
||||
const option = el("option", label); option.value = source.id; return option;
|
||||
@@ -26,8 +47,9 @@
|
||||
return "—";
|
||||
}
|
||||
function entrySummary(entry) {
|
||||
if (entry.protocol_error) return entry.protocol_error;
|
||||
if (entry.method || entry.path) return `${entry.method || ""} ${entry.host || ""}${entry.path || ""}`.trim();
|
||||
if (entry.reason || entry.protocol_error) return entry.reason || entry.protocol_error;
|
||||
if (entry.method || entry.path) return `${entry.method || ""} ${entry.host || entry.authority || ""}${entry.path || ""}`.trim();
|
||||
if (entry.error || entry.error_detail) return entry.error || entry.error_detail;
|
||||
if (entry.message) return entry.message;
|
||||
if (entry.raw) return entry.raw;
|
||||
return entry.outcome || "—";
|
||||
@@ -36,19 +58,72 @@
|
||||
const status = Number(entry.status) || 0;
|
||||
return status ? String(status) : "—";
|
||||
}
|
||||
function renderRows(entries) {
|
||||
const rows = entries.map(entry => {
|
||||
const row = el("tr"); row.tabIndex = 0;
|
||||
for (const value of [entryTime(entry), entry.event || "—", entryStatus(entry), entry.client_ip || "—", entry.vhost || "—", entrySummary(entry), entry._source || "—"]) {
|
||||
function syncHeaders() {
|
||||
document.querySelectorAll("#file-logs-table-head [data-file-log-column]").forEach(header => {
|
||||
header.hidden = !visibleColumns.has(header.dataset.fileLogColumn);
|
||||
});
|
||||
}
|
||||
function syncColumnPicker() {
|
||||
const picker = $("file-log-column-options");
|
||||
picker.replaceChildren(...columns.map(column => {
|
||||
const label = el("label", undefined, "check log-column-option");
|
||||
const input = document.createElement("input"); input.type = "checkbox"; input.checked = visibleColumns.has(column.id); input.dataset.column = column.id;
|
||||
const text = el("span", t(column.label)); text.dataset.i18n = column.label;
|
||||
input.addEventListener("change", () => {
|
||||
if (!input.checked && visibleColumns.size === 1) { input.checked = true; toast(t("fileLogsColumnsRequired"), true); return; }
|
||||
if (input.checked) visibleColumns.add(column.id); else visibleColumns.delete(column.id);
|
||||
syncHeaders(); renderRows(); void savePreferences();
|
||||
});
|
||||
label.append(input, text); return label;
|
||||
}));
|
||||
}
|
||||
function applyPreferences(preferences) {
|
||||
const selected = Array.isArray(preferences?.file_log_columns)
|
||||
? preferences.file_log_columns.filter(id => columns.some(column => column.id === id))
|
||||
: [];
|
||||
visibleColumns = new Set(selected.length ? selected : defaultColumnIds);
|
||||
preferencesLoaded = true;
|
||||
syncColumnPicker(); syncHeaders(); renderRows();
|
||||
}
|
||||
async function savePreferences() {
|
||||
if (!authenticated || !preferencesLoaded) return;
|
||||
if (preferenceSaveRunning) { preferenceSaveQueued = true; return; }
|
||||
preferenceSaveRunning = true;
|
||||
try {
|
||||
do {
|
||||
preferenceSaveQueued = false;
|
||||
const selected = columns.filter(column => visibleColumns.has(column.id)).map(column => column.id);
|
||||
const preferences = await window.rustProxyPreferences.update({ file_log_columns: selected });
|
||||
if (!preferenceSaveQueued) applyPreferences(preferences);
|
||||
} while (preferenceSaveQueued);
|
||||
} catch (error) { toast(error.message, true); }
|
||||
finally { preferenceSaveRunning = false; }
|
||||
}
|
||||
function renderRows(entries = currentEntries) {
|
||||
currentEntries = entries;
|
||||
const nodes = [];
|
||||
entries.forEach((entry, index) => {
|
||||
const row = el("tr"); row.tabIndex = 0; row.classList.toggle("selected", selectedIndex === index);
|
||||
row.setAttribute("aria-expanded", selectedIndex === index ? "true" : "false");
|
||||
for (const column of columns) {
|
||||
if (!visibleColumns.has(column.id)) continue;
|
||||
const value = column.value(entry);
|
||||
const cell = el("td", String(value)); cell.title = String(value); row.append(cell);
|
||||
}
|
||||
const show = () => { $("file-log-detail").textContent = JSON.stringify(entry, null, 2); };
|
||||
row.addEventListener("click", show); row.addEventListener("keydown", event => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); show(); } });
|
||||
return row;
|
||||
const select = () => { selectedIndex = selectedIndex === index ? null : index; renderRows(); };
|
||||
row.addEventListener("click", select);
|
||||
row.addEventListener("keydown", event => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); select(); } });
|
||||
nodes.push(row);
|
||||
if (selectedIndex === index) {
|
||||
const detailRow = el("tr", undefined, "file-log-detail-row");
|
||||
const cell = el("td"); cell.colSpan = Math.max(1, visibleColumns.size);
|
||||
const heading = el("strong", t("fileLogsDetails")); heading.dataset.i18n = "fileLogsDetails";
|
||||
const pre = el("pre", JSON.stringify(entry, null, 2), "metrics-example");
|
||||
const detail = el("div", undefined, "file-log-inline-detail"); detail.append(heading, pre); cell.append(detail); detailRow.append(cell); nodes.push(detailRow);
|
||||
}
|
||||
});
|
||||
$("file-log-rows").replaceChildren(...rows);
|
||||
$("file-logs-empty").hidden = rows.length !== 0;
|
||||
if (!rows.length) $("file-log-detail").textContent = t("fileLogsSelectRecord");
|
||||
$("file-log-rows").replaceChildren(...nodes);
|
||||
$("file-logs-empty").hidden = entries.length !== 0;
|
||||
}
|
||||
function queryString() {
|
||||
const params = new URLSearchParams();
|
||||
@@ -66,7 +141,8 @@
|
||||
try {
|
||||
if (!inventory) await loadInventory();
|
||||
const value = await api(`/api/file-logs/query?${queryString()}`);
|
||||
lastTotal = Number(value.total) || 0; renderRows(Array.isArray(value.entries) ? value.entries : []);
|
||||
lastTotal = Number(value.total) || 0; selectedIndex = null;
|
||||
renderRows(Array.isArray(value.entries) ? value.entries : []);
|
||||
const from = lastTotal ? offset + 1 : 0, to = Math.min(offset + currentLimit(), lastTotal);
|
||||
$("file-log-summary").textContent = t("fileLogsSummary")
|
||||
.replace("{from}", fmt(from)).replace("{to}", fmt(to)).replace("{total}", fmt(lastTotal))
|
||||
@@ -78,14 +154,18 @@
|
||||
finally { busy = false; $("file-logs-refresh").disabled = false; }
|
||||
}
|
||||
function reset() {
|
||||
inventory = null; offset = 0; lastTotal = 0; $("file-log-rows").replaceChildren(); $("file-log-detail").textContent = "";
|
||||
inventory = null; offset = 0; lastTotal = 0; currentEntries = []; selectedIndex = null;
|
||||
preferencesLoaded = false; preferenceSaveRunning = false; preferenceSaveQueued = false;
|
||||
visibleColumns = new Set(defaultColumnIds); $("file-log-rows").replaceChildren(); syncColumnPicker(); syncHeaders();
|
||||
}
|
||||
$("file-logs-open").addEventListener("click", () => setPanel("filelogs"));
|
||||
$("settings-file-logs-open").addEventListener("click", () => setPanel("filelogs"));
|
||||
$("file-logs-refresh").addEventListener("click", () => refresh(true));
|
||||
$("file-logs-filter").addEventListener("submit", event => { event.preventDefault(); void refresh(true); });
|
||||
$("file-log-q").addEventListener("input", () => { clearTimeout(searchTimer); searchTimer = setTimeout(() => void refresh(true), 250); });
|
||||
$("file-logs-prev").addEventListener("click", () => { offset = Math.max(0, offset - currentLimit()); void refresh(); });
|
||||
$("file-logs-next").addEventListener("click", () => { if (offset + currentLimit() < lastTotal) { offset += currentLimit(); void refresh(); } });
|
||||
sourceSelect().addEventListener("change", () => refresh(true));
|
||||
window.rustProxyFileLogs = { refresh: () => refresh(false), reset, inventory: loadInventory };
|
||||
syncColumnPicker(); syncHeaders();
|
||||
window.rustProxyFileLogs = { refresh: () => refresh(false), reset, inventory: loadInventory, applyPreferences };
|
||||
})();
|
||||
Reference in new issue
Block a user