Some checks failed
Build and Publish Docker Image / deploy (push) Has been cancelled
Refactor the admin storage backend creation and editing flows to use provider-specific pages (e.g., `/admin/storage/new/sftp`) instead of a single generic form. This ensures only relevant fields are rendered for each storage provider (such as SFTP, S3, or WebDAV). Additionally: - Prevent mutation of the storage provider type during backend edits. - Add comprehensive unit tests for provider-specific rendering, edit validation, and CSRF/admin route protection.
116 lines
3.8 KiB
JavaScript
116 lines
3.8 KiB
JavaScript
(function () {
|
|
document.querySelectorAll("[data-storage-speed-open]").forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
const modal = document.querySelector("[data-storage-speed-modal]");
|
|
if (modal) {
|
|
modal.hidden = false;
|
|
}
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll("[data-storage-modal-close]").forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
const modal = button.closest(".storage-modal");
|
|
if (modal) {
|
|
modal.hidden = true;
|
|
}
|
|
});
|
|
});
|
|
|
|
document.addEventListener("keydown", (event) => {
|
|
if (event.key !== "Escape") {
|
|
return;
|
|
}
|
|
document.querySelectorAll(".storage-modal").forEach((modal) => {
|
|
modal.hidden = true;
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll(".storage-speed-form").forEach((form) => {
|
|
const customFields = form.querySelector("[data-storage-custom-fields]");
|
|
function syncCustomFields() {
|
|
if (!customFields) {
|
|
return;
|
|
}
|
|
const customSelected = form.querySelector('input[name="mode"]:checked')?.value === "custom";
|
|
customFields.hidden = !customSelected;
|
|
customFields.querySelectorAll("input").forEach((input) => {
|
|
input.disabled = !customSelected;
|
|
});
|
|
}
|
|
form.querySelectorAll('input[name="mode"]').forEach((input) => {
|
|
input.addEventListener("change", syncCustomFields);
|
|
});
|
|
syncCustomFields();
|
|
});
|
|
|
|
const testList = document.querySelector("[data-storage-tests-page]");
|
|
if (!testList) {
|
|
return;
|
|
}
|
|
|
|
function escapeHTML(value) {
|
|
return String(value || "").replace(/[&<>"']/g, (char) => ({
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"'": "'",
|
|
})[char]);
|
|
}
|
|
|
|
function renderTest(test) {
|
|
const progress = Math.max(0, Math.min(100, Number(test.progress || 0)));
|
|
const error = test.error
|
|
? `<span class="storage-result-error"><strong>Error</strong>${escapeHTML(test.error)}</span>`
|
|
: "";
|
|
return `
|
|
<details class="storage-result-row" data-storage-test-id="${escapeHTML(test.id)}">
|
|
<summary>
|
|
<span>${escapeHTML(test.startedLabel)}</span>
|
|
<span>${escapeHTML(test.customLabel || test.modeLabel)}</span>
|
|
<span class="storage-result-status is-${escapeHTML(test.status)}">${escapeHTML(test.status)}</span>
|
|
</summary>
|
|
<div class="storage-test-progress" aria-label="Test progress">
|
|
<div class="storage-test-progress-bar"><span style="width: ${progress}%"></span></div>
|
|
<small>${progress}%${test.stage ? " · " + escapeHTML(test.stage) : ""}</small>
|
|
</div>
|
|
<div class="storage-result-detail">
|
|
<span><strong>Finished</strong>${escapeHTML(test.finishedLabel)}</span>
|
|
<span><strong>Files</strong>${escapeHTML(test.files)}</span>
|
|
<span><strong>Size</strong>${escapeHTML(test.sizeLabel)}</span>
|
|
<span><strong>Write</strong>${escapeHTML(test.writeSpeed)}</span>
|
|
<span><strong>Read</strong>${escapeHTML(test.readSpeed)}</span>
|
|
${error}
|
|
</div>
|
|
</details>`;
|
|
}
|
|
|
|
async function refreshTests() {
|
|
const url = testList.getAttribute("data-storage-tests-url");
|
|
if (!url) {
|
|
return;
|
|
}
|
|
const response = await fetch(url, { headers: { Accept: "application/json" } });
|
|
if (!response.ok) {
|
|
return;
|
|
}
|
|
const payload = await response.json();
|
|
const openIDs = new Set(Array.from(testList.querySelectorAll("details[open]")).map((row) => row.dataset.storageTestId));
|
|
const tests = payload.tests || [];
|
|
if (tests.length === 0) {
|
|
return;
|
|
}
|
|
testList.innerHTML = tests.map(renderTest).join("");
|
|
testList.querySelectorAll("details").forEach((row) => {
|
|
if (openIDs.has(row.dataset.storageTestId)) {
|
|
row.open = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
setInterval(() => {
|
|
refreshTests().catch(() => {});
|
|
}, 1200);
|
|
})();
|