Files
UNITRONIX bbf839754e Harden bd-mgmt, API key, and WS security
Add multiple security hardenings across the server and web console: enforce proof-of-possession for /ws/bd-mgmt using Ed25519-signed headers with timestamp/nonce and replay protection (public key binding, canonicalization, storage, verification, and tests); remove legacy API key query param and config-table fallback in favor of scoped api_keys (migrate bootstrap key into api_keys); tighten WebSocket origin handling for relay and signal servers to allow only localhost origins by default unless an explicit allowlist is set; update auth middleware public paths and test helpers to use X-API-Key header; add ensureScopedAPIKey migration and related helpers; add a GitHub Secret Scan workflow and an audit report. Misc: propagate audit logging on bd-mgmt connect/disconnect and validate enrollment public keys during device register.
2026-03-29 01:48:14 +01:00

109 lines
4.7 KiB
HTML

<%- include('layouts/main', {
title: typeof title !== 'undefined' ? title : 'Tenants',
pageStyles: ['tenants'],
pageScripts: ['tenants'],
currentPage: 'tenants',
breadcrumb: [{ label: _('nav.system') }, { label: _('tenants.title') }],
body: `
<div class="page-header">
<h1>${_('tenants.title')}</h1>
</div>
<!-- Stats -->
<div class="stat-cards">
<div class="stat-card">
<div class="stat-icon primary"><span class="material-icons">business</span></div>
<div class="stat-info">
<span class="stat-value" id="stat-tenants">0</span>
<span class="stat-label">${_('tenants.total_tenants')}</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success"><span class="material-icons">check_circle</span></div>
<div class="stat-info">
<span class="stat-value" id="stat-active">0</span>
<span class="stat-label">${_('tenants.active_tenants')}</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon warning"><span class="material-icons">devices</span></div>
<div class="stat-info">
<span class="stat-value" id="stat-devices">0</span>
<span class="stat-label">${_('tenants.total_devices')}</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon primary"><span class="material-icons">people</span></div>
<div class="stat-info">
<span class="stat-value" id="stat-users">0</span>
<span class="stat-label">${_('tenants.total_users')}</span>
</div>
</div>
</div>
<!-- Tenant list -->
<div class="panel-actions">
<div class="search-wrapper">
<span class="material-icons">search</span>
<input type="text" id="tenant-search" placeholder="${_('tenants.search')}" />
</div>
<button class="btn btn-primary" id="create-tenant-btn">
<span class="material-icons">add</span> ${_('tenants.create_tenant')}
</button>
</div>
<div class="tenants-grid" id="tenants-grid"></div>
<!-- Tenant detail modal -->
<div class="modal-overlay" id="detail-modal" style="display:none">
<div class="modal modal-lg">
<div class="modal-header">
<h2 id="detail-modal-title">${_('tenants.detail')}</h2>
<button class="modal-close" data-modal="detail-modal"><span class="material-icons">close</span></button>
</div>
<div class="modal-body" id="detail-body"></div>
</div>
</div>
<!-- Tenant form modal -->
<div class="modal-overlay" id="form-modal" style="display:none">
<div class="modal">
<div class="modal-header">
<h2 id="form-modal-title">${_('tenants.create_tenant')}</h2>
<button class="modal-close" data-modal="form-modal"><span class="material-icons">close</span></button>
</div>
<div class="modal-body">
<input type="hidden" id="tenant-id" />
<div class="form-row">
<label for="tenant-name">${_('tenants.name')}</label>
<input type="text" id="tenant-name" />
</div>
<div class="form-row">
<label for="tenant-slug">${_('tenants.slug')}</label>
<input type="text" id="tenant-slug" placeholder="my-company" />
</div>
<div class="form-row">
<label for="tenant-contact-name">${_('tenants.contact_name')}</label>
<input type="text" id="tenant-contact-name" />
</div>
<div class="form-row">
<label for="tenant-contact-email">${_('tenants.contact_email')}</label>
<input type="email" id="tenant-contact-email" />
</div>
<div class="form-row">
<label for="tenant-max-devices">${_('tenants.max_devices')}</label>
<input type="number" id="tenant-max-devices" value="100" />
</div>
<div class="form-row">
<label><input type="checkbox" id="tenant-active" checked /> ${_('tenants.enabled')}</label>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="tenant-cancel-btn">${_('common.cancel')}</button>
<button class="btn btn-primary" id="tenant-save-btn">${_('common.save')}</button>
</div>
</div>
</div>
`
}) %>