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 0f0e4a9..45ba8f4 100644 --- a/containers/libreportal/frontend/components/admin/config/js/catalog-manager.js +++ b/containers/libreportal/frontend/components/admin/config/js/catalog-manager.js @@ -25,20 +25,21 @@ 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". + // The pinned official row (source #1). Renders the official URL in a read-only + // input — same element (and height) as the third-party rows — with the badge + // overlaid. 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 url = base || ''; const off = enabled === false; return ` -
-
-
- ✓ Official - - ${this._esc(host)}${off ? ' (hidden from browse)' : ''} - +
+
+
+ ✓ Official +
${window.officialCatalogActionHTML(!off)} @@ -46,10 +47,14 @@ class CatalogManager {
`; } - async _officialBase() { + // Official catalog URL = the Release Host (the official catalog and the release + // host are the same). Read from the loaded config; falls back to the generated + // catalog data's source.base if for some reason it isn't present. + _officialBase() { try { - const r = await fetch('/data/apps/generated/registry_catalog.json', { cache: 'no-store' }); - if (r.ok) { const j = await r.json(); return (j.source && j.source.base) || ''; } + const cfg = window.configData && window.configData.config; + const v = cfg && cfg.CFG_RELEASE_BASE_URL && cfg.CFG_RELEASE_BASE_URL.value; + if (v) return String(v).replace(/\/+$/, ''); } catch (_) {} return ''; } @@ -64,7 +69,7 @@ class CatalogManager { const officialItem = configItems.find(i => i.key === 'CFG_CATALOG_OFFICIAL_ENABLED') || {}; const officialEnabled = String(officialItem.value == null ? 'true' : officialItem.value) !== 'false'; - const officialBase = await this._officialBase(); + const officialBase = this._officialBase(); let blocks = this._officialRow(officialBase, officialEnabled); withContent.forEach(key => { @@ -82,13 +87,8 @@ class CatalogManager {
-
-
- 🛈 -
- The official catalog is always included. Add third-party catalog URLs to browse more apps — third-party sources are unverified. -
-
+
+ 🛈The official catalog is always included. Add third-party catalog URLs to browse more apps — third-party sources are unverified.
${blocks} @@ -212,11 +212,11 @@ window._setOfficialCatalog = function (enabled) { if (input) input.value = enabled ? 'true' : 'false'; const row = document.querySelector('[data-catalog-official]'); if (row) { - row.style.opacity = enabled ? '' : '.55'; + row.style.opacity = enabled ? '' : '.6'; 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)'; + const urlInput = row.querySelector('.catalog-official-input'); + if (urlInput) urlInput.title = 'Official catalog — always source #1' + (enabled ? '' : ' (hidden from browse)'); } if (window.notificationSystem) { window.notificationSystem.info(enabled diff --git a/containers/libreportal/frontend/core/forms/css/config.css b/containers/libreportal/frontend/core/forms/css/config.css index 11cba56..1bf29e1 100644 --- a/containers/libreportal/frontend/core/forms/css/config.css +++ b/containers/libreportal/frontend/core/forms/css/config.css @@ -279,6 +279,48 @@ padding: 16px 18px; } +/* Catalog info note — icon inline with the text, matches the content-box look. */ +.catalog-note { + background: rgba(0, 0, 0, 0.20); + border: 1px solid var(--border-color); + border-radius: 10px; + padding: 12px 16px; + margin-bottom: 16px; + color: var(--text-secondary); + font-size: 13px; + line-height: 1.6; +} +.catalog-note strong { color: var(--text-primary); font-weight: 600; margin-right: 2px; } +.catalog-note em { font-style: normal; color: var(--status-warning); font-weight: 600; } +.catalog-note-icon { margin-right: 6px; } + +/* Official catalog row — read-only URL input with a pinned badge overlaid; same + height/box as the third-party rows. */ +.catalog-official-field { position: relative; flex: 1; min-width: 0; } +.catalog-official-field .catalog-official-input { + width: 100%; + padding-left: 84px; + opacity: 0.9; + cursor: default; +} +.catalog-official-badge { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + height: 20px; + padding: 0 8px; + border-radius: 999px; + background: rgba(46, 204, 113, 0.15); + color: #2ecc71; + font-weight: 600; + font-size: 11px; + white-space: nowrap; + pointer-events: none; +} + /* 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