feat(license): add BSL license enforcement with device limit indicator

- Add LICENSE_DEVICES env var (default 250, matches BSL 1.1 free tier)
- Add /api/settings/license endpoint returning device count vs limit
- Header shows flashing red "502/500 licensed" badge when over limit
- About page shows license tier, device count, and over-limit warning
- Nothing is crippled — all features work regardless of device count
- Bump version to 9.7.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-19 22:28:56 -05:00
parent 0142107e68
commit fdc8d9cb68
9 changed files with 81 additions and 5 deletions

View File

@@ -14,6 +14,7 @@ import {
import { useAuth, isSuperAdmin } from '@/lib/auth'
import { useUIStore } from '@/lib/store'
import { tenantsApi, metricsApi } from '@/lib/api'
import { getLicenseStatus } from '@/lib/settingsApi'
import { useEventStreamContext } from '@/contexts/EventStreamContext'
import type { ConnectionState } from '@/hooks/useEventStream'
import { NotificationBell } from '@/components/alerts/NotificationBell'
@@ -88,6 +89,14 @@ export function ContextStrip() {
const offlineCount = fleet?.filter((d) => d.status === 'offline').length ?? 0
const degradedCount = fleet?.filter((d) => d.status === 'degraded').length ?? 0
// License status (super_admin only)
const { data: license } = useQuery({
queryKey: ['license-status'],
queryFn: getLicenseStatus,
enabled: superAdmin,
refetchInterval: 60_000,
})
const handleLogout = async () => {
await logout()
void navigate({ to: '/login' })
@@ -188,6 +197,11 @@ export function ContextStrip() {
) : (
<span className="text-xs text-text-muted">Status loading...</span>
)}
{license?.over_limit && (
<span className="text-xs font-mono text-error animate-pulse">
{license.actual_devices}/{license.licensed_devices} licensed
</span>
)}
</div>
{/* Right: Actions */}