Search by

rllngr / kirby-moniter

RLLNGR

Kirby CMS plugin — exposes a protected status endpoint for the Moniter dashboard

Package info

github.com/RLLNGR/kirby-moniter

Type:kirby-plugin

pkg:composer/rllngr/kirby-moniter

Statistics

Installs: 79

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

1.3.0 2026-10-08 17:27 UTC

This package is auto-updated.

Last update: 2026-10-08 17:33:17 UTC


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 a script-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 (needs pdo_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-Key header, the endpoint always returns 401
  • 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