Compare commits

...

3 Commits

9 changed files with 222 additions and 0 deletions
+2
View File
@@ -203,6 +203,7 @@ jobs:
expose:
- "8000"
volumes:
- ./config.json:/app/config.json:ro
- app_uploads:/app/Web/uploads
- app_thumbnails:/app/Web/thumbnails
- app_previews:/app/Web/previews
@@ -225,6 +226,7 @@ jobs:
cp stop.sh release-bundle/stop.sh
cp restart.sh release-bundle/restart.sh
cp backup.sh release-bundle/backup.sh
cp config.json release-bundle/config.json
cp update.sh release-bundle/update.sh
cp init-admin.sh release-bundle/init-admin.sh
Binary file not shown.
+48
View File
@@ -713,6 +713,42 @@ def sanitize_form_value(value):
return value
FILTER_SELECT_ALL_TOKEN = '__ALL__'
def expand_filter_selection(selected_values, filter_num):
"""Expand special filter token into all predefined values for a filter."""
if not isinstance(selected_values, list):
return []
has_select_all = False
cleaned_values = []
seen = set()
for raw_value in selected_values:
value = str(raw_value).strip() if raw_value is not None else ''
if not value:
continue
if value == FILTER_SELECT_ALL_TOKEN:
has_select_all = True
continue
if value not in seen:
cleaned_values.append(value)
seen.add(value)
if not has_select_all:
return cleaned_values
predefined_values = it.get_predefined_filter_values(filter_num)
for predefined_value in predefined_values:
value = str(predefined_value).strip() if predefined_value is not None else ''
if value and value not in seen:
cleaned_values.append(value)
seen.add(value)
return cleaned_values
def normalize_isbn(isbn_raw):
"""Return only digits/X from ISBN input in canonical uppercase form."""
if isbn_raw is None:
@@ -2638,6 +2674,10 @@ def upload_item():
flash('Fehler beim Verarbeiten der Formulardaten. Bitte versuchen Sie es erneut.', 'error')
return redirect(url_for('home_admin'))
# Expand special "all values" selections for predefined filters.
filter_upload = expand_filter_selection(filter_upload, 1)
filter_upload2 = expand_filter_selection(filter_upload2, 2)
# Validation
if not name or not ort or not beschreibung:
error_msg = 'Bitte füllen Sie alle erforderlichen Felder aus'
@@ -3683,6 +3723,10 @@ def edit_item(id):
filter1 = sanitize_form_value(request.form.getlist('filter'))
filter2 = sanitize_form_value(request.form.getlist('filter2'))
filter3 = sanitize_form_value(request.form.getlist('filter3'))
# Expand special "all values" selections for predefined filters.
filter1 = expand_filter_selection(filter1, 1)
filter2 = expand_filter_selection(filter2, 2)
anschaffungs_jahr = sanitize_form_value(request.form.get('anschaffungsjahr'))
anschaffungs_kosten = sanitize_form_value(request.form.get('anschaffungskosten'))
@@ -5906,6 +5950,10 @@ def add_filter_value(filter_num):
if not value:
flash('Bitte geben Sie einen Wert ein', 'error')
return redirect(url_for('manage_filters'))
if value == FILTER_SELECT_ALL_TOKEN:
flash('Dieser Wert ist reserviert und kann nicht als Filterwert gespeichert werden.', 'error')
return redirect(url_for('manage_filters'))
# Add the value to the filter
success = it.add_predefined_filter_value(filter_num, value)
+57
View File
@@ -1923,6 +1923,42 @@
applyFilters();
}
function bulkToggleFilterOptions(filterNum, shouldSelect) {
const filterKey = `filter${filterNum}`;
const checkboxes = Array.from(document.querySelectorAll(`#filter${filterNum}-options input[type="checkbox"]`));
if (checkboxes.length === 0) {
return;
}
const nextValues = new Set(activeFilters[filterKey]);
checkboxes.forEach(checkbox => {
const value = checkbox.value;
const group = checkbox.dataset.group || '';
const filterValue = group ? `${group}:${value}` : value;
checkbox.checked = shouldSelect;
if (shouldSelect) {
nextValues.add(filterValue);
} else {
nextValues.delete(filterValue);
}
});
activeFilters[filterKey] = Array.from(nextValues);
updateSelectedFilters(filterNum);
if (filterNum === 1) {
rebuildFilter2Options();
rebuildFilter3Options();
} else if (filterNum === 2) {
rebuildFilter3Options();
}
saveFilterState();
applyFilters();
}
function populateFilterOptions(containerId, filterValues, filterNum) {
const container = document.getElementById(containerId);
if (!container) {
@@ -1932,6 +1968,27 @@
container.innerHTML = ''; // Clear previous options
const bulkActions = document.createElement('div');
bulkActions.style.display = 'flex';
bulkActions.style.gap = '8px';
bulkActions.style.marginBottom = '10px';
const selectAllBtn = document.createElement('button');
selectAllBtn.type = 'button';
selectAllBtn.className = 'clear-filter';
selectAllBtn.textContent = 'Alle wählen';
selectAllBtn.onclick = () => bulkToggleFilterOptions(filterNum, true);
const clearAllBtn = document.createElement('button');
clearAllBtn.type = 'button';
clearAllBtn.className = 'clear-filter';
clearAllBtn.textContent = 'Alle abwählen';
clearAllBtn.onclick = () => bulkToggleFilterOptions(filterNum, false);
bulkActions.appendChild(selectAllBtn);
bulkActions.appendChild(clearAllBtn);
container.appendChild(bulkActions);
// First group values by category/prefix if they exist
const groupedValues = {};
+62
View File
@@ -2847,6 +2847,11 @@ document.addEventListener('DOMContentLoaded', ()=>{
while (select.children.length > 1) {
select.removeChild(select.lastChild);
}
const allOption = document.createElement('option');
allOption.value = '__ALL__';
allOption.textContent = '-- Alle auswählen --';
select.appendChild(allOption);
// Add new options - data.values contains the array
data.values.forEach(filter => {
@@ -4401,6 +4406,42 @@ document.addEventListener('DOMContentLoaded', ()=>{
applyFilters();
}
function bulkToggleFilterOptions(filterNum, shouldSelect) {
const filterKey = `filter${filterNum}`;
const checkboxes = Array.from(document.querySelectorAll(`#filter${filterNum}-options input[type="checkbox"]`));
if (checkboxes.length === 0) {
return;
}
const nextValues = new Set(activeFilters[filterKey]);
checkboxes.forEach(checkbox => {
const value = checkbox.value;
const group = checkbox.dataset.group || '';
const filterValue = group ? `${group}:${value}` : value;
checkbox.checked = shouldSelect;
if (shouldSelect) {
nextValues.add(filterValue);
} else {
nextValues.delete(filterValue);
}
});
activeFilters[filterKey] = Array.from(nextValues);
updateSelectedFiltersDisplay(filterNum);
if (filterNum === 1) {
rebuildFilter2Options();
rebuildFilter3Options();
} else if (filterNum === 2) {
rebuildFilter3Options();
}
saveFilterState();
applyFilters();
}
function populateFilterOptions(containerId, filterValues, filterNum) {
const container = document.getElementById(containerId);
if (!container) {
@@ -4408,6 +4449,27 @@ document.addEventListener('DOMContentLoaded', ()=>{
}
container.innerHTML = '';
const bulkActions = document.createElement('div');
bulkActions.style.display = 'flex';
bulkActions.style.gap = '8px';
bulkActions.style.marginBottom = '10px';
const selectAllBtn = document.createElement('button');
selectAllBtn.type = 'button';
selectAllBtn.className = 'clear-filter';
selectAllBtn.textContent = 'Alle wählen';
selectAllBtn.onclick = () => bulkToggleFilterOptions(filterNum, true);
const clearAllBtn = document.createElement('button');
clearAllBtn.type = 'button';
clearAllBtn.className = 'clear-filter';
clearAllBtn.textContent = 'Alle abwählen';
clearAllBtn.onclick = () => bulkToggleFilterOptions(filterNum, false);
bulkActions.appendChild(selectAllBtn);
bulkActions.appendChild(clearAllBtn);
container.appendChild(bulkActions);
const groupedValues = {};
+8
View File
@@ -1114,9 +1114,17 @@
while (select.children.length > 1) {
select.removeChild(select.lastChild);
}
const allOption = document.createElement('option');
allOption.value = '__ALL__';
allOption.textContent = '-- Alle auswählen --';
select.appendChild(allOption);
// Add new options - data.values contains the array
data.values.forEach(value => {
if (!value || String(value).trim() === '') {
return;
}
const option = document.createElement('option');
option.value = value;
option.textContent = value;
+1
View File
@@ -42,6 +42,7 @@ services:
expose:
- "8000"
volumes:
- ./config.json:/app/config.json:ro
- ./Web:/app/Web
- app_uploads:/app/Web/uploads
- app_thumbnails:/app/Web/thumbnails
+39
View File
@@ -290,6 +290,44 @@ EOF
fi
}
ensure_runtime_config_json() {
local config_path backup_path
config_path="$SCRIPT_DIR/config.json"
if [ -d "$config_path" ]; then
backup_path="${config_path}.dir.$(date +%Y%m%d-%H%M%S).bak"
mv "$config_path" "$backup_path"
echo "Warning: moved unexpected directory $config_path to $backup_path"
fi
if [ ! -f "$config_path" ]; then
cat > "$config_path" <<'EOF'
{
"ver": "2.6.5",
"dbg": false,
"host": "0.0.0.0",
"port": 443,
"mongodb": {
"host": "mongodb",
"port": 27017,
"db": "Inventarsystem"
},
"modules": {
"library": {
"enabled": false
},
"student_cards": {
"enabled": false,
"default_borrow_days": 14,
"max_borrow_days": 365
}
}
}
EOF
echo "Created default runtime config at $config_path"
fi
}
ensure_app_image_loaded() {
if docker image inspect "$APP_IMAGE_VALUE" >/dev/null 2>&1; then
return 0
@@ -558,6 +596,7 @@ ensure_runtime_dependencies
setup_boot_autostart_service
ensure_tls_certificates
ensure_nginx_config_mount_source
ensure_runtime_config_json
setup_scheduled_jobs
configure_nuitka_mode
resolve_app_image
+5
View File
@@ -325,6 +325,11 @@ download_and_extract_bundle() {
cp -f "$tmp_dir/update.sh" "$PROJECT_DIR/update.sh"
fi
if [ ! -f "$PROJECT_DIR/config.json" ] && [ -f "$tmp_dir/config.json" ]; then
cp -f "$tmp_dir/config.json" "$PROJECT_DIR/config.json"
log_message "Installed default config.json from release bundle"
fi
chmod +x "$PROJECT_DIR/start.sh" "$PROJECT_DIR/stop.sh" "$PROJECT_DIR/restart.sh" "$PROJECT_DIR/update.sh" "$PROJECT_DIR/backup.sh"
}