Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2bb015b80 | |||
| 35c4e9c6f6 | |||
| 3c5ee65b03 | |||
| 0edb87c347 | |||
| 678cc61cef | |||
| f26ab5c5fb | |||
| abb09fbdb5 | |||
| 96f5b9e3cd |
+1
-1
@@ -7116,7 +7116,7 @@ def register():
|
||||
name = (request.form.get('name') or '').strip()
|
||||
last_name = (request.form.get('last-name') or '').strip()
|
||||
|
||||
# Always generate username from abbreviation logic and auto-extend on collisions.
|
||||
# Generate a username from the first 2 letters of first and last name.
|
||||
username = us.build_unique_username_from_name(name, last_name)
|
||||
|
||||
permission_preset = (request.form.get('permission_preset') or 'standard_user').strip()
|
||||
|
||||
@@ -14,6 +14,7 @@ import os
|
||||
import json
|
||||
import atexit
|
||||
from threading import Lock
|
||||
from flask import has_request_context
|
||||
from pymongo import MongoClient as _PyMongoClient
|
||||
|
||||
# Base directory of this Web package
|
||||
@@ -250,8 +251,29 @@ class _MongoClientProxy:
|
||||
return getattr(self._client, name)
|
||||
|
||||
def __getitem__(self, name):
|
||||
if has_request_context():
|
||||
try:
|
||||
from tenant import get_tenant_context
|
||||
ctx = get_tenant_context()
|
||||
if ctx and ctx.db_name:
|
||||
if name == MONGODB_DB or name == MONGODB_DB.lower() or name == 'inventar_default':
|
||||
return self._client[ctx.db_name]
|
||||
except Exception:
|
||||
pass
|
||||
return self._client[name]
|
||||
|
||||
def get_database(self, name=None, *args, **kwargs):
|
||||
if has_request_context():
|
||||
try:
|
||||
from tenant import get_tenant_context
|
||||
ctx = get_tenant_context()
|
||||
if ctx and ctx.db_name:
|
||||
if name is None or name == MONGODB_DB or name == MONGODB_DB.lower() or name == 'inventar_default':
|
||||
return self._client.get_database(ctx.db_name, *args, **kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
return self._client.get_database(name, *args, **kwargs)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
|
||||
+106
-8
@@ -206,6 +206,82 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body.tutorial-tooltips-active [data-tutorial-tip] {
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
outline: 1px dashed rgba(59, 130, 246, 0.35);
|
||||
outline-offset: 2px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
body.tutorial-tooltips-active [data-tutorial-tip]::before {
|
||||
content: '💡';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -26px;
|
||||
transform: translateY(-50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
color: #0f172a;
|
||||
font-size: 0.75rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.tutorial-tooltips-active [data-tutorial-tip]::after {
|
||||
content: attr(data-tutorial-tip);
|
||||
position: absolute;
|
||||
top: calc(100% + 10px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(6px);
|
||||
min-width: 220px;
|
||||
max-width: 280px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
background: rgba(15, 23, 42, 0.92);
|
||||
color: #f8fafc;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 24px 48px rgba(15, 23, 42, 0.22);
|
||||
z-index: 9999;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
body.tutorial-tooltips-active [data-tutorial-tip]:hover::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateX(-50%) translateY(10px);
|
||||
}
|
||||
|
||||
.tutorial-tooltip-indicator {
|
||||
position: fixed;
|
||||
top: 92px;
|
||||
right: 16px;
|
||||
z-index: 2000;
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(56, 189, 248, 0.12);
|
||||
border: 1px solid rgba(56, 189, 248, 0.25);
|
||||
color: #0f172a;
|
||||
font-size: 0.95rem;
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
body.tutorial-tooltips-active .tutorial-tooltip-indicator {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* iPad/Tablet responsive adjustments */
|
||||
@media (max-width: 1024px) {
|
||||
.navbar .container-fluid {
|
||||
@@ -952,24 +1028,24 @@
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="inventoryNavList">
|
||||
{% if current_permissions.pages.get('home', True) %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
<a class="nav-link {% if current_path == url_for('home') %}nav-active{% endif %}" href="{{ url_for('home') }}">Artikel</a>
|
||||
<a class="nav-link {% if current_path == url_for('home') %}nav-active{% endif %}" href="{{ url_for('home') }}" data-tutorial-tip="Starten Sie hier mit dem Materialbestand und suchen Sie nach Artikeln.">Artikel</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if 'username' in session %}
|
||||
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Ausleihen</a>
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}" data-tutorial-tip="Ihre aktuellen Ausleihen und Rückgaben finden Sie hier.">Meine Ausleihen</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}">Tutorial</a>
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}" data-tutorial-tip="Öffnen Sie das Tutorial, um das System Schritt für Schritt kennenzulernen.">Tutorial</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if 'username' in session and current_permissions.pages.get('upload_admin', True) and current_permissions.actions.get('can_insert', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}">➕ Hochladen</a>
|
||||
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}" data-tutorial-tip="Hier können Sie neue Artikel ins Inventar einpflegen.">➕ Hochladen</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
@@ -1071,24 +1147,24 @@
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="libraryNavList">
|
||||
{% if current_permissions.pages.get('library_view', True) %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
<a class="nav-link {% if current_path == url_for('library_view') %}nav-active{% endif %}" href="{{ url_for('library_view') }}">Medien</a>
|
||||
<a class="nav-link {% if current_path == url_for('library_view') %}nav-active{% endif %}" href="{{ url_for('library_view') }}" data-tutorial-tip="Die Bibliothek zeigt Ihnen alle Medien und verfügbaren Bücher.">Medien</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if 'username' in session %}
|
||||
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Medien</a>
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}" data-tutorial-tip="Hier sehen Sie Ihre bibliotheksbezogenen Ausleihen.">Meine Medien</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}">Tutorial</a>
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}" data-tutorial-tip="Nutzen Sie das Tutorial, um die Bibliotheksfunktionen kennenzulernen.">Tutorial</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if 'username' in session and current_permissions.actions.get('can_insert', True) and current_permissions.pages.get('library_admin', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link nav-priority-link {% if current_path == url_for('library_admin') %}nav-active{% endif %}" href="{{ url_for('library_admin') }}">📖 Hochladen</a>
|
||||
<a class="nav-link nav-priority-link {% if current_path == url_for('library_admin') %}nav-active{% endif %}" href="{{ url_for('library_admin') }}" data-tutorial-tip="Neue Bibliotheksmedien können hier aufgenommen werden.">📖 Hochladen</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item" data-nav-fixed="true">
|
||||
@@ -1999,6 +2075,28 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
(function () {
|
||||
const username = {{ (session['username'] if 'username' in session else '')|tojson }};
|
||||
const tooltipModeKey = username ? ('inventarsystem_tutorial_tooltips_enabled_' + username) : 'inventarsystem_tutorial_tooltips_enabled';
|
||||
const enabled = localStorage.getItem(tooltipModeKey) === '1';
|
||||
|
||||
function applyTooltipMode(active) {
|
||||
document.body.classList.toggle('tutorial-tooltips-active', active);
|
||||
let indicator = document.getElementById('tutorialTooltipIndicator');
|
||||
if (!indicator) {
|
||||
indicator = document.createElement('div');
|
||||
indicator.id = 'tutorialTooltipIndicator';
|
||||
indicator.className = 'tutorial-tooltip-indicator';
|
||||
indicator.textContent = 'Tutorial-Modus aktiv: Tooltips sind eingeschaltet';
|
||||
document.body.appendChild(indicator);
|
||||
}
|
||||
indicator.style.display = active ? 'inline-flex' : 'none';
|
||||
}
|
||||
|
||||
applyTooltipMode(enabled);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -189,6 +189,38 @@
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tutorial-tooltip-toggle {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
border-radius: 18px;
|
||||
background: rgba(224, 242, 254, 0.9);
|
||||
border: 1px solid #bae6fd;
|
||||
}
|
||||
|
||||
.tutorial-tooltip-toggle label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.tutorial-tooltip-toggle input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
accent-color: #2563eb;
|
||||
}
|
||||
|
||||
.tooltip-description {
|
||||
margin: 0;
|
||||
color: #475569;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.workflow-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -230,6 +262,13 @@
|
||||
<span class="tutorial-pill">Format: Ruhig und geführt</span>
|
||||
<span class="tutorial-pill">Benutzer: {{ username }}</span>
|
||||
</div>
|
||||
<div class="tutorial-tooltip-toggle">
|
||||
<label for="toggleTutorialTooltips">
|
||||
<input type="checkbox" id="toggleTutorialTooltips" />
|
||||
Tutorial-Tooltips für das gesamte System aktivieren
|
||||
</label>
|
||||
<p class="tooltip-description">Wenn aktiviert, erscheinen auf allen Seiten erklärende Hinweise und Icons für die wichtigsten Navigationselemente.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="tutorial-grid">
|
||||
@@ -438,8 +477,32 @@
|
||||
renderStep();
|
||||
});
|
||||
|
||||
const tooltipToggle = document.getElementById('toggleTutorialTooltips');
|
||||
const tooltipModeKey = 'inventarsystem_tutorial_tooltips_enabled_{{ username }}';
|
||||
|
||||
function setTooltipMode(enabled) {
|
||||
localStorage.setItem(tooltipModeKey, enabled ? '1' : '0');
|
||||
document.body.classList.toggle('tutorial-tooltips-active', enabled);
|
||||
}
|
||||
|
||||
function loadTooltipMode() {
|
||||
const saved = localStorage.getItem(tooltipModeKey);
|
||||
const enabled = saved === '1';
|
||||
if (tooltipToggle) {
|
||||
tooltipToggle.checked = enabled;
|
||||
}
|
||||
setTooltipMode(enabled);
|
||||
}
|
||||
|
||||
if (tooltipToggle) {
|
||||
tooltipToggle.addEventListener('change', function () {
|
||||
setTooltipMode(this.checked);
|
||||
});
|
||||
}
|
||||
|
||||
loadState();
|
||||
renderStep();
|
||||
loadTooltipMode();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
+92
-66
@@ -13,6 +13,8 @@ Provides methods for creating, validating, and retrieving user information.
|
||||
import hashlib
|
||||
import copy
|
||||
import re
|
||||
import secrets
|
||||
import string
|
||||
from bson.objectid import ObjectId
|
||||
import settings as cfg
|
||||
from settings import MongoClient
|
||||
@@ -43,24 +45,33 @@ def _clean_name_fragment(value):
|
||||
return cleaned
|
||||
|
||||
|
||||
def _get_tenant_db(client):
|
||||
"""Return the current tenant database for the request, or fall back to default."""
|
||||
try:
|
||||
from tenant import get_tenant_db
|
||||
return get_tenant_db(client)
|
||||
except Exception:
|
||||
return client[cfg.MONGODB_DB]
|
||||
|
||||
|
||||
def build_name_synonym(first_name, last_name=''):
|
||||
"""Build a deterministic, non-personalized short alias like 'SimFri'."""
|
||||
"""Build a deterministic, non-personalized short alias from 2 letters each."""
|
||||
first = _clean_name_fragment(first_name)
|
||||
last = _clean_name_fragment(last_name)
|
||||
|
||||
if first and last:
|
||||
return (first[:3] + last[:3]).title()
|
||||
return (first[:2] + last[:2]).title()
|
||||
|
||||
combined = (first + last)
|
||||
if not combined:
|
||||
return 'User'
|
||||
return combined[:6].title()
|
||||
return combined[:4].title()
|
||||
|
||||
|
||||
def build_username_from_name(first_name, last_name=''):
|
||||
"""
|
||||
Build a deterministic username abbreviation from first and last name.
|
||||
Uses the same short alias logic (e.g. SimFri) and stores it lowercase.
|
||||
Uses 2 letters from each name and stores it lowercase.
|
||||
|
||||
Args:
|
||||
first_name (str): First name
|
||||
@@ -75,32 +86,23 @@ def build_username_from_name(first_name, last_name=''):
|
||||
|
||||
def build_unique_username_from_name(first_name, last_name=''):
|
||||
"""
|
||||
Build a unique username based on the abbreviation logic.
|
||||
If a collision occurs, increase the username by one additional letter
|
||||
from the combined cleaned name until it is unique.
|
||||
Build a unique username from the first 2 letters of the first name and
|
||||
the first 2 letters of the last name.
|
||||
"""
|
||||
first = _clean_name_fragment(first_name)
|
||||
last = _clean_name_fragment(last_name)
|
||||
combined = (first + last).lower()
|
||||
base_username = (first[:2] + last[:2]).lower()
|
||||
|
||||
base_username = build_username_from_name(first_name, last_name)
|
||||
if not combined:
|
||||
combined = base_username or 'user'
|
||||
if not base_username:
|
||||
base_username = 'user'
|
||||
|
||||
start_len = len(base_username) if base_username else min(6, len(combined))
|
||||
start_len = max(1, min(start_len, len(combined)))
|
||||
if not get_user(base_username):
|
||||
return base_username
|
||||
|
||||
# Main strategy: take one more letter on each collision.
|
||||
for length in range(start_len, len(combined) + 1):
|
||||
candidate = combined[:length]
|
||||
if not get_user(candidate):
|
||||
return candidate
|
||||
|
||||
# Fallback if full combined name is already taken repeatedly.
|
||||
suffix = 2
|
||||
while get_user(f"{combined}{suffix}"):
|
||||
while get_user(f"{base_username}{suffix}"):
|
||||
suffix += 1
|
||||
return f"{combined}{suffix}"
|
||||
return f"{base_username}{suffix}"
|
||||
|
||||
|
||||
ACTION_PERMISSION_KEYS = (
|
||||
@@ -310,7 +312,7 @@ def update_user_permissions(username, preset_key, action_permissions=None, page_
|
||||
}
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
result = users.update_one({'Username': username}, {'$set': update_data})
|
||||
|
||||
@@ -325,7 +327,7 @@ def update_user_permissions(username, preset_key, action_permissions=None, page_
|
||||
def get_favorites(username):
|
||||
"""Return a list of favorite item ObjectId strings for the user."""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
user = users.find_one({'Username': username}) or users.find_one({'username': username})
|
||||
client.close()
|
||||
@@ -339,7 +341,7 @@ def add_favorite(username, item_id):
|
||||
"""Add an item to user's favorites (idempotent)."""
|
||||
try:
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
users.update_one(
|
||||
{'$or': [{'Username': username}, {'username': username}]},
|
||||
@@ -354,7 +356,7 @@ def remove_favorite(username, item_id):
|
||||
"""Remove an item from user's favorites."""
|
||||
try:
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
users.update_one(
|
||||
{'$or': [{'Username': username}, {'username': username}]},
|
||||
@@ -418,25 +420,29 @@ def check_nm_pwd(username, password):
|
||||
dict: User document if credentials are valid, None otherwise
|
||||
"""
|
||||
db_name = cfg.MONGODB_DB
|
||||
tenant_db = None
|
||||
try:
|
||||
from tenant import get_tenant_context
|
||||
ctx = get_tenant_context()
|
||||
if ctx and ctx.tenant_id:
|
||||
db_name = ctx.db_name or ctx.resolve_tenant()
|
||||
tenant_db = ctx.db_name or ctx.resolve_tenant()
|
||||
db_name = tenant_db
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[db_name]
|
||||
users = db['users']
|
||||
user = users.find_one({'Username': username}) or users.find_one({'username': username})
|
||||
client.close()
|
||||
try:
|
||||
hashed_password = hashing(password)
|
||||
|
||||
if not user:
|
||||
return None
|
||||
def find_user_in_db(database_name):
|
||||
db = client[database_name]
|
||||
users = db['users']
|
||||
return users.find_one({'Username': username, 'Password': hashed_password}) or users.find_one({'username': username, 'Password': hashed_password})
|
||||
|
||||
return find_user_in_db(db_name)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
if user.get('Password') == hashing(password):
|
||||
return user
|
||||
return None
|
||||
|
||||
|
||||
@@ -463,7 +469,7 @@ def add_user(
|
||||
bool: True if user was added successfully, False if password was too weak
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
if not check_password_strength(password):
|
||||
return False
|
||||
@@ -513,7 +519,7 @@ def student_card_exists(student_card_id):
|
||||
if not normalized:
|
||||
return False
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
exists = users.find_one({'StudentCardId': normalized}) is not None
|
||||
client.close()
|
||||
@@ -526,7 +532,7 @@ def get_user_by_student_card(student_card_id):
|
||||
if not normalized:
|
||||
return None
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
found_user = users.find_one({'StudentCardId': normalized})
|
||||
client.close()
|
||||
@@ -544,11 +550,13 @@ def make_admin(username):
|
||||
bool: True if user was promoted successfully
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
users.update_one({'Username': username}, {'$set': {'Admin': True}})
|
||||
result = users.update_one({'Username': username}, {'$set': {'Admin': True}})
|
||||
if result.matched_count == 0:
|
||||
result = users.update_one({'username': username}, {'$set': {'Admin': True}})
|
||||
client.close()
|
||||
return True
|
||||
return result.matched_count > 0
|
||||
|
||||
def remove_admin(username):
|
||||
"""
|
||||
@@ -561,11 +569,13 @@ def remove_admin(username):
|
||||
bool: True if user was demoted successfully
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
users.update_one({'Username': username}, {'$set': {'Admin': False}})
|
||||
result = users.update_one({'Username': username}, {'$set': {'Admin': False}})
|
||||
if result.matched_count == 0:
|
||||
result = users.update_one({'username': username}, {'$set': {'Admin': False}})
|
||||
client.close()
|
||||
return True
|
||||
return result.matched_count > 0
|
||||
|
||||
def get_user(username):
|
||||
"""
|
||||
@@ -578,11 +588,31 @@ def get_user(username):
|
||||
dict: User document or None if not found
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
users = db['users']
|
||||
users_return = users.find_one({'Username': username})
|
||||
client.close()
|
||||
return users_return
|
||||
try:
|
||||
def find_in_db(database_name):
|
||||
db = client[database_name]
|
||||
users = db['users']
|
||||
return users.find_one({'Username': username}) or users.find_one({'username': username})
|
||||
|
||||
# Try current tenant first when available
|
||||
try:
|
||||
from tenant import get_tenant_context
|
||||
ctx = get_tenant_context()
|
||||
if ctx and ctx.db_name:
|
||||
user = find_in_db(ctx.db_name)
|
||||
if user:
|
||||
return user
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback to default configured database
|
||||
user = find_in_db(cfg.MONGODB_DB)
|
||||
if user:
|
||||
return user
|
||||
|
||||
return None
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def check_admin(username):
|
||||
@@ -595,12 +625,8 @@ def check_admin(username):
|
||||
Returns:
|
||||
bool: True if user is an administrator, False otherwise
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
users = db['users']
|
||||
user = users.find_one({'Username': username})
|
||||
client.close()
|
||||
return user and user.get('Admin', False)
|
||||
user = get_user(username)
|
||||
return bool(user and user.get('Admin', False))
|
||||
|
||||
|
||||
def update_active_ausleihung(username, id_item, ausleihung):
|
||||
@@ -616,7 +642,7 @@ def update_active_ausleihung(username, id_item, ausleihung):
|
||||
bool: True if successful
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
users.update_one({'Username': username}, {'$set': {'active_ausleihung': {'Item': id_item, 'Ausleihung': ausleihung}}})
|
||||
client.close()
|
||||
@@ -634,7 +660,7 @@ def get_active_ausleihung(username):
|
||||
dict: Active borrowing information or None
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
user = users.find_one({'Username': username})
|
||||
return user['active_ausleihung']
|
||||
@@ -652,7 +678,7 @@ def has_active_borrowing(username):
|
||||
"""
|
||||
try:
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
|
||||
user = users.find_one({'username': username})
|
||||
@@ -683,14 +709,14 @@ def delete_user(username):
|
||||
bool: True if user was deleted successfully, False otherwise
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
result = users.delete_one({'username': username})
|
||||
client.close()
|
||||
if result.deleted_count == 0:
|
||||
# Try with different field name
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
result = users.delete_one({'Username': username})
|
||||
client.close()
|
||||
@@ -712,7 +738,7 @@ def update_active_borrowing(username, item_id, status):
|
||||
"""
|
||||
try:
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
result = users.update_one(
|
||||
{'username': username},
|
||||
@@ -745,7 +771,7 @@ def get_name(username):
|
||||
str: String of name
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
user = users.find_one({'Username': username})
|
||||
name = user.get("name")
|
||||
@@ -760,7 +786,7 @@ def get_last_name(username):
|
||||
str: String of last_name
|
||||
"""
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
user = users.find_one({'Username': username})
|
||||
name = user.get("last_name")
|
||||
@@ -777,7 +803,7 @@ def get_all_users():
|
||||
"""
|
||||
try:
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
all_users = list(users.find())
|
||||
client.close()
|
||||
@@ -801,7 +827,7 @@ def update_password(username, new_password):
|
||||
return False
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
|
||||
# Hash the new password
|
||||
@@ -834,7 +860,7 @@ def update_user_name(username, name, last_name):
|
||||
try:
|
||||
name_alias = build_name_synonym(name, last_name)
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
db = client[cfg.MONGODB_DB]
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
|
||||
result = users.update_one(
|
||||
|
||||
@@ -106,11 +106,16 @@ services:
|
||||
INVENTAR_DELETED_ARCHIVE_FOLDER: /data/deleted-archives
|
||||
|
||||
# Performance Tuning
|
||||
INVENTAR_WORKER_CLASS: gevent
|
||||
INVENTAR_WORKERS: "4"
|
||||
INVENTAR_THREADS: "2"
|
||||
INVENTAR_WORKER_TIMEOUT: "30"
|
||||
INVENTAR_WORKER_CONNECTIONS: "100"
|
||||
INVENTAR_WORKER_CLASS: ${INVENTAR_WORKER_CLASS:-gevent}
|
||||
INVENTAR_WORKERS: "${INVENTAR_WORKERS:-4}"
|
||||
INVENTAR_THREADS: "${INVENTAR_THREADS:-2}"
|
||||
INVENTAR_WORKER_TIMEOUT: "${INVENTAR_WORKER_TIMEOUT:-30}"
|
||||
INVENTAR_WORKER_CONNECTIONS: "${INVENTAR_WORKER_CONNECTIONS:-100}"
|
||||
|
||||
# Auto-scaled runtime settings
|
||||
INVENTAR_APP_CPUS: ${INVENTAR_APP_CPUS:-1.0}
|
||||
INVENTAR_APP_MEM_LIMIT: ${INVENTAR_APP_MEM_LIMIT:-256m}
|
||||
INVENTAR_APP_MEM_SWAP_LIMIT: ${INVENTAR_APP_MEM_SWAP_LIMIT:-512m}
|
||||
|
||||
# Multi-Tenant
|
||||
INVENTAR_MULTITENANT_ENABLED: "true"
|
||||
@@ -131,9 +136,9 @@ services:
|
||||
# Resource Limits (per instance)
|
||||
# With 10 instances: each gets ~150MB, totaling ~1.5GB
|
||||
# Adjust based on server resources
|
||||
mem_limit: 256m
|
||||
memswap_limit: 512m
|
||||
cpus: "1.0"
|
||||
mem_limit: "${INVENTAR_APP_MEM_LIMIT:-256m}"
|
||||
memswap_limit: "${INVENTAR_APP_MEM_SWAP_LIMIT:-512m}"
|
||||
cpus: "${INVENTAR_APP_CPUS:-1.0}"
|
||||
|
||||
# Health check for load balancer
|
||||
healthcheck:
|
||||
|
||||
@@ -504,12 +504,71 @@ ensure_min_docker_disk_space() {
|
||||
fi
|
||||
}
|
||||
|
||||
detect_server_capacity() {
|
||||
local cpus mem_kb mem_gb app_workers app_cpus app_mem app_mem_swap
|
||||
|
||||
cpus="$(nproc 2>/dev/null || echo 1)"
|
||||
mem_kb="$(awk '/MemAvailable:/ {print $2; exit}' /proc/meminfo 2>/dev/null || awk '/MemTotal:/ {print $2; exit}' /proc/meminfo || echo 0)"
|
||||
mem_gb=$(( (mem_kb + 1024*1024 - 1) / (1024*1024) ))
|
||||
|
||||
if [ "$cpus" -ge 8 ] && [ "$mem_gb" -ge 16 ]; then
|
||||
app_workers=8
|
||||
app_cpus="2.0"
|
||||
app_mem="512m"
|
||||
app_mem_swap="1024m"
|
||||
elif [ "$cpus" -ge 4 ] && [ "$mem_gb" -ge 8 ]; then
|
||||
app_workers=4
|
||||
app_cpus="1.0"
|
||||
app_mem="384m"
|
||||
app_mem_swap="768m"
|
||||
elif [ "$cpus" -ge 2 ] && [ "$mem_gb" -ge 4 ]; then
|
||||
app_workers=2
|
||||
app_cpus="0.75"
|
||||
app_mem="320m"
|
||||
app_mem_swap="640m"
|
||||
else
|
||||
app_workers=1
|
||||
app_cpus="0.5"
|
||||
app_mem="256m"
|
||||
app_mem_swap="512m"
|
||||
fi
|
||||
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
local env_app_cpus env_app_mem env_app_mem_swap env_workers
|
||||
env_app_cpus="$(awk -F= '/^INVENTAR_APP_CPUS=/{print $2; exit}' "$ENV_FILE" | tr -d ' ' || true)"
|
||||
env_app_mem="$(awk -F= '/^INVENTAR_APP_MEM_LIMIT=/{print $2; exit}' "$ENV_FILE" | tr -d ' ' || true)"
|
||||
env_app_mem_swap="$(awk -F= '/^INVENTAR_APP_MEM_SWAP_LIMIT=/{print $2; exit}' "$ENV_FILE" | tr -d ' ' || true)"
|
||||
env_workers="$(awk -F= '/^INVENTAR_WORKERS=/{print $2; exit}' "$ENV_FILE" | tr -d ' ' || true)"
|
||||
|
||||
[ -n "$env_app_cpus" ] && app_cpus="$env_app_cpus"
|
||||
[ -n "$env_app_mem" ] && app_mem="$env_app_mem"
|
||||
[ -n "$env_app_mem_swap" ] && app_mem_swap="$env_app_mem_swap"
|
||||
[ -n "$env_workers" ] && app_workers="$env_workers"
|
||||
fi
|
||||
|
||||
INVENTAR_APP_CPUS="${INVENTAR_APP_CPUS:-$app_cpus}"
|
||||
INVENTAR_APP_MEM_LIMIT="${INVENTAR_APP_MEM_LIMIT:-$app_mem}"
|
||||
INVENTAR_APP_MEM_SWAP_LIMIT="${INVENTAR_APP_MEM_SWAP_LIMIT:-$app_mem_swap}"
|
||||
INVENTAR_WORKERS="${INVENTAR_WORKERS:-$app_workers}"
|
||||
INVENTAR_THREADS="${INVENTAR_THREADS:-2}"
|
||||
|
||||
echo "Detected host capacity: CPUs=$cpus, RAM=${mem_gb}GB"
|
||||
echo "Configured app runtime: INVENTAR_WORKERS=$INVENTAR_WORKERS, INVENTAR_APP_CPUS=$INVENTAR_APP_CPUS, INVENTAR_APP_MEM_LIMIT=$INVENTAR_APP_MEM_LIMIT"
|
||||
}
|
||||
|
||||
write_env_file() {
|
||||
cat > "$ENV_FILE" <<EOF
|
||||
NUITKA_BUILD=$NUITKA_BUILD_VALUE
|
||||
INVENTAR_HTTP_PORT=$HTTP_PORT_VALUE
|
||||
INVENTAR_HTTP_PORTS=${HTTP_PORTS_VALUE// /,}
|
||||
INVENTAR_APP_IMAGE=$APP_IMAGE_VALUE
|
||||
INVENTAR_APP_CPUS=$INVENTAR_APP_CPUS
|
||||
INVENTAR_APP_MEM_LIMIT=$INVENTAR_APP_MEM_LIMIT
|
||||
INVENTAR_APP_MEM_SWAP_LIMIT=$INVENTAR_APP_MEM_SWAP_LIMIT
|
||||
INVENTAR_WORKERS=$INVENTAR_WORKERS
|
||||
INVENTAR_THREADS=$INVENTAR_THREADS
|
||||
INVENTAR_WORKER_TIMEOUT=${INVENTAR_WORKER_TIMEOUT:-30}
|
||||
INVENTAR_WORKER_CONNECTIONS=${INVENTAR_WORKER_CONNECTIONS:-100}
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -518,7 +577,7 @@ write_runtime_compose_override() {
|
||||
services:
|
||||
app:
|
||||
working_dir: /app/Web
|
||||
command: ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "2", "--timeout", "30", "--graceful-timeout", "20", "--max-requests", "200", "--max-requests-jitter", "50", "--log-level", "info", "--access-logfile", "-", "--error-logfile", "-"]
|
||||
command: ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "${INVENTAR_WORKERS:-2}", "--threads", "${INVENTAR_THREADS:-2}", "--timeout", "${INVENTAR_WORKER_TIMEOUT:-30}", "--graceful-timeout", "20", "--worker-connections", "${INVENTAR_WORKER_CONNECTIONS:-100}", "--max-requests", "200", "--max-requests-jitter", "50", "--log-level", "info", "--access-logfile", "-", "--error-logfile", "-"]
|
||||
image: ${APP_IMAGE_VALUE}
|
||||
build: null
|
||||
EOF
|
||||
@@ -595,6 +654,7 @@ configure_nuitka_mode
|
||||
resolve_app_image
|
||||
configure_host_ports
|
||||
ensure_min_docker_disk_space
|
||||
detect_server_capacity
|
||||
ensure_app_image_loaded
|
||||
write_env_file
|
||||
write_runtime_compose_override
|
||||
|
||||
Reference in New Issue
Block a user