@tdhftw/emdash-plugin-umami 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/LICENSE +21 -0
- package/README.md +132 -2
- package/dist/index.d.ts +144 -0
- package/dist/index.js +796 -0
- package/package.json +74 -4
- package/src/admin/AnalyticsPage.tsx +73 -0
- package/src/admin/Chart.tsx +23 -0
- package/src/admin/OverviewView.tsx +150 -0
- package/src/admin/SettingsView.tsx +319 -0
- package/src/admin/Widget.tsx +75 -0
- package/src/admin/api.ts +14 -0
- package/src/admin/chartOptions.ts +54 -0
- package/src/admin/format.ts +119 -0
- package/src/admin/hooks.ts +74 -0
- package/src/admin/index.tsx +13 -0
- package/src/admin/query.tsx +29 -0
- package/src/shared/api-types.ts +43 -0
- package/src/shared/constants.ts +35 -0
- package/src/shared/settings-types.ts +17 -0
- package/src/shared/stats.ts +52 -0
- package/src/shared/tracker.ts +10 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.2 (2026-10-28)
|
|
4
|
+
|
|
5
|
+
First release on npm. No code changes since 0.1.1.
|
|
6
|
+
|
|
7
|
+
- README: current status, tested versions, and a link to the upstream dev-mode issue.
|
|
8
|
+
- Package description no longer mentions Umami Cloud, which is still switched off.
|
|
9
|
+
|
|
10
|
+
## 0.1.1 (2026-10-28)
|
|
11
|
+
|
|
12
|
+
- Chart bars show real counts: pageviews and visitors are drawn over each other instead of
|
|
13
|
+
stacked, which made each bar the sum of both.
|
|
14
|
+
- Chart x-axis labels follow the site's timezone and no longer repeat or overlap around the
|
|
15
|
+
start of a month (seen on the 90-day range).
|
|
16
|
+
- Chart y-axis shows whole numbers only, so a range without data no longer reads 1, 1, 1, 0, 0, 0.
|
|
17
|
+
- The chart has a legend for the two series.
|
|
18
|
+
|
|
19
|
+
## 0.1.0 (2026-09-28)
|
|
20
|
+
|
|
21
|
+
First version.
|
|
22
|
+
|
|
23
|
+
- Analytics page in the EmDash admin, with KPI tiles compared against the previous period,
|
|
24
|
+
active visitors, a pageviews and visitors chart, and top pages, referrers, countries,
|
|
25
|
+
browsers, operating systems, devices and events. Ranges are 24 hours, 7, 30 and 90 days.
|
|
26
|
+
- Dashboard widget with the last 7 days and a sparkline.
|
|
27
|
+
- Guided settings for self-hosted Umami, with API key or username and password sign-in, a
|
|
28
|
+
connection test and a website picker that includes team-shared sites.
|
|
29
|
+
- Umami Cloud support is implemented but disabled (shown as "coming in 0.2.0") until it has
|
|
30
|
+
been tested against a Cloud account.
|
|
31
|
+
- Optional tracker injection into public pages through `page:fragments`.
|
|
32
|
+
- Statistics cached in plugin storage, with the saved copy shown when Umami is unreachable.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thord D. Hedengren
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,133 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tdhftw/emdash-plugin-umami
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[Umami](https://umami.is) analytics inside the [EmDash](https://emdashcms.com) admin, for
|
|
4
|
+
self-hosted Umami. Umami Cloud support is built but switched off until it has been tested against
|
|
5
|
+
a Cloud account; it is planned for 0.2.0.
|
|
6
|
+
|
|
7
|
+
- **Analytics page.** Visitors, visits, pageviews, bounce rate and visit duration, each compared
|
|
8
|
+
with the previous period. It also shows active visitors, a chart, and the top pages,
|
|
9
|
+
referrers, countries, browsers, operating systems, devices and custom events for the last 24
|
|
10
|
+
hours, 7, 30 or 90 days.
|
|
11
|
+
- **Dashboard widget.** The last 7 days at a glance, with a sparkline and active visitors.
|
|
12
|
+
- **Guided setup.** It checks the connection and lists the Umami websites you can pick from.
|
|
13
|
+
- **Optional tracker.** It can add the Umami script to your public pages, so the theme doesn't
|
|
14
|
+
need to.
|
|
15
|
+
|
|
16
|
+
> Status: 0.1.x, self-hosted Umami only. Tested with EmDash 1.0.1 to 1.3.1 and Umami 3.4.
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- EmDash 1.0 or later, with Astro 7.
|
|
21
|
+
- A self-hosted Umami and an API key for it. Umami 3.x has API keys under Settings → API keys.
|
|
22
|
+
Older versions can sign in with a username and password instead.
|
|
23
|
+
- `EMDASH_ENCRYPTION_KEY` set on the site, which EmDash needs to store the API key or password
|
|
24
|
+
encrypted. Generate one with `npx emdash secrets generate`. On Cloudflare, store it with
|
|
25
|
+
`wrangler secret put EMDASH_ENCRYPTION_KEY`.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @tdhftw/emdash-plugin-umami
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
// astro.config.mjs
|
|
35
|
+
import { umamiPlugin } from "@tdhftw/emdash-plugin-umami";
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
integrations: [
|
|
39
|
+
emdash({
|
|
40
|
+
// ...database, storage and the rest of your config
|
|
41
|
+
plugins: [umamiPlugin()],
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This is a native EmDash plugin, so it goes in `plugins`, not `sandboxed`. Deploy the site after
|
|
48
|
+
adding it.
|
|
49
|
+
|
|
50
|
+
## Set up
|
|
51
|
+
|
|
52
|
+
1. Open **Analytics** in the admin sidebar. Until Umami is connected, it opens on **Settings**.
|
|
53
|
+
2. Enter the address you open Umami at, for example `https://stats.example.com`. The Umami Cloud
|
|
54
|
+
option is shown but disabled until 0.2.0.
|
|
55
|
+
3. Paste an API key, or on older self-hosted versions, a username and password.
|
|
56
|
+
4. Select **Save and test connection**. When it connects, the websites the key can see appear
|
|
57
|
+
under **Website**, including sites shared with you through a team.
|
|
58
|
+
5. Pick the website, then save again.
|
|
59
|
+
|
|
60
|
+
The key and password are encrypted with `EMDASH_ENCRYPTION_KEY` and never sent back to the
|
|
61
|
+
browser. The same settings are also available from the gear on the plugin's card under
|
|
62
|
+
**Plugins**, as a plain form.
|
|
63
|
+
|
|
64
|
+
## Adding the tracker (optional)
|
|
65
|
+
|
|
66
|
+
Turning on **Add the Umami tracker to public pages** adds the Umami script to the `<head>` of
|
|
67
|
+
every public page:
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<script src="https://stats.example.com/script.js" defer data-website-id="…"></script>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **Theme requirement.** Your theme must render EmDash's `<EmDashHead>` component, as the EmDash
|
|
74
|
+
templates do.
|
|
75
|
+
- **Avoid double counting.** Remove any Umami script the theme already includes, or every visit
|
|
76
|
+
is counted twice.
|
|
77
|
+
- **Custom script URL.** Set one if you serve a renamed tracker or proxy it.
|
|
78
|
+
- **Cached pages.** If your site caches pages, cached copies change over only when their cache
|
|
79
|
+
expires.
|
|
80
|
+
- **Admin pages** are never tracked.
|
|
81
|
+
|
|
82
|
+
## Permissions
|
|
83
|
+
|
|
84
|
+
| Capability | Why |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `network:request:unrestricted` | Umami's address is configured by you, so no fixed host list is possible. The plugin only calls the Umami URL you set. |
|
|
87
|
+
| `hooks.page-fragments:register` | Needed only for the optional tracker. |
|
|
88
|
+
|
|
89
|
+
Reading statistics requires the `plugins:read` permission. Changing settings requires
|
|
90
|
+
`plugins:manage`.
|
|
91
|
+
|
|
92
|
+
## Caching and rate limits
|
|
93
|
+
|
|
94
|
+
- **Statistics** are cached in the plugin's storage for 1 minute on the 24-hour range, and up to
|
|
95
|
+
15 minutes on longer ranges. If Umami can't be reached, the last saved copy is shown and
|
|
96
|
+
marked as such.
|
|
97
|
+
- **Active visitors** are cached for 20 seconds.
|
|
98
|
+
- **One overview load** makes 10 requests to Umami. That matters for Umami Cloud, which allows
|
|
99
|
+
50 requests per 15 seconds per key.
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm install
|
|
105
|
+
npm run typecheck
|
|
106
|
+
npm test
|
|
107
|
+
npm run build
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`src/index.ts` holds the plugin definition, settings schema, routes and hooks. It is compiled to
|
|
111
|
+
`dist/`. `src/admin/` is the React admin UI. It ships as source because the site's own build
|
|
112
|
+
compiles it.
|
|
113
|
+
|
|
114
|
+
To try the plugin in a local EmDash site, install a packed tarball rather than a folder link. A
|
|
115
|
+
linked folder brings along a second copy of React.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm run build && npm pack --pack-destination /tmp
|
|
119
|
+
cd ../your-site && npm install --no-save /tmp/tdhftw-emdash-plugin-umami-<version>.tgz
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Two EmDash quirks to know about under `astro dev`** (still present in 1.3.1):
|
|
123
|
+
|
|
124
|
+
- **Host-context hooks crash.** Plugin components can't use the admin's Lingui or React Query
|
|
125
|
+
providers, because dev mode loads a second copy of those libraries
|
|
126
|
+
([emdash#3587](https://github.com/emdash-cms/emdash/issues/3587)). This plugin brings its own
|
|
127
|
+
React Query client and doesn't use Lingui.
|
|
128
|
+
- **The dev admin can look off.** It may pick up the site's own stylesheet. Production builds
|
|
129
|
+
don't.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT © Thord D. Hedengren
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { PluginDescriptor } from "emdash";
|
|
2
|
+
import { z } from "astro/zod";
|
|
3
|
+
//#region src/shared/constants.d.ts
|
|
4
|
+
/** Plugin id. One URL segment: routes mount at /_emdash/api/plugins/umami/<route>. */
|
|
5
|
+
export declare const PLUGIN_ID = "umami";
|
|
6
|
+
/** Keep in sync with package.json; test/plugin.test.ts enforces it. */
|
|
7
|
+
export declare const PLUGIN_VERSION = "0.1.2";
|
|
8
|
+
type Hosting = "self-hosted" | "cloud";
|
|
9
|
+
type AuthMode = "apiKey" | "password";
|
|
10
|
+
type CloudRegion = "default" | "us" | "eu";
|
|
11
|
+
/** Shape returned by the `status` route. Shared by server and admin. */
|
|
12
|
+
interface StatusResponse {
|
|
13
|
+
configured: boolean;
|
|
14
|
+
/** Settings that still need a value before the plugin can query Umami. */
|
|
15
|
+
missing: string[];
|
|
16
|
+
hosting: Hosting;
|
|
17
|
+
authMode: AuthMode;
|
|
18
|
+
websiteId: string | null;
|
|
19
|
+
/** Origin of the Umami dashboard, for "open in Umami" links. Never contains credentials. */
|
|
20
|
+
dashboardUrl: string | null;
|
|
21
|
+
injectScript: boolean;
|
|
22
|
+
/** Whether the host handed this plugin a ctx.http client. */
|
|
23
|
+
hasHttp: boolean;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/shared/stats.d.ts
|
|
27
|
+
/** Stats shapes shared by the server routes and the admin UI. */
|
|
28
|
+
declare const RANGES: readonly ["24h", "7d", "30d", "90d"];
|
|
29
|
+
type Range = (typeof RANGES)[number];
|
|
30
|
+
declare const METRIC_TYPES: readonly ["path", "referrer", "country", "browser", "os", "device", "event"];
|
|
31
|
+
type MetricType = (typeof METRIC_TYPES)[number];
|
|
32
|
+
interface Totals {
|
|
33
|
+
pageviews: number;
|
|
34
|
+
visitors: number;
|
|
35
|
+
visits: number;
|
|
36
|
+
bounces: number;
|
|
37
|
+
/** Total visit duration in seconds. */
|
|
38
|
+
totaltime: number;
|
|
39
|
+
}
|
|
40
|
+
interface SeriesPoint {
|
|
41
|
+
/** Bucket start as a wall-clock label in the requested time zone: "2026-09-28" or "2026-09-28 14:00". */
|
|
42
|
+
label: string;
|
|
43
|
+
/** Bucket start as epoch milliseconds (UTC instant), for charting. */
|
|
44
|
+
t: number;
|
|
45
|
+
pageviews: number;
|
|
46
|
+
visitors: number;
|
|
47
|
+
}
|
|
48
|
+
interface MetricRow {
|
|
49
|
+
label: string;
|
|
50
|
+
value: number;
|
|
51
|
+
}
|
|
52
|
+
interface Overview {
|
|
53
|
+
websiteId: string;
|
|
54
|
+
range: Range;
|
|
55
|
+
unit: "hour" | "day";
|
|
56
|
+
timezone: string;
|
|
57
|
+
startAt: number;
|
|
58
|
+
endAt: number;
|
|
59
|
+
totals: Totals;
|
|
60
|
+
/** Same-length period immediately before this one. */
|
|
61
|
+
previous: Totals;
|
|
62
|
+
series: SeriesPoint[];
|
|
63
|
+
top: Record<MetricType, MetricRow[]>;
|
|
64
|
+
fetchedAt: number;
|
|
65
|
+
/** True when Umami could not be reached and this is an older cached copy. */
|
|
66
|
+
stale: boolean;
|
|
67
|
+
}
|
|
68
|
+
interface Active {
|
|
69
|
+
visitors: number;
|
|
70
|
+
fetchedAt: number;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/shared/settings-types.d.ts
|
|
74
|
+
interface UmamiSettingsValues {
|
|
75
|
+
hosting: Hosting;
|
|
76
|
+
baseUrl: string | null;
|
|
77
|
+
region: CloudRegion;
|
|
78
|
+
authMode: AuthMode;
|
|
79
|
+
username: string | null;
|
|
80
|
+
websiteId: string | null;
|
|
81
|
+
injectScript: boolean;
|
|
82
|
+
scriptUrl: string | null;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/shared/api-types.d.ts
|
|
86
|
+
type UmamiErrorCode = "not-configured" | "unauthorized" | "forbidden" | "not-found" | "rate-limited" | "unreachable" | "upstream";
|
|
87
|
+
interface UmamiWebsite {
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
domain: string | null;
|
|
91
|
+
}
|
|
92
|
+
/** Settings as the admin sees them: everything except secret values. */
|
|
93
|
+
interface SettingsView {
|
|
94
|
+
values: UmamiSettingsValues;
|
|
95
|
+
secretsSet: {
|
|
96
|
+
apiKey: boolean;
|
|
97
|
+
password: boolean;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
type ConnectionTestResult = {
|
|
101
|
+
ok: true;
|
|
102
|
+
websites: UmamiWebsite[];
|
|
103
|
+
website: UmamiWebsite | null;
|
|
104
|
+
} | {
|
|
105
|
+
ok: false;
|
|
106
|
+
code: UmamiErrorCode;
|
|
107
|
+
message: string;
|
|
108
|
+
};
|
|
109
|
+
type StatsResult<T> = {
|
|
110
|
+
ok: true;
|
|
111
|
+
data: T;
|
|
112
|
+
} | {
|
|
113
|
+
ok: false;
|
|
114
|
+
code: UmamiErrorCode;
|
|
115
|
+
message: string;
|
|
116
|
+
};
|
|
117
|
+
/** Body of `settings/save`. Secrets: a string replaces, null clears, omitted keeps. */
|
|
118
|
+
interface SaveSettingsInput {
|
|
119
|
+
hosting?: "self-hosted" | "cloud";
|
|
120
|
+
baseUrl?: string | null;
|
|
121
|
+
region?: "default" | "us" | "eu";
|
|
122
|
+
authMode?: "apiKey" | "password";
|
|
123
|
+
apiKey?: string | null;
|
|
124
|
+
username?: string | null;
|
|
125
|
+
password?: string | null;
|
|
126
|
+
websiteId?: string | null;
|
|
127
|
+
injectScript?: boolean;
|
|
128
|
+
scriptUrl?: string | null;
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/index.d.ts
|
|
132
|
+
/** Constructor options. Everything user-facing lives in the admin settings form instead. */
|
|
133
|
+
export type UmamiPluginOptions = Record<string, never>;
|
|
134
|
+
/**
|
|
135
|
+
* Descriptor for astro.config.mjs:
|
|
136
|
+
*
|
|
137
|
+
* import { umamiPlugin } from "@tdhftw/emdash-plugin-umami";
|
|
138
|
+
* emdash({ plugins: [umamiPlugin()] })
|
|
139
|
+
*/
|
|
140
|
+
export declare function umamiPlugin(options?: UmamiPluginOptions): PluginDescriptor<UmamiPluginOptions>;
|
|
141
|
+
/** Runtime definition. EmDash imports this named export from the package root. */
|
|
142
|
+
export declare function createPlugin(_options?: UmamiPluginOptions): import("emdash").ResolvedPlugin<import("emdash").PluginStorageConfig>;
|
|
143
|
+
//#endregion
|
|
144
|
+
export { type Active, type ConnectionTestResult, type METRIC_TYPES, type MetricRow, type MetricType, type Overview, type RANGES, type Range, type SaveSettingsInput, type SeriesPoint, type SettingsView, type StatsResult, type StatusResponse, type Totals, createPlugin as default };
|