show existing backups and anonymized DBs, #4321

This commit is contained in:
zadam 2023-10-18 23:16:47 +02:00
parent 2132cf3bdc
commit 148bff9f77
8 changed files with 103 additions and 15 deletions

14
package-lock.json generated
View File

@ -79,7 +79,7 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "7.0.3", "cross-env": "7.0.3",
"electron": "25.9.1", "electron": "25.9.2",
"electron-builder": "24.6.4", "electron-builder": "24.6.4",
"electron-packager": "17.1.2", "electron-packager": "17.1.2",
"electron-rebuild": "3.2.9", "electron-rebuild": "3.2.9",
@ -4276,9 +4276,9 @@
} }
}, },
"node_modules/electron": { "node_modules/electron": {
"version": "25.9.1", "version": "25.9.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.1.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.2.tgz",
"integrity": "sha512-Uo/Fh7igjoUXA/f90iTATZJesQEArVL1uLA672JefNWTLymdKSZkJKiCciu/Xnd0TS6qvdIOUGuJFSTQnKskXQ==", "integrity": "sha512-hVBN5rsrL99BKNHvzMeYy2PkAmewuIobu4U3o3EzVz4MDoLmMfW4yTH5GZ4RbJrpokoEky5IzGtRR/ggPzL6Fw==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@electron/get": "^2.0.0", "@electron/get": "^2.0.0",
@ -16633,9 +16633,9 @@
} }
}, },
"electron": { "electron": {
"version": "25.9.1", "version": "25.9.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.1.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.2.tgz",
"integrity": "sha512-Uo/Fh7igjoUXA/f90iTATZJesQEArVL1uLA672JefNWTLymdKSZkJKiCciu/Xnd0TS6qvdIOUGuJFSTQnKskXQ==", "integrity": "sha512-hVBN5rsrL99BKNHvzMeYy2PkAmewuIobu4U3o3EzVz4MDoLmMfW4yTH5GZ4RbJrpokoEky5IzGtRR/ggPzL6Fw==",
"requires": { "requires": {
"@electron/get": "^2.0.0", "@electron/get": "^2.0.0",
"@types/node": "^18.11.18", "@types/node": "^18.11.18",

View File

@ -97,7 +97,7 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "7.0.3", "cross-env": "7.0.3",
"electron": "25.9.1", "electron": "25.9.2",
"electron-builder": "24.6.4", "electron-builder": "24.6.4",
"electron-packager": "17.1.2", "electron-packager": "17.1.2",
"electron-rebuild": "3.2.9", "electron-rebuild": "3.2.9",

View File

@ -20,6 +20,10 @@ const TPL = `
<p>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.</p> <p>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.</p>
<button class="anonymize-light-button btn">Save lightly anonymized database</button> <button class="anonymize-light-button btn">Save lightly anonymized database</button>
<h5>Existing anonymized databases</h5>
<ul class="existing-anonymized-databases"></ul>
</div>`; </div>`;
export default class DatabaseAnonymizationOptions extends OptionsWidget { export default class DatabaseAnonymizationOptions extends OptionsWidget {
@ -38,6 +42,8 @@ export default class DatabaseAnonymizationOptions extends OptionsWidget {
else { else {
toastService.showMessage(`Created fully anonymized database in ${resp.anonymizedFilePath}`, 10000); toastService.showMessage(`Created fully anonymized database in ${resp.anonymizedFilePath}`, 10000);
} }
this.refresh();
}); });
this.$anonymizeLightButton.on('click', async () => { this.$anonymizeLightButton.on('click', async () => {
@ -51,6 +57,24 @@ export default class DatabaseAnonymizationOptions extends OptionsWidget {
else { else {
toastService.showMessage(`Created lightly anonymized database in ${resp.anonymizedFilePath}`, 10000); 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($("<li>").text(filePath));
}
}); });
} }
} }

View File

@ -37,6 +37,12 @@ const TPL = `
<button class="backup-database-button btn">Backup database now</button> <button class="backup-database-button btn">Backup database now</button>
</div> </div>
<div class="options-section">
<h4>Existing backups</h4>
<ul class="existing-backup-list"></ul>
</div>
`; `;
export default class BackupOptions extends OptionsWidget { export default class BackupOptions extends OptionsWidget {
@ -49,6 +55,8 @@ export default class BackupOptions extends OptionsWidget {
const {backupFile} = await server.post('database/backup-database'); const {backupFile} = await server.post('database/backup-database');
toastService.showMessage(`Database has been backed up to ${backupFile}`, 10000); toastService.showMessage(`Database has been backed up to ${backupFile}`, 10000);
this.refresh();
}); });
this.$dailyBackupEnabled = this.$widget.find(".daily-backup-enabled"); this.$dailyBackupEnabled = this.$widget.find(".daily-backup-enabled");
@ -63,11 +71,25 @@ export default class BackupOptions extends OptionsWidget {
this.$monthlyBackupEnabled.on('change', () => this.$monthlyBackupEnabled.on('change', () =>
this.updateCheckboxOption('monthlyBackupEnabled', this.$monthlyBackupEnabled)); this.updateCheckboxOption('monthlyBackupEnabled', this.$monthlyBackupEnabled));
this.$existingBackupList = this.$widget.find(".existing-backup-list");
} }
optionsLoaded(options) { optionsLoaded(options) {
this.setCheckboxState(this.$dailyBackupEnabled, options.dailyBackupEnabled); this.setCheckboxState(this.$dailyBackupEnabled, options.dailyBackupEnabled);
this.setCheckboxState(this.$weeklyBackupEnabled, options.weeklyBackupEnabled); this.setCheckboxState(this.$weeklyBackupEnabled, options.weeklyBackupEnabled);
this.setCheckboxState(this.$monthlyBackupEnabled, options.monthlyBackupEnabled); 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($("<li>").text(filePath));
}
});
} }
} }

View File

@ -6,8 +6,8 @@ const backupService = require('../../services/backup');
const anonymizationService = require('../../services/anonymization'); const anonymizationService = require('../../services/anonymization');
const consistencyChecksService = require('../../services/consistency_checks'); const consistencyChecksService = require('../../services/consistency_checks');
async function anonymize(req) { function getExistingBackups() {
return await anonymizationService.createAnonymizedCopy(req.params.type); return backupService.getExistingBackups();
} }
async function backupDatabase() { async function backupDatabase() {
@ -22,6 +22,18 @@ function vacuumDatabase() {
log.info("Database has been vacuumed."); 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() { function checkIntegrity() {
const results = sql.getRows("PRAGMA integrity_check"); const results = sql.getRows("PRAGMA integrity_check");
@ -32,14 +44,12 @@ function checkIntegrity() {
}; };
} }
function findAndFixConsistencyIssues() {
consistencyChecksService.runOnDemandChecks(true);
}
module.exports = { module.exports = {
getExistingBackups,
backupDatabase, backupDatabase,
vacuumDatabase, vacuumDatabase,
findAndFixConsistencyIssues, findAndFixConsistencyIssues,
getExistingAnonymizedDatabases,
anonymize, anonymize,
checkIntegrity checkIntegrity
}; };

View File

@ -289,9 +289,11 @@ function register(app) {
apiRoute(GET, '/api/sql/schema', sqlRoute.getSchema); apiRoute(GET, '/api/sql/schema', sqlRoute.getSchema);
apiRoute(PST, '/api/sql/execute/:noteId', sqlRoute.execute); apiRoute(PST, '/api/sql/execute/:noteId', sqlRoute.execute);
route(PST, '/api/database/anonymize/:type', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.anonymize, apiResultHandler, false); 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 // backup requires execution outside of transaction
route(PST, '/api/database/backup-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.backupDatabase, apiResultHandler, false); 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 // VACUUM requires execution outside of transaction
route(PST, '/api/database/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.vacuumDatabase, apiResultHandler, false); route(PST, '/api/database/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.vacuumDatabase, apiResultHandler, false);

View File

@ -4,6 +4,7 @@ const dataDir = require("./data_dir");
const dateUtils = require("./date_utils"); const dateUtils = require("./date_utils");
const Database = require("better-sqlite3"); const Database = require("better-sqlite3");
const sql = require("./sql"); const sql = require("./sql");
const path = require("path");
function getFullAnonymizationScript() { function getFullAnonymizationScript() {
// we want to delete all non-builtin attributes because they can contain sensitive names and values // 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 = { module.exports = {
getFullAnonymizationScript, getFullAnonymizationScript,
createAnonymizedCopy createAnonymizedCopy,
getExistingAnonymizedDatabases
} }

View File

@ -8,6 +8,20 @@ const log = require('./log');
const syncMutexService = require('./sync_mutex'); const syncMutexService = require('./sync_mutex');
const cls = require('./cls'); const cls = require('./cls');
const sql = require('./sql'); 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() { function regularBackup() {
cls.init(() => { cls.init(() => {
@ -58,6 +72,7 @@ if (!fs.existsSync(dataDir.BACKUP_DIR)) {
} }
module.exports = { module.exports = {
getExistingBackups,
backupNow, backupNow,
regularBackup regularBackup
}; };