fix(catalog/webui): inline info note + official row shows its URL, height matches

- Info banner: drop the borrowed traefik warning banner (icon sat above the
  text, amber-warning styling) for a .catalog-note — icon inline with the text,
  neutral boxed look matching the content sections.
- Official row (#1): render the release-host URL in a read-only input (same
  element/height as the third-party rows) with the "✓ Official" badge overlaid,
  instead of short text — so it shows the URL and its box height matches the
  others. URL is read from the loaded config (CFG_RELEASE_BASE_URL).

Signed-off-by: librelad <librelad@digitalangels.vip>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
librelad 2026-07-06 22:44:55 +01:00
parent 8e8856f5d0
commit 190c06ebce
2 changed files with 67 additions and 25 deletions

View File

@ -25,20 +25,21 @@ class CatalogManager {
</div>`;
}
// 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 `
<div class="domain-building-block" data-catalog-official style="${off ? 'opacity:.55;' : ''}">
<div class="domain-header" style="align-items:center; gap:.6rem;">
<div style="flex:1; display:flex; align-items:center; gap:.6rem; min-width:0;">
<span style="display:inline-flex; align-items:center; padding:.15rem .55rem; border-radius:999px; background:rgba(46,204,113,.15); color:#2ecc71; font-weight:600; font-size:.8rem; white-space:nowrap;"> Official</span>
<span style="opacity:.85; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
${this._esc(host)}<span data-official-hint>${off ? ' <em style="opacity:.7;">(hidden from browse)</em>' : ''}</span>
</span>
<div class="domain-building-block" data-catalog-official style="${off ? 'opacity:.6;' : ''}">
<div class="domain-header">
<div class="catalog-official-field">
<span class="catalog-official-badge"> Official</span>
<input type="text" class="form-control catalog-input catalog-official-input"
value="${this._esc(url)}" readonly tabindex="-1"
title="Official catalog — always source #1${off ? ' (hidden from browse)' : ''}">
</div>
<input type="hidden" name="CFG_CATALOG_OFFICIAL_ENABLED" id="config-CFG_CATALOG_OFFICIAL_ENABLED" value="${off ? 'false' : 'true'}">
<span data-official-action>${window.officialCatalogActionHTML(!off)}</span>
@ -46,10 +47,14 @@ class CatalogManager {
</div>`;
}
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 {
</div>
</div>
<div class="domains-divider"></div>
<div class="traefik-warning-banner">
<div class="traefik-warning-content">
<span class="traefik-warning-icon">🛈</span>
<div class="traefik-warning-text">
<strong>The official catalog is always included.</strong> Add third-party catalog URLs to browse more apps third-party sources are <em>unverified</em>.
</div>
</div>
<div class="catalog-note">
<span class="catalog-note-icon">🛈</span><strong>The official catalog is always included.</strong> Add third-party catalog URLs to browse more apps third-party sources are <em>unverified</em>.
</div>
<div class="domain-building-blocks">
${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 ? '' : ' <em style="opacity:.7;">(hidden from browse)</em>';
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

View File

@ -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