From 9ec8070c688dfa1fd9ec899835988f14176211ba Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sun, 13 Sep 2026 02:27:54 -0400 Subject: [PATCH] feat: add analytics modules --- CLAUDE.md | 64 +- backend/api/analytics.ts | 154 +++++ backend/api/index.ts | 2 + backend/api/schemas/analytics.ts | 76 +++ backend/helpers/appShell.ts | 60 +- backend/index.ts | 4 + backend/locales/en.json | 15 +- backend/models/analytics.ts | 408 ++++++++++++ backend/models/auditLog.ts | 1 + backend/models/index.ts | 2 + backend/models/sites.ts | 8 + backend/modules/analytics/baidu/code.yml | 11 + .../modules/analytics/baidu/definition.yml | 13 + backend/modules/analytics/clarity/code.yml | 9 + .../modules/analytics/clarity/definition.yml | 13 + .../modules/analytics/contentsquare/code.yml | 12 + .../analytics/contentsquare/definition.yml | 13 + backend/modules/analytics/elasticapm/code.yml | 12 + .../analytics/elasticapm/definition.yml | 34 + backend/modules/analytics/fathom/code.yml | 2 + .../modules/analytics/fathom/definition.yml | 20 + backend/modules/analytics/google/code.yml | 9 + .../modules/analytics/google/definition.yml | 13 + backend/modules/analytics/gtm/code.yml | 13 + backend/modules/analytics/gtm/definition.yml | 13 + backend/modules/analytics/matomo/code.yml | 17 + .../modules/analytics/matomo/definition.yml | 20 + backend/modules/analytics/plausible/code.yml | 2 + .../analytics/plausible/definition.yml | 20 + .../modules/analytics/statcounter/code.yml | 10 + .../analytics/statcounter/definition.yml | 20 + backend/modules/analytics/umami/code.yml | 2 + .../modules/analytics/umami/definition.yml | 20 + backend/modules/analytics/yandex/code.yml | 16 + .../modules/analytics/yandex/definition.yml | 20 + .../_assets/icons/ultraviolet-baidu.svg | 1 + .../_assets/icons/ultraviolet-clarity.svg | 1 + .../icons/ultraviolet-contentsquare.svg | 1 + .../_assets/icons/ultraviolet-elastic.svg | 1 + .../_assets/icons/ultraviolet-fathom.svg | 1 + .../icons/ultraviolet-google-analytics.svg | 1 + .../icons/ultraviolet-google-tag-manager.svg | 1 + .../_assets/icons/ultraviolet-matomo.svg | 1 + .../_assets/icons/ultraviolet-plausible.svg | 1 + .../_assets/icons/ultraviolet-statcounter.svg | 1 + .../_assets/icons/ultraviolet-umami.svg | 1 + .../icons/ultraviolet-yandex-metrica.svg | 1 + frontend/src/layouts/AdminLayout.vue | 28 +- frontend/src/pages/AdminAnalytics.vue | 607 +++++++++++++----- frontend/src/pages/AdminDashboard.vue | 3 - frontend/src/pages/AdminTags.vue | 260 -------- frontend/src/router/routes.js | 1 + 52 files changed, 1574 insertions(+), 465 deletions(-) create mode 100644 backend/api/analytics.ts create mode 100644 backend/api/schemas/analytics.ts create mode 100644 backend/models/analytics.ts create mode 100644 backend/modules/analytics/baidu/code.yml create mode 100644 backend/modules/analytics/baidu/definition.yml create mode 100644 backend/modules/analytics/clarity/code.yml create mode 100644 backend/modules/analytics/clarity/definition.yml create mode 100644 backend/modules/analytics/contentsquare/code.yml create mode 100644 backend/modules/analytics/contentsquare/definition.yml create mode 100644 backend/modules/analytics/elasticapm/code.yml create mode 100644 backend/modules/analytics/elasticapm/definition.yml create mode 100644 backend/modules/analytics/fathom/code.yml create mode 100644 backend/modules/analytics/fathom/definition.yml create mode 100644 backend/modules/analytics/google/code.yml create mode 100644 backend/modules/analytics/google/definition.yml create mode 100644 backend/modules/analytics/gtm/code.yml create mode 100644 backend/modules/analytics/gtm/definition.yml create mode 100644 backend/modules/analytics/matomo/code.yml create mode 100644 backend/modules/analytics/matomo/definition.yml create mode 100644 backend/modules/analytics/plausible/code.yml create mode 100644 backend/modules/analytics/plausible/definition.yml create mode 100644 backend/modules/analytics/statcounter/code.yml create mode 100644 backend/modules/analytics/statcounter/definition.yml create mode 100644 backend/modules/analytics/umami/code.yml create mode 100644 backend/modules/analytics/umami/definition.yml create mode 100644 backend/modules/analytics/yandex/code.yml create mode 100644 backend/modules/analytics/yandex/definition.yml create mode 100644 frontend/public/_assets/icons/ultraviolet-baidu.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-clarity.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-contentsquare.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-elastic.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-fathom.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-google-analytics.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-google-tag-manager.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-matomo.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-plausible.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-statcounter.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-umami.svg create mode 100644 frontend/public/_assets/icons/ultraviolet-yandex-metrica.svg delete mode 100644 frontend/src/pages/AdminTags.vue diff --git a/CLAUDE.md b/CLAUDE.md index 20a2b488e..a51069ef9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,7 +79,8 @@ path in silence. - `modules/` — pluggable extensions, discovered from disk. Each module is a directory with a `definition.yml` (key, title, props/config schema) plus its implementation — e.g. `modules/authentication/local/`. `modules/storage/*` ships `db` and `disk` — see - [Storage targets](#storage-targets). + [Storage targets](#storage-targets). `modules/analytics/*` is the odd one out: a pair of YAML files + and no implementation at all — see [Analytics](#analytics). - `tasks/simple/` — jobs run in-process by the scheduler; each exports `task()`. File name is kebab-case, the task key is its camelCase form. - `tasks/workers/` — CPU-bound jobs run in a worker thread via `worker.ts`, which boots a minimal @@ -878,6 +879,67 @@ What already existed and is unchanged: `controllers/rootFiles.ts` serves `robots `sitemap.xml` (with `hreflang` alternates), so **discovery** was never the missing half — the document was. +### Analytics + +A tracking tag from one of a dozen third-party services, turned on per site under **Admin → +Analytics**. `models/analytics.ts`, `api/analytics.ts` and `modules/analytics//`. + +**A module here is two YAML files and nothing else.** `definition.yml` declares what the provider is +and what it needs configured (the same `props` shape every other module type uses, read through +`parseModuleProps`), and `code.yml` holds the markup it contributes. There is no `analytics.ts` +beside them and there is nothing to load: the whole of what a provider does happens in the reader's +browser, so the wiki's only job is to put the right string in the right place. Unlike +`modules/storage/`, a directory that cannot be read is skipped with a warning rather than emptying +the list — a provider nobody can turn on is better than every site's existing tags going quiet. + +**The markup is served, never injected by the app.** It goes into the document `renderAppShell` +hands out, so it is in the HTML of every response — including the one a client that will not run +JavaScript receives. That is the point rather than an implementation detail: several providers verify +an installation by fetching the page and looking for their snippet, which a tag the SPA adds after +boot would fail, and a tag that arrives after boot has already missed the page load it exists to +measure. `code.yml` has two slots, `head` and `bodyStart`; the second exists only because Google Tag +Manager's ` + diff --git a/backend/modules/analytics/gtm/definition.yml b/backend/modules/analytics/gtm/definition.yml new file mode 100644 index 000000000..785fdb317 --- /dev/null +++ b/backend/modules/analytics/gtm/definition.yml @@ -0,0 +1,13 @@ +title: Google Tag Manager +description: Google Tag Manager loads and manages the tracking tags of other services from one container, so the tags themselves are configured at Google rather than here. Use it instead of the individual providers, not alongside them, or a page will be counted twice. +website: https://tagmanager.google.com +icon: '/_assets/icons/ultraviolet-google-tag-manager.svg' +requires: ['containerId'] +props: + containerId: + type: String + title: Container ID + default: '' + hint: The container this site loads, shown at the top of the Tag Manager workspace. Starts with GTM-. + icon: 3d-touch + order: 1 diff --git a/backend/modules/analytics/matomo/code.yml b/backend/modules/analytics/matomo/code.yml new file mode 100644 index 000000000..aaa5d3f4f --- /dev/null +++ b/backend/modules/analytics/matomo/code.yml @@ -0,0 +1,17 @@ +head: | + + + + diff --git a/backend/modules/analytics/matomo/definition.yml b/backend/modules/analytics/matomo/definition.yml new file mode 100644 index 000000000..30c9b790c --- /dev/null +++ b/backend/modules/analytics/matomo/definition.yml @@ -0,0 +1,20 @@ +title: Matomo +description: Matomo is an open source web analytics platform that keeps the data on your own server or in a Matomo Cloud account, rather than with an advertising network. It is the closest like-for-like replacement for Google Analytics. +website: https://matomo.org +icon: '/_assets/icons/ultraviolet-matomo.svg' +requires: ['serverUrl', 'siteId'] +props: + serverUrl: + type: String + title: Server URL + default: '' + hint: The root URL of the Matomo installation, with the scheme and without a trailing slash, e.g. https://example.matomo.cloud + icon: dns + order: 1 + siteId: + type: String + title: Site ID + default: '1' + hint: The number Matomo assigned this site, shown under Administration > Websites > Manage. + icon: 3d-touch + order: 2 diff --git a/backend/modules/analytics/plausible/code.yml b/backend/modules/analytics/plausible/code.yml new file mode 100644 index 000000000..923d89504 --- /dev/null +++ b/backend/modules/analytics/plausible/code.yml @@ -0,0 +1,2 @@ +head: | + diff --git a/backend/modules/analytics/plausible/definition.yml b/backend/modules/analytics/plausible/definition.yml new file mode 100644 index 000000000..ab3265fbe --- /dev/null +++ b/backend/modules/analytics/plausible/definition.yml @@ -0,0 +1,20 @@ +title: Plausible Analytics +description: Plausible is a lightweight, open source analytics tool that sets no cookies and collects no personal data, so a wiki using it needs no consent banner. Available as a hosted service or self-hosted. +website: https://plausible.io +icon: '/_assets/icons/ultraviolet-plausible.svg' +requires: ['domain', 'scriptUrl'] +props: + domain: + type: String + title: Domain + default: '' + hint: The domain as it is registered in Plausible, without a scheme, e.g. wiki.example.com + icon: website + order: 1 + scriptUrl: + type: String + title: Script URL + default: 'https://plausible.io/js/script.js' + hint: Where the tracking script is loaded from. Change it only for a self-hosted instance, or to use one of the extension scripts such as script.hash.js. + icon: link + order: 2 diff --git a/backend/modules/analytics/statcounter/code.yml b/backend/modules/analytics/statcounter/code.yml new file mode 100644 index 000000000..0da86011c --- /dev/null +++ b/backend/modules/analytics/statcounter/code.yml @@ -0,0 +1,10 @@ +head: | + + + + + diff --git a/backend/modules/analytics/statcounter/definition.yml b/backend/modules/analytics/statcounter/definition.yml new file mode 100644 index 000000000..2137cdade --- /dev/null +++ b/backend/modules/analytics/statcounter/definition.yml @@ -0,0 +1,20 @@ +title: StatCounter +description: StatCounter is a long-running hosted analytics service with a free tier, reporting visitors, referrers and pages viewed. It adds a no-script pixel so visits without JavaScript are counted too. +website: https://statcounter.com +icon: '/_assets/icons/ultraviolet-statcounter.svg' +requires: ['projectId', 'securityToken'] +props: + projectId: + type: Number + title: Project ID + default: 0 + hint: The numeric project ID, found in the code snippet StatCounter gives you. + icon: 3d-touch + order: 1 + securityToken: + type: String + title: Security Token + default: '' + hint: The token beside the project ID in the same snippet. It is public - it identifies the project rather than authenticating you. + icon: key + order: 2 diff --git a/backend/modules/analytics/umami/code.yml b/backend/modules/analytics/umami/code.yml new file mode 100644 index 000000000..694e82013 --- /dev/null +++ b/backend/modules/analytics/umami/code.yml @@ -0,0 +1,2 @@ +head: | + diff --git a/backend/modules/analytics/umami/definition.yml b/backend/modules/analytics/umami/definition.yml new file mode 100644 index 000000000..14eaeb873 --- /dev/null +++ b/backend/modules/analytics/umami/definition.yml @@ -0,0 +1,20 @@ +title: Umami +description: Umami is an open source, privacy-focused alternative to Google Analytics, with a single script and no cookies. Runs as a hosted service or on your own server. +website: https://umami.is +icon: '/_assets/icons/ultraviolet-umami.svg' +requires: ['websiteId', 'scriptUrl'] +props: + websiteId: + type: String + title: Website ID + default: '' + hint: The ID Umami assigned this site, shown on its Settings > Websites page. + icon: 3d-touch + order: 1 + scriptUrl: + type: String + title: Script URL + default: 'https://cloud.umami.is/script.js' + hint: Where the tracking script is loaded from. For a self-hosted instance this is your own server, e.g. https://umami.example.com/script.js + icon: link + order: 2 diff --git a/backend/modules/analytics/yandex/code.yml b/backend/modules/analytics/yandex/code.yml new file mode 100644 index 000000000..d7f1fd63a --- /dev/null +++ b/backend/modules/analytics/yandex/code.yml @@ -0,0 +1,16 @@ +head: | + + + + diff --git a/backend/modules/analytics/yandex/definition.yml b/backend/modules/analytics/yandex/definition.yml new file mode 100644 index 000000000..700208e1a --- /dev/null +++ b/backend/modules/analytics/yandex/definition.yml @@ -0,0 +1,20 @@ +title: Yandex Metrica +description: Yandex Metrica is a free analytics service with session replay and heatmaps built in, widely used where Yandex is the main search engine. +website: https://metrica.yandex.com +icon: '/_assets/icons/ultraviolet-yandex-metrica.svg' +requires: ['tagNumber'] +props: + tagNumber: + type: Number + title: Tag Number + default: 0 + hint: The numeric tag ID. When creating the tag choose "CMS and website builders" and copy the number it gives you. + icon: 3d-touch + order: 1 + webvisor: + type: Boolean + title: Session Replay + default: false + hint: Record what readers do on a page so it can be played back. Off by default - it captures far more about a reader than a page view does, and may need disclosing in your privacy policy. + icon: video-playlist + order: 2 diff --git a/frontend/public/_assets/icons/ultraviolet-baidu.svg b/frontend/public/_assets/icons/ultraviolet-baidu.svg new file mode 100644 index 000000000..739d97998 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-baidu.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-clarity.svg b/frontend/public/_assets/icons/ultraviolet-clarity.svg new file mode 100644 index 000000000..5bd4ae7b1 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-clarity.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-contentsquare.svg b/frontend/public/_assets/icons/ultraviolet-contentsquare.svg new file mode 100644 index 000000000..7ff312b4f --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-contentsquare.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-elastic.svg b/frontend/public/_assets/icons/ultraviolet-elastic.svg new file mode 100644 index 000000000..4554503a8 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-elastic.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-fathom.svg b/frontend/public/_assets/icons/ultraviolet-fathom.svg new file mode 100644 index 000000000..7262fc242 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-fathom.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-google-analytics.svg b/frontend/public/_assets/icons/ultraviolet-google-analytics.svg new file mode 100644 index 000000000..ec167fcc5 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-google-analytics.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-google-tag-manager.svg b/frontend/public/_assets/icons/ultraviolet-google-tag-manager.svg new file mode 100644 index 000000000..9eb12b695 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-google-tag-manager.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-matomo.svg b/frontend/public/_assets/icons/ultraviolet-matomo.svg new file mode 100644 index 000000000..2f3c0e0b7 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-matomo.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-plausible.svg b/frontend/public/_assets/icons/ultraviolet-plausible.svg new file mode 100644 index 000000000..affcb1842 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-plausible.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-statcounter.svg b/frontend/public/_assets/icons/ultraviolet-statcounter.svg new file mode 100644 index 000000000..ae8b31f42 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-statcounter.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-umami.svg b/frontend/public/_assets/icons/ultraviolet-umami.svg new file mode 100644 index 000000000..0c9d46a1a --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-umami.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/_assets/icons/ultraviolet-yandex-metrica.svg b/frontend/public/_assets/icons/ultraviolet-yandex-metrica.svg new file mode 100644 index 000000000..ac26be380 --- /dev/null +++ b/frontend/public/_assets/icons/ultraviolet-yandex-metrica.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue index 3860ff21d..2096f362c 100644 --- a/frontend/src/layouts/AdminLayout.vue +++ b/frontend/src/layouts/AdminLayout.vue @@ -130,6 +130,15 @@ {{ t('admin.general.title') }} + + + + + {{ t('admin.analytics.title') }} + @@ -139,15 +148,6 @@ {{ t('admin.approval.title') }}