From 16cd91eb027d628c5b31c7d72c5aef98fb427ede Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 14 Aug 2025 23:10:25 +0300 Subject: [PATCH] feat(react/settings): port database anonymization --- apps/client/src/widgets/react/Column.tsx | 6 +- .../widgets/type_widgets/content_widget.tsx | 2 - .../widgets/type_widgets/options/advanced.tsx | 90 ++++++++++++- .../advanced/database_anonymization.ts | 119 ------------------ apps/server/src/services/anonymization.ts | 5 +- packages/commons/src/lib/server_api.ts | 10 ++ 6 files changed, 106 insertions(+), 126 deletions(-) delete mode 100644 apps/client/src/widgets/type_widgets/options/advanced/database_anonymization.ts diff --git a/apps/client/src/widgets/react/Column.tsx b/apps/client/src/widgets/react/Column.tsx index 12b8ac9e5..8fd70bf38 100644 --- a/apps/client/src/widgets/react/Column.tsx +++ b/apps/client/src/widgets/react/Column.tsx @@ -1,14 +1,16 @@ import type { ComponentChildren } from "preact"; +import { CSSProperties } from "preact/compat"; interface ColumnProps { md?: number; children: ComponentChildren; className?: string; + style?: CSSProperties; } -export default function Column({ md, children, className }: ColumnProps) { +export default function Column({ md, children, className, style }: ColumnProps) { return ( -
+
{children}
) diff --git a/apps/client/src/widgets/type_widgets/content_widget.tsx b/apps/client/src/widgets/type_widgets/content_widget.tsx index e3ff15330..d4f6978b9 100644 --- a/apps/client/src/widgets/type_widgets/content_widget.tsx +++ b/apps/client/src/widgets/type_widgets/content_widget.tsx @@ -21,8 +21,6 @@ import RevisionsSnapshotIntervalOptions from "./options/other/revisions_snapshot import RevisionSnapshotsLimitOptions from "./options/other/revision_snapshots_limit.js"; import NetworkConnectionsOptions from "./options/other/network_connections.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 AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js"; import RibbonOptions from "./options/appearance/ribbon.js"; diff --git a/apps/client/src/widgets/type_widgets/options/advanced.tsx b/apps/client/src/widgets/type_widgets/options/advanced.tsx index 643ff7bb3..b16dca89d 100644 --- a/apps/client/src/widgets/type_widgets/options/advanced.tsx +++ b/apps/client/src/widgets/type_widgets/options/advanced.tsx @@ -1,15 +1,18 @@ -import { DatabaseCheckIntegrityResponse } from "@triliumnext/commons"; +import { AnonymizedDbResponse, DatabaseAnonymizeResponse, DatabaseCheckIntegrityResponse } from "@triliumnext/commons"; import { t } from "../../../services/i18n"; import server from "../../../services/server"; import toast from "../../../services/toast"; import Button from "../../react/Button"; import FormText from "../../react/FormText"; import OptionsSection from "./components/OptionsSection" +import Column from "../../react/Column"; +import { useEffect, useState } from "preact/hooks"; export default function AdvancedSettings() { return <> + ; } @@ -69,6 +72,91 @@ function DatabaseIntegrityOptions() { ) } +function DatabaseAnonymizationOptions() { + const [ existingAnonymizedDatabases, setExistingAnonymizedDatabases ] = useState([]); + + function refreshAnonymizedDatabase() { + server.get("database/anonymized-databases").then(setExistingAnonymizedDatabases); + } + + useEffect(refreshAnonymizedDatabase, []); + + return ( + + {t("database_anonymization.choose_anonymization")} + +
+ { + toast.showMessage(t("database_anonymization.creating_fully_anonymized_database")); + const resp = await server.post("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(); + } + }} + /> + { + toast.showMessage(t("database_anonymization.creating_lightly_anonymized_database")); + const resp = await server.post("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(); + } + }} + /> +
+ +
+ +
+ ) +} + +function DatabaseAnonymizationOption({ title, description, buttonText, buttonClick }: { title: string, description: string, buttonText: string, buttonClick: () => void }) { + return ( + +
{title}
+ {description} + -
- -
-
${t("database_anonymization.light_anonymization")}
- -

${t("database_anonymization.light_anonymization_description")}

- - -
- - -
- - - - - - - -
${t("database_anonymization.existing_anonymized_databases")}
-`; - -// TODO: Deduplicate with server -interface AnonymizeResponse { - success: boolean; - anonymizedFilePath: string; -} - -interface AnonymizedDbResponse { - filePath: string; -} - -export default class DatabaseAnonymizationOptions extends OptionsWidget { - - private $anonymizeFullButton!: JQuery; - private $anonymizeLightButton!: JQuery; - private $existingAnonymizedDatabases!: JQuery; - - 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("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("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("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($("").append($("").text(filePath))); - } - }); - } -} diff --git a/apps/server/src/services/anonymization.ts b/apps/server/src/services/anonymization.ts index bcc614c1f..6950be72d 100644 --- a/apps/server/src/services/anonymization.ts +++ b/apps/server/src/services/anonymization.ts @@ -5,6 +5,7 @@ import dateUtils from "./date_utils.js"; import Database from "better-sqlite3"; import sql from "./sql.js"; import path from "path"; +import { AnonymizedDbResponse, DatabaseAnonymizeResponse } from "@triliumnext/commons"; function getFullAnonymizationScript() { // 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 { success: true, anonymizedFilePath: anonymizedFile - }; + } satisfies DatabaseAnonymizeResponse; } function getExistingAnonymizedDatabases() { @@ -87,7 +88,7 @@ function getExistingAnonymizedDatabases() { .map((fileName) => ({ fileName: fileName, filePath: path.resolve(dataDir.ANONYMIZED_DB_DIR, fileName) - })); + })) satisfies AnonymizedDbResponse[]; } export default { diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index ce520db83..543246f8f 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -67,3 +67,13 @@ export interface DatabaseCheckIntegrityResponse { integrity_check: string; }[]; } + +export interface DatabaseAnonymizeResponse { + success: boolean; + anonymizedFilePath: string; +} + +export interface AnonymizedDbResponse { + filePath: string; + fileName: string; +}