Skip to content

Commit

Permalink
Backup file after dkg (fedimint#587)
Browse files Browse the repository at this point in the history
* feat: trim whitespace in verification codes

* feat: trim leading/trailing whitespace in verification codes

* feat: trim leading/trailing whitespace in verification codes/rebase

* feat: update backup modal

* feat: improve backup modal UI and download flow

* fix: use correct setup action type in modal close

* fix: update hook usrTrimmedInput

* Update apps/router/src/hooks/custom/useTrimmedInput.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: enhance BackupModal functionality

* refactor: remove redundant BackupModal from FederationSetup

* feat: update downloaded translations for multiple languages

* refactor: move @chakra-ui/icons to apps/router/package.json

* chore: revert registry change to yarn.lock

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Alex Lewin <[email protected]>
  • Loading branch information
3 people authored Jan 16, 2025
1 parent 6e3c4c1 commit b3e188a
Show file tree
Hide file tree
Showing 18 changed files with 214 additions and 43 deletions.
1 change: 1 addition & 0 deletions apps/router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@codemirror/lang-json": "^6.0.1",
"@fedimint/types": "*",
"@fedimint/ui": "*",
"@chakra-ui/icons": "^2.2.4",
"@fedimint/utils": "*",
"@uiw/codemirror-theme-github": "^4.23.2",
"@uiw/react-codemirror": "^4.23.2",
Expand Down
140 changes: 140 additions & 0 deletions apps/router/src/guardian-ui/components/BackupModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
Button,
Flex,
Alert,
AlertIcon,
AlertTitle,
Text,
} from '@chakra-ui/react';
import { useGuardianAdminApi, useGuardianDispatch } from '../../hooks';
import { hexToBlob } from '../utils/api';
import { GUARDIAN_APP_ACTION_TYPE, GuardianStatus } from '../../types/guardian';

interface BackupModalProps {
isOpen: boolean;
onClose: () => void;
}

export const BackupModal: React.FC<BackupModalProps> = ({
isOpen,
onClose,
}) => {
const { t } = useTranslation();
const [hasDownloaded, setHasDownloaded] = useState(false);
const [isDownloading, setIsDownloading] = useState(false);
const [downloadError, setDownloadError] = useState<string | null>(null);
const api = useGuardianAdminApi();
const dispatch = useGuardianDispatch();

useEffect(() => {
if (isOpen) {
setHasDownloaded(false);
setDownloadError(null);
}
}, [isOpen]);

const handleDownload = useCallback(async () => {
setIsDownloading(true);
setDownloadError(null);

try {
const response = await api.downloadGuardianBackup();
const blob = hexToBlob(response.tar_archive_bytes, 'application/x-tar');
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'guardianBackup.tar';
link.click();

setTimeout(() => URL.revokeObjectURL(url), 100);
setHasDownloaded(true);
} catch (error) {
console.error('Error in handleDownload:', error);
setDownloadError(
t('federation-dashboard.danger-zone.backup.error-download')
);
} finally {
setIsDownloading(false);
}
}, [api, t]);

const handleContinue = useCallback(() => {
if (hasDownloaded) {
dispatch({
type: GUARDIAN_APP_ACTION_TYPE.SET_STATUS,
payload: GuardianStatus.Admin,
});
onClose();
}
}, [dispatch, hasDownloaded, onClose]);

return (
<Modal
isOpen={isOpen}
onClose={onClose}
closeOnOverlayClick={false}
isCentered
>
<ModalOverlay />
<ModalContent>
<ModalHeader alignSelf='center'>
{t('federation-dashboard.danger-zone.backup.title')}
</ModalHeader>
<ModalBody pb={6}>
<Flex direction='column' gap={4}>
<Alert status='warning'>
<AlertIcon />
<AlertTitle>
{t('federation-dashboard.danger-zone.backup.warning-title')}
</AlertTitle>
</Alert>
<Text mb={4}>
{t('federation-dashboard.danger-zone.backup.warning-text')}
</Text>
{downloadError && (
<Alert status='error'>
<AlertIcon />
<AlertTitle>{downloadError}</AlertTitle>
</Alert>
)}
<Flex justifyContent='center' gap={4} direction={['column', 'row']}>
<Button
variant='ghost'
size={['sm', 'md']}
onClick={handleDownload}
isDisabled={hasDownloaded || isDownloading}
isLoading={isDownloading}
bg='red.500'
color='white'
_hover={{ bg: 'red.600' }}
_active={{ bg: 'red.700' }}
>
{hasDownloaded
? t('federation-dashboard.danger-zone.backup.downloaded') +
' ✓'
: t(
'federation-dashboard.danger-zone.acknowledge-and-download'
)}
</Button>
<Button
colorScheme='blue'
size={['sm', 'md']}
onClick={handleContinue}
isDisabled={!hasDownloaded}
>
{t('common.close')}
</Button>
</Flex>
</Flex>
</ModalBody>
</ModalContent>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { Flex, Heading, Text, Spinner } from '@chakra-ui/react';
import { useTranslation } from '@fedimint/utils';
import {
Expand All @@ -7,6 +7,7 @@ import {
GuardianStatus,
} from '../../../../../types/guardian';
import { useGuardianDispatch } from '../../../../../hooks';
import { BackupModal } from '../../../BackupModal';

interface SetupCompleteProps {
role: GuardianRole;
Expand All @@ -15,40 +16,50 @@ interface SetupCompleteProps {
export const SetupComplete: React.FC<SetupCompleteProps> = ({ role }) => {
const { t } = useTranslation();
const dispatch = useGuardianDispatch();
const [showBackupModal, setShowBackupModal] = useState(true);

useEffect(() => {
const timer = setTimeout(() => {
dispatch({
type: GUARDIAN_APP_ACTION_TYPE.SET_STATUS,
payload: GuardianStatus.Admin,
});
}, 3000);
if (!showBackupModal) {
const timer = setTimeout(() => {
dispatch({
type: GUARDIAN_APP_ACTION_TYPE.SET_STATUS,
payload: GuardianStatus.Admin,
});
}, 3000);

return () => clearTimeout(timer);
}, [dispatch]);
return () => clearTimeout(timer);
}
}, [dispatch, showBackupModal]);

return (
<Flex
direction='column'
justify='center'
align='center'
textAlign='center'
pt={10}
>
<Heading size='sm' fontSize='42px' mb={8}>
{t('setup-complete.header')}
</Heading>
<Heading size='md' fontWeight='medium' mb={2}>
{t('setup-complete.congratulations')}
</Heading>
<Text mb={16} fontWeight='medium'>
{role === GuardianRole.Follower
? t(`setup-complete.follower-message`)
: t(`setup-complete.leader-message`)}
</Text>
<Flex direction='column' align='center'>
<Spinner size='xl' mb={4} />
<>
<Flex
direction='column'
justify='center'
align='center'
textAlign='center'
pt={10}
>
<Heading size='sm' fontSize='42px' mb={8}>
{t('setup-complete.header')}
</Heading>
<Heading size='md' fontWeight='medium' mb={2}>
{t('setup-complete.congratulations')}
</Heading>
<Text mb={16} fontWeight='medium'>
{role === GuardianRole.Follower
? t(`setup-complete.follower-message`)
: t(`setup-complete.leader-message`)}
</Text>
<Flex direction='column' align='center'>
<Spinner size='xl' mb={4} />
</Flex>
</Flex>
</Flex>

<BackupModal
isOpen={showBackupModal}
onClose={() => setShowBackupModal(false)}
/>
</>
);
};
2 changes: 1 addition & 1 deletion apps/router/src/hooks/custom/useTrimmedInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from 'react';

const cleanInput = (value: string) => value.trim();
const cleanInput = (value: string | undefined) => value?.trim() ?? '';

export const useTrimmedInput = (initialValue = '') => {
const [value, setValue] = useState(initialValue);
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "Advertència",
"warning-text": "La còpia de seguretat conté claus privades i material secret per al guardià de la federació i s'ha de mantenir segura. La recuperació utilitzant aquesta còpia de seguretat requereix la vostra contrasenya d'administrador!",
"cancelButton": "Cancel·la",
"acknowledgeButton": "Reconeix i descarrega"
"acknowledgeButton": "Reconeix i descarrega",
"downloaded": "Descarregat"
},
"sign-api-announcement": {
"label": "Anunci de l'API de signatura",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "Warnung",
"warning-text": "Die Sicherung enthält private Schlüssel und geheimes Material für den Föderationswächter und muss sicher aufbewahrt werden. Die Wiederherstellung mit dieser Sicherung erfordert Ihr Admin-Passwort!",
"cancelButton": "Stornieren",
"acknowledgeButton": "Bestätigen & Herunterladen"
"acknowledgeButton": "Bestätigen & Herunterladen",
"downloaded": "Heruntergeladen"
},
"sign-api-announcement": {
"label": "Ankündigung der Sign API",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@
"warning-title": "Warning",
"warning-text": "The backup contains private keys and secret material for the federation guardian and must be kept secure. Recovery using this backup requires your admin password!",
"cancelButton": "Cancel",
"acknowledgeButton": "Acknowledge & Download"
"acknowledgeButton": "Acknowledge & Download",
"downloaded": "Downloaded"
},
"sign-api-announcement": {
"label": "Sign API Announcement",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "Advertencia",
"warning-text": "La copia de seguridad contiene claves privadas y material secreto para el guardián de la federación y debe mantenerse segura. ¡La recuperación usando esta copia de seguridad requiere tu contraseña de administrador!",
"cancelButton": "Cancelar",
"acknowledgeButton": "Reconocer y Descargar"
"acknowledgeButton": "Reconocer y Descargar",
"downloaded": "Descargado"
},
"sign-api-announcement": {
"label": "Anuncio de API de Firma",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "Avertissement",
"warning-text": "La sauvegarde contient des clés privées et des matériaux secrets pour le gardien de la fédération et doit être conservée en toute sécurité. La récupération à l'aide de cette sauvegarde nécessite votre mot de passe administrateur !",
"cancelButton": "Annuler",
"acknowledgeButton": "Reconnaître & Télécharger"
"acknowledgeButton": "Reconnaître & Télécharger",
"downloaded": "Téléchargé"
},
"sign-api-announcement": {
"label": "Annonce de l'API Sign",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "Figyelmeztetés",
"warning-text": "A biztonsági mentés tartalmazza a szövetségi őrző privát kulcsait és titkos anyagait, és biztonságban kell tartani. A helyreállítás ehhez a biztonsági mentéshez az adminisztrátori jelszavadat igényli!",
"cancelButton": "Mégsem",
"acknowledgeButton": "Elfogad és Letölt"
"acknowledgeButton": "Elfogad és Letölt",
"downloaded": "Letöltve"
},
"sign-api-announcement": {
"label": "API bejelentés aláírása",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "Avvertimento",
"warning-text": "Il backup contiene chiavi private e materiale segreto per il guardiano della federazione e deve essere mantenuto sicuro. Il recupero utilizzando questo backup richiede la tua password di amministratore!",
"cancelButton": "Annulla",
"acknowledgeButton": "Riconosci e Scarica"
"acknowledgeButton": "Riconosci e Scarica",
"downloaded": "Scaricato"
},
"sign-api-announcement": {
"label": "Annuncio API di segno",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "警告",
"warning-text": "このバックアップには、連邦ガーディアンのプライベートキーと秘密情報が含まれており、安全に保管する必要があります。このバックアップを使用した復旧には、あなたの管理者パスワードが必要です!",
"cancelButton": "キャンセル",
"acknowledgeButton": "認識してダウンロードする"
"acknowledgeButton": "認識してダウンロードする",
"downloaded": "ダウンロード済み"
},
"sign-api-announcement": {
"label": "APIのお知らせ",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "경고",
"warning-text": "백업에는 연합 가디언의 개인 키와 비밀 자료가 포함되어 있으며, 안전하게 보관해야 합니다. 이 백업을 사용한 복구는 관리자 비밀번호가 필요합니다!",
"cancelButton": "취소",
"acknowledgeButton": "인정 및 다운로드"
"acknowledgeButton": "인정 및 다운로드",
"downloaded": "다운로드됨"
},
"sign-api-announcement": {
"label": "API 공지 사항",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "Aviso",
"warning-text": "O backup contém chaves privadas e material secreto para o guardião da federação e deve ser mantido em segurança. A recuperação usando este backup requer sua senha de administrador!",
"cancelButton": "Cancelar",
"acknowledgeButton": "Reconhecer & Baixar"
"acknowledgeButton": "Reconhecer & Baixar",
"downloaded": "Baixado"
},
"sign-api-announcement": {
"label": "Anúncio da API de Assinatura",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "Предупреждение",
"warning-text": "Резервная копия содержит закрытые ключи и секретные материалы для стража федерации и должна храниться в безопасном месте. Восстановление с использованием этой резервной копии требует вашего административного пароля!",
"cancelButton": "Отменить",
"acknowledgeButton": "Подтвердить и Скачать"
"acknowledgeButton": "Подтвердить и Скачать",
"downloaded": "Скачано"
},
"sign-api-announcement": {
"label": "Объявление о API для подписи",
Expand Down
3 changes: 2 additions & 1 deletion apps/router/src/languages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@
"warning-title": "警告",
"warning-text": "备份包含联邦监护人的私钥和秘密材料,必须保持安全。使用此备份进行恢复需要您的管理员密码!",
"cancelButton": "取消",
"acknowledgeButton": "确认并下载"
"acknowledgeButton": "确认并下载",
"downloaded": "已下载"
},
"sign-api-announcement": {
"label": "签名API公告",
Expand Down
2 changes: 2 additions & 0 deletions apps/router/src/types/guardian.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export enum SetupProgress {
RunDKG = 'RunDKG',
VerifyGuardians = 'VerifyGuardians',
SetupComplete = 'SetupComplete',
Backup = 'Backup',
}

export enum StepState {
Expand All @@ -79,6 +80,7 @@ export interface SetupState {
numPeers: number;
peers: Peer[];
tosConfig: tosConfigState;
guardianName?: string;
}

export enum SETUP_ACTION_TYPE {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,11 @@
dependencies:
"@chakra-ui/shared-utils" "2.0.5"

"@chakra-ui/icons@^2.2.4":
version "2.2.4"
resolved "https://registry.yarnpkg.com/@chakra-ui/icons/-/icons-2.2.4.tgz#fc3f59a7e377d6e4efdbe8ee0a3aec7f29a4ab32"
integrity sha512-l5QdBgwrAg3Sc2BRqtNkJpfuLw/pWRDwwT58J6c4PqQT6wzXxyNa8Q0PForu1ltB5qEiFb1kxr/F/HO1EwNa6g==

"@chakra-ui/[email protected]":
version "2.0.16"
resolved "https://registry.yarnpkg.com/@chakra-ui/image/-/image-2.0.16.tgz#0e3a48c3caa6dc1d340502ea96766d9ef31e27e8"
Expand Down

0 comments on commit b3e188a

Please sign in to comment.