Shot Counter
{{ counterOnly ? 'Sign in to view the counter.' : 'Sign in to access the operator console.' }}

Access restricted

The operator console is available to members of {{ adminGroupName }}.

Signed in as {{ auth.username }}.

{{ pageTitle }}

{{ pageSubtitle }}

{{ nowLocal }} · Berlin
Live · Fleet count
Last event · {{ formatRelative(kafkaStatus.last_event_at) }}
Live · Authoritative count
Last event · {{ formatRelative(kafkaStatus.last_event_at) }}
Today’s shots
{{ dailyShots ? formatNumber(dailyShots.today_count) : '—' }}
{{ dailyShots.difference > 0 ? '+' : dailyShots.difference < 0 ? '−' : '±' }}{{ formatNumber(Math.abs(dailyShots.difference)) }} vs yesterday Yesterday · {{ formatNumber(dailyShots.yesterday_count) }}
Berlin · Recorded brews
Processed
{{ formatNumber(kafkaStatus.processed_events_total) }}
Skipped
{{ formatNumber(kafkaStatus.skipped_events_total) }}
Online devices
{{ onlineCount }}
Paused devices
{{ pausedCount }}

Recent events

Latest counted / skipped messages.

{{ event.event_id || 'no-event-id' }} {{ event.action }}
{{ formatTime(event.created_at) }} {{ event.iot_device_id || 'unknown-device' }} #{{ formatNumber(event.resulting_count) }}
No Kafka events yet.

Fleet snapshot

Current state of registered counters.

{{ deviceLabel(device) }} {{ device.connected ? 'Online' : 'Offline' }}
{{ device.device_id }}
Display count
Last edge ACK · {{ formatRelative(device.acknowledged_display_at) }}
No devices registered yet.

Startup greeting

Show HELLO before the count when a counter starts. Applies to the entire fleet on the next startup.

Loading greeting settings…

Count offset

Add missed historical shots to every counter. Saving replaces the previous offset.

Loading count settings…

Live · Current count
Last event · {{ formatRelative(kafkaStatus.last_event_at) }}
Processed
{{ formatNumber(kafkaStatus.processed_events_total) }}
Skipped
{{ formatNumber(kafkaStatus.skipped_events_total) }}
Total events
{{ formatNumber(kafkaStatus.total_events) }}
Last event
{{ formatTime(kafkaStatus.last_event_at) }}
{{ filteredKafkaEvents.length }} / {{ kafkaRecentEvents.length }} rows
Visible columns {{ visibleKafkaColumns.length }} / {{ kafkaColumnLayout.length }}
{{ column.label }}
Drag to reorder.
{{ column.label }}
No events match the current filter.
{{ filteredDevices.length }} / {{ devices.length }} devices
{{ device.location || device.device_id }}
{{ device.connected ? 'Online' : 'Offline' }}
Last acknowledged display Event mode
Current event count{{ formatNumber(device.actual_count) }}
Last ACK · {{ formatRelative(device.acknowledged_display_at) }}
{{ (device.event_source_device_ids || []).length }} event sources Started {{ formatClock(device.event_mode_started_at) }}
  • {{ source }}
Last seen {{ formatRelative(device.last_seen_at) }} · Agent {{ device.agent_version || 'unknown' }}
No devices match the current filter.

{{ deviceLabel(selectedDevice) }}

{{ selectedDevice.location || selectedDevice.device_id }}
{{ selectedDevice.connected ? 'Online' : 'Offline' }}
Last acknowledged displayEvent mode
Current event count{{ formatNumber(selectedDevice.actual_count) }}
Last ACK · {{ formatRelative(selectedDevice.acknowledged_display_at) }}

Commands are queued until the device reconnects.

Send count now updates the display once. Pause and quiet hours remain active.

Last seen {{ formatRelative(selectedDevice.last_seen_at) }}

Counting mode

{{ selectedDevice.event_mode ? 'Event mode' : 'Fleet count' }}

Shows the fleet count. Start an event to count brews from selected sources at zero.

Started {{ formatTime(selectedDevice.event_mode_started_at) }} · {{ (selectedDevice.event_source_device_ids || []).length }} sources. Counting continues while display updates are paused.

  • {{ source }}
Separate IDs with commas or new lines. {{ selectedDevice.event_mode ? 'Adding sources preserves the count and start time.' : 'Only brews after entry count. The fleet count continues normally.' }}

Quiet hours

{{ selectedDevice.quiet_hours_active ? 'Active' : selectedDevice.quiet_hours_start ? 'Scheduled' : 'Off' }}

{{ selectedDevice.quiet_hours_start ? `${selectedDevice.quiet_hours_start}–${selectedDevice.quiet_hours_end} every day · Europe/Berlin` : 'No daily schedule. Display updates follow the current pause state.' }}

Automatic display updates pause during the blue interval. Counts keep accumulating.

Activity

Display updates, schedule changes, and commands. Times in Berlin.

{{ activityTitle(row) }}{{ activitySummary(row) }}
{{ row.details }}
No activity yet. Schedule changes and display commands will appear here.

Send command

Dispatch a raw command payload to the selected display.

Explicit commands also run during pauses and quiet hours.

Accepts number, JSON, boolean, null, or plain text.

Device information

Device ID
{{ selectedDevice.device_id }}
Location
{{ selectedDevice.location || 'Not set' }}
Agent
{{ selectedDevice.agent_version || 'Unknown' }}
Last heartbeat
{{ formatTime(selectedDevice.last_heartbeat_at) }}
Manual override
{{ selectedDevice.manual_override == null ? 'None' : formatNumber(selectedDevice.manual_override) }}

Device history

Unified timeline — command dispatch, acknowledgements, and telemetry events.

{{ filteredDeviceTimeline.length }} entries
Time (Berlin) Kind Name Status Source
No matching history entries.
No devices registered. Devices appear here once they connect.
Target devices
{{ fleetAnimationTargets.length === devices.length ? `All devices (${devices.length})` : `${fleetAnimationTargets.length} of ${devices.length} devices` }}
Choose animation target devices

Untick devices to exclude them. Overview filters do not affect targets.

{{ t.message }}