diff --git a/containers/libreportal/frontend/components/apps/core/js/config-form.js b/containers/libreportal/frontend/components/apps/core/js/config-form.js index e7cd194..00b167a 100644 --- a/containers/libreportal/frontend/components/apps/core/js/config-form.js +++ b/containers/libreportal/frontend/components/apps/core/js/config-form.js @@ -46,6 +46,18 @@ Object.assign(AppsManager.prototype, { }); } }, + // Config values arrive as the raw right-hand side of "KEY=value # comment". + // Most fields are written back without their comment, so this rarely + // mattered — but any field whose comment is REGENERATED keeps one + // (CFG__STORAGE carries the location it currently resolves to), and then + // a then matched no option and fell back to showing + // the field's default, so an app living on a second disk read as "Primary". + if (appData && appData.config) { + appData = Object.assign({}, appData, { + config: Object.fromEntries( + Object.entries(appData.config).map(([k, v]) => [k, this.stripInlineComment(v)]) + ) + }); + } + const cleanAppName = appData.command.split(' ').pop(); const requiresKey = Object.keys(appData.config || {}).find(k => k.endsWith('_REQUIRES_SERVICE')); @@ -361,6 +395,7 @@ Object.assign(AppsManager.prototype, { // Get current value or use default let fieldValue = cfgKey && appConfig.hasOwnProperty(cfgKey) ? appConfig[cfgKey] : (fieldConfig.default || ''); + fieldValue = this.stripInlineComment(fieldValue); fieldValue = this.applyContextualDefault(fieldKey, fieldValue, appData); const fieldHTML = await this.generateField(fieldKey, cfgKey, fieldValue, fieldConfig); if (fieldConfig.hideByDefault) { diff --git a/scripts/webui/data/generators/categories/webui_create_app_field_mappings.sh b/scripts/webui/data/generators/categories/webui_create_app_field_mappings.sh index 87632b6..197aa48 100755 --- a/scripts/webui/data/generators/categories/webui_create_app_field_mappings.sh +++ b/scripts/webui/data/generators/categories/webui_create_app_field_mappings.sh @@ -18,6 +18,28 @@ webuiCreateAppFieldMappings() { # Create temp file first, then atomic move local temp_file="$(mktemp)" local final_file="${output_dir}/apps-field-mappings.json" + + # Storage Location's choices are whatever is registered right now, so unlike + # every other select here they cannot be written into the static block + # below. Built once and substituted for the placeholder. + # + # This generator runs on the same regen that refreshes the storage view, so + # adding a drive makes it selectable without a further step — which is what + # was missing: the config file's own dropdown listed the locations, but the + # WebUI's editor only renders fields named in THIS file, so the field was + # invisible in the UI no matter what the config said. + local storage_options='[{"value": "default", "label": "Primary"}]' + if declare -f storageRoots >/dev/null 2>&1; then + local _opts='[{"value": "default", "label": "Primary"}]' _root _name + while IFS= read -r _root; do + [[ -z "$_root" ]] && continue + [[ "${_root%/}" == "$(primaryRoot 2>/dev/null)" ]] && continue + _name=$(storageLocationName "$_root" 2>/dev/null) || continue + [[ -z "$_name" || "$_name" == "default" ]] && continue + _opts="${_opts%]}, {\"value\": \"${_name}\", \"label\": \"${_name} (${_root})\"}]" + done < <(storageRoots 2>/dev/null) + storage_options="$_opts" + fi # Generate the complete JSON in one go cat > "$temp_file" << 'JSONEOF' @@ -88,6 +110,7 @@ webuiCreateAppFieldMappings() { }, JSONEOF + # Add PORT_1 through PORT_20 dynamically for i in {1..20}; do cat >> "$temp_file" << PORTEOF @@ -170,6 +193,14 @@ PORTEOF ], "default": "auto" }, + "STORAGE": { + "category": "general", + "label": "Storage Location", + "type": "select", + "tooltip": "Which drive this app's data lives on. Apps can each use a different one.", + "options": __STORAGE_OPTIONS__, + "default": "default" + }, "UPDATE_TYPE": { "category": "general", "label": "Updates", @@ -999,12 +1030,24 @@ RESTEOF # Substitute live-runtime placeholders the heredoc can't compute # (single-quoted heredoc → no $var expansion). Currently: - # __INSTALL_NAME__ → CFG_INSTALL_NAME + # __INSTALL_NAME__ → CFG_INSTALL_NAME + # __STORAGE_OPTIONS__ → the storage locations registered right now if [ $? -eq 0 ]; then local install_name_safe install_name_safe=$(printf '%s' "${CFG_INSTALL_NAME:-LibrePortal}" \ | sed -e 's/[\/&]/\\&/g' -e 's/"/\\"/g') sed -i "s|__INSTALL_NAME__|${install_name_safe}|g" "$temp_file" + # awk, not sed: the replacement is JSON and contains characters sed + # would treat as syntax. Never leave the placeholder behind — that is + # invalid JSON and the whole config editor fails to load. + # The default is assigned separately, not inline as ${x:-...}: that form + # ends at the first unescaped } and the replacement is JSON, so the rest + # of it leaked out as literal text and produced a stray "]}". + local _opts_json="$storage_options" + [[ -n "$_opts_json" ]] || _opts_json='[{"value": "default", "label": "Primary"}]' + awk -v opts="$_opts_json" \ + '{ gsub(/__STORAGE_OPTIONS__/, opts); print }' "$temp_file" > "${temp_file}.s" \ + && mv -f "${temp_file}.s" "$temp_file" runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file" else rm -f "$temp_file" 2>/dev/null