rllngr / kirby-moniter
Kirby CMS plugin — exposes a protected status endpoint for the Moniter dashboard
Requires
- php: >=8.0
- getkirby/composer-installer: ^1.2
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A lightweight Kirby CMS plugin that exposes a secured status endpoint for the Moniter dashboard — tracks version and uptime of client websites.
Installation
Via Composer (recommended)
composer require rllngr/kirby-moniter
Manual
Copy the folder into site/plugins/moniter/.
Configuration
Add the API key to site/config/config.php:
return [ 'moniter.key' => 'your-key-generated-by-moniter', // ... ];
The key is automatically generated by the Moniter dashboard when adding a client.
Endpoint
GET /moniter/status
Header: X-Moniter-Key: <key>
Response
{
"kirby": "5.3.2",
"php": "8.3.0",
"stats": true,
"plugins": {
"author/plugin": "1.2.0"
}
}
Error (invalid key)
{ "error": "Unauthorized" }
HTTP 401
Page views (cookieless)
Since v1.2.0 the plugin counts page views without cookies or identifiers. Stored per day: page path + view count, and the referring domain for external visits. No IP, no User-Agent, no cookie — so no consent banner is needed.
Since v1.3.0 it also stores, per page and day, the seconds the page stayed visible (only while the tab is in the foreground, capped at 30 min per view), the browser's time zone (Europe/Paris), from which Moniter derives the country, and the browser's language (fr, en… — region dropped). The IP address is never read.
These are page views, not visitors: without an identifier, unique visitors cannot be counted.
- Kirby sites: nothing to do. A
<script src="/moniter/beacon.js" defer>is added to every HTML page (works with the page cache and with ascript-src 'self'CSP). - Filtered at count time: bots / monitoring tools (by User-Agent, never stored), logged-in Panel users, foreign origins, Panel / API / media paths, query strings.
- Storage: SQLite in
site/logs/moniter/stats.sqlite(needspdo_sqlite), 400 days retention.
return [ 'moniter.stats' => true, // false to disable 'moniter.stats.origins' => ['https://www.example.com'], // headless fronts allowed to send hits ];
Headless / Nuxt
Add the front's URL to moniter.stats.origins, then create plugins/moniter.client.ts:
export default defineNuxtPlugin((nuxtApp) => { // e.g. https://cms.example.com/moniter/hit const endpoint = useRuntimeConfig().public.moniterHit as string if (!endpoint) return const timeUrl = endpoint.replace(/hit$/, 'time') const z = Intl.DateTimeFormat().resolvedOptions().timeZone || '' const visible = () => document.visibilityState === 'visible' let last = '', referrer = document.referrer, ms = 0, sent = 0, since = visible() ? Date.now() : 0 // Sends the visible time accumulated on the current page since the last send const flush = () => { if (since) { ms += Date.now() - since; since = visible() ? Date.now() : 0 } const n = Math.min(Math.round(ms / 1000), 1800), d = n - sent if (last && d > 0) { sent = n; navigator.sendBeacon(timeUrl, JSON.stringify({ p: last, t: d })) } } document.addEventListener('visibilitychange', () => { if (visible()) since = Date.now(); else flush() }) addEventListener('pagehide', flush) nuxtApp.hook('page:finish', () => { const p = location.pathname if (p === last) return flush() ms = 0; sent = 0; since = visible() ? Date.now() : 0 last = p navigator.sendBeacon(endpoint, JSON.stringify({ p, r: referrer, z, l: navigator.language || '' })) referrer = '' // only the landing page carries the external referrer }) })
If the front has a CSP, allow the CMS domain in connect-src.
Export
GET /moniter/stats?since=YYYY-MM-DD
Header: X-Moniter-Key: <key>
{ "since": "2026-09-01", "today": "2026-10-02",
"pages": [{ "d": "2026-10-02", "p": "/projets", "v": 12, "s": 540 }],
"referrers": [{ "d": "2026-10-02", "h": "google.com", "v": 3 }],
"zones": [{ "d": "2026-10-02", "z": "Europe/Paris", "v": 11 }],
"langs": [{ "d": "2026-10-02", "l": "fr", "v": 10 }] }
Security
- Key comparison uses
hash_equals()(timing attack protection) - Without a valid
X-Moniter-Keyheader, the endpoint always returns401 - Never commit the key in the client site repository — use an environment variable if needed
Requirements
- Kirby 4.x or 5.x
- PHP 8.0+
License
MIT — rollinger.design