kdjfs / user-presence
Shows who is currently working in the Kirby Panel, right in the users view
Package info
github.com/kdjfs-design/kirby-user-presence
Type:kirby-plugin
pkg:composer/kdjfs/user-presence
Requires
- php: ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0
- getkirby/composer-installer: ^1.2
Requires (Dev)
- getkirby/cms: ^5.2
- phpunit/phpunit: ^11.0
README
Shows who is currently working in the Kirby Panel, right in the users view.
| Dot | Meaning |
|---|---|
| green | active in the Panel within the last few minutes |
| blue | signed in, but idle for a while |
| no dot | not signed in |
Green means worked in the Panel: opened a view, edited a page, saved a field. An open tab on its own is not activity, so a forgotten browser window turns blue like everyone else.
Three tabs filter the list, and they combine with the role filter
(/users?role=editor&presence=active):
| Tab | Query | Shows |
|---|---|---|
| Active | presence=active |
the green dots |
| Idle | presence=idle |
the blue dots |
| Signed in | presence=signedin |
both, everyone with a session |
Signed in is the union of the other two, so someone working right now appears under Active and under Signed in, but never under Idle.
A presence tab always shows every role: Active means every active user, never a subset. To combine, pick the presence first and the role second — the role tabs keep an active presence filter. Kirby's own All tab clears both at once and is the way back to the unfiltered list.
The active dot pulses. Anyone who has asked for less motion in their system
settings (prefers-reduced-motion) gets it at rest — colour and meaning stay
the same.
Requirements
Kirby 5.2 or newer, PHP 8.2 or newer. No build step and no npm.
Installation
With Composer
composer require kdjfs/user-presence
The getkirby/composer-installer places the plugin in site/plugins/.
Without Composer
Copy the directory to site/plugins/user-presence/. The plugin loads its own
classes, so no autoloader is required.
Options
In site/config/config.php, all values in seconds:
'kdjfs.user-presence' => [
'activeWithin' => 300, // green up to here, blue after
'sessionTimeout' => 1800, // no dot beyond this
'throttle' => 60, // minimum gap between writes
'staleAfter' => 120, // when the stale notice appears
],
Presence is kept in the plugin cache, which is enabled by default
(kdjfs.user-presence.cache). A different cache type can be configured as for
any Kirby plugin.
If the cache is unavailable, the plugin follows Kirby's own convention: with
debug enabled the request fails with the original error message, so a
misconfigured cache is fixed rather than worked around. In production the
message goes to the error log, every user counts as offline, and the Panel keeps
working. Nothing is swallowed silently either way.
Which cache type. The default file cache is fine for a few hundred users. Beyond that a shared in-memory cache is noticeably quicker, because filtering by presence reads one entry per user:
'kdjfs.user-presence' => [
'cache' => ['type' => 'apcu'],
],
Kirby's memory type is not the one you want: it lives for a single request
only, so every user would always appear offline. apcu needs no extra service;
use memcached or redis when several web servers have to share the data.
Losing it on a restart is harmless — everyone shows offline until their next
click.
Language
The Panel labels ship in English and German and follow the Panel language of the signed-in user. Any other language falls back to English.
A language is added in site/config/config.php, no plugin required:
'extensions' => [
'translations' => [
'fr' => [
'kdjfs.user-presence.tab.active' => 'Actif',
'kdjfs.user-presence.tab.idle' => 'Inactif',
'kdjfs.user-presence.tab.signedin' => 'Connecté',
'kdjfs.user-presence.stale' => 'État d’il y a {{ minutes }} minutes.',
'kdjfs.user-presence.refresh' => 'Actualiser',
],
],
],
The extensions wrapper is what makes it work: only api, routes and hooks are
read straight from the options, everything else has to sit below that key. A
translations key at the top level of the config has no effect.
The same route renames single labels, because config extensions are registered after the plugins and merged recursively — so this changes one word and leaves the rest of the German set alone:
'extensions' => [
'translations' => [
'de' => ['kdjfs.user-presence.tab.idle' => 'Abwesend'],
],
],
A language that others should get too is welcome as a pull request; it then ships with the plugin instead of living in one project's config.
Privacy
The plugin stores one timestamp per user — when they were last seen in the Panel — and shows everyone with access to the users view who is currently working.
It is built to keep as little as possible:
- No history. Only the latest timestamp is held, never a trail.
- Automatic deletion. Entries live in the cache with
sessionTimeoutas their lifetime, 30 minutes by default, and expire on their own. - Removed on sign-out, immediately.
- No export, no API, no third party. The data never leaves the site.
Even so, in an employment context a display of colleagues' activity is a technical facility capable of monitoring conduct and performance. Under German law that can require the works council's agreement (§ 87 (1) no. 6 BetrVG), and comparable rules exist in other jurisdictions — being capable of it is enough, an intention to monitor is not required. This is not legal advice: please check it before rolling the plugin out to a team.
Limitations
The list shows the state at the time it was loaded. If it stays open, a notice appears offering to refresh it. There is deliberately no automatic refresh in the background: it would put constant load on the server for information that is rarely urgent.
Closing the browser without signing out leaves a user marked as signed in until
sessionTimeout expires. They turn blue after activeWithin, so the display
never claims presence that isn't there.
Your own account is always green and never appears under Idle — not as a matter of principle, but because the display only changes when you act: loading the list is activity, and so is the refresh from the notice. Watching your own entry grow old would take a refresh in the background, and that refresh would have to be excluded from activity itself — the way the keep-alive ping is. There deliberately is no such refresh, see the paragraph above.
With a role and a presence filter set at once, only the presence tab is underlined. Both filters still apply — the tab bar can mark just one of them.
Support the work
This plugin is free and stays that way. If it saves you some time, you are welcome to buy me a coffee or chip in directly:
Internals
How the plugin is put together, and which Panel behaviour it relies on, is described in ARCHITECTURE.md.
Licence
MIT — see LICENSE.
Eine deutsche Fassung dieser Datei liegt unter README.de.md.
