fix(desktop): clarify migration key setup and small-window layout
This commit is contained in:
13 files changed
+69
-2
No files matched your search
@@ -119,7 +119,8 @@ async function diagnostic() {
|
||||
<span class="text-muted-foreground">{{ t("migration.backupRequiredLabel") }}</span> {{ status.backupRequired ? t("migration.yes") : t("migration.no") }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">{{ t("migration.keyProviderLabel") }}</span> {{ status.persistentKeyConfigured ? t("migration.ready") : t("migration.pending") }}
|
||||
<span class="text-muted-foreground">{{ t("migration.keyProviderLabel") }}</span>
|
||||
{{ status.keyStatus === "will_create" ? t("migration.keyWillCreate") : status.persistentKeyConfigured ? t("migration.ready") : t("migration.pending") }}
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="status?.dataDir" class="break-all text-xs text-muted-foreground">
|
||||
@@ -136,6 +137,7 @@ async function diagnostic() {
|
||||
</button>
|
||||
</p>
|
||||
<p v-if="status?.keyProviderAvailable === false && !status.keyCreationAllowed" class="rounded-lg bg-destructive/10 p-3 text-sm text-destructive">{{ t("migration.keyUnavailable") }}</p>
|
||||
<p v-else-if="status?.keyStatus === 'will_create'" class="rounded-lg bg-primary/5 p-3 text-sm text-muted-foreground">{{ t("migration.keyWillCreateHint") }}</p>
|
||||
<p v-if="status?.keyFileConfigured === false && status?.persistentKeyConfigured === false && status?.keyCreationAllowed === false" class="text-xs text-muted-foreground">{{ t("migration.keyRecoveryHint") }}</p>
|
||||
<div v-if="props.store.state.error" class="space-y-1 rounded-lg bg-destructive/10 p-4 text-sm text-destructive">
|
||||
<p>{{ t(`migration.${props.store.state.error === "statusFailed" ? "statusFailed" : props.store.state.error === "cleanupFailed" ? "cleanupFailed" : "failed"}`) }}</p>
|
||||
|
||||
@@ -10978,6 +10978,8 @@ export default {
|
||||
yes: "yes",
|
||||
no: "no",
|
||||
keyUnavailable: "The device key is unavailable. Allow keychain access or configure a persistent key.",
|
||||
keyWillCreate: "created during first migration",
|
||||
keyWillCreateHint: "DBX has not created a local encryption key on this device yet. Start the migration to create a persistent key, and allow credential-store access if prompted.",
|
||||
keyRecoveryHint: "Restore the original key used for this data directory, or configure the same external key that encrypted the existing data. Do not replace it with a new key.",
|
||||
progress: "Creating backup, migrating, and verifying. Do not close DBX…",
|
||||
failed: "Migration did not complete. Original data has been retained.",
|
||||
|
||||
@@ -10489,6 +10489,8 @@ export default withEnglishFallback({
|
||||
},
|
||||
launching: "Iniciando DBX…",
|
||||
openBackupDir: "Abrir ubicación de copia de seguridad",
|
||||
keyWillCreate: "Se crea en la primera migración",
|
||||
keyWillCreateHint: "Este dispositivo aún no tiene una clave de cifrado local de DBX. Después de hacer clic en «Iniciar migración», DBX creará una clave persistente; si el sistema muestra una ventana de autorización de acceso a credenciales, permita el acceso.",
|
||||
},
|
||||
solrAdmin: {
|
||||
title: "Administración de Solr",
|
||||
|
||||
@@ -10489,6 +10489,8 @@ export default withEnglishFallback({
|
||||
},
|
||||
launching: "Avvio di DBX in corso…",
|
||||
openBackupDir: "Apri posizione backup",
|
||||
keyWillCreate: "Creato durante la prima migrazione",
|
||||
keyWillCreateHint: "Questo dispositivo non dispone ancora di una chiave di crittografia locale DBX. Dopo aver fatto clic su \"Avvia migrazione\", DBX creerà una chiave persistente; se il sistema visualizza una finestra di autorizzazione all'accesso alle credenziali, consenti l'accesso.",
|
||||
},
|
||||
solrAdmin: {
|
||||
title: "Amministrazione Solr",
|
||||
|
||||
@@ -10539,6 +10539,8 @@ export default withEnglishFallback({
|
||||
retry: "再試行",
|
||||
launching: "DBX を起動しています…",
|
||||
openBackupDir: "バックアップの場所を開く",
|
||||
keyWillCreate: "初回移行時に作成",
|
||||
keyWillCreateHint: "現在のデバイスにはまだ DBX ローカル暗号化キーがありません。「移行を開始」をクリックすると、DBX が永続化キーを作成します。システムが資格情報アクセスの承認ウィンドウを表示した場合は、アクセスを許可してください。",
|
||||
},
|
||||
solrAdmin: {
|
||||
title: "Solr 管理",
|
||||
|
||||
@@ -9946,6 +9946,8 @@ export default withEnglishFallback({
|
||||
},
|
||||
launching: "DBX를 시작하는 중…",
|
||||
openBackupDir: "백업 위치 열기",
|
||||
keyWillCreate: "최초 마이그레이션 시 생성",
|
||||
keyWillCreateHint: "현재 장치에는 아직 DBX 로컬 암호화 키가 없습니다. “마이그레이션 시작”을 클릭하면 DBX가 영구 키를 생성합니다. 시스템에서 자격 증명 접근 권한 창이 표시되면 접근을 허용해 주세요.",
|
||||
},
|
||||
solrAdmin: {
|
||||
title: "Solr 관리",
|
||||
|
||||
@@ -10490,6 +10490,8 @@ export default withEnglishFallback({
|
||||
},
|
||||
launching: "Iniciando o DBX…",
|
||||
openBackupDir: "Abrir local do backup",
|
||||
keyWillCreate: "Criado na primeira migração",
|
||||
keyWillCreateHint: "O dispositivo atual ainda não tem uma chave de criptografia local do DBX. Depois de clicar em “Iniciar migração”, o DBX criará uma chave persistente; se o sistema exibir uma janela de autorização de acesso a credenciais, permita o acesso.",
|
||||
},
|
||||
solrAdmin: {
|
||||
title: "Administração do Solr",
|
||||
|
||||
@@ -10945,6 +10945,8 @@ export default withEnglishFallback({
|
||||
yes: "是",
|
||||
no: "否",
|
||||
keyUnavailable: "当前设备密钥不可用,请允许钥匙串访问或配置持久化密钥。",
|
||||
keyWillCreate: "首次迁移时创建",
|
||||
keyWillCreateHint: "当前设备还没有 DBX 本地加密密钥。点击“开始迁移”后,DBX 会创建持久化密钥;如果系统弹出凭据访问授权窗口,请允许访问。",
|
||||
keyRecoveryHint: "请恢复该数据目录原有的密钥,或配置用于加密现有数据的同一外部密钥。不要使用新密钥替换原密钥。",
|
||||
progress: "正在创建备份、迁移并验证,请勿关闭 DBX…",
|
||||
failed: "迁移没有完成,原始数据已保留。",
|
||||
|
||||
@@ -10485,6 +10485,8 @@ export default withEnglishFallback({
|
||||
verifyAdvice: "驗證成功前請保留備份。",
|
||||
},
|
||||
launching: "正在啟動 DBX…",
|
||||
keyWillCreate: "首次遷移時建立",
|
||||
keyWillCreateHint: "目前裝置還沒有 DBX 本機加密金鑰。點選「開始遷移」後,DBX 會建立持久化金鑰;如果系統彈出認證存取授權視窗,請允許存取。",
|
||||
},
|
||||
solrAdmin: {
|
||||
title: "Solr 管理",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
export type MigrationState = "pending" | "running" | "succeeded" | "failed" | "not_required";
|
||||
export type MigrationKeyStatus = "ready" | "will_create" | "unavailable" | "invalid" | "mismatch" | "missing_for_ciphertext";
|
||||
export interface MigrationPreflight {
|
||||
migrationId: string;
|
||||
state: MigrationState;
|
||||
needsMigration: boolean;
|
||||
keyProviderAvailable: boolean;
|
||||
keyStatus?: MigrationKeyStatus;
|
||||
keyCreationAllowed?: boolean;
|
||||
databasePlaintextCount: number;
|
||||
connectionCount: number;
|
||||
|
||||
+22
@@ -118,6 +118,28 @@ describe("migration store", () => {
|
||||
expect(store.state.errorCode).toBe("MISSING_MANAGED_KEY");
|
||||
});
|
||||
|
||||
it("treats an absent platform key as actionable setup before migration", async () => {
|
||||
const store = useMigrationStore({
|
||||
migrationStatus: vi.fn().mockResolvedValue({
|
||||
...pending(),
|
||||
keyProviderAvailable: false,
|
||||
keyCreationAllowed: true,
|
||||
keyStatus: "will_create",
|
||||
errorCode: "KEY_PROVIDER_UNAVAILABLE",
|
||||
errorMessage: "The local secret provider is unavailable",
|
||||
}),
|
||||
migrationStart: vi.fn(),
|
||||
migrationRetry: vi.fn(),
|
||||
migrationCleanupBackups: vi.fn(),
|
||||
});
|
||||
|
||||
await store.initialize();
|
||||
|
||||
expect(store.state.error).toBeNull();
|
||||
expect(store.state.errorCode).toBe("KEY_PROVIDER_UNAVAILABLE");
|
||||
expect(store.diagnostic().keyStatus).toBe("will_create");
|
||||
});
|
||||
|
||||
it("keeps completion through a failed cleanup and successful cleanup retry", async () => {
|
||||
let finishCleanup!: () => void;
|
||||
const cleanup = vi
|
||||
|
||||
@@ -22,7 +22,7 @@ export function useMigrationStore(backend: MigrationApi = api) {
|
||||
// A managed data-directory key is intentionally created when migration
|
||||
// starts. Its absence during the read-only preflight is actionable, not a
|
||||
// migration failure, as long as the backend explicitly allows creation.
|
||||
const hasBlockingError = () => Boolean(state.errorCode && !(state.errorCode === "MISSING_MANAGED_KEY" && state.status?.keyCreationAllowed === true));
|
||||
const hasBlockingError = () => Boolean(state.errorCode && state.status?.keyStatus !== "will_create" && !(state.errorCode === "MISSING_MANAGED_KEY" && state.status?.keyCreationAllowed === true));
|
||||
// A retained backup is a recovery asset and must not make the success page
|
||||
// reappear on later launches. An attempt in this session keeps the wizard
|
||||
// visible even when its response is lost, until the user enters the app.
|
||||
@@ -106,6 +106,7 @@ export function useMigrationStore(backend: MigrationApi = api) {
|
||||
state: s?.state,
|
||||
counts: s ? { connections: s.connectionCount, databasePlaintext: s.databasePlaintextCount, plugin: s.pluginSecretCount, ai: s.aiSecretCount, tunnel: s.tunnelSecretCount, sync: s.syncCredentialCount } : {},
|
||||
keyProviderAvailable: s?.keyProviderAvailable,
|
||||
keyStatus: s?.keyStatus,
|
||||
backupPath: s?.backupPath,
|
||||
errorCode: state.errorCode,
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user