diff --git a/containers/libreportal/frontend/css/services.css b/containers/libreportal/frontend/css/services.css index 17dc8db..4836a97 100644 --- a/containers/libreportal/frontend/css/services.css +++ b/containers/libreportal/frontend/css/services.css @@ -242,6 +242,24 @@ } body:not(.lp-ui--advanced) .service-rich { display: none; } +/* "Show logs" / "Hide logs" toggle sitting at the very bottom of the + open .task-details panel. Logs are off by default — the user opts in + per service so a row of expanded services doesn't open one SSE stream + each. Centred and given a little top breathing room so it reads as + a footer action rather than another inline button. */ +.service-logs-toggle { + display: flex; + justify-content: center; + margin-top: 8px; + margin-bottom: 4px; +} +.service-logs-toggle .service-show-logs { + padding: 6px 14px; +} +.service-item.logs-shown .service-logs-toggle { + margin-bottom: 8px; +} + /* ============================================================ Beginner / Advanced toggle in the Services tab title row. Project-wide visual; same component will be reused wherever diff --git a/containers/libreportal/frontend/js/components/app/app-tabbed-manager.js b/containers/libreportal/frontend/js/components/app/app-tabbed-manager.js index c4046a7..f6fe575 100755 --- a/containers/libreportal/frontend/js/components/app/app-tabbed-manager.js +++ b/containers/libreportal/frontend/js/components/app/app-tabbed-manager.js @@ -942,10 +942,13 @@ class AppTabbedManager { if (button.classList.contains('tab-button')) { return; } - // Logs toggle buttons stay clickable while a task runs — viewing - // log output is read-only and the whole point during a long task. - if (button.dataset.action === 'toggle-logs' || - button.classList.contains('service-logs') || + // Details + log-stream toggles stay clickable while a task runs — + // viewing service details and tailing logs is read-only and the + // whole point during a long task. + if (button.dataset.action === 'toggle-details' || + button.dataset.action === 'toggle-log-stream' || + button.classList.contains('service-details') || + button.classList.contains('service-show-logs') || button.classList.contains('toggle-details')) { return; } diff --git a/containers/libreportal/frontend/js/components/app/services-manager.js b/containers/libreportal/frontend/js/components/app/services-manager.js index 088754f..796eeda 100644 --- a/containers/libreportal/frontend/js/components/app/services-manager.js +++ b/containers/libreportal/frontend/js/components/app/services-manager.js @@ -428,7 +428,7 @@ class ServicesManager { return `
-
+
${iconHtml} ${escapeHtml(svc.serviceName)} @@ -446,11 +446,11 @@ class ServicesManager { Restart -
@@ -462,7 +462,14 @@ class ServicesManager {
State: ${escapeHtml(state)}
Status: ${escapeHtml(svc.statusText)}
-
+ ${this._renderRichDetail(info)} +
+ +
+ - ${this._renderRichDetail(info)}
`; } @@ -494,8 +500,10 @@ class ServicesManager { if (action === 'restart') { await this._restartService(serviceName, btn); - } else if (action === 'toggle-logs') { - this._toggleLogs(item, serviceName); + } else if (action === 'toggle-details') { + this._toggleDetails(item, serviceName); + } else if (action === 'toggle-log-stream') { + this._toggleLogStream(item, serviceName); } else if (action === 'resume-logs') { this._resumeLogs(item, serviceName); } @@ -593,26 +601,61 @@ class ServicesManager { } } - _toggleLogs(item, serviceName) { - // The task-list uses a .task-details-open class (not the `hidden` - // attribute) because .task-details has `display: none` baked in. + // Toggle the .task-details panel (meta + rich detail + log toggle). + // Logs are NOT auto-opened here — the user has to click "Show logs" + // explicitly. Closing the panel also tears down any open log stream + // and resets the inline log block back to its hidden state. + _toggleDetails(item, serviceName) { const details = item.querySelector('.task-details'); - const output = item.querySelector('.service-log-output'); - if (!details || !output) return; + if (!details) return; const isOpen = details.classList.contains('task-details-open'); if (isOpen) { details.classList.remove('task-details-open'); - this._closeLogStream(serviceName); - this._hideLogOverlay(output); + this._resetLogBlock(item, serviceName); + return; + } + details.classList.add('task-details-open'); + } + + // Show / hide the log block inside the open details panel. Opening + // starts the SSE stream so logs auto-update; closing tears it down. + _toggleLogStream(item, serviceName) { + const logsBlock = item.querySelector('.task-logs'); + const output = item.querySelector('.service-log-output'); + if (!logsBlock || !output) return; + + const showing = item.classList.contains('logs-shown'); + if (showing) { + this._resetLogBlock(item, serviceName); return; } - details.classList.add('task-details-open'); + logsBlock.style.display = ''; output.textContent = ''; this._hideLogOverlay(output); output.dataset.stream = 'connecting'; this._openLogStream(serviceName, output); + item.classList.add('logs-shown'); + this._setLogToggleLabel(item, 'Hide logs'); + } + + // Tear down the log stream and put the block back to its closed state. + // Used both when the user clicks "Hide logs" and when the parent + // details panel collapses (so reopening starts in the clean state). + _resetLogBlock(item, serviceName) { + const logsBlock = item.querySelector('.task-logs'); + const output = item.querySelector('.service-log-output'); + if (output) this._hideLogOverlay(output); + if (logsBlock) logsBlock.style.display = 'none'; + this._closeLogStream(serviceName); + item.classList.remove('logs-shown'); + this._setLogToggleLabel(item, 'Show logs'); + } + + _setLogToggleLabel(item, text) { + const lbl = item.querySelector('.service-show-logs .task-btn-label'); + if (lbl) lbl.textContent = text; } _openLogStream(serviceName, outputEl) { diff --git a/containers/libreportal/frontend/themes/nebula/theme.css b/containers/libreportal/frontend/themes/nebula/theme.css index 86d0efa..fc759b3 100644 --- a/containers/libreportal/frontend/themes/nebula/theme.css +++ b/containers/libreportal/frontend/themes/nebula/theme.css @@ -247,7 +247,8 @@ [data-theme="nebula"] .uninstall-btn, [data-theme="nebula"] .btn-uninstall, [data-theme="nebula"] .btn-danger, -[data-theme="nebula"] .backup-danger-btn { +[data-theme="nebula"] .backup-danger-btn, +[data-theme="nebula"] .tool-run-btn.destructive { background: rgba(var(--status-danger-rgb), 0.35) !important; color: var(--text-primary) !important; border: 1px solid rgba(var(--status-danger-rgb), 0.65) !important; @@ -259,12 +260,34 @@ [data-theme="nebula"] .uninstall-btn:hover:not(:disabled), [data-theme="nebula"] .btn-uninstall:hover:not(:disabled), [data-theme="nebula"] .btn-danger:hover:not(:disabled), -[data-theme="nebula"] .backup-danger-btn:hover:not(:disabled) { +[data-theme="nebula"] .backup-danger-btn:hover:not(:disabled), +[data-theme="nebula"] .tool-run-btn.destructive:hover:not(:disabled) { background: rgba(var(--status-danger-rgb), 0.50) !important; border-color: rgba(var(--status-danger-rgb), 0.85) !important; transform: translateY(-1px); } +/* Tools-page Run buttons — same translucent recipe as service-trigger + (the small "Open" pill on installed app cards). The 0.12/0.30 alphas + from tools.css read as muddy green/red against Nebula's cosmic + gradient, especially with --status-success/danger text — bump to + 0.35/0.65 with neutral text so they pop. .destructive picks up the + danger recipe above via the chained selector. */ +[data-theme="nebula"] .tool-run-btn { + background: rgba(var(--status-success-rgb), 0.35) !important; + color: var(--text-primary) !important; + border: 1px solid rgba(var(--status-success-rgb), 0.65) !important; + text-shadow: none; + font-weight: 600 !important; + transition: background 0.18s ease, border-color 0.18s ease, transform 0.15s ease !important; +} + +[data-theme="nebula"] .tool-run-btn:hover:not(:disabled):not(.destructive) { + background: rgba(var(--status-success-rgb), 0.50) !important; + border-color: rgba(var(--status-success-rgb), 0.85) !important; + transform: translateY(-1px); +} + [data-theme="nebula"] .manage-btn, [data-theme="nebula"] .btn-manage, [data-theme="nebula"] .btn-primary,