diff --git a/configs/general/general_catalogs b/configs/general/general_catalogs
index 95f3c47..a4ca06c 100644
--- a/configs/general/general_catalogs
+++ b/configs/general/general_catalogs
@@ -6,6 +6,7 @@
# in priority order 1..9. The official catalog (from the Release Host) is always
# source #1 and is NOT listed here. Third-party catalogs are UNVERIFIED β you are
# trusting that host. Edit these from the App Center's Catalog Sources block.
+CFG_CATALOG_OFFICIAL_ENABLED=true # Official Catalog - Include the official LibrePortal catalog (source #1) in the App Center. Disabling only hides its apps from browsing; system updates/hotfixes still use the official release host. [true:On|false:Off] **ADVANCED**
CFG_CATALOG_1= # Catalog 1 - Extra catalog base URL, e.g. https://catalog.example.org
CFG_CATALOG_2= # Catalog 2 - Extra catalog base URL
CFG_CATALOG_3= # Catalog 3 - Extra catalog base URL
diff --git a/containers/libreportal/frontend/components/admin/config/js/catalog-manager.js b/containers/libreportal/frontend/components/admin/config/js/catalog-manager.js
index 64626d4..0f0e4a9 100644
--- a/containers/libreportal/frontend/components/admin/config/js/catalog-manager.js
+++ b/containers/libreportal/frontend/components/admin/config/js/catalog-manager.js
@@ -25,15 +25,48 @@ class CatalogManager {
`;
}
+ // The pinned official row (source #1). URL comes from the generated catalog
+ // data; the hidden CFG_CATALOG_OFFICIAL_ENABLED input carries its on/off state
+ // so it saves with the rest on "Save Config".
+ _officialRow(base, enabled) {
+ const host = base ? base.replace(/^https?:\/\//, '').replace(/\/$/, '') : 'official catalog';
+ const off = enabled === false;
+ return `
+
+
+
+ β Official
+
+ ${this._esc(host)}${off ? ' (hidden from browse)' : ''}
+
+
- The official catalog is always included. Add third-party catalog URLs to browse more apps β third-party sources are unverified (you are trusting that host). Each is served with a signed /<channel>/index.json.
+ The official catalog is always included. Add third-party catalog URLs to browse more apps β third-party sources are unverified.
@@ -150,3 +183,44 @@ window.validateCatalogFormat = function (input) {
input.title = ok ? '' : 'Catalog must be an http(s):// URL';
return ok;
};
+
+// --- official catalog (source #1): shown pinned, removable only with a warning ---
+window.officialCatalogActionHTML = function (enabled) {
+ return enabled
+ ? ``
+ : ``;
+};
+
+window.deleteOfficialCatalog = function () {
+ const apply = () => window._setOfficialCatalog(false);
+ if (window.showConfirmation) {
+ window.showConfirmation(
+ 'Remove the official catalog?',
+ 'This hides the official LibrePortal catalogβs apps from the App Center. Your system updates and hotfixes are NOT affected β they always use the official release host. You can re-enable it any time. Click Save Config to apply.',
+ apply, 'Remove from browsing', 'Cancel', 'danger', true,
+ 'I understand this only hides official apps from browsing, not system updates'
+ );
+ } else if (window.confirm('Remove the official catalog from browsing?')) {
+ apply();
+ }
+};
+
+window.enableOfficialCatalog = function () { window._setOfficialCatalog(true); };
+
+window._setOfficialCatalog = function (enabled) {
+ const input = document.getElementById('config-CFG_CATALOG_OFFICIAL_ENABLED');
+ if (input) input.value = enabled ? 'true' : 'false';
+ const row = document.querySelector('[data-catalog-official]');
+ if (row) {
+ row.style.opacity = enabled ? '' : '.55';
+ const action = row.querySelector('[data-official-action]');
+ if (action) action.innerHTML = window.officialCatalogActionHTML(enabled);
+ const hint = row.querySelector('[data-official-hint]');
+ if (hint) hint.innerHTML = enabled ? '' : ' (hidden from browse)';
+ }
+ if (window.notificationSystem) {
+ window.notificationSystem.info(enabled
+ ? 'Official catalog will be re-enabled on Save.'
+ : 'Official catalog will be hidden on Save.');
+ }
+};
diff --git a/containers/libreportal/frontend/core/forms/css/config.css b/containers/libreportal/frontend/core/forms/css/config.css
index 4c81bba..11cba56 100644
--- a/containers/libreportal/frontend/core/forms/css/config.css
+++ b/containers/libreportal/frontend/core/forms/css/config.css
@@ -265,6 +265,20 @@
grid-auto-rows: minmax(min-content, max-content);
}
+/* Boxed content panel for a config section's body (the area after the divider,
+ before any action buttons). A darker inset inside the section card so the
+ options read as their own content section β matches the boxed look used
+ elsewhere. Applies to regular option grids and the block editors
+ (domains/catalog/whitelist). Scoped to the main config editor (not app-config). */
+.config-category:not(.app-config) .config-fields,
+.config-category:not(.app-config) .domain-building-blocks,
+.config-category:not(.app-config) .whitelist-building-blocks {
+ background: rgba(0, 0, 0, 0.20);
+ border: 1px solid var(--border-color);
+ border-radius: 10px;
+ padding: 16px 18px;
+}
+
/* Responsive column counts β colocated here (and not in style.css)
because the base 3-column rule above loads later than style.css,
so the unscoped base would otherwise win the cascade against the
diff --git a/containers/libreportal/frontend/core/overlays/js/confirmation-dialog.js b/containers/libreportal/frontend/core/overlays/js/confirmation-dialog.js
index c826a77..0f9a1b1 100755
--- a/containers/libreportal/frontend/core/overlays/js/confirmation-dialog.js
+++ b/containers/libreportal/frontend/core/overlays/js/confirmation-dialog.js
@@ -31,8 +31,8 @@ class ConfirmationDialog {
}
- show(title, message, onConfirm, confirmText = 'Confirm', cancelText = 'Cancel', confirmClass = 'primary', showDataLossCheckbox = false) {
-
+ show(title, message, onConfirm, confirmText = 'Confirm', cancelText = 'Cancel', confirmClass = 'primary', showDataLossCheckbox = false, checkboxText = 'I understand I will lose all my data and it cannot be undone') {
+
this.callback = onConfirm;
// Build dialog content
@@ -50,7 +50,7 @@ class ConfirmationDialog {
` : ''}
@@ -142,12 +142,12 @@ function initConfirmationDialog() {
// initConfirmationDialog() will be called centrally
// Global function
-window.showConfirmation = (title, message, onConfirm, confirmText, cancelText, confirmClass, showDataLossCheckbox) => {
+window.showConfirmation = (title, message, onConfirm, confirmText, cancelText, confirmClass, showDataLossCheckbox, checkboxText) => {
// Ensure dialog is initialized
initConfirmationDialog();
-
+
if (confirmationDialog) {
- confirmationDialog.show(title, message, onConfirm, confirmText, cancelText, confirmClass, showDataLossCheckbox);
+ confirmationDialog.show(title, message, onConfirm, confirmText, cancelText, confirmClass, showDataLossCheckbox, checkboxText);
} else {
// Fallback to native confirm
if (confirm(message)) {
diff --git a/scripts/catalog/catalog_sources.sh b/scripts/catalog/catalog_sources.sh
index 1bd91dc..6b8ebc0 100644
--- a/scripts/catalog/catalog_sources.sh
+++ b/scripts/catalog/catalog_sources.sh
@@ -40,26 +40,29 @@ catalogExtraSourcesJson() {
printf '%s' "$arr"
}
-# The full ordered source list as a JSON array. Each: {idx,name,base,channel,official,trust}.
+# The full ordered source list as a JSON array. Each:
+# {idx,name,base,channel,official,trust,enabled}. The official source can be
+# switched off for browsing via CFG_CATALOG_OFFICIAL_ENABLED (default on) β
+# extras are always enabled (an empty slot is simply absent).
catalogSourcesJson() {
- local off_base off_channel extras
+ local off_base off_channel extras off_enabled
off_base="$(lpReleaseBaseUrl)"; off_base="${off_base%/}"
off_channel="$(lpReleaseChannel)"
+ off_enabled="true"; [[ "${CFG_CATALOG_OFFICIAL_ENABLED:-true}" == "false" ]] && off_enabled="false"
extras="$(catalogExtraSourcesJson)"
- jq -cn --arg base "$off_base" --arg channel "$off_channel" --argjson extras "$extras" '
+ jq -cn --arg base "$off_base" --arg channel "$off_channel" --argjson oe "$off_enabled" --argjson extras "$extras" '
[ { idx: 1, name: "LibrePortal Official", base: $base, channel: $channel,
- official: true, trust: "official" } ]
- + ( $extras | map( . + { official: false, trust: "community" } ) )'
+ official: true, trust: "official", enabled: $oe } ]
+ + ( $extras | map( . + { official: false, trust: "community", enabled: true } ) )'
}
-# One source per line (compact JSON), priority order β for `while read`. Every
-# configured source is active (empty slots are skipped upstream).
-catalogEnabledSources() { catalogSourcesJson | jq -c '.[]'; }
+# One ENABLED source per line (compact JSON), priority order β for `while read`.
+catalogEnabledSources() { catalogSourcesJson | jq -c '.[] | select(.enabled)'; }
# Human-readable listing for the CLI.
catalogSourceList() {
catalogSourcesJson | jq -r '.[] |
- "[\(.idx)] \(.name) β \(.base)/\(.channel) (\(if .official then "official β" else "unverified" end))"'
+ "[\(.idx)] \(.name) β \(.base)/\(.channel) (\(if .official then "official β" else "unverified" end)\(if .enabled then "" else ", off" end))"'
}
# Fetch a THIRD-PARTY catalog index. Deliberately NOT verified against the