diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml
index f06e7b3e9..2fe298217 100644
--- a/.github/workflows/main-docker.yml
+++ b/.github/workflows/main-docker.yml
@@ -111,6 +111,9 @@ jobs:
- dockerfile: Dockerfile
platform: linux/arm/v7
image: ubuntu-24.04-arm
+ - dockerfile: Dockerfile
+ platform: linux/arm/v8
+ image: ubuntu-24.04-arm
runs-on: ${{ matrix.image }}
needs:
- test_docker
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 248e32538..52afe1134 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -37,7 +37,7 @@ jobs:
shell: bash
forge_platform: darwin
- name: linux
- image: ubuntu-latest
+ image: ubuntu-22.04
shell: bash
forge_platform: linux
- name: windows
@@ -102,7 +102,7 @@ jobs:
arch: [x64, arm64]
include:
- arch: x64
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
- arch: arm64
runs-on: ubuntu-24.04-arm
runs-on: ${{ matrix.runs-on }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f571d634d..5f2bfbc62 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -73,7 +73,7 @@ jobs:
arch: [x64, arm64]
include:
- arch: x64
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
- arch: arm64
runs-on: ubuntu-24.04-arm
runs-on: ${{ matrix.runs-on }}
diff --git a/.gitignore b/.gitignore
index cf2fa89dd..ecb8c9edf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,4 +43,6 @@ apps/*/out
upload
.rollup.cache
-*.tsbuildinfo
\ No newline at end of file
+*.tsbuildinfo
+
+/result
\ No newline at end of file
diff --git a/README.md b/README.md
index ce0b930bc..f62084f7d 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for q
* [Evernote](https://triliumnext.github.io/Docs/Wiki/evernote-import) and [Markdown import & export](https://triliumnext.github.io/Docs/Wiki/markdown)
* [Web Clipper](https://triliumnext.github.io/Docs/Wiki/web-clipper) for easy saving of web content
* Customizable UI (sidebar buttons, user-defined widgets, ...)
+* [Metrics](./docs/User%20Guide/User%20Guide/Advanced%20Usage/Metrics.md), along with a [Grafana Dashboard](./docs/User%20Guide/User%20Guide/Advanced%20Usage/Metrics/grafana-dashboard.json)
✨ Check out the following third-party resources/communities for more TriliumNext related goodies:
diff --git a/_regroup/bin/build-docker.sh b/_regroup/bin/build-docker.sh
deleted file mode 100644
index d95c289d4..000000000
--- a/_regroup/bin/build-docker.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-
-set -e # Fail on any command error
-
-VERSION=`jq -r ".version" package.json`
-SERIES=${VERSION:0:4}-latest
-
-sudo docker build -t triliumnext/notes:$VERSION --network host -t triliumnext/notes:$SERIES .
-
-if [[ $VERSION != *"beta"* ]]; then
- sudo docker tag triliumnext/notes:$VERSION triliumnext/notes:latest
-fi
diff --git a/_regroup/package.json b/_regroup/package.json
index 80e210e1d..117be7cc6 100644
--- a/_regroup/package.json
+++ b/_regroup/package.json
@@ -36,12 +36,12 @@
},
"devDependencies": {
"@playwright/test": "1.52.0",
- "@stylistic/eslint-plugin": "4.4.0",
+ "@stylistic/eslint-plugin": "4.4.1",
"@types/express": "5.0.1",
- "@types/node": "22.15.29",
+ "@types/node": "22.15.30",
"@types/yargs": "17.0.33",
- "@vitest/coverage-v8": "3.1.4",
- "eslint": "9.27.0",
+ "@vitest/coverage-v8": "3.2.2",
+ "eslint": "9.28.0",
"eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25",
"jsdoc": "4.0.4",
diff --git a/_regroup/test-etapi/_login.http b/_regroup/test-etapi/_login.http
deleted file mode 100644
index 9976e7cd4..000000000
--- a/_regroup/test-etapi/_login.http
+++ /dev/null
@@ -1,12 +0,0 @@
-POST {{triliumHost}}/etapi/auth/login
-Content-Type: application/json
-
-{
- "password": "1234"
-}
-
-> {%
- client.assert(response.status === 201);
-
- client.global.set("authToken", response.body.authToken);
-%}
diff --git a/_regroup/test-etapi/api-metrics.http b/_regroup/test-etapi/api-metrics.http
deleted file mode 100644
index 78aee7217..000000000
--- a/_regroup/test-etapi/api-metrics.http
+++ /dev/null
@@ -1,43 +0,0 @@
-### Test regular API metrics endpoint (requires session authentication)
-
-### Get metrics from regular API (default Prometheus format)
-GET {{triliumHost}}/api/metrics
-
-> {%
-client.test("API metrics endpoint returns Prometheus format by default", function() {
- client.assert(response.status === 200, "Response status is not 200");
- client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain");
- client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric");
- client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric");
- client.assert(response.body.includes("# HELP"), "Should contain HELP comments");
- client.assert(response.body.includes("# TYPE"), "Should contain TYPE comments");
-});
-%}
-
-### Get metrics in JSON format
-GET {{triliumHost}}/api/metrics?format=json
-
-> {%
-client.test("API metrics endpoint returns JSON when requested", function() {
- client.assert(response.status === 200, "Response status is not 200");
- client.assert(response.headers["content-type"].includes("application/json"), "Content-Type should be application/json");
- client.assert(response.body.version, "Version info not present");
- client.assert(response.body.database, "Database info not present");
- client.assert(response.body.timestamp, "Timestamp not present");
- client.assert(typeof response.body.database.totalNotes === 'number', "Total notes should be a number");
- client.assert(typeof response.body.database.activeNotes === 'number', "Active notes should be a number");
- client.assert(response.body.noteTypes, "Note types breakdown not present");
- client.assert(response.body.attachmentTypes, "Attachment types breakdown not present");
- client.assert(response.body.statistics, "Statistics not present");
-});
-%}
-
-### Test invalid format parameter
-GET {{triliumHost}}/api/metrics?format=xml
-
-> {%
-client.test("Invalid format parameter returns error", function() {
- client.assert(response.status === 500, "Response status should be 500");
- client.assert(response.body.message.includes("prometheus"), "Error message should mention supported formats");
-});
-%}
\ No newline at end of file
diff --git a/_regroup/test-etapi/app-info.http b/_regroup/test-etapi/app-info.http
deleted file mode 100644
index a851005c2..000000000
--- a/_regroup/test-etapi/app-info.http
+++ /dev/null
@@ -1,7 +0,0 @@
-GET {{triliumHost}}/etapi/app-info
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.body.clipperProtocolVersion === "1.0");
-%}
diff --git a/_regroup/test-etapi/basic-auth.http b/_regroup/test-etapi/basic-auth.http
deleted file mode 100644
index cf79c357e..000000000
--- a/_regroup/test-etapi/basic-auth.http
+++ /dev/null
@@ -1,21 +0,0 @@
-GET {{triliumHost}}/etapi/app-info
-Authorization: Basic etapi {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.body.clipperProtocolVersion === "1.0");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/app-info
-Authorization: Basic etapi wrong
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/app-info
-Authorization: Basic wrong {{authToken}}
-
-> {% client.assert(response.status === 401); %}
diff --git a/_regroup/test-etapi/create-backup.http b/_regroup/test-etapi/create-backup.http
deleted file mode 100644
index 59ffbebc4..000000000
--- a/_regroup/test-etapi/create-backup.http
+++ /dev/null
@@ -1,4 +0,0 @@
-PUT {{triliumHost}}/etapi/backup/etapi_test
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 201); %}
diff --git a/_regroup/test-etapi/create-entities.http b/_regroup/test-etapi/create-entities.http
deleted file mode 100644
index 98dae28b1..000000000
--- a/_regroup/test-etapi/create-entities.http
+++ /dev/null
@@ -1,158 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "forcedId{{$randomInt}}",
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!",
- "dateCreated": "2023-08-21 23:38:51.123+0200",
- "utcDateCreated": "2023-08-21 23:38:51.123Z"
-}
-
-> {%
- client.assert(response.status === 201);
- client.assert(response.body.note.noteId.startsWith("forcedId"));
- client.assert(response.body.note.title == "Hello");
- client.assert(response.body.note.dateCreated == "2023-08-21 23:38:51.123+0200");
- client.assert(response.body.note.utcDateCreated == "2023-08-21 23:38:51.123Z");
- client.assert(response.body.branch.parentNoteId == "root");
-
- client.log(`Created note ` + response.body.note.noteId + ` and branch ` + response.body.branch.branchId);
-
- client.global.set("createdNoteId", response.body.note.noteId);
- client.global.set("createdBranchId", response.body.branch.branchId);
-%}
-
-### Clone to another location
-
-POST {{triliumHost}}/etapi/branches
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{createdNoteId}}",
- "parentNoteId": "_hidden"
-}
-
-> {%
- client.assert(response.status === 201);
- client.assert(response.body.parentNoteId == "_hidden");
-
- client.global.set("clonedBranchId", response.body.branchId);
-
- client.log(`Created cloned branch ` + response.body.branchId);
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.body.noteId == client.global.get("createdNoteId"));
- client.assert(response.body.title == "Hello");
- // order is not defined and may fail in the future
- client.assert(response.body.parentBranchIds[0] == client.global.get("clonedBranchId"))
- client.assert(response.body.parentBranchIds[1] == client.global.get("createdBranchId"));
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.body == "Hi there!");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.body.branchId == client.global.get("createdBranchId"));
- client.assert(response.body.parentNoteId == "root");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.body.branchId == client.global.get("clonedBranchId"));
- client.assert(response.body.parentNoteId == "_hidden");
-%}
-
-###
-
-POST {{triliumHost}}/etapi/attributes
-Content-Type: application/json
-Authorization: {{authToken}}
-
-{
- "attributeId": "forcedAttributeId{{$randomInt}}",
- "noteId": "{{createdNoteId}}",
- "type": "label",
- "name": "mylabel",
- "value": "val",
- "isInheritable": true
-}
-
-> {%
- client.assert(response.status === 201);
- client.assert(response.body.attributeId.startsWith("forcedAttributeId"));
-
- client.global.set("createdAttributeId", response.body.attributeId);
-%}
-
-###
-
-GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.body.attributeId == client.global.get("createdAttributeId"));
-%}
-
-###
-
-POST {{triliumHost}}/etapi/attachments
-Content-Type: application/json
-Authorization: {{authToken}}
-
-{
- "ownerId": "{{createdNoteId}}",
- "role": "file",
- "mime": "plain/text",
- "title": "my attachment",
- "content": "my text"
-}
-
-> {%
- client.assert(response.status === 201);
-
- client.global.set("createdAttachmentId", response.body.attachmentId);
-%}
-
-###
-
-GET {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.body.attachmentId == client.global.get("createdAttachmentId"));
- client.assert(response.body.role == "file");
- client.assert(response.body.mime == "plain/text");
- client.assert(response.body.title == "my attachment");
-%}
diff --git a/_regroup/test-etapi/delete-attachment.http b/_regroup/test-etapi/delete-attachment.http
deleted file mode 100644
index d12e8de43..000000000
--- a/_regroup/test-etapi/delete-attachment.http
+++ /dev/null
@@ -1,52 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-POST {{triliumHost}}/etapi/attachments
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "ownerId": "{{createdNoteId}}",
- "role": "file",
- "mime": "text/plain",
- "title": "my attachment",
- "content": "text"
-}
-
-> {% client.global.set("createdAttachmentId", response.body.attachmentId); %}
-
-###
-
-DELETE {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 204, "Response status is not 204"); %}
-
-### repeat the DELETE request to test the idempotency
-
-DELETE {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 204, "Response status is not 204"); %}
-
-###
-
-GET {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 404, "Response status is not 404");
- client.assert(response.body.code === "ATTACHMENT_NOT_FOUND");
-%}
diff --git a/_regroup/test-etapi/delete-attribute.http b/_regroup/test-etapi/delete-attribute.http
deleted file mode 100644
index d61b75ba2..000000000
--- a/_regroup/test-etapi/delete-attribute.http
+++ /dev/null
@@ -1,52 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-POST {{triliumHost}}/etapi/attributes
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{createdNoteId}}",
- "type": "label",
- "name": "mylabel",
- "value": "val",
- "isInheritable": true
-}
-
-> {% client.global.set("createdAttributeId", response.body.attributeId); %}
-
-###
-
-DELETE {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 204, "Response status is not 204"); %}
-
-### repeat the DELETE request to test the idempotency
-
-DELETE {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 204, "Response status is not 204"); %}
-
-###
-
-GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 404, "Response status is not 404");
- client.assert(response.body.code === "ATTRIBUTE_NOT_FOUND");
-%}
diff --git a/_regroup/test-etapi/delete-cloned-branch.http b/_regroup/test-etapi/delete-cloned-branch.http
deleted file mode 100644
index a87a6fa4d..000000000
--- a/_regroup/test-etapi/delete-cloned-branch.http
+++ /dev/null
@@ -1,87 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {%
- client.global.set("createdNoteId", response.body.note.noteId);
- client.global.set("createdBranchId", response.body.branch.branchId);
-%}
-
-### Clone to another location
-
-POST {{triliumHost}}/etapi/branches
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{createdNoteId}}",
- "parentNoteId": "_hidden"
-}
-
-> {% client.global.set("clonedBranchId", response.body.branchId); %}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-DELETE {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 204, "Response status is not 204"); %}
-
-### repeat the DELETE request to test the idempotency
-
-DELETE {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 204, "Response status is not 204"); %}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 404, "Response status is not 404");
- client.assert(response.body.code === "BRANCH_NOT_FOUND");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
diff --git a/_regroup/test-etapi/delete-note-with-all-branches.http b/_regroup/test-etapi/delete-note-with-all-branches.http
deleted file mode 100644
index 5a50bc4a9..000000000
--- a/_regroup/test-etapi/delete-note-with-all-branches.http
+++ /dev/null
@@ -1,126 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {%
- client.global.set("createdNoteId", response.body.note.noteId);
- client.global.set("createdBranchId", response.body.branch.branchId);
-%}
-
-###
-
-POST {{triliumHost}}/etapi/attributes
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{createdNoteId}}",
- "type": "label",
- "name": "mylabel",
- "value": "val",
- "isInheritable": true
-}
-
-> {% client.global.set("createdAttributeId", response.body.attributeId); %}
-
-### Clone to another location
-
-POST {{triliumHost}}/etapi/branches
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{createdNoteId}}",
- "parentNoteId": "_hidden"
-}
-
-> {% client.global.set("clonedBranchId", response.body.branchId); %}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-DELETE {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 204, "Response status is not 204"); %}
-
-### repeat the DELETE request to test the idempotency
-
-DELETE {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 204, "Response status is not 204"); %}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 404, "Response status is not 404");
- client.assert(response.body.code === "BRANCH_NOT_FOUND");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{clonedBranchId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 404, "Response status is not 404");
- client.assert(response.body.code == "BRANCH_NOT_FOUND");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 404, "Response status is not 404");
- client.assert(response.body.code === "NOTE_NOT_FOUND");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 404, "Response status is not 404");
- client.assert(response.body.code === "ATTRIBUTE_NOT_FOUND");
-%}
diff --git a/_regroup/test-etapi/export-note-subtree.http b/_regroup/test-etapi/export-note-subtree.http
deleted file mode 100644
index 28d90a362..000000000
--- a/_regroup/test-etapi/export-note-subtree.http
+++ /dev/null
@@ -1,37 +0,0 @@
-GET {{triliumHost}}/etapi/notes/root/export
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.headers.valueOf("Content-Type") == "application/zip");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/root/export?format=html
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.headers.valueOf("Content-Type") == "application/zip");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/root/export?format=markdown
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.headers.valueOf("Content-Type") == "application/zip");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/root/export?format=wrong
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code === "UNRECOGNIZED_EXPORT_FORMAT");
-%}
diff --git a/_regroup/test-etapi/get-date-notes.http b/_regroup/test-etapi/get-date-notes.http
deleted file mode 100644
index 19f0b4fc9..000000000
--- a/_regroup/test-etapi/get-date-notes.http
+++ /dev/null
@@ -1,72 +0,0 @@
-GET {{triliumHost}}/etapi/inbox/2022-01-01
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/days/2022-01-01
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/days/2022-1
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code === "DATE_INVALID");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/weeks/2022-01-01
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/weeks/2022-1
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code === "DATE_INVALID");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/months/2022-01
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/months/2022-1
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code === "MONTH_INVALID");
-%}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/years/2022
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/years/202
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code === "YEAR_INVALID");
-%}
diff --git a/_regroup/test-etapi/get-inherited-attribute-cloned.http b/_regroup/test-etapi/get-inherited-attribute-cloned.http
deleted file mode 100644
index eaf8d91b1..000000000
--- a/_regroup/test-etapi/get-inherited-attribute-cloned.http
+++ /dev/null
@@ -1,116 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello parent",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {%
-client.assert(response.status === 201);
-client.global.set("parentNoteId", response.body.note.noteId);
-client.global.set("parentBranchId", response.body.branch.branchId);
-%}
-
-### Create inheritable parent attribute
-
-POST {{triliumHost}}/etapi/attributes
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{parentNoteId}}",
- "type": "label",
- "name": "mylabel",
- "value": "",
- "isInheritable": true,
- "position": 10
-}
-
-> {%
-client.assert(response.status === 201);
-client.global.set("parentAttributeId", response.body.attributeId);
-%}
-
-### Create child note under root
-
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello child",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {%
-client.assert(response.status === 201);
-client.global.set("childNoteId", response.body.note.noteId);
-client.global.set("childBranchId", response.body.branch.branchId);
-%}
-
-### Create child attribute
-
-POST {{triliumHost}}/etapi/attributes
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{childNoteId}}",
- "type": "label",
- "name": "mylabel",
- "value": "val",
- "isInheritable": false,
- "position": 10
-}
-
-> {%
-client.assert(response.status === 201);
-client.global.set("childAttributeId", response.body.attributeId);
-%}
-
-### Clone child to parent
-
-POST {{triliumHost}}/etapi/branches
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{childNoteId}}",
- "parentNoteId": "{{parentNoteId}}"
-}
-
-> {%
-client.assert(response.status === 201);
-client.assert(response.body.parentNoteId == client.global.get("parentNoteId"));
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{childNoteId}}
-Authorization: {{authToken}}
-
-> {%
-
-function hasAttribute(list, attributeId) {
- for (let i = 0; i < list.length; i++) {
- if (list[i]["attributeId"] === attributeId) {
- return true;
- }
- }
- return false;
-}
-
-client.log(JSON.stringify(response.body.attributes));
-
-client.assert(response.status === 200);
-client.assert(response.body.noteId == client.global.get("childNoteId"));
-client.assert(response.body.attributes.length == 2);
-client.assert(hasAttribute(response.body.attributes, client.global.get("parentAttributeId")));
-client.assert(hasAttribute(response.body.attributes, client.global.get("childAttributeId")));
-%}
diff --git a/_regroup/test-etapi/get-inherited-attribute.http b/_regroup/test-etapi/get-inherited-attribute.http
deleted file mode 100644
index 26e9af854..000000000
--- a/_regroup/test-etapi/get-inherited-attribute.http
+++ /dev/null
@@ -1,61 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "GetInheritedAttributes Test Note",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {%
- client.assert(response.status === 201);
- client.global.set("parentNoteId", response.body.note.noteId);
-%}
-
-###
-
-POST {{triliumHost}}/etapi/attributes
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{parentNoteId}}",
- "type": "label",
- "name": "mylabel",
- "value": "val",
- "isInheritable": true
-}
-
-> {% client.global.set("createdAttributeId", response.body.attributeId); %}
-
-###
-
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "{{parentNoteId}}",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {%
-client.global.set("createdNoteId", response.body.note.noteId);
-client.global.set("createdBranchId", response.body.branch.branchId);
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {%
-client.assert(response.status === 200);
-client.assert(response.body.noteId == client.global.get("createdNoteId"));
-client.assert(response.body.attributes.length == 1);
-client.assert(response.body.attributes[0].attributeId == client.global.get("createdAttributeId"));
-%}
diff --git a/_regroup/test-etapi/get-note-content.http b/_regroup/test-etapi/get-note-content.http
deleted file mode 100644
index 50c677dd8..000000000
--- a/_regroup/test-etapi/get-note-content.http
+++ /dev/null
@@ -1,25 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {%
- client.global.set("createdNoteId", response.body.note.noteId);
- client.global.set("createdBranchId", response.body.branch.branchId);
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.status === 200);
- client.assert(response.body === "Hi there!");
-%}
diff --git a/_regroup/test-etapi/http-client.env.json b/_regroup/test-etapi/http-client.env.json
deleted file mode 100644
index 8ede0719c..000000000
--- a/_regroup/test-etapi/http-client.env.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "dev": {
- "triliumHost": "http://localhost:37740"
- }
-}
diff --git a/_regroup/test-etapi/import-zip.http b/_regroup/test-etapi/import-zip.http
deleted file mode 100644
index e831a050a..000000000
--- a/_regroup/test-etapi/import-zip.http
+++ /dev/null
@@ -1,12 +0,0 @@
-POST {{triliumHost}}/etapi/notes/root/import
-Authorization: {{authToken}}
-Content-Type: application/octet-stream
-Content-Transfer-Encoding: binary
-
-< ../db/demo.zip
-
-> {%
- client.assert(response.status === 201);
- client.assert(response.body.note.title == "Trilium Demo");
- client.assert(response.body.branch.parentNoteId == "root");
-%}
diff --git a/_regroup/test-etapi/logout.http b/_regroup/test-etapi/logout.http
deleted file mode 100644
index 9bd7355e0..000000000
--- a/_regroup/test-etapi/logout.http
+++ /dev/null
@@ -1,34 +0,0 @@
-POST {{triliumHost}}/etapi/auth/login
-Content-Type: application/json
-
-{
- "password": "1234"
-}
-
-> {%
- client.assert(response.status === 201);
-
- client.global.set("testAuthToken", response.body.authToken);
-%}
-
-###
-
-GET {{triliumHost}}/etapi/notes/root
-Authorization: {{testAuthToken}}
-
-> {% client.assert(response.status === 200); %}
-
-###
-
-POST {{triliumHost}}/etapi/auth/logout
-Authorization: {{testAuthToken}}
-Content-Type: application/json
-
-> {% client.assert(response.status === 204); %}
-
-###
-
-GET {{triliumHost}}/etapi/notes/root
-Authorization: {{testAuthToken}}
-
-> {% client.assert(response.status === 401); %}
diff --git a/_regroup/test-etapi/metrics.http b/_regroup/test-etapi/metrics.http
deleted file mode 100644
index 24435f954..000000000
--- a/_regroup/test-etapi/metrics.http
+++ /dev/null
@@ -1,82 +0,0 @@
-### Test ETAPI metrics endpoint
-
-# First login to get a token
-POST {{triliumHost}}/etapi/auth/login
-Content-Type: application/json
-
-{
- "password": "{{password}}"
-}
-
-> {%
-client.test("Login successful", function() {
- client.assert(response.status === 201, "Response status is not 201");
- client.assert(response.body.authToken, "Auth token not present");
- client.global.set("authToken", response.body.authToken);
-});
-%}
-
-### Get metrics with authentication (default Prometheus format)
-GET {{triliumHost}}/etapi/metrics
-Authorization: {{authToken}}
-
-> {%
-client.test("Metrics endpoint returns Prometheus format by default", function() {
- client.assert(response.status === 200, "Response status is not 200");
- client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain");
- client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric");
- client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric");
- client.assert(response.body.includes("# HELP"), "Should contain HELP comments");
- client.assert(response.body.includes("# TYPE"), "Should contain TYPE comments");
-});
-%}
-
-### Get metrics in JSON format
-GET {{triliumHost}}/etapi/metrics?format=json
-Authorization: {{authToken}}
-
-> {%
-client.test("Metrics endpoint returns JSON when requested", function() {
- client.assert(response.status === 200, "Response status is not 200");
- client.assert(response.headers["content-type"].includes("application/json"), "Content-Type should be application/json");
- client.assert(response.body.version, "Version info not present");
- client.assert(response.body.database, "Database info not present");
- client.assert(response.body.timestamp, "Timestamp not present");
- client.assert(typeof response.body.database.totalNotes === 'number', "Total notes should be a number");
- client.assert(typeof response.body.database.activeNotes === 'number', "Active notes should be a number");
-});
-%}
-
-### Get metrics in Prometheus format explicitly
-GET {{triliumHost}}/etapi/metrics?format=prometheus
-Authorization: {{authToken}}
-
-> {%
-client.test("Metrics endpoint returns Prometheus format when requested", function() {
- client.assert(response.status === 200, "Response status is not 200");
- client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain");
- client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric");
- client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric");
-});
-%}
-
-### Test invalid format parameter
-GET {{triliumHost}}/etapi/metrics?format=xml
-Authorization: {{authToken}}
-
-> {%
-client.test("Invalid format parameter returns error", function() {
- client.assert(response.status === 400, "Response status should be 400");
- client.assert(response.body.code === "INVALID_FORMAT", "Error code should be INVALID_FORMAT");
- client.assert(response.body.message.includes("prometheus"), "Error message should mention supported formats");
-});
-%}
-
-### Test without authentication (should fail)
-GET {{triliumHost}}/etapi/metrics
-
-> {%
-client.test("Metrics endpoint requires authentication", function() {
- client.assert(response.status === 401, "Response status should be 401");
-});
-%}
\ No newline at end of file
diff --git a/_regroup/test-etapi/no-token.http b/_regroup/test-etapi/no-token.http
deleted file mode 100644
index d8198ed2b..000000000
--- a/_regroup/test-etapi/no-token.http
+++ /dev/null
@@ -1,109 +0,0 @@
-GET {{triliumHost}}/etapi/notes?search=aaa
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/notes/root
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-PATCH {{triliumHost}}/etapi/notes/root
-Authorization: fakeauth
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-DELETE {{triliumHost}}/etapi/notes/root
-Authorization: fakeauth
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/branches/root
-Authorization: fakeauth
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-PATCH {{triliumHost}}/etapi/branches/root
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-DELETE {{triliumHost}}/etapi/branches/root
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/attributes/000
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-PATCH {{triliumHost}}/etapi/attributes/000
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-DELETE {{triliumHost}}/etapi/attributes/000
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/inbox/2022-02-22
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/days/2022-02-22
-Authorization: fakeauth
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/weeks/2022-02-22
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/months/2022-02
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/calendar/years/2022
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-POST {{triliumHost}}/etapi/create-note
-
-> {% client.assert(response.status === 401); %}
-
-###
-
-GET {{triliumHost}}/etapi/app-info
-
-> {% client.assert(response.status === 401); %}
-
-### Fake URL will get a 404 even without token
-
-GET {{triliumHost}}/etapi/zzzzzz
-
-> {% client.assert(response.status === 404); %}
diff --git a/_regroup/test-etapi/other.http b/_regroup/test-etapi/other.http
deleted file mode 100644
index c3f92fc94..000000000
--- a/_regroup/test-etapi/other.http
+++ /dev/null
@@ -1,4 +0,0 @@
-POST {{triliumHost}}/etapi/refresh-note-ordering/root
-Authorization: {{authToken}}
-
-> {% client.assert(response.status === 200); %}
\ No newline at end of file
diff --git a/_regroup/test-etapi/patch-attachment.http b/_regroup/test-etapi/patch-attachment.http
deleted file mode 100644
index 44ffe696f..000000000
--- a/_regroup/test-etapi/patch-attachment.http
+++ /dev/null
@@ -1,79 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-POST {{triliumHost}}/etapi/attachments
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "ownerId": "{{createdNoteId}}",
- "role": "file",
- "mime": "text/plain",
- "title": "my attachment",
- "content": "text"
-}
-
-> {% client.global.set("createdAttachmentId", response.body.attachmentId); %}
-
-###
-
-PATCH {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "title": "CHANGED",
- "position": 999
-}
-
-###
-
-GET {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}
-Authorization: {{authToken}}
-
-> {%
- client.assert(response.body.title === "CHANGED");
- client.assert(response.body.position === 999);
-%}
-
-###
-
-PATCH {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "ownerId": "root"
-}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code == "PROPERTY_NOT_ALLOWED");
-%}
-
-###
-
-PATCH {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "title": null
-}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code == "PROPERTY_VALIDATION_ERROR");
-%}
diff --git a/_regroup/test-etapi/patch-attribute.http b/_regroup/test-etapi/patch-attribute.http
deleted file mode 100644
index 625c19446..000000000
--- a/_regroup/test-etapi/patch-attribute.http
+++ /dev/null
@@ -1,80 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {%
- client.global.set("createdNoteId", response.body.note.noteId);
- client.global.set("createdBranchId", response.body.branch.branchId);
-%}
-
-###
-
-POST {{triliumHost}}/etapi/attributes
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "{{createdNoteId}}",
- "type": "label",
- "name": "mylabel",
- "value": "val",
- "isInheritable": true
-}
-
-> {% client.global.set("createdAttributeId", response.body.attributeId); %}
-
-###
-
-PATCH {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "value": "CHANGED"
-}
-
-###
-
-GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-
-> {%
-client.assert(response.body.value === "CHANGED");
-%}
-
-###
-
-PATCH {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "noteId": "root"
-}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code == "PROPERTY_NOT_ALLOWED");
-%}
-
-###
-
-PATCH {{triliumHost}}/etapi/attributes/{{createdAttributeId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "value": null
-}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code == "PROPERTY_VALIDATION_ERROR");
-%}
\ No newline at end of file
diff --git a/_regroup/test-etapi/patch-branch.http b/_regroup/test-etapi/patch-branch.http
deleted file mode 100644
index 48116120c..000000000
--- a/_regroup/test-etapi/patch-branch.http
+++ /dev/null
@@ -1,66 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "type": "text",
- "title": "Hello",
- "content": ""
-}
-
-> {% client.global.set("createdBranchId", response.body.branch.branchId); %}
-
-###
-
-PATCH {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "prefix": "pref",
- "notePosition": 666,
- "isExpanded": true
-}
-
-###
-
-GET {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-
-> {%
-client.assert(response.status === 200);
-client.assert(response.body.prefix === 'pref');
-client.assert(response.body.notePosition === 666);
-client.assert(response.body.isExpanded === true);
-%}
-
-###
-
-PATCH {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root"
-}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code == "PROPERTY_NOT_ALLOWED");
-%}
-
-###
-
-PATCH {{triliumHost}}/etapi/branches/{{createdBranchId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "prefix": 123
-}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code == "PROPERTY_VALIDATION_ERROR");
-%}
\ No newline at end of file
diff --git a/_regroup/test-etapi/patch-note.http b/_regroup/test-etapi/patch-note.http
deleted file mode 100644
index 24b9251d2..000000000
--- a/_regroup/test-etapi/patch-note.http
+++ /dev/null
@@ -1,83 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "code",
- "mime": "application/json",
- "content": "{}"
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {%
-client.assert(response.status === 200);
-client.assert(response.body.title === 'Hello');
-client.assert(response.body.type === 'code');
-client.assert(response.body.mime === 'application/json');
-%}
-
-###
-
-PATCH {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "title": "Wassup",
- "type": "html",
- "mime": "text/html",
- "dateCreated": "2023-08-21 23:38:51.123+0200",
- "utcDateCreated": "2023-08-21 23:38:51.123Z"
-}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-
-> {%
-client.assert(response.status === 200);
-client.assert(response.body.title === 'Wassup');
-client.assert(response.body.type === 'html');
-client.assert(response.body.mime === 'text/html');
-client.assert(response.body.dateCreated == "2023-08-21 23:38:51.123+0200");
-client.assert(response.body.utcDateCreated == "2023-08-21 23:38:51.123Z");
-%}
-
-###
-
-PATCH {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "isProtected": true
-}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code == "PROPERTY_NOT_ALLOWED");
-%}
-
-###
-
-PATCH {{triliumHost}}/etapi/notes/{{createdNoteId}}
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "title": true
-}
-
-> {%
- client.assert(response.status === 400);
- client.assert(response.body.code == "PROPERTY_VALIDATION_ERROR");
-%}
diff --git a/_regroup/test-etapi/post-revision.http b/_regroup/test-etapi/post-revision.http
deleted file mode 100644
index 139397855..000000000
--- a/_regroup/test-etapi/post-revision.http
+++ /dev/null
@@ -1,23 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "code",
- "mime": "text/plain",
- "content": "Hi there!"
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-POST {{triliumHost}}/etapi/notes/{{createdNoteId}}/revision
-Authorization: {{authToken}}
-Content-Type: text/plain
-
-Changed content
-
-> {% client.assert(response.status === 204); %}
diff --git a/_regroup/test-etapi/put-attachment-content-binary.http b/_regroup/test-etapi/put-attachment-content-binary.http
deleted file mode 100644
index 6e6d6dad3..000000000
--- a/_regroup/test-etapi/put-attachment-content-binary.http
+++ /dev/null
@@ -1,39 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-POST {{triliumHost}}/etapi/attachments
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "ownerId": "{{createdNoteId}}",
- "role": "file",
- "mime": "text/plain",
- "title": "my attachment",
- "content": "text"
-}
-
-> {% client.global.set("createdAttachmentId", response.body.attachmentId); %}
-
-###
-
-PUT {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}/content
-Authorization: {{authToken}}
-Content-Type: application/octet-stream
-Content-Transfer-Encoding: binary
-
-< ../images/icon-color.png
-
-> {% client.assert(response.status === 204); %}
diff --git a/_regroup/test-etapi/put-attachment-content.http b/_regroup/test-etapi/put-attachment-content.http
deleted file mode 100644
index 57e96a4b9..000000000
--- a/_regroup/test-etapi/put-attachment-content.http
+++ /dev/null
@@ -1,45 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "text",
- "content": "Hi there!"
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-POST {{triliumHost}}/etapi/attachments
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "ownerId": "{{createdNoteId}}",
- "role": "file",
- "mime": "text/plain",
- "title": "my attachment",
- "content": "text"
-}
-
-> {% client.global.set("createdAttachmentId", response.body.attachmentId); %}
-
-###
-
-PUT {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}/content
-Authorization: {{authToken}}
-Content-Type: text/plain
-
-Changed content
-
-> {% client.assert(response.status === 204); %}
-
-###
-
-GET {{triliumHost}}/etapi/attachments/{{createdAttachmentId}}/content
-Authorization: {{authToken}}
-
-> {% client.assert(response.body === "Changed content"); %}
diff --git a/_regroup/test-etapi/put-note-content-binary.http b/_regroup/test-etapi/put-note-content-binary.http
deleted file mode 100644
index 545b3c111..000000000
--- a/_regroup/test-etapi/put-note-content-binary.http
+++ /dev/null
@@ -1,25 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "image",
- "mime": "image/png",
- "content": ""
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-PUT {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
-Authorization: {{authToken}}
-Content-Type: application/octet-stream
-Content-Transfer-Encoding: binary
-
-< ../images/icon-color.png
-
-> {% client.assert(response.status === 204); %}
-
diff --git a/_regroup/test-etapi/put-note-content.http b/_regroup/test-etapi/put-note-content.http
deleted file mode 100644
index 670195ac2..000000000
--- a/_regroup/test-etapi/put-note-content.http
+++ /dev/null
@@ -1,30 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "Hello",
- "type": "code",
- "mime": "text/plain",
- "content": "Hi there!"
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-PUT {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
-Authorization: {{authToken}}
-Content-Type: text/plain
-
-Changed content
-
-> {% client.assert(response.status === 204); %}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
-Authorization: {{authToken}}
-
-> {% client.assert(response.body === "Changed content"); %}
diff --git a/_regroup/test-etapi/search.http b/_regroup/test-etapi/search.http
deleted file mode 100644
index 4655f22e0..000000000
--- a/_regroup/test-etapi/search.http
+++ /dev/null
@@ -1,39 +0,0 @@
-POST {{triliumHost}}/etapi/create-note
-Authorization: {{authToken}}
-Content-Type: application/json
-
-{
- "parentNoteId": "root",
- "title": "title",
- "type": "text",
- "content": "{{$uuid}}"
-}
-
-> {% client.global.set("createdNoteId", response.body.note.noteId); %}
-
-###
-
-GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
-Authorization: {{authToken}}
-
-> {% client.global.set("content", response.body); %}
-
-###
-
-GET {{triliumHost}}/etapi/notes?search={{content}}&debug=true
-Authorization: {{authToken}}
-
-> {%
-client.assert(response.status === 200);
-client.assert(response.body.results.length === 1);
-%}
-
-### Same but with fast search which doesn't look in the content so 0 notes should be found
-
-GET {{triliumHost}}/etapi/notes?search={{content}}&fastSearch=true
-Authorization: {{authToken}}
-
-> {%
-client.assert(response.status === 200);
-client.assert(response.body.results.length === 0);
-%}
diff --git a/apps/client/package.json b/apps/client/package.json
index a1e4c9c6a..94f1a0ed3 100644
--- a/apps/client/package.json
+++ b/apps/client/package.json
@@ -10,7 +10,7 @@
"url": "https://github.com/TriliumNext/Notes"
},
"dependencies": {
- "@eslint/js": "9.27.0",
+ "@eslint/js": "9.28.0",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.17",
"@fullcalendar/daygrid": "6.1.17",
@@ -64,9 +64,9 @@
"@types/leaflet-gpx": "1.3.7",
"@types/mark.js": "8.11.12",
"@types/react": "19.1.6",
- "@types/react-dom": "19.1.5",
+ "@types/react-dom": "19.1.6",
"copy-webpack-plugin": "13.0.0",
- "happy-dom": "17.5.6",
+ "happy-dom": "17.6.3",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.0.0"
},
diff --git a/apps/client/src/components/note_context.ts b/apps/client/src/components/note_context.ts
index 074e03e4c..11d32cf0d 100644
--- a/apps/client/src/components/note_context.ts
+++ b/apps/client/src/components/note_context.ts
@@ -269,14 +269,32 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
return true;
}
- const blob = await this.note.getBlob();
- if (!blob) {
- return false;
+ // Store the initial decision about read-only status in the viewScope
+ // This will be "remembered" until the viewScope is refreshed
+ if (!this.viewScope) {
+ this.resetViewScope();
}
- const sizeLimit = this.note.type === "text" ? options.getInt("autoReadonlySizeText") : options.getInt("autoReadonlySizeCode");
+ const viewScope = this.viewScope!;
- return sizeLimit && blob.contentLength > sizeLimit && !this.note.isLabelTruthy("autoReadOnlyDisabled");
+ if (viewScope.isReadOnly === undefined) {
+ const blob = await this.note.getBlob();
+ if (!blob) {
+ viewScope.isReadOnly = false;
+ return false;
+ }
+
+ const sizeLimit = this.note.type === "text"
+ ? options.getInt("autoReadonlySizeText")
+ : options.getInt("autoReadonlySizeCode");
+
+ viewScope.isReadOnly = Boolean(sizeLimit &&
+ blob.contentLength > sizeLimit &&
+ !this.note.isLabelTruthy("autoReadOnlyDisabled"));
+ }
+
+ // Return the cached decision, which won't change until viewScope is reset
+ return viewScope.isReadOnly || false;
}
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
diff --git a/apps/client/src/menus/context_menu.ts b/apps/client/src/menus/context_menu.ts
index 7d0bc0a2f..a8a37f462 100644
--- a/apps/client/src/menus/context_menu.ts
+++ b/apps/client/src/menus/context_menu.ts
@@ -192,13 +192,16 @@ class ContextMenu {
// it's important to stop the propagation especially for sub-menus, otherwise the event
// might be handled again by top-level menu
return false;
- })
- .on("mouseup", (e) =>{
+ });
+
+ if (!this.isMobile) {
+ $item.on("mouseup", (e) =>{
e.stopPropagation();
// Hide the content menu on mouse up to prevent the mouse event from propagating to the elements below.
this.hide();
return false;
});
+ }
if ("enabled" in item && item.enabled !== undefined && !item.enabled) {
$item.addClass("disabled");
diff --git a/apps/client/src/server_types.ts b/apps/client/src/server_types.ts
index df6e1a7fc..2aa521405 100644
--- a/apps/client/src/server_types.ts
+++ b/apps/client/src/server_types.ts
@@ -8,7 +8,7 @@ interface Entity {
export interface EntityChange {
id?: number | null;
noteId?: string;
- entityName: EntityRowNames;
+ entityName: EntityType;
entityId: string;
entity?: Entity;
positions?: Record Welcome to TriliumNext Notes!
+
This is initial "demo" document provided by TriliumNext by default to
showcase some of its features and also give you some ideas how you might
structure your notes. You can play with it, modify note content and tree
structure as you wish. If you need any help, visit TriliumNext website: https://github.com/TriliumNext
+
Once you're finished with experimenting and want to cleanup these pages,
you can simply delete them all. TriliumNext supports classic formatting like italic, bold, bold and italic.
Of course you can add links like this one pointing to google.com
+
Lists Ordered:
+
Unordered:
+
… here put main characters … … describe main plot lines … scifi / drama / romance Checkout Kindle daily deals: https://www.amazon.com/gp/feature.html?docId=1000677541
+
For larger pieces of code it is better to use a code note, which uses
a fully-fledged code editor (CodeMirror). For an example of a code note,
see Custom request handler. \(% \f is defined as #1f(#2) using the macro \f\relax{x} = \int_{-\infty}^\infty \f\hat\xi\,e^{2 \pi i \xi x} \,d\xi\)Some
math examples: Another: Inline math is also possible: \(c^2 = a^2 + b^2\) This page demonstrates two things: You can read some explanation on how this journal works here: https://github.com/zadam/trilium/wiki/Day-notes
+
Wiki: https://en.wikipedia.org/wiki/Trusted_timestamping
+
Bozho: https://techblog.bozho.net/using-trusted-timestamping-java/
+
Trusted timestamping is the process of securely keeping
track of the creation and modification time of a document. Security here
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Journal/2021/12 - December/18 - Monday.html b/apps/edit-docs/demo/root/Trilium Demo/Journal/2021/12 - December/18 - Monday.html
index ae490681e..3722a62c0 100644
--- a/apps/edit-docs/demo/root/Trilium Demo/Journal/2021/12 - December/18 - Monday.html
+++ b/apps/edit-docs/demo/root/Trilium Demo/Journal/2021/12 - December/18 - Monday.html
@@ -16,6 +16,7 @@
Miscellaneous notes done on monday ... Interesting video: https://www.youtube.com/watch?v=_eSAF_qT_FY&feature=youtu.be
+
Maybe CodeNames? https://boardgamegeek.com/boardgame/178900/codenames
+
Cleanup
+
Formatting
+
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Journal/2021/11 - November/28 - Tuesday/Christmas gift ideas.html b/apps/edit-docs/demo/root/Trilium Demo/Journal/2021/11 - November/28 - Tuesday/Christmas gift ideas.html
index 2a2d08336..01ad36c16 100644
--- a/apps/edit-docs/demo/root/Trilium Demo/Journal/2021/11 - November/28 - Tuesday/Christmas gift ideas.html
+++ b/apps/edit-docs/demo/root/Trilium Demo/Journal/2021/11 - November/28 - Tuesday/Christmas gift ideas.html
@@ -17,6 +17,7 @@
Main characters
+
Plot
+
Tone
+
Genre
+
Similar books
+
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Books/To read.html b/apps/edit-docs/demo/root/Trilium Demo/Books/To read.html
index 17b164b6c..30cf1d0d1 100644
--- a/apps/edit-docs/demo/root/Trilium Demo/Books/To read.html
+++ b/apps/edit-docs/demo/root/Trilium Demo/Books/To read.html
@@ -14,11 +14,14 @@
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Journal.html b/apps/edit-docs/demo/root/Trilium Demo/Journal.html
index faac301b5..629b4870f 100644
--- a/apps/edit-docs/demo/root/Trilium Demo/Journal.html
+++ b/apps/edit-docs/demo/root/Trilium Demo/Journal.html
@@ -14,6 +14,7 @@
This is a simple TODO/Task manager. You can see some description and explanation here: https://github.com/zadam/trilium/wiki/Task-manager +
Please note that this is meant as scripting example only and feature/bug support is very limited.
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Scripting examples/Task manager/Done/Buy a board game for Alice.html b/apps/edit-docs/demo/root/Trilium Demo/Scripting examples/Task manager/Done/Buy a board game for Alice.html index 9cda89645..65b1819f2 100644 --- a/apps/edit-docs/demo/root/Trilium Demo/Scripting examples/Task manager/Done/Buy a board game for Alice.html +++ b/apps/edit-docs/demo/root/Trilium Demo/Scripting examples/Task manager/Done/Buy a board game for Alice.html @@ -18,6 +18,7 @@ width="209" height="300">Maybe CodeNames? https://boardgamegeek.com/boardgame/178900/codenames +
https://en.wikipedia.org/wiki/The_Black_Swan:_The_Impact_of_the_Highly_Improbable +
The Black Swan: The Impact of the Highly Improbable is a 2007 book by author and former options trader diff --git a/apps/edit-docs/demo/root/Trilium Demo/Tech/Linux/Bash scripting.html b/apps/edit-docs/demo/root/Trilium Demo/Tech/Linux/Bash scripting.html index aaa6cd1ad..55c1364be 100644 --- a/apps/edit-docs/demo/root/Trilium Demo/Tech/Linux/Bash scripting.html +++ b/apps/edit-docs/demo/root/Trilium Demo/Tech/Linux/Bash scripting.html @@ -25,6 +25,7 @@ and Apple's macOS (formerly OS X). A version is also available for Windows 10.
As a "login shell", Bash reads and sets (executes) the user's profile from /etc/profile and one of ~/.bash_profile, ~/.bash_login, or ~/.profile (in that order, using the first one that's readable!).
@@ -23,6 +24,7 @@ that only make sense for the initial user login. That's why all UNIX® shells have (should have) a "login" mode.Methods to start Bash as a login shell: +
Methods to test for login shell mode: +
Related switches: +
When Bash starts as an interactive non-login shell, it reads and executes commands from ~/.bashrc. This file should contain, for example, aliases, since they need to be defined in every shell as they're not inherited from @@ -51,11 +56,13 @@ The classic way to have a system-wide rc file is to source /etc/bashrc from every user's ~/.bashrc.
Methods to test for interactive-shell mode: +
Related switches: +
When Bash starts in SH compatiblity mode, it tries to mimic the startup behaviour of historical versions of sh as closely as possible, while conforming to the POSIX® standard as well. The profile files read are /etc/profile @@ -74,6 +82,7 @@ file.
After the startup files are read, Bash enters the POSIX(r) compatiblity mode (for running, not for starting!).
Bash starts in sh compatiblity mode when: +
Documentation: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html +
#!/bin/bash
# This script opens 4 terminal windows.
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Tech/Node.js/Overview/Platform architecture.html b/apps/edit-docs/demo/root/Trilium Demo/Tech/Node.js/Overview/Platform architecture.html
index 1a977bccc..4b51ab5be 100644
--- a/apps/edit-docs/demo/root/Trilium Demo/Tech/Node.js/Overview/Platform architecture.html
+++ b/apps/edit-docs/demo/root/Trilium Demo/Tech/Node.js/Overview/Platform architecture.html
@@ -20,6 +20,7 @@
href="https://en.wikipedia.org/wiki/Node.js#cite_note-b1-31">[31]Developers can create scalable servers without using threading,
by using a simplified model of event-driven programming that
uses callbacks to signal the completion of a task.[31]
+
Week and quarter notes are disabled by default, since it might be too
- much for some people. To enable them, you need to set #enableWeekNotes
and #enableQuarterNotes
attributes
+ much for some people. To enable them, you need to set #enableWeekNote
and #enableQuarterNote
attributes
on the root calendar note, which is identified by #calendarRoot
label.
Week note is affected by the first week of year option. Be careful when
you already have some week notes created, it will not automatically change
@@ -40,15 +40,26 @@
(identified by #calendarRoot
label):
#enableQuarterNotes
is set)#enableQuarterNote
is set)#enableWeekNotes
is set)#enableWeekNote
is set)All of these are relations. When Trilium creates a new note for year or
month or date, it will take a look at the root and attach a corresponding ~template
relation
to the newly created role. Using this, you can e.g. create your daily template
with e.g. checkboxes for daily routine etc.
If you have been using Journal prior to version v0.93.0, the previous
+ template pattern likely used was ~child:template=
.
+
To transition to the new system:
child:template
and child:child:template
from
+ all notes under the Journal (calendar root).You can customize the title of generated journal notes by defining a #datePattern
, #weekPattern
, #monthPattern
, #quarterPattern
and #yearPattern
attribute
on a root calendar note (identified by #calendarRoot
label).
@@ -138,9 +149,4 @@
Trilium has some special support for day notes in the form of backend Script API - see e.g. getDayNote() function.
Day (and year, month) notes are created with a label - e.g. #dateNote="2025-03-09"
this
- can then be used by other scripts to add new notes to day note etc.
Journal also has relation child:child:child:template=Day template
(see
- [[attribute inheritance]]) which effectively adds [[template]] to day notes
- (grand-grand-grand children of Journal). Please note that, when you enable
- week notes or quarter notes, it will not automatically change the relation
- for the child level.
400
- Invalid format parameter401
- Missing or invalid ETAPI token500
- Internal server error+
+
You can also use the Grafana Dashboard that has been created for TriliumNext + - just take the JSON from grafana-dashboard.json and + then import the dashboard, following these screenshots:
+Then paste the JSON, and hit load:
+\ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Metrics/grafana-dashboard.json b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Metrics/grafana-dashboard.json new file mode 100644 index 000000000..2e1e4511e --- /dev/null +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Metrics/grafana-dashboard.json @@ -0,0 +1,1335 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": 549, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 100, + "panels": [], + "title": "🏠 Trilium Overview", + "type": "row" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Current Trilium version and build information", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 101, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_info{job=~'$job',instance=~'$instance'}", + "format": "table", + "instant": true, + "refId": "A" + } + ], + "title": "📋 Instance Information", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "__name__": true, + "instance": true, + "job": true + }, + "indexByName": {}, + "renameByName": { + "build_date": "Build Date", + "build_revision": "Git Revision", + "db_version": "DB Version", + "node_version": "Node.js", + "sync_version": "Sync Version", + "version": "Version" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Database file size in human-readable format", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "yellow", + "value": 500000000 + }, + { + "color": "red", + "value": 1000000000 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 102, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_database_size_bytes{job=~'$job',instance=~'$instance'}", + "refId": "A" + } + ], + "title": "💾 Database Size", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Total active notes in your Trilium instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "yellow", + "value": 1000 + }, + { + "color": "red", + "value": 5000 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 103, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_notes_active{job=~'$job',instance=~'$instance'}", + "refId": "A" + } + ], + "title": "📝 Active Notes", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 200, + "panels": [], + "title": "📊 Key Metrics", + "type": "row" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Total notes including deleted ones", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 8 + }, + "id": 201, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + "values": [] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_notes_active{job=~'$job',instance=~'$instance'}", + "legendFormat": "Active Notes", + "refId": "A" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_notes_deleted{job=~'$job',instance=~'$instance'}", + "legendFormat": "Deleted Notes", + "refId": "B" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_notes_protected{job=~'$job',instance=~'$instance'}", + "legendFormat": "Protected Notes", + "refId": "C" + } + ], + "title": "📝 Notes Distribution", + "type": "piechart" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Breakdown of attachments by MIME type", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 8 + }, + "id": 202, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + "values": [] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_attachments_by_type{job=~'$job',instance=~'$instance'}", + "legendFormat": "{{mime_type}}", + "refId": "A" + } + ], + "title": "🖼️ Attachments by Type", + "type": "piechart" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Distribution of notes by their content type", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 203, + "options": { + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_notes_by_type{job=~'$job',instance=~'$instance'}", + "legendFormat": "{{type}}", + "refId": "A" + } + ], + "title": "📄 Notes by Content Type", + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 300, + "panels": [], + "title": "📈 Trends & Time Series", + "type": "row" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Growth of notes over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Active Notes" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Notes" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 301, + "options": { + "legend": { + "calcs": [ + "last", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_notes_active{job=~'$job',instance=~'$instance'}", + "legendFormat": "Active Notes", + "refId": "A" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_notes_total{job=~'$job',instance=~'$instance'}", + "legendFormat": "Total Notes", + "refId": "B" + } + ], + "title": "📈 Notes Growth Over Time", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Attachment storage trends", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Active Attachments" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Attachments" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 302, + "options": { + "legend": { + "calcs": [ + "last", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_attachments_active{job=~'$job',instance=~'$instance'}", + "legendFormat": "Active Attachments", + "refId": "A" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_attachments_total{job=~'$job',instance=~'$instance'}", + "legendFormat": "Total Attachments", + "refId": "B" + } + ], + "title": "📎 Attachments Growth Over Time", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 400, + "panels": [], + "title": "🔧 Advanced Metrics", + "type": "row" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Number of branches connecting notes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 26 + }, + "id": 401, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_branches_total{job=~'$job',instance=~'$instance'}", + "refId": "A" + } + ], + "title": "🌳 Total Branches", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Number of note attributes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 26 + }, + "id": 402, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_attributes_total{job=~'$job',instance=~'$instance'}", + "refId": "A" + } + ], + "title": "🏷️ Attributes", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Number of note revisions", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 26 + }, + "id": 403, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_revisions_total{job=~'$job',instance=~'$instance'}", + "refId": "A" + } + ], + "title": "🔄 Revisions", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Number of ETAPI tokens", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 26 + }, + "id": 404, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_etapi_tokens_total{job=~'$job',instance=~'$instance'}", + "refId": "A" + } + ], + "title": "🔑 API Tokens", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Various storage and system metrics", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Recent Notes" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 30 + }, + "id": 405, + "options": { + "legend": { + "calcs": [ + "last" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_blobs_total{job=~'$job',instance=~'$instance'}", + "legendFormat": "Blob Records", + "refId": "A" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_recent_notes_total{job=~'$job',instance=~'$instance'}", + "legendFormat": "Recent Notes", + "refId": "B" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_embeddings_total{job=~'$job',instance=~'$instance'}", + "legendFormat": "Embeddings", + "refId": "C" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_embedding_providers_total{job=~'$job',instance=~'$instance'}", + "legendFormat": "Embedding Providers", + "refId": "D" + } + ], + "title": "📊 Storage & System Metrics", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Timeline showing when content was created and last modified", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "dateTimeAsIso" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 30 + }, + "id": 406, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_oldest_note_timestamp{job=~'$job',instance=~'$instance'} * 1000", + "legendFormat": "Oldest Note", + "refId": "A" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_newest_note_timestamp{job=~'$job',instance=~'$instance'} * 1000", + "legendFormat": "Newest Note", + "refId": "B" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "expr": "trilium_last_modified_timestamp{job=~'$job',instance=~'$instance'} * 1000", + "legendFormat": "Last Modified", + "refId": "C" + } + ], + "title": "⏰ Content Timeline", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "1m", + "schemaVersion": 41, + "tags": [ + "trilium", + "notes", + "monitoring", + "enhanced" + ], + "templating": { + "list": [ + { + "current": { + "text": "myprom", + "value": "PA04845DA3A4B088E" + }, + "includeAll": false, + "label": "Datasource", + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "//", + "type": "datasource" + }, + { + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": { + "UID": "", + "type": "" + }, + "includeAll": true, + "label": "Job", + "multi": true, + "name": "job", + "options": [], + "query": "query_result(up)", + "refresh": 1, + "regex": "/job=\"([^\"]+)\"/", + "sort": 1, + "type": "query" + }, + { + "allValue": ".*", + "current": { + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "includeAll": true, + "label": "Instance", + "multi": true, + "name": "instance", + "options": [], + "query": "trilium_database_size_bytes", + "refresh": 1, + "regex": "/instance=\"([^\"]+)\"/", + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "TriliumNext Dashboard", + "uid": "06993f9b-a477-4723-bf18-47743393b382", + "version": 5 +} \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Metrics_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Metrics_image.png new file mode 100644 index 000000000..ae68ddd02 Binary files /dev/null and b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Metrics_image.png differ diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Templates.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Templates.html index c0ebf0b9c..4c91ffc9c 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Templates.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Templates.html @@ -40,7 +40,19 @@ you can also mark templates with
#workspaceTemplate
to display
them only in the workspace.
Templates can also be added or changed after note creation by creating
- a ~template
relation pointing to the desired template note.
~template
relation pointing to the desired template note.
+To specify a template for child notes, you can use a ~child:template
relation
+ pointing to the appropriate template note. There is no limit to the depth
+ of the hierarchy — you can use ~child:child:template
, ~child:child:child:template
,
+ and so on.
From a visual perspective, templates can define #iconClass
and #cssClass
attributes,
allowing all instance notes (e.g., books) to display a specific icon and
diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts.html
index ddd6b647d..a9c2d7c65 100644
--- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts.html
+++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts.html
@@ -77,7 +77,7 @@
class="reference-link" href="#root/_help_QrtTYPmdd1qq">Markdown-like formatting.
The location of a marker is stored in the #geolocation
attribute
of the child notes:
-
-
This value can be added manually if needed. The value of the attribute is made up of the latitude and longitude separated by a comma.
Similarly to the Google Maps approach:
Trilium has basic support for displaying GPS tracks on the geo map.
This occurs if the application is not at 100% zoom which causes the pixels of the map to not render correctly due to fractional scaling. The only possible solution is to set the UI zoom at 100% (default keyboard shortcut diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Keyboard shortcuts.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Keyboard shortcuts.html index df312d7bf..f26061d1c 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Keyboard shortcuts.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Keyboard shortcuts.html @@ -60,7 +60,7 @@