CH-J ToolsSSH를 통한 서버 관리
메뉴
공개된 소스

CH-J Tools

특정 애플리케이션 릴리스의 디렉터리와 파일을 살펴보세요.

소스 ZIP 다운로드
CH-J / Server Manager 1704 개 파일
0.1.2-beta 추가됨: · 빌드됨:
rsync.js 추가됨: 날짜 기록 없음 6.7 KB · 104 줄
코드 파일 다운로드
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-25692e16c36469abe7e556088f044423a4aa7ac038ff3cf7b4bf2ce9e04fe6266bd

CH-J Proprietary Software License 1.14 · 소스는 CH-J Proprietary Software License 1.14에 따라 제공됩니다. 소스를 공개한다고 해서 라이선스 조항이 바뀌거나 추가 권리가 부여되지는 않습니다.

압축 파일 SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
파일