- Convert invcount to self-contained module - Add Module Manager for install/uninstall - Create module_registry database table - Support hot-reloading of modules - Move data imports into invcount module - Update all templates and routes to new structure Version bumped to 0.16.0
182 lines
6.4 KiB
HTML
182 lines
6.4 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Module Manager - ScanLook{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container mt-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1><i class="fas fa-puzzle-piece"></i> Module Manager</h1>
|
|
<a href="{{ url_for('home') }}" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left"></i> Back to Home
|
|
</a>
|
|
</div>
|
|
|
|
<p class="lead">Install, uninstall, and manage ScanLook modules</p>
|
|
|
|
<div class="row">
|
|
{% for module in modules %}
|
|
<div class="col-md-6 col-lg-4 mb-4">
|
|
<div class="card h-100 {% if module.is_active %}border-success{% elif module.is_installed %}border-warning{% endif %}">
|
|
<div class="card-header {% if module.is_active %}bg-success text-white{% elif module.is_installed %}bg-warning text-dark{% else %}bg-light{% endif %}">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-cube"></i> {{ module.name }}
|
|
<span class="badge badge-secondary float-right">v{{ module.version }}</span>
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="card-text">{{ module.description }}</p>
|
|
|
|
<p class="mb-2">
|
|
<small class="text-muted">
|
|
<strong>Author:</strong> {{ module.author }}<br>
|
|
<strong>Module Key:</strong> <code>{{ module.module_key }}</code>
|
|
</small>
|
|
</p>
|
|
|
|
<div class="mt-3">
|
|
{% if module.is_installed and module.is_active %}
|
|
<span class="badge badge-success mb-2">
|
|
<i class="fas fa-check-circle"></i> Active
|
|
</span>
|
|
{% elif module.is_installed %}
|
|
<span class="badge badge-warning mb-2">
|
|
<i class="fas fa-pause-circle"></i> Installed (Inactive)
|
|
</span>
|
|
{% else %}
|
|
<span class="badge badge-secondary mb-2">
|
|
<i class="fas fa-times-circle"></i> Not Installed
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="card-footer bg-light">
|
|
{% if not module.is_installed %}
|
|
<button class="btn btn-primary btn-sm btn-block" onclick="installModule('{{ module.module_key }}')">
|
|
<i class="fas fa-download"></i> Install
|
|
</button>
|
|
{% elif module.is_active %}
|
|
<button class="btn btn-warning btn-sm btn-block mb-2" onclick="deactivateModule('{{ module.module_key }}')">
|
|
<i class="fas fa-pause"></i> Deactivate
|
|
</button>
|
|
<button class="btn btn-danger btn-sm btn-block" onclick="uninstallModule('{{ module.module_key }}')">
|
|
<i class="fas fa-trash"></i> Uninstall
|
|
</button>
|
|
{% else %}
|
|
<button class="btn btn-success btn-sm btn-block mb-2" onclick="activateModule('{{ module.module_key }}')">
|
|
<i class="fas fa-play"></i> Activate
|
|
</button>
|
|
<button class="btn btn-danger btn-sm btn-block" onclick="uninstallModule('{{ module.module_key }}')">
|
|
<i class="fas fa-trash"></i> Uninstall
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{% if not modules %}
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-info-circle"></i> No modules found in the <code>/modules</code> directory.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script>
|
|
function installModule(moduleKey) {
|
|
if (!confirm(`Install module "${moduleKey}"?\n\nThis will create database tables and activate the module.`)) {
|
|
return;
|
|
}
|
|
|
|
fetch(`/admin/modules/${moduleKey}/install`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(`✅ ${data.message}\n\nPlease reload the page.`);
|
|
location.reload();
|
|
} else {
|
|
alert(`❌ ${data.message}`);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert(`❌ Error: ${error}`);
|
|
});
|
|
}
|
|
|
|
function uninstallModule(moduleKey) {
|
|
if (!confirm(`⚠️ UNINSTALL module "${moduleKey}"?\n\nThis will DELETE all module data and cannot be undone!`)) {
|
|
return;
|
|
}
|
|
|
|
fetch(`/admin/modules/${moduleKey}/uninstall`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(`✅ ${data.message}\n\nPlease reload the page.`);
|
|
location.reload();
|
|
} else {
|
|
alert(`❌ ${data.message}`);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert(`❌ Error: ${error}`);
|
|
});
|
|
}
|
|
|
|
function activateModule(moduleKey) {
|
|
fetch(`/admin/modules/${moduleKey}/activate`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(`✅ ${data.message}\n\nPlease reload the page.`);
|
|
location.reload();
|
|
} else {
|
|
alert(`❌ ${data.message}`);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert(`❌ Error: ${error}`);
|
|
});
|
|
}
|
|
|
|
function deactivateModule(moduleKey) {
|
|
if (!confirm(`Deactivate module "${moduleKey}"?\n\nUsers will lose access until reactivated.`)) {
|
|
return;
|
|
}
|
|
|
|
fetch(`/admin/modules/${moduleKey}/deactivate`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(`✅ ${data.message}\n\nPlease reload the page.`);
|
|
location.reload();
|
|
} else {
|
|
alert(`❌ ${data.message}`);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert(`❌ Error: ${error}`);
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %} |