Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3c114e4b5 | |||
| 170a2a53ab | |||
| a8ac08d5b0 |
+1
-1
@@ -32,4 +32,4 @@ RUN if [ "$NUITKA_BUILD" = "1" ]; then \
|
|||||||
WORKDIR /app/Web
|
WORKDIR /app/Web
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "3", "--timeout", "60", "--graceful-timeout", "20", "--max-requests", "1000", "--max-requests-jitter", "100", "--log-level", "info", "--access-logfile", "-", "--error-logfile", "-"]
|
CMD ["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", "-"]
|
||||||
|
|||||||
+95
-7
@@ -2950,6 +2950,7 @@ def get_items():
|
|||||||
available_only = str(request.args.get('available_only', '')).strip().lower() in ('1', 'true', 'yes', 'on')
|
available_only = str(request.args.get('available_only', '')).strip().lower() in ('1', 'true', 'yes', 'on')
|
||||||
offset_raw = request.args.get('offset')
|
offset_raw = request.args.get('offset')
|
||||||
limit_raw = request.args.get('limit')
|
limit_raw = request.args.get('limit')
|
||||||
|
light_mode_param = str(request.args.get('light_mode', '')).strip().lower()
|
||||||
pagination_requested = offset_raw is not None or limit_raw is not None
|
pagination_requested = offset_raw is not None or limit_raw is not None
|
||||||
|
|
||||||
if pagination_requested:
|
if pagination_requested:
|
||||||
@@ -2966,6 +2967,13 @@ def get_items():
|
|||||||
offset = 0
|
offset = 0
|
||||||
limit = None
|
limit = None
|
||||||
|
|
||||||
|
if light_mode_param in ('1', 'true', 'yes', 'on'):
|
||||||
|
light_mode = True
|
||||||
|
elif light_mode_param in ('0', 'false', 'no', 'off'):
|
||||||
|
light_mode = False
|
||||||
|
else:
|
||||||
|
light_mode = (offset == 0)
|
||||||
|
|
||||||
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||||
db = client[MONGODB_DB]
|
db = client[MONGODB_DB]
|
||||||
items_col = db['items']
|
items_col = db['items']
|
||||||
@@ -2977,18 +2985,97 @@ def get_items():
|
|||||||
|
|
||||||
total_count = items_col.count_documents(base_query)
|
total_count = items_col.count_documents(base_query)
|
||||||
|
|
||||||
items_cur = items_col.find(base_query).sort([('Name', 1), ('_id', 1)])
|
light_projection = {
|
||||||
|
'Name': 1,
|
||||||
|
'Code_4': 1,
|
||||||
|
'Images': 1,
|
||||||
|
'ThumbnailInfo': 1,
|
||||||
|
'Verfuegbar': 1,
|
||||||
|
'Filter': 1,
|
||||||
|
'Filter2': 1,
|
||||||
|
'Filter3': 1,
|
||||||
|
'Ort': 1,
|
||||||
|
'User': 1,
|
||||||
|
'ItemType': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Full projection: all details for detailed view
|
||||||
|
full_projection = {
|
||||||
|
'Name': 1,
|
||||||
|
'Ort': 1,
|
||||||
|
'Beschreibung': 1,
|
||||||
|
'Filter': 1,
|
||||||
|
'Filter2': 1,
|
||||||
|
'Filter3': 1,
|
||||||
|
'Code_4': 1,
|
||||||
|
'Images': 1,
|
||||||
|
'ThumbnailInfo': 1,
|
||||||
|
'Verfuegbar': 1,
|
||||||
|
'User': 1,
|
||||||
|
'BorrowerInfo': 1,
|
||||||
|
'appointments': 1,
|
||||||
|
'BlockedNow': 1,
|
||||||
|
'Reservierbar': 1,
|
||||||
|
'DamageReports': 1,
|
||||||
|
'ISBN': 1,
|
||||||
|
'Author': 1,
|
||||||
|
'Autor': 1,
|
||||||
|
'Anschaffungsjahr': 1,
|
||||||
|
'Anschaffungskosten': 1,
|
||||||
|
'Condition': 1,
|
||||||
|
'HasDamage': 1,
|
||||||
|
'ItemType': 1,
|
||||||
|
'SeriesGroupId': 1,
|
||||||
|
'LastUpdated': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
parent_projection = light_projection if light_mode else full_projection
|
||||||
|
|
||||||
|
items_cur = items_col.find(base_query, parent_projection).sort([('Name', 1), ('_id', 1)])
|
||||||
if pagination_requested:
|
if pagination_requested:
|
||||||
items_cur = items_cur.skip(offset).limit(limit)
|
items_cur = items_cur.skip(offset).limit(limit)
|
||||||
|
|
||||||
items = []
|
parent_items = list(items_cur)
|
||||||
for itm in items_cur:
|
parent_ids = [str(item.get('_id')) for item in parent_items if item.get('_id') is not None]
|
||||||
item_id_str = str(itm['_id'])
|
|
||||||
grouped_children = list(items_col.find({
|
children_by_parent = {}
|
||||||
'ParentItemId': item_id_str,
|
if parent_ids:
|
||||||
|
# Light mode: minimal child data for counting only
|
||||||
|
light_child_projection = {
|
||||||
|
'_id': 1,
|
||||||
|
'ParentItemId': 1,
|
||||||
|
'Code_4': 1,
|
||||||
|
'Verfuegbar': 1,
|
||||||
|
'Name': 1,
|
||||||
|
}
|
||||||
|
# Full mode: complete child data with all details
|
||||||
|
full_child_projection = {
|
||||||
|
'_id': 1,
|
||||||
|
'ParentItemId': 1,
|
||||||
|
'Code_4': 1,
|
||||||
|
'Verfuegbar': 1,
|
||||||
|
'Name': 1,
|
||||||
|
'Images': 1,
|
||||||
|
'ThumbnailInfo': 1,
|
||||||
|
'Beschreibung': 1,
|
||||||
|
}
|
||||||
|
child_projection = light_child_projection if light_mode else full_child_projection
|
||||||
|
|
||||||
|
child_cursor = items_col.find({
|
||||||
|
'ParentItemId': {'$in': parent_ids},
|
||||||
'IsGroupedSubItem': True,
|
'IsGroupedSubItem': True,
|
||||||
'Deleted': {'$ne': True},
|
'Deleted': {'$ne': True},
|
||||||
}))
|
}, child_projection)
|
||||||
|
for child in child_cursor:
|
||||||
|
parent_id = str(child.get('ParentItemId') or '')
|
||||||
|
if not parent_id:
|
||||||
|
continue
|
||||||
|
children_by_parent.setdefault(parent_id, []).append(child)
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for itm in parent_items:
|
||||||
|
item_id_str = str(itm['_id'])
|
||||||
|
grouped_children = children_by_parent.get(item_id_str, [])
|
||||||
grouped_count = 1 + len(grouped_children)
|
grouped_count = 1 + len(grouped_children)
|
||||||
|
|
||||||
grouped_units = [itm] + grouped_children
|
grouped_units = [itm] + grouped_children
|
||||||
@@ -3028,6 +3115,7 @@ def get_items():
|
|||||||
'limit': limit if limit is not None else count,
|
'limit': limit if limit is not None else count,
|
||||||
'count': count,
|
'count': count,
|
||||||
'total': total_count,
|
'total': total_count,
|
||||||
|
'light_mode': light_mode,
|
||||||
'has_more': pagination_requested and ((offset + count) < total_count)
|
'has_more': pagination_requested and ((offset + count) < total_count)
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -728,6 +728,7 @@
|
|||||||
let mainItemsNextOffset = 0;
|
let mainItemsNextOffset = 0;
|
||||||
let mainItemsHasMore = false;
|
let mainItemsHasMore = false;
|
||||||
let mainItemsLoadingMore = false;
|
let mainItemsLoadingMore = false;
|
||||||
|
let mainItemsLightMode = true; // Track if we're in light mode to gradually request full data
|
||||||
let mainItemsObserver = null;
|
let mainItemsObserver = null;
|
||||||
let mainItemsSentinel = null;
|
let mainItemsSentinel = null;
|
||||||
let mainItemsLoadingIndicator = null;
|
let mainItemsLoadingIndicator = null;
|
||||||
@@ -788,7 +789,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadItems(offset = 0, append = false) {
|
function loadItems(offset = 0, append = false) {
|
||||||
return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ITEMS_PAGE_SIZE}`)
|
// Für Pages nach der ersten: Explizit vollständige Daten laden (light_mode=false)
|
||||||
|
// Erste Page: light_mode wird automatisch enablet
|
||||||
|
const lightModeParam = offset > 0 ? '&light_mode=false' : '';
|
||||||
|
return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ITEMS_PAGE_SIZE}${lightModeParam}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const itemsContainer = document.querySelector('#items-container');
|
const itemsContainer = document.querySelector('#items-container');
|
||||||
|
|||||||
@@ -415,13 +415,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bulk-delete-row {
|
.bulk-delete-row {
|
||||||
display: flex;
|
display: none;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bulk-delete-mode-active .bulk-delete-row {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.bulk-delete-toggle {
|
.bulk-delete-toggle {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -3056,6 +3060,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
|||||||
bulkDeleteDrawerOpen = Boolean(open);
|
bulkDeleteDrawerOpen = Boolean(open);
|
||||||
const drawer = document.getElementById('bulk-delete-drawer');
|
const drawer = document.getElementById('bulk-delete-drawer');
|
||||||
const toggle = document.getElementById('bulk-delete-drawer-toggle');
|
const toggle = document.getElementById('bulk-delete-drawer-toggle');
|
||||||
|
const itemsContainer = document.querySelector('#items-container');
|
||||||
if (drawer) {
|
if (drawer) {
|
||||||
drawer.classList.toggle('open', bulkDeleteDrawerOpen);
|
drawer.classList.toggle('open', bulkDeleteDrawerOpen);
|
||||||
drawer.setAttribute('aria-hidden', bulkDeleteDrawerOpen ? 'false' : 'true');
|
drawer.setAttribute('aria-hidden', bulkDeleteDrawerOpen ? 'false' : 'true');
|
||||||
@@ -3065,6 +3070,10 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
|||||||
toggle.innerHTML = bulkDeleteDrawerOpen ? '<span class="drawer-icon">✕</span>' : '<span class="drawer-icon">⚙</span>';
|
toggle.innerHTML = bulkDeleteDrawerOpen ? '<span class="drawer-icon">✕</span>' : '<span class="drawer-icon">⚙</span>';
|
||||||
toggle.title = bulkDeleteDrawerOpen ? 'Massenlöschung schließen' : 'Massenlöschung öffnen';
|
toggle.title = bulkDeleteDrawerOpen ? 'Massenlöschung schließen' : 'Massenlöschung öffnen';
|
||||||
}
|
}
|
||||||
|
// Toggle checkbox visibility: show checkboxes only when bulk delete mode is active
|
||||||
|
if (itemsContainer) {
|
||||||
|
itemsContainer.classList.toggle('bulk-delete-mode-active', bulkDeleteDrawerOpen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleBulkDeleteDrawer() {
|
function toggleBulkDeleteDrawer() {
|
||||||
@@ -3310,6 +3319,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
|||||||
let mainAdminItemsNextOffset = 0;
|
let mainAdminItemsNextOffset = 0;
|
||||||
let mainAdminItemsHasMore = false;
|
let mainAdminItemsHasMore = false;
|
||||||
let mainAdminItemsLoadingMore = false;
|
let mainAdminItemsLoadingMore = false;
|
||||||
|
let mainAdminLightMode = true; // Track if we're in light mode to gradually request full data
|
||||||
let mainAdminItemsObserver = null;
|
let mainAdminItemsObserver = null;
|
||||||
let mainAdminItemsSentinel = null;
|
let mainAdminItemsSentinel = null;
|
||||||
let mainAdminItemsLoadingIndicator = null;
|
let mainAdminItemsLoadingIndicator = null;
|
||||||
@@ -3371,7 +3381,10 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadItems(offset = 0, append = false) {
|
function loadItems(offset = 0, append = false) {
|
||||||
return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ADMIN_ITEMS_PAGE_SIZE}`)
|
// Für Pages nach der ersten: Explizit vollständige Daten laden (light_mode=false)
|
||||||
|
// Erste Page: light_mode wird automatisch enablet
|
||||||
|
const lightModeParam = offset > 0 ? '&light_mode=false' : '';
|
||||||
|
return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ADMIN_ITEMS_PAGE_SIZE}${lightModeParam}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const itemsContainer = document.querySelector('#items-container');
|
const itemsContainer = document.querySelector('#items-container');
|
||||||
|
|||||||
Reference in New Issue
Block a user