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:
@@ -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
@@ -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'))
|
||||||
|
|||||||
@@ -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']))
|
||||||
|
|||||||
@@ -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
@@ -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)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user