Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 77 additions & 41 deletions app/(dashboard)/sse/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, Tabl
import { useSSE } from "@/hooks/use-sse"
import { useMessage } from "@/lib/feedback/message"
import { formatDateTime } from "@/lib/functions"
import { isImmediateKeyDeletionConfirmed, isKeyPendingDeletion } from "@/lib/sse/key-deletion"
import {
INITIAL_FORM_STATE,
buildFormStateFromStatus,
Expand Down Expand Up @@ -187,7 +188,12 @@ export default function SSEPage() {
const [keyDetailsReloadVersion, setKeyDetailsReloadVersion] = React.useState(0)
const keyDetailsRequestRef = React.useRef(0)

const [pendingKeyAction, setPendingKeyAction] = React.useState<KeyActionState>(null)
const [pendingKeyAction, setPendingKeyActionState] = React.useState<KeyActionState>(null)
const [confirmKeyId, setConfirmKeyId] = React.useState("")
const setPendingKeyAction = React.useCallback((action: KeyActionState) => {
setConfirmKeyId("")
setPendingKeyActionState(action)
}, [])
const [processingKeyAction, setProcessingKeyAction] = React.useState(false)
const [pendingServiceAction, setPendingServiceAction] = React.useState<"start" | "restart" | "stop" | null>(null)
const [pendingNavigation, setPendingNavigation] = React.useState<PendingNavigation>(null)
Expand Down Expand Up @@ -938,7 +944,7 @@ export default function SSEPage() {
}

if (pendingKeyAction.type === "forceDelete") {
const response = await forceDeleteKey(pendingKeyAction.key.key_id)
const response = await forceDeleteKey(pendingKeyAction.key.key_id, confirmKeyId)
if (response.success === false) {
message.error(response.message || t("Failed to force delete key"))
setPendingKeyAction(null)
Expand Down Expand Up @@ -981,7 +987,9 @@ export default function SSEPage() {

const keyActionDescription =
pendingKeyAction?.type === "forceDelete"
? t("This permanently deletes the key immediately and cannot be undone.")
? t(
"This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.",
)
: pendingKeyAction?.type === "cancelDeletion"
? t("This restores the key from the pending deletion state.")
: t("This schedules the key for deletion using the default pending window.")
Expand Down Expand Up @@ -1223,7 +1231,9 @@ export default function SSEPage() {
<Alert>
<AlertTitle>{t("Two-stage key deletion")}</AlertTitle>
<AlertDescription>
{t("Keys in PendingDeletion can still be restored or deleted immediately.")}
{t(
"Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.",
)}
</AlertDescription>
</Alert>

Expand Down Expand Up @@ -1257,7 +1267,7 @@ export default function SSEPage() {
<div className="space-y-3 md:hidden">
{keys.map((key) => {
const isDefaultKey = key.key_id === status?.config_summary?.default_key_id
const isPendingDeletion = (key.status ?? "").toLowerCase().includes("pendingdeletion")
const isPendingDeletion = isKeyPendingDeletion(key.status)
return (
<article key={key.key_id} className="space-y-4 border p-4">
<div className="flex items-start justify-between gap-3">
Expand Down Expand Up @@ -1316,21 +1326,23 @@ export default function SSEPage() {
{t("Schedule Deletion")}
</Button>
)}
<Button
type="button"
variant="destructive"
className="min-h-11 flex-1 sm:flex-none"
disabled={
isDefaultKey ||
Boolean(activeMutation) ||
loadingStatus ||
Boolean(keysError) ||
staticKmsReadOnly
}
onClick={() => setPendingKeyAction({ type: "forceDelete", key })}
>
{t("Delete Immediately")}
</Button>
{isPendingDeletion ? (
<Button
type="button"
variant="destructive"
className="min-h-11 flex-1 sm:flex-none"
disabled={
isDefaultKey ||
Boolean(activeMutation) ||
loadingStatus ||
Boolean(keysError) ||
staticKmsReadOnly
}
onClick={() => setPendingKeyAction({ type: "forceDelete", key })}
>
{t("Delete Immediately")}
</Button>
) : null}
</div>
</article>
)
Expand All @@ -1354,7 +1366,7 @@ export default function SSEPage() {
</TableHeader>
<TableBody>
{keys.map((key) => {
const isPendingDeletion = (key.status ?? "").toLowerCase().includes("pendingdeletion")
const isPendingDeletion = isKeyPendingDeletion(key.status)
const isDefaultKey = key.key_id === status?.config_summary?.default_key_id
return (
<TableRow key={key.key_id} className="group">
Expand Down Expand Up @@ -1401,27 +1413,31 @@ export default function SSEPage() {
</DropdownMenuItem>
)}
{isPendingDeletion && (
<DropdownMenuItem
onClick={() => setPendingKeyAction({ type: "cancelDeletion", key })}
disabled={Boolean(activeMutation) || loadingStatus || Boolean(keysError)}
>
{t("Cancel Deletion")}
</DropdownMenuItem>
<>
<DropdownMenuItem
onClick={() => setPendingKeyAction({ type: "cancelDeletion", key })}
disabled={
Boolean(activeMutation) || loadingStatus || Boolean(keysError)
}
>
{t("Cancel Deletion")}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => setPendingKeyAction({ type: "forceDelete", key })}
disabled={
isDefaultKey ||
Boolean(activeMutation) ||
loadingStatus ||
Boolean(keysError) ||
staticKmsReadOnly
}
className="text-destructive focus:text-destructive"
>
{t("Delete Immediately")}
</DropdownMenuItem>
</>
)}
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => setPendingKeyAction({ type: "forceDelete", key })}
disabled={
isDefaultKey ||
Boolean(activeMutation) ||
loadingStatus ||
Boolean(keysError) ||
staticKmsReadOnly
}
className="text-destructive focus:text-destructive"
>
{t("Delete Immediately")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand Down Expand Up @@ -2349,6 +2365,22 @@ export default function SSEPage() {
) : null}
</AlertDialogDescription>
</AlertDialogHeader>
{pendingKeyAction?.type === "forceDelete" ? (
<Field>
<FieldLabel htmlFor="confirmKeyId">{t("Type the KMS key ID to confirm")}</FieldLabel>
<FieldContent>
<Input
id="confirmKeyId"
name="confirmKeyId"
value={confirmKeyId}
onChange={(event) => setConfirmKeyId(event.target.value)}
autoComplete="off"
spellCheck={false}
disabled={processingKeyAction}
/>
</FieldContent>
</Field>
) : null}
<AlertDialogFooter>
<AlertDialogCancel disabled={processingKeyAction}>{t("Cancel")}</AlertDialogCancel>
<AlertDialogAction
Expand All @@ -2357,6 +2389,10 @@ export default function SSEPage() {
processingKeyAction ||
Boolean(activeMutation) ||
Boolean(statusError) ||
Boolean(
pendingKeyAction?.type === "forceDelete" &&
!isImmediateKeyDeletionConfirmed(pendingKeyAction.key.key_id, confirmKeyId),
) ||
Boolean(isPendingDefaultKey && pendingKeyAction?.type !== "cancelDeletion")
}
className={pendingKeyAction?.type === "forceDelete" ? DANGER_BUTTON_CLASS : undefined}
Expand Down
12 changes: 6 additions & 6 deletions hooks/use-sse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useCallback } from "react"
import { useApi } from "@/contexts/api-context"
import { buildImmediateKeyDeletionRequest } from "@/lib/sse/key-deletion"
import type {
KmsCancelDeletionRequest,
KmsConfigPayload,
Expand Down Expand Up @@ -113,20 +114,19 @@ export function useSSE() {
if (options.pending_window_in_days != null) {
queryParams.append("pending_window_in_days", String(options.pending_window_in_days))
}
if (options.force_immediate) {
queryParams.append("force_immediate", "true")
}
const url = `/kms/keys/delete?${queryParams.toString()}`
return (await api.delete(url)) as KmsMutationResponse
},
[api],
)

const forceDeleteKey = useCallback(
async (keyId: string): Promise<KmsMutationResponse> => {
return deleteKey(keyId, { force_immediate: true })
async (keyId: string, confirmKeyId: string): Promise<KmsMutationResponse> => {
return (await api.delete("/kms/keys/delete", {
body: buildImmediateKeyDeletionRequest(keyId, confirmKeyId),
})) as KmsMutationResponse
},
[deleteKey],
[api],
)

const cancelKeyDeletion = useCallback(
Expand Down
5 changes: 3 additions & 2 deletions i18n/locales/ar-MA.json
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@
"Key list refreshed": "تم تحديث قائمة المفاتيح",
"Key services and configuration values reported by the cluster.": "خدمات المفاتيح وقيم الإعداد التي أبلغ عنها المجموعة.",
"Key storage path prefix in KV store": "بادئة مسار تخزين المفتاح في مخزن KV",
"Keys in PendingDeletion can still be restored or deleted immediately.": "لا يزال بإمكان استعادة المفاتيح في حالة PendingDeletion أو حذفها فورًا.",
"Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "قم بجدولة الحذف أولاً؛ يمكن إلغاؤه خلال فترة الانتظار. لا يمكن الحذف الفوري إلا لمفتاح في حالة PendingDeletion، ويجب أن يسمح الخادم بالحذف الفوري.",
"Language": "اللغة",
"Large File Count Warning": "تحذير من كثرة الملفات الكبيرة",
"Last": "الأخير",
Expand Down Expand Up @@ -1195,7 +1195,8 @@
"This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "يُستخدم هذا المفتاح كمفتاح SSE الافتراضي للمنصة لكل من SSE-KMS وSSE-S3.",
"This key is used as the platform default for SSE-KMS.": "يُستخدم هذا المفتاح كافتراضي للمنصة لـ SSE-KMS.",
"This link will expire when your session ends or at the specified time.": "ستنتهي صلاحية هذا الرابط عند انتهاء جلستك أو في الوقت المحدد.",
"This permanently deletes the key immediately and cannot be undone.": "يحذف المفتاح نهائيًا فورًا ولا يمكن التراجع.",
"This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "سيُحذف المفتاح نهائياً الآن دون انتظار تاريخ الحذف المجدول. لن يمكن أبداً فك تشفير الكائنات المشفّرة به.",
"Type the KMS key ID to confirm": "اكتب معرّف مفتاح KMS للتأكيد",
"This restores the key from the pending deletion state.": "يستعيد المفتاح من حالة الحذف المعلّق.",
"This schedules the key for deletion using the default pending window.": "يجدول حذف المفتاح باستخدام نافذة الانتظار الافتراضية.",
"This server does not provide the detailed site replication status endpoint yet.": "هذا الخادم لا يوفّر بعد نقطة نهاية تفصيلية لحالة نسخ المواقع.",
Expand Down
5 changes: 3 additions & 2 deletions i18n/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@
"Key list refreshed": "Schlüssel-Liste aktualisiert",
"Key services and configuration values reported by the cluster.": "Schlüsseldienste und Konfigurationswerte, die vom Cluster gemeldet werden.",
"Key storage path prefix in KV store": "Schlüsselspeicherpfad-Präfix im KV-Speicher",
"Keys in PendingDeletion can still be restored or deleted immediately.": "Schlüssel im Status PendingDeletion können weiterhin wiederhergestellt oder sofort gelöscht werden.",
"Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Planen Sie zuerst die Löschung; sie kann während der Wartezeit abgebrochen werden. Nur ein Schlüssel im Status PendingDeletion kann sofort gelöscht werden, und der Server muss die sofortige Löschung erlauben.",
"Language": "Sprache",
"Large File Count Warning": "Warnung bei großer Dateianzahl",
"Last": "Letzte",
Expand Down Expand Up @@ -1195,7 +1195,8 @@
"This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "Dieser Schlüssel wird als plattformweiter Standard-SSE-Schlüssel für SSE-KMS und SSE-S3 verwendet.",
"This key is used as the platform default for SSE-KMS.": "Dieser Schlüssel ist die Plattform-Standardvorgabe für SSE-KMS.",
"This link will expire when your session ends or at the specified time.": "Dieser Link läuft ab, wenn Ihre Sitzung endet oder zum angegebenen Zeitpunkt.",
"This permanently deletes the key immediately and cannot be undone.": "Der Schlüssel wird sofort dauerhaft gelöscht und kann nicht rückgängig gemacht werden.",
"This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "Der Schlüssel wird jetzt endgültig gelöscht, ohne das geplante Löschdatum abzuwarten. Damit verschlüsselte Objekte können nie wieder entschlüsselt werden.",
"Type the KMS key ID to confirm": "KMS-Schlüssel-ID zur Bestätigung eingeben",
"This restores the key from the pending deletion state.": "Stellt den Schlüssel aus dem ausstehenden Löschzustand wieder her.",
"This schedules the key for deletion using the default pending window.": "Plant die Schlüssellöschung mit dem Standard-Wartefenster.",
"This server does not provide the detailed site replication status endpoint yet.": "Dieser Server stellt noch keinen detaillierten Endpunkt für den Status der Site-Replikation bereit.",
Expand Down
5 changes: 3 additions & 2 deletions i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@
"Key list refreshed": "Key list refreshed",
"Key services and configuration values reported by the cluster.": "Key services and configuration values reported by the cluster.",
"Key storage path prefix in KV store": "Key storage path prefix in KV store",
"Keys in PendingDeletion can still be restored or deleted immediately.": "Keys in PendingDeletion can still be restored or deleted immediately.",
"Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.": "Schedule a deletion first; it can be canceled during the waiting period. Only a key in PendingDeletion can be deleted immediately, and the server must allow immediate deletion.",
"Language": "Language",
"Large File Count Warning": "Large File Count Warning",
"Last": "Last",
Expand Down Expand Up @@ -1195,7 +1195,8 @@
"This key is used as the platform default SSE key for SSE-KMS and SSE-S3.": "This key is used as the platform default SSE key for SSE-KMS and SSE-S3.",
"This key is used as the platform default for SSE-KMS.": "This key is used as the platform default for SSE-KMS.",
"This link will expire when your session ends or at the specified time.": "This link will expire when your session ends or at the specified time.",
"This permanently deletes the key immediately and cannot be undone.": "This permanently deletes the key immediately and cannot be undone.",
"This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.": "This permanently deletes the key now instead of waiting for its scheduled deletion date. Objects encrypted with it can never be decrypted again.",
"Type the KMS key ID to confirm": "Type the KMS key ID to confirm",
"This restores the key from the pending deletion state.": "This restores the key from the pending deletion state.",
"This schedules the key for deletion using the default pending window.": "This schedules the key for deletion using the default pending window.",
"This server does not provide the detailed site replication status endpoint yet.": "This server does not provide the detailed site replication status endpoint yet.",
Expand Down
Loading