secubox-openwrt/secubox-tools/webui/templates/settings.html
CyberMind-FR 0d6aaa1111 feat(webui): add Project Hub workspace and remove Command Center glow effects
- Add complete Project Hub & Workspace Interface implementation
  - New data models: Project, ModuleKit, Workspace
  - 3 fixture projects (cybermind.fr, cybermood.eu, secubox-c3)
  - 4 module kits (Security, Network, Automation, Media)
  - Workspace routes with project switching and kit installation
  - 4 workspace tabs: Overview, Module Kits, Devices, Composer
  - New navigation item: Workspace (7th section)

- Remove all glowing effects from UI
  - Remove Command Center widget glow and backdrop blur
  - Remove device status indicator glow
  - Remove toggle button glow effects

- Extend DataStore with 13 new methods for workspace management
- Add 270+ lines of workspace-specific CSS with responsive layouts
- Create workspace templates and result partials

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 08:10:22 +01:00

568 lines
21 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends theme_template %}
{% block title %}Settings - SecuBox WebUI{% endblock %}
{% block content %}
<section class="panel">
<div class="panel-head">
<div>
<h2>⚙️ Settings</h2>
<p>Configure WebUI preferences and backend connections</p>
</div>
</div>
<form hx-post="/settings/save"
hx-target="#settings-result"
hx-swap="innerHTML"
style="display: flex; flex-direction: column; gap: 2rem; max-width: 600px;">
<!-- General Settings -->
<div class="settings-group">
<h3>General</h3>
<div class="form-field">
<label for="theme">Theme</label>
<select id="theme" name="theme" class="input">
{% for theme_key in available_themes %}
<option value="{{ theme_key }}" {% if settings.theme == theme_key %}selected{% endif %}>
{{ theme_key|title }}
</option>
{% endfor %}
</select>
</div>
<div class="form-field">
<label for="language">Language</label>
<select id="language" name="language" class="input">
<option value="en" {% if settings.language == 'en' %}selected{% endif %}>English</option>
<option value="fr" {% if settings.language == 'fr' %}selected{% endif %}>Français</option>
<option value="de" {% if settings.language == 'de' %}selected{% endif %}>Deutsch</option>
<option value="es" {% if settings.language == 'es' %}selected{% endif %}>Español</option>
</select>
</div>
</div>
<!-- Backend Configuration -->
<div class="settings-group" x-data="{ backendType: '{{ settings.backend_type }}' }">
<h3>Backend Connection</h3>
<div class="form-field">
<label for="backend_type">Backend Type</label>
<select id="backend_type" name="backend_type" class="input" x-model="backendType">
<option value="virtualized">Virtualized (Development)</option>
<option value="ssh">SSH Connection</option>
<option value="http">HTTP API</option>
</select>
</div>
<!-- Virtualized Backend Info -->
<div x-show="backendType === 'virtualized'" style="padding: 1rem; background: var(--bg-secondary); border-radius: 8px; margin-top: 0.5rem;">
<p class="muted">Running in virtualized mode. Commands are simulated locally using templates.</p>
</div>
<!-- HTTP Backend Configuration -->
<div x-show="backendType === 'http'" style="padding: 1rem; background: var(--bg-secondary); border-radius: 8px; margin-top: 0.5rem; display: flex; flex-direction: column; gap: 1rem;">
<p class="muted" style="margin-bottom: 0.5rem;">Configure connection to OpenWrt device via HTTP/RPC:</p>
<div class="form-field">
<label for="openwrt_protocol">Protocol</label>
<select id="openwrt_protocol" name="openwrt_protocol" class="input">
<option value="http" {% if settings.openwrt_connection and settings.openwrt_connection.get('protocol') == 'http' %}selected{% endif %}>HTTP</option>
<option value="https" {% if settings.openwrt_connection and settings.openwrt_connection.get('protocol') == 'https' %}selected{% endif %}>HTTPS</option>
</select>
</div>
<div class="form-field">
<label for="openwrt_host">Host / IP Address</label>
<input type="text" id="openwrt_host" name="openwrt_host" class="input"
value="{{ settings.openwrt_connection.get('host', '192.168.1.1') if settings.openwrt_connection else '192.168.1.1' }}"
placeholder="192.168.1.1">
</div>
<div class="form-field">
<label for="openwrt_port">Port</label>
<input type="number" id="openwrt_port" name="openwrt_port" class="input"
value="{{ settings.openwrt_connection.get('port', 80) if settings.openwrt_connection else 80 }}"
placeholder="80">
</div>
<div class="form-field">
<label for="openwrt_username">Username</label>
<input type="text" id="openwrt_username" name="openwrt_username" class="input"
value="{{ settings.openwrt_connection.get('username', 'root') if settings.openwrt_connection else 'root' }}"
placeholder="root">
</div>
<div class="form-field">
<label for="openwrt_password">Password</label>
<input type="password" id="openwrt_password" name="openwrt_password" class="input"
placeholder="Enter password">
<span class="muted" style="font-size: 0.85rem;">Password is stored in data/settings.json</span>
</div>
<button type="button" class="btn ghost"
hx-post="/api/backend/test"
hx-target="#settings-result">
🔌 Test Connection
</button>
</div>
<!-- SSH Backend Info -->
<div x-show="backendType === 'ssh'" style="padding: 1rem; background: var(--bg-secondary); border-radius: 8px; margin-top: 0.5rem;">
<p class="muted">SSH backend is not yet implemented. Please use HTTP or Virtualized backend.</p>
</div>
</div>
<!-- Preferences -->
<div class="settings-group">
<h3>Preferences</h3>
<div class="form-field">
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<input type="checkbox" name="auto_update" {% if settings.auto_update %}checked{% endif %}>
<span>Auto-update module catalog</span>
</label>
</div>
<div class="form-field">
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<input type="checkbox" name="notifications" {% if settings.notifications %}checked{% endif %}>
<span>Enable notifications</span>
</label>
</div>
<div class="form-field">
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<input type="checkbox" name="advanced_mode" {% if settings.advanced_mode %}checked{% endif %}>
<span>Advanced mode (show developer options)</span>
</label>
</div>
</div>
<!-- Actions -->
<div style="display: flex; gap: 1rem; padding-top: 1rem; border-top: 1px solid var(--border);">
<button type="submit" class="btn primary">💾 Save Settings</button>
<button type="button"
class="btn ghost"
hx-post="/settings/reset"
hx-target="#settings-result"
hx-confirm="Are you sure you want to reset all settings to defaults?">
🔄 Reset to Defaults
</button>
</div>
<!-- Result Container -->
<div id="settings-result"></div>
</form>
<!-- Device Management -->
<div style="margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--border);" x-data="{ showAddDevice: false }">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
<div>
<h3>🌐 Device Management</h3>
<p class="muted">Manage multiple OpenWrt devices and switch between them</p>
</div>
<button type="button" class="btn primary" @click="showAddDevice = true">
Add Device
</button>
</div>
<!-- Device Cards List -->
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 1.5rem;">
{% if devices %}
{% for device in devices %}
<div class="device-card {% if device.active %}device-active{% endif %}">
<div class="device-header">
<div style="display: flex; align-items: center; gap: 0.75rem;">
<span style="font-size: 1.5rem;">{{ device.emoji }}</span>
<div>
<strong>{{ device.name }}</strong>
{% if device.description %}
<div class="muted" style="font-size: 0.85rem;">{{ device.description }}</div>
{% endif %}
<div class="muted" style="font-size: 0.85rem; font-family: monospace;">
{{ device.host }}:{{ device.port }}
</div>
</div>
</div>
<div style="display: flex; align-items: center; gap: 0.5rem;">
{% if device.active %}
<span class="pill pill-stable">✓ Active</span>
{% endif %}
<span class="pill">{{ device.connection_type }}</span>
</div>
</div>
<div class="device-footer">
{% if not device.active %}
<button type="button" class="btn ghost small"
hx-post="/devices/{{ device.id }}/activate"
hx-target="#device-result-{{ device.id }}"
hx-swap="innerHTML">
▶️ Switch
</button>
{% endif %}
<button type="button" class="btn ghost small"
hx-post="/devices/{{ device.id }}/test"
hx-target="#device-result-{{ device.id }}"
hx-swap="innerHTML">
🔌 Test
</button>
<button type="button" class="btn ghost small"
hx-get="/devices/{{ device.id }}/edit"
hx-target="#device-modal-{{ device.id }}"
hx-swap="innerHTML">
✏️ Edit
</button>
{% if not device.active %}
<button type="button" class="btn ghost small danger"
hx-delete="/devices/{{ device.id }}"
hx-confirm="Are you sure you want to delete this device?"
hx-target="#device-result-{{ device.id }}"
hx-swap="innerHTML">
🗑️ Delete
</button>
{% endif %}
</div>
<!-- Result container for this device -->
<div id="device-result-{{ device.id }}"></div>
<!-- Modal container for this device -->
<div id="device-modal-{{ device.id }}"></div>
</div>
{% endfor %}
{% else %}
<div style="padding: 2rem; text-align: center; background: var(--bg-secondary); border-radius: 8px; border: 1px dashed var(--border);">
<p class="muted">No devices configured yet. Click "Add Device" to get started.</p>
</div>
{% endif %}
</div>
<!-- Add Device Modal -->
<div x-show="showAddDevice" x-cloak class="modal-overlay">
<div class="modal-content" @click.away="showAddDevice = false">
<div class="modal-header">
<h3> Add New Device</h3>
<button type="button" class="modal-close" @click="showAddDevice = false"></button>
</div>
<form hx-post="/devices/add"
hx-target="#add-device-result"
hx-swap="innerHTML"
@htmx:after-request="if(event.detail.successful) { showAddDevice = false; setTimeout(() => window.location.reload(), 1000); }"
style="padding: 1.5rem;">
<div class="form-group">
<label for="device-name">Device Name *</label>
<input type="text"
id="device-name"
name="device_name"
required
placeholder="My OpenWrt Router">
</div>
<div class="form-group">
<label for="device-emoji">Emoji</label>
<input type="text"
id="device-emoji"
name="device_emoji"
value="🌐"
placeholder="🌐"
maxlength="2">
</div>
<div class="form-group">
<label for="device-description">Description</label>
<input type="text"
id="device-description"
name="device_description"
placeholder="Home router on main floor">
</div>
<div class="form-group">
<label for="device-connection-type">Connection Type *</label>
<select id="device-connection-type" name="device_connection_type" required>
<option value="http" selected>HTTP API</option>
<option value="virtualized">Virtualized (Template Mode)</option>
<option value="ssh" disabled>SSH (Coming Soon)</option>
</select>
</div>
<div class="form-row">
<div class="form-group">
<label for="device-host">Host *</label>
<input type="text"
id="device-host"
name="device_host"
required
value="192.168.1.1"
placeholder="192.168.1.1">
</div>
<div class="form-group">
<label for="device-port">Port *</label>
<input type="number"
id="device-port"
name="device_port"
required
value="80"
min="1"
max="65535">
</div>
</div>
<div class="form-group">
<label for="device-protocol">Protocol *</label>
<select id="device-protocol" name="device_protocol" required>
<option value="http" selected>HTTP</option>
<option value="https">HTTPS</option>
</select>
</div>
<div class="form-group">
<label for="device-username">Username *</label>
<input type="text"
id="device-username"
name="device_username"
required
value="root"
placeholder="root">
</div>
<div class="form-group">
<label for="device-password">Password</label>
<input type="password"
id="device-password"
name="device_password"
placeholder="Enter device password">
</div>
<div id="add-device-result" style="margin-top: 1rem;"></div>
<div class="modal-footer">
<button type="button" class="btn secondary" @click="showAddDevice = false">
Cancel
</button>
<button type="submit" class="btn primary">
💾 Add Device
</button>
</div>
</form>
</div>
</div>
</div>
<!-- System Information -->
<div style="margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--border);">
<h3>System Information</h3>
<div style="display: grid; grid-template-columns: 200px 1fr; gap: 0.5rem; margin-top: 1rem; font-family: monospace; font-size: 0.9rem;">
<div class="muted">Backend:</div>
<div>{{ settings.backend_type }}</div>
<div class="muted">Theme:</div>
<div>{{ settings.theme }}</div>
<div class="muted">Advanced Mode:</div>
<div>{{ "Enabled" if settings.advanced_mode else "Disabled" }}</div>
</div>
</div>
</section>
<style>
.settings-group {
display: flex;
flex-direction: column;
gap: 1rem;
}
.form-field {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-field label {
font-weight: 500;
font-size: 0.95rem;
}
.input, select {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
font-size: 0.95rem;
}
.input:focus, select:focus {
outline: none;
border-color: var(--accent);
}
/* Device Management Styles */
.device-card {
padding: 1.25rem;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg-secondary);
transition: all 0.2s;
}
.device-card:hover {
border-color: var(--border-hover, var(--border));
}
.device-active {
border-left: 4px solid var(--accent);
background: linear-gradient(90deg, rgba(59, 130, 246, 0.05) 0%, var(--bg-secondary) 100%);
}
.device-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.device-footer {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.btn.small {
padding: 0.4rem 0.75rem;
font-size: 0.85rem;
}
.btn.danger:hover {
background: rgba(239, 68, 68, 0.1);
border-color: #ef4444;
color: #ef4444;
}
.pill {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 500;
background: var(--bg-tertiary, var(--bg));
border: 1px solid var(--border);
}
.pill-stable {
background: rgba(34, 197, 94, 0.1);
border-color: rgba(34, 197, 94, 0.3);
color: #22c55e;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(4px);
}
.modal-content {
background: var(--bg-primary, #1a1a1a);
border-radius: 12px;
padding: 0;
width: 90%;
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
border: 1px solid var(--border);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid var(--border);
}
.modal-header h3 {
margin: 0;
font-size: 1.25rem;
}
.modal-close {
background: none;
border: none;
color: var(--text-muted, #888);
font-size: 1.5rem;
cursor: pointer;
padding: 0;
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.2s;
}
.modal-close:hover {
background: var(--bg-secondary, #2a2a2a);
color: var(--text-primary, #fff);
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--text-primary, #fff);
}
.form-group input,
.form-group select {
width: 100%;
padding: 0.75rem;
background: var(--bg-secondary, #2a2a2a);
border: 1px solid var(--border, #333);
border-radius: 6px;
color: var(--text-primary, #fff);
font-size: 0.95rem;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: var(--accent, #3b82f6);
}
.form-row {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 1rem;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding-top: 1rem;
margin-top: 1rem;
border-top: 1px solid var(--border);
}
[x-cloak] {
display: none !important;
}
</style>
{% endblock %}