From 148bff9f7785719600664fed438205488a375b82 Mon Sep 17 00:00:00 2001 From: zadam Date: Wed, 18 Oct 2023 23:16:47 +0200 Subject: [PATCH] show existing backups and anonymized DBs, #4321 --- package-lock.json | 14 +++++------ package.json | 2 +- .../advanced/database_anonymization.js | 24 +++++++++++++++++++ .../widgets/type_widgets/options/backup.js | 22 +++++++++++++++++ src/routes/api/database.js | 22 ++++++++++++----- src/routes/routes.js | 2 ++ src/services/anonymization.js | 17 ++++++++++++- src/services/backup.js | 15 ++++++++++++ 8 files changed, 103 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 24ae2a6a1..6f7731af6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,7 +79,7 @@ }, "devDependencies": { "cross-env": "7.0.3", - "electron": "25.9.1", + "electron": "25.9.2", "electron-builder": "24.6.4", "electron-packager": "17.1.2", "electron-rebuild": "3.2.9", @@ -4276,9 +4276,9 @@ } }, "node_modules/electron": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.1.tgz", - "integrity": "sha512-Uo/Fh7igjoUXA/f90iTATZJesQEArVL1uLA672JefNWTLymdKSZkJKiCciu/Xnd0TS6qvdIOUGuJFSTQnKskXQ==", + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.2.tgz", + "integrity": "sha512-hVBN5rsrL99BKNHvzMeYy2PkAmewuIobu4U3o3EzVz4MDoLmMfW4yTH5GZ4RbJrpokoEky5IzGtRR/ggPzL6Fw==", "hasInstallScript": true, "dependencies": { "@electron/get": "^2.0.0", @@ -16633,9 +16633,9 @@ } }, "electron": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.1.tgz", - "integrity": "sha512-Uo/Fh7igjoUXA/f90iTATZJesQEArVL1uLA672JefNWTLymdKSZkJKiCciu/Xnd0TS6qvdIOUGuJFSTQnKskXQ==", + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.2.tgz", + "integrity": "sha512-hVBN5rsrL99BKNHvzMeYy2PkAmewuIobu4U3o3EzVz4MDoLmMfW4yTH5GZ4RbJrpokoEky5IzGtRR/ggPzL6Fw==", "requires": { "@electron/get": "^2.0.0", "@types/node": "^18.11.18", diff --git a/package.json b/package.json index 6e124be63..ff3526a0b 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ }, "devDependencies": { "cross-env": "7.0.3", - "electron": "25.9.1", + "electron": "25.9.2", "electron-builder": "24.6.4", "electron-packager": "17.1.2", "electron-rebuild": "3.2.9", diff --git a/src/public/app/widgets/type_widgets/options/advanced/database_anonymization.js b/src/public/app/widgets/type_widgets/options/advanced/database_anonymization.js index a17e0675b..9bdcce843 100644 --- a/src/public/app/widgets/type_widgets/options/advanced/database_anonymization.js +++ b/src/public/app/widgets/type_widgets/options/advanced/database_anonymization.js @@ -20,6 +20,10 @@ const TPL = `

You can decide yourself if you want to provide a fully or lightly anonymized database. Even fully anonymized DB is very useful, however in some cases lightly anonymized database can speed up the process of bug identification and fixing.

+ +
Existing anonymized databases
+ + `; export default class DatabaseAnonymizationOptions extends OptionsWidget { @@ -38,6 +42,8 @@ export default class DatabaseAnonymizationOptions extends OptionsWidget { else { toastService.showMessage(`Created fully anonymized database in ${resp.anonymizedFilePath}`, 10000); } + + this.refresh(); }); this.$anonymizeLightButton.on('click', async () => { @@ -51,6 +57,24 @@ export default class DatabaseAnonymizationOptions extends OptionsWidget { else { toastService.showMessage(`Created lightly anonymized database in ${resp.anonymizedFilePath}`, 10000); } + + this.refresh(); + }); + + this.$existingAnonymizedDatabases = this.$widget.find(".existing-anonymized-databases"); + } + + optionsLoaded(options) { + server.get("database/anonymized-databases").then(anonymizedDatabases => { + this.$existingAnonymizedDatabases.empty(); + + if (!anonymizedDatabases.length) { + anonymizedDatabases = [{filePath: "no anonymized database yet"}]; + } + + for (const {filePath} of anonymizedDatabases) { + this.$existingAnonymizedDatabases.append($("
  • ").text(filePath)); + } }); } } diff --git a/src/public/app/widgets/type_widgets/options/backup.js b/src/public/app/widgets/type_widgets/options/backup.js index d5430d30b..a5c5b0c13 100644 --- a/src/public/app/widgets/type_widgets/options/backup.js +++ b/src/public/app/widgets/type_widgets/options/backup.js @@ -37,6 +37,12 @@ const TPL = ` + +
    +

    Existing backups

    + +
      +
      `; export default class BackupOptions extends OptionsWidget { @@ -49,6 +55,8 @@ export default class BackupOptions extends OptionsWidget { const {backupFile} = await server.post('database/backup-database'); toastService.showMessage(`Database has been backed up to ${backupFile}`, 10000); + + this.refresh(); }); this.$dailyBackupEnabled = this.$widget.find(".daily-backup-enabled"); @@ -63,11 +71,25 @@ export default class BackupOptions extends OptionsWidget { this.$monthlyBackupEnabled.on('change', () => this.updateCheckboxOption('monthlyBackupEnabled', this.$monthlyBackupEnabled)); + + this.$existingBackupList = this.$widget.find(".existing-backup-list"); } optionsLoaded(options) { this.setCheckboxState(this.$dailyBackupEnabled, options.dailyBackupEnabled); this.setCheckboxState(this.$weeklyBackupEnabled, options.weeklyBackupEnabled); this.setCheckboxState(this.$monthlyBackupEnabled, options.monthlyBackupEnabled); + + server.get("database/backups").then(backupFiles => { + this.$existingBackupList.empty(); + + if (!backupFiles.length) { + backupFiles = [{filePath: "no backup yet"}]; + } + + for (const {filePath} of backupFiles) { + this.$existingBackupList.append($("
    • ").text(filePath)); + } + }); } } diff --git a/src/routes/api/database.js b/src/routes/api/database.js index 27658f39d..d8d8cfa9a 100644 --- a/src/routes/api/database.js +++ b/src/routes/api/database.js @@ -6,8 +6,8 @@ const backupService = require('../../services/backup'); const anonymizationService = require('../../services/anonymization'); const consistencyChecksService = require('../../services/consistency_checks'); -async function anonymize(req) { - return await anonymizationService.createAnonymizedCopy(req.params.type); +function getExistingBackups() { + return backupService.getExistingBackups(); } async function backupDatabase() { @@ -22,6 +22,18 @@ function vacuumDatabase() { log.info("Database has been vacuumed."); } +function findAndFixConsistencyIssues() { + consistencyChecksService.runOnDemandChecks(true); +} + +function getExistingAnonymizedDatabases() { + return anonymizationService.getExistingAnonymizedDatabases(); +} + +async function anonymize(req) { + return await anonymizationService.createAnonymizedCopy(req.params.type); +} + function checkIntegrity() { const results = sql.getRows("PRAGMA integrity_check"); @@ -32,14 +44,12 @@ function checkIntegrity() { }; } -function findAndFixConsistencyIssues() { - consistencyChecksService.runOnDemandChecks(true); -} - module.exports = { + getExistingBackups, backupDatabase, vacuumDatabase, findAndFixConsistencyIssues, + getExistingAnonymizedDatabases, anonymize, checkIntegrity }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 97a2f8de8..1cbe4cd83 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -289,9 +289,11 @@ function register(app) { apiRoute(GET, '/api/sql/schema', sqlRoute.getSchema); apiRoute(PST, '/api/sql/execute/:noteId', sqlRoute.execute); route(PST, '/api/database/anonymize/:type', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.anonymize, apiResultHandler, false); + apiRoute(GET, '/api/database/anonymized-databases', databaseRoute.getExistingAnonymizedDatabases); // backup requires execution outside of transaction route(PST, '/api/database/backup-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.backupDatabase, apiResultHandler, false); + apiRoute(GET, '/api/database/backups', databaseRoute.getExistingBackups); // VACUUM requires execution outside of transaction route(PST, '/api/database/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.vacuumDatabase, apiResultHandler, false); diff --git a/src/services/anonymization.js b/src/services/anonymization.js index 877a9b38b..160b6e2a2 100644 --- a/src/services/anonymization.js +++ b/src/services/anonymization.js @@ -4,6 +4,7 @@ const dataDir = require("./data_dir"); const dateUtils = require("./date_utils"); const Database = require("better-sqlite3"); const sql = require("./sql"); +const path = require("path"); function getFullAnonymizationScript() { // we want to delete all non-builtin attributes because they can contain sensitive names and values @@ -70,7 +71,21 @@ async function createAnonymizedCopy(type) { }; } +function getExistingAnonymizedDatabases() { + if (!fs.existsSync(dataDir.ANONYMIZED_DB_DIR)) { + return []; + } + + return fs.readdirSync(dataDir.ANONYMIZED_DB_DIR) + .filter(fileName => fileName.includes("anonymized")) + .map(fileName => ({ + fileName: fileName, + filePath: path.resolve(dataDir.ANONYMIZED_DB_DIR, fileName) + })); +} + module.exports = { getFullAnonymizationScript, - createAnonymizedCopy + createAnonymizedCopy, + getExistingAnonymizedDatabases } diff --git a/src/services/backup.js b/src/services/backup.js index c7908524f..808331ff9 100644 --- a/src/services/backup.js +++ b/src/services/backup.js @@ -8,6 +8,20 @@ const log = require('./log'); const syncMutexService = require('./sync_mutex'); const cls = require('./cls'); const sql = require('./sql'); +const path = require('path'); + +function getExistingBackups() { + if (!fs.existsSync(dataDir.BACKUP_DIR)) { + return []; + } + + return fs.readdirSync(dataDir.BACKUP_DIR) + .filter(fileName => fileName.includes("backup")) + .map(fileName => ({ + fileName: fileName, + filePath: path.resolve(dataDir.BACKUP_DIR, fileName) + })); +} function regularBackup() { cls.init(() => { @@ -58,6 +72,7 @@ if (!fs.existsSync(dataDir.BACKUP_DIR)) { } module.exports = { + getExistingBackups, backupNow, regularBackup };