From e7dbee563f560c5f03ae4fb6c7acf2acf4302cf1 Mon Sep 17 00:00:00 2001 From: busya Date: Mon, 6 Jul 2026 19:00:24 +0300 Subject: [PATCH] fix(ui): render KeyRecoveryWizard into body portal to avoid layout margin Modal now mounts at document.body level via svelte mount/unmount. Extracted WizardContent.svelte for portal rendering. Eliminates 32px margin from page layout wrapper (max-w-7xl/space-y-6/px-4 container). Added KeyRecoveryWizard as portal wrapper, WizardContent as the actual modal component. --- .env.example | 2 +- .../security/KeyRecoveryWizard.svelte | 265 +++--------------- .../components/security/WizardContent.svelte | 187 ++++++++++++ 3 files changed, 222 insertions(+), 232 deletions(-) create mode 100644 frontend/src/lib/components/security/WizardContent.svelte diff --git a/.env.example b/.env.example index 85112d0d..b60fa8ed 100644 --- a/.env.example +++ b/.env.example @@ -26,7 +26,7 @@ AGENT_HOST_PORT=7860 AUTH_SECRET_KEY=change-me-to-a-random-secret-32-chars-min # Fernet-ключ шифрования паролей подключений и API-ключей. # Сгенерировать: python3 -c "import base64,os; print(base64.urlsafe_b64encode(os.urandom(32)).decode())" -ENCRYPTION_KEY=3YIxOr3_GFht9ZyjRQMqOdtuO1CKOj8Y9mpY89iMbZY= +ENCRYPTION_KEY=D40dpvWPZxKd41jeaTHtEs2R7nwMVLxbkMRLjAICRls= # Сервисный токен для agent→backend вызовов. # Сгенерировать: python3 -c "import secrets; print('svc-' + secrets.token_urlsafe(24))" SERVICE_JWT=agent-service-secret diff --git a/frontend/src/lib/components/security/KeyRecoveryWizard.svelte b/frontend/src/lib/components/security/KeyRecoveryWizard.svelte index d6f49239..ea4531ec 100644 --- a/frontend/src/lib/components/security/KeyRecoveryWizard.svelte +++ b/frontend/src/lib/components/security/KeyRecoveryWizard.svelte @@ -2,9 +2,11 @@ - -{#if show && model.state !== "idle"} - -{/if} diff --git a/frontend/src/lib/components/security/WizardContent.svelte b/frontend/src/lib/components/security/WizardContent.svelte new file mode 100644 index 00000000..df6b4d6a --- /dev/null +++ b/frontend/src/lib/components/security/WizardContent.svelte @@ -0,0 +1,187 @@ + + + + + +
+
+ +
+

+ {model.state === "scanning" + ? t.settings?.encryption_recovery_scanning || "Scanning..." + : model.state === "healthy" + ? t.settings?.encryption_recovery_healthy || "All credentials healthy" + : model.state === "complete" + ? t.settings?.encryption_recovery_complete || "Recovery complete" + : t.settings?.encryption_recovery_title || "Encrypted credentials need re-entry"} +

+ +
+ + +
+ {#if model.state === "scanning"} +
+ + {t.settings?.encryption_recovery_scanning || "Checking..."} +
+ {/if} + + {#if model.state === "error"} +
{model.error || "Scan failed"}
+ + {/if} + + {#if model.state === "healthy"} +
+ +
+

{t.settings?.encryption_recovery_healthy || "All known encrypted secrets can be decrypted."}

+

{t.settings?.encryption_recovery_key_fingerprint || "Key fingerprint"}: {model.health?.key_fingerprint ?? "?"}

+
+
+ {/if} + + {#if ["needs_recovery", "editing", "partial_success", "saving", "complete"].includes(model.state as string)} +
+

{t.settings?.encryption_recovery_key_fingerprint || "Key fingerprint"}: {model.health?.key_fingerprint ?? "?"}

+

{t.settings?.encryption_recovery_auth_vs_encryption || "Changing AUTH_SECRET_KEY..."}

+
+ +
+ {#each tabFilters as tab} + + {/each} +
+ +
+ {#each brokenFiltered as item (itemIdType(item))} +
+
+
+ {item.label} + {itemTypeLabel(item.type)} +
+ {t.settings?.encryption_recovery_cannot_decrypt || "⚠ cannot decrypt"} +
+
{itemLocation(item)}
+ + {#if model.state === "editing" && recoverableTypes.has(item.type)} +
+ { const el = e.target as HTMLInputElement; model.setSecretValue(item.id, el.value); }} + /> + {#if model.savingIds.has(item.id)} + {t.settings?.encryption_recovery_saving || "Saving..."} + {:else if model.savedIds.has(item.id)} + + {:else if model.failedIds.has(item.id)} + + {/if} +
+ {:else if item.type === "profile_git_token"} +

{t.settings?.encryption_recovery_git_token_hint || "Git tokens are personal..."}

+ {/if} +
+ {/each} +
+ + {#if brokenFiltered.length === 0} +

{t.settings?.encryption_recovery_empty_category || "No broken items in this category."}

+ {/if} + {/if} + + {#if model.state === "needs_recovery"} +
+

{t.settings?.encryption_recovery_recommended || "Recommended if old key is available:"}

+ {t.settings?.encryption_recovery_reencrypt_cmd || "..."} +
+ {/if} + + {#if model.state === "partial_success"} +
{t.settings?.encryption_recovery_partial_success || "Some saved, some failed."}
+ {/if} +
+ + +
+
+ +
+
+ {#if model.state === "needs_recovery"} + + {:else if model.state === "editing"} + + + {:else if model.state === "saving"} + + {/if} + +
+
+
+
+