Replace em-dash and ad-hoc loaders with the shared job-tray spinner for deploy templates, host/swarm/tunnels/events, health latency, container stats/logs/processes, and registry/volume browsers.
This commit is contained in:
@@ -21,7 +21,16 @@ import {
|
||||
import { initAddContainerPage } from './libs/addContainer.js';
|
||||
import { initRegistryManager, openPushImageModal, pullImageWithAuth } from './libs/registryManager.js';
|
||||
import { showContainerSkeleton, createProgressBar, updateProgressBar, removeProgressBar } from './libs/loadingStates.js';
|
||||
import { closeAllModals, showStatusIndicator, hideStatusIndicator, updateStatusIndicator, showAlert } from './libs/uiUtils.js';
|
||||
import {
|
||||
closeAllModals,
|
||||
showStatusIndicator,
|
||||
hideStatusIndicator,
|
||||
updateStatusIndicator,
|
||||
showAlert,
|
||||
jobSpinnerHtml,
|
||||
jobSpinnerPending,
|
||||
jobSpinnerLoadingBlock,
|
||||
} from './libs/uiUtils.js';
|
||||
import notificationManager from './libs/notifications.js';
|
||||
import { initOpsApp } from './ui/ops-app.js';
|
||||
import { presentError, formatResponseError, isBackgroundMethod } from './client/errors.js';
|
||||
@@ -2071,12 +2080,21 @@ function updateHealthBadge(info, conn) {
|
||||
const docker = info?.docker ?? conn?.dockerHealth;
|
||||
const status = info?.status ?? conn?.healthStatus ?? 'unknown';
|
||||
const role = conn?.role || info?.role || null;
|
||||
const pending = conn?.connected || status === 'connecting' || status === 'unknown';
|
||||
|
||||
lat.textContent = latency != null ? `${latency} ms` : '—';
|
||||
if (latency != null) {
|
||||
lat.textContent = `${latency} ms`;
|
||||
} else if (pending) {
|
||||
lat.innerHTML = jobSpinnerPending();
|
||||
} else {
|
||||
lat.textContent = '—';
|
||||
}
|
||||
if (docker?.ok === true) {
|
||||
dock.textContent = `Docker ${docker.apiVersion || 'ok'}`;
|
||||
} else if (docker?.ok === false) {
|
||||
dock.textContent = 'Docker down';
|
||||
} else if (pending) {
|
||||
dock.innerHTML = `Docker ${jobSpinnerPending()}`;
|
||||
} else {
|
||||
dock.textContent = 'Docker —';
|
||||
}
|
||||
@@ -2427,8 +2445,7 @@ function loadContainerTop(containerId) {
|
||||
containerTopState.containerId = containerId;
|
||||
const el = document.getElementById('container-top-content');
|
||||
if (el) {
|
||||
el.innerHTML =
|
||||
'<div class="procs-loading"><i class="fas fa-circle-notch fa-spin"></i> Loading processes…</div>';
|
||||
el.innerHTML = `<div class="procs-loading">${jobSpinnerHtml('Loading processes…')}</div>`;
|
||||
}
|
||||
updateProcsResultCount(null, null);
|
||||
sendCommand('containerTop', { id: containerId });
|
||||
@@ -5664,7 +5681,7 @@ function populateNetworkingTab(config) {
|
||||
if (!joinPanel) return;
|
||||
joinPanel.hidden = false;
|
||||
if (joinSelect) {
|
||||
joinSelect.innerHTML = '<option value="">Loading…</option>';
|
||||
joinSelect.innerHTML = '<option value="">Loading networks…</option>';
|
||||
try {
|
||||
const res = await manager.request(Methods.listNetworks, {});
|
||||
const list = res?.data || res?.networks || [];
|
||||
@@ -6007,6 +6024,18 @@ function updateContainerDetailsStats(container) {
|
||||
ensureDetailsStatsWired();
|
||||
detailsStatsState.containerId = container.Id;
|
||||
ensureHistorySeries(container.Id);
|
||||
// Pending summary chips until history/live samples arrive
|
||||
for (const id of [
|
||||
'detail-stats-window',
|
||||
'detail-stats-cpu-p95',
|
||||
'detail-stats-mem-peak',
|
||||
'detail-stats-updated',
|
||||
]) {
|
||||
const el = document.getElementById(id);
|
||||
if (el && (!el.textContent || el.textContent === '—' || el.querySelector?.('.job-spinner'))) {
|
||||
el.innerHTML = jobSpinnerPending();
|
||||
}
|
||||
}
|
||||
updateContainerDetailsStatsLive(container.Id);
|
||||
fetchServerStatsHistory(container.Id, true);
|
||||
}
|
||||
@@ -6428,8 +6457,7 @@ async function startDetailsLogs(containerId) {
|
||||
btn.classList.toggle('active', btn.dataset.filter === 'all');
|
||||
});
|
||||
|
||||
logsContent.innerHTML =
|
||||
'<div class="text-muted text-center logs-loading">Loading logs...</div>';
|
||||
logsContent.innerHTML = `<div class="text-muted text-center logs-loading">${jobSpinnerHtml('Loading logs…')}</div>`;
|
||||
|
||||
const handler = (logData) => appendDetailsLogChunk(logData, logsContent, gen);
|
||||
handler._pdDetailsLogs = true;
|
||||
@@ -7788,9 +7816,18 @@ function setDeployTemplates(templates, stats) {
|
||||
}
|
||||
window.__peardockSetDeployTemplates = setDeployTemplates;
|
||||
|
||||
/** Show tray-style spinner in the template count line while lists load. */
|
||||
function setDeployTemplateCountLoading(message = 'Loading templates…') {
|
||||
const countEl = document.getElementById('deploy-template-count');
|
||||
if (!countEl) return;
|
||||
countEl.classList.add('d-flex', 'align-items-center', 'gap-2', 'deploy-template-count--loading');
|
||||
countEl.innerHTML = jobSpinnerHtml(message);
|
||||
}
|
||||
|
||||
function updateDeployTemplateCount(query = '') {
|
||||
const countEl = document.getElementById('deploy-template-count');
|
||||
if (!countEl) return;
|
||||
countEl.classList.remove('deploy-template-count--loading');
|
||||
const total = deployViewTemplates.length;
|
||||
const q = String(query || '').trim();
|
||||
const filtered = q ? filterTemplatesByQuery(deployViewTemplates, q).length : total;
|
||||
@@ -7856,11 +7893,12 @@ async function loadDeployView() {
|
||||
});
|
||||
}
|
||||
|
||||
// Show loading state only when we don't have cache
|
||||
// Show tray-style loading state only when we don't have cache
|
||||
if (!deployViewTemplates.length) {
|
||||
const n = getTemplateListUrls().length;
|
||||
templateListContainer.innerHTML =
|
||||
`<div class="text-center text-muted py-4"><i class="fas fa-spinner fa-spin me-2"></i>Loading templates from ${n} list${n === 1 ? '' : 's'}…</div>`;
|
||||
const msg = `Loading templates from ${n} list${n === 1 ? '' : 's'}…`;
|
||||
setDeployTemplateCountLoading(msg);
|
||||
templateListContainer.innerHTML = jobSpinnerLoadingBlock(msg);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -7881,6 +7919,11 @@ async function loadDeployView() {
|
||||
updateDeployTemplateCount(query);
|
||||
} catch (error) {
|
||||
console.error('[ERROR] Failed to fetch templates:', error.message);
|
||||
const countEl = document.getElementById('deploy-template-count');
|
||||
if (countEl) {
|
||||
countEl.classList.remove('deploy-template-count--loading');
|
||||
countEl.textContent = 'Failed to load templates';
|
||||
}
|
||||
templateListContainer.innerHTML =
|
||||
'<div class="text-center text-danger py-4"><i class="fas fa-exclamation-triangle me-2"></i>Failed to load templates. Check network access and Settings → Deploy template lists.</div>';
|
||||
}
|
||||
@@ -10176,9 +10219,12 @@ function applyStatsToRow(row, stats) {
|
||||
const ipEl = row.querySelector('.ip-address');
|
||||
|
||||
const cpuText = formatCpuDisplay(stats.cpu);
|
||||
if (cpuEl && cpuEl.textContent !== cpuText) {
|
||||
cpuEl.textContent = cpuText;
|
||||
cpuEl.title = cpuText;
|
||||
if (cpuEl) {
|
||||
// Clear pending spinner HTML when real stats arrive
|
||||
if (cpuEl.querySelector?.('.job-spinner') || cpuEl.textContent !== cpuText) {
|
||||
cpuEl.textContent = cpuText;
|
||||
cpuEl.title = cpuText;
|
||||
}
|
||||
}
|
||||
if (cpuBar) {
|
||||
// Bar is % of one full core-equivalent up to 100% (cap display)
|
||||
@@ -10189,10 +10235,12 @@ function applyStatsToRow(row, stats) {
|
||||
}
|
||||
|
||||
const memText = formatMemDisplay(stats.memory);
|
||||
if (memoryEl && memoryEl.textContent !== memText) {
|
||||
memoryEl.textContent = memText;
|
||||
const lim = stats.memoryLimit ? ` / ${formatMemDisplay(stats.memoryLimit)}` : '';
|
||||
memoryEl.title = `${memText}${lim}`;
|
||||
if (memoryEl) {
|
||||
if (memoryEl.querySelector?.('.job-spinner') || memoryEl.textContent !== memText) {
|
||||
memoryEl.textContent = memText;
|
||||
const lim = stats.memoryLimit ? ` / ${formatMemDisplay(stats.memoryLimit)}` : '';
|
||||
memoryEl.title = `${memText}${lim}`;
|
||||
}
|
||||
}
|
||||
if (memoryBar) {
|
||||
const memoryPercent = memoryBarPercent(stats.memory, stats.memoryLimit);
|
||||
@@ -10493,8 +10541,9 @@ function buildContainerRow(container) {
|
||||
const state = container.State || 'Unknown';
|
||||
const statusClass = containerStatusClass(state);
|
||||
const prior = smoothedStats[containerId];
|
||||
const cpuText = prior ? formatCpuDisplay(prior.cpu) : '—';
|
||||
const memText = prior ? formatMemDisplay(prior.memory) : '—';
|
||||
// Pending first stats sample → tray spinner (not em-dash)
|
||||
const cpuText = prior ? formatCpuDisplay(prior.cpu) : jobSpinnerPending();
|
||||
const memText = prior ? formatMemDisplay(prior.memory) : jobSpinnerPending();
|
||||
const cpuWidth = prior ? Math.min(100, Math.max(0, prior.cpu || 0)) : 0;
|
||||
const memWidth = prior
|
||||
? memoryBarPercent(prior.memory, prior.memoryLimit)
|
||||
|
||||
+19
-15
@@ -53,8 +53,8 @@
|
||||
</div>
|
||||
<div id="connection-health-badge" class="connection-health-badge" title="Connection health">
|
||||
<span class="health-dot health-dot--unknown" id="health-dot"></span>
|
||||
<span id="health-latency" class="health-latency">—</span>
|
||||
<span id="health-docker" class="health-docker">Docker —</span>
|
||||
<span id="health-latency" class="health-latency"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></span>
|
||||
<span id="health-docker" class="health-docker">Docker <span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></span>
|
||||
<span id="health-role" class="health-role"></span>
|
||||
</div>
|
||||
<!-- Recent activity (job history) -->
|
||||
@@ -451,10 +451,14 @@
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<input type="search" id="deploy-template-search-input" class="form-control bg-dark text-white" placeholder="Search templates (debounced)…" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="small text-muted mb-2" id="deploy-template-count">—</div>
|
||||
<div class="small text-muted mb-2 deploy-template-count d-flex align-items-center gap-2" id="deploy-template-count">
|
||||
<span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span>
|
||||
<span>Loading templates…</span>
|
||||
</div>
|
||||
<div id="deploy-template-list-container" style="flex: 1; overflow-y: auto; overflow-x: hidden; min-height: 0;">
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i>Loading templates...
|
||||
<div class="text-center text-muted py-4 d-flex align-items-center justify-content-center gap-2">
|
||||
<span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span>
|
||||
<span>Loading templates…</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1972,10 +1976,10 @@
|
||||
|
||||
<div class="detail-stats-summary" id="detail-stats-summary">
|
||||
<div class="detail-stats-sum-item"><span>Samples</span><strong id="detail-stats-samples">0</strong></div>
|
||||
<div class="detail-stats-sum-item"><span>Window</span><strong id="detail-stats-window">—</strong></div>
|
||||
<div class="detail-stats-sum-item"><span>CPU p95</span><strong id="detail-stats-cpu-p95">—</strong></div>
|
||||
<div class="detail-stats-sum-item"><span>Mem peak</span><strong id="detail-stats-mem-peak">—</strong></div>
|
||||
<div class="detail-stats-sum-item"><span>Updated</span><strong id="detail-stats-updated">—</strong></div>
|
||||
<div class="detail-stats-sum-item"><span>Window</span><strong id="detail-stats-window"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></strong></div>
|
||||
<div class="detail-stats-sum-item"><span>CPU p95</span><strong id="detail-stats-cpu-p95"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></strong></div>
|
||||
<div class="detail-stats-sum-item"><span>Mem peak</span><strong id="detail-stats-mem-peak"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></strong></div>
|
||||
<div class="detail-stats-sum-item"><span>Updated</span><strong id="detail-stats-updated"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></strong></div>
|
||||
</div>
|
||||
|
||||
<div id="stats-charts-container" class="detail-stats-charts">
|
||||
@@ -2189,7 +2193,7 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-hover table-sm">
|
||||
<thead><tr><th>Name</th><th>Image</th><th>Replicas</th><th>ID</th><th></th></tr></thead>
|
||||
<tbody id="swarm-services-body"><tr><td colspan="5" class="text-muted">—</td></tr></tbody>
|
||||
<tbody id="swarm-services-body"><tr class="pd-loading-row"><td colspan="5" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2197,7 +2201,7 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-hover table-sm">
|
||||
<thead><tr><th>Hostname</th><th>Role</th><th>Status</th><th>Availability</th><th>ID</th></tr></thead>
|
||||
<tbody id="swarm-nodes-body"><tr><td colspan="5" class="text-muted">—</td></tr></tbody>
|
||||
<tbody id="swarm-nodes-body"><tr class="pd-loading-row"><td colspan="5" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2205,7 +2209,7 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-hover table-sm">
|
||||
<thead><tr><th>Service</th><th>Node</th><th>Desired</th><th>Current</th><th>ID</th></tr></thead>
|
||||
<tbody id="swarm-tasks-body"><tr><td colspan="5" class="text-muted">—</td></tr></tbody>
|
||||
<tbody id="swarm-tasks-body"><tr class="pd-loading-row"><td colspan="5" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2221,7 +2225,7 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-hover table-sm">
|
||||
<thead><tr><th>Name</th><th>Created</th><th>ID</th></tr></thead>
|
||||
<tbody id="swarm-secrets-body"><tr><td colspan="3" class="text-muted">—</td></tr></tbody>
|
||||
<tbody id="swarm-secrets-body"><tr class="pd-loading-row"><td colspan="3" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2237,7 +2241,7 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-hover table-sm">
|
||||
<thead><tr><th>Name</th><th>Created</th><th>ID</th></tr></thead>
|
||||
<tbody id="swarm-configs-body"><tr><td colspan="3" class="text-muted">—</td></tr></tbody>
|
||||
<tbody id="swarm-configs-body"><tr class="pd-loading-row"><td colspan="3" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2489,7 +2493,7 @@ services:
|
||||
<div class="col-lg-6">
|
||||
<div class="settings-section">
|
||||
<h3>Metrics</h3>
|
||||
<div id="host-metrics-summary" class="small text-muted">—</div>
|
||||
<div id="host-metrics-summary" class="small text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Waiting for peer…</span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
|
||||
@@ -4,7 +4,12 @@
|
||||
*/
|
||||
import { manager, Methods } from '../client/manager.js'
|
||||
import { presentError } from '../client/errors.js'
|
||||
import { showAlert, showStatusIndicator, hideStatusIndicator } from './uiUtils.js'
|
||||
import {
|
||||
showAlert,
|
||||
showStatusIndicator,
|
||||
hideStatusIndicator,
|
||||
jobSpinnerHtml,
|
||||
} from './uiUtils.js'
|
||||
import {
|
||||
createProgressBar,
|
||||
updateProgressBar,
|
||||
@@ -431,8 +436,8 @@ export async function loadRegistryCatalog(opts = {}) {
|
||||
return
|
||||
}
|
||||
if (status) {
|
||||
status.textContent = 'Loading catalog…'
|
||||
status.className = 'small text-muted mb-2'
|
||||
status.className = 'small text-muted mb-2 d-flex align-items-center gap-2'
|
||||
status.innerHTML = jobSpinnerHtml('Loading catalog…')
|
||||
}
|
||||
try {
|
||||
const args = {
|
||||
@@ -572,7 +577,7 @@ export async function openRepository(repo) {
|
||||
const empty = document.getElementById('registry-tags-empty')
|
||||
if (empty) {
|
||||
empty.classList.remove('d-none')
|
||||
empty.textContent = `Loading tags for ${name}…`
|
||||
empty.innerHTML = jobSpinnerHtml(`Loading tags for ${name}…`)
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+10
-3
@@ -1,5 +1,12 @@
|
||||
// Import dependencies first (ES6 imports must be at top)
|
||||
import { closeAllModals, showStatusIndicator, hideStatusIndicator, updateStatusIndicator, showAlert } from './uiUtils.js';
|
||||
import {
|
||||
closeAllModals,
|
||||
showStatusIndicator,
|
||||
hideStatusIndicator,
|
||||
updateStatusIndicator,
|
||||
showAlert,
|
||||
jobSpinnerLoadingBlock,
|
||||
} from './uiUtils.js';
|
||||
import { fetchMergedTemplates, getTemplateListUrls } from '../client/templateLists.js';
|
||||
import {
|
||||
isStackTemplate,
|
||||
@@ -1439,8 +1446,8 @@ async function loadDirectoryContents(path) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading
|
||||
fileBrowserContent.innerHTML = '<div class="text-center p-4"><i class="fas fa-spinner fa-spin"></i> Loading...</div>';
|
||||
// Show tray-style loading
|
||||
fileBrowserContent.innerHTML = jobSpinnerLoadingBlock('Loading…');
|
||||
|
||||
// Update breadcrumb
|
||||
if (fileBrowserBreadcrumb) {
|
||||
|
||||
@@ -421,7 +421,70 @@ function escapeToast(s) {
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Job-tray spinner (same markup as client/jobs.js active step).
|
||||
* Use for pending / loading UI instead of em-dash "—" placeholders.
|
||||
*/
|
||||
export const JOB_SPINNER_HTML =
|
||||
'<span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span>';
|
||||
|
||||
/**
|
||||
* @param {string} [label]
|
||||
* @param {{ center?: boolean, className?: string }} [opts]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function jobSpinnerHtml(label = 'Loading…', opts = {}) {
|
||||
const cls = [
|
||||
'pd-job-loading',
|
||||
'd-inline-flex',
|
||||
'align-items-center',
|
||||
'gap-2',
|
||||
opts.center ? 'justify-content-center' : '',
|
||||
opts.className || '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
if (!label) {
|
||||
return `<span class="${cls}">${JOB_SPINNER_HTML}</span>`;
|
||||
}
|
||||
const safe = String(label)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
return `<span class="${cls}">${JOB_SPINNER_HTML}<span>${safe}</span></span>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact spinner for table cells / metric chips (no label).
|
||||
* @returns {string}
|
||||
*/
|
||||
export function jobSpinnerPending() {
|
||||
return JOB_SPINNER_HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading row for table bodies.
|
||||
* @param {number} colspan
|
||||
* @param {string} [label]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function jobSpinnerLoadingRow(colspan, label = 'Loading…') {
|
||||
return `<tr class="pd-loading-row"><td colspan="${colspan}" class="text-muted">${jobSpinnerHtml(label)}</td></tr>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Centered block for list containers / empty panes.
|
||||
* @param {string} [label]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function jobSpinnerLoadingBlock(label = 'Loading…') {
|
||||
return `<div class="text-center text-muted py-4 d-flex align-items-center justify-content-center gap-2">${jobSpinnerHtml(label)}</div>`;
|
||||
}
|
||||
|
||||
// Bridge for jobs.js (avoids circular import) — mark feedback when jobs complete
|
||||
if (typeof globalThis !== 'undefined') {
|
||||
globalThis.__peardockMarkFeedback = markFeedbackShown
|
||||
globalThis.__peardockJobSpinnerHtml = jobSpinnerHtml
|
||||
globalThis.__peardockJobSpinnerPending = jobSpinnerPending
|
||||
}
|
||||
|
||||
+38
-9
@@ -30,6 +30,8 @@ import {
|
||||
showAlert,
|
||||
markFeedbackShown,
|
||||
clearTopToasts,
|
||||
jobSpinnerHtml,
|
||||
jobSpinnerLoadingRow,
|
||||
} from '../libs/uiUtils.js'
|
||||
import {
|
||||
warmSnapshot,
|
||||
@@ -887,7 +889,8 @@ export async function loadHostView(opts = {}) {
|
||||
if (summary) summary.textContent = 'Not connected'
|
||||
return
|
||||
}
|
||||
if (summary && !silent) summary.innerHTML = '<span class="text-muted">Loading…</span>'
|
||||
if (summary && !silent) summary.innerHTML = jobSpinnerHtml('Loading host…')
|
||||
if (metricsEl && !silent) metricsEl.innerHTML = jobSpinnerHtml('Loading metrics…')
|
||||
try {
|
||||
const [snap, metrics, sys] = await Promise.all([
|
||||
getSnapshot({ force: true }),
|
||||
@@ -915,14 +918,18 @@ export async function loadHostView(opts = {}) {
|
||||
.then((m) => m.loadSchedulesPanel?.())
|
||||
.catch(() => {})
|
||||
}
|
||||
if (metricsEl && metrics) {
|
||||
metricsEl.innerHTML = `
|
||||
if (metricsEl) {
|
||||
if (metrics) {
|
||||
metricsEl.innerHTML = `
|
||||
<div>Uptime: ${Math.round((metrics.uptimeMs || 0) / 1000)}s</div>
|
||||
<div>RPC total: ${metrics.rpc?.total ?? 0} · errors: ${metrics.rpc?.errors ?? 0}</div>
|
||||
<div>Latency p50/p99: ${metrics.rpc?.latencyMs?.p50 ?? 0} / ${metrics.rpc?.latencyMs?.p99 ?? 0} ms</div>
|
||||
<div>RSS: ${formatBytes(metrics.process?.rss)}</div>
|
||||
<div>Features: swarm=${metrics.features?.swarm} plugins=${metrics.features?.plugins} holesail=${metrics.features?.holesail}</div>
|
||||
`
|
||||
} else {
|
||||
metricsEl.textContent = 'Metrics unavailable'
|
||||
}
|
||||
}
|
||||
if (raw) {
|
||||
raw.textContent = JSON.stringify(sys?.data || eng.info || {}, null, 2)
|
||||
@@ -937,6 +944,7 @@ export async function loadEventsView(opts = {}) {
|
||||
const silent = opts.silent === true
|
||||
const host = document.getElementById('events-full-list')
|
||||
if (!host || !manager.active?.connected) return
|
||||
if (!silent) host.innerHTML = `<div class="text-muted p-3">${jobSpinnerHtml('Loading events…')}</div>`
|
||||
try {
|
||||
const res = await manager.request(Methods.getDockerEvents, { timeoutMs: 1500 })
|
||||
const filter = (document.getElementById('events-filter')?.value || '').toLowerCase()
|
||||
@@ -991,6 +999,14 @@ export async function loadTunnelsView(opts = {}) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
if (banner) {
|
||||
banner.className = 'alert alert-secondary small mb-3'
|
||||
banner.innerHTML = jobSpinnerHtml('Loading tunnels…')
|
||||
}
|
||||
if (list) list.innerHTML = `<div class="text-muted small p-2">${jobSpinnerHtml('Loading…')}</div>`
|
||||
}
|
||||
|
||||
try {
|
||||
const statusRes = await manager.request(Methods.getHolesailStatus, {}).catch(() => null)
|
||||
const st = statusRes?.status || {}
|
||||
@@ -1188,6 +1204,24 @@ export async function loadSwarmView(opts = {}) {
|
||||
}
|
||||
return
|
||||
}
|
||||
const svcBody = document.getElementById('swarm-services-body')
|
||||
const nodeBody = document.getElementById('swarm-nodes-body')
|
||||
const taskBody = document.getElementById('swarm-tasks-body')
|
||||
const secBody = document.getElementById('swarm-secrets-body')
|
||||
const cfgBody = document.getElementById('swarm-configs-body')
|
||||
|
||||
if (!silent) {
|
||||
if (svcBody) svcBody.innerHTML = jobSpinnerLoadingRow(5, 'Loading services…')
|
||||
if (nodeBody) nodeBody.innerHTML = jobSpinnerLoadingRow(5, 'Loading nodes…')
|
||||
if (taskBody) taskBody.innerHTML = jobSpinnerLoadingRow(5, 'Loading tasks…')
|
||||
if (secBody) secBody.innerHTML = jobSpinnerLoadingRow(3, 'Loading secrets…')
|
||||
if (cfgBody) cfgBody.innerHTML = jobSpinnerLoadingRow(3, 'Loading configs…')
|
||||
if (banner) {
|
||||
banner.className = 'alert alert-secondary small mb-3'
|
||||
banner.innerHTML = jobSpinnerHtml('Loading Swarm…')
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const inspect = await manager.request(Methods.swarmInspect, {})
|
||||
if (banner) {
|
||||
@@ -1202,11 +1236,6 @@ export async function loadSwarmView(opts = {}) {
|
||||
manager.request(Methods.listSecrets, {}).catch(() => ({ data: [] })),
|
||||
manager.request(Methods.listConfigs, {}).catch(() => ({ data: [] })),
|
||||
])
|
||||
const svcBody = document.getElementById('swarm-services-body')
|
||||
const nodeBody = document.getElementById('swarm-nodes-body')
|
||||
const taskBody = document.getElementById('swarm-tasks-body')
|
||||
const secBody = document.getElementById('swarm-secrets-body')
|
||||
const cfgBody = document.getElementById('swarm-configs-body')
|
||||
const svcs = services?.data || []
|
||||
const nds = nodes?.data || []
|
||||
const tks = tasks?.data || []
|
||||
@@ -1493,7 +1522,7 @@ export function loadSettingsView(forceTab) {
|
||||
if (!c?.connected) {
|
||||
feat.textContent = 'Connect a peer to inspect feature flags.'
|
||||
} else {
|
||||
feat.innerHTML = '<span class="text-muted">Loading…</span>'
|
||||
feat.innerHTML = jobSpinnerHtml('Loading feature flags…')
|
||||
manager
|
||||
.request(Methods.getMetrics, {})
|
||||
.then((res) => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* create, schedules, registry hub search, fleet env filter, stack env file.
|
||||
*/
|
||||
import { manager, Methods } from '../client/manager.js'
|
||||
import { showAlert } from '../libs/uiUtils.js'
|
||||
import { showAlert, jobSpinnerHtml } from '../libs/uiUtils.js'
|
||||
import { presentError } from '../client/errors.js'
|
||||
import { confirmDialog } from './components.js'
|
||||
|
||||
@@ -74,7 +74,9 @@ async function refreshVolumeBrowser() {
|
||||
const list = document.getElementById('volume-browse-list')
|
||||
const pathEl = document.getElementById('volume-browse-path')
|
||||
if (pathEl) pathEl.textContent = volumeBrowseState.path
|
||||
if (list) list.innerHTML = '<div class="list-group-item text-muted">Loading…</div>'
|
||||
if (list) {
|
||||
list.innerHTML = `<div class="list-group-item text-muted">${jobSpinnerHtml('Loading…')}</div>`
|
||||
}
|
||||
try {
|
||||
const res = await manager.request(Methods.browseVolume, {
|
||||
name: volumeBrowseState.name,
|
||||
|
||||
Reference in New Issue
Block a user