Files
UNITRONIX 43e0b6f40a Integrate P2P/relay controls for issue #157.
Expose global connection strategy in the web panel with systemd/Docker persistence, extend server health diagnostics, enforce org network policy in the signal handler, and document when relay fallback is expected vs misconfiguration.
2026-06-04 00:26:58 +02:00

216 lines
14 KiB
HTML

<%- include('layouts/main', {
title: _('policies.title') || 'Organization Policies',
pageStyles: ['policies'],
pageScripts: ['policies'],
currentPage: 'policies',
breadcrumb: [{ label: _('policies.title') || 'Organization Policies' }],
body: `
<div class="policy-page">
<!-- Header -->
<div class="policy-header">
<div class="policy-title">
<h1>${_('policies.title') || 'Organization Policies'}</h1>
<p class="policy-subtitle">${_('policies.subtitle') || 'Manage security, connection, and feature policies for your organizations'}</p>
</div>
</div>
<!-- Organization selector -->
<div class="policy-org-selector">
<label>${_('policies.select_org') || 'Organization'}</label>
<select id="policy-org-select" class="form-input">
<option value="">${_('policies.select_org_placeholder') || 'Select an organization...'}</option>
</select>
</div>
<!-- Policy sections (hidden until org selected) -->
<div class="policy-sections" id="policy-sections" style="display:none;">
<!-- Connection Policy -->
<div class="policy-card" data-section="connection">
<div class="policy-card-header">
<div class="policy-card-icon"><span class="material-icons">settings_remote</span></div>
<div class="policy-card-info">
<h3>${_('policies.connection_title') || 'Connection Policy'}</h3>
<p>${_('policies.connection_desc') || 'Control how remote sessions are established'}</p>
</div>
<span class="policy-status-badge" id="conn-status"></span>
</div>
<div class="policy-card-body">
<div class="policy-field">
<label>${_('policies.connection_mode') || 'Connection Mode'}</label>
<select id="pol-connection-mode" class="form-input">
<option value="attended">${_('policies.mode_attended') || 'Attended (requires user approval)'}</option>
<option value="unattended">${_('policies.mode_unattended') || 'Unattended (always allow)'}</option>
<option value="ask_always">${_('policies.mode_ask') || 'Always Ask'}</option>
</select>
</div>
<div class="policy-field">
<label>${_('policies.max_session') || 'Max Session Duration (minutes)'}</label>
<input type="number" id="pol-max-session" class="form-input" min="0" max="1440" value="480" />
</div>
<div class="policy-field">
<label>${_('policies.idle_lock') || 'Idle Lock Timeout (minutes)'}</label>
<input type="number" id="pol-idle-lock" class="form-input" min="0" max="120" value="15" />
</div>
<div class="policy-field">
<label>${_('policies.wallpaper_policy') || 'Wallpaper During Session'}</label>
<select id="pol-wallpaper" class="form-input">
<option value="show">${_('policies.wallpaper_show') || 'Show'}</option>
<option value="hide_during_session">${_('policies.wallpaper_hide_session') || 'Hide during session'}</option>
<option value="hide_always">${_('policies.wallpaper_hide_always') || 'Always hide'}</option>
</select>
</div>
<button class="btn btn-primary btn-sm" onclick="Policies.saveSection('connection')">${_('common.save') || 'Save'}</button>
</div>
</div>
<!-- Feature Policy -->
<div class="policy-card" data-section="features">
<div class="policy-card-header">
<div class="policy-card-icon"><span class="material-icons">extension</span></div>
<div class="policy-card-info">
<h3>${_('policies.features_title') || 'Feature Policy'}</h3>
<p>${_('policies.features_desc') || 'Enable or disable specific features for devices'}</p>
</div>
<span class="policy-status-badge" id="feat-status"></span>
</div>
<div class="policy-card-body">
<div class="policy-toggle-group">
<div class="policy-toggle">
<label>${_('policies.allow_file_transfer') || 'Allow File Transfer'}</label>
<input type="checkbox" id="pol-file-transfer" class="toggle-switch" checked />
</div>
<div class="policy-toggle">
<label>${_('policies.allow_clipboard') || 'Allow Clipboard Sync'}</label>
<input type="checkbox" id="pol-clipboard" class="toggle-switch" checked />
</div>
<div class="policy-toggle">
<label>${_('policies.allow_audio') || 'Allow Audio Streaming'}</label>
<input type="checkbox" id="pol-audio" class="toggle-switch" />
</div>
<div class="policy-toggle">
<label>${_('policies.allow_recording') || 'Allow Session Recording'}</label>
<input type="checkbox" id="pol-recording" class="toggle-switch" checked />
</div>
</div>
<button class="btn btn-primary btn-sm" onclick="Policies.saveSection('features')">${_('common.save') || 'Save'}</button>
</div>
</div>
<!-- Security Policy -->
<div class="policy-card" data-section="security">
<div class="policy-card-header">
<div class="policy-card-icon"><span class="material-icons">shield</span></div>
<div class="policy-card-info">
<h3>${_('policies.security_title') || 'Security Policy'}</h3>
<p>${_('policies.security_desc') || 'Enforce security requirements for organization devices'}</p>
</div>
<span class="policy-status-badge" id="sec-status"></span>
</div>
<div class="policy-card-body">
<div class="policy-toggle-group">
<div class="policy-toggle">
<label>${_('policies.require_2fa') || 'Require 2FA for Unattended Access'}</label>
<input type="checkbox" id="pol-require-2fa" class="toggle-switch" checked />
</div>
</div>
<div class="policy-field">
<label>${_('policies.password_min_length') || 'Minimum Password Length'}</label>
<input type="number" id="pol-pass-min" class="form-input" min="6" max="128" value="12" />
</div>
<div class="policy-toggle-group">
<div class="policy-toggle">
<label>${_('policies.password_require_special') || 'Require Special Characters'}</label>
<input type="checkbox" id="pol-pass-special" class="toggle-switch" checked />
</div>
</div>
<div class="policy-field">
<label>${_('policies.password_max_age') || 'Password Max Age (days, 0=never)'}</label>
<input type="number" id="pol-pass-age" class="form-input" min="0" max="365" value="90" />
</div>
<div class="policy-field">
<label>${_('policies.allowed_operators') || 'Allowed Operator Roles (comma-separated)'}</label>
<input type="text" id="pol-allowed-ops" class="form-input" placeholder="admin, helpdesk" />
</div>
<button class="btn btn-primary btn-sm" onclick="Policies.saveSection('security')">${_('common.save') || 'Save'}</button>
</div>
</div>
<!-- Network Policy -->
<div class="policy-card" data-section="network">
<div class="policy-card-header">
<div class="policy-card-icon"><span class="material-icons">lan</span></div>
<div class="policy-card-info">
<h3>${_('policies.network_title') || 'Network Policy'}</h3>
<p>${_('policies.network_desc') || 'Control relay servers and direct connections'}</p>
</div>
<span class="policy-status-badge" id="net-status"></span>
</div>
<div class="policy-card-body">
<div class="policy-toggle-group">
<div class="policy-toggle">
<label>${_('policies.block_direct_p2p') || 'Block Direct P2P (force relay)'}</label>
<input type="checkbox" id="pol-block-p2p" class="toggle-switch" />
</div>
<p class="text-muted policy-hint">${_('policies.block_direct_p2p_hint') || 'Applies to devices in this organization. Global default is configured under Settings → Connection strategy.'}</p>
</div>
<div class="policy-field">
<label>${_('policies.allowed_relays') || 'Allowed Relay Servers (one per line)'}</label>
<textarea id="pol-relay-servers" class="form-input" rows="3" placeholder="relay1.example.com:21117"></textarea>
</div>
<div class="policy-field">
<label>${_('policies.ip_allowlist') || 'IP Allowlist (CIDR, one per line)'}</label>
<textarea id="pol-ip-allowlist" class="form-input" rows="3" placeholder="192.168.1.0/24\n10.0.0.0/8"></textarea>
</div>
<button class="btn btn-primary btn-sm" onclick="Policies.saveSection('network')">${_('common.save') || 'Save'}</button>
</div>
</div>
<!-- Update Policy -->
<div class="policy-card" data-section="update">
<div class="policy-card-header">
<div class="policy-card-icon"><span class="material-icons">system_update</span></div>
<div class="policy-card-info">
<h3>${_('policies.update_title') || 'Update Policy'}</h3>
<p>${_('policies.update_desc') || 'Control automatic updates and update channels'}</p>
</div>
<span class="policy-status-badge" id="upd-status"></span>
</div>
<div class="policy-card-body">
<div class="policy-toggle-group">
<div class="policy-toggle">
<label>${_('policies.auto_update') || 'Enable Auto-Update'}</label>
<input type="checkbox" id="pol-auto-update" class="toggle-switch" checked />
</div>
</div>
<div class="policy-field">
<label>${_('policies.update_channel') || 'Update Channel'}</label>
<select id="pol-update-channel" class="form-input">
<option value="stable">Stable</option>
<option value="beta">Beta</option>
<option value="dev">Dev</option>
</select>
</div>
<button class="btn btn-primary btn-sm" onclick="Policies.saveSection('update')">${_('common.save') || 'Save'}</button>
</div>
</div>
<!-- Policy Audit Log -->
<div class="policy-card" data-section="audit">
<div class="policy-card-header">
<div class="policy-card-icon"><span class="material-icons">history</span></div>
<div class="policy-card-info">
<h3>${_('policies.audit_title') || 'Policy Changes'}</h3>
<p>${_('policies.audit_desc') || 'Recent policy change history'}</p>
</div>
</div>
<div class="policy-card-body">
<div id="policy-audit-list" class="policy-audit-list">
<p class="text-muted">${_('policies.select_org_first') || 'Select an organization to view policy audit log.'}</p>
</div>
</div>
</div>
</div>
</div>
`
}) %>