From 4c153bca5827d7db13c6b6a3f88e6db1a796b2bb Mon Sep 17 00:00:00 2001 From: Yury Kossakovsky Date: Fri, 12 Dec 2025 18:00:46 -0700 Subject: [PATCH] refactor(welcome): reorganize service card layout - move extra info from header to bottom section with credentials - display links (docs, dashboard, admin, ui) as compact "Link" buttons - move password toggle button to left of masked value - add python runner mount/entry info to data generation - remove redundant postgresus note - reduce vertical spacing in bottom section --- scripts/generate_welcome_page.sh | 6 +- welcome/app.js | 168 +++++++++++++++++++++---------- 2 files changed, 116 insertions(+), 58 deletions(-) diff --git a/scripts/generate_welcome_page.sh b/scripts/generate_welcome_page.sh index f4dcc97..e375ba3 100755 --- a/scripts/generate_welcome_page.sh +++ b/scripts/generate_welcome_page.sh @@ -93,9 +93,7 @@ fi if is_profile_active "postgresus"; then SERVICES_ARRAY+=(" \"postgresus\": { \"hostname\": \"$(json_escape "$POSTGRESUS_HOSTNAME")\", - \"credentials\": { - \"note\": \"Uses PostgreSQL credentials from .env\" - }, + \"credentials\": {}, \"extra\": { \"pg_host\": \"postgres\", \"pg_port\": \"${POSTGRES_PORT:-5432}\", @@ -414,6 +412,8 @@ if is_profile_active "python-runner"; then \"note\": \"Internal service only\" }, \"extra\": { + \"mounted_dir\": \"./python-runner -> /app\", + \"entry_file\": \"/app/main.py\", \"logs_command\": \"docker compose -p localai logs -f python-runner\" } }") diff --git a/welcome/app.js b/welcome/app.js index b6b5766..1baacd9 100644 --- a/welcome/app.js +++ b/welcome/app.js @@ -527,8 +527,8 @@ // Copy button (reusing the component) const copyBtn = createCopyButton(password); - container.appendChild(passwordSpan); container.appendChild(toggleBtn); + container.appendChild(passwordSpan); container.appendChild(copyBtn); return container; @@ -565,64 +565,123 @@ } /** - * Create credentials section for a service card + * Mapping of extra fields to display labels + * Fields not in this map are skipped (internal_api, internal_url shown in header) */ - function createCredentialsSection(creds) { - const section = document.createElement('div'); - section.className = 'mt-4 pt-4 border-t border-surface-400 space-y-2'; + const EXTRA_FIELD_LABELS = { + workers: { label: 'Workers', isSecret: false }, + mounted_dir: { label: 'Mount', isSecret: false }, + entry_file: { label: 'Entry', isSecret: false }, + logs_command: { label: 'Logs', isSecret: false }, + dashboard: { label: 'Dashboard', isLink: true }, + docs: { label: 'Docs', isLink: true }, + api_endpoint: { label: 'API', isLink: true }, + admin: { label: 'Admin', isLink: true }, + ui: { label: 'UI', isLink: true }, + service_role_key: { label: 'Service Key', isSecret: true }, + bolt_port: { label: 'Bolt Port', isSecret: false }, + pg_host: { label: 'PG Host', isSecret: false }, + pg_port: { label: 'PG Port', isSecret: false }, + pg_user: { label: 'PG User', isSecret: false }, + pg_password: { label: 'PG Password', isSecret: true }, + pg_db: { label: 'PG Database', isSecret: false }, + swagger_user: { label: 'Swagger User', isSecret: false }, + swagger_pass: { label: 'Swagger Pass', isSecret: true }, + internal_host: { label: 'Internal Host', isSecret: false }, + internal_port: { label: 'Internal Port', isSecret: false }, + database: { label: 'Database', isSecret: false } + }; - if (creds.note) { + /** + * Create a link row with label and "Link" text + icon + */ + function createLinkRow(label, url) { + const row = document.createElement('div'); + row.className = 'flex justify-between items-center'; + + const labelSpan = document.createElement('span'); + labelSpan.className = 'text-gray-500 text-sm'; + labelSpan.textContent = `${label}:`; + row.appendChild(labelSpan); + + // Container to align with other rows that have copy buttons + const valueContainer = document.createElement('div'); + valueContainer.className = 'flex items-center gap-1 pr-2'; + + const link = document.createElement('a'); + link.href = url; + link.target = '_blank'; + link.rel = 'noopener'; + link.className = 'text-brand hover:text-brand-400 text-sm font-medium inline-flex items-center gap-1 group transition-colors'; + link.innerHTML = ` + Link + ${Icons.externalLink('w-3 h-3 group-hover:translate-x-0.5 transition-transform')} + `; + valueContainer.appendChild(link); + row.appendChild(valueContainer); + + return row; + } + + /** + * Create bottom section with credentials and extra info + * @param {Object} creds - Credentials object + * @param {Object} extra - Extra data object + */ + function createBottomSection(creds, extra) { + const section = document.createElement('div'); + section.className = 'mt-4 pt-4 border-t border-surface-400 space-y-0'; + + // Handle credentials note (special case - just show note text) + if (creds && creds.note) { const noteP = document.createElement('p'); noteP.className = 'text-sm text-gray-500 italic'; noteP.textContent = creds.note; section.appendChild(noteP); - return section; } - if (creds.username) { - section.appendChild(createCredentialRow('Username', creds.username, false)); + // Add credential fields + if (creds) { + if (creds.username) { + section.appendChild(createCredentialRow('Username', creds.username, false)); + } + if (creds.password) { + section.appendChild(createCredentialRow('Password', creds.password, true)); + } + if (creds.api_key) { + section.appendChild(createCredentialRow('API Key', creds.api_key, true)); + } } - if (creds.password) { - section.appendChild(createCredentialRow('Password', creds.password, true)); - } - if (creds.api_key) { - section.appendChild(createCredentialRow('API Key', creds.api_key, true)); + + // Add extra fields (skip internal_api/internal_url - shown in header) + if (extra) { + for (const [key, value] of Object.entries(extra)) { + // Skip fields shown in header + if (key === 'internal_api' || key === 'internal_url') continue; + + // Handle recommendation as plain italic text (like credentials.note) + if (key === 'recommendation' && value) { + const noteP = document.createElement('p'); + noteP.className = 'text-sm text-gray-500 italic'; + noteP.textContent = value; + section.appendChild(noteP); + continue; + } + + const fieldConfig = EXTRA_FIELD_LABELS[key]; + if (fieldConfig && value) { + if (fieldConfig.isLink) { + section.appendChild(createLinkRow(fieldConfig.label, value)); + } else { + section.appendChild(createCredentialRow(fieldConfig.label, value, fieldConfig.isSecret)); + } + } + } } return section; } - /** - * Create extra info section (internal URLs, etc.) - * @param {Object} extra - Extra data object - * @param {boolean} skipInternalUrls - Skip internal_api/internal_url (shown in header) - */ - function createExtraSection(extra, skipInternalUrls = false) { - const items = []; - - if (!skipInternalUrls) { - if (extra.internal_api) { - items.push(`Internal: ${escapeHtml(extra.internal_api)}`); - } - if (extra.internal_url) { - items.push(`Internal: ${escapeHtml(extra.internal_url)}`); - } - } - if (extra.workers) { - items.push(`Workers: ${escapeHtml(extra.workers)}`); - } - if (extra.recommendation) { - items.push(`${escapeHtml(extra.recommendation)}`); - } - - if (items.length === 0) return null; - - const container = document.createElement('div'); - container.className = 'mt-2 flex flex-wrap gap-2'; - container.innerHTML = items.join(''); - return container; - } - /** * Create service card header */ @@ -696,12 +755,6 @@ content.appendChild(internalSpan); } - // Extra info (excluding internal URLs which are now shown above) - if (serviceData.extra) { - const extraSection = createExtraSection(serviceData.extra, true); - if (extraSection) content.appendChild(extraSection); - } - // Make icon clickable if docsUrl is available if (metadata.docsUrl) { const iconLink = document.createElement('a'); @@ -738,10 +791,15 @@ const header = createCardHeader(metadata, serviceData); card.appendChild(header); - // Credentials section - if (serviceData.credentials && Object.keys(serviceData.credentials).length > 0) { - const credsSection = createCredentialsSection(serviceData.credentials); - card.appendChild(credsSection); + // Bottom section with credentials and extra info + const hasCredentials = serviceData.credentials && Object.keys(serviceData.credentials).length > 0; + const hasExtra = serviceData.extra && Object.keys(serviceData.extra).some( + key => key !== 'internal_api' && key !== 'internal_url' && EXTRA_FIELD_LABELS[key] + ); + + if (hasCredentials || hasExtra) { + const bottomSection = createBottomSection(serviceData.credentials, serviceData.extra); + card.appendChild(bottomSection); } return card;