Published source
Download source ZIP
CH-J Tools
Browse directories and files for a specific application release.
CH-J / Server Manager
1704 files
0.1.2-beta
Added: · Built:
1
"use strict";3
const api = window.chjPlugin;4
const i18n = window.chjPluginI18n.createI18n("cs");5
const t = i18n.t;6
const 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}`)]));7
const bits = { rsa: [2048, 3072, 4096], ecdsa: [256, 384, 521], dsa: [1024], rsa1: [1024] };8
let generationId = null;9
let pgpGenerationId = null;10
let pgpBusy = false;11
let pgpAlgorithmCatalog = null;12
let modeRevision = -1;13
let pgpEditor = null;14
let activeRequest = null;15
let progressStarted = 0;16
let progressPhase = "prepare";17
function 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;20
}21
function 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)});26
}27
function finishProgress() {activeRequest=null;document.querySelector("#pgpProgress").hidden=true;}28
setInterval(renderProgress,100);30
function setStatus(message, error = false) {31
elements.status.textContent = message;32
elements.status.classList.toggle("error", error);33
}35
function 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);45
}47
elements.keyType.addEventListener("change", updateType);48
elements.closeButton.addEventListener("click", () => api.close());49
elements.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.checked62
});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
});78
elements.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 ----------87
let passwordId = null;88
const passwordElements = Object.fromEntries(["passwordForm", "passwordLength", "passwordLengthRange", "pwUpper", "pwLower", "pwDigits", "pwSpecial", "pwGrouped", "passwordOutput", "copyPasswordButton", "passwordInfo"].map((id) => [id, document.querySelector(`#${id}`)]));89
passwordElements.passwordLength.addEventListener("input", () => { passwordElements.passwordLengthRange.value = passwordElements.passwordLength.value; });90
passwordElements.passwordLengthRange.addEventListener("input", () => { passwordElements.passwordLength.value = passwordElements.passwordLengthRange.value; });91
passwordElements.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.checked100
});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).108
passwordElements.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));125
function 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; });129
}130
function applyModeState(state) {131
if (state.revision > modeRevision) { modeRevision = state.revision; selectMode(state.mode); }132
}133
async 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 {}145
}146
document.querySelectorAll("[data-mode]").forEach(button => button.addEventListener("click", () => selectMode(button.dataset.mode)));147
setInterval(() => { if (!document.hidden) void refreshMode(); }, 1500);148
window.addEventListener("focus", () => void refreshMode());150
async 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; }158
}159
document.querySelector("#storeSshButton").addEventListener("click", event => void storeGenerated(generationId, event.currentTarget));160
document.querySelector("#storePgpButton").addEventListener("click", event => void storeGenerated(pgpGenerationId, event.currentTarget));162
function 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."};170
}171
function availableAlgorithms(version, capability) {172
return (pgpAlgorithmCatalog || []).filter(spec => spec.versions.includes(version) && spec.capabilities.includes(capability));173
}174
function 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
}182
}183
function algorithmLabel(id, version) {184
if (id === "curve25519" && version === 6) return "X25519";185
return (pgpAlgorithmCatalog || []).find(spec => spec.id === id)?.label || id;186
}187
function 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();209
}210
document.querySelector("#pgpVersion").addEventListener("change", () => { document.querySelector("#pgpLegacyConfirmed").checked=false; updatePgpPreset(); });211
document.querySelector("#pgpAlgorithm").addEventListener("change", updatePgpPreset);212
document.querySelector("#pgpLegacyConfirmed").addEventListener("change", updatePgpPreset);213
function subkeyAlgorithms(role, version = Number(document.querySelector("#pgpVersion").value)) { return availableAlgorithms(version,{encrypt:"E",sign:"S",authenticate:"A"}[role] || "S").map(spec=>spec.id); }214
function 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);246
}247
document.querySelector("#addSubkey").addEventListener("click", () => addSubkey());248
function 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");266
}267
document.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
});286
document.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
});292
function 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;297
}298
// Owner's default (2026-11-05): new OpenPGP keys and subkeys are valid for 5 years; empty = no limit.299
function defaultExpiry() {300
const date=new Date();date.setFullYear(date.getFullYear()+5);return localDateTime(Math.floor(date.getTime()/1000));301
}302
function 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);305
}306
function 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="";311
}312
function 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;317
}318
function 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();348
}349
async 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);}364
}365
document.querySelector("#pgpEditRole").addEventListener("change",updateEditAlgorithms);366
document.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
});372
document.querySelector("#cancelCrypto").addEventListener("click",()=>api.close());373
for (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
});384
for (const id of ["#pgpExpires","#pgpEditExpires"]) { const input=document.querySelector(id); if (input && !input.value) input.value=defaultExpiry(); }SHA-256
7314ed72575f36cca1603b93bcd50855dd0d8a17dee5c33cb3d374cc758662deCH-J Proprietary Software License 1.14 · Source is provided under the CH-J Proprietary Software License 1.14. Its availability does not change the license terms or grant additional rights.
Archive SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f