Mã nguồn đã công bố
Tải mã nguồn dạng ZIP
CH-J Server Manager
Duyệt thư mục và tệp của một bản phát hành ứng dụng cụ thể.
CH-J / Server Manager
368 tệp
core-20261017T201717Z-057348c4a70f
Ngày thêm: Chưa ghi nhận ngày · Ngày dựng:
1
"use strict";3
const api = window.chjPlugin;4
const el = new Proxy({}, { get: (_target, id) => document.getElementById(id) });5
const HISTORY = 60;6
const state = {7
generation: 0,8
busy: false,9
previousNetwork: new Map(),10
previousPaging: null,11
ramHistory: [],12
swapHistory: [],13
metrics: null,14
vm: null,15
vmBusy: false16
};18
// ---------- formatting (server data is always rendered as text) ----------19
const known = (value) => value !== null && value !== undefined && Number.isFinite(Number(value));20
function bytes(value) {21
if (!known(value)) return "nedostupné";22
const units = ["B", "KiB", "MiB", "GiB", "TiB"]; let amount = Number(value); let unit = 0;23
while (Math.abs(amount) >= 1024 && unit < units.length - 1) { amount /= 1024; unit += 1; }24
return `${amount.toLocaleString("cs-CZ", { maximumFractionDigits: unit >= 3 ? 2 : 1 })} ${units[unit]}`;25
}26
const pct = (used, total) => known(used) && known(total) && total > 0 ? Math.max(0, Math.min(100, used / total * 100)) : null;27
const pctText = (value) => known(value) ? `${Number(value).toLocaleString("cs-CZ", { maximumFractionDigits: 1 })} %` : "nedostupné";28
function duration(seconds) {29
if (!known(seconds)) return "nedostupné";30
let remaining = Math.max(0, Number(seconds)); const days = Math.floor(remaining / 86400); remaining %= 86400;31
return `${days ? `${days} d ` : ""}${Math.floor(remaining / 3600)} h ${Math.floor((remaining % 3600) / 60)} min`;32
}33
function setStatus(message, error = false) { el.status.textContent = message; el.status.classList.toggle("error", error); }34
function h(tag, props = {}, children = []) {35
const node = document.createElement(tag);36
for (const [key, value] of Object.entries(props)) {37
if (value === undefined || value === null || value === false) continue;38
if (key === "className") node.className = value;39
else if (key === "text") node.textContent = String(value);40
else if (key === "style") Object.assign(node.style, value);41
else if (key.startsWith("on") && typeof value === "function") node.addEventListener(key.slice(2), value);42
else if (typeof value === "boolean") node[key] = value;43
else node.setAttribute(key, String(value));44
}45
for (const child of [].concat(children)) if (child !== null && child !== undefined && child !== false) node.append(child instanceof Node ? child : String(child));46
return node;47
}48
function parseError(error) {49
const raw = String(error?.message || error || "");50
const match = raw.match(/\b([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+): ([\s\S]*)$/);51
return match ? { code: match[1], detail: match[2].trim() } : { code: "UNKNOWN", detail: raw.replace(/^Error invoking remote method '[^']+': (?:\w*Error: )?/, "") };52
}53
const ERROR_TEXT = {54
CHJ_CANCELLED: "Operace byla zrušena.",55
SUDO_NOT_PERMITTED: "Účet SSH relace nemá oprávnění sudo; zadání hesla to nezmění.",56
SUDO_PASSWORD_INCORRECT: "Heslo pro sudo bylo odmítnuto.",57
SUDO_TTY_REQUIRED: "Politika sudo vyžaduje terminál (requiretty); CH-J ji neobchází.",58
SESSION_NOT_CONNECTED: "SSH relace byla odpojena; stav operace na serveru ověřte po novém připojení.",59
REMOTE_COMMAND_TIMEOUT: "Server neodpověděl včas.",60
VM_BUSY: "Na této relaci právě probíhá jiná změna virtuální paměti.",61
VM_SWAP_BUSY: "Na serveru právě probíhá jiná změna swapu.",62
VM_SWAPOFF_UNSAFE: "Deaktivace by mohla vyčerpat paměť, operace byla odmítnuta:",63
VM_SWAP_DISK_FULL: "Nedostatek místa na disku:",64
VM_SWAP_FS_UNSUPPORTED: "Souborový systém nepodporuje bezpečný swapfile:",65
VM_SWAP_SIZE_INVALID: "Neplatná velikost (64 MiB – 64 GiB, celé MiB).",66
VM_SWAP_PATH_INVALID: "Neplatná cesta swapfile:",67
VM_SWAP_MANUAL_ONLY: "Tuto operaci je nutné provést ručně:",68
VM_SWAP_EXISTS: "Soubor již existuje.",69
VM_WINDOWS_ELEVATION_REQUIRED: "Změna pagefile vyžaduje relaci správce se zvýšeným tokenem (CH-J neobchází UAC).",70
VM_PAGEFILE_LAST: "Jde o poslední pagefile – vyžaduje výslovné potvrzení.",71
VM_PAGEFILE_CRASHDUMP: "Systémový pagefile slouží pro výpis paměti při pádu – vyžaduje výslovné potvrzení.",72
VM_PAGEFILE_SIZE_INVALID: "Neplatná velikost pagefile:",73
VM_PAGEFILE_DISK_FULL: "Nedostatek místa na svazku:",74
VM_PAGEFILE_AUTOMATIC: "Nejprve přepněte na ruční správu.",75
METRICS_PLATFORM_UNSUPPORTED: "Operační systém serveru nelze rozpoznat:"76
};77
function describe(error) {78
const { code, detail } = parseError(error);79
const base = ERROR_TEXT[code];80
return base ? (base.endsWith(":") ? `${base} ${detail}` : base) : detail || code;81
}83
// ---------- charts ----------84
function drawChart(canvas, values, color) {85
const context = canvas.getContext("2d");86
const ratio = window.devicePixelRatio || 1;87
const width = canvas.clientWidth || 560; const height = canvas.clientHeight || 120;88
canvas.width = width * ratio; canvas.height = height * ratio;89
context.setTransform(ratio, 0, 0, ratio, 0, 0);90
context.clearRect(0, 0, width, height);91
context.strokeStyle = "rgba(151,176,205,.15)"; context.lineWidth = 1;92
for (const level of [25, 50, 75]) { const y = height - (level / 100) * height; context.beginPath(); context.moveTo(0, y); context.lineTo(width, y); context.stroke(); }93
context.strokeStyle = color; context.lineWidth = 2; context.beginPath();94
let started = false;95
values.forEach((value, index) => {96
if (!known(value)) { started = false; return; }97
const x = index * (width / (HISTORY - 1)); const y = height - (value / 100) * (height - 4) - 2;98
if (!started) { context.moveTo(x, y); started = true; } else context.lineTo(x, y);99
});100
context.stroke();101
context.fillStyle = "#8ea5bf"; context.font = "10px sans-serif";102
const last = values.at(-1);103
context.fillText(known(last) ? `${last.toFixed(1)} %` : "—", 6, 13);104
}105
function pushHistory(list, value) { list.push(known(value) ? value : null); while (list.length > HISTORY) list.shift(); }107
// ---------- sessions and metrics ----------108
async function loadSessions() {109
const selected = el.sessionSelect.value;110
let sessions = [];111
try { sessions = (await api.sessions.list()).filter((session) => session.state === "connected"); }112
catch (error) { setStatus(`Relace nelze načíst: ${describe(error)}`, true); }113
el.sessionSelect.replaceChildren(new Option(sessions.length ? "Vyberte relaci…" : "Žádná připojená relace", ""));114
for (const session of sessions) el.sessionSelect.add(new Option(`${session.label} — ${session.username}@${session.host}`, session.sessionId));115
el.sessionSelect.value = sessions.some((session) => session.sessionId === selected) ? selected : (sessions[0]?.sessionId || "");116
if (el.sessionSelect.value !== selected) resetSession();117
el.refreshMetricsButton.disabled = !el.sessionSelect.value;118
if (!sessions.length) setStatus("Nejprve se v Core připojte k serveru přes SSH.");119
}121
function resetSession() {122
state.generation += 1;123
Object.assign(state, { previousNetwork: new Map(), previousPaging: null, ramHistory: [], swapHistory: [], metrics: null, vm: null });124
el.manageVmButton.disabled = true;125
}127
async function loadMetrics() {128
const sessionId = el.sessionSelect.value;129
if (!sessionId || state.busy) return;130
const token = state.generation;131
state.busy = true; el.refreshMetricsButton.disabled = true;132
try {133
const metrics = await api.system.readMetrics(sessionId);134
if (token !== state.generation) return;135
state.metrics = metrics;136
renderMetrics(metrics);137
setStatus(`Aktualizováno ${new Date(metrics.collectedAt).toLocaleTimeString("cs-CZ")}${metrics.unavailable?.length ? ` · nedostupné zdroje: ${metrics.unavailable.length}` : ""}.`);138
} catch (error) {139
if (token === state.generation) setStatus(describe(error), true);140
if (parseError(error).code === "SESSION_NOT_CONNECTED") void loadSessions();141
} finally {142
state.busy = false; el.refreshMetricsButton.disabled = !el.sessionSelect.value;143
}144
}146
// Each card renders independently: one missing metric never breaks the others.147
function safely(render) { try { render(); } catch (error) { console.error("Metric render failed", error); } }149
function renderMetrics(metrics) {150
const windows = metrics.os === "windows";151
safely(() => {152
el.hostname.textContent = metrics.hostname || "nedostupné";153
el.platform.textContent = windows ? `${metrics.osName || "Windows"}${metrics.installationType ? ` (${metrics.installationType})` : ""}` : metrics.platform || "nedostupné";154
el.kernelLabel.textContent = windows ? "Sestavení" : "Kernel";155
el.kernel.textContent = metrics.kernel || "nedostupné";156
el.uptime.textContent = duration(metrics.uptimeSeconds);157
});158
safely(() => {159
const cpu = metrics.cpu || {};160
const usage = known(cpu.usagePercent) ? Number(cpu.usagePercent) : null;161
el.cpu.textContent = usage !== null ? pctText(usage) : cpu.usagePending ? "měřím…" : "nedostupné";162
el.cpuBar.style.width = `${usage ?? 0}%`;163
el.cpuDetail.textContent = known(cpu.logicalCores) ? `${cpu.logicalCores} logických procesorů` : "počet procesorů nedostupný";164
el.cpuArchitecture.textContent = cpu.architecture || "neznámá architektura";165
el.cpuModel.textContent = cpu.model || "Údaj o modelu CPU není dostupný.";166
el.cpuTopology.textContent = `${known(cpu.logicalCores) ? cpu.logicalCores : "?"} logických · ${known(cpu.physicalCores) ? cpu.physicalCores : "nedostupné"} fyzických · ${known(cpu.sockets) ? cpu.sockets : "nedostupné"} socketů`;167
el.cpuTypes.textContent = Array.isArray(cpu.coreTypes) && cpu.coreTypes.length ? cpu.coreTypes.map((type) => `${type.name} ${type.count}`).join(" · ") : "Server nerozlišuje typy jader.";168
});169
safely(() => {170
if (metrics.loadAverageSupported === false) {171
el.loadLabel.textContent = "Zatížení (load average)";172
el.load.textContent = "nedostupné";173
el.loadDetail.textContent = `Windows load average neposkytuje${known(metrics.processorQueueLength) ? ` · fronta procesoru ${metrics.processorQueueLength}` : ""}`;174
} else {175
el.loadLabel.textContent = "Zatížení 1 min";176
el.load.textContent = known(metrics.load1) ? Number(metrics.load1).toLocaleString("cs-CZ", { maximumFractionDigits: 2 }) : "nedostupné";177
el.loadDetail.textContent = "load average";178
}179
});180
safely(() => {181
const total = metrics.memory?.total; const available = metrics.memory?.available;182
const used = known(total) && known(available) ? Math.max(0, total - available) : null;183
const usedPercent = pct(used, total);184
el.memory.textContent = pctText(usedPercent);185
el.memoryBar.style.width = `${usedPercent ?? 0}%`;186
el.memoryDetail.textContent = `${bytes(used)} / ${bytes(total)}`;187
pushHistory(state.ramHistory, usedPercent);188
});189
safely(() => {190
const swap = metrics.swap || {};191
el.swapLabel.textContent = windows ? "Pagefile" : "Swap";192
const swapPercent = pct(swap.used, swap.total);193
el.swap.textContent = known(swap.total) ? (swap.total > 0 ? pctText(swapPercent) : "neaktivní") : "nedostupné";194
el.swapBar.style.width = `${swapPercent ?? 0}%`;195
el.swapDetail.textContent = known(swap.total) ? `${bytes(swap.used)} / ${bytes(swap.total)}` : "údaj není dostupný";196
pushHistory(state.swapHistory, known(swap.total) ? (swap.total > 0 ? swapPercent : 0) : null);197
});198
safely(() => {199
const disk = metrics.disk || {};200
el.diskLabel.textContent = `Disk ${disk.mount || "/"}`;201
const diskPercent = pct(disk.used, disk.total);202
el.disk.textContent = pctText(diskPercent);203
el.diskBar.style.width = `${diskPercent ?? 0}%`;204
el.diskDetail.textContent = `${bytes(disk.used)} / ${bytes(disk.total)}${Array.isArray(metrics.disks) && metrics.disks.length > 1 ? ` · ${metrics.disks.length} svazků` : ""}`;205
});206
safely(() => renderNetwork(metrics.network, metrics.collectedAt));207
safely(() => renderMemorySection(metrics));208
}210
function renderNetwork(network, collectedAt) {211
const interfaces = Array.isArray(network?.interfaces) ? network.interfaces : [];212
el.networkInterfaces.replaceChildren();213
const collectedMs = Date.parse(collectedAt) || Date.now();214
const next = new Map();215
const active = interfaces.filter((item) => item.state === "up" || item.state === "active").length;216
el.networkSummary.textContent = interfaces.length ? `${active}/${interfaces.length} aktivní${network.defaultInterface ? ` · výchozí ${network.defaultInterface}` : ""}` : "žádná rozhraní";217
if (!interfaces.length) el.networkInterfaces.append(h("p", { className: "empty", text: "Nebyla nalezena žádná síťová rozhraní mimo loopback." }));218
for (const item of interfaces) {219
const isUp = item.state === "up" || item.state === "active";220
const previous = state.previousNetwork.get(item.name);221
const seconds = previous ? Math.max(0, (collectedMs - previous.collectedMs) / 1000) : 0;222
const rate = (current, before) => (previous && known(current) && known(before) && seconds > 0 && current >= before ? (current - before) / seconds : null);223
const rxRate = rate(item.rxBytes, previous?.rxBytes); const txRate = rate(item.txBytes, previous?.txBytes);224
const traffic = known(item.rxBytes) || known(item.txBytes)225
? [h("span", { className: "rx", text: `↓ ${bytes(item.rxBytes)}${rxRate !== null ? ` · ${bytes(rxRate)}/s` : ""}` }),226
h("span", { className: "tx", text: `↑ ${bytes(item.txBytes)}${txRate !== null ? ` · ${bytes(txRate)}/s` : ""}` })]227
: [h("span", { className: "rx", text: `↓ ${known(item.rxPackets) ? `${item.rxPackets.toLocaleString("cs-CZ")} paketů` : "nedostupné"}` }),228
h("span", { className: "tx", text: `↑ ${known(item.txPackets) ? `${item.txPackets.toLocaleString("cs-CZ")} paketů` : "nedostupné"}` }),229
item.bytesUnavailableReason ? h("span", { className: "mac", text: "Ovladač síťové karty nehlásí přenesené bajty." }) : null];230
el.networkInterfaces.append(h("article", { className: "network-interface" }, [231
h("div", { className: "interface-heading" }, [h("strong", { className: "interface-name" }, [item.name || "unknown", item.isDefault ? h("span", { className: "default-mark", text: "výchozí" }) : null]),232
h("span", { className: `state ${isUp ? "up" : "down"}`, text: isUp ? "aktivní" : (item.state || "neznámý") })]),233
h("p", { className: "addresses", text: Array.isArray(item.addresses) && item.addresses.length ? item.addresses.join(" · ") : "bez globální IP adresy" }),234
item.mac ? h("p", { className: "mac", text: `MAC ${item.mac}` }) : null,235
h("div", { className: "traffic" }, traffic)236
]));237
next.set(item.name, { rxBytes: item.rxBytes, txBytes: item.txBytes, collectedMs });238
}239
state.previousNetwork = next;240
}242
// ---------- memory and virtual memory section ----------243
const COLORS = { used: "#42a5ff", buffers: "#9b8cff", cached: "#43d6a0", reclaimable: "#2fb39a", free: "#2b3d52", inUse: "#42a5ff", modified: "#f2c463", standby: "#43d6a0" };245
function renderMemorySection(metrics) {246
const windows = metrics.os === "windows";247
const detail = metrics.memoryDetail;248
const total = windows ? metrics.memory?.total : detail?.total ?? metrics.memory?.total;249
el.memorySummary.textContent = `RAM ${pctText(state.ramHistory.at(-1))} · ${windows ? "pagefile" : "swap"} ${metrics.swap && known(metrics.swap.total) && metrics.swap.total > 0 ? pctText(pct(metrics.swap.used, metrics.swap.total)) : "neaktivní"}`;250
drawChart(el.ramChart, state.ramHistory, "#42a5ff");251
drawChart(el.swapChart, state.swapHistory, "#f2c463");252
let slices = []; let facts = []; let explain = "";253
const rate = (value) => (known(value) ? value.toFixed(1) : "—");254
if (windows && detail) {255
slices = [["inUse", "Používaná", detail.slices.inUse], ["modified", "Upravená (Modified)", detail.slices.modified], ["standby", "Standby cache", detail.slices.standby], ["free", "Volná a vynulovaná", detail.slices.free]];256
facts = [["Fyzická RAM celkem", bytes(total)], ["Skutečně využitá", `${bytes(detail.slices.inUse)} (${pctText(pct(detail.slices.inUse, total))})`], ["Dostupná", bytes(detail.available)], ["Volná", bytes(detail.slices.free)],257
["Standby cache", bytes(detail.slices.standby)], ["Modified page list", bytes(detail.slices.modified)], ["System Cache (rezidentní)", bytes(detail.systemCache)],258
["Paměť jádra", `${bytes((detail.kernel.pagedPool || 0) + (detail.kernel.nonpagedPool || 0))} (stránkovaný pool ${bytes(detail.kernel.pagedPool)}, nestránkovaný ${bytes(detail.kernel.nonpagedPool)})`],259
["Commit (Committed / Limit)", `${bytes(detail.commit.committed)} / ${bytes(detail.commit.limit)} (${pctText(pct(detail.commit.committed, detail.commit.limit))})`],260
["Stránkování in / out", `${rate(detail.paging.pagesInPerSecond)} / ${rate(detail.paging.pagesOutPerSecond)} stránek/s`]];261
explain = "Používaná = RAM obsazená procesy a jádrem. Dostupná = Standby cache + volná paměť, kterou Windows uvolní okamžitě. Modified obsahuje data, která se musí nejdřív zapsat. System Cache se s ostatními kategoriemi překrývá, proto není v pruhu. Commit Limit = RAM + pagefile, nejde o velikost pagefile.";262
} else if (detail) {263
slices = [["used", "Využitá (aplikace a jádro)", detail.used], ["buffers", "Buffers", detail.buffers], ["cached", "Cache (Cached)", detail.cached], ["reclaimable", "Uvolnitelná slab", detail.slabReclaimable], ["free", "Volná", detail.free]];264
const kernel = (detail.slab || 0) + (detail.kernelStack || 0) + (detail.pageTables || 0);265
facts = [["Fyzická RAM celkem", bytes(detail.total)], ["Skutečně využitá", `${bytes(detail.used)} (${pctText(pct(detail.used, detail.total))})`], ["Dostupná (MemAvailable)", bytes(detail.available)], ["Volná (MemFree)", bytes(detail.free)],266
["Cache / Buffers", `${bytes(detail.cached)} / ${bytes(detail.buffers)}`], ["Reclaimable (SReclaimable)", bytes(detail.slabReclaimable)], ["Sdílená (Shmem, součást Cached)", bytes(detail.shmem)],267
["Paměť jádra", `${bytes(kernel)} (slab ${bytes(detail.slab)}, stack ${bytes(detail.kernelStack)}, page tables ${bytes(detail.pageTables)})`],268
["Dirty / Writeback", `${bytes(detail.dirty)} / ${bytes(detail.writeback)}`], ["Commit (Committed_AS / CommitLimit)", `${bytes(detail.commit.committed)} / ${bytes(detail.commit.limit)}`]];269
explain = "Využitá = MemTotal − MemFree − Buffers − Cached − SReclaimable (paměť, kterou nelze rychle uvolnit). Dostupná je odhad jádra, kolik lze použít bez swapování (zahrnuje volnou paměť a uvolnitelnou cache). Shmem je součástí Cached a není uvolnitelný. Kategorie v pruhu se nepřekrývají.";270
} else {271
facts = [["Fyzická RAM celkem", bytes(total)], ["Dostupná", bytes(metrics.memory?.available)]];272
explain = "Podrobný rozpis paměti není z tohoto serveru dostupný (např. chybí oprávnění ke čítačům).";273
}274
el.ramStack.replaceChildren(...slices.map(([key, label, value]) => h("i", { title: `${label}: ${bytes(value)}`, style: { width: `${pct(value, total) ?? 0}%`, background: COLORS[key] } })));275
el.ramLegend.replaceChildren(...slices.map(([key, label, value]) => h("li", {}, [h("b", { style: { background: COLORS[key] } }), `${label} ${bytes(value)}`])));276
el.ramFacts.replaceChildren(...facts.map(([label, value]) => h("div", {}, [h("dt", { text: label }), h("dd", { text: value })])));277
el.ramExplain.textContent = explain;278
renderVirtual(metrics);279
}281
function renderVirtual(metrics) {282
const windows = metrics.os === "windows";283
el.virtualTitle.textContent = windows ? "Pagefile (virtuální paměť)" : "Swap";284
const swap = metrics.swap || {};285
const facts = known(swap.total) && swap.total === 0286
? [["Stav", windows ? "žádný aktivní pagefile" : "swap není aktivní"]]287
: [["Celková velikost", bytes(swap.total)], ["Využito", `${bytes(swap.used)} (${pctText(pct(swap.used, swap.total))})`], ["K dispozici", bytes(swap.free)]];288
const paging = metrics.memoryDetail?.paging;289
if (!windows && paging && known(paging.swapInPages)) {290
const pageSize = metrics.pageSize || 4096;291
const now = Date.parse(metrics.collectedAt) || Date.now();292
const previous = state.previousPaging;293
let rates = "měřím…";294
if (previous && now > previous.time) {295
const seconds = (now - previous.time) / 1000;296
rates = `${bytes(((paging.swapInPages - previous.in) * pageSize) / seconds)}/s in · ${bytes(((paging.swapOutPages - previous.out) * pageSize) / seconds)}/s out`;297
}298
state.previousPaging = { in: paging.swapInPages, out: paging.swapOutPages, time: now };299
facts.push(["Swap-in / swap-out", rates]);300
}301
if (windows && paging) facts.push(["Stránkování in / out", `${known(paging.pagesInPerSecond) ? paging.pagesInPerSecond.toFixed(1) : "—"} / ${known(paging.pagesOutPerSecond) ? paging.pagesOutPerSecond.toFixed(1) : "—"} stránek/s`]);302
if (!windows && metrics.memoryDetail?.zswap) facts.push(["zswap (jen čtení)", metrics.memoryDetail.zswap.enabled ? `zapnut (${metrics.memoryDetail.zswap.compressor || "?"}), pool ${bytes(metrics.memoryDetail.zswapPool)}` : "vypnut"]);303
el.swapFacts.replaceChildren(...facts.map(([label, value]) => h("div", {}, [h("dt", { text: label }), h("dd", { text: value })])));304
const config = state.vm;305
if (windows) {306
el.swapHead.replaceChildren(h("tr", {}, ["Pagefile", "Alokováno", "Využito", "Maximum využití", "Nastaveno (min–max)"].map((text) => h("th", { text }))));307
const rows = (metrics.pagefiles || []).map((file) => {308
const setting = config?.pagefiles?.find((entry) => String(entry.name).toLowerCase() === file.name.toLowerCase());309
return h("tr", {}, [h("td", { text: file.name }), h("td", { text: bytes(file.allocated) }), h("td", { text: bytes(file.used) }), h("td", { text: bytes(file.peak) }),310
h("td", { text: config ? (config.automatic ? "automaticky (Windows)" : setting?.configured ? (setting.systemManagedSize ? "velikost spravuje systém" : `${bytes(setting.initial)} – ${bytes(setting.maximum)}`) : "nenastaveno") : "načte se ve správě" })]);311
});312
el.swapRows.replaceChildren(...(rows.length ? rows : [h("tr", {}, h("td", { colspan: 5, className: "empty", text: metrics.pagefiles ? "Žádný aktivní pagefile." : "Údaje o pagefile nejsou dostupné (WMI/CIM odepřeno)." }))]));313
el.swapNote.textContent = config ? `${config.automatic ? "Velikost pagefile spravuje Windows automaticky." : "Ruční správa pagefile."}${config.restartPending ? " Změny čekají na restart Windows." : ""}` : "Konfigurace pagefile se načte po otevření správy.";314
el.vmPrivilege.textContent = config ? (config.canManage ? "Relace má zvýšený token správce." : "Jen pro čtení: chybí zvýšený token správce.") : "";315
} else {316
el.swapHead.replaceChildren(h("tr", {}, ["Swap prostor", "Typ", "Velikost", "Využito", "Priorita", "Po restartu"].map((text) => h("th", { text }))));317
const rows = (metrics.swapAreas || []).map((area) => {318
const configured = config?.swapAreas?.find((entry) => entry.path === area.name);319
return h("tr", {}, [h("td", { text: area.name }), h("td", { text: area.type === "zram" ? "zram" : area.type === "partition" ? "oddíl" : "soubor" }), h("td", { text: bytes(area.size) }),320
h("td", { text: `${bytes(area.used)} (${pctText(pct(area.used, area.size))})` }), h("td", { text: String(area.priority ?? "—") }), h("td", { text: config ? (configured?.persistent ? "ano (fstab)" : "ne") : "—" })]);321
});322
el.swapRows.replaceChildren(...(rows.length ? rows : [h("tr", {}, h("td", { colspan: 6, className: "empty", text: "Swap není aktivní." }))]));323
const zram = (metrics.memoryDetail?.zram || []).map((device) => `${device.device}: logicky ${bytes(device.diskSize)}, data ${bytes(device.originalData)} → komprimováno ${bytes(device.compressedData)}, RAM ${bytes(device.memoryUsed)}${device.algorithm ? `, ${device.algorithm}` : ""}`);324
el.swapNote.textContent = [zram.length ? `zram (jen pro čtení): ${zram.join("; ")}` : "", config ? "" : "Trvalost a souborové systémy se načtou po otevření správy."].filter(Boolean).join(" ");325
const mode = config?.privilege?.mode;326
el.vmPrivilege.textContent = config ? (mode === "none" ? "Jen pro čtení: účet nemá sudo." : mode === "sudo-password" ? "Změny si vyžádají heslo pro sudo." : "Změny lze provést (root/sudo bez hesla).") : "";327
}328
el.manageVmButton.disabled = !el.sessionSelect.value;329
}331
// ---------- dialogs ----------332
function runDialog(dialog, { init, submit } = {}) {333
return new Promise((resolve) => {334
const form = dialog.querySelector("form");335
let finished = false;336
const finish = (value) => {337
if (finished) return; finished = true;338
form?.removeEventListener("submit", onSubmit); dialog.removeEventListener("close", onClose);339
for (const button of dialog.querySelectorAll("[data-close]")) button.removeEventListener("click", onClose);340
if (dialog.open) dialog.close();341
resolve(value);342
};343
const onSubmit = async (event) => { event.preventDefault(); const value = await submit(); if (value !== undefined) finish(value); };344
const onClose = () => finish(null);345
form?.addEventListener("submit", onSubmit);346
dialog.addEventListener("close", onClose);347
for (const button of dialog.querySelectorAll("[data-close]")) button.addEventListener("click", onClose);348
init?.();349
if (!dialog.open) dialog.showModal();350
});351
}352
function askSudo(label, error) {353
return runDialog(el.sudoDialog, {354
init: () => { el.sudoMessage.textContent = `Operace „${label}“ vyžaduje oprávnění správce.`; el.sudoPassword.value = ""; el.sudoError.textContent = error || ""; el.sudoError.hidden = !error; setTimeout(() => el.sudoPassword.focus(), 0); },355
submit: () => { const value = el.sudoPassword.value; el.sudoPassword.value = ""; return value || undefined; }356
}).finally(() => { el.sudoPassword.value = ""; });357
}358
function confirmAction({ title, details, confirmLabel = "Potvrdit", option = "" }) {359
return runDialog(el.confirmDialog, {360
init: () => {361
el.confirmTitle.textContent = title;362
el.confirmDetails.replaceChildren(...details.map((item) => h("li", { text: item })));363
el.confirmOptionLabel.hidden = !option; el.confirmOptionText.textContent = option; el.confirmOption.checked = false;364
el.confirmSubmit.textContent = confirmLabel;365
},366
submit: () => ({ option: el.confirmOption.checked })367
});368
}369
function askNumber(title, label, value) {370
return runDialog(el.confirmDialog, {371
init: () => {372
el.confirmTitle.textContent = title;373
const input = h("input", { id: "numberInput", type: "number", value: String(value), "aria-label": label });374
el.confirmDetails.replaceChildren(h("li", {}, [h("label", {}, [label, input])]));375
el.confirmOptionLabel.hidden = true; el.confirmSubmit.textContent = "Pokračovat";376
setTimeout(() => input.focus(), 0);377
},378
submit: () => Number(document.getElementById("numberInput").value)379
});380
}381
// Core is called without a password first; the dialog appears only when Core reports that sudo needs one.382
async function withSudo(label, invoke) {383
let password = ""; let attempts = 0;384
try {385
for (;;) {386
try { return await invoke(password ? { sudoPassword: password } : {}); }387
catch (error) {388
const { code } = parseError(error);389
if ((code === "SUDO_PASSWORD_REQUIRED" || code === "SUDO_PASSWORD_INCORRECT") && attempts < 3) {390
attempts += 1;391
password = await askSudo(label, code === "SUDO_PASSWORD_INCORRECT" && password ? "Heslo bylo odmítnuto." : "");392
if (!password) throw new Error("CHJ_CANCELLED: Operace byla zrušena.");393
continue;394
}395
throw error;396
}397
}398
} finally { password = ""; }399
}401
// ---------- virtual memory management (never part of the monitoring loop) ----------402
async function openVirtualMemory() {403
const sessionId = el.sessionSelect.value;404
if (!sessionId) return;405
el.vmContent.replaceChildren(h("p", { className: "hint", text: "Načítám konfiguraci virtuální paměti…" }));406
el.vmStatus.textContent = "";407
void runDialog(el.vmDialog);408
await reloadVirtualMemory(sessionId, false);409
}411
async function reloadVirtualMemory(sessionId, elevated) {412
const token = state.generation;413
try {414
const run = (auth) => api.system.virtualMemory(sessionId, { ...auth, privileged: elevated });415
const vm = elevated ? await withSudo("Načtení konfigurace swapu", run) : await run({});416
if (token !== state.generation) return;417
state.vm = vm;418
renderVirtualMemoryDialog(sessionId, vm);419
if (state.metrics) renderVirtual(state.metrics);420
} catch (error) {421
if (token === state.generation) el.vmStatus.textContent = describe(error);422
}423
}425
function sizeInput(id, value, unit) {426
return h("span", { className: "inline-actions" }, [427
h("input", { id: `${id}Value`, type: "number", min: "1", step: "1", value: String(value), "aria-label": "Velikost" }),428
h("select", { id: `${id}Unit`, "aria-label": "Jednotka" }, [h("option", { value: "MiB", text: "MiB", selected: unit === "MiB" }), h("option", { value: "GiB", text: "GiB", selected: unit === "GiB" })])429
]);430
}431
function readSize(id) {432
const value = Number(document.getElementById(`${id}Value`).value);433
return Number.isFinite(value) ? Math.round(value * (document.getElementById(`${id}Unit`).value === "GiB" ? 1024 : 1)) : NaN;434
}436
async function runVmChange(sessionId, label, payload, details, option = "") {437
if (state.vmBusy) return;438
const answer = await confirmAction({ title: label, details, confirmLabel: "Provést", option });439
if (!answer || (option && !answer.option)) { el.vmStatus.textContent = "Požadavek nebyl odeslán – neuložená změna byla zrušena."; return; }440
state.vmBusy = true;441
el.vmStatus.textContent = `${label}… (změna běží na serveru)`;442
for (const button of el.vmContent.querySelectorAll("button")) button.disabled = true;443
try {444
const result = await withSudo(label, (auth) => api.system.manageVirtualMemory(sessionId, { ...payload, ...auth }));445
const steps = (result.steps || []).map((step) => `${step.step}: ${step.status}${step.message ? ` (${step.message})` : ""}`).join(", ");446
el.vmStatus.textContent = result.restartRequired447
? `Uloženo – čeká na restart Windows (CH-J restart neprovádí).${result.backup ? ` Záloha: ${result.backup}` : ""}`448
: `Provedeno a ověřeno podle skutečného stavu serveru.${steps ? ` Kroky: ${steps}.` : ""}`;449
if (result.state) { state.vm = { ...state.vm, ...result.state }; renderVirtualMemoryDialog(sessionId, state.vm); if (state.metrics) renderVirtual(state.metrics); }450
else await reloadVirtualMemory(sessionId, false);451
void loadMetrics();452
} catch (error) {453
el.vmStatus.textContent = `Nebylo provedeno: ${describe(error)}`;454
} finally {455
state.vmBusy = false;456
for (const button of el.vmContent.querySelectorAll("button")) button.disabled = false;457
}458
}460
function renderVirtualMemoryDialog(sessionId, vm) {461
const children = vm.platform === "windows" ? windowsDialog(sessionId, vm) : linuxDialog(sessionId, vm);462
el.vmContent.replaceChildren(...children);463
}465
function windowsDialog(sessionId, vm) {466
const children = [h("p", { className: "hint", text: `Správa: ${vm.automatic === null ? "nelze zjistit" : vm.automatic ? "automatická (Windows)" : "ruční"} · Commit Limit ${bytes(vm.commit.limit)} (RAM + pagefile, nejde o velikost pagefile) · výpis paměti při pádu: ${vm.crashDumpEnabled ? "zapnut" : "vypnut"}` })];467
if (vm.restartPending) children.push(h("p", {}, h("span", { className: "pill warn", text: `Čeká na restart: ${vm.pendingChanges.map((change) => change.description).join("; ") || "konfigurace se liší od aktivní"}` })));468
if (!vm.canManage) children.push(h("p", { className: "error", text: vm.readOnlyReason }));469
children.push(h("div", { className: "table-wrap" }, h("table", {}, [470
h("thead", {}, h("tr", {}, ["Pagefile", "Aktivní", "Alokováno", "Využito / maximum", "Nastaveno", "Volné místo", ""].map((text) => h("th", { text })))),471
h("tbody", {}, vm.pagefiles.map((file) => h("tr", {}, [h("td", { text: file.name }), h("td", { text: file.active ? "ano" : "ne (po restartu)" }), h("td", { text: bytes(file.allocated) }), h("td", { text: `${bytes(file.used)} / ${bytes(file.peak)}` }),472
h("td", { text: file.configured ? (file.systemManagedSize ? "systémová velikost" : `${bytes(file.initial)} – ${bytes(file.maximum)}`) : vm.automatic ? "automaticky" : "—" }), h("td", { text: bytes(file.volumeFree) }),473
h("td", {}, vm.canManage && file.configured && vm.automatic === false ? h("button", { type: "button", className: "mini danger", text: "Odebrat…", onclick: () => removePagefile(sessionId, vm, file) }) : null)])))])));474
if (vm.canManage) {475
if (vm.automatic === false) children.push(h("div", { className: "inline-actions" }, h("button", { type: "button", className: "button", text: "Vrátit automatickou správu Windows", onclick: () => runVmChange(sessionId, "Automatická správa pagefile", { action: "automatic" }, ["Windows bude velikost pagefile spravovat sám.", "Změna se projeví po restartu Windows; CH-J restart neprovede."]) })));476
const ntfs = vm.volumes.filter((volume) => ["NTFS", "ReFS"].includes(volume.format));477
children.push(h("div", { className: "vm-form" }, [h("h4", { text: vm.automatic ? "Přepnout na ruční správu a nastavit pagefile" : "Nastavit nebo přidat pagefile" }),478
h("div", { className: "form-grid" }, [479
h("label", {}, ["Svazek", h("select", { id: "pfDrive" }, ntfs.map((volume) => h("option", { value: volume.drive, text: `${volume.drive} (${volume.format}, volno ${bytes(volume.free)})`, selected: volume.drive === vm.systemDrive })))]),480
h("label", {}, ["Počáteční velikost", sizeInput("pfInitial", 1024, "MiB")]),481
h("label", {}, ["Maximální velikost", sizeInput("pfMaximum", 4096, "MiB")]),482
h("label", { className: "check" }, [h("input", { id: "pfSystem", type: "checkbox" }), "velikost určí systém (0/0)"])483
]),484
h("p", { id: "pfError", className: "error", hidden: true }),485
h("div", { className: "inline-actions" }, h("button", { type: "button", className: "button primary", text: "Uložit (čeká na restart)", onclick: () => setPagefile(sessionId, vm) }))]));486
}487
children.push(h("p", { className: "hint", text: "Změny pagefile se uloží do konfigurace a projeví se až po restartu Windows; CH-J restart nikdy neprovádí. Velikost pagefile ovlivňuje Commit Limit a možnost uložit výpis paměti při pádu." }));488
return children;489
}491
function linuxDialog(sessionId, vm) {492
const mode = vm.privilege?.mode;493
const children = [h("p", { className: "hint", text: `${vm.os || "Linux"} · oprávnění: ${mode === "root" ? "root" : mode === "sudo-nopasswd" ? "sudo bez hesla" : mode === "sudo-password" ? "sudo s heslem (vyžádá se při změně)" : "bez sudo – jen čtení"} · RAM dostupná ${bytes(vm.memory.available)} · swap volný ${bytes(vm.memory.swapFree)}` })];494
if (!vm.canManage) children.push(h("p", { className: "error", text: vm.readOnlyReason }));495
children.push(h("div", { className: "table-wrap" }, h("table", {}, [496
h("thead", {}, h("tr", {}, ["Swap prostor", "Typ / FS", "Velikost", "Využito", "Priorita", "Stav", "Po restartu", "Akce"].map((text) => h("th", { text })))),497
h("tbody", {}, vm.swapAreas.length ? vm.swapAreas.map((area) => swapRow(sessionId, vm, area)) : [h("tr", {}, h("td", { colspan: 8, className: "empty", text: "Žádný swap není nakonfigurován." }))])])));498
if (vm.zram.length) children.push(h("p", { className: "hint", text: "zram zařízení jsou v této verzi jen pro čtení." }));499
if (vm.canManage) {500
const supported = new Set(["ext4", "ext2/ext3", "xfs", "btrfs", "f2fs"]);501
children.push(h("div", { className: "vm-form" }, [h("h4", { text: "Nový swapfile" }),502
h("div", { className: "form-grid" }, [503
h("label", {}, ["Umístění", h("select", { id: "swapDir" }, vm.locations.map((location) => h("option", { value: location.dir, disabled: !supported.has(location.fsType), text: `${location.dir} (${location.fsType}${supported.has(location.fsType) ? "" : " – nepodporováno"}, volno ${bytes(location.free)})` })))]),504
h("label", {}, ["Název souboru", h("input", { id: "swapName", value: "swapfile-chj", maxlength: "48" })]),505
h("label", {}, ["Velikost", sizeInput("swapSize", 1, "GiB")]),506
h("label", {}, ["Priorita (−1 = výchozí)", h("input", { id: "swapPriority", type: "number", min: "-1", max: "32767", value: "-1" })]),507
h("label", { className: "check" }, [h("input", { id: "swapPersist", type: "checkbox", checked: true }), "aktivovat i po restartu (/etc/fstab se zálohou)"])508
]),509
h("p", { id: "swapError", className: "error", hidden: true }),510
h("p", { className: "hint", text: "Swapfile se vytvoří zápisem všech bloků (dd) a mkswap, na Btrfs jako NOCOW (btrfs mkswapfile). Vyžaduje volné místo + 1 GiB rezervu. Pokud deaktivace stávajícího swapu není bezpečná, je lepší přidat nový swapfile." }),511
h("div", { className: "inline-actions" }, h("button", { type: "button", className: "button primary", text: "Vytvořit a aktivovat", onclick: () => createSwap(sessionId) }))]));512
}513
if (!vm.root && mode === "sudo-password") children.push(h("div", { className: "inline-actions" }, h("button", { type: "button", className: "button", text: "Načíst úplnou konfiguraci (sudo)", onclick: () => reloadVirtualMemory(sessionId, true) })));514
children.push(h("p", { className: "hint", text: "Deaktivace je odmítnuta, pokud by obsah swapu nemohl bezpečně přejít do RAM (rezerva 10 % RAM, min. 512 MiB, nejvýše čtvrtina RAM; nepoužitý swap lze vypnout vždy) nebo do jiného swapu. Oddíly CH-J nemaže ani nepřerozděluje; zram a zswap jsou jen pro čtení. Úpravy /etc/fstab zachovávají ostatní řádky a vytvářejí zálohu." }));515
return children;516
}518
function swapRow(sessionId, vm, area) {519
const allowed = vm.canManage ? new Set(area.allowedActions || []) : new Set();520
const actions = [];521
if (allowed.has("activate") && !area.active) actions.push(h("button", { type: "button", className: "mini", text: "Aktivovat", onclick: () => runVmChange(sessionId, `Aktivace ${area.path}`, { action: "activate", path: area.path, priority: area.persistentPriority ?? -1 }, [`swapon ${area.path}`]) }));522
if (allowed.has("deactivate") && area.active) actions.push(h("button", { type: "button", className: "mini", text: "Deaktivovat", onclick: () => runVmChange(sessionId, `Deaktivace ${area.path}`, { action: "deactivate", path: area.path }, [`swapoff ${area.path}: obsah (${bytes(area.used)}) se přesune do RAM nebo jiného swapu.`, "Před provedením se ověří dostupná RAM; při riziku se operace odmítne.", "Záznam v /etc/fstab zůstane beze změny."]) }));523
if (allowed.has("priority")) actions.push(h("button", { type: "button", className: "mini", text: "Priorita…", onclick: () => changePriority(sessionId, area) }));524
if (allowed.has("persist")) actions.push(h("button", { type: "button", className: "mini", text: area.persistent ? "Nepoužít po restartu" : "Použít po restartu", onclick: () => runVmChange(sessionId, area.persistent ? "Odebrání z /etc/fstab" : "Přidání do /etc/fstab", { action: "persist", path: area.path, persist: !area.persistent, priority: area.priority ?? -1 }, ["/etc/fstab bude zazálohován; ostatní řádky zůstanou beze změny."]) }));525
if (allowed.has("resize")) actions.push(h("button", { type: "button", className: "mini", text: "Změnit velikost…", onclick: () => resizeSwap(sessionId, area) }));526
if (allowed.has("delete")) actions.push(h("button", { type: "button", className: "mini danger", text: "Odstranit…", onclick: () => runVmChange(sessionId, `Odstranění ${area.path}`, { action: "delete", path: area.path }, [`Soubor ${area.path} bude ${area.active ? "deaktivován (s kontrolou paměti) a " : ""}smazán.`, area.persistent ? "Jeho záznam bude odebrán z /etc/fstab (se zálohou)." : "Není v /etc/fstab."]) }));527
const management = area.management === "partition" ? "oddíl – velikost a odstranění jen ručně" : area.management === "read-only" ? "jen pro čtení" : area.management === "foreign-file" ? "soubor mimo spravované cesty – bez změny velikosti a mazání" : "";528
return h("tr", {}, [h("td", { text: area.path }), h("td", { text: `${area.type === "zram" ? "zram" : area.type === "partition" ? "oddíl" : "soubor"}${area.fsType ? ` / ${area.fsType}` : ""}` }), h("td", { text: bytes(area.size) }),529
h("td", { text: area.active ? bytes(area.used) : "—" }), h("td", { text: String(area.priority ?? area.persistentPriority ?? "—") }),530
h("td", {}, h("span", { className: `pill ${area.active ? "ok" : ""}`, text: area.active ? "aktivní" : "neaktivní" })),531
h("td", { text: area.persistent ? `ano${area.persistentByChj ? " (CH-J)" : ""}${area.systemdUnit ? ` · ${area.systemdUnit.unit}` : ""}` : "ne" }),532
h("td", {}, [h("div", { className: "inline-actions" }, actions), management ? h("p", { className: "hint", text: management }) : null])]);533
}535
async function changePriority(sessionId, area) {536
const value = await askNumber(`Priorita ${area.path}`, "Priorita (−1 = výchozí, 0–32767)", area.priority ?? -1);537
if (value === null) return;538
if (!Number.isInteger(value) || value < -1 || value > 32767) { el.vmStatus.textContent = "Neplatná priorita (−1 až 32767)."; return; }539
await runVmChange(sessionId, `Priorita ${area.path}`, { action: "priority", path: area.path, priority: value }, [area.active ? "Aktivní swap se krátce deaktivuje a znovu aktivuje (s kontrolou paměti)." : "Uloží se pro další aktivaci.", "Případný záznam v /etc/fstab se upraví se zálohou."]);540
}542
async function resizeSwap(sessionId, area) {543
const value = await askNumber(`Nová velikost ${area.path}`, "Velikost v MiB (64 – 65536)", Math.round((area.size || 1073741824) / 1048576));544
if (value === null) return;545
if (!Number.isInteger(value) || value < 64 || value > 65536) { el.vmStatus.textContent = "Neplatná velikost (64 – 65536 MiB)."; return; }546
await runVmChange(sessionId, `Změna velikosti ${area.path}`, { action: "resize", path: area.path, sizeMiB: value, priority: area.priority ?? -1 }, [547
area.active ? `Swap se deaktivuje (jen pokud ${bytes(area.used)} bezpečně přejde do RAM nebo jiného swapu), znovu vytvoří a aktivuje.` : "Soubor se znovu vytvoří v nové velikosti.",548
"Nový soubor se nejprve sestaví vedle starého; při chybě se původní soubor znovu aktivuje.",549
"Pokud deaktivace není bezpečná, přidejte raději nový swapfile."]);550
}552
async function createSwap(sessionId) {553
const dir = el.swapDir.value; const name = el.swapName.value.trim(); const size = readSize("swapSize"); const priority = Number(el.swapPriority.value);554
const target = dir === "/" ? `/${name}` : `${dir}/${name}`;555
let problem = "";556
if (!/^[A-Za-z0-9._-]{1,48}$/.test(name)) problem = "Název smí obsahovat jen písmena, číslice, tečku, _ a -.";557
else if (dir === "/" && !/^swapfile/.test(name)) problem = "V kořenovém adresáři musí název začínat „swapfile“.";558
else if (!Number.isInteger(size) || size < 64 || size > 65536) problem = "Velikost musí být 64 MiB až 64 GiB.";559
else if (!Number.isInteger(priority) || priority < -1 || priority > 32767) problem = "Priorita musí být −1 až 32767.";560
el.swapError.textContent = problem; el.swapError.hidden = !problem;561
if (problem) return;562
await runVmChange(sessionId, `Vytvoření ${target}`, { action: "create", path: target, sizeMiB: size, priority, persist: el.swapPersist.checked }, [563
`Soubor ${target}, ${bytes(size * 1048576)}, priorita ${priority}.`, el.swapPersist.checked ? "Přidá se do /etc/fstab (se zálohou)." : "Nebude aktivní po restartu.", "Zápis velkého souboru může trvat několik minut."]);564
}566
async function setPagefile(sessionId, vm) {567
const system = el.pfSystem.checked;568
const initial = system ? 0 : readSize("pfInitial"); const maximum = system ? 0 : readSize("pfMaximum"); const drive = el.pfDrive.value;569
let problem = "";570
if (!drive) problem = "Vyberte svazek NTFS.";571
else if (!system && (!Number.isInteger(initial) || !Number.isInteger(maximum) || initial < 16 || maximum < initial)) problem = "Počáteční velikost musí být alespoň 16 MiB a maximální alespoň počáteční.";572
el.pfError.textContent = problem; el.pfError.hidden = !problem;573
if (problem) return;574
await runVmChange(sessionId, `Pagefile ${drive}`, { action: "set", drive, initialMb: initial, maximumMb: maximum }, [575
vm.automatic ? "Automatická správa Windows bude vypnuta (ruční správa)." : "Ruční správa zůstane zapnutá.",576
system ? `Na ${drive} bude pagefile se systémem spravovanou velikostí.` : `${drive}\\pagefile.sys: ${bytes(initial * 1048576)} – ${bytes(maximum * 1048576)}.`,577
"Konfigurace se zazálohuje a projeví po restartu Windows; CH-J restart neprovede.", "Změní se Commit Limit; příliš malý pagefile může znemožnit výpis paměti při pádu."]);578
}580
async function removePagefile(sessionId, vm, file) {581
const last = vm.pagefiles.filter((entry) => entry.configured).length <= 1;582
const crash = file.drive === vm.systemDrive && Boolean(vm.crashDumpEnabled);583
await runVmChange(sessionId, `Odebrání ${file.name}`, { action: "remove", drive: file.drive, confirmLast: last, confirmCrashDump: crash }, [584
"Konfigurace se zazálohuje; změna se projeví po restartu Windows.",585
last ? "Jde o POSLEDNÍ pagefile: Commit Limit klesne na velikost RAM a hrozí chyby nedostatku paměti." : "",586
crash ? "Je na systémovém svazku a výpis paměti při pádu je zapnut – bez něj se výpis nemusí uložit." : ""].filter(Boolean),587
last || crash ? "Rozumím rizikům a výslovně potvrzuji odebrání" : "");588
}590
// ---------- events ----------591
el.closeButton.addEventListener("click", () => api.close());592
el.refreshSessionsButton.addEventListener("click", () => { void loadSessions(); });593
el.refreshMetricsButton.addEventListener("click", () => { void loadMetrics(); });594
el.sessionSelect.addEventListener("change", () => { resetSession(); el.refreshMetricsButton.disabled = !el.sessionSelect.value; void loadMetrics(); });595
el.manageVmButton.addEventListener("click", () => { void openVirtualMemory(); });596
// Monitoring loop: metrics only, never configuration changes or administrative checks.597
setInterval(() => { if (el.autoRefresh.checked) void loadMetrics(); }, 5000);599
(async () => {600
try { const info = await api.getInfo(); el.pluginVersion.textContent = `${info.name} v${info.version} · ${info.publisher}`; } catch {}601
await loadSessions();602
await loadMetrics();603
})().catch((error) => setStatus(describe(error), true));SHA-256
977f200b334564bd80e826693a9e292a25fd8fc09b417977afb1f5616e9bdf71CH-J Proprietary Software License 1.14 · Mã nguồn được cung cấp theo CH-J Proprietary Software License 1.14. Việc công khai mã nguồn không thay đổi các điều khoản giấy phép và không cấp thêm quyền nào.
SHA-256 của tệp nén
cf4455b698f0f28e456a37b1834976a0194d8486acb7101052d59a838745cca3