mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge remote-tracking branch 'origin/stable'
# Conflicts: # libraries/ckeditor/ckeditor.js # libraries/ckeditor/ckeditor.js.map
This commit is contained in:
commit
d65624d8d2
@ -24,6 +24,13 @@ const TPL = `
|
|||||||
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
|
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
|
||||||
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
||||||
|
|
||||||
|
<h4>Backup database</h4>
|
||||||
|
|
||||||
|
<button id="backup-database-button" class="btn">Backup database</button>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
<h4>Vacuum database</h4>
|
<h4>Vacuum database</h4>
|
||||||
|
|
||||||
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
|
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
|
||||||
@ -37,6 +44,7 @@ export default class AdvancedOptions {
|
|||||||
this.$forceFullSyncButton = $("#force-full-sync-button");
|
this.$forceFullSyncButton = $("#force-full-sync-button");
|
||||||
this.$fillSyncRowsButton = $("#fill-sync-rows-button");
|
this.$fillSyncRowsButton = $("#fill-sync-rows-button");
|
||||||
this.$anonymizeButton = $("#anonymize-button");
|
this.$anonymizeButton = $("#anonymize-button");
|
||||||
|
this.$backupDatabaseButton = $("#backup-database-button");
|
||||||
this.$vacuumDatabaseButton = $("#vacuum-database-button");
|
this.$vacuumDatabaseButton = $("#vacuum-database-button");
|
||||||
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
|
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
|
||||||
|
|
||||||
@ -58,16 +66,22 @@ export default class AdvancedOptions {
|
|||||||
toastService.showMessage("Created anonymized database");
|
toastService.showMessage("Created anonymized database");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$backupDatabaseButton.on('click', async () => {
|
||||||
|
const {backupFile} = await server.post('database/backup-database');
|
||||||
|
|
||||||
|
toastService.showMessage("Database has been backed up to " + backupFile, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
this.$vacuumDatabaseButton.on('click', async () => {
|
this.$vacuumDatabaseButton.on('click', async () => {
|
||||||
await server.post('cleanup/vacuum-database');
|
await server.post('database/vacuum-database');
|
||||||
|
|
||||||
toastService.showMessage("Database has been vacuumed");
|
toastService.showMessage("Database has been vacuumed");
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
|
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
|
||||||
await server.post('cleanup/find-and-fix-consistency-issues');
|
await server.post('database/find-and-fix-consistency-issues');
|
||||||
|
|
||||||
toastService.showMessage("Consistency issues should be fixed.");
|
toastService.showMessage("Consistency issues should be fixed.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,17 +5,23 @@ import TypeWidget from "./type_widget.js";
|
|||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="note-detail-file note-detail-printable">
|
<div class="note-detail-file note-detail-printable">
|
||||||
|
<style>
|
||||||
|
.file-table td {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<table class="file-table">
|
<table class="file-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th nowrap>Note ID:</th>
|
<th>Note ID:</th>
|
||||||
<td class="file-note-id"></td>
|
<td class="file-note-id"></td>
|
||||||
<th nowrap>Original file name:</th>
|
<th>Original file name:</th>
|
||||||
<td class="file-filename"></td>
|
<td class="file-filename"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th nowrap>File type:</th>
|
<th>File type:</th>
|
||||||
<td class="file-filetype"></td>
|
<td class="file-filetype"></td>
|
||||||
<th nowrap>File size:</th>
|
<th>File size:</th>
|
||||||
<td class="file-filesize"></td>
|
<td class="file-filesize"></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@ -94,7 +100,7 @@ export default class FileTypeWidget extends TypeWidget {
|
|||||||
toastService.showError("Upload of a new file revision failed.");
|
toastService.showError("Upload of a new file revision failed.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.$widget;
|
return this.$widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,4 +136,4 @@ export default class FileTypeWidget extends TypeWidget {
|
|||||||
getFileUrl() {
|
getFileUrl() {
|
||||||
return utils.getUrlForDownload("api/notes/" + this.noteId + "/download");
|
return utils.getUrlForDownload("api/notes/" + this.noteId + "/download");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,15 @@
|
|||||||
|
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
|
const backupService = require('../../services/backup');
|
||||||
const consistencyChecksService = require('../../services/consistency_checks');
|
const consistencyChecksService = require('../../services/consistency_checks');
|
||||||
|
|
||||||
|
async function backupDatabase() {
|
||||||
|
return {
|
||||||
|
backupFile: await backupService.backupNow("now")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function vacuumDatabase() {
|
async function vacuumDatabase() {
|
||||||
await sql.execute("VACUUM");
|
await sql.execute("VACUUM");
|
||||||
|
|
||||||
@ -15,6 +22,7 @@ async function findAndFixConsistencyIssues() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
backupDatabase,
|
||||||
vacuumDatabase,
|
vacuumDatabase,
|
||||||
findAndFixConsistencyIssues
|
findAndFixConsistencyIssues
|
||||||
};
|
};
|
@ -16,7 +16,7 @@ const ApiToken = require('../../entities/api_token');
|
|||||||
|
|
||||||
async function loginSync(req) {
|
async function loginSync(req) {
|
||||||
if (!await sqlInit.schemaExists()) {
|
if (!await sqlInit.schemaExists()) {
|
||||||
return [400, { message: "DB schema does not exist, can't sync." }];
|
return [500, { message: "DB schema does not exist, can't sync." }];
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestampStr = req.body.timestamp;
|
const timestampStr = req.body.timestamp;
|
||||||
@ -27,7 +27,7 @@ async function loginSync(req) {
|
|||||||
|
|
||||||
// login token is valid for 5 minutes
|
// login token is valid for 5 minutes
|
||||||
if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) {
|
if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) {
|
||||||
return [400, { message: 'Auth request time is out of sync, please check that both client and server have correct time.' }];
|
return [401, { message: 'Auth request time is out of sync, please check that both client and server have correct time.' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncVersion = req.body.syncVersion;
|
const syncVersion = req.body.syncVersion;
|
||||||
@ -102,4 +102,4 @@ module.exports = {
|
|||||||
loginSync,
|
loginSync,
|
||||||
loginToProtectedSession,
|
loginToProtectedSession,
|
||||||
token
|
token
|
||||||
};
|
};
|
||||||
|
@ -25,7 +25,7 @@ const importRoute = require('./api/import');
|
|||||||
const setupApiRoute = require('./api/setup');
|
const setupApiRoute = require('./api/setup');
|
||||||
const sqlRoute = require('./api/sql');
|
const sqlRoute = require('./api/sql');
|
||||||
const anonymizationRoute = require('./api/anonymization');
|
const anonymizationRoute = require('./api/anonymization');
|
||||||
const cleanupRoute = require('./api/cleanup');
|
const databaseRoute = require('./api/database');
|
||||||
const imageRoute = require('./api/image');
|
const imageRoute = require('./api/image');
|
||||||
const attributesRoute = require('./api/attributes');
|
const attributesRoute = require('./api/attributes');
|
||||||
const scriptRoute = require('./api/script');
|
const scriptRoute = require('./api/script');
|
||||||
@ -223,10 +223,13 @@ function register(app) {
|
|||||||
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
|
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
|
||||||
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
|
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
|
||||||
|
|
||||||
// VACUUM requires execution outside of transaction
|
// backup requires execution outside of transaction
|
||||||
route(POST, '/api/cleanup/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.vacuumDatabase, apiResultHandler, false);
|
route(POST, '/api/database/backup-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.backupDatabase, apiResultHandler, false);
|
||||||
|
|
||||||
route(POST, '/api/cleanup/find-and-fix-consistency-issues', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.findAndFixConsistencyIssues, apiResultHandler, false);
|
// VACUUM requires execution outside of transaction
|
||||||
|
route(POST, '/api/database/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.vacuumDatabase, apiResultHandler, false);
|
||||||
|
|
||||||
|
route(POST, '/api/database/find-and-fix-consistency-issues', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.findAndFixConsistencyIssues, apiResultHandler, false);
|
||||||
|
|
||||||
apiRoute(POST, '/api/script/exec', scriptRoute.exec);
|
apiRoute(POST, '/api/script/exec', scriptRoute.exec);
|
||||||
apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run);
|
apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run);
|
||||||
@ -268,4 +271,4 @@ function register(app) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
register
|
register
|
||||||
};
|
};
|
||||||
|
@ -29,13 +29,38 @@ async function periodBackup(optionName, fileName, periodInSeconds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function backupNow(name) {
|
async function backupNow(name) {
|
||||||
|
const sql = require('./sql');
|
||||||
|
|
||||||
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state
|
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state
|
||||||
await syncMutexService.doExclusively(async () => {
|
return await syncMutexService.doExclusively(async () => {
|
||||||
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
|
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
|
||||||
|
|
||||||
fs.copySync(dataDir.DOCUMENT_PATH, backupFile);
|
try {
|
||||||
|
fs.unlinkSync(backupFile);
|
||||||
|
}
|
||||||
|
catch (e) {} // unlink throws exception if the file did not exist
|
||||||
|
|
||||||
log.info("Created backup at " + backupFile);
|
let success = false;
|
||||||
|
let attemptCount = 0
|
||||||
|
|
||||||
|
for (; attemptCount < 50 && !success; attemptCount++) {
|
||||||
|
try {
|
||||||
|
await sql.executeNoWrap(`VACUUM INTO '${backupFile}'`);
|
||||||
|
success++;
|
||||||
|
}
|
||||||
|
catch (e) {}
|
||||||
|
// we re-try since VACUUM is very picky and it can't run if there's any other query currently running
|
||||||
|
// which is difficult to guarantee so we just re-try
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attemptCount === 10) {
|
||||||
|
log.error(`Creating backup ${backupFile} failed`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.info("Created backup at " + backupFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return backupFile;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,4 +77,4 @@ sqlInit.dbReady.then(() => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
backupNow
|
backupNow
|
||||||
};
|
};
|
||||||
|
@ -153,6 +153,10 @@ async function execute(query, params = []) {
|
|||||||
return await wrap(async db => db.run(query, ...params), query);
|
return await wrap(async db => db.run(query, ...params), query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function executeNoWrap(query, params = []) {
|
||||||
|
await dbConnection.run(query, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
async function executeMany(query, params) {
|
async function executeMany(query, params) {
|
||||||
// essentially just alias
|
// essentially just alias
|
||||||
await getManyRows(query, params);
|
await getManyRows(query, params);
|
||||||
@ -264,6 +268,7 @@ module.exports = {
|
|||||||
getMap,
|
getMap,
|
||||||
getColumn,
|
getColumn,
|
||||||
execute,
|
execute,
|
||||||
|
executeNoWrap,
|
||||||
executeMany,
|
executeMany,
|
||||||
executeScript,
|
executeScript,
|
||||||
transactional,
|
transactional,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user