fix
This commit is contained in:
parent
10badd4d52
commit
22d9f275f2
1 changed files with 84 additions and 43 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
import { requireAuthOrRedirect, logout } from "./auth.js";
|
import { requireAuthOrRedirect, logout } from "./auth.js";
|
||||||
import { apiFetch } from "./api.js";
|
import { apiFetch } from "./api.js";
|
||||||
import { loadSession } from "./storage.js";
|
|
||||||
import { CONFIG } from "./config.js";
|
import { CONFIG } from "./config.js";
|
||||||
|
import { loadSession, saveSession, clearSession } from "./storage.js";
|
||||||
|
|
||||||
if (!requireAuthOrRedirect()) {
|
if (!requireAuthOrRedirect()) {
|
||||||
// редирект уже сделан
|
// редирект уже сделан
|
||||||
|
|
@ -63,32 +63,34 @@ function renderTable(filter = "") {
|
||||||
for (const r of filtered) {
|
for (const r of filtered) {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
|
|
||||||
const commentPreview = r.comments.length > 180 ? (r.comments.slice(0, 180) + "…") : r.comments;
|
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>${r.id}</td>
|
<td>${escapeHtml(r.id)}</td>
|
||||||
<td><span class="badge">${escapeHtml(r.name)}</span></td>
|
<td>${escapeHtml(r.name)}</td>
|
||||||
<td>${escapeHtml(r.status)}</td>
|
<td>${escapeHtml(r.status)}</td>
|
||||||
<td>${escapeHtml(r.ownerName)} (#${r.ownerId})</td>
|
<td>${escapeHtml(r.ipLocal)}</td>
|
||||||
<td>${escapeHtml(r.targetName)} (#${r.targetId})</td>
|
<td>${escapeHtml(r.ipServer)}</td>
|
||||||
<td><div class="pre">${escapeHtml(commentPreview)}</div></td>
|
|
||||||
<td>${escapeHtml(r.inventoryId)}</td>
|
|
||||||
<td></td>
|
<td></td>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const actionTd = tr.lastElementChild;
|
const tdBtn = tr.lastElementChild;
|
||||||
|
const btn = document.createElement("button");
|
||||||
if (isWireguardLike(r.comments)) {
|
btn.className = "btn btn-ghost btn-sm";
|
||||||
const btn = document.createElement("button");
|
btn.textContent = "Скачать";
|
||||||
btn.className = "btn btn-ghost btn-sm";
|
btn.addEventListener("click", async () => {
|
||||||
btn.textContent = "Копировать WG";
|
clearError();
|
||||||
btn.addEventListener("click", async () => {
|
try {
|
||||||
await copyToClipboard(r.comments);
|
await triggerDownloadById(r.id);
|
||||||
setStatus("Скопировано.");
|
} catch (err) {
|
||||||
setTimeout(() => setStatus(""), 1200);
|
if (err?.status === CONFIG.AUTH_ERROR_STATUS) {
|
||||||
});
|
await logout();
|
||||||
actionTd.appendChild(btn);
|
location.replace("./login.html");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
showError("Не удалось скачать конфиг.");
|
||||||
|
setStatus("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tdBtn.appendChild(btn);
|
||||||
|
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
}
|
}
|
||||||
|
|
@ -108,28 +110,15 @@ async function loadData() {
|
||||||
setStatus("Загрузка…");
|
setStatus("Загрузка…");
|
||||||
|
|
||||||
try {
|
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 data = await apiFetch(CONFIG.ENDPOINTS.list, { method: "GET", auth: true });
|
||||||
|
const list = Array.isArray(data?.ls) ? data.ls : Array.isArray(data) ? data : [];
|
||||||
const list = Array.isArray(data?.ls) ? data.ls : [];
|
rows = list
|
||||||
rows = list.map((a) => {
|
.map(mapLsItem)
|
||||||
const id = normalizeCell(a?.[0]);
|
.filter(Boolean)
|
||||||
const name = normalizeCell(a?.[1]);
|
.map(r => ({
|
||||||
const status = normalizeCell(a?.[2]);
|
...r,
|
||||||
const ownerId = normalizeCell(a?.[3]);
|
searchBlob: [r.id, r.name, r.status, r.ipLocal, r.ipServer].join(" ").toLowerCase()
|
||||||
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);
|
renderTable(searchEl.value);
|
||||||
setStatus(`Готово. Строк: ${rows.length}`);
|
setStatus(`Готово. Строк: ${rows.length}`);
|
||||||
|
|
@ -160,3 +149,55 @@ async function loadData() {
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
})();
|
})();
|
||||||
|
function filenameFromDisposition(cd) {
|
||||||
|
if (!cd) return null;
|
||||||
|
|
||||||
|
// filename*=UTF-8''...
|
||||||
|
let m = cd.match(/filename\*\s*=\s*UTF-8''([^;]+)/i);
|
||||||
|
if (m?.[1]) return decodeURIComponent(m[1].replace(/"/g, "").trim());
|
||||||
|
|
||||||
|
// filename="..."
|
||||||
|
m = cd.match(/filename\s*=\s*"?([^";]+)"?/i);
|
||||||
|
if (m?.[1]) return m[1].trim();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiFetchBlob(path, { method = "GET", headers = {}, auth = true } = {}) {
|
||||||
|
const s = loadSession();
|
||||||
|
const reqHeaders = { ...headers };
|
||||||
|
|
||||||
|
if (auth) {
|
||||||
|
if (!s?.short_token) throw new Error("NO_AUTH");
|
||||||
|
reqHeaders["short_token"] = String(s.short_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doRequest = async () => {
|
||||||
|
return await fetch(path.startsWith("http") ? path : (CONFIG.API_BASE_URL ? CONFIG.API_BASE_URL.replace(/\/+$/, "") + path : path), {
|
||||||
|
method,
|
||||||
|
headers: reqHeaders
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = await doRequest();
|
||||||
|
|
||||||
|
if (auth && res.status === CONFIG.AUTH_ERROR_STATUS) {
|
||||||
|
// renew + retry один раз
|
||||||
|
await (await import("./api.js")).renewTokens?.(); // на случай циклического импорта (если будет) — безопасно
|
||||||
|
const s2 = loadSession();
|
||||||
|
reqHeaders["short_token"] = String(s2.short_token);
|
||||||
|
res = await doRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const text = await res.text().catch(() => "");
|
||||||
|
const err = new Error("API_BLOB_ERROR");
|
||||||
|
err.status = res.status;
|
||||||
|
err.payload = text;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await res.blob();
|
||||||
|
const filename = filenameFromDisposition(res.headers.get("content-disposition"));
|
||||||
|
return { blob, filename };
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue