Rework Dashboard, add toasts, container folders/icons/WebUI links, detail tabs
Dashboard: - Plain-count stat tiles (Running/Stopped/Pods/Images/Volumes/Networks) separated from a single "Resource Usage" card (CPU/Memory/Swap/Storage meter rows) instead of forcing both into one tile grid, which produced awkward spanning-tile/dead-cell layouts. - Fixed CPU usage never changing (libpod's own cpuUtilization is computed once and never resampled) by computing it from /proc/stat deltas instead. - Fixed memory usage reading far too high by using /proc/meminfo's MemAvailable instead of libpod's raw (non-reclaimable-aware) memFree. - Added an Autostart Queue table reusing podman-autostart.sh's own failure-counter files. - Dashboard and Containers now auto-refresh every ~2s (paused when the tab is hidden or a modal is open). Toasts: - Real success/warn/error/info toast notifications replacing every alert() used for one-way feedback, across every panel. Container detail modal: - 5 new tabs: Resources, Logs, Console, Events, Healthcheck. Containers panel: - Folders to group containers (name + icon), stored in the plugin's own folders.json — a folder's header always shows an icon+name+status chip per member, matching Unraid's own Docker page folders. "Move to Folder" becomes "Remove from Folder" once a container is already grouped. - Containers can carry an icon URL and a WebUI URL (small button next to the name), both stored as container labels and auto-filled from templates where applicable. - Settings: an "Add container" control for the Autostart order table. Fixes: - Context menus now measure their own rendered size and flip above the anchor when there isn't room below, instead of running off-screen. - Containers table now uses table-layout:fixed with explicit column widths — auto layout was shifting every column (and the header) on every folder expand/collapse, and briefly again when a flex wrapper was mistakenly placed directly on a <td>. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
'use strict';
|
||||
const P = window.Podman;
|
||||
let autostartNames = [];
|
||||
let allContainerNames = [];
|
||||
|
||||
function renderAutostart() {
|
||||
const tbody = P.el('autostart-tbody');
|
||||
@@ -24,11 +25,24 @@
|
||||
'</div></td></tr>';
|
||||
}).join('')
|
||||
: '<tr><td colspan="3" class="podman-empty-note">No containers in the autostart chain.</td></tr>';
|
||||
renderAddOptions();
|
||||
}
|
||||
|
||||
// Repopulated after every add/remove so it only ever offers containers
|
||||
// that exist and aren't already in the chain — avoids duplicate entries
|
||||
// and offering a container name that no longer exists.
|
||||
function renderAddOptions() {
|
||||
const select = P.el('autostart-add-select');
|
||||
const available = allContainerNames.filter(function (name) { return autostartNames.indexOf(name) === -1; });
|
||||
select.innerHTML = available.length
|
||||
? available.map(function (name) { return '<option value="' + P.escapeHtml(name) + '">' + P.escapeHtml(name) + '</option>'; }).join('')
|
||||
: '<option value="">All containers already added</option>';
|
||||
P.el('autostart-add-btn').disabled = !available.length;
|
||||
}
|
||||
|
||||
function saveAutostart() {
|
||||
return P.post('settings', 'autostart_save', { names: autostartNames }).catch(function (err) {
|
||||
alert('Could not save autostart order: ' + err.message);
|
||||
P.toast('Could not save autostart order: ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,8 +66,19 @@
|
||||
}
|
||||
|
||||
function load() {
|
||||
return P.get('settings', 'get').then(fillForm).catch(function (err) {
|
||||
alert('Could not load settings: ' + err.message);
|
||||
return Promise.all([
|
||||
P.get('settings', 'get').then(fillForm),
|
||||
// Needed for the "Add container" dropdown — a separate, tolerant
|
||||
// fetch so a containers.php hiccup doesn't also break the rest of
|
||||
// Settings (autostart reorder/remove don't need this list at all).
|
||||
P.get('containers', 'list').then(function (containers) {
|
||||
allContainerNames = containers.map(function (c) { return c.name; }).sort();
|
||||
renderAddOptions();
|
||||
}).catch(function () {
|
||||
allContainerNames = [];
|
||||
}),
|
||||
]).catch(function (err) {
|
||||
P.toast('Could not load settings: ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -170,11 +195,11 @@
|
||||
P.post('disks', 'format', { device: select.value }).then(function (data) {
|
||||
close();
|
||||
P.el('settings-storage-path').value = data.mountPath;
|
||||
alert('Formatted and mounted at ' + data.mountPath + '. Storage path has been filled in below — click "Save Settings" to use it, then Restart Podman.');
|
||||
P.toast('Formatted and mounted at ' + data.mountPath + '. Click "Save Settings" below, then Restart Podman.', 'warn');
|
||||
}).catch(function (err) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Format Disk';
|
||||
alert('Format failed: ' + err.message);
|
||||
P.toast('Format failed: ' + err.message, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -190,15 +215,24 @@
|
||||
stopTimeoutSeconds: parseInt(P.el('settings-stop-timeout').value, 10) || 10,
|
||||
};
|
||||
return P.post('settings', 'save', body).then(function () {
|
||||
alert('Saved. Restart podman (rc.podman restart) to apply storage/enabled changes.');
|
||||
P.toast('Saved. Restart podman (rc.podman restart) to apply storage/enabled changes.', 'warn');
|
||||
}).catch(function (err) {
|
||||
alert('Save failed: ' + err.message);
|
||||
P.toast('Save failed: ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
P.el('settings-save-btn').addEventListener('click', save);
|
||||
|
||||
P.el('autostart-add-btn').addEventListener('click', function () {
|
||||
const select = P.el('autostart-add-select');
|
||||
const name = select.value;
|
||||
if (!name) return;
|
||||
autostartNames.push(name);
|
||||
renderAutostart();
|
||||
saveAutostart();
|
||||
});
|
||||
|
||||
P.el('autostart-tbody').addEventListener('click', function (e) {
|
||||
const btn = e.target.closest('button[data-action]');
|
||||
if (!btn) return;
|
||||
|
||||
Reference in New Issue
Block a user