mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-11 01:55:43 +00:00
bbf839754e
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.
109 lines
4.7 KiB
HTML
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>
|
|
`
|
|
}) %>
|