mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 07:08:55 +02:00
feat(react/settings): port database anonymization
This commit is contained in:
parent
7e03774b8e
commit
16cd91eb02
@ -1,14 +1,16 @@
|
|||||||
import type { ComponentChildren } from "preact";
|
import type { ComponentChildren } from "preact";
|
||||||
|
import { CSSProperties } from "preact/compat";
|
||||||
|
|
||||||
interface ColumnProps {
|
interface ColumnProps {
|
||||||
md?: number;
|
md?: number;
|
||||||
children: ComponentChildren;
|
children: ComponentChildren;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
style?: CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Column({ md, children, className }: ColumnProps) {
|
export default function Column({ md, children, className, style }: ColumnProps) {
|
||||||
return (
|
return (
|
||||||
<div className={`col-md-${md ?? 6} ${className ?? ""}`}>
|
<div className={`col-md-${md ?? 6} ${className ?? ""}`} style={style}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -21,8 +21,6 @@ import RevisionsSnapshotIntervalOptions from "./options/other/revisions_snapshot
|
|||||||
import RevisionSnapshotsLimitOptions from "./options/other/revision_snapshots_limit.js";
|
import RevisionSnapshotsLimitOptions from "./options/other/revision_snapshots_limit.js";
|
||||||
import NetworkConnectionsOptions from "./options/other/network_connections.js";
|
import NetworkConnectionsOptions from "./options/other/network_connections.js";
|
||||||
import HtmlImportTagsOptions from "./options/other/html_import_tags.js";
|
import HtmlImportTagsOptions from "./options/other/html_import_tags.js";
|
||||||
import VacuumDatabaseOptions from "./options/advanced/vacuum_database.js";
|
|
||||||
import DatabaseAnonymizationOptions from "./options/advanced/database_anonymization.js";
|
|
||||||
import BackendLogWidget from "./content/backend_log.js";
|
import BackendLogWidget from "./content/backend_log.js";
|
||||||
import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js";
|
import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js";
|
||||||
import RibbonOptions from "./options/appearance/ribbon.js";
|
import RibbonOptions from "./options/appearance/ribbon.js";
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { DatabaseCheckIntegrityResponse } from "@triliumnext/commons";
|
import { AnonymizedDbResponse, DatabaseAnonymizeResponse, DatabaseCheckIntegrityResponse } from "@triliumnext/commons";
|
||||||
import { t } from "../../../services/i18n";
|
import { t } from "../../../services/i18n";
|
||||||
import server from "../../../services/server";
|
import server from "../../../services/server";
|
||||||
import toast from "../../../services/toast";
|
import toast from "../../../services/toast";
|
||||||
import Button from "../../react/Button";
|
import Button from "../../react/Button";
|
||||||
import FormText from "../../react/FormText";
|
import FormText from "../../react/FormText";
|
||||||
import OptionsSection from "./components/OptionsSection"
|
import OptionsSection from "./components/OptionsSection"
|
||||||
|
import Column from "../../react/Column";
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
export default function AdvancedSettings() {
|
export default function AdvancedSettings() {
|
||||||
return <>
|
return <>
|
||||||
<AdvancedSyncOptions />
|
<AdvancedSyncOptions />
|
||||||
<DatabaseIntegrityOptions />
|
<DatabaseIntegrityOptions />
|
||||||
|
<DatabaseAnonymizationOptions />
|
||||||
<VacuumDatabaseOptions />
|
<VacuumDatabaseOptions />
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
@ -69,6 +72,91 @@ function DatabaseIntegrityOptions() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DatabaseAnonymizationOptions() {
|
||||||
|
const [ existingAnonymizedDatabases, setExistingAnonymizedDatabases ] = useState<AnonymizedDbResponse[]>([]);
|
||||||
|
|
||||||
|
function refreshAnonymizedDatabase() {
|
||||||
|
server.get<AnonymizedDbResponse[]>("database/anonymized-databases").then(setExistingAnonymizedDatabases);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(refreshAnonymizedDatabase, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OptionsSection title={t("database_anonymization.title")}>
|
||||||
|
<FormText>{t("database_anonymization.choose_anonymization")}</FormText>
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
|
<DatabaseAnonymizationOption
|
||||||
|
title={t("database_anonymization.full_anonymization")}
|
||||||
|
description={t("database_anonymization.full_anonymization_description")}
|
||||||
|
buttonText={t("database_anonymization.save_fully_anonymized_database")}
|
||||||
|
buttonClick={async () => {
|
||||||
|
toast.showMessage(t("database_anonymization.creating_fully_anonymized_database"));
|
||||||
|
const resp = await server.post<DatabaseAnonymizeResponse>("database/anonymize/full");
|
||||||
|
|
||||||
|
if (!resp.success) {
|
||||||
|
toast.showError(t("database_anonymization.error_creating_anonymized_database"));
|
||||||
|
} else {
|
||||||
|
toast.showMessage(t("database_anonymization.successfully_created_fully_anonymized_database", { anonymizedFilePath: resp.anonymizedFilePath }), 10000);
|
||||||
|
refreshAnonymizedDatabase();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DatabaseAnonymizationOption
|
||||||
|
title={t("database_anonymization.light_anonymization")}
|
||||||
|
description={t("database_anonymization.light_anonymization_description")}
|
||||||
|
buttonText={t("database_anonymization.save_lightly_anonymized_database")}
|
||||||
|
buttonClick={async () => {
|
||||||
|
toast.showMessage(t("database_anonymization.creating_lightly_anonymized_database"));
|
||||||
|
const resp = await server.post<DatabaseAnonymizeResponse>("database/anonymize/light");
|
||||||
|
|
||||||
|
if (!resp.success) {
|
||||||
|
toast.showError(t("database_anonymization.error_creating_anonymized_database"));
|
||||||
|
} else {
|
||||||
|
toast.showMessage(t("database_anonymization.successfully_created_lightly_anonymized_database", { anonymizedFilePath: resp.anonymizedFilePath }), 10000);
|
||||||
|
refreshAnonymizedDatabase();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<ExistingAnonymizedDatabases databases={existingAnonymizedDatabases} />
|
||||||
|
</OptionsSection>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DatabaseAnonymizationOption({ title, description, buttonText, buttonClick }: { title: string, description: string, buttonText: string, buttonClick: () => void }) {
|
||||||
|
return (
|
||||||
|
<Column md={6} style={{ display: "flex", flexDirection: "column", alignItems: "flex-start", marginTop: "1em" }}>
|
||||||
|
<h5>{title}</h5>
|
||||||
|
<FormText>{description}</FormText>
|
||||||
|
<Button text={buttonText} onClick={buttonClick} />
|
||||||
|
</Column>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExistingAnonymizedDatabases({ databases }: { databases: AnonymizedDbResponse[] }) {
|
||||||
|
if (!databases.length) {
|
||||||
|
return <FormText>{t("database_anonymization.no_anonymized_database_yet")}</FormText>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table className="table table-stripped">
|
||||||
|
<thead>
|
||||||
|
<th>{t("database_anonymization.existing_anonymized_databases")}</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{databases.map(({ filePath }) => (
|
||||||
|
<tr>
|
||||||
|
<td>{filePath}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function VacuumDatabaseOptions() {
|
function VacuumDatabaseOptions() {
|
||||||
return (
|
return (
|
||||||
<OptionsSection title={t("vacuum_database.title")}>
|
<OptionsSection title={t("vacuum_database.title")}>
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
import OptionsWidget from "../options_widget.js";
|
|
||||||
import toastService from "../../../../services/toast.js";
|
|
||||||
import server from "../../../../services/server.js";
|
|
||||||
import { t } from "../../../../services/i18n.js";
|
|
||||||
import type { OptionMap } from "@triliumnext/commons";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
|
||||||
<div class="options-section">
|
|
||||||
<style>
|
|
||||||
.database-database-anonymization-option {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.database-database-anonymization-option p {
|
|
||||||
margin-top: .75em;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<h4>${t("database_anonymization.title")}</h4>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<p class="form-text">${t("database_anonymization.choose_anonymization")}</p>
|
|
||||||
|
|
||||||
<div class="col-md-6 database-database-anonymization-option">
|
|
||||||
<h5>${t("database_anonymization.full_anonymization")}</h5>
|
|
||||||
|
|
||||||
<p class="form-text">${t("database_anonymization.full_anonymization_description")}</p>
|
|
||||||
<button class="anonymize-full-button btn btn-secondary">${t("database_anonymization.save_fully_anonymized_database")}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 database-database-anonymization-option">
|
|
||||||
<h5>${t("database_anonymization.light_anonymization")}</h5>
|
|
||||||
|
|
||||||
<p class="form-text">${t("database_anonymization.light_anonymization_description")}</p>
|
|
||||||
|
|
||||||
<button class="anonymize-light-button btn btn-secondary">${t("database_anonymization.save_lightly_anonymized_database")}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<table class="existing-anonymized-databases-table table table-stripped">
|
|
||||||
<thead>
|
|
||||||
<th>${t("database_anonymization.existing_anonymized_databases")}</th>
|
|
||||||
</thead>
|
|
||||||
<tbody class="existing-anonymized-databases">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
// TODO: Deduplicate with server
|
|
||||||
interface AnonymizeResponse {
|
|
||||||
success: boolean;
|
|
||||||
anonymizedFilePath: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AnonymizedDbResponse {
|
|
||||||
filePath: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class DatabaseAnonymizationOptions extends OptionsWidget {
|
|
||||||
|
|
||||||
private $anonymizeFullButton!: JQuery<HTMLElement>;
|
|
||||||
private $anonymizeLightButton!: JQuery<HTMLElement>;
|
|
||||||
private $existingAnonymizedDatabases!: JQuery<HTMLElement>;
|
|
||||||
|
|
||||||
doRender() {
|
|
||||||
this.$widget = $(TPL);
|
|
||||||
this.$anonymizeFullButton = this.$widget.find(".anonymize-full-button");
|
|
||||||
this.$anonymizeLightButton = this.$widget.find(".anonymize-light-button");
|
|
||||||
this.$anonymizeFullButton.on("click", async () => {
|
|
||||||
toastService.showMessage(t("database_anonymization.creating_fully_anonymized_database"));
|
|
||||||
|
|
||||||
const resp = await server.post<AnonymizeResponse>("database/anonymize/full");
|
|
||||||
|
|
||||||
if (!resp.success) {
|
|
||||||
toastService.showError(t("database_anonymization.error_creating_anonymized_database"));
|
|
||||||
} else {
|
|
||||||
toastService.showMessage(t("database_anonymization.successfully_created_fully_anonymized_database", { anonymizedFilePath: resp.anonymizedFilePath }), 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$anonymizeLightButton.on("click", async () => {
|
|
||||||
toastService.showMessage(t("database_anonymization.creating_lightly_anonymized_database"));
|
|
||||||
|
|
||||||
const resp = await server.post<AnonymizeResponse>("database/anonymize/light");
|
|
||||||
|
|
||||||
if (!resp.success) {
|
|
||||||
toastService.showError(t("database_anonymization.error_creating_anonymized_database"));
|
|
||||||
} else {
|
|
||||||
toastService.showMessage(t("database_anonymization.successfully_created_lightly_anonymized_database", { anonymizedFilePath: resp.anonymizedFilePath }), 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$existingAnonymizedDatabases = this.$widget.find(".existing-anonymized-databases");
|
|
||||||
}
|
|
||||||
|
|
||||||
optionsLoaded(options: OptionMap) {
|
|
||||||
server.get<AnonymizedDbResponse[]>("database/anonymized-databases").then((anonymizedDatabases) => {
|
|
||||||
this.$existingAnonymizedDatabases.empty();
|
|
||||||
|
|
||||||
if (!anonymizedDatabases.length) {
|
|
||||||
anonymizedDatabases = [{ filePath: t("database_anonymization.no_anonymized_database_yet") }];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const { filePath } of anonymizedDatabases) {
|
|
||||||
this.$existingAnonymizedDatabases.append($("<tr>").append($("<td>").text(filePath)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ import dateUtils from "./date_utils.js";
|
|||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
import sql from "./sql.js";
|
import sql from "./sql.js";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { AnonymizedDbResponse, DatabaseAnonymizeResponse } from "@triliumnext/commons";
|
||||||
|
|
||||||
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
|
||||||
@ -73,7 +74,7 @@ async function createAnonymizedCopy(type: "full" | "light") {
|
|||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
anonymizedFilePath: anonymizedFile
|
anonymizedFilePath: anonymizedFile
|
||||||
};
|
} satisfies DatabaseAnonymizeResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExistingAnonymizedDatabases() {
|
function getExistingAnonymizedDatabases() {
|
||||||
@ -87,7 +88,7 @@ function getExistingAnonymizedDatabases() {
|
|||||||
.map((fileName) => ({
|
.map((fileName) => ({
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
filePath: path.resolve(dataDir.ANONYMIZED_DB_DIR, fileName)
|
filePath: path.resolve(dataDir.ANONYMIZED_DB_DIR, fileName)
|
||||||
}));
|
})) satisfies AnonymizedDbResponse[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -67,3 +67,13 @@ export interface DatabaseCheckIntegrityResponse {
|
|||||||
integrity_check: string;
|
integrity_check: string;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DatabaseAnonymizeResponse {
|
||||||
|
success: boolean;
|
||||||
|
anonymizedFilePath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnonymizedDbResponse {
|
||||||
|
filePath: string;
|
||||||
|
fileName: string;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user