Enhance module management: add inventory module support in settings, update templates and app logic for module access control, and extend tenant management script for module configuration

This commit is contained in:
2026-05-08 11:24:53 +02:00
parent 326fc4ef91
commit 5cb59f4b63
6 changed files with 199 additions and 32 deletions
+1
View File
@@ -9,3 +9,4 @@ INVENTAR_WORKERS=4
INVENTAR_THREADS=2 INVENTAR_THREADS=2
INVENTAR_WORKER_TIMEOUT=30 INVENTAR_WORKER_TIMEOUT=30
INVENTAR_WORKER_CONNECTIONS=100 INVENTAR_WORKER_CONNECTIONS=100
+46 -1
View File
@@ -401,12 +401,42 @@ def _is_library_module_path(path):
) )
return path.startswith(library_prefixes) return path.startswith(library_prefixes)
def _is_inventory_module_path(path):
"""Return True when the current request path belongs to the inventory module."""
if not path:
return False
inventory_prefixes = (
'/scanner',
'/inventory',
'/upload_admin',
'/manage_filters',
'/manage_locations',
'/admin_borrowings',
'/admin_damaged_items'
)
return path.startswith(inventory_prefixes)
@app.before_request
def _enforce_module_access():
endpoint = request.endpoint or ''
if endpoint == 'static' or endpoint.startswith('static') or request.path.startswith('/api/'):
return None
if not cfg.LIBRARY_MODULE_ENABLED and _is_library_module_path(request.path):
return "Bibliotheks-Modul ist deaktiviert.", 403
if not cfg.INVENTORY_MODULE_ENABLED and _is_inventory_module_path(request.path):
return "Inventar-Modul ist deaktiviert.", 403
def _get_current_module(path): def _get_current_module(path):
"""Resolve the active UI module for navbar separation.""" """Resolve the active UI module for navbar separation."""
if cfg.LIBRARY_MODULE_ENABLED and _is_library_module_path(path): if cfg.LIBRARY_MODULE_ENABLED and _is_library_module_path(path):
return 'library' return 'library'
return 'inventory' if cfg.INVENTORY_MODULE_ENABLED and _is_inventory_module_path(path):
return 'inventory'
# Default fallback:
return 'library' if cfg.LIBRARY_MODULE_ENABLED else 'inventory'
def _get_current_user_permissions(): def _get_current_user_permissions():
@@ -1118,6 +1148,7 @@ def inject_version():
'csrf_token': csrf_token, 'csrf_token': csrf_token,
'CURRENT_MODULE': current_module, 'CURRENT_MODULE': current_module,
'school_periods': SCHOOL_PERIODS, 'school_periods': SCHOOL_PERIODS,
'inventory_module_enabled': cfg.INVENTORY_MODULE_ENABLED,
'library_module_enabled': cfg.LIBRARY_MODULE_ENABLED, 'library_module_enabled': cfg.LIBRARY_MODULE_ENABLED,
'student_cards_module_enabled': cfg.STUDENT_CARDS_MODULE_ENABLED, 'student_cards_module_enabled': cfg.STUDENT_CARDS_MODULE_ENABLED,
'is_admin': is_admin, 'is_admin': is_admin,
@@ -2741,6 +2772,13 @@ def home():
if 'username' not in session: if 'username' not in session:
flash('Bitte mit registriertem Konto anmelden!', 'error') flash('Bitte mit registriertem Konto anmelden!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.INVENTORY_MODULE_ENABLED:
if cfg.LIBRARY_MODULE_ENABLED:
return redirect(url_for('library_view'))
else:
return "Weder Inventar- noch Bibliotheks-Modul sind aktiviert.", 403
elif not us.check_admin(session['username']): elif not us.check_admin(session['username']):
return render_template( return render_template(
'main.html', 'main.html',
@@ -2773,6 +2811,13 @@ def home_admin():
if 'username' not in session: if 'username' not in session:
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error') flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
if not cfg.INVENTORY_MODULE_ENABLED:
if cfg.LIBRARY_MODULE_ENABLED:
return redirect(url_for('library_admin'))
else:
return "Weder Inventar- noch Bibliotheks-Modul sind aktiviert.", 403
if not us.check_admin(session['username']): if not us.check_admin(session['username']):
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error') flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
+4
View File
@@ -74,6 +74,9 @@ DEFAULTS = {
"10": {"start": "16:00", "end": "16:45", "label": "10. Stunde (16:00 - 16:45)"} "10": {"start": "16:00", "end": "16:45", "label": "10. Stunde (16:00 - 16:45)"}
}, },
'modules': { 'modules': {
'inventory': {
'enabled': True
},
'library': { 'library': {
'enabled': False 'enabled': False
}, },
@@ -190,6 +193,7 @@ class _TenantAwareBool:
return f"_TenantAwareBool(module_name={self.module_name!r}, value={self.resolve()!r})" return f"_TenantAwareBool(module_name={self.module_name!r}, value={self.resolve()!r})"
INVENTORY_MODULE_ENABLED = _TenantAwareBool('inventory', _get(_conf, ['modules', 'inventory', 'enabled'], DEFAULTS['modules']['inventory']['enabled']))
LIBRARY_MODULE_ENABLED = _TenantAwareBool('library', _get(_conf, ['modules', 'library', 'enabled'], DEFAULTS['modules']['library']['enabled'])) LIBRARY_MODULE_ENABLED = _TenantAwareBool('library', _get(_conf, ['modules', 'library', 'enabled'], DEFAULTS['modules']['library']['enabled']))
STUDENT_CARDS_MODULE_ENABLED = _TenantAwareBool('student_cards', _get(_conf, ['modules', 'student_cards', 'enabled'], DEFAULTS['modules']['student_cards']['enabled'])) STUDENT_CARDS_MODULE_ENABLED = _TenantAwareBool('student_cards', _get(_conf, ['modules', 'student_cards', 'enabled'], DEFAULTS['modules']['student_cards']['enabled']))
STUDENT_DEFAULT_BORROW_DAYS = int(_get(_conf, ['modules', 'student_cards', 'default_borrow_days'], DEFAULTS['modules']['student_cards']['default_borrow_days'])) STUDENT_DEFAULT_BORROW_DAYS = int(_get(_conf, ['modules', 'student_cards', 'default_borrow_days'], DEFAULTS['modules']['student_cards']['default_borrow_days']))
+6 -2
View File
@@ -1010,14 +1010,18 @@
<div class="module-selector-bar" id="moduleBar"> <div class="module-selector-bar" id="moduleBar">
<span class="module-label">Module:</span> <span class="module-label">Module:</span>
<div class="module-tabs"> <div class="module-tabs">
{% if inventory_module_enabled %}
<a href="{{ url_for('home') }}" <a href="{{ url_for('home') }}"
class="module-tab {% if CURRENT_MODULE == 'inventory' %}active{% endif %}" class="module-tab {% if CURRENT_MODULE == 'inventory' %}active{% endif %}"
id="inventoryModule" id="inventoryModule"
data-module="inventory"> data-module="inventory">
📦 Inventarsystem 📦 Inventarsystem
</a> </a>
{% if library_module_enabled %} {% endif %}
{% if inventory_module_enabled and library_module_enabled %}
<div class="module-separator"></div> <div class="module-separator"></div>
{% endif %}
{% if library_module_enabled %}
<a href="{{ url_for('library_view') }}" <a href="{{ url_for('library_view') }}"
class="module-tab {% if CURRENT_MODULE == 'library' %}active{% endif %}" class="module-tab {% if CURRENT_MODULE == 'library' %}active{% endif %}"
id="libraryModule" id="libraryModule"
@@ -1028,7 +1032,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if CURRENT_MODULE != 'library' %} {% if CURRENT_MODULE != 'library' and inventory_module_enabled %}
<nav class="navbar navbar-expand-lg navbar-dark" id="inventoryNavbar"> <nav class="navbar navbar-expand-lg navbar-dark" id="inventoryNavbar">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('home') }}" data-icon="📦">Inventarsystem</a> <a class="navbar-brand" href="{{ url_for('home') }}" data-icon="📦">Inventarsystem</a>
+87 -29
View File
@@ -4,42 +4,44 @@
"ver": "0.6.44", "ver": "0.6.44",
"host": "0.0.0.0", "host": "0.0.0.0",
"port": 8000, "port": 8000,
"mongodb": { "mongodb": {
"host": "localhost", "host": "localhost",
"port": 27017, "port": 27017,
"db": "Inventarsystem" "db": "Inventarsystem"
}, },
"scheduler": { "scheduler": {
"enabled": true, "enabled": true,
"interval_minutes": 1, "interval_minutes": 1,
"backup_interval_hours": 24 "backup_interval_hours": 24
}, },
"ssl": { "ssl": {
"enabled": true, "enabled": true,
"cert": "Web/certs/cert.pem", "cert": "Web/certs/cert.pem",
"key": "Web/certs/key.pem" "key": "Web/certs/key.pem"
}, },
"images": { "images": {
"thumbnail_size": [150, 150], "thumbnail_size": [
"preview_size": [400, 400] 150,
150
],
"preview_size": [
400,
400
]
}, },
"upload": { "upload": {
"max_size_mb": 10, "max_size_mb": 10,
"image_max_size_mb": 15, "image_max_size_mb": 15,
"video_max_size_mb": 100 "video_max_size_mb": 100
}, },
"paths": { "paths": {
"backups": "backups", "backups": "backups",
"logs": "logs" "logs": "logs"
}, },
"modules": { "modules": {
"inventory": {
"enabled": true
},
"library": { "library": {
"enabled": true "enabled": true
}, },
@@ -49,27 +51,83 @@
"max_borrow_days": 365 "max_borrow_days": 365
} }
}, },
"tenants": {
"tenants": {}, },
"allowed_extensions": [ "allowed_extensions": [
"png", "jpg", "jpeg", "gif", "png",
"hevc", "heif", "jpg",
"mp4", "mov", "avi", "mkv", "webm", "jpeg",
"mp3", "wav", "ogg", "flac", "aac", "m4a", "opus", "gif",
"pdf", "docx", "xlsx", "pptx", "txt" "hevc",
"heif",
"mp4",
"mov",
"avi",
"mkv",
"webm",
"mp3",
"wav",
"ogg",
"flac",
"aac",
"m4a",
"opus",
"pdf",
"docx",
"xlsx",
"pptx",
"txt"
], ],
"schoolPeriods": { "schoolPeriods": {
"1": { "start": "08:00", "end": "08:45", "label": "1. Stunde (08:00 - 08:45)" }, "1": {
"2": { "start": "08:45", "end": "09:30", "label": "2. Stunde (08:45 - 09:30)" }, "start": "08:00",
"3": { "start": "09:45", "end": "10:30", "label": "3. Stunde (09:45 - 10:30)" }, "end": "08:45",
"4": { "start": "10:30", "end": "11:15", "label": "4. Stunde (10:30 - 11:15)" }, "label": "1. Stunde (08:00 - 08:45)"
"5": { "start": "11:30", "end": "12:15", "label": "5. Stunde (11:30 - 12:15)" }, },
"6": { "start": "12:15", "end": "13:00", "label": "6. Stunde (12:15 - 13:00)" }, "2": {
"7": { "start": "13:30", "end": "14:15", "label": "7. Stunde (13:30 - 14:15)" }, "start": "08:45",
"8": { "start": "14:15", "end": "15:00", "label": "8. Stunde (14:15 - 15:00)" }, "end": "09:30",
"9": { "start": "15:15", "end": "16:00", "label": "9. Stunde (15:15 - 16:00)" }, "label": "2. Stunde (08:45 - 09:30)"
"10": { "start": "16:00", "end": "16:45", "label": "10. Stunde (16:00 - 16:45)" } },
"3": {
"start": "09:45",
"end": "10:30",
"label": "3. Stunde (09:45 - 10:30)"
},
"4": {
"start": "10:30",
"end": "11:15",
"label": "4. Stunde (10:30 - 11:15)"
},
"5": {
"start": "11:30",
"end": "12:15",
"label": "5. Stunde (11:30 - 12:15)"
},
"6": {
"start": "12:15",
"end": "13:00",
"label": "6. Stunde (12:15 - 13:00)"
},
"7": {
"start": "13:30",
"end": "14:15",
"label": "7. Stunde (13:30 - 14:15)"
},
"8": {
"start": "14:15",
"end": "15:00",
"label": "8. Stunde (14:15 - 15:00)"
},
"9": {
"start": "15:15",
"end": "16:00",
"label": "9. Stunde (15:15 - 16:00)"
},
"10": {
"start": "16:00",
"end": "16:45",
"label": "10. Stunde (16:00 - 16:45)"
}
} }
} }
+55
View File
@@ -20,11 +20,13 @@ show_help() {
echo " restart-tenant <id> 'Restart' a single tenant (clears cache/sessions)" echo " restart-tenant <id> 'Restart' a single tenant (clears cache/sessions)"
echo " restart-all Restart all application containers (zero-downtime reload)" echo " restart-all Restart all application containers (zero-downtime reload)"
echo " list List active tenants" echo " list List active tenants"
echo " module <tenant_id> <module>=<on|off>... Enable/disable modules for a tenant"
echo " -h, --help Show this help message" echo " -h, --help Show this help message"
echo "" echo ""
echo "Examples:" echo "Examples:"
echo " ./manage-tenant.sh add school_a 10001" echo " ./manage-tenant.sh add school_a 10001"
echo " ./manage-tenant.sh remove test_tenant" echo " ./manage-tenant.sh remove test_tenant"
echo " ./manage-tenant.sh module school_a inventory=off library=on"
echo " ./manage-tenant.sh restart-all" echo " ./manage-tenant.sh restart-all"
echo " ./manage-tenant.sh -h" echo " ./manage-tenant.sh -h"
exit 1 exit 1
@@ -492,6 +494,59 @@ PY
fi fi
;; ;;
module)
if [ -z "$TENANT_ID" ] || [ -z "${3:-}" ]; then
echo "Error: Usage: manage-tenant.sh module <tenant_id> <module_name>=<on|off> [...]"
exit 1
fi
# Pass all remaining arguments to python script
shift 2
if python3 - "$CONFIG_FILE" "$TENANT_ID" "$@" <<'PY'
import json, sys, os
path = sys.argv[1]
tenant_id = sys.argv[2]
module_args = sys.argv[3:]
if not os.path.isfile(path):
print(f"Error: config file not found: {path}", file=sys.stderr)
sys.exit(1)
with open(path, 'r', encoding='utf-8') as f:
cfg = json.load(f)
tenants = cfg.setdefault('tenants', {})
tenant_cfg = tenants.setdefault(tenant_id, {})
if 'port' not in tenant_cfg:
print(f"Warning: Tenant {tenant_id} doesn't have a port mapping in config.json.", file=sys.stderr)
modules = tenant_cfg.setdefault('modules', {})
for arg in module_args:
if '=' not in arg:
print(f"Warning: Ignoring invalid argument format '{arg}'. Expected name=on|off.", file=sys.stderr)
continue
mod_name, state_str = arg.split('=', 1)
state = str(state_str).lower() in ('on', '1', 'true', 'yes')
module_cfg = modules.setdefault(mod_name, {})
module_cfg['enabled'] = state
print(f"Module '{mod_name}' set to '{'on' if state else 'off'}' for tenant '{tenant_id}'.")
with open(path, 'w', encoding='utf-8') as f:
json.dump(cfg, f, indent=4, ensure_ascii=False)
PY
then
echo "Module configurations updated successfully."
if [ -n "$(docker ps -qf 'name=app' | head -n 1)" ]; then
restart_app_container
fi
else
echo "Failed to update module configuration."
exit 1
fi
;;
*) *)
echo "Unknown command: $COMMAND" echo "Unknown command: $COMMAND"
show_help show_help