Track E: recreate, system prune, Hub search, swarm secrets/configs
CI / test (push) Successful in 10m3s

Investigate Docker/Portainer gaps and ship high-value ops: recreate
container, system prune + builder prune UI, Docker Hub search in
pull modal, Swarm secrets/configs tables. Update roadmap Track E/F.
This commit is contained in:
2026-07-10 23:48:30 -04:00
parent 869ba6a932
commit b62bcd697e
10 changed files with 490 additions and 92 deletions
+29 -1
View File
@@ -816,14 +816,18 @@ export async function loadSwarmView(opts = {}) {
const id = inspect?.data?.ID || inspect?.data?.id || '—'
banner.innerHTML = `Swarm active · ID <code>${escape(String(id).slice(0, 16))}</code>`
}
const [services, nodes, tasks] = await Promise.all([
const [services, nodes, tasks, secrets, configs] = await Promise.all([
manager.request(Methods.listServices, {}).catch(() => ({ data: [] })),
manager.request(Methods.listNodes, {}).catch(() => ({ data: [] })),
manager.request(Methods.listTasks, {}).catch(() => ({ data: [] })),
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 || []
if (svcBody) {
svcBody.innerHTML = svcs.length
@@ -864,6 +868,30 @@ export async function loadSwarmView(opts = {}) {
.join('')
: '<tr><td colspan="5" class="text-muted">No tasks</td></tr>'
}
const secs = secrets?.data || []
if (secBody) {
secBody.innerHTML = secs.length
? secs
.map((s) => {
const name = s.Spec?.Name || '—'
const created = s.CreatedAt ? new Date(s.CreatedAt).toLocaleString() : '—'
return `<tr><td>${escape(name)}</td><td class="small">${escape(created)}</td><td class="small font-monospace">${escape(String(s.ID || '').slice(0, 12))}</td></tr>`
})
.join('')
: '<tr><td colspan="3" class="text-muted">No secrets</td></tr>'
}
const cfgs = configs?.data || []
if (cfgBody) {
cfgBody.innerHTML = cfgs.length
? cfgs
.map((c) => {
const name = c.Spec?.Name || '—'
const created = c.CreatedAt ? new Date(c.CreatedAt).toLocaleString() : '—'
return `<tr><td>${escape(name)}</td><td class="small">${escape(created)}</td><td class="small font-monospace">${escape(String(c.ID || '').slice(0, 12))}</td></tr>`
})
.join('')
: '<tr><td colspan="3" class="text-muted">No configs</td></tr>'
}
} catch (err) {
const code = err?.code || ''
const msg = err?.message || String(err)