A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys, Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun VPN routing, and a web dashboard to manage it all. Free & open forever to self-host; optional paid hosted services fund it. See PROMISE.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
129 lines
4.8 KiB
JavaScript
Executable File
129 lines
4.8 KiB
JavaScript
Executable File
// Dashboard functionality
|
|
// loadSystemInfo() and updateDiskChart() live in data-loader.js — that version
|
|
// uses waitForDashboardElements() so it doesn't fire before the dashboard HTML
|
|
// is in the DOM. Defining them here too overrode the safer version and produced
|
|
// the spurious "Disk chart elements not found" errors when called from non-dashboard pages.
|
|
|
|
// Load installed apps and render icon grid on dashboard
|
|
async function loadInstalledApps() {
|
|
if (!window.apps || window.apps.length === 0) {
|
|
try {
|
|
const response = await fetch('/data/apps/generated/apps.json', { cache: 'no-store' });
|
|
const data = await response.json();
|
|
window.apps = data.apps || [];
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
}
|
|
renderInstalledApps();
|
|
}
|
|
|
|
function renderInstalledApps() {
|
|
const section = document.getElementById('frontpage-apps-section');
|
|
const container = document.getElementById('frontpage-apps-container');
|
|
if (!section || !container) return;
|
|
|
|
const installed = (window.apps || []).filter(a => a.installed);
|
|
if (installed.length === 0) return;
|
|
|
|
container.innerHTML = installed.map(app => createInstalledAppCard(app)).join('');
|
|
section.style.display = '';
|
|
|
|
populateDashboardServiceButtons(installed);
|
|
}
|
|
|
|
function createInstalledAppCard(app) {
|
|
const appName = app.command.split(' ').pop();
|
|
let icon = app.icon || 'icons/apps/default.svg';
|
|
if (!icon.startsWith('/')) icon = '/' + icon;
|
|
const shortName = app.name.split(' - ')[0].trim();
|
|
|
|
return `
|
|
<div class="frontpage-app-tile" onclick="window.location.href='/app?=${appName}'">
|
|
<div class="frontpage-app-icon-wrap">
|
|
<img src="${icon}" alt="${shortName}" onerror="this.src='/icons/apps/default.svg'">
|
|
<div class="frontpage-app-overlay" id="frontpage-overlay-${appName}" onclick="event.stopPropagation()"></div>
|
|
</div>
|
|
<span class="frontpage-app-name">${shortName}</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
async function populateDashboardServiceButtons(installedApps) {
|
|
let services = [];
|
|
|
|
if (window.serviceButtons) {
|
|
if (window.serviceButtons.services.length === 0) await window.serviceButtons.loadServices();
|
|
services = window.serviceButtons.services;
|
|
} else {
|
|
try {
|
|
const res = await fetch('/data/apps/generated/apps-services.json', { cache: 'no-store' });
|
|
const data = await res.json();
|
|
services = data.services || [];
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const proto = s => ['http', 'https'].includes((s.protocol || '').toLowerCase()) ? s.protocol.toLowerCase() : 'http';
|
|
|
|
installedApps.forEach(app => {
|
|
const appName = app.command.split(' ').pop();
|
|
const shortName = app.name.split(' - ')[0].trim();
|
|
const overlay = document.getElementById(`frontpage-overlay-${appName}`);
|
|
if (!overlay) return;
|
|
|
|
const appServices = services.filter(s => s.app === appName && s.buttonEnabled === true);
|
|
|
|
// Multi-button render via the shared expandServiceLinks() helper.
|
|
const serviceButtons = appServices.flatMap(s =>
|
|
window.expandServiceLinks(s).map(({ url, label }) => `
|
|
<a href="${url}" target="_blank" rel="noopener noreferrer" class="frontpage-overlay-btn" onclick="event.stopPropagation()">
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
<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>
|
|
${label}
|
|
</a>
|
|
`)
|
|
).filter(Boolean).join('');
|
|
|
|
overlay.innerHTML = serviceButtons + `<button class="frontpage-app-manage-btn" onclick="event.stopPropagation(); navigateToApp('${appName}')">${shortName}</button>`;
|
|
});
|
|
}
|
|
|
|
// Setup event listeners
|
|
function setupEventListeners() {
|
|
setupMobileMenu();
|
|
loadInstalledApps();
|
|
}
|
|
|
|
// Navigate to app page using SPA router
|
|
function navigateToApp(appName) {
|
|
// Use proper SPA navigation to the app page
|
|
if (window.librePortalSPA && typeof window.librePortalSPA.navigate === 'function') {
|
|
window.librePortalSPA.navigate(`/app?=${appName}`);
|
|
} else if (window.navigateToRoute && typeof window.navigateToRoute === 'function') {
|
|
window.navigateToRoute(`app?=${appName}`);
|
|
} else {
|
|
// Fallback to direct navigation
|
|
window.location.href = `/app?=${appName}`;
|
|
}
|
|
}
|
|
|
|
// Filter apps by search term (removed - not used in dashboard)
|
|
function filterApps(searchTerm) {
|
|
//console.log('Filter apps functionality removed from dashboard');
|
|
}
|
|
|
|
// Filter apps by category (removed - not used in dashboard)
|
|
function filterAppsByCategory(category) {
|
|
//console.log('Filter apps by category functionality removed from dashboard');
|
|
}
|
|
|
|
// Populate category filter (removed - not used in dashboard)
|
|
function populateCategoryFilter() {
|
|
//console.log('Category filter population removed from dashboard');
|
|
}
|