LibrePortal/containers/libreportal/frontend/html/apps-unified-layout.html
librelad 713cba76f0 ux(backup): match per-app Backups tab action buttons to the config Save style
"Backup now" and "Open backup center" looked off compared to the rest of
the app page — the secondary link sat underlined with a trailing arrow
glyph instead of a real button, and neither carried an icon. Re-skins
both to use the .btn .btn-primary / .btn .btn-secondary pattern the
config Save / Reset buttons use, so the three action surfaces on an
app page read as one family.

  Backup now           .btn .btn-primary   + upload-cloud SVG (16x16)
  Open backup center   .btn .btn-secondary + external-link SVG (16x16)

The "Open backup center" link is now SPA-routed (preventDefault + call
navigateToRoute) so clicking it doesn't trigger a full page reload —
same behaviour as the deep-link cells in the global Snapshots table.
href is still /backup so cmd/ctrl-click and right-click → open-in-new-tab
still work the natural way.

Applied to both apps-unified-layout.html and the legacy app-content.html
since the existing app-page surface lives in both templates.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 01:36:00 +01:00

257 lines
11 KiB
HTML
Executable File

<!-- Unified Apps Layout with Persistent Sidebar -->
<div class="apps-layout">
<!-- Mobile overlay -->
<div class="mobile-overlay" id="mobile-overlay"></div>
<!-- Sidebar Container -->
<div class="sidebar-container">
<div class="sidebar" id="sidebar">
<div class="apps-search">
<svg class="apps-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input
type="search"
id="apps-search-input"
class="apps-search-input"
placeholder="Search apps…"
autocomplete="off"
spellcheck="false"
oninput="if(window.appsManager) window.appsManager.filterAppsByQuery(this.value)"
>
<button type="button" class="apps-search-clear" aria-label="Clear search" onclick="if(window.appsManager) window.appsManager.clearAppsSearch()">&times;</button>
</div>
<!-- Categories will be dynamically generated here -->
<div id="dynamic-categories">
<!-- Loading spinner for categories -->
<div class="loading-categories">
<div class="loading-spinner"></div>
<p style="color: #ffffff;">Loading categories...</p>
</div>
</div>
</div>
</div>
<!-- Main Content Area -->
<div class="main-content">
<!-- Apps List View -->
<div id="apps-view" class="content-view">
<div class="apps-section" id="apps-section">
<!-- Loading spinner for apps -->
<div class="loading-content" style="
text-align: center;
padding: 22px;
background: var(--input-bg);
border: 1px solid var(--border-color);
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
width: 100%;
box-sizing: border-box;
margin: 22px;
">
<div class="loading-spinner" style="
width: 24px;
height: 24px;
border: 3px solid rgba(52, 152, 219, 0.3);
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px auto;
display: inline-block;
"></div>
<div class="loading-message" style="
font-size: 16px;
color: #ffffff;
font-weight: 500;
margin-bottom: 8px;
">
Loading applications...
</div>
<div class="loading-subtitle" style="
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
font-style: italic;
">
Discovering the perfect applications for you...
</div>
</div>
</div>
</div>
<!-- App Detail View -->
<div id="app-detail-view" class="content-view">
<div class="app-header" id="app-header">
<!-- App info will be dynamically inserted -->
</div>
<!-- Tabbed Interface -->
<div class="tabbed-interface">
<div class="tab-navigation">
<button class="main-tab-button active" data-tab="config" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('config')">
<span class="tab-emoji">🛠️</span>
<span class="tab-name">Config</span>
</button>
<button class="main-tab-button" data-tab="services" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('services')">
<span class="tab-emoji"></span>
<span class="tab-name">Services</span>
</button>
<button class="main-tab-button" data-tab="tools" style="display: none;" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('tools')">
<span class="tab-emoji">🧰</span>
<span class="tab-name">Tools</span>
</button>
<button class="main-tab-button" data-tab="routing" style="display: none;" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('routing')">
<span class="tab-emoji">🛡️</span>
<span class="tab-name">Routing</span>
</button>
<button class="main-tab-button" data-tab="backups" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('backups')">
<span class="tab-emoji">💾</span>
<span class="tab-name">Backups</span>
</button>
<button class="main-tab-button" data-tab="tasks" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('tasks')">
<span class="tab-emoji">📋</span>
<span class="tab-name">Tasks</span>
</button>
</div>
<!-- Tab Content -->
<div class="tab-content">
<!-- Config Tab -->
<div class="tab-pane active" id="config-tab">
<div class="config-section" id="config-section">
<!-- Loading spinner for config -->
<div class="loading-content" style="
text-align: center;
padding: 40px 20px;
color: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
margin: 16px;
">
<div class="loading-spinner" style="
width: 24px;
height: 24px;
border: 3px solid rgba(52, 152, 219, 0.3);
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
"></div>
<p>Loading configuration...</p>
</div>
<!-- Config form will be dynamically inserted -->
</div>
</div>
<!-- Services Tab -->
<div class="tab-pane" id="services-tab">
<div class="services-section">
<div id="services-list" class="services-list">
<!-- Service rows are rendered here by services-manager.js -->
</div>
</div>
</div>
<!-- Tools Tab -->
<div class="tab-pane" id="tools-tab">
<div class="tools-section">
<div id="tools-list" class="tools-list">
<!-- Tool buttons are rendered here by tools-manager.js -->
</div>
</div>
</div>
<!-- Routing Tab (Traefik only) -->
<div class="tab-pane" id="routing-tab">
<div class="routing-section-wrap">
<div id="routing-list" class="routing-list">
<!-- Routing rows rendered by routing-manager.js -->
</div>
</div>
</div>
<!-- Backups Tab -->
<div class="tab-pane" id="backups-tab">
<div class="backup-section" id="backup-section">
<div class="backup-title">
<div class="backup-title-main">
<h3>💾 Backups</h3>
<p>Backups for <span id="backup-app-name">this app</span> across all configured locations.</p>
</div>
<div class="backup-title-actions">
<button type="button" class="btn btn-primary" id="backup-app-card-backup-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
Backup now
</button>
<a class="btn btn-secondary" href="/backup" onclick="event.preventDefault(); if(window.navigateToRoute){window.navigateToRoute('/backup');}else{window.location.href='/backup';}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
Open backup center
</a>
</div>
</div>
<div class="backup-snapshots-container">
<div class="backup-app-card-status" id="backup-app-card-status">Loading…</div>
<div class="backup-app-card-snapshots" id="backup-app-card-snapshots"></div>
</div>
</div>
</div>
<!-- Tasks Tab -->
<div class="tab-pane" id="tasks-tab">
<div class="tasks-section">
<div class="tasks-title">
<h3>📋 Task Management</h3>
<p>Tasks for <span id="tasks-app-name">this application</span> - Monitor and manage application tasks</p>
</div>
<div class="tasks-container" id="app-tasks">
<!-- Loading spinner for tasks -->
<div class="loading-content" style="
text-align: center;
padding: 40px 20px;
color: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
margin: 16px;
">
<div class="loading-spinner" style="
width: 24px;
height: 24px;
border: 3px solid rgba(52, 152, 219, 0.3);
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
"></div>
<p>Loading tasks...</p>
</div>
<!-- App-specific tasks will be loaded here -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>