CH-J Toolsġestjoni tas-server fuq SSH
Menu
Sors ippubblikat

CH-J Tools

direttorji browse u fajls għal rilaxx applikazzjoni speċifika.

Sors ta' tniżżil ZIP
CH-J / Server Manager 1704 fajls
0.1.2-beta Miżjud: · Buillt:
index.js Miżjud: Data mhux irreġistrata 28.4 KB · 384 linji
Kodiċi Fajl tat-tniżżil
1"use strict";
3const api = window.chjPlugin;
4const i18n = window.chjPluginI18n.createI18n("cs");
5const t = i18n.t;
6const elements = Object.fromEntries(["pluginVersion", "closeButton", "generatorForm", "keyType", "bitsLabel", "keyBits", "keyFormat", "keyComment", "keyPassphrase", "legacyField", "legacyConfirmed", "saveButton", "status", "result", "privateKey", "privateFormat", "publicKey", "publicFormat", "notice"].map((id) => [id, document.querySelector(`#${id}`)]));
7const bits = { rsa: [2048, 3072, 4096], ecdsa: [256, 384, 521], dsa: [1024], rsa1: [1024] };
8let generationId = null;
9let pgpGenerationId = null;
10let pgpBusy = false;
11let pgpAlgorithmCatalog = null;
12let modeRevision = -1;
13let pgpEditor = null;
14let activeRequest = null;
15let progressStarted = 0;
16let progressPhase = "prepare";
17function startProgress() {
18 activeRequest = globalThis.crypto.randomUUID?.() || [...globalThis.crypto.getRandomValues(new Uint8Array(16))].map(v=>v.toString(16).padStart(2,"0")).join(""); progressStarted = Date.now(); progressPhase = "prepare";
19 document.querySelector("#pgpProgress").hidden = false; renderProgress(); return activeRequest;
21function renderProgress() {
22 if (!activeRequest) return;
23 const labels = {prepare:"Připravuji generování…",primary:"Generuji hlavní klíč…",subkey:"Generuji podklíč a jeho vazbu…",revocation:"Vytvářím revokační certifikát…",protect:"Chráním tajné klíče heslem…",verify:"Ověřuji certifikát…",unlock:"Odemykám hlavní klíč…"};
24 document.querySelector("#pgpProgressText").textContent = t(labels[progressPhase] || labels.prepare);
25 document.querySelector("#pgpElapsed").textContent = t("Uplynulo {seconds} s",{seconds:((Date.now()-progressStarted)/1000).toFixed(1)});
27function finishProgress() {activeRequest=null;document.querySelector("#pgpProgress").hidden=true;}
28setInterval(renderProgress,100);
30function setStatus(message, error = false) {
31 elements.status.textContent = message;
32 elements.status.classList.toggle("error", error);
35function updateType() {
36 const type = elements.keyType.value;
37 const choices = bits[type] || [];
38 elements.keyBits.replaceChildren(...choices.map((value) => new Option(String(value), String(value), value === (type === "rsa" ? 4096 : choices[0]), value === (type === "rsa" ? 4096 : choices[0]))));
39 elements.bitsLabel.hidden = choices.length === 0;
40 elements.legacyField.hidden = type !== "dsa" && type !== "rsa1";
41 if (elements.legacyField.hidden) elements.legacyConfirmed.checked = false;
42 if (type === "rsa1") elements.keyFormat.value = "ppk";
43 if (type === "mldsa44-ed25519") elements.keyFormat.value = "openssh";
44 elements.keyFormat.disabled = ["rsa1","mldsa44-ed25519"].includes(type);
47elements.keyType.addEventListener("change", updateType);
48elements.closeButton.addEventListener("click", () => api.close());
49elements.generatorForm.addEventListener("submit", async (event) => {
50 event.preventDefault();
51 setStatus(t("Generuji klíčový pár…"));
52 const submit = elements.generatorForm.querySelector('[type="submit"]'); submit.disabled = true;
53 generationId = null; elements.saveButton.disabled = true; document.querySelector("#storeSshButton").disabled = true;
54 try {
55 const generated = await api.keys.generate({
56 type: elements.keyType.value,
57 bits: Number(elements.keyBits.value || 0),
58 comment: elements.keyComment.value,
59 passphrase: elements.keyPassphrase.value,
60 privateKeyFormat: elements.keyFormat.value,
61 legacyConfirmed: elements.legacyConfirmed.checked
62 });
63 generationId = generated.generationId;
64 elements.privateKey.value = generated.privateKey;
65 elements.privateFormat.textContent = { openssh: "OpenSSH", pem: "PEM / PKCS#8", ppk: "PuTTY PPK v2" }[generated.privateKeyFormat] || generated.privateKeyFormat;
66 elements.publicKey.value = generated.publicKey;
67 elements.publicFormat.textContent = generated.publicKeyOpenSsh ? "OpenSSH" : "SPKI PEM";
68 elements.notice.hidden = !generated.notice;
69 elements.notice.textContent = generated.notice;
70 elements.result.hidden = false;
71 elements.saveButton.disabled = false;
72 document.querySelector("#storeSshButton").disabled = false;
73 elements.keyPassphrase.value = "";
74 setStatus(t("{type} bylo vygenerováno.", { type: `${generated.type}${generated.bits ? ` / ${generated.bits}` : ""}` }));
75 } catch (error) { setStatus(error?.message || String(error), true); }
76 finally { submit.disabled = false; }
77});
78elements.saveButton.addEventListener("click", async () => {
79 if (!generationId) return;
80 try {
81 const result = await api.keys.save(generationId);
82 if (!result.canceled) setStatus(t("Uloženo: {privatePath} a {publicPath}", { privatePath: result.privatePath, publicPath: result.publicPath }));
83 } catch (error) { setStatus(error?.message || String(error), true); }
84});
86// ---------- password generator ----------
87let passwordId = null;
88const passwordElements = Object.fromEntries(["passwordForm", "passwordLength", "passwordLengthRange", "pwUpper", "pwLower", "pwDigits", "pwSpecial", "pwGrouped", "passwordOutput", "copyPasswordButton", "passwordInfo"].map((id) => [id, document.querySelector(`#${id}`)]));
89passwordElements.passwordLength.addEventListener("input", () => { passwordElements.passwordLengthRange.value = passwordElements.passwordLength.value; });
90passwordElements.passwordLengthRange.addEventListener("input", () => { passwordElements.passwordLength.value = passwordElements.passwordLengthRange.value; });
91passwordElements.passwordForm.addEventListener("submit", async (event) => {
92 event.preventDefault();
93 passwordElements.copyPasswordButton.disabled = true;
94 try {
95 const result = await api.keys.password({
96 length: Number(passwordElements.passwordLength.value),
97 upper: passwordElements.pwUpper.checked, lower: passwordElements.pwLower.checked,
98 digits: passwordElements.pwDigits.checked, special: passwordElements.pwSpecial.checked,
99 grouped: passwordElements.pwGrouped.checked
100 });
101 passwordElements.passwordOutput.value = result.password;
102 passwordId = result.passwordId;
103 passwordElements.copyPasswordButton.disabled = false;
104 setStatus(t("Heslo: {length} znaků, {random} náhodných z {pool} možných (přibližně {bits} bitů entropie).", { length: result.length, random: result.randomCharacters, pool: result.poolSize, bits: result.entropyBits }));
105 } catch (error) { passwordElements.passwordOutput.value = ""; setStatus(error?.message || String(error), true); }
106});
107// Core copies the password (plugin windows have no clipboard permission).
108passwordElements.copyPasswordButton.addEventListener("click", async () => {
109 try { await api.keys.copyPassword(passwordId); setStatus(t("Heslo zkopírováno do schránky.")); }
110 catch (error) { setStatus(error?.message || String(error), true); }
111});
113(async () => {
114 try { i18n.setLanguage(await api.ui.getLanguage()); } catch {}
115 i18n.apply(document);
116 api.ui.onLanguageChanged((language) => { i18n.setLanguage(language); i18n.apply(document); updatePgpPreset(); });
117 const info = await api.getInfo();
118 elements.pluginVersion.textContent = `${info.name} v${info.version} · ${info.publisher}`;
119 updateType(); await refreshMode(); addSubkey("encrypt", "curve25519"); updatePgpPreset();
120 api.keys.onModeChanged(applyModeState);
121 await refreshMode();
122})().catch((error) => setStatus(error?.message || String(error), true));
125function selectMode(mode) {
126 if (!["ssh", "openpgp", "password"].includes(mode)) return;
127 document.querySelectorAll("[data-mode]").forEach(button => { const active = button.dataset.mode === mode; button.setAttribute("aria-selected", String(active)); button.tabIndex = active ? 0 : -1; });
128 document.querySelectorAll("[data-generator-panel]").forEach(panel => { panel.hidden = panel.dataset.generatorPanel !== mode; });
130function applyModeState(state) {
131 if (state.revision > modeRevision) { modeRevision = state.revision; selectMode(state.mode); }
133async function refreshMode() {
134 try {
135 const state = await api.keys.state(); applyModeState(state);
136 if (!pgpAlgorithmCatalog && Array.isArray(state.openpgpAlgorithms)) { pgpAlgorithmCatalog=state.openpgpAlgorithms; updatePgpPreset(); }
137 if (activeRequest && state.progress?.requestId === activeRequest) { progressPhase = state.progress.phase; renderProgress(); }
138 const context = state.editContext;
139 if (context && !pgpBusy && (pgpEditor?.source !== "stored" || pgpEditor.token !== context.token)) {
140 pgpGenerationId = null; document.querySelector("#pgpFiles").replaceChildren(); document.querySelector("#pgpIdentity").textContent="";
141 document.querySelector("#storePgpButton").disabled=true; document.querySelector("#savePgpBundle").disabled=true;
142 renderEditor({source:"stored",token:context.token,identity:context.identity}); selectMode("openpgp");
143 } else if (!context && pgpEditor?.source === "stored") {clearEditor();}
144 } catch {}
146document.querySelectorAll("[data-mode]").forEach(button => button.addEventListener("click", () => selectMode(button.dataset.mode)));
147setInterval(() => { if (!document.hidden) void refreshMode(); }, 1500);
148window.addEventListener("focus", () => void refreshMode());
150async function storeGenerated(id, button) {
151 if (!id) return;
152 button.disabled = true;
153 try {
154 const result = await api.keys.storeKeychain(id);
155 setStatus(t(result.needsUnlock ? "Odemkněte trezor a poté znovu zvolte Uložit do Keychainu." : "Klíč byl uložen do Keychainu."));
156 } catch (error) { setStatus(error?.message || String(error), true); }
157 finally { button.disabled = false; }
159document.querySelector("#storeSshButton").addEventListener("click", event => void storeGenerated(generationId, event.currentTarget));
160document.querySelector("#storePgpButton").addEventListener("click", event => void storeGenerated(pgpGenerationId, event.currentTarget));
162function pgpPreset(version) {
163 return {
164 2:{supported:true,legacy:true,notice:"V2: legacy PGP 2.x / RFC 1991, RSA, odvozené C/S/E bez A a podklíčů. MD5/IDEA jsou překonané. Identita a heslo pouze ASCII; platnost bez omezení."},
165 3:{supported:true,legacy:true,notice:"V3: legacy PGP, RSA, odvozené C/S/E bez A a podklíčů. MD5/IDEA jsou překonané. Identita a heslo pouze ASCII; platnost se zaokrouhlí nahoru na celé dny."},
166 4:{supported:true,notice:"V4: předvolba pro kompatibilitu. C/S/A/E se ukládají jako Key Flags do podpisů."},
167 5:{supported:true,notice:"V5: alternativa LibrePGP (draft-koch-librepgp-06), vlastní formát mimo RFC 9580. Není zaměnitelná s V6; ověřte podporu příjemce. AES-256/S2K ochrana heslem."},
168 6:{supported:true,notice:"V6: RFC 9580, moderní Ed25519 / X25519 a nativní Key Flags."}
169 }[version] || {supported:false,notice:"Nepodporovaná verze PGP."};
171function availableAlgorithms(version, capability) {
172 return (pgpAlgorithmCatalog || []).filter(spec => spec.versions.includes(version) && spec.capabilities.includes(capability));
174function algorithmOptions(select,ids,version) {
175 select.replaceChildren();
176 for (const category of ["classic","post-quantum"]) {
177 const specs=ids.filter(id=>(pgpAlgorithmCatalog || []).find(s=>s.id===id)?.category===category);
178 if (!specs.length) continue;
179 const group=document.createElement("optgroup");group.label=t(category==="post-quantum"?"Post-Quantum / Hybrid":"Klasické algoritmy");
180 for (const id of specs)group.append(new Option(algorithmLabel(id,version),id));select.append(group);
181 }
183function algorithmLabel(id, version) {
184 if (id === "curve25519" && version === 6) return "X25519";
185 return (pgpAlgorithmCatalog || []).find(spec => spec.id === id)?.label || id;
187function updatePgpPreset() {
188 const version = Number(document.querySelector("#pgpVersion").value), preset = pgpPreset(version);
189 document.querySelector("#pgpVersionNotice").textContent = t(preset.notice);
190 const legacy = !!preset.legacy, confirmation = document.querySelector("#pgpLegacyConfirmed");
191 document.querySelector("#pgpLegacyField").hidden = !legacy;
192 if (!legacy) confirmation.checked = false;
193 document.querySelector("#generatePgpButton").disabled = pgpBusy || !preset.supported || !pgpAlgorithmCatalog || (legacy && !confirmation.checked);
194 document.querySelector("#pgpSubkeysField").hidden = legacy;
195 document.querySelector("#pgpExpires").disabled = version === 2;
196 const primary = document.querySelector("#pgpAlgorithm"), previous = primary.value;
197 const algorithms = availableAlgorithms(version,"C").map(spec=>spec.id);
198 algorithmOptions(primary,algorithms,version);
199 primary.value = algorithms.includes(previous) ? previous : algorithms[0];
200 const allowedCaps=(pgpAlgorithmCatalog || []).find(spec=>spec.id===primary.value)?.capabilities || "";
201 const caps = document.querySelector("#pgpPrimaryCapabilities");
202 for (const input of caps.querySelectorAll('input')) {
203 input.disabled = legacy || input.value === "C" || !preset.supported || !allowedCaps.includes(input.value);
204 if (legacy) input.checked = "CSE".includes(input.value);
205 else if (input.disabled && input.value !== "C") input.checked = false;
206 }
207 for (const row of document.querySelector("#pgpSubkeys").children) row.updateAlgorithms?.();
208 if (pgpEditor) updateEditAlgorithms();
210document.querySelector("#pgpVersion").addEventListener("change", () => { document.querySelector("#pgpLegacyConfirmed").checked=false; updatePgpPreset(); });
211document.querySelector("#pgpAlgorithm").addEventListener("change", updatePgpPreset);
212document.querySelector("#pgpLegacyConfirmed").addEventListener("change", updatePgpPreset);
213function subkeyAlgorithms(role, version = Number(document.querySelector("#pgpVersion").value)) { return availableAlgorithms(version,{encrypt:"E",sign:"S",authenticate:"A"}[role] || "S").map(spec=>spec.id); }
214function addSubkey(role = "sign", algorithm = "ed25519") {
215 const host = document.querySelector("#pgpSubkeys");
216 if (host.children.length >= 8) return;
217 const row = document.createElement("div"); row.className = "pgp-subkey";
218 const purpose = document.createElement("select"); purpose.dataset.field = "role"; purpose.setAttribute("aria-label", t("Účel podklíče"));
219 for (const [value,label] of [["encrypt","E · Šifrování"],["sign","S · Podepisování"],["authenticate","A · Autentizace"]]) purpose.append(new Option(t(label), value));
220 purpose.value = role;
221 const type = document.createElement("select"); type.dataset.field = "algorithm"; type.setAttribute("aria-label", t("Algoritmus podklíče"));
222 const capabilities = document.createElement("div"); capabilities.className = "capabilities subkey-capabilities";
223 for (const value of "SAE") {
224 const label = document.createElement("label"), input = document.createElement("input"); input.type = "checkbox"; input.value = value; input.dataset.capability = value;
225 label.append(input,document.createTextNode(value)); capabilities.append(label);
226 }
227 const validate = (reset = false) => {
228 for (const input of capabilities.querySelectorAll("input")) {
229 input.disabled = !(pgpAlgorithmCatalog || []).find(spec=>spec.id===type.value)?.capabilities.includes(input.value);
230 if (input.disabled) input.checked = false;
231 else if (reset) input.checked = input.value === {encrypt:"E",sign:"S",authenticate:"A"}[purpose.value];
232 }
233 };
234 const update = (preserve = false) => {
235 const previous=type.value, choices=subkeyAlgorithms(purpose.value);
236 algorithmOptions(type,choices,Number(document.querySelector("#pgpVersion").value));
237 if (preserve && choices.includes(previous)) type.value=previous;
238 validate(!preserve || !choices.includes(previous));
239 };
240 row.updateAlgorithms=()=>update(true);
241 update(); type.value = algorithm; validate(true);
242 purpose.addEventListener("change", () => update(false)); type.addEventListener("change", () => validate(true));
243 const remove = document.createElement("button"); remove.type = "button"; remove.className = "button"; remove.textContent = t("Odebrat"); remove.addEventListener("click", () => row.remove());
244 const expiry=document.createElement("input");expiry.type="datetime-local";expiry.dataset.field="expiresAt";expiry.value=defaultExpiry();expiry.setAttribute("aria-label",t("Platnost do (prázdné = bez omezení)"));
245 row.append(purpose,type,capabilities,expiry,remove); host.append(row);
247document.querySelector("#addSubkey").addEventListener("click", () => addSubkey());
248function renderPgpFiles(generated) {
249 const host = document.querySelector("#pgpFiles"); host.replaceChildren();
250 generated.files.forEach((file,index) => {
251 const card = document.createElement("article"); card.className = "panel pgp-file";
252 const heading = document.createElement("div"); heading.className = "result-title";
253 const name = document.createElement("strong"); name.textContent = file.name;
254 const save = document.createElement("button"); save.className = "button"; save.type = "button"; save.textContent = t("Uložit soubor…");
255 save.addEventListener("click", async () => {
256 try { const result = await api.keys.saveArtifact(generated.generationId,index); if (!result.canceled) setStatus(t("Soubor byl uložen.")); }
257 catch (error) { setStatus(error?.message || String(error),true); }
258 });
259 const text = document.createElement("textarea"); text.readOnly = true; text.spellcheck = false; text.value = file.content; text.setAttribute("aria-label", file.name);
260 heading.append(name,save); card.append(heading,text); host.append(card);
261 });
262 const identity = generated.identity;
263 const capabilities = key => `V${key.version || "?"} · ${key.algorithm} · ID ${key.algorithmId ?? "?"} · ${key.keyId || ""}${key.qBits ? ` · DSA ${key.bits}/q${key.qBits} · ${key.signatureHash}` : ""} · ${key.fingerprint} [${key.effectiveCapabilities || "—"}] · ${t("Deklarované")}: ${key.declaredCapabilities || "—"} · ${t("Odvozené")}: ${key.derivedCapabilities || "—"} · ${t("Efektivní")}: ${key.effectiveCapabilities || "—"}`;
264 const operations=[["Import",identity.importSupported],["Export veřejného klíče",identity.exportPublicSupported],["Export soukromého klíče",identity.exportPrivateSupported],["Podepisování",identity.signSupported],["Šifrování",identity.encryptSupported],["Revokace",identity.revokeSupported]].filter(([,supported])=>supported).map(([label])=>t(label)).join(" · ");
265 document.querySelector("#pgpIdentity").textContent = [identity.label,`${identity.serializationProfile || "OpenPGP"} · ${operations}`,`${t("Certifikát celkem")}: [${identity.certificateCapabilities || "—"}]`,capabilities(identity),...(identity.subkeys || []).map(key => `${t("Podklíč")}: ${key.algorithm} · ${capabilities(key)}`)].join("\n");
267document.querySelector("#pgpForm").addEventListener("submit", async event => {
268 event.preventDefault();
269 const field = id => document.querySelector(`#${id}`);
270 if (field("pgpPassphrase").value !== field("pgpPassphraseConfirm").value) { setStatus(t("Hesla klíče se neshodují."),true); return; }
271 if (!pgpPreset(Number(field("pgpVersion").value)).supported || pgpBusy) return;
272 const previousEditor=pgpEditor; pgpBusy = true; clearEditor(); const requestId = startProgress();
273 pgpGenerationId = null; field("storePgpButton").disabled = true; field("savePgpBundle").disabled = true; field("generatePgpButton").disabled = true;
274 field("pgpFiles").replaceChildren(); field("pgpIdentity").textContent = ""; setStatus(t("Generuji OpenPGP identitu a podklíče…"));
275 try {
276 if(previousEditor?.source==="stored")await api.keys.edit({operation:"closeEditor",editToken:previousEditor.token});
277 const generated = await api.keys.generate({ family:"openpgp", requestId, legacyConfirmed:field("pgpLegacyConfirmed").checked, expiresAt:Number(field("pgpVersion").value) === 2 ? null : expirationValue(field("pgpExpires").value), version:Number(field("pgpVersion").value), primaryCapabilities:[...field("pgpPrimaryCapabilities").querySelectorAll("input:checked")].map(input=>input.value).join(""), name:field("pgpName").value, email:field("pgpEmail").value, comment:field("pgpComment").value, algorithm:field("pgpAlgorithm").value, passphrase:field("pgpPassphrase").value,
278 subkeys:pgpPreset(Number(field("pgpVersion").value)).legacy ? [] : [...field("pgpSubkeys").children].map(row => ({ expiresAt:expirationValue(row.querySelector('[data-field="expiresAt"]').value),role:row.querySelector('[data-field="role"]').value,algorithm:row.querySelector('[data-field="algorithm"]').value,capabilities:[...row.querySelectorAll("[data-capability]:checked")].map(input=>input.value).join("") })) });
279 pgpGenerationId = generated.generationId; renderPgpFiles(generated); renderEditor({source:"generated",generationId:pgpGenerationId,identity:generated.identity});
280 field("storePgpButton").disabled = false; field("savePgpBundle").disabled = false;
281 field("pgpPassphrase").value = ""; field("pgpPassphraseConfirm").value = "";
282 setStatus(t("OpenPGP identita a všechny soubory byly vygenerovány."));
283 } catch (error) { setStatus(error?.message || String(error),true); }
284 finally { pgpBusy = false; finishProgress(); updatePgpPreset(); if(pgpEditor)renderEditor(pgpEditor); }
285});
286document.querySelector("#savePgpBundle").addEventListener("click", async () => {
287 if (!pgpGenerationId) return;
288 try { const result = await api.keys.saveBundle(pgpGenerationId); if (!result.canceled) setStatus(t("Všechny soubory byly uloženy.")); }
289 catch (error) { setStatus(error?.message || String(error),true); }
290});
292function expirationValue(value) {
293 if (!value) return null;
294 const seconds=Math.floor(new Date(value).getTime()/1000);
295 if (!Number.isFinite(seconds) || seconds<=Date.now()/1000) throw Error(t("Zvolte budoucí datum platnosti."));
296 return seconds;
298// Owner's default (2026-11-05): new OpenPGP keys and subkeys are valid for 5 years; empty = no limit.
299function defaultExpiry() {
300 const date=new Date();date.setFullYear(date.getFullYear()+5);return localDateTime(Math.floor(date.getTime()/1000));
302function localDateTime(seconds) {
303 if (!seconds) return "";
304 const date=new Date(seconds*1000);date.setMinutes(date.getMinutes()-date.getTimezoneOffset());return date.toISOString().slice(0,16);
306function clearEditor() {
307 pgpEditor=null; document.querySelector("#pgpEditor").hidden=true;
308 document.querySelector("#pgpEditorIdentity").textContent="";document.querySelector("#pgpEditComponents").replaceChildren();
309 document.querySelector("#pgpEditPassphrase").value="";
310 document.querySelector("#pgpMessageInput").value="";document.querySelector("#pgpMessageOutput").value="";
312function updateEditAlgorithms() {
313 const role=document.querySelector("#pgpEditRole").value, version=pgpEditor?.identity.version || 6;
314 const field=document.querySelector("#pgpEditAlgorithm"), previous=field.value, choices=subkeyAlgorithms(role,version);
315 algorithmOptions(field,choices,version);
316 if (choices.includes(previous)) field.value=previous;
318function renderEditor(editor) {
319 if (editor.identity.editSupported === false) { clearEditor(); return; }
320 pgpEditor=editor;
321 const identity=editor.identity, host=document.querySelector("#pgpEditComponents");host.replaceChildren();
322 document.querySelector("#pgpEditor").hidden=false;
323 document.querySelector("#pgpEditorIdentity").textContent=`${identity.label} · V${identity.version} · ${identity.fingerprint} [${identity.effectiveCapabilities || "—"}] · ${t(editor.source==="stored"?"Uložená identita":"Vygenerovaná identita")}`;
324 for (const [index,key] of [identity,...(identity.subkeys || [])].entries()) {
325 const row=document.createElement("div");row.className="pgp-component";
326 const label=document.createElement("p");label.textContent=`${t(index===0?"Hlavní klíč":"Podklíč")} · ${key.algorithm} · ${key.fingerprint} [${key.effectiveCapabilities || "—"}]${key.revoked?" · "+t("Odvoláno"):key.expired?" · "+t("Platnost vypršela"):""}`;
327 const expiry=document.createElement("input");expiry.type="datetime-local";expiry.value=localDateTime(key.expiresAt);expiry.setAttribute("aria-label",t("Platnost do (prázdné = bez omezení)"));
328 const save=document.createElement("button");save.type="button";save.className="button";save.textContent=t("Uložit platnost");save.disabled=key.revoked || identity.revoked || pgpBusy;
329 save.addEventListener("click",()=>{try{void editPgp({operation:"setExpiration",fingerprint:index?key.fingerprint:undefined,expiresAt:expirationValue(expiry.value)});}catch(error){setStatus(error.message,true);}});
330 row.append(label,expiry,save);
331 if (index) {
332 const revoke=document.createElement("button");revoke.type="button";revoke.className="button";revoke.textContent=t("Odvolat podklíč");revoke.disabled=key.revoked || identity.revoked || pgpBusy;
333 revoke.addEventListener("click",()=>{if(window.confirm(t("Trvale odvolat tento podklíč?")))void editPgp({operation:"revokeSubkey",fingerprint:key.fingerprint,confirmed:true});});row.append(revoke);
334 }
335 host.append(row);
336 }
337 for (const user of identity.userIdentityDetails || []) {
338 const row=document.createElement("div"),label=document.createElement("p");label.textContent=user.userId+(user.revoked?" · "+t("Odvoláno"):"");row.append(label);
339 if (!user.revoked && !identity.revoked && [4,6].includes(identity.version)) {
340 const revoke=document.createElement("button");revoke.type="button";revoke.className="button";revoke.textContent=t("Odvolat identitu");revoke.disabled=pgpBusy;
341 revoke.addEventListener("click",()=>{if(window.confirm(t("Trvale odvolat tuto identitu?")))void editPgp({operation:"revokeUserId",userId:user.userId,confirmed:true});});row.append(revoke);
342 }host.append(row);
343 }
344 document.querySelector("#pgpTextOperations").hidden=![4,6].includes(identity.version);
345 for(const button of document.querySelectorAll("[data-text-operation]"))button.disabled=pgpBusy || (button.dataset.textOperation==="signText" && !identity.inlineSignSupported) || (button.dataset.textOperation==="encryptText" && !identity.encryptSupported) || (button.dataset.textOperation==="decryptText" && !identity.decryptSupported);
346 document.querySelector("#pgpEditAdd").disabled=identity.revoked || identity.expired || pgpBusy;
347 updateEditAlgorithms();
349async function editPgp(payload) {
350 if (!pgpEditor || pgpBusy) return;
351 const editor=pgpEditor;
352 pgpBusy=true;const requestId=startProgress();updatePgpPreset();
353 document.querySelectorAll("#pgpEditor button").forEach(button=>{button.disabled=true;});
354 try {
355 const result=await api.keys.edit({...payload,requestId,passphrase:document.querySelector("#pgpEditPassphrase").value,...(editor.source==="stored"?{editToken:editor.token}:{generationId:editor.generationId})});
356 // A lock or new selection may have cleared this editor during the operation.
357 if (pgpEditor !== editor) return;
358 if(editor.source==="generated")renderPgpFiles(result);
359 editor.identity=result.identity;
360 document.querySelector("#pgpEditPassphrase").value="";
361 setStatus(t("Klíč byl aktualizován."));
362 }catch(error){setStatus(error?.message || String(error),true);}
363 finally{pgpBusy=false;finishProgress();updatePgpPreset();if(pgpEditor===editor)renderEditor(editor);}
365document.querySelector("#pgpEditRole").addEventListener("change",updateEditAlgorithms);
366document.querySelector("#pgpAddExistingForm").addEventListener("submit",event=>{
367 event.preventDefault();
368 try {void editPgp({operation:"addSubkey",subkey:{role:document.querySelector("#pgpEditRole").value,algorithm:document.querySelector("#pgpEditAlgorithm").value,expiresAt:expirationValue(document.querySelector("#pgpEditExpires").value)}});}
369 catch(error){setStatus(error?.message || String(error),true);}
370});
372document.querySelector("#cancelCrypto").addEventListener("click",()=>api.close());
373for (const button of document.querySelectorAll("[data-text-operation]"))button.addEventListener("click",async()=>{
374 if (!pgpEditor || pgpBusy)return;
375 const editor=pgpEditor;pgpBusy=true;const requestId=startProgress();renderEditor(editor);
376 document.querySelector("#pgpMessageOutput").value="";
377 try {
378 const result=await api.keys.edit({operation:button.dataset.textOperation,requestId,text:document.querySelector("#pgpMessageInput").value,passphrase:document.querySelector("#pgpEditPassphrase").value,...(editor.source==="stored"?{editToken:editor.token}:{generationId:editor.generationId})});
379 if (pgpEditor!==editor)return;
380 document.querySelector("#pgpMessageOutput").value=result.text;document.querySelector("#pgpEditPassphrase").value="";setStatus(t(result.verified?"Podpis je platný.":"Operace dokončena."));
381 } catch(error){setStatus(error?.message || String(error),true);}
382 finally {pgpBusy=false;finishProgress();if(pgpEditor===editor)renderEditor(editor);}
383});
384for (const id of ["#pgpExpires","#pgpEditExpires"]) { const input=document.querySelector(id); if (input && !input.value) input.value=defaultExpiry(); }
SHA-2567314ed72575f36cca1603b93bcd50855dd0d8a17dee5c33cb3d374cc758662de

CH-J Proprietary Software License 1.14 · Sors huwa pprovdut taħt iż-CH-J Proprietary Software License 1.14. Id-disponibbiltà tagħha ma tibdilx it-termini tal-liċenzja jew tagħti drittijiet addizzjonali.

Archive SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Fajls