110 lines
4.4 KiB
HTML
110 lines
4.4 KiB
HTML
{% extends 'base.html' %}
|
||
{% block title %}Modules — Enterprise Digital Platform{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="settings-wrapper">
|
||
<div class="settings-header">
|
||
<h2 class="section-title">Settings</h2>
|
||
<div class="settings-tabs">
|
||
<a href="{{ url_for('settings.index') }}" class="tab-link">Users & Access</a>
|
||
<a href="{{ url_for('settings.api_keys') }}" class="tab-link">API Keys</a>
|
||
<a href="{{ url_for('settings.modules') }}" class="tab-link active">Modules</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-section">
|
||
<div class="section-toolbar">
|
||
<h3>Installed Modules</h3>
|
||
<span class="text-muted" style="font-size:0.875rem;">
|
||
Enable or disable services to control which applications run on this instance.
|
||
</span>
|
||
</div>
|
||
|
||
<div class="app-grid" style="margin-top:1.25rem;">
|
||
{% for ms in module_statuses %}
|
||
{% set app = ms.app %}
|
||
{% set on = ms.enabled %}
|
||
{% set alive = ms.running %}
|
||
<div class="app-card {% if not on %}app-card--locked{% endif %}"
|
||
style="--accent: {{ app.color }};">
|
||
<div class="app-card-header">
|
||
<span class="app-icon">{{ app.icon }}</span>
|
||
<span class="app-status">
|
||
<span class="status-dot {% if alive %}status-dot--active{% else %}status-dot--inactive{% endif %}"></span>
|
||
<span class="status-label">{% if alive %}running{% else %}stopped{% endif %}</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="app-card-body">
|
||
<div class="app-name">{{ app.name }}</div>
|
||
<div class="app-desc" style="margin-top:0.4rem;">{{ app.description }}</div>
|
||
</div>
|
||
|
||
<div class="app-card-footer" style="display:flex; align-items:center; justify-content:space-between; gap:1rem;">
|
||
{# Toggle switch — submits a hidden form on change #}
|
||
<label class="module-toggle" title="{% if on %}Disable {{ app.name }}{% else %}Enable {{ app.name }}{% endif %}">
|
||
<input
|
||
type="checkbox"
|
||
class="module-toggle-input"
|
||
data-app="{{ app.id }}"
|
||
data-action-enable="{{ url_for('settings.toggle_module', app_id=app.id) }}"
|
||
{% if on %}checked{% endif %}
|
||
/>
|
||
<span class="toggle-slider"></span>
|
||
<span class="toggle-label">{% if on %}Enabled{% else %}Disabled{% endif %}</span>
|
||
</label>
|
||
|
||
{# Hidden forms — one per action #}
|
||
<form id="form-enable-{{ app.id }}" method="POST"
|
||
action="{{ url_for('settings.toggle_module', app_id=app.id) }}" style="display:none;">
|
||
<input type="hidden" name="action" value="enable" />
|
||
</form>
|
||
<form id="form-disable-{{ app.id }}" method="POST"
|
||
action="{{ url_for('settings.toggle_module', app_id=app.id) }}" style="display:none;">
|
||
<input type="hidden" name="action" value="disable" />
|
||
</form>
|
||
|
||
{% if on and alive %}
|
||
<a href="{{ app.url }}" class="btn btn-sm btn-secondary" target="_blank">Open ↗</a>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<div class="form-inline-card" style="margin-top:1.5rem; display:flex; align-items:flex-start; gap:0.75rem;">
|
||
<span style="font-size:1.25rem; flex-shrink:0;">ℹ️</span>
|
||
<div style="font-size:0.875rem; color:var(--text-secondary); line-height:1.6;">
|
||
<strong style="color:var(--text-primary);">How modules work</strong><br/>
|
||
Disabling a module sends a stop signal to its process and prevents it from being proxied.
|
||
Enabling a module starts its process in the background.
|
||
Module state is preserved across page reloads and respected by
|
||
<code>./start-dev.sh</code> on next launch.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.querySelectorAll('.module-toggle-input').forEach(function(cb) {
|
||
cb.addEventListener('change', function() {
|
||
var appId = this.dataset.app;
|
||
var action = this.checked ? 'enable' : 'disable';
|
||
var confirmed = true;
|
||
if (action === 'disable') {
|
||
confirmed = confirm(
|
||
'Disable and stop this service?\n\n' +
|
||
'Users will lose access until it is re-enabled.'
|
||
);
|
||
}
|
||
if (confirmed) {
|
||
document.getElementById('form-' + action + '-' + appId).submit();
|
||
} else {
|
||
// Revert visual state without submitting
|
||
this.checked = !this.checked;
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|