diff --git a/frontend/js/atlus.js b/frontend/js/atlus.js
index 0eef45d..1804782 100644
--- a/frontend/js/atlus.js
+++ b/frontend/js/atlus.js
@@ -338,36 +338,54 @@
const container = $('#panelApps');
if (!container) return;
+ let guiApps = [];
+ let runningApps = [];
+
try {
const cfgRes = await Atlus.apiFetch('/api/settings');
- if (!cfgRes.ok) return;
- const cfg = await cfgRes.json();
- const guiApps = cfg.gui_apps || [];
+ if (cfgRes && cfgRes.ok) {
+ const cfg = await cfgRes.json();
+ guiApps = cfg.gui_apps || [];
+ }
+ } catch (e) {
+ console.warn('Panel apps: failed to load config', e);
+ }
- // Get running apps (may 503 if display deps unavailable)
- let runningApps = [];
+ try {
+ const runRes = await Atlus.apiFetch('/api/display/apps');
+ if (runRes && runRes.ok) runningApps = await runRes.json();
+ } catch (e) { /* display may be unavailable */ }
+
+ // Ensure all GUI apps are registered as Atlus app modules
+ for (const app of guiApps) {
try {
- const runRes = await Atlus.apiFetch('/api/display/apps');
- if (runRes.ok) runningApps = await runRes.json();
- } catch (e) { /* ignore */ }
-
- // Ensure all GUI apps are registered as Atlus app modules
- for (const app of guiApps) {
const appId = 'gui-' + app.id;
if (!Atlus.apps[appId] && window._atlusRegisterGuiApp) {
window._atlusRegisterGuiApp(app);
}
+ } catch (e) {
+ console.warn('Panel apps: failed to register', app.id, e);
}
+ }
- container.innerHTML = '';
+ // Clear and rebuild — always append Add button at the end
+ container.innerHTML = '';
- if (guiApps.length === 0) {
- container.innerHTML = '
No apps configured
';
- } else {
- for (const app of guiApps) {
- const running = runningApps.find(r => r.command === app.command && r.alive);
+ if (guiApps.length === 0) {
+ const empty = document.createElement('div');
+ empty.style.cssText = 'color:var(--text-muted);font-size:12px;font-family:var(--font-mono);padding:4px 0;';
+ empty.textContent = 'No apps configured';
+ container.appendChild(empty);
+ } else {
+ for (const app of guiApps) {
+ try {
+ const running = Array.isArray(runningApps)
+ ? runningApps.find(r => r.command === app.command && r.alive)
+ : null;
const row = document.createElement('div');
row.className = 'panel-app-row';
+
+ const args = Array.isArray(app.args) ? app.args.join(' ') : '';
row.innerHTML = `
${app.icon || '🖥'}
@@ -375,13 +393,13 @@
`;
- // Click name to open in tab
+ // Click name/icon to open in tab
row.querySelector('.panel-app-name').addEventListener('click', () => {
openApp('gui-' + app.id);
});
@@ -389,54 +407,54 @@
openApp('gui-' + app.id);
});
+ // Action button (launch/stop)
+ const actionBtn = row.querySelector('.panel-app-action');
+ actionBtn.addEventListener('click', async (e) => {
+ e.stopPropagation();
+ const isRunning = actionBtn.classList.contains('stop');
+ actionBtn.disabled = true;
+
+ try {
+ if (isRunning) {
+ const id = actionBtn.dataset.appId;
+ if (id) await Atlus.apiFetch(`/api/display/apps/${id}`, { method: 'DELETE' });
+ } else {
+ const a = actionBtn.dataset.args ? actionBtn.dataset.args.split(' ').filter(Boolean) : [];
+ await Atlus.apiFetch('/api/display/apps', {
+ method: 'POST',
+ body: {
+ command: actionBtn.dataset.command,
+ title: actionBtn.dataset.title,
+ args: a,
+ target_fps: parseInt(actionBtn.dataset.fps) || 10,
+ },
+ });
+ }
+ } catch (err) {
+ console.warn('Panel app action failed', err);
+ }
+ setTimeout(loadPanelApps, 1500);
+ });
+
container.appendChild(row);
+ } catch (e) {
+ console.warn('Panel apps: failed to render', app.id, e);
}
}
+ }
- // Add App button
- const addBtn = document.createElement('button');
- addBtn.className = 'panel-app-add';
- addBtn.textContent = '+ Add';
- addBtn.addEventListener('click', () => {
- openApp('settings');
- // Navigate to Applications section after a brief delay
- setTimeout(() => {
- const navItem = document.querySelector('.settings-nav-item[data-section="applications"]');
- if (navItem) navItem.click();
- }, 200);
- });
- container.appendChild(addBtn);
-
- // Action button handlers (launch/stop)
- container.querySelectorAll('.panel-app-action').forEach(btn => {
- btn.addEventListener('click', async (e) => {
- e.stopPropagation();
- const isRunning = btn.classList.contains('stop');
- btn.disabled = true;
-
- if (isRunning) {
- // Stop the app
- const appId = btn.dataset.appId;
- if (appId) {
- await Atlus.apiFetch(`/api/display/apps/${appId}`, { method: 'DELETE' });
- }
- } else {
- // Launch the app
- const args = btn.dataset.args ? btn.dataset.args.split(' ').filter(Boolean) : [];
- await Atlus.apiFetch('/api/display/apps', {
- method: 'POST',
- body: {
- command: btn.dataset.command,
- title: btn.dataset.title,
- args: args,
- target_fps: parseInt(btn.dataset.fps) || 10,
- },
- });
- }
- setTimeout(loadPanelApps, 1500);
- });
- });
- } catch (e) { /* ignore */ }
+ // Add App button — ALWAYS appended, regardless of errors above
+ const addBtn = document.createElement('button');
+ addBtn.className = 'panel-app-add';
+ addBtn.textContent = '+ Add';
+ addBtn.addEventListener('click', () => {
+ openApp('settings');
+ setTimeout(() => {
+ const navItem = document.querySelector('.settings-nav-item[data-section="applications"]');
+ if (navItem) navItem.click();
+ }, 200);
+ });
+ container.appendChild(addBtn);
}
// =====================================================================