Publicerad källa
Ladda ner källa ZIP
CH-J Tools
Bläddra i kataloger och filer för en specifik programutgåva.
CH-J / Server Manager
1704 filer
0.1.2-beta
Tillagd: · Byggd:
1
"use strict";3
// File Manager side of rsync: a quick job between two panels. The engine is in the Core, shared with the Rsync Manager.4
(function () {5
const $ = (id) => document.getElementById(id);6
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));7
const errorText = (error) => String(error?.message || error).replace(/^Error invoking remote method '[^']*': Error: /, "");8
const TITLES = { copy: "Synchronizovat pomocí rsync", compare: "Porovnat s jiným panelem", versioned: "Zálohovat pomocí rsync" };9
const LABELS = { new: "nové", changed: "změna", delete: "smazat", metadata: "metadata", directory: "složka", link: "odkaz" };10
let spec = null, plan = null, activeJob = null, onDone = null;11
// A dialog closed or reopened in the meantime must not take over the result of an older analysis.12
let generation = 0;14
function formatBytes(bytes) {15
const value = Number(bytes) || 0, units = ["B", "KiB", "MiB", "GiB", "TiB"];16
const power = value > 0 ? Math.min(Math.floor(Math.log(value) / Math.log(1024)), units.length - 1) : 0;17
return `${(value / 1024 ** power).toFixed(power ? 1 : 0)} ${units[power]}`;18
}19
function status(message, error = false) { $("rsyncStatus").textContent = message; $("rsyncStatus").classList.toggle("error", error); }20
function setRun(enabled) { $("rsyncRun").disabled = !enabled; $("rsyncAnalyze").disabled = Boolean(activeJob); $("rsyncCancel").hidden = !activeJob; }22
async function follow(id) {23
activeJob = id; setRun(false);24
for (;;) {25
const job = await api.rsync.job({ id });26
$("rsyncProgress").textContent = job.state === "running" ? [job.progress?.percent != null ? `${job.progress.percent} %` : "", job.progress?.rate || "", job.itemsTotal ? t("{count} položek", { count: job.itemsTotal }) : ""].filter(Boolean).join(" · ") : "";27
if (job.state !== "running") { activeJob = null; setRun(false); return job; }28
await sleep(400);29
}30
}32
function showItems(job) {33
const summary = job.summary || {};34
$("rsyncSummary").textContent = t("Nové: {new}, změněné: {changed}, k odstranění: {delete}, objem {bytes}.", { new: summary.new || 0, changed: summary.changed || 0, delete: summary.delete || 0, bytes: formatBytes(summary.bytes) });35
$("rsyncItems").replaceChildren(...(job.items || []).slice(0, 500).map((item) => {36
const line = document.createElement("div"), kind = document.createElement("b"), name = document.createElement("span");37
line.className = `rsync-item ${item.change}`; kind.textContent = t(LABELS[item.change] || item.change); name.textContent = item.path;38
line.append(kind, name); return line;39
}));40
}42
async function analyze() {43
const own = ++generation;44
plan = null; setRun(false); status(t("Analyzuji rozdíly, nic se nezmění…")); $("rsyncItems").replaceChildren(); $("rsyncSummary").textContent = "";45
try {46
const started = await api.rsync.preview(spec);47
const job = await follow(started.jobId);48
if (own !== generation) return;49
if (job.state === "failed" && job.errorCode === "RSYNC_MISSING") { status(t("Na jednom ze serverů chybí rsync. Otevřete Rsync Manager, který ho umí nainstalovat."), true); return; }50
if (!["ok", "warning"].includes(job.state)) { status(`${job.message || ""} ${job.output || ""}`.trim(), true); return; }51
showItems(job);52
plan = job;53
const nothing = !job.itemsTotal;54
status(nothing ? t("Obě místa jsou shodná.") : spec.mode === "compare" ? t("Porovnání je hotové. Nic se nezměnilo.") : t("Zkontrolujte změny a spusťte přenos."));55
setRun(spec.mode !== "compare" && !nothing);56
} catch (error) { if (own === generation) status(errorText(error), true); }57
}59
async function run() {60
if (!plan) return;61
generation += 1;62
const summary = plan.summary || {};63
const remove = spec.mode === "versioned" && summary.delete;64
if (!window.confirm(t("Spustit přenos? Nové: {new}, změněné: {changed}, objem {bytes}.", { new: summary.new || 0, changed: summary.changed || 0, bytes: formatBytes(summary.bytes) }) + (remove ? `\n${t("Do verzí se přesune souborů: {count}.", { count: summary.delete })}` : ""))) return;65
const payload = { planId: plan.planId, confirm: true, ...(remove ? { acceptDeletes: summary.delete } : {}) };66
if (remove && plan.protectedTarget) { const typed = window.prompt(t("Cíl {path} je systémová nebo domovská složka. Pro pokračování opište přesnou cestu.", { path: plan.target.path })); if (typed === null) return; payload.confirmProtected = typed; }67
if (remove && plan.emptySource) { if (!window.confirm(t("Zdroj je prázdný, takže se z cíle smaže všechno. Opravdu pokračovat?"))) return; payload.allowEmptySource = true; }68
status(t("Synchronizuji…")); plan = null;69
try {70
const started = await api.rsync.run(payload);71
const job = await follow(started.jobId);72
const good = ["ok", "warning"].includes(job.state);73
status(good ? t("Hotovo.") : `${job.message || ""} ${job.output || ""}`.trim(), !good);74
if (good) onDone?.();75
} catch (error) { status(errorText(error), true); }76
}78
function close() { generation += 1; $("rsyncDialog").hidden = true; spec = null; plan = null; }80
// `spec` is a complete job without secrets, `refresh` reloads the panels when it finishes.81
function open(prepared, refresh) {82
spec = prepared; plan = null; onDone = refresh;83
$("rsyncTitle").textContent = t(TITLES[prepared.mode] || TITLES.copy);84
$("rsyncFrom").textContent = prepared.sourceLabel; $("rsyncTo").textContent = prepared.targetLabel;85
delete spec.sourceLabel; delete spec.targetLabel;86
$("rsyncItems").replaceChildren(); $("rsyncSummary").textContent = ""; $("rsyncProgress").textContent = "";87
$("rsyncDialog").hidden = false;88
setRun(false);89
void analyze();90
}92
$("rsyncAnalyze").addEventListener("click", () => void analyze());93
$("rsyncRun").addEventListener("click", () => void run());94
$("rsyncCancel").addEventListener("click", () => { if (activeJob) void api.rsync.cancel({ id: activeJob }); });95
$("rsyncClose").addEventListener("click", close);96
$("rsyncManager").addEventListener("click", () => { const prepared = spec; close(); void api.rsync.openManager({ spec: prepared }).catch(() => {}); });97
$("rsyncSave").addEventListener("click", async () => {98
const name = window.prompt(t("Název profilu"));99
if (!name) return;100
try { await api.rsync.saveProfile({ name, spec }); status(t("Profil byl uložen. Plán a další nastavení doplníte v Rsync Manageru.")); }101
catch (error) { status(/PROFILE_NEEDS_SAVED_SERVER/.test(errorText(error)) ? t("Profil potřebuje server s uloženým SSH profilem, ruční přihlášení se neukládá.") : errorText(error), true); }102
});103
window.chjRsync = { open };104
})();SHA-256
92e16c36469abe7e556088f044423a4aa7ac038ff3cf7b4bf2ce9e04fe6266bdCH-J Proprietary Software License 1.14 · Källan finns under CH-J Proprietary Software License 1.14. Dess tillgänglighet ändrar inte licensvillkoren eller ger ytterligare rättigheter.
Arkiv SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f