QFix: Backup info page (#7703)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2025-01-17 23:10:42 +07:00
committed by GitHub
parent ec0d69e475
commit f728c40527
10 changed files with 167 additions and 108 deletions
+7 -7
View File
@@ -122,14 +122,14 @@
"OwnerOrMaintainerRequired": "Musíte být vlastníkem nebo správcem pracovního prostoru",
"Backup": "Záloha",
"BackupLast": "Poslední záloha",
"BackupTotalSnapshots": "Celkem snímků",
"BackupTotalSnapshots": "Celkový počet snímků",
"BackupTotalFiles": "Soubory",
"BackupSize": "Velikost zálohy",
"BackupLinkInfo": "Záložní adresář je dostupný na",
"BackupBearerTokenInfo": "Pro přístup k záloze je vyžadován přístupový token. Klikněte na tlačítko pro zkopírování tokenu do schránky.",
"BackupSnapshots": "Snímky",
"BackupFileDownload": "Stáhnout",
"BackupFiles": "Soubory",
"BackupNoBackup": "Žádná záloha není k dispozici"
"BackupLinkInfo": "URL adresář zálohy, který lze stáhnout rekurzivně pomocí nástrojů jako wget nebo curl.",
"BackupBearerTokenInfo": "Přístup k záloze vyžaduje bearer token.",
"BackupSnapshots": "Snímky záloh",
"BackupFileDownload": "Stáhnout soubor",
"BackupFiles": "Soubory záloh",
"BackupNoBackup": "V současné době nejsou k dispozici žádné zálohy."
}
}
+7 -7
View File
@@ -122,14 +122,14 @@
"OwnerOrMaintainerRequired": "Sie müssen Eigentümer oder Betreuer des Arbeitsbereichs sein",
"Backup": "Sicherung",
"BackupLast": "Letzte Sicherung",
"BackupTotalSnapshots": "Gesamtanzahl Snapshots",
"BackupTotalSnapshots": "Gesamtanzahl der Schnappschüsse",
"BackupTotalFiles": "Dateien",
"BackupSize": "Sicherungsgröße",
"BackupLinkInfo": "Ein Sicherungsverzeichnis ist verfügbar unter",
"BackupBearerTokenInfo": "Für den Zugriff auf die Sicherung wird ein Bearer-Token benötigt. Bitte klicken Sie auf die Schaltfläche 'Kopieren', um den Token in die Zwischenablage zu kopieren.",
"BackupSnapshots": "Snapshots",
"BackupFileDownload": "Herunterladen",
"BackupFiles": "Dateien",
"BackupNoBackup": "Keine Sicherung verfügbar"
"BackupLinkInfo": "Die URL eines Sicherungsverzeichnisses, das rekursiv mit Tools wie wget oder curl heruntergeladen werden kann.",
"BackupBearerTokenInfo": "Ein Bearer-Token ist erforderlich, um auf die Sicherung zuzugreifen.",
"BackupSnapshots": "Sicherungsschnappschüsse",
"BackupFileDownload": "Datei herunterladen",
"BackupFiles": "Sicherungsdateien",
"BackupNoBackup": "Derzeit sind keine Sicherungen verfügbar."
}
}
+6 -6
View File
@@ -125,11 +125,11 @@
"BackupTotalSnapshots": "Total Snapshots",
"BackupTotalFiles": "Files",
"BackupSize": "Backup size",
"BackupLinkInfo": "A backup directory is available at",
"BackupBearerTokenInfo": "A bearer token is required to access the backup. Please click copy to clipboard button to copy the token." ,
"BackupSnapshots": "Snapshots",
"BackupFileDownload": "Download",
"BackupFiles": "Files",
"BackupNoBackup": "No backup available"
"BackupLinkInfo": "The URL of a backup directory that can be downloaded recursively using tools like wget or curl.",
"BackupBearerTokenInfo": "A bearer token is required to access the backup.",
"BackupSnapshots": "Backup Snapshots",
"BackupFileDownload": "Download File",
"BackupFiles": "Backup Files",
"BackupNoBackup": "No backups are currently available."
}
}
+8 -8
View File
@@ -113,14 +113,14 @@
"OwnerOrMaintainerRequired": "Necesitas ser Propietario o Mantenedor del espacio de trabajo",
"Backup": "Copia de seguridad",
"BackupLast": "Última copia de seguridad",
"BackupTotalSnapshots": "Total de instantáneas",
"BackupTotalSnapshots": "Total instantáneas",
"BackupTotalFiles": "Archivos",
"BackupSize": "Tamaño de la copia",
"BackupLinkInfo": "El directorio de copias de seguridad está disponible en",
"BackupBearerTokenInfo": "Se requiere un token de acceso para acceder a la copia de seguridad. Por favor, haga clic en el botón para copiar el token al portapapeles.",
"BackupSnapshots": "Instantáneas",
"BackupFileDownload": "Descargar",
"BackupFiles": "Archivos",
"BackupNoBackup": "No hay copias de seguridad disponibles"
"BackupSize": "Tamaño de la copia de seguridad",
"BackupLinkInfo": "La URL de un directorio de copia de seguridad que se puede descargar de forma recursiva usando herramientas como wget o curl.",
"BackupBearerTokenInfo": "Se requiere un token de portador para acceder a la copia de seguridad.",
"BackupSnapshots": "Instantáneas de copia de seguridad",
"BackupFileDownload": "Descargar archivo",
"BackupFiles": "Archivos de copia de seguridad",
"BackupNoBackup": "No hay copias de seguridad disponibles actualmente."
}
}
+6 -6
View File
@@ -125,11 +125,11 @@
"BackupTotalSnapshots": "Total des instantanés",
"BackupTotalFiles": "Fichiers",
"BackupSize": "Taille de la sauvegarde",
"BackupLinkInfo": "Un répertoire de sauvegarde est disponible à",
"BackupBearerTokenInfo": "Un jeton d'authentification est nécessaire pour accéder à la sauvegarde. Veuillez cliquer sur le bouton pour copier le jeton dans le presse-papiers.",
"BackupSnapshots": "Instantanés",
"BackupFileDownload": "Télécharger",
"BackupFiles": "Fichiers",
"BackupNoBackup": "Aucune sauvegarde disponible"
"BackupLinkInfo": "L'URL d'un répertoire de sauvegarde pouvant être téléchargé de manière récursive à l'aide d'outils comme wget ou curl.",
"BackupBearerTokenInfo": "Un jeton d'accès est requis pour accéder à la sauvegarde.",
"BackupSnapshots": "Instantanés de sauvegarde",
"BackupFileDownload": "Télécharger le fichier",
"BackupFiles": "Fichiers de sauvegarde",
"BackupNoBackup": "Aucune sauvegarde n'est actuellement disponible."
}
}
+6 -6
View File
@@ -125,11 +125,11 @@
"BackupTotalSnapshots": "Totale istantanee",
"BackupTotalFiles": "File",
"BackupSize": "Dimensione backup",
"BackupLinkInfo": "Una directory di backup è disponibile in",
"BackupBearerTokenInfo": "È richiesto un token di accesso per accedere al backup. Fare clic sul pulsante per copiare il token negli appunti.",
"BackupSnapshots": "Istantanee",
"BackupFileDownload": "Scarica",
"BackupFiles": "File",
"BackupNoBackup": "Nessun backup disponibile"
"BackupLinkInfo": "L'URL di una directory di backup che può essere scaricata ricorsivamente utilizzando strumenti come wget o curl.",
"BackupBearerTokenInfo": "È richiesto un token di accesso per accedere al backup.",
"BackupSnapshots": "Istantanee di backup",
"BackupFileDownload": "Scarica file",
"BackupFiles": "File di backup",
"BackupNoBackup": "Non sono attualmente disponibili backup."
}
}
+7 -7
View File
@@ -113,14 +113,14 @@
"OwnerOrMaintainerRequired": "Precisa de ser proprietário ou mantenedor do espaço de trabalho",
"Backup": "Backup",
"BackupLast": "Último backup",
"BackupTotalSnapshots": "Total de instantâneos",
"BackupTotalSnapshots": "Total de Snapshots",
"BackupTotalFiles": "Arquivos",
"BackupSize": "Tamanho do backup",
"BackupLinkInfo": "Um diretório de backup está disponível em",
"BackupBearerTokenInfo": "É necessário um token de acesso para acessar o backup. Clique no botão para copiar o token para a área de transferência.",
"BackupSnapshots": "Instantâneos",
"BackupFileDownload": "Baixar",
"BackupFiles": "Arquivos",
"BackupNoBackup": "Nenhum backup disponível"
"BackupLinkInfo": "A URL de um diretório de backup que pode ser baixado recursivamente usando ferramentas como wget ou curl.",
"BackupBearerTokenInfo": "É necessário um token bearer para acessar o backup.",
"BackupSnapshots": "Snapshots de Backup",
"BackupFileDownload": "Baixar Arquivo",
"BackupFiles": "Arquivos de Backup",
"BackupNoBackup": "Não há backups disponíveis no momento."
}
}
+8 -8
View File
@@ -121,16 +121,16 @@
"WorkspaceName": "Название рабочего пространства",
"Workspace": "Рабочее пространство",
"OwnerOrMaintainerRequired": "Вы должны иметь роль Владельца/Сопровождаемого",
"Backup": "Резервная копия",
"BackupLast": "Последняя резервная копия",
"Backup": "Резервное копирование",
"BackupLast": "Последнее резервное копирование",
"BackupTotalSnapshots": "Всего снимков",
"BackupTotalFiles": "Файлы",
"BackupSize": "Размер резервной копии",
"BackupLinkInfo": "Директория резервных копий доступна по адресу",
"BackupBearerTokenInfo": "Для доступа к резервной копии требуется токен авторизации. Пожалуйста, нажмите кнопку, чтобы скопировать токен в буфер обмена.",
"BackupSnapshots": "Снимки",
"BackupFileDownload": "Скачать",
"BackupFiles": "Файлы",
"BackupNoBackup": "Резервные копии отсутствуют"
"BackupLinkInfo": "URL-адрес каталога резервной копии, который можно скачать рекурсивно с помощью инструментов, таких как wget или curl.",
"BackupBearerTokenInfo": "Для доступа к резервной копии требуется токен доступа.",
"BackupSnapshots": "Снимки резервной копии",
"BackupFileDownload": "Скачать файл",
"BackupFiles": "Файлы резервной копии",
"BackupNoBackup": "В настоящее время резервные копии недоступны."
}
}
+8 -8
View File
@@ -121,15 +121,15 @@
"Workspace": "工作区",
"OwnerOrMaintainerRequired": "您需要成为工作空间所有者或维护者",
"Backup": "备份",
"BackupLast": "最近备份",
"BackupLast": "上次备份",
"BackupTotalSnapshots": "快照总数",
"BackupTotalFiles": "文件",
"BackupTotalFiles": "文件数量",
"BackupSize": "备份大小",
"BackupLinkInfo": "备份目录位于",
"BackupBearerTokenInfo": "访问备份需要访问令牌。请点击复制按钮将令牌复制到剪贴板。",
"BackupSnapshots": "快照",
"BackupFileDownload": "下载",
"BackupFiles": "文件",
"BackupNoBackup": "暂无可用备份"
"BackupLinkInfo": "可以使用 wget 或 curl 等工具递归下载的备份目录的 URL。",
"BackupBearerTokenInfo": "访问备份需要一个持票者令牌。",
"BackupSnapshots": "备份快照",
"BackupFileDownload": "下载文件",
"BackupFiles": "备份文件",
"BackupNoBackup": "当前没有可用的备份。"
}
}
@@ -22,6 +22,7 @@
import { onMount } from 'svelte'
import setting from '../plugin'
import { BackupInfo, BackupSnapshot } from '../types'
import Expand from '@hcengineering/ui/src/components/icons/Expand.svelte'
let loading = true
@@ -108,9 +109,9 @@
if (diffHours < 24) {
if (diffHours === 0) {
const diffMinutes = Math.floor(diffMs / (1000 * 60))
return `- ${diffMinutes} minutes ago`
return `${diffMinutes} minutes ago`
}
return `- ${diffHours} hours ago`
return `${diffHours} hours ago`
}
return ''
@@ -142,6 +143,7 @@
}
}
let copied = false
let copied2 = false
$: snapshots = backupInfo?.info?.snapshots ?? []
</script>
@@ -172,47 +174,48 @@
<span class="flex-row-center">
<Label label={setting.string.BackupSize} />: {getBackupSize(backupInfo?.files ?? [])}
</span>
<div class="mt-2">
<div class="flex-row-center mt-2">
<Label label={setting.string.BackupLinkInfo} />:
<a href={getFileUrl('index.html')} target="_blank" rel="noopener noreferrer">
<Link label="index.html">
{getFileUrl('index.html')}
</Link>
</a>
</div>
<div class="mt-2">
<Label label={setting.string.BackupBearerTokenInfo} />
</div>
<Button
label={copied ? view.string.Copied : view.string.CopyToClipboard}
on:click={() => {
void navigator.clipboard.writeText(token).then(() => {
copied = true
setTimeout(() => {
copied = false
}, 2500)
})
}}
/>
</div>
</div>
<Expandable bordered>
<svelte:fragment slot="title">
<div class="flex-no-shrink flex-row-center">
<div class="flex-no-shrink flex-row-center p-1">
<Label label={setting.string.BackupSnapshots} />:
{snapshots.length}
</div>
</svelte:fragment>
<div class="p-1">
<div class="file-item">
<div class="file-name">{'index.json'}</div>
<div class="file-info">
<span class="file-size">
{getSize(JSON.stringify(backupInfo).length)}
</span>
<div class="file-actions">
<Button label={setting.string.BackupFileDownload} on:click={() => downloadFile('index.json')} />
</div>
</div>
</div>
<div class="file-item">
<div class="file-name">{'backup.json.gz'}</div>
<div class="file-info">
<span class="file-size">
{getSize(fileSizes.get('backup.json.gz') ?? 0)}
</span>
<div class="file-actions">
<Button label={setting.string.BackupFileDownload} on:click={() => downloadFile('backup.json.gz')} />
</div>
</div>
</div>
</div>
{#if backupInfo?.info?.snapshots}
{#each backupInfo.info.snapshots as snapshot, i}
{@const snLen = backupInfo.info.snapshots.length}
{#each backupInfo.info.snapshots.reverse() as snapshot, i}
<div class="flex-no-shrink">
<Expandable expandable bordered>
<svelte:fragment slot="title">
<div class="flex-row-center">
#{i}
<div class="flex-row-center ml-1">
#{snLen - i}
{formatDate(snapshot.date)}
{formatAgoHours(snapshot.date)} -
{getSnapshotSummary(snapshot)}
@@ -241,6 +244,22 @@
</div>
</div>
{/each}
{#each data.snapshots ?? [] as filename}
<div class="file-item">
<div class="file-name">{filename}</div>
<div class="file-info">
<span class="file-size">
{getSize(fileSizes.get(filename) ?? 0)}
</span>
<div class="file-actions">
<Button
label={setting.string.BackupFileDownload}
on:click={() => downloadFile(filename)}
/>
</div>
</div>
</div>
{/each}
</div>
</div>
{/if}
@@ -260,27 +279,67 @@
{/if}
</Expandable>
<Expandable bordered>
<Expandable expanded={true} bordered>
<svelte:fragment slot="title">
<div class="flex-no-shrink flex-row-center">
<Label label={setting.string.BackupFiles} />:
{backupInfo?.files.length}
{getBackupSize(backupInfo?.files ?? [])}
<div class="p-1">
<Label label={setting.string.BackupFiles} />
</div>
</svelte:fragment>
{#each backupInfo?.files ?? [] as file}
<div class="p-1">
<div class="file-item">
<div class="file-name">{file.name}</div>
<div class="file-name">{'index.json'}</div>
<div class="file-info">
<span class="file-size">
{getSize(file.size ?? 0)}
</span>
<div class="file-actions">
<Button label={setting.string.BackupFileDownload} on:click={() => downloadFile(file.name)} />
<Button label={setting.string.BackupFileDownload} on:click={() => downloadFile('index.json')} />
</div>
</div>
</div>
{/each}
<div class="file-item">
<div class="file-name">{'index.html'}</div>
<div class="file-info">
<div class="file-actions">
<Button label={setting.string.BackupFileDownload} on:click={() => downloadFile('index.html')} />
</div>
</div>
</div>
</div>
<div class="mt-2">
<div class="flex-row-center mt-2 flex-between">
<div class="wrap">
<Label label={setting.string.BackupLinkInfo} />
<div class="select-text anti-component p-3 border-b-1 border-divider-color">
{getFileUrl('index.html')}
</div>
</div>
<Button
label={copied ? view.string.Copied : view.string.CopyToClipboard}
on:click={() => {
void navigator.clipboard.writeText(getFileUrl('index.html')).then(() => {
copied = true
setTimeout(() => {
copied = false
}, 2500)
})
}}
/>
</div>
<div class="mt-2 flex-row-center flex-between">
<Label label={setting.string.BackupBearerTokenInfo} />
<Button
label={copied2 ? view.string.Copied : view.string.CopyToClipboard}
on:click={() => {
void navigator.clipboard.writeText(token).then(() => {
copied2 = true
setTimeout(() => {
copied2 = false
}, 2500)
})
}}
/>
</div>
</div>
</Expandable>
{/if}
</Scroller>
@@ -305,14 +364,14 @@
.files-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
gap: 0.05rem;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
padding: 0.05rem;
background-color: var(--theme-bg-accent);
border-radius: 0.25rem;