125 lines
4.4 KiB
JavaScript
125 lines
4.4 KiB
JavaScript
import { pageRoute } from "../routes.js";
|
|
|
|
/* Orphaned-upload administration view. */
|
|
|
|
/** Format a byte count for the administrative upload list. */
|
|
function formatFileSize(size) {
|
|
const bytes = Number(size || 0);
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
class OrphanedUploadsAdmin {
|
|
/**
|
|
* goal : Create the controller for uploads without current references.
|
|
* pre : api performs authenticated requests and translate resolves UI keys.
|
|
* post : The controller is connected to the orphaned-upload list.
|
|
* result : A directly usable orphaned-upload administration controller.
|
|
*/
|
|
constructor(api, translate) {
|
|
this.api = api;
|
|
this.translate = translate;
|
|
this.list = document.getElementById("orphaned-uploads-list");
|
|
}
|
|
|
|
/**
|
|
* goal : Load orphaned uploads and their last historical uses.
|
|
* pre : The current session has administrator permission.
|
|
* post : The upload list represents the current server state.
|
|
* result : A promise that resolves when all upload rows have been rendered.
|
|
*/
|
|
async load() {
|
|
const result = await this.api("/api/admin/uploads/orphaned");
|
|
const uploads = result.uploads || [];
|
|
this.list.replaceChildren();
|
|
|
|
if (uploads.length === 0) {
|
|
const empty = document.createElement("p");
|
|
empty.className = "muted";
|
|
empty.textContent = this.translate("no-orphaned-uploads", "No orphaned uploads.");
|
|
this.list.append(empty);
|
|
return;
|
|
}
|
|
|
|
for (const upload of uploads) this.list.append(this.#renderUpload(upload));
|
|
}
|
|
|
|
/** Build one upload row with history, view link and deletion action. */
|
|
#renderUpload(upload) {
|
|
const row = document.createElement("article");
|
|
row.className = "orphaned-upload-row";
|
|
|
|
const title = document.createElement("strong");
|
|
title.textContent = upload.originalName;
|
|
|
|
const meta = document.createElement("div");
|
|
meta.className = "muted";
|
|
meta.textContent = `${upload.mimeType} · ${formatFileSize(upload.size)} · ` +
|
|
`${this.translate("uploaded-by", "uploaded by")} ${upload.uploadedBy}`;
|
|
|
|
const history = this.#uploadHistory(upload);
|
|
const actions = document.createElement("div");
|
|
actions.className = "orphaned-upload-actions";
|
|
|
|
const view = document.createElement("a");
|
|
view.href = `/uploads/${encodeURIComponent(upload.ownerSlug)}/` +
|
|
encodeURIComponent(upload.storedName);
|
|
view.target = "_blank";
|
|
view.rel = "noopener";
|
|
view.textContent = this.translate("view", "View");
|
|
|
|
const remove = document.createElement("button");
|
|
remove.className = "danger";
|
|
remove.textContent = this.translate("delete", "Delete");
|
|
remove.addEventListener("click", () => {
|
|
this.#deleteUpload(upload).catch((error) => console.error(error));
|
|
});
|
|
|
|
actions.append(view, remove);
|
|
row.append(title, meta, history, actions);
|
|
return row;
|
|
}
|
|
|
|
/** Build the historical-use portion of one upload row. */
|
|
#uploadHistory(upload) {
|
|
const history = document.createElement("div");
|
|
const lastUses = Array.isArray(upload.lastUses) ? upload.lastUses : [];
|
|
if (lastUses.length === 0) {
|
|
history.textContent = this.translate(
|
|
"never-referenced", "Never referenced by a saved page.");
|
|
return history;
|
|
}
|
|
|
|
const label = document.createElement("div");
|
|
label.textContent = `${this.translate("last-used", "Last used")}:`;
|
|
const uses = document.createElement("ul");
|
|
uses.className = "orphaned-upload-uses";
|
|
for (const use of lastUses) {
|
|
const item = document.createElement("li");
|
|
const link = document.createElement("a");
|
|
link.href = pageRoute(use.slug);
|
|
link.textContent = use.title || use.slug;
|
|
item.append(link);
|
|
if (use.version) {
|
|
item.append(document.createTextNode(
|
|
` · ${this.translate("version", "Version")} ${use.version}`));
|
|
}
|
|
uses.append(item);
|
|
}
|
|
history.append(label, uses);
|
|
return history;
|
|
}
|
|
|
|
/** Confirm and remove one still-orphaned upload, then refresh the list. */
|
|
async #deleteUpload(upload) {
|
|
const question = this.translate(
|
|
"delete-orphaned-upload-confirm", "Delete this orphaned upload?");
|
|
if (!window.confirm(question)) return;
|
|
await this.api(`/api/admin/uploads/orphaned/${upload.id}`, { method: "DELETE" });
|
|
await this.load();
|
|
}
|
|
}
|
|
|
|
export { OrphanedUploadsAdmin };
|