Render admin cron list as a nested directory tree.

Drop the leading-dot decoration and indent child folders/sessions with padding so user → job → run reads like a real folder hierarchy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-08 17:17:35 +08:00
parent 040d973fdf
commit ae4b7611ed

View file

@ -290,10 +290,7 @@ window.__ModuleLoader__.load({
.dsh-ct-projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}
.dsh-ct-title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}
.dsh-ct-session .dsh-ct-title{flex:1;margin:0 6px 0 4px}
.dsh-ct-treeLead{width:16px;height:20px;flex:none;justify-content:center;align-items:center;display:inline-flex;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:20px;letter-spacing:0.5px;opacity:.72;user-select:none}
.dsh-ct-depth1{padding-left:20px!important}
.dsh-ct-depth2{padding-left:36px!important}
.dsh-ct-ownerChild{margin-left:0}
.dsh-ct-dirChildren{padding-left:16px;box-sizing:border-box}
.dsh-ct-time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:20px}
.dsh-ct-rowActs{flex:none;align-items:center;gap:12px;display:none;height:20px}
.dsh-ct-project:hover .dsh-ct-rowActs,.dsh-ct-session:hover .dsh-ct-rowActs{display:inline-flex}
@ -740,22 +737,18 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
return name && name !== key ? `${key} · ${name}` : key
}
function treeLead() {
return h('span', { className: 'dsh-ct-treeLead', 'aria-hidden': 'true' }, '\u00b7\u00b7\u00b7')
}
function renderRunRows(job, depth) {
function renderRunRows(job) {
const jobRuns = runs.filter((run) => run.jobId === job.id)
return jobRuns.map((run) => {
const selectedRun = selection?.type === 'run' && selection.runId === run.id
return h('div', {
key: run.id,
className: joinClass(skin.session, depth >= 2 ? 'dsh-ct-depth2' : 'dsh-ct-depth1'),
className: skin.session,
role: 'treeitem',
'aria-level': 3,
'data-on': selectedRun ? 'true' : 'false',
onClick: () => onSelectRun(job.id, run),
},
treeLead(),
h('span', { className: skin.slot }, sessionIcon() || '·'),
h('span', { className: skin.title }, (run.summary || run.status || '').split('\n')[0] || run.status),
h('span', { className: skin.time }, formatTime(run.actualAt || run.scheduledAt, job.schedule?.timezone)),
@ -763,24 +756,22 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
})
}
// depth 0: top-level job (normal users). depth 1: job under a user folder (super/fallback).
function renderJobGroup(job, depth = 0) {
// Directory node: job folder → session leaves. When nestedUnderUser, this is level 2.
function renderJobGroup(job, nestedUnderUser = false) {
const open = expanded[job.id] === true || !!needle
const paused = job.enabled === false
const selectedJob = selection?.type === 'job' && selection.jobId === job.id
const containsCurrent = selectedJob || (selection?.type === 'run' && selection.jobId === job.id)
const nested = depth > 0
return h('div', { className: joinClass(skin.group, nested && 'dsh-ct-ownerChild'), key: job.id },
return h('div', { className: skin.group, key: job.id },
h('div', {
className: joinClass(skin.project, nested && 'dsh-ct-depth1'),
className: skin.project,
role: 'treeitem',
'aria-expanded': open,
'aria-level': nested ? 2 : 1,
'aria-level': nestedUnderUser ? 2 : 1,
'data-on': selectedJob ? 'true' : 'false',
'data-paused': paused ? 'true' : 'false',
onClick: () => onSelectJob(job.id),
},
nested ? treeLead() : null,
h('span', {
className: joinClass(skin.slot, skin.folder, open && containsCurrent ? skin.folderOn : ''),
'data-on': open && containsCurrent ? 'true' : 'false',
@ -811,13 +802,14 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
),
),
h('div', { className: 'dsh-ct-runs', 'data-open': open ? 'true' : 'false' },
h('div', { className: 'dsh-ct-runsInner' },
renderRunRows(job, nested ? 2 : 1),
h('div', { className: joinClass('dsh-ct-runsInner', 'dsh-ct-dirChildren') },
renderRunRows(job),
),
),
)
}
// Super / fallback_admin: user folder → job folder → session leaf.
function renderGrouped() {
const groups = new Map()
for (const job of visibleJobs) {
@ -862,8 +854,8 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
h('span', { className: skin.title }, `${group.label} · ${group.jobs.length}`)),
),
h('div', { className: 'dsh-ct-runs', 'data-open': open ? 'true' : 'false' },
h('div', { className: 'dsh-ct-runsInner' },
group.jobs.map((job) => renderJobGroup(job, 1)),
h('div', { className: joinClass('dsh-ct-runsInner', 'dsh-ct-dirChildren') },
group.jobs.map((job) => renderJobGroup(job, true)),
),
),
)