162 lines
5.2 KiB
JavaScript
Executable file
162 lines
5.2 KiB
JavaScript
Executable file
import { requireAuthOrRedirect, logout } from "./auth.js";
|
|
import { apiFetch } from "./api.js";
|
|
import { loadSession } from "./storage.js";
|
|
import { CONFIG } from "./config.js";
|
|
|
|
if (!requireAuthOrRedirect()) {
|
|
// редирект уже сделан
|
|
}
|
|
|
|
const userNameEl = document.getElementById("userName");
|
|
const logoutBtn = document.getElementById("logoutBtn");
|
|
const reloadBtn = document.getElementById("reloadBtn");
|
|
const statusEl = document.getElementById("status");
|
|
const tbody = document.getElementById("tbody");
|
|
const errorBox = document.getElementById("errorBox");
|
|
const searchEl = document.getElementById("search");
|
|
|
|
let rows = [];
|
|
|
|
function showError(text) {
|
|
errorBox.textContent = text;
|
|
errorBox.classList.remove("hidden");
|
|
}
|
|
|
|
function clearError() {
|
|
errorBox.textContent = "";
|
|
errorBox.classList.add("hidden");
|
|
}
|
|
|
|
function setStatus(text) {
|
|
statusEl.textContent = text;
|
|
}
|
|
|
|
function normalizeCell(v) {
|
|
if (v === null || v === undefined) return "";
|
|
return String(v);
|
|
}
|
|
|
|
function isWireguardLike(text) {
|
|
const t = String(text || "");
|
|
return t.includes("[Interface]") || t.includes("[Peer]") || t.includes("PrivateKey") || t.includes("AllowedIPs");
|
|
}
|
|
|
|
async function copyToClipboard(text) {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
} catch {
|
|
// fallback
|
|
const ta = document.createElement("textarea");
|
|
ta.value = text;
|
|
document.body.appendChild(ta);
|
|
ta.select();
|
|
document.execCommand("copy");
|
|
ta.remove();
|
|
}
|
|
}
|
|
|
|
function renderTable(filter = "") {
|
|
const q = filter.trim().toLowerCase();
|
|
const filtered = !q ? rows : rows.filter(r => r.searchBlob.includes(q));
|
|
|
|
tbody.innerHTML = "";
|
|
for (const r of filtered) {
|
|
const tr = document.createElement("tr");
|
|
|
|
const commentPreview = r.comments.length > 180 ? (r.comments.slice(0, 180) + "…") : r.comments;
|
|
|
|
tr.innerHTML = `
|
|
<td>${r.id}</td>
|
|
<td><span class="badge">${escapeHtml(r.name)}</span></td>
|
|
<td>${escapeHtml(r.status)}</td>
|
|
<td>${escapeHtml(r.ownerName)} (#${r.ownerId})</td>
|
|
<td>${escapeHtml(r.targetName)} (#${r.targetId})</td>
|
|
<td><div class="pre">${escapeHtml(commentPreview)}</div></td>
|
|
<td>${escapeHtml(r.inventoryId)}</td>
|
|
<td></td>
|
|
`;
|
|
|
|
const actionTd = tr.lastElementChild;
|
|
|
|
if (isWireguardLike(r.comments)) {
|
|
const btn = document.createElement("button");
|
|
btn.className = "btn btn-ghost btn-sm";
|
|
btn.textContent = "Копировать WG";
|
|
btn.addEventListener("click", async () => {
|
|
await copyToClipboard(r.comments);
|
|
setStatus("Скопировано.");
|
|
setTimeout(() => setStatus(""), 1200);
|
|
});
|
|
actionTd.appendChild(btn);
|
|
}
|
|
|
|
tbody.appendChild(tr);
|
|
}
|
|
}
|
|
|
|
function escapeHtml(s) {
|
|
return String(s)
|
|
.replaceAll("&", "&")
|
|
.replaceAll("<", "<")
|
|
.replaceAll(">", ">")
|
|
.replaceAll('"', """)
|
|
.replaceAll("'", "'");
|
|
}
|
|
|
|
async function loadData() {
|
|
clearError();
|
|
setStatus("Загрузка…");
|
|
|
|
try {
|
|
// /ls/ возвращает {"ls": bd_module.ls_item(uid)} :contentReference[oaicite:7]{index=7}
|
|
// Формат строк из БД: [item_id,item_name,status,user_id_1,user_name1,user_id_2,user_name2,icon,comments,inventory_id] :contentReference[oaicite:8]{index=8}
|
|
const data = await apiFetch(CONFIG.ENDPOINTS.list, { method: "GET", auth: true });
|
|
|
|
const list = Array.isArray(data?.ls) ? data.ls : [];
|
|
rows = list.map((a) => {
|
|
const id = normalizeCell(a?.[0]);
|
|
const name = normalizeCell(a?.[1]);
|
|
const status = normalizeCell(a?.[2]);
|
|
const ownerId = normalizeCell(a?.[3]);
|
|
const ownerName = normalizeCell(a?.[4]);
|
|
const targetId = normalizeCell(a?.[5]);
|
|
const targetName = normalizeCell(a?.[6]);
|
|
const comments = normalizeCell(a?.[8]);
|
|
const inventoryId = normalizeCell(a?.[9]);
|
|
|
|
const searchBlob = [
|
|
id, name, status, ownerId, ownerName, targetId, targetName, comments, inventoryId
|
|
].join(" ").toLowerCase();
|
|
|
|
return { id, name, status, ownerId, ownerName, targetId, targetName, comments, inventoryId, searchBlob };
|
|
});
|
|
|
|
renderTable(searchEl.value);
|
|
setStatus(`Готово. Строк: ${rows.length}`);
|
|
} catch (err) {
|
|
if (err?.status === CONFIG.AUTH_ERROR_STATUS) {
|
|
// если даже renew не помог — сессия умерла
|
|
await logout();
|
|
location.replace("./login.html");
|
|
return;
|
|
}
|
|
showError("Ошибка загрузки данных.");
|
|
setStatus("");
|
|
}
|
|
}
|
|
|
|
(function init() {
|
|
const s = loadSession();
|
|
userNameEl.textContent = s?.user_name || `#${s?.user_id || "—"}`;
|
|
|
|
logoutBtn.addEventListener("click", async () => {
|
|
await logout();
|
|
location.replace("./index.html");
|
|
});
|
|
|
|
reloadBtn.addEventListener("click", loadData);
|
|
|
|
searchEl.addEventListener("input", () => renderTable(searchEl.value));
|
|
|
|
loadData();
|
|
})();
|