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:
parent
8e8856f5d0
commit
190c06ebce
@ -25,20 +25,21 @@ class CatalogManager {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pinned official row (source #1). URL comes from the generated catalog
|
// The pinned official row (source #1). Renders the official URL in a read-only
|
||||||
// data; the hidden CFG_CATALOG_OFFICIAL_ENABLED input carries its on/off state
|
// input — same element (and height) as the third-party rows — with the badge
|
||||||
// so it saves with the rest on "Save Config".
|
// 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) {
|
_officialRow(base, enabled) {
|
||||||
const host = base ? base.replace(/^https?:\/\//, '').replace(/\/$/, '') : 'official catalog';
|
const url = base || '';
|
||||||
const off = enabled === false;
|
const off = enabled === false;
|
||||||
return `
|
return `
|
||||||
<div class="domain-building-block" data-catalog-official style="${off ? 'opacity:.55;' : ''}">
|
<div class="domain-building-block" data-catalog-official style="${off ? 'opacity:.6;' : ''}">
|
||||||
<div class="domain-header" style="align-items:center; gap:.6rem;">
|
<div class="domain-header">
|
||||||
<div style="flex:1; display:flex; align-items:center; gap:.6rem; min-width:0;">
|
<div class="catalog-official-field">
|
||||||
<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 class="catalog-official-badge">✓ Official</span>
|
||||||
<span style="opacity:.85; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
|
<input type="text" class="form-control catalog-input catalog-official-input"
|
||||||
${this._esc(host)}<span data-official-hint>${off ? ' <em style="opacity:.7;">(hidden from browse)</em>' : ''}</span>
|
value="${this._esc(url)}" readonly tabindex="-1"
|
||||||
</span>
|
title="Official catalog — always source #1${off ? ' (hidden from browse)' : ''}">
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="CFG_CATALOG_OFFICIAL_ENABLED" id="config-CFG_CATALOG_OFFICIAL_ENABLED" value="${off ? 'false' : 'true'}">
|
<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>
|
<span data-official-action>${window.officialCatalogActionHTML(!off)}</span>
|
||||||
@ -46,10 +47,14 @@ class CatalogManager {
|
|||||||
</div>`;
|
</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 {
|
try {
|
||||||
const r = await fetch('/data/apps/generated/registry_catalog.json', { cache: 'no-store' });
|
const cfg = window.configData && window.configData.config;
|
||||||
if (r.ok) { const j = await r.json(); return (j.source && j.source.base) || ''; }
|
const v = cfg && cfg.CFG_RELEASE_BASE_URL && cfg.CFG_RELEASE_BASE_URL.value;
|
||||||
|
if (v) return String(v).replace(/\/+$/, '');
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -64,7 +69,7 @@ class CatalogManager {
|
|||||||
|
|
||||||
const officialItem = configItems.find(i => i.key === 'CFG_CATALOG_OFFICIAL_ENABLED') || {};
|
const officialItem = configItems.find(i => i.key === 'CFG_CATALOG_OFFICIAL_ENABLED') || {};
|
||||||
const officialEnabled = String(officialItem.value == null ? 'true' : officialItem.value) !== 'false';
|
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);
|
let blocks = this._officialRow(officialBase, officialEnabled);
|
||||||
withContent.forEach(key => {
|
withContent.forEach(key => {
|
||||||
@ -82,13 +87,8 @@ class CatalogManager {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="domains-divider"></div>
|
<div class="domains-divider"></div>
|
||||||
<div class="traefik-warning-banner">
|
<div class="catalog-note">
|
||||||
<div class="traefik-warning-content">
|
<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>.
|
||||||
<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>
|
</div>
|
||||||
<div class="domain-building-blocks">
|
<div class="domain-building-blocks">
|
||||||
${blocks}
|
${blocks}
|
||||||
@ -212,11 +212,11 @@ window._setOfficialCatalog = function (enabled) {
|
|||||||
if (input) input.value = enabled ? 'true' : 'false';
|
if (input) input.value = enabled ? 'true' : 'false';
|
||||||
const row = document.querySelector('[data-catalog-official]');
|
const row = document.querySelector('[data-catalog-official]');
|
||||||
if (row) {
|
if (row) {
|
||||||
row.style.opacity = enabled ? '' : '.55';
|
row.style.opacity = enabled ? '' : '.6';
|
||||||
const action = row.querySelector('[data-official-action]');
|
const action = row.querySelector('[data-official-action]');
|
||||||
if (action) action.innerHTML = window.officialCatalogActionHTML(enabled);
|
if (action) action.innerHTML = window.officialCatalogActionHTML(enabled);
|
||||||
const hint = row.querySelector('[data-official-hint]');
|
const urlInput = row.querySelector('.catalog-official-input');
|
||||||
if (hint) hint.innerHTML = enabled ? '' : ' <em style="opacity:.7;">(hidden from browse)</em>';
|
if (urlInput) urlInput.title = 'Official catalog — always source #1' + (enabled ? '' : ' (hidden from browse)');
|
||||||
}
|
}
|
||||||
if (window.notificationSystem) {
|
if (window.notificationSystem) {
|
||||||
window.notificationSystem.info(enabled
|
window.notificationSystem.info(enabled
|
||||||
|
|||||||
@ -279,6 +279,48 @@
|
|||||||
padding: 16px 18px;
|
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)
|
/* Responsive column counts — colocated here (and not in style.css)
|
||||||
because the base 3-column rule above loads later than 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
|
so the unscoped base would otherwise win the cascade against the
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user