new raceday and backup

This commit is contained in:
larssand
2026-09-01 07:07:23 +02:00
parent c2bfa97d30
commit f561dd8feb
21 changed files with 1042 additions and 60 deletions

View File

@@ -1,3 +1,10 @@
function formatBackupSize(bytes) {
const value = Math.max(0, Number(bytes || 0));
if (value < 1024) return `${value} B`;
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
}
export function renderSettings(deps) {
const {
dom,
@@ -45,7 +52,22 @@ export function renderSettings(deps) {
normalizeRaceAudioSettings,
raceAudioEventDefinitions,
triggerRaceAudio,
serverBackups,
serverBackupsLoaded,
createManualServerBackup,
createSafetyServerBackup,
refreshServerBackups,
} = deps;
const runWithSafetyBackup = async (action) => {
try {
await createSafetyServerBackup();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (!window.confirm(t("settings.safety_backup_failed_confirm", { msg: message }))) return false;
}
await action();
return true;
};
const ammcStatus = ammc.status || {};
const ammcOutput = Array.isArray(ammcStatus.lastOutput) ? ammcStatus.lastOutput : [];
const suggestedWsUrl = getManagedWsUrl("main");
@@ -415,6 +437,31 @@ export function renderSettings(deps) {
<button id="settingsTestBackend" class="btn" type="button">${t("settings.test_backend")}</button>
<button id="settingsSyncNow" class="btn btn-primary" type="button">${t("settings.sync_now")}</button>
</div>
<div class="backup-manager mt-16">
<div class="backup-manager-header">
<div>
<strong>${t("settings.server_backups")}</strong>
<p class="hint">${t("settings.server_backups_hint")}</p>
</div>
<div class="actions">
<button id="createServerBackup" class="btn btn-primary" type="button">${t("settings.create_backup")}</button>
<button id="refreshServerBackups" class="btn" type="button">${t("settings.refresh_backups")}</button>
</div>
</div>
<div class="backup-list">
${!serverBackupsLoaded
? `<p class="hint">${t("settings.loading_backups")}</p>`
: serverBackups.length
? serverBackups.slice(0, 10).map((backup) => `
<article class="backup-row">
<div><strong>${escapeHtml(new Date(backup.createdAt).toLocaleString())}</strong><small>${escapeHtml(backup.fileName)}</small></div>
<span>${escapeHtml(t(`settings.backup_reason_${backup.reason}`))}</span>
<span>${formatBackupSize(backup.sizeBytes)}</span>
</article>`).join("")
: `<p class="hint">${t("settings.no_backups")}</p>`}
</div>
<p class="hint">${t("settings.restore_backup_hint")}</p>
</div>
<div class="mt-16">
<p class="hint">${t("settings.storage_note_full")}</p>
<div class="actions">
@@ -673,6 +720,19 @@ Erik Eriksson;654321;4WD;Associated;;</code></pre>
await syncStateToBackend();
renderView();
});
document.getElementById("createServerBackup")?.addEventListener("click", async (event) => {
const button = event.currentTarget;
if (button instanceof HTMLButtonElement) button.disabled = true;
try {
await createManualServerBackup();
await refreshServerBackups({ render: false });
alert(t("settings.backup_created"));
} catch (error) {
alert(t("settings.backup_failed", { msg: error instanceof Error ? error.message : String(error) }));
}
renderView();
});
document.getElementById("refreshServerBackups")?.addEventListener("click", () => refreshServerBackups());
document.getElementById("logoUpload")?.addEventListener("change", (event) => {
const input = event.currentTarget;
@@ -707,10 +767,11 @@ Erik Eriksson;654321;4WD;Associated;;</code></pre>
return;
}
const reader = new FileReader();
reader.onload = () => {
reader.onload = async () => {
try {
const parsed = JSON.parse(String(reader.result || "{}"));
importDataPayload(parsed, "full");
const completed = await runWithSafetyBackup(() => importDataPayload(parsed, "full"));
if (!completed && input instanceof HTMLInputElement) input.value = "";
} catch (error) {
settingsStorageNotice = t("settings.import_failed", { msg: error instanceof Error ? error.message : String(error) });
settingsStorageNoticeIsError = true;
@@ -756,10 +817,12 @@ Erik Eriksson;654321;4WD;Associated;;</code></pre>
bindCsvImport("selectCarsCsv", "importCarsCsv", "cars");
document.querySelectorAll("[data-clear-scope]").forEach((button) => {
button.addEventListener("click", () => {
button.addEventListener("click", async () => {
const scope = String(button.dataset.clearScope || "");
if (!scope || !window.confirm(t(`settings.clear_${scope}_confirm`))) return;
clearDataScope(scope);
button.disabled = true;
const completed = await runWithSafetyBackup(() => clearDataScope(scope));
if (!completed) button.disabled = false;
});
});