From 81dd4cdee783ef5deba134b6aef1da6f66075808 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Wed, 15 Jan 2025 11:08:51 +0700 Subject: [PATCH] UBERF-9107: Add backup list support (#7662) Signed-off-by: Andrey Sobolev --- common/config/rush/command-line.json | 2 +- desktop/src/ui/index.ts | 15 +- dev/prod/public/config-dev.json | 3 +- dev/prod/src/platform.ts | 5 +- models/setting/src/index.ts | 13 + plugins/setting-assets/lang/cs.json | 13 +- plugins/setting-assets/lang/de.json | 13 +- plugins/setting-assets/lang/en.json | 13 +- plugins/setting-assets/lang/es.json | 13 +- plugins/setting-assets/lang/fr.json | 13 +- plugins/setting-assets/lang/it.json | 13 +- plugins/setting-assets/lang/pt.json | 13 +- plugins/setting-assets/lang/ru.json | 13 +- plugins/setting-assets/lang/zh.json | 13 +- .../src/components/Backup.svelte | 355 ++++++++++++++++++ plugins/setting-resources/src/index.ts | 2 + plugins/setting-resources/src/types.ts | 51 +++ plugins/setting/src/index.ts | 24 +- pods/front/src/__start.ts | 3 +- 19 files changed, 570 insertions(+), 20 deletions(-) create mode 100644 plugins/setting-resources/src/components/Backup.svelte create mode 100644 plugins/setting-resources/src/types.ts diff --git a/common/config/rush/command-line.json b/common/config/rush/command-line.json index f43af3a1825..54ec8f64463 100644 --- a/common/config/rush/command-line.json +++ b/common/config/rush/command-line.json @@ -202,7 +202,7 @@ "name": "docker:build", "phases": ["_phase:build", "_phase:bundle", "_phase:package", "_phase:docker-build"], "enableParallelism": true, - "incremental": true + "incremental": true }, { "summary": "docker:rebuild", diff --git a/desktop/src/ui/index.ts b/desktop/src/ui/index.ts index c1fa495cb4c..8733648ddef 100644 --- a/desktop/src/ui/index.ts +++ b/desktop/src/ui/index.ts @@ -1,7 +1,7 @@ import login, { loginId } from '@hcengineering/login' import { getEmbeddedLabel, getMetadata, setMetadata } from '@hcengineering/platform' import presentation, { closeClient, MessageBox, setDownloadProgress } from '@hcengineering/presentation' -import { settingId } from '@hcengineering/setting' +import settings, { settingId } from '@hcengineering/setting' import { closePanel, closePopup, @@ -24,7 +24,6 @@ import { isOwnerOrMaintainer } from '@hcengineering/core' import { configurePlatform } from './platform' import { defineScreenShare } from './screenShare' import { IPCMainExposed } from './types' -import settings from '@hcengineering/setting' defineScreenShare() @@ -108,7 +107,17 @@ window.addEventListener('DOMContentLoaded', () => { const workspace = getMetadata(presentation.metadata.WorkspaceId) if (isOwnerOrMaintainer()) { if (token != null && endpoint != null && workspace != null) { - ipcMain.startBackup(token, endpoint, workspace) + // ipcMain.startBackup(token, endpoint, workspace) + closePopup() + closePanel() + const loc = getCurrentResolvedLocation() + loc.fragment = undefined + loc.query = undefined + loc.path[2] = settingId + loc.path[3] = 'setting' + loc.path[4] = 'backup' + loc.path.length = 5 + navigate(loc) } } else { showPopup(MessageBox, { diff --git a/dev/prod/public/config-dev.json b/dev/prod/public/config-dev.json index 2093d02ddc1..507037a39f7 100644 --- a/dev/prod/public/config-dev.json +++ b/dev/prod/public/config-dev.json @@ -8,5 +8,6 @@ "REKONI_URL": "https://rekoni.hc.engineering", "COLLABORATOR_URL": "wss://collaborator.hc.engineering", "STATS_URL": "https://stats.hc.engineering", - "PRESENCE_URL": "wss://presence.hc.engineering" + "PRESENCE_URL": "wss://presence.hc.engineering", + "BACKUP_URL": "https://front.hc.engineering/api/backup" } \ No newline at end of file diff --git a/dev/prod/src/platform.ts b/dev/prod/src/platform.ts index 8579682c7bb..23e83112ee0 100644 --- a/dev/prod/src/platform.ts +++ b/dev/prod/src/platform.ts @@ -45,7 +45,7 @@ import { questionsId } from '@hcengineering/questions' import { recruitId } from '@hcengineering/recruit' import rekoni from '@hcengineering/rekoni' import { requestId } from '@hcengineering/request' -import { settingId } from '@hcengineering/setting' +import setting, { settingId } from '@hcengineering/setting' import sign from '@hcengineering/sign' import { supportId } from '@hcengineering/support' import { tagsId } from '@hcengineering/tags' @@ -165,6 +165,7 @@ export interface Config { PRESENCE_URL?: string USE_BINARY_PROTOCOL?: boolean, TRANSACTOR_OVERRIDE?: string + BACKUP_URL?: string } export interface Branding { @@ -433,5 +434,7 @@ export async function configurePlatform() { setMetadata(workbench.metadata.DefaultSpace, myBranding.defaultSpace ?? tracker.project.DefaultProject) setMetadata(workbench.metadata.DefaultSpecial, myBranding.defaultSpecial ?? 'issues') + setMetadata(setting.metadata.BackupUrl, config.BACKUP_URL ?? '') + initThemeStore() } diff --git a/models/setting/src/index.ts b/models/setting/src/index.ts index 4000781794c..5d5bda6e907 100644 --- a/models/setting/src/index.ts +++ b/models/setting/src/index.ts @@ -226,6 +226,19 @@ export function createModel (builder: Builder): void { }, setting.ids.General ) + builder.createDoc( + setting.class.WorkspaceSettingCategory, + core.space.Model, + { + name: 'backup', + label: setting.string.Backup, + icon: setting.icon.Setting, + component: setting.component.Backup, + order: 950, + role: AccountRole.Owner + }, + setting.ids.Backup + ) builder.createDoc( setting.class.WorkspaceSettingCategory, core.space.Model, diff --git a/plugins/setting-assets/lang/cs.json b/plugins/setting-assets/lang/cs.json index eb17192ec70..1fb667b6241 100644 --- a/plugins/setting-assets/lang/cs.json +++ b/plugins/setting-assets/lang/cs.json @@ -119,6 +119,17 @@ "DeleteSpaceTypeConfirm": "Opravdu chcete tento typ prostoru smazat?", "WorkspaceName": "Název pracovního prostoru", "Workspace": "Pracovní prostor", - "OwnerOrMaintainerRequired": "Musíte být vlastníkem nebo správcem pracovního prostoru" + "OwnerOrMaintainerRequired": "Musíte být vlastníkem nebo správcem pracovního prostoru", + "Backup": "Záloha", + "BackupLast": "Poslední záloha", + "BackupTotalSnapshots": "Celkem 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" } } \ No newline at end of file diff --git a/plugins/setting-assets/lang/de.json b/plugins/setting-assets/lang/de.json index f37cc76e6e8..517ad825eb3 100644 --- a/plugins/setting-assets/lang/de.json +++ b/plugins/setting-assets/lang/de.json @@ -119,6 +119,17 @@ "DeleteSpaceTypeConfirm": "Sind Sie sicher, dass Sie diesen Bereichstyp löschen möchten?", "WorkspaceName": "Arbeitsbereich-Name", "Workspace": "Arbeitsbereich", - "OwnerOrMaintainerRequired": "Sie müssen Eigentümer oder Betreuer des Arbeitsbereichs sein" + "OwnerOrMaintainerRequired": "Sie müssen Eigentümer oder Betreuer des Arbeitsbereichs sein", + "Backup": "Sicherung", + "BackupLast": "Letzte Sicherung", + "BackupTotalSnapshots": "Gesamtanzahl Snapshots", + "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" } } diff --git a/plugins/setting-assets/lang/en.json b/plugins/setting-assets/lang/en.json index 5ac62762dd9..1954a468b0a 100644 --- a/plugins/setting-assets/lang/en.json +++ b/plugins/setting-assets/lang/en.json @@ -119,6 +119,17 @@ "DeleteSpaceTypeConfirm": "Are you sure you want to delete this space type?", "WorkspaceName": "Workspace name", "Workspace": "Workspace", - "OwnerOrMaintainerRequired": "You need to be a workspace Owner or Maintainer" + "OwnerOrMaintainerRequired": "You need to be a workspace Owner or Maintainer", + "Backup": "Backup", + "BackupLast": "Last backup", + "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" } } diff --git a/plugins/setting-assets/lang/es.json b/plugins/setting-assets/lang/es.json index f184848511b..fec61037bd6 100644 --- a/plugins/setting-assets/lang/es.json +++ b/plugins/setting-assets/lang/es.json @@ -110,6 +110,17 @@ "DeleteSpaceTypeConfirm": "¿Estás seguro de que quieres eliminar este tipo de espacio?", "WorkspaceName": "Nombre del espacio de trabajo", "Workspace": "Espacio de trabajo", - "OwnerOrMaintainerRequired": "Necesitas ser Propietario o Mantenedor del espacio de trabajo" + "OwnerOrMaintainerRequired": "Necesitas ser Propietario o Mantenedor del espacio de trabajo", + "Backup": "Copia de seguridad", + "BackupLast": "Última copia de seguridad", + "BackupTotalSnapshots": "Total de 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" } } \ No newline at end of file diff --git a/plugins/setting-assets/lang/fr.json b/plugins/setting-assets/lang/fr.json index 6df3ac18ef4..1c831fd9ced 100644 --- a/plugins/setting-assets/lang/fr.json +++ b/plugins/setting-assets/lang/fr.json @@ -119,6 +119,17 @@ "DeleteSpaceTypeConfirm": "Êtes-vous sûr de vouloir supprimer ce type d'espace ?", "WorkspaceName": "Nom de l'espace de travail", "Workspace": "Espace de travail", - "OwnerOrMaintainerRequired": "Vous devez être propriétaire ou responsable d'un espace de travail" + "OwnerOrMaintainerRequired": "Vous devez être propriétaire ou responsable d'un espace de travail", + "Backup": "Sauvegarde", + "BackupLast": "Dernière sauvegarde", + "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" } } \ No newline at end of file diff --git a/plugins/setting-assets/lang/it.json b/plugins/setting-assets/lang/it.json index 7c8cba63053..e87c4a798f9 100644 --- a/plugins/setting-assets/lang/it.json +++ b/plugins/setting-assets/lang/it.json @@ -119,6 +119,17 @@ "DeleteSpaceTypeConfirm": "Sei sicuro di voler eliminare questo tipo di spazio?", "WorkspaceName": "Nome spazio di lavoro", "Workspace": "Spazio di lavoro", - "OwnerOrMaintainerRequired": "Devi essere un proprietario o un manutentore dello spazio di lavoro" + "OwnerOrMaintainerRequired": "Devi essere un proprietario o un manutentore dello spazio di lavoro", + "Backup": "Backup", + "BackupLast": "Ultimo backup", + "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" } } diff --git a/plugins/setting-assets/lang/pt.json b/plugins/setting-assets/lang/pt.json index 81327c2b44a..491fa688e8e 100644 --- a/plugins/setting-assets/lang/pt.json +++ b/plugins/setting-assets/lang/pt.json @@ -110,6 +110,17 @@ "DeleteSpaceTypeConfirm": "Tem certeza de que deseja excluir este tipo de espaço?", "WorkspaceName": "Nome do espaço de trabalho", "Workspace": "Espaço de trabalho", - "OwnerOrMaintainerRequired": "Precisa de ser proprietário ou mantenedor do espaço de trabalho" + "OwnerOrMaintainerRequired": "Precisa de ser proprietário ou mantenedor do espaço de trabalho", + "Backup": "Backup", + "BackupLast": "Último backup", + "BackupTotalSnapshots": "Total de instantâneos", + "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" } } \ No newline at end of file diff --git a/plugins/setting-assets/lang/ru.json b/plugins/setting-assets/lang/ru.json index c613a8db577..23766dd5924 100644 --- a/plugins/setting-assets/lang/ru.json +++ b/plugins/setting-assets/lang/ru.json @@ -120,6 +120,17 @@ "DeleteSpaceTypeConfirm": "Вы действительно хотите удалить этот тип пространства?", "WorkspaceName": "Название рабочего пространства", "Workspace": "Рабочее пространство", - "OwnerOrMaintainerRequired": "Вы должны иметь роль Владельца/Сопровождаемого" + "OwnerOrMaintainerRequired": "Вы должны иметь роль Владельца/Сопровождаемого", + "Backup": "Резервная копия", + "BackupLast": "Последняя резервная копия", + "BackupTotalSnapshots": "Всего снимков", + "BackupTotalFiles": "Файлы", + "BackupSize": "Размер резервной копии", + "BackupLinkInfo": "Директория резервных копий доступна по адресу", + "BackupBearerTokenInfo": "Для доступа к резервной копии требуется токен авторизации. Пожалуйста, нажмите кнопку, чтобы скопировать токен в буфер обмена.", + "BackupSnapshots": "Снимки", + "BackupFileDownload": "Скачать", + "BackupFiles": "Файлы", + "BackupNoBackup": "Резервные копии отсутствуют" } } diff --git a/plugins/setting-assets/lang/zh.json b/plugins/setting-assets/lang/zh.json index ac2092ebe17..14664d5c4a6 100644 --- a/plugins/setting-assets/lang/zh.json +++ b/plugins/setting-assets/lang/zh.json @@ -119,6 +119,17 @@ "DeleteSpaceTypeConfirm": "您确定要删除此空间类型吗?", "WorkspaceName": "工作区名称", "Workspace": "工作区", - "OwnerOrMaintainerRequired": "您需要成为工作空间所有者或维护者" + "OwnerOrMaintainerRequired": "您需要成为工作空间所有者或维护者", + "Backup": "备份", + "BackupLast": "最近备份", + "BackupTotalSnapshots": "快照总数", + "BackupTotalFiles": "文件", + "BackupSize": "备份大小", + "BackupLinkInfo": "备份目录位于", + "BackupBearerTokenInfo": "访问备份需要访问令牌。请点击复制按钮将令牌复制到剪贴板。", + "BackupSnapshots": "快照", + "BackupFileDownload": "下载", + "BackupFiles": "文件", + "BackupNoBackup": "暂无可用备份" } } diff --git a/plugins/setting-resources/src/components/Backup.svelte b/plugins/setting-resources/src/components/Backup.svelte new file mode 100644 index 00000000000..24641b779ef --- /dev/null +++ b/plugins/setting-resources/src/components/Backup.svelte @@ -0,0 +1,355 @@ + + + +
+
+ +
+ + + {#if loading} + + {:else if backupInfo == null} +
+
+ {:else} +
+ + + + + + + +
+ +
+
+
+
+ + + +
+
+
+ {#if backupInfo?.info?.snapshots} + {#each backupInfo.info.snapshots as snapshot, i} +
+ + +
+ #{i} + {formatDate(snapshot.date)} + {formatAgoHours(snapshot.date)} - + {getSnapshotSummary(snapshot)} +
+
+
+ {#if snapshot.domains} + {#each Object.entries(snapshot.domains) as [domain, data]} + {#if data.storage?.length} +
+
{domain}
+
+ {#each data.storage as filename} +
+
{filename}
+
+ + {getSize(fileSizes.get(filename) ?? 0)} + +
+
+
+
+ {/each} +
+
+ {/if} + {/each} + {/if} +
+
+
+ {/each} + {:else} +
+
+ {/if} + {#if backupInfo?.error} +
{backupInfo.error}
+ {/if} +
+ + + +
+
+
+ {#each backupInfo?.files ?? [] as file} +
+
{file.name}
+
+ + {getSize(file.size ?? 0)} + +
+
+
+
+ {/each} +
+ {/if} +
+
+ + diff --git a/plugins/setting-resources/src/index.ts b/plugins/setting-resources/src/index.ts index 024d4af0708..f5ab827bf8e 100644 --- a/plugins/setting-resources/src/index.ts +++ b/plugins/setting-resources/src/index.ts @@ -24,6 +24,7 @@ import EditEnum from './components/EditEnum.svelte' import EnumSetting from './components/EnumSetting.svelte' import Integrations from './components/Integrations.svelte' import General from './components/General.svelte' +import Backup from './components/Backup.svelte' import Owners from './components/Owners.svelte' import Password from './components/Password.svelte' import Privacy from './components/Privacy.svelte' @@ -110,6 +111,7 @@ export default async (): Promise => ({ EditEnum, EnumSetting, General, + Backup, Owners, CreateMixin, InviteSetting, diff --git a/plugins/setting-resources/src/types.ts b/plugins/setting-resources/src/types.ts new file mode 100644 index 00000000000..ab8d803c431 --- /dev/null +++ b/plugins/setting-resources/src/types.ts @@ -0,0 +1,51 @@ +/** + * @public + */ +export interface Snapshot { + added: Map + updated: Map + removed: string[] +} + +/** + * @public + */ +export interface SnapshotV6 { + added: Record + updated: Record + removed: string[] +} + +/** + * @public + */ +export interface DomainData { + snapshot?: string // 0.6 json snapshot + snapshots?: string[] + storage?: string[] + + // Some statistics + added: number + updated: number + removed: number +} + +/** + * @public + */ +export interface BackupSnapshot { + // _id => hash of added items. + domains: Record + date: number +} + +/** + * @public + */ +export interface BackupInfo { + workspace: string + version: string + snapshots: BackupSnapshot[] + snapshotsIndex?: number + lastTxId?: string +} diff --git a/plugins/setting/src/index.ts b/plugins/setting/src/index.ts index 427b5222d91..d57a708edc5 100644 --- a/plugins/setting/src/index.ts +++ b/plugins/setting/src/index.ts @@ -14,7 +14,7 @@ // import type { Account, AccountRole, Blob, Class, Configuration, Doc, Mixin, Ref } from '@hcengineering/core' -import type { Plugin } from '@hcengineering/platform' +import type { Metadata, Plugin } from '@hcengineering/platform' import { Asset, IntlString, Resource, plugin } from '@hcengineering/platform' import { TemplateField, TemplateFieldCategory } from '@hcengineering/templates' import { AnyComponent } from '@hcengineering/ui' @@ -133,7 +133,8 @@ export default plugin(settingId, { InviteSettings: '' as Ref, WorkspaceSetting: '' as Ref, ManageSpaces: '' as Ref, - Spaces: '' as Ref + Spaces: '' as Ref, + Backup: '' as Ref }, mixin: { Editable: '' as Ref>, @@ -166,7 +167,8 @@ export default plugin(settingId, { SpaceTypeRolesSectionEditor: '' as AnyComponent, RoleEditor: '' as AnyComponent, RoleAssignmentEditor: '' as AnyComponent, - RelationSetting: '' as AnyComponent + RelationSetting: '' as AnyComponent, + Backup: '' as AnyComponent }, string: { Settings: '' as IntlString, @@ -206,7 +208,18 @@ export default plugin(settingId, { Collections: '' as IntlString, SpaceTypes: '' as IntlString, Roles: '' as IntlString, - OwnerOrMaintainerRequired: '' as IntlString + OwnerOrMaintainerRequired: '' as IntlString, + Backup: '' as IntlString, + BackupLast: '' as IntlString, + BackupTotalSnapshots: '' as IntlString, + BackupTotalFiles: '' as IntlString, + BackupSize: '' as IntlString, + BackupLinkInfo: '' as IntlString, + BackupBearerTokenInfo: '' as IntlString, + BackupSnapshots: '' as IntlString, + BackupFileDownload: '' as IntlString, + BackupFiles: '' as IntlString, + BackupNoBackup: '' as IntlString }, icon: { AccountSettings: '' as Asset, @@ -234,5 +247,8 @@ export default plugin(settingId, { OwnerLastName: '' as Ref, OwnerPosition: '' as Ref, Value: '' as Ref + }, + metadata: { + BackupUrl: '' as Metadata } }) diff --git a/pods/front/src/__start.ts b/pods/front/src/__start.ts index 3a691438d0c..d4e44fa4a87 100644 --- a/pods/front/src/__start.ts +++ b/pods/front/src/__start.ts @@ -46,5 +46,6 @@ startFront(metricsContext, { ANALYTICS_COLLECTOR_URL: process.env.ANALYTICS_COLLECTOR_URL, AI_URL: process.env.AI_URL, TELEGRAM_BOT_URL: process.env.TELEGRAM_BOT_URL, - STATS_URL: process.env.STATS_API ?? process.env.STATS_URL + STATS_URL: process.env.STATS_API ?? process.env.STATS_URL, + BACKUP_URL: process.env.BACKUP_URL })