Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Upload 6 files
Browse files- web/static/app.css +11 -20
- web/static/app.js +31 -102
- web/static/components.js +11 -1
- web/static/progress.js +8 -18
- web/static/runs.js +12 -134
web/static/app.css
CHANGED
|
@@ -1,22 +1,13 @@
|
|
| 1 |
:root{
|
| 2 |
-
--bg:#
|
| 3 |
-
--blue:#
|
| 4 |
-
--radius:18px;--radius-sm:12px;--shadow:0
|
| 5 |
}
|
| 6 |
-
*{box-sizing:border-box}
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
.
|
| 10 |
-
.
|
| 11 |
-
.
|
| 12 |
-
|
| 13 |
-
.
|
| 14 |
-
.top-tabs{display:flex;gap:8px;align-items:center;background:rgba(255,255,255,.78);border:1px solid var(--line);box-shadow:var(--shadow-sm);border-radius:18px;padding:6px;margin-bottom:18px;width:max-content;max-width:100%;overflow:auto}.nav-item{border:0;background:transparent;padding:11px 16px;border-radius:13px;color:#334155;font-weight:850;cursor:pointer;white-space:nowrap;transition:.16s ease}.nav-item:hover{background:#f1f5f9}.nav-item.active{background:var(--blue);color:#fff;box-shadow:0 9px 22px rgba(37,99,235,.22)}
|
| 15 |
-
.main{min-width:0}.auth-panel,.operation-status{border:1px solid var(--line);border-radius:14px;padding:11px 14px;margin-bottom:16px;background:#fff;color:#334155;box-shadow:0 8px 20px rgba(15,23,42,.04);font-size:14px}.auth-panel.ok{display:none}.auth-panel.warning,.operation-status.warning{border-color:#f59e0b;background:var(--amber-soft);color:#92400e}.operation-status.loading{border-color:#bfdbfe;background:#eff6ff;color:#1d4ed8}.operation-status.success{border-color:rgba(22,163,74,.25);background:var(--green-soft);color:#166534}.operation-status.error{border-color:#fecaca;background:#fef2f2;color:#991b1b}.auth-panel p{margin:4px 0 10px;color:inherit;font-size:13px}
|
| 16 |
-
.single-column{max-width:1020px}.dashboard-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(360px,430px);gap:18px;align-items:start}.workspace-column{display:flex;flex-direction:column;gap:16px}.right-rail{display:flex;flex-direction:column;gap:16px;min-width:0}.panel,.progress-card,.bucket-strip{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px}.bucket-strip{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:16px}.eyebrow{display:block;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.08em;font-weight:850;margin-bottom:4px}.bucket-source{display:flex;gap:9px;align-items:center}.bucket-source strong{font-size:15px}.mini-copy{margin:8px 0 0}.badge{font-size:12px;border-radius:999px;padding:4px 10px;margin-left:8px;display:inline-flex;align-items:center;gap:5px;font-weight:850;background:#f1f5f9;color:#475569}.badge.success,.running,.live{background:var(--green-soft);color:#15803d}.badge.warn{background:var(--amber-soft);color:#92400e}.badge.error{background:var(--red-soft);color:#991b1b}.badge.neutral{background:#eef2ff;color:#3730a3}.strip-actions{display:flex;gap:8px;flex-wrap:wrap}.hero-card{max-width:1000px}.panel h2,.progress-card h2{font-size:18px;letter-spacing:-.02em;margin:0 0 12px}.panel-title-row,.panel-head,.progress-head{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap}.form-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:13px}.compact-form{margin-top:14px}label{font-size:13px;font-weight:750;color:#334155}input,select,textarea{width:100%;margin-top:6px;border:1px solid var(--line);background:#fff;border-radius:11px;padding:11px 12px;font:inherit;color:var(--ink);outline:none;transition:border .15s ease,box-shadow .15s ease}input:focus,select:focus,textarea:focus{border-color:#bfdbfe;box-shadow:0 0 0 4px #dbeafe}textarea{resize:vertical;min-height:100px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-size:13px;line-height:1.45}.toggles{display:grid;gap:9px;margin:15px 0}.toggles label{border:1px solid var(--line);border-radius:13px;padding:10px 12px;background:#fafcff;display:flex;gap:10px;align-items:flex-start}.toggles input{width:auto;margin:2px 0 0}.toggles small{display:block;color:var(--muted);font-weight:500;margin-top:2px}.primary,.secondary,.link-btn{border-radius:11px;padding:10px 13px;font-weight:850;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:.15s ease}.primary{border:0;background:linear-gradient(135deg,var(--blue),var(--blue2));color:white;width:100%;font-size:15px;box-shadow:0 10px 24px rgba(37,99,235,.25)}.primary:hover,.secondary:hover,.link-btn:hover{transform:translateY(-1px)}.primary:disabled,.secondary:disabled{opacity:.5;cursor:not-allowed;transform:none}.secondary{border:1px solid var(--line);background:#fff;color:#334155}.secondary.small{padding:8px 10px;font-size:12px}.secondary.full{width:100%;margin-top:12px}.link-btn{border:1px solid var(--line);color:#1f2937;background:#fff;font-size:13px}.link-btn.disabled{opacity:.45;pointer-events:none}.build-gate{margin:10px 0 0;text-align:center}.is-busy::after{content:"";width:12px;height:12px;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;display:inline-block;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}
|
| 17 |
-
.build-mode-panel{border:1px solid #dbeafe;background:linear-gradient(180deg,#fff,#f8fbff);border-radius:14px;padding:12px 13px;margin:12px 0}.build-mode-panel strong{color:#1d4ed8}.build-mode-panel p{margin:5px 0 0;color:var(--muted);font-size:13px}.build-mode-panel.inspecting{border-color:#f59e0b;background:var(--amber-soft)}.build-mode-panel.inspecting strong{color:#92400e}.build-mode-panel code{background:rgba(255,255,255,.65);border:1px solid #fde68a;border-radius:6px;padding:1px 4px}
|
| 18 |
-
.progress-card{margin:0}.progress-head strong:first-child{font-size:18px}.progress-head>strong{font-size:24px;letter-spacing:-.04em}.progress-bar{height:10px;background:#e6eaf1;border-radius:999px;margin:14px 0;overflow:hidden}.progress-bar div{height:100%;background:linear-gradient(90deg,var(--blue),#7c3aed);border-radius:999px}.progress-meta{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;border-top:1px solid var(--line);padding-top:13px}.progress-meta div{min-width:0;background:#fbfdff;border:1px solid var(--line);border-radius:12px;padding:10px}.progress-meta span,.detail-grid span{display:block;color:var(--muted);font-size:12px;margin-bottom:4px}.progress-meta strong,.detail-grid strong{overflow-wrap:anywhere;font-size:13px}.timeline{display:grid;grid-template-columns:repeat(auto-fit,minmax(118px,1fr));gap:8px;margin-top:16px;overflow:visible}.step{display:grid;grid-template-columns:26px minmax(0,1fr);gap:8px;align-items:center;padding:9px 10px;border:1px solid var(--line);border-radius:12px;background:#fbfdff;color:#475569;font-size:12px;min-height:46px}.step-dot{width:24px;height:24px;border-radius:50%;display:grid;place-items:center;font-size:12px;font-weight:900;border:1px solid #cbd5e1;background:#fff;color:#64748b}.step div:last-child{font-weight:750}.step::after{content:attr(data-status);grid-column:2;text-transform:uppercase;font-size:9px;letter-spacing:.06em;color:#64748b;font-weight:850;margin-top:-5px}.step.done{background:#f0fdf4;border-color:#bbf7d0}.step.done .step-dot{background:#dcfce7;border-color:var(--green);color:var(--green)}.step.running{background:#eff6ff;border-color:#bfdbfe}.step.running .step-dot{background:#dbeafe;border-color:var(--blue);color:var(--blue);box-shadow:0 0 0 3px #dbeafe}.step.failed{background:#fff1f2;border-color:#fecaca}.step.failed .step-dot{background:#fee2e2;border-color:var(--red);color:var(--red)}.step.pending{opacity:.78}.manual-action{margin-top:14px;border:1px solid #f59e0b;background:var(--amber-soft);border-radius:14px;padding:13px;display:flex;justify-content:space-between;gap:12px;align-items:center}.manual-action p{margin:4px 0 0;color:#92400e;font-size:13px}.manual-action strong{color:#92400e}.manual-action-buttons{display:flex;gap:8px;flex-wrap:wrap}.poll-warning{margin-top:12px;border:1px solid #f59e0b;background:var(--amber-soft);color:#92400e;border-radius:12px;padding:10px 12px;font-size:13px}.bottom-grid{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);gap:16px}.metric-list{list-style:none;margin:0;padding:0}.metric-list li{display:flex;justify-content:space-between;border-bottom:1px solid var(--line);padding:9px 0;gap:12px}.metric-list li:last-child{border-bottom:0}.metric-ok{color:var(--green)}.metric-warn{color:var(--amber)}.events{margin:0;background:#0f172a;border:1px solid #1e293b;border-radius:13px;padding:12px;min-height:160px;max-height:280px;overflow:auto;font-size:12px;color:#dbeafe;white-space:pre-wrap;line-height:1.5}
|
| 19 |
-
.run-filters{display:flex;gap:8px;flex-wrap:wrap;margin:8px 0 12px}.filter{border:1px solid var(--line);background:#fff;color:#334155;border-radius:999px;padding:7px 10px;font-weight:800;font-size:12px;cursor:pointer}.filter.active{background:#eaf2ff;border-color:#bfdbfe;color:#1d4ed8}.runs-table{border:1px solid var(--line);border-radius:14px;padding:0;overflow:hidden;color:var(--muted);background:#fff}.run-explorer.is-refreshing .runs-table{opacity:.65}.run-row{width:100%;display:grid;grid-template-columns:14px minmax(110px,1fr) minmax(95px,.9fr) auto minmax(90px,.8fr) 74px;gap:8px;align-items:center;border:0;border-bottom:1px solid var(--line);background:#fff;text-align:left;padding:10px 11px;cursor:pointer;color:var(--ink);font:inherit}.run-row:last-child{border-bottom:0}.run-row:hover{background:#f8fafc}.run-row.selected{background:#eef2ff}.run-row.selected strong{color:#1d4ed8}.run-id{font-size:12px}.dot{width:8px;height:8px;border-radius:999px;background:#94a3b8}.dot.success{background:var(--green)}.dot.running{background:var(--blue)}.dot.warn{background:var(--amber)}.dot.error{background:var(--red)}.pill{font-style:normal;font-size:10px;border-radius:999px;padding:4px 7px;background:#f1f5f9;color:#475569;white-space:nowrap;font-weight:850}.pill.success{background:#dcfce7;color:#166534}.pill.running{background:#dbeafe;color:#1d4ed8}.pill.warn{background:#fef3c7;color:#92400e}.pill.error{background:#fee2e2;color:#991b1b}.tiny-note{font-size:12px;margin:10px 0 0}.detail-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px}.detail-grid div{border:1px solid var(--line);border-radius:12px;padding:10px;min-width:0;background:#fbfdff}.home-detail-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.quick-links{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:13px}.report-home{border:1px solid var(--line);border-radius:13px;background:#fbfdff;padding:12px;margin-top:13px}.report-home strong{display:flex;gap:8px;align-items:center}.report-home p{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.45}.test-layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,360px);gap:18px}.large{max-width:900px}.help-panel{align-self:start}.blockers{border:1px solid #f59e0b;background:var(--amber-soft);border-radius:12px;padding:12px;margin:12px 0}.blockers strong{display:block;color:#92400e;font-size:13px}.blockers p{margin:4px 0 10px;color:#92400e;font-size:12px}.hidden-run-inspector{display:none!important}
|
| 20 |
-
.empty{padding:16px;color:var(--muted)}[hidden]{display:none!important}
|
| 21 |
-
@media(max-width:1280px){.dashboard-grid{grid-template-columns:1fr}.right-rail{display:grid;grid-template-columns:1fr 1fr}.progress-meta{grid-template-columns:repeat(2,minmax(0,1fr))}.test-layout{grid-template-columns:1fr}.single-column{max-width:none}}
|
| 22 |
-
@media(max-width:860px){.app-shell{padding:16px}.app-header{display:block}.top-actions{justify-content:flex-start;margin-top:12px}.top-tabs{width:100%}.nav-item{flex:1}.bucket-strip,.manual-action{display:block}.strip-actions,.manual-action-buttons{margin-top:12px}.form-grid,.bottom-grid,.right-rail,.detail-grid,.home-detail-grid,.quick-links,.progress-meta{grid-template-columns:1fr}.timeline{grid-template-columns:1fr 1fr}.run-row{grid-template-columns:14px minmax(0,1fr);gap:7px}.run-row span:nth-child(n+3),.run-row em{grid-column:2}.toggles{grid-template-columns:1fr}}
|
|
|
|
| 1 |
:root{
|
| 2 |
+
--bg:#f7f9fc;--panel:#fff;--panel2:#fbfdff;--ink:#101828;--muted:#667085;--soft:#98a2b3;--line:#e4e7ec;
|
| 3 |
+
--blue:#1f6feb;--blue2:#0f5bd7;--blue-soft:#eff6ff;--green:#16a34a;--green-soft:#ecfdf3;--amber:#f59e0b;--amber-soft:#fffbeb;--red:#ef4444;--red-soft:#fef2f2;--purple:#7c3aed;
|
| 4 |
+
--radius:18px;--radius-sm:12px;--shadow:0 14px 36px rgba(16,24,40,.07);--shadow-sm:0 8px 22px rgba(16,24,40,.055)
|
| 5 |
}
|
| 6 |
+
*{box-sizing:border-box}html,body{min-height:100%;background:var(--bg)}body{margin:0;color:var(--ink);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;-webkit-font-smoothing:antialiased;background:radial-gradient(circle at top,#fff 0,#f7f9fc 42%,#f3f6fb 100%)}
|
| 7 |
+
.app-shell{min-height:100vh;max-width:1540px;margin:0 auto;padding:18px 22px 28px}.topbar{height:auto;display:grid;grid-template-columns:minmax(260px,1fr) auto minmax(320px,1fr);gap:18px;align-items:center;border:1px solid var(--line);background:rgba(255,255,255,.9);box-shadow:var(--shadow-sm);border-radius:22px;padding:12px 16px;margin-bottom:18px;backdrop-filter:blur(10px)}
|
| 8 |
+
.brand{display:flex;align-items:center;gap:12px}.brand-mark{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;background:linear-gradient(135deg,var(--blue),var(--purple));color:#fff;font-size:22px;box-shadow:0 10px 24px rgba(31,111,235,.22)}.brand h1{font-size:22px;letter-spacing:-.035em;margin:0}.brand p{margin:2px 0 0;color:var(--muted);font-size:13px}.top-tabs{display:flex;align-items:center;gap:4px;background:#f2f4f7;border:1px solid var(--line);border-radius:15px;padding:4px}.nav-item{border:0;background:transparent;padding:10px 18px;border-radius:11px;color:#344054;font-weight:800;cursor:pointer;white-space:nowrap}.nav-item:hover{background:#fff}.nav-item.active{background:#fff;color:var(--blue);box-shadow:0 5px 15px rgba(16,24,40,.08)}.top-actions{display:flex;justify-content:flex-end;gap:10px;align-items:center}.bucket-pill,.user-chip,.logout-link{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--line);background:#fff;border-radius:999px;padding:9px 12px;color:#344054;font-size:13px;text-decoration:none;font-weight:700}.bucket-pill{cursor:pointer}.status-dot{width:8px;height:8px;border-radius:999px;background:#98a2b3;display:inline-block}.green{background:var(--green)}.amber-dot{background:var(--amber)}.red-dot{background:var(--red)}
|
| 9 |
+
.main{min-width:0}.auth-panel,.operation-status{border:1px solid var(--line);border-radius:14px;padding:12px 14px;margin-bottom:14px;background:#fff;color:#344054;box-shadow:var(--shadow-sm);font-size:14px}.auth-panel.warning,.operation-status.warning{border-color:#fbbf24;background:var(--amber-soft);color:#92400e}.operation-status.loading{border-color:#bfdbfe;background:var(--blue-soft);color:#1d4ed8}.operation-status.success{border-color:#bbf7d0;background:var(--green-soft);color:#166534}.operation-status.error{border-color:#fecaca;background:var(--red-soft);color:#991b1b}.auth-panel p{margin:4px 0 10px;color:inherit}.panel{background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);border-radius:var(--radius);padding:20px}.build-workspace{display:grid;grid-template-columns:minmax(390px,520px) minmax(0,1fr);gap:18px;align-items:start}.build-column,.runs-column{min-width:0}.section-head{display:flex;justify-content:space-between;align-items:flex-start;gap:14px;margin-bottom:16px}.section-head.compact{align-items:center}.eyebrow{display:block;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.08em;font-weight:900;margin-bottom:5px}h2{font-size:22px;line-height:1.15;letter-spacing:-.035em;margin:0}h3{font-size:18px;letter-spacing:-.02em;margin:0 0 8px}h4{font-size:14px;margin:0 0 12px;color:#1d2939}.muted{color:var(--muted);font-size:13px;line-height:1.45}.field-stack{display:grid;gap:13px}label{font-size:13px;font-weight:780;color:#344054}input,select,textarea{width:100%;margin-top:7px;border:1px solid var(--line);background:#fff;border-radius:11px;padding:11px 12px;font:inherit;color:var(--ink);outline:none;transition:border .15s ease,box-shadow .15s ease}textarea{resize:vertical;min-height:90px;font-family:inherit}input:focus,select:focus,textarea:focus{border-color:#93c5fd;box-shadow:0 0 0 4px #dbeafe}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.settings-box{border:1px solid var(--line);border-radius:15px;padding:12px;margin:16px 0;background:#fcfcfd}.settings-box summary{font-weight:850;cursor:pointer;color:#344054;margin-bottom:12px}.toggle-list{display:grid;gap:8px;margin-top:12px}.toggle-list label{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--line);padding-top:9px}.toggle-list small{display:block;color:var(--muted);font-weight:500;margin-top:2px}.toggle-list input{width:auto;margin:0;accent-color:var(--blue)}.primary,.secondary,.link-btn,.text-link{border-radius:11px;padding:10px 13px;font-weight:850;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:.15s ease}.primary{border:0;background:linear-gradient(135deg,var(--blue),var(--blue2));color:white;width:100%;font-size:15px;box-shadow:0 10px 24px rgba(31,111,235,.25)}.secondary{border:1px solid var(--line);background:#fff;color:#344054}.secondary.small{padding:8px 10px;font-size:12px}.secondary.full{width:100%;margin-top:12px}.link-btn{border:1px solid var(--line);background:#fff;color:#344054;font-size:13px}.link-btn.disabled{opacity:.45;pointer-events:none}.text-link{border:0;background:transparent;color:var(--blue);padding:10px 0;justify-content:flex-start}.primary:hover,.secondary:hover,.link-btn:hover{transform:translateY(-1px)}.primary:disabled,.secondary:disabled{opacity:.5;cursor:not-allowed;transform:none}.is-busy::after{content:"";width:12px;height:12px;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;display:inline-block;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}
|
| 10 |
+
.build-mode-panel{border:1px solid #bfdbfe;background:linear-gradient(180deg,#fff,#f8fbff);border-radius:14px;padding:12px;margin-bottom:14px}.build-mode-panel strong{color:#1d4ed8}.build-mode-panel p{margin:5px 0 0;color:var(--muted);font-size:13px}.build-mode-panel.inspecting{border-color:#f59e0b;background:var(--amber-soft)}.build-mode-panel.inspecting strong{color:#92400e}.build-mode-panel code{background:rgba(255,255,255,.8);border:1px solid #fde68a;border-radius:6px;padding:1px 4px}.run-storage-card{display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center;border:1px solid var(--line);border-radius:15px;padding:13px;background:#fff}.run-storage-card strong{display:flex;align-items:center;gap:8px}.inline-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.badge{font-size:12px;border-radius:999px;padding:5px 10px;display:inline-flex;align-items:center;gap:5px;font-weight:850;background:#f2f4f7;color:#475467}.badge.success,.pill.success,.status-chip.success{background:var(--green-soft);color:#15803d}.badge.warn,.pill.warn,.status-chip.warn{background:var(--amber-soft);color:#92400e}.badge.error,.pill.error,.status-chip.error{background:var(--red-soft);color:#991b1b}.badge.running,.pill.running,.status-chip.running{background:var(--blue-soft);color:#1d4ed8}.badge.neutral,.pill.neutral,.status-chip.neutral{background:#f2f4f7;color:#475467}.launch-summary{border:1px solid var(--line);border-radius:15px;padding:13px;margin:14px 0;background:#fbfdff}.launch-summary ul{margin:8px 0 0;padding:0;list-style:none}.launch-summary li{font-size:13px;color:#344054;margin:6px 0}.launch-summary li:before{content:"β";color:var(--green);font-weight:900;margin-right:8px}.build-gate{text-align:center;margin:9px 0 0}
|
| 11 |
+
.runs-layout{display:grid;grid-template-columns:300px minmax(0,1fr);gap:16px}.run-list-card{border:1px solid var(--line);border-radius:16px;padding:12px;background:#fbfdff;min-width:0}.search-wrap{position:relative;margin-bottom:10px}.search-wrap input{padding-right:36px;margin:0}.search-wrap span{position:absolute;right:12px;top:50%;transform:translateY(-50%);color:#667085}.run-filters{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}.filter{border:1px solid var(--line);background:#fff;color:#344054;border-radius:999px;padding:7px 10px;font-weight:800;font-size:12px;cursor:pointer}.filter.active{background:#eaf2ff;border-color:#93c5fd;color:#1d4ed8}.runs-table{border:1px solid var(--line);border-radius:14px;padding:0;overflow:hidden;background:#fff;color:var(--muted)}.run-explorer.is-refreshing .runs-table{opacity:.65}.run-row{width:100%;border:0;border-bottom:1px solid var(--line);background:#fff;text-align:left;padding:12px;display:grid;grid-template-columns:12px minmax(0,1fr) auto;gap:8px;align-items:start;color:var(--ink);font:inherit;cursor:pointer}.run-row:last-child{border-bottom:0}.run-row:hover{background:#f8fafc}.run-row.selected{background:#eef4ff;box-shadow:inset 3px 0 0 var(--blue)}.run-row .run-id{font-size:13px;overflow-wrap:anywhere}.run-row span:nth-child(3),.run-row span:nth-child(4),.run-row span:nth-child(5),.run-row span:nth-child(6){grid-column:2;color:var(--muted);font-size:12px}.dot{width:9px;height:9px;border-radius:999px;background:#98a2b3;margin-top:5px}.dot.success{background:var(--green)}.dot.running{background:var(--blue)}.dot.warn{background:var(--amber)}.dot.error{background:var(--red)}.pill{font-style:normal;font-size:10px;border-radius:999px;padding:4px 7px;background:#f2f4f7;color:#475467;font-weight:850;white-space:nowrap}.run-detail-card{border:1px solid var(--line);border-radius:16px;background:#fff;padding:16px;min-width:0}.run-summary-card{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,1fr) auto;gap:14px;align-items:start}.summary-meta{display:grid;grid-template-columns:1fr 1fr;gap:10px}.summary-meta div,.detail-grid>div,.diagnostic-list>div{border:1px solid var(--line);border-radius:11px;padding:9px;background:#fbfdff;min-width:0}.summary-meta span,.detail-grid span,.diagnostic-list span{display:block;color:var(--muted);font-size:11px;margin-bottom:4px}.summary-meta strong,.detail-grid strong,.diagnostic-list strong{font-size:13px;overflow-wrap:anywhere}.quick-links{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}.progress-bar{height:9px;background:#e5e7eb;border-radius:999px;overflow:hidden;margin:16px 0 7px}.progress-bar div{height:100%;background:linear-gradient(90deg,var(--blue),var(--purple));border-radius:999px}.progress-percent{display:flex;justify-content:space-between;gap:14px;color:var(--muted);font-size:13px;margin-bottom:14px}.progress-percent strong{color:var(--blue)}.timeline{display:grid;grid-template-columns:repeat(8,minmax(86px,1fr));gap:8px;margin:12px 0 16px}.step{position:relative;display:grid;place-items:center;text-align:center;gap:6px;color:#475467;font-size:12px;min-height:70px}.step:not(:last-child):after{content:"";position:absolute;height:2px;background:#d0d5dd;left:63%;right:-42%;top:18px}.step.completed:not(:last-child):after,.step.done:not(:last-child):after{background:#22c55e}.step.running:not(:last-child):after{background:linear-gradient(90deg,#22c55e,#93c5fd)}.step-dot{width:30px;height:30px;border-radius:999px;display:grid;place-items:center;background:#fff;border:2px solid #d0d5dd;color:#667085;font-weight:900;z-index:1}.step.completed .step-dot,.step.done .step-dot{background:#22c55e;border-color:#22c55e;color:#fff}.step.running .step-dot{background:#fff;border-color:var(--blue);color:var(--blue);box-shadow:0 0 0 4px #dbeafe}.step.failed .step-dot,.step.blocked .step-dot{background:#fff;border-color:var(--red);color:var(--red)}.step small{display:block;color:var(--muted);font-size:10px;margin-top:2px}.manual-action{margin:12px 0;border:1px solid #f59e0b;background:var(--amber-soft);border-radius:14px;padding:13px;display:flex;justify-content:space-between;gap:12px;align-items:center}.manual-action p{margin:4px 0 0;color:#92400e;font-size:13px}.manual-action strong{color:#92400e}.poll-warning{margin:12px 0;border:1px solid #f59e0b;background:var(--amber-soft);color:#92400e;border-radius:12px;padding:10px 12px;font-size:13px}.detail-grid{display:grid;gap:10px}.detail-grid.three{grid-template-columns:1.2fr .9fr 1fr;margin-top:14px}.detail-grid.four{grid-template-columns:repeat(4,minmax(0,1fr));margin-bottom:12px}.mini-panel{border:1px solid var(--line);border-radius:15px;background:#fbfdff;padding:13px;min-width:0}.activity-feed{display:grid;gap:8px}.activity-row{display:grid;grid-template-columns:62px 10px minmax(0,1fr) auto;gap:8px;align-items:start;padding:7px 0;border-bottom:1px solid var(--line)}.activity-row:last-child{border-bottom:0}.activity-row time{font-size:11px;color:var(--muted)}.activity-row .activity-dot{width:8px;height:8px;border-radius:999px;background:#98a2b3;margin-top:5px}.activity-row.success .activity-dot{background:var(--green)}.activity-row.running .activity-dot{background:var(--blue)}.activity-row.warning .activity-dot{background:var(--amber)}.activity-row.error .activity-dot{background:var(--red)}.activity-row strong{font-size:12px}.activity-row p{margin:2px 0 0;color:var(--muted);font-size:11px}.activity-row em{font-style:normal;font-size:10px;border:1px solid var(--line);border-radius:999px;padding:3px 6px;background:#fff;color:#667085;white-space:nowrap}.status-chips{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}.status-chip{border-radius:12px;padding:8px;background:#f2f4f7;display:flex;justify-content:space-between;gap:8px;font-size:11px}.status-chip em{font-style:normal}.issue-card{border-radius:12px;padding:10px;margin-top:10px;background:var(--amber-soft);border:1px solid #fde68a}.issue-card.error{background:var(--red-soft);border-color:#fecaca}.issue-card p{font-size:12px;color:#667085;margin:4px 0 0}.report-home p{font-size:12px;color:var(--muted);line-height:1.45}.empty{padding:13px;color:var(--muted);font-size:13px}.full-runs-grid{display:grid;grid-template-columns:360px minmax(0,1fr);gap:16px}.run-detail-card.expanded{min-height:540px}.extra-links{justify-content:flex-start;margin:10px 0}.split-panels{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:12px 0}.metric-list{list-style:none;margin:0;padding:0}.metric-list li{display:flex;justify-content:space-between;gap:12px;border-bottom:1px solid var(--line);padding:9px 0;font-size:13px}.metric-list li:last-child{border-bottom:0}.metric-ok{color:var(--green)}.metric-warn{color:var(--amber)}.artifact-list{display:grid;gap:6px;font-size:13px}.artifact-list a{display:flex;justify-content:space-between;gap:8px;color:var(--blue);text-decoration:none;border-bottom:1px solid var(--line);padding:6px 0}.artifact-list em{font-style:normal;color:var(--muted)}.report-preview,.events{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;background:#101828;color:#d0d5dd;border:1px solid #1d2939;border-radius:12px;min-height:190px;max-height:380px;overflow:auto;padding:12px;white-space:pre-wrap;font-size:12px;line-height:1.5}.validate-grid{display:grid;grid-template-columns:330px minmax(0,1fr) 280px;gap:16px}.result-card{border:1px solid var(--line);border-radius:15px;background:#fbfdff;padding:14px}.about-panel{max-width:980px;margin:0 auto}.about-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.about-grid div{border:1px solid var(--line);border-radius:15px;background:#fbfdff;padding:14px}.blockers{border:1px solid #f59e0b;background:var(--amber-soft);border-radius:12px;padding:12px;margin:12px 0}.blockers strong{display:block;color:#92400e;font-size:13px}.blockers p{margin:4px 0 10px;color:#92400e;font-size:12px}.hidden-run-inspector{display:none!important}[hidden]{display:none!important}
|
| 12 |
+
@media(max-width:1320px){.topbar{grid-template-columns:1fr}.top-tabs{width:max-content}.top-actions{justify-content:flex-start}.build-workspace{grid-template-columns:1fr}.runs-layout{grid-template-columns:320px minmax(0,1fr)}.timeline{grid-template-columns:repeat(4,1fr)}.detail-grid.three,.validate-grid{grid-template-columns:1fr}.full-runs-grid{grid-template-columns:1fr}.run-summary-card{grid-template-columns:1fr}.quick-links{justify-content:flex-start}}
|
| 13 |
+
@media(max-width:820px){.app-shell{padding:12px}.brand p{display:none}.top-tabs{overflow:auto;width:100%}.nav-item{flex:1}.form-grid,.summary-meta,.detail-grid.four,.split-panels,.about-grid,.runs-layout{grid-template-columns:1fr}.timeline{grid-template-columns:1fr 1fr}.step:not(:last-child):after{display:none}.run-storage-card{grid-template-columns:1fr}.manual-action{display:block}.manual-action .inline-actions{margin-top:10px}.activity-row{grid-template-columns:56px 10px 1fr}.activity-row em{grid-column:3}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web/static/app.js
CHANGED
|
@@ -1,114 +1,43 @@
|
|
| 1 |
-
const state={
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
bucketSource:null,
|
| 5 |
-
bucketReady:false,
|
| 6 |
-
poll:null,
|
| 7 |
-
activeLinks:{},
|
| 8 |
-
selectedRunDetail:null,
|
| 9 |
-
runStatusFilter:'all',
|
| 10 |
-
runsCache:[],
|
| 11 |
-
runsLastLoaded:0,
|
| 12 |
-
pollErrors:0,
|
| 13 |
-
busy:false,
|
| 14 |
-
user:null,
|
| 15 |
-
};
|
| 16 |
-
const TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed']);
|
| 17 |
-
const DEFAULT_TIMELINE=[
|
| 18 |
-
{label:'Bucket ready',status:'pending'},
|
| 19 |
-
{label:'Job launched',status:'pending'},
|
| 20 |
-
{label:'Dependencies',status:'pending'},
|
| 21 |
-
{label:'Authenticated',status:'pending'},
|
| 22 |
-
{label:'Model analysis',status:'pending'},
|
| 23 |
-
{label:'Workspace',status:'pending'},
|
| 24 |
-
{label:'Pi installed',status:'pending'},
|
| 25 |
-
{label:'Pi running',status:'pending'},
|
| 26 |
-
{label:'Create Space',status:'pending'},
|
| 27 |
-
{label:'Upload files',status:'pending'},
|
| 28 |
-
{label:'Hardware request',status:'pending'},
|
| 29 |
-
{label:'API validation',status:'pending'},
|
| 30 |
-
{label:'Smoke test',status:'pending'},
|
| 31 |
-
{label:'Done',status:'pending'},
|
| 32 |
-
];
|
| 33 |
-
function $(id){return document.getElementById(id)}
|
| 34 |
-
function formValue(id){return ($(id)?.value||'').trim()}
|
| 35 |
-
function setText(id,value){const el=$(id);if(el)el.textContent=value??'β'}
|
| 36 |
-
function setInputValue(id,value){const el=$(id);if(!el)return;el.value=value||''}
|
| 37 |
-
function escapeHtml(value){return String(value??'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]))}
|
| 38 |
function saveActiveRun(){try{localStorage.setItem('asf.activeRun',JSON.stringify({runId:state.runId,bucketName:state.bucketName,links:state.activeLinks||{}}))}catch(_){}}
|
| 39 |
function clearSavedRun(){try{localStorage.removeItem('asf.activeRun')}catch(_){}}
|
| 40 |
-
function
|
| 41 |
-
|
| 42 |
-
function
|
| 43 |
-
|
| 44 |
-
if(operationTimer){clearTimeout(operationTimer);operationTimer=null}
|
| 45 |
-
if(!message){el.hidden=true;el.textContent='';el.className='operation-status';return}
|
| 46 |
-
el.hidden=false;el.className='operation-status '+type;el.textContent=message;
|
| 47 |
-
if(type==='success'||type==='info')operationTimer=setTimeout(()=>{if(!state.busy)setOperation('')},7000)
|
| 48 |
-
}
|
| 49 |
function showMessage(msg,isError=false){const type=isError===true?'error':(typeof isError==='string'?isError:'info');setOperation(msg,type);const el=$('eventsPanel');if(el&&!state.runId)el.textContent=(isError?'Error: ':'')+msg;const warn=$('pollWarning');if(warn&&isError){warn.textContent=msg;warn.hidden=false}}
|
| 50 |
-
function setBusy(isBusy,label='Workingβ¦'){
|
| 51 |
-
state.busy=Boolean(isBusy);
|
| 52 |
-
document.querySelectorAll('[data-action-button]').forEach(btn=>{btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady});
|
| 53 |
-
if($('launchBuild'))$('launchBuild').disabled=state.busy||!state.bucketReady;
|
| 54 |
-
if($('launchValidate'))$('launchValidate').disabled=state.busy;
|
| 55 |
-
if(state.busy)setOperation(label,'loading')
|
| 56 |
-
}
|
| 57 |
function setButtonBusy(id,isBusy,busyLabel){const btn=$(id);if(!btn)return;if(isBusy){btn.dataset.oldText=btn.textContent;btn.textContent=busyLabel||'Workingβ¦';btn.disabled=true;btn.classList.add('is-busy')}else{if(btn.dataset.oldText)btn.textContent=btn.dataset.oldText;btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady;btn.classList.remove('is-busy')}}
|
| 58 |
-
function setBucketReady(ok,message){
|
| 59 |
-
state.bucketReady=Boolean(ok);
|
| 60 |
-
const btn=$('launchBuild');if(btn)btn.disabled=state.busy||!state.bucketReady;
|
| 61 |
-
setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':(message||'Create your private run bucket before launching builds.'));
|
| 62 |
-
setText('bucketStatusText',state.bucketReady?'Bucket ready':'Bucket not ready');
|
| 63 |
-
setText('bucketInlineStatus',state.bucketReady?'Bucket ready':'Not ready');
|
| 64 |
-
for(const id of ['bucketStatusDot','bucketInlineDot','sidebarBucketDot']){const dot=$(id);if(dot)dot.className='status-dot '+(state.bucketReady?'green':'amber-dot')}
|
| 65 |
-
const inline=$('bucketInlineStatus');if(inline)inline.className='badge '+(state.bucketReady?'success':'warn');
|
| 66 |
-
document.querySelectorAll('[data-requires-bucket="true"]').forEach(btn=>btn.disabled=state.busy||!state.bucketReady)
|
| 67 |
-
}
|
| 68 |
function effectivePreferredHardware(){const preferred=formValue('preferredHardware')||'zero-a10g';const fallback=formValue('fallbackHardware')||'l40sx1';const tryZero=$('tryZeroGpu')?.checked??true;if(tryZero)return preferred;if(preferred==='zero-a10g'||preferred.startsWith('zero'))return fallback;return preferred}
|
| 69 |
function hardwarePlanLabel(){const tryZero=$('tryZeroGpu')?.checked??true;const preferred=effectivePreferredHardware();const fallback=formValue('fallbackHardware')||'l40sx1';const allow=$('allowFallback')?.checked??true;if(tryZero)return `ZeroGPU β ${allow?fallback:'no fixed fallback'}`;return `${preferred}${allow&&fallback!==preferred?' β '+fallback+' fallback':''}`}
|
| 70 |
function validateBuildForm(){if(!state.bucketReady)return'Create or check your private run bucket before launching a build.';if(!formValue('modelId'))return'Enter a model card URL or model ID.';if(!formValue('targetSpace'))return'Enter a fresh target Space name.';if(!formValue('piModel'))return'Enter the Pi model to use.';return''}
|
| 71 |
-
function validateValidationForm(){if(!formValue('validateSpace'))return'Enter the target Space ID to validate.';try{JSON.parse(formValue('testArgs')||'[]');JSON.parse(formValue('testKwargs')||'{}')}catch(e){return`Invalid JSON payload: ${e.message}`
|
| 72 |
-
function
|
| 73 |
-
function
|
| 74 |
-
function
|
| 75 |
-
function
|
| 76 |
-
function
|
| 77 |
-
function resetRunDetails(){
|
| 78 |
-
for(const [id,value] of [['detailRunId','β'],['detailJobStatus','β'],['detailGateStatus','β'],['detailSpace','β'],['detailHardware','β'],['detailTraces','β'],['homeRunDetailStatus','No run']])setText(id,value);
|
| 79 |
-
setQuickLinks({});
|
| 80 |
-
const report=$('homeReportPreview');if(report)report.innerHTML='<strong>No run selected</strong><p>Select a run to preview its report and actions.</p>';
|
| 81 |
-
const manual=$('manualActionPanel');if(manual)manual.hidden=true;
|
| 82 |
-
}
|
| 83 |
-
function prepareNewBuild(){
|
| 84 |
-
if(state.poll){clearInterval(state.poll);state.poll=null}
|
| 85 |
-
state.runId=null;state.selectedRunDetail=null;state.activeLinks={};state.pollErrors=0;clearSavedRun();
|
| 86 |
-
document.querySelectorAll('.run-row').forEach(r=>r.classList.remove('selected'));
|
| 87 |
-
setBuildModeNew();resetRunDetails();renderTimeline(DEFAULT_TIMELINE);setText('progressPercent','0%');const fill=$('progressFill');if(fill)fill.style.width='0%';setText('currentStep','Ready for a new build');setText('lastEvent','Choose a model card and launch a build.');setText('elapsed','β');setText('lastPolled','β');setText('hardwarePlan',hardwarePlanLabel());renderEvents([]);renderMetrics({});showMessage('New build mode ready. The form is independent from selected runs.','info')
|
| 88 |
-
}
|
| 89 |
-
function setActiveRun(result){state.runId=result.run_id;if(result.bucket_source){state.bucketSource=result.bucket_source}setText('detailRunId',result.run_id);setText('detailJobStatus',result.status||'RUNNING');setText('detailGateStatus',result.kind||'running');setText('detailSpace',result.target_space||'β');setText('detailHardware',result.selected_hardware||result.preferred_space_hardware||'β');setText('homeRunDetailStatus',result.status||'Running');setBuildModeInspecting(result.run_id);setQuickLinks({...result.links,bucket_source:result.bucket_source,run_id:result.run_id,job_id:result.job_id,job_owner:state.user?.username});saveActiveRun();switchView('progress');startPolling(result.run_id)}
|
| 90 |
function startPolling(runId){state.runId=runId;state.pollErrors=0;const warn=$('pollWarning');if(warn)warn.hidden=true;if(state.poll)clearInterval(state.poll);saveActiveRun();refreshProgress();state.poll=setInterval(refreshProgress,4000)}
|
| 91 |
-
async function
|
|
|
|
|
|
|
| 92 |
function renderSignedOut(){const auth=$('authPanel');if(!auth)return;auth.hidden=false;auth.className='auth-panel warning';auth.innerHTML='<strong>Sign in required</strong><p>Use Hugging Face OAuth before launching Jobs or reading your Bucket.</p><a class="link-btn" href="/oauth/huggingface/login">Sign in with Hugging Face β</a>'}
|
| 93 |
function renderAuthWarnings(me){const auth=$('authPanel');if(!auth)return;const missing=me.missing_scopes||[];const warnings=me.warnings||[];if(!missing.length&&!warnings.length){auth.hidden=true;auth.innerHTML='';return}auth.hidden=false;auth.className='auth-panel warning';auth.innerHTML=`<strong>OAuth session warning</strong><p>${escapeHtml([...warnings,missing.length?'Missing scopes: '+missing.join(', '):''].filter(Boolean).join(' β’ '))}</p><a class="link-btn" href="/oauth/huggingface/login">Refresh sign-in β</a>`}
|
| 94 |
-
async function checkBucket(noisy=true){setButtonBusy('checkBucket',true,'Checkingβ¦');
|
| 95 |
-
async function createBucket(){setButtonBusy('createBucket',true,'Creatingβ¦');try{const data=await apiPost('/api/bucket/create',{bucket_name:state.bucketName});const source=data.bucket_source||state.bucketName;state.bucketSource=source;setText('
|
| 96 |
-
function renderManualAction(p={}){const panel=$('manualActionPanel');if(!panel)return;const status=String(p.status||p.state?.status||p.inference_gate?.status||'');const manual=Boolean(p.manual_hardware_required||p.inference_gate?.manual_hardware_required||p.hardware_strategy?.manual_action_required||status.includes('manual_hardware_required')||status.includes('generated_needs_manual_hardware'));panel.hidden=!manual;if(!manual)return;setText('manualActionTitle','Manual hardware required');setText('manualActionText','Automatic hardware selection was not available. Open Space settings, choose the recommended GPU, then run
|
| 97 |
-
function prepareValidationFromActiveRun(){const detail=state.selectedRunDetail;const summary=detail?.summary||{};const target=summary.target_space||
|
| 98 |
-
async function launchBuild(){const formError=validateBuildForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching build Jobβ¦');setButtonBusy('launchBuild',true,'Launching
|
| 99 |
-
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Jobβ¦');setButtonBusy('launchValidate',true,'Launching
|
| 100 |
-
async function refreshProgress(){if(!state.runId)return;try{const p=await apiGet(`/api/runs/${encodeURIComponent(state.runId)}/progress?bucket_name=${encodeURIComponent(state.bucketName)}`);state.pollErrors=0;const warn=$('pollWarning');if(warn)warn.hidden=true;renderProgress(p);renderEvents(p.events||[]);renderMetrics(p);
|
| 101 |
-
function renderMetrics(p){const root=$('metricsList');if(!root)return;const smoke=p.generation_smoke||{};const gate=p.inference_gate||{};const health=(gate.implementation_signals&&gate.implementation_signals.health_passed)||smoke.health_passed;const smokeOk=smoke.ok||smoke.status==='success';const latency=smoke.latency_seconds;const duration=smoke.recommended_zerogpu_duration_seconds||smoke.recommended_duration_seconds;root.innerHTML=`<li><span>/health</span><strong class="${health?'metric-ok':''}">${health?'passed':'pending'}</strong></li><li><span>/generate smoke test</span><strong class="${smokeOk?'metric-ok':'metric-warn'}">${smokeOk?'passed':(smoke.status||'pending')}</strong></li><li><span>Latency</span><strong>${latency?fmtSeconds(Math.round(latency)):'β'}</strong></li><li><span>Recommended ZeroGPU duration</span><strong>${duration?fmtSeconds(Math.round(duration)):'β'}</strong></li>`}
|
| 102 |
function renderEvents(events){const root=$('eventsPanel');if(!root)return;root.textContent=(events||[]).slice(-12).map(e=>`${e.ts||''} ${e.step||''} ${e.status||''} ${e.message||''}`).join('\n')||'No events yet. The run may still be scheduling.'}
|
| 103 |
-
function
|
|
|
|
| 104 |
function bindNavigation(){document.querySelectorAll('.nav-item').forEach(btn=>btn.addEventListener('click',()=>switchView(btn.dataset.view||'build')));document.querySelectorAll('[data-view-target]').forEach(btn=>btn.addEventListener('click',()=>switchView(btn.dataset.viewTarget||'build')))}
|
| 105 |
-
function bindRunFilters(){document.querySelectorAll('.filter[data-status]').forEach(btn=>btn.addEventListener('click',()=>{state.runStatusFilter=btn.dataset.status||'all';
|
| 106 |
-
function bindActions(){
|
| 107 |
-
|
| 108 |
-
$('launchBuild')?.addEventListener('click',launchBuild);$('launchValidate')?.addEventListener('click',launchValidate);$('prepareNewBuild')?.addEventListener('click',prepareNewBuild);
|
| 109 |
-
$('manualGoValidate')?.addEventListener('click',prepareValidationFromActiveRun);$('homeValidateBtn')?.addEventListener('click',prepareValidationFromActiveRun);
|
| 110 |
-
$('checkBucket')?.addEventListener('click',()=>checkBucket(true));$('createBucket')?.addEventListener('click',createBucket);$('checkBucketSide')?.addEventListener('click',()=>checkBucket(true));
|
| 111 |
-
$('refreshRuns')?.addEventListener('click',()=>loadRuns(true));$('refreshRunsHome')?.addEventListener('click',()=>loadRuns(true));
|
| 112 |
-
['tryZeroGpu','preferredHardware','fallbackHardware','allowFallback'].forEach(id=>$(id)?.addEventListener('change',()=>setText('hardwarePlan',hardwarePlanLabel())))
|
| 113 |
-
}
|
| 114 |
-
bindNavigation();bindActions();bindRunFilters();setText('hardwarePlan',hardwarePlanLabel());setBuildModeNew();loadAppInfo();renderTimeline(DEFAULT_TIMELINE);
|
|
|
|
| 1 |
+
const state={runId:null,bucketName:'space-factory-runs',bucketSource:null,bucketReady:false,poll:null,activeLinks:{},selectedRunDetail:null,runStatusFilter:'all',runSearch:'',runsCache:[],runsLastLoaded:0,pollErrors:0,busy:false,user:null,currentView:'build'};
|
| 2 |
+
const TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale']);
|
| 3 |
+
const DEFAULT_TIMELINE=[{label:'Validate Input',status:'pending'},{label:'Analyze Model',status:'pending'},{label:'Generate App',status:'pending'},{label:'Create Space',status:'pending'},{label:'Hardware',status:'pending'},{label:'Live Validation',status:'pending'},{label:'Report',status:'pending'}];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
function saveActiveRun(){try{localStorage.setItem('asf.activeRun',JSON.stringify({runId:state.runId,bucketName:state.bucketName,links:state.activeLinks||{}}))}catch(_){}}
|
| 5 |
function clearSavedRun(){try{localStorage.removeItem('asf.activeRun')}catch(_){}}
|
| 6 |
+
function artifactUrl(runId,bucketSource){if(!runId||!bucketSource)return'';return `https://huggingface.co/buckets/${bucketSource}/tree/main/runs/${runId}`}
|
| 7 |
+
function normalizeLinks(links={}){const out={...links};const bucket=out.bucket_source||state.bucketSource;const run=out.run_id||state.runId;if(!out.artifacts_url&&bucket&&run)out.artifacts_url=artifactUrl(run,bucket);if(!out.job_url){const jobId=out.job_id||out.jobId;const owner=out.job_owner||out.owner||out.username||state.user?.username;if(jobId&&owner)out.job_url=`https://huggingface.co/jobs/${owner}/${jobId}`}return out}
|
| 8 |
+
function setQuickLinks(links={}){links=normalizeLinks(links||{});state.activeLinks=links;for(const id of ['openJob','selectedOpenJob'])setLink(id,links.job_url);for(const id of ['openSpace','selectedOpenSpace'])setLink(id,links.target_space_url);for(const id of ['openSettings','selectedOpenSettings','manualOpenSettings'])setLink(id,links.target_space_settings_url||links.target_space_url&&links.target_space_url+'/settings');for(const id of ['openArtifacts','selectedOpenArtifacts'])setLink(id,links.artifacts_url);renderManualAction({links})}
|
| 9 |
+
let operationTimer=null;function setOperation(message,type='info'){const el=$('operationStatus');if(!el)return;if(operationTimer){clearTimeout(operationTimer);operationTimer=null}if(!message){el.hidden=true;el.textContent='';el.className='operation-status';return}el.hidden=false;el.className='operation-status '+type;el.textContent=message;if(type==='success'||type==='info')operationTimer=setTimeout(()=>{if(!state.busy)setOperation('')},7000)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
function showMessage(msg,isError=false){const type=isError===true?'error':(typeof isError==='string'?isError:'info');setOperation(msg,type);const el=$('eventsPanel');if(el&&!state.runId)el.textContent=(isError?'Error: ':'')+msg;const warn=$('pollWarning');if(warn&&isError){warn.textContent=msg;warn.hidden=false}}
|
| 11 |
+
function setBusy(isBusy,label='Workingβ¦'){state.busy=Boolean(isBusy);document.querySelectorAll('[data-action-button]').forEach(btn=>{btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady});if($('launchBuild'))$('launchBuild').disabled=state.busy||!state.bucketReady;if($('launchValidate'))$('launchValidate').disabled=state.busy;if(state.busy)setOperation(label,'loading')}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
function setButtonBusy(id,isBusy,busyLabel){const btn=$(id);if(!btn)return;if(isBusy){btn.dataset.oldText=btn.textContent;btn.textContent=busyLabel||'Workingβ¦';btn.disabled=true;btn.classList.add('is-busy')}else{if(btn.dataset.oldText)btn.textContent=btn.dataset.oldText;btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady;btn.classList.remove('is-busy')}}
|
| 13 |
+
function setBucketReady(ok,message){state.bucketReady=Boolean(ok);const btn=$('launchBuild');if(btn)btn.disabled=state.busy||!state.bucketReady;setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':(message||'Create your private run bucket before launching builds.'));setText('bucketStatusText',state.bucketReady?`Bucket: ${state.bucketSource||'connected'}`:'Bucket not ready');setText('bucketInlineStatus',state.bucketReady?'Bucket ready':'Not ready');for(const id of ['bucketStatusDot','bucketInlineDot','sidebarBucketDot']){const dot=$(id);if(dot)dot.className='status-dot '+(state.bucketReady?'green':'amber-dot')}const inline=$('bucketInlineStatus');if(inline)inline.className='badge '+(state.bucketReady?'success':'warn');document.querySelectorAll('[data-requires-bucket="true"]').forEach(btn=>btn.disabled=state.busy||!state.bucketReady)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
function effectivePreferredHardware(){const preferred=formValue('preferredHardware')||'zero-a10g';const fallback=formValue('fallbackHardware')||'l40sx1';const tryZero=$('tryZeroGpu')?.checked??true;if(tryZero)return preferred;if(preferred==='zero-a10g'||preferred.startsWith('zero'))return fallback;return preferred}
|
| 15 |
function hardwarePlanLabel(){const tryZero=$('tryZeroGpu')?.checked??true;const preferred=effectivePreferredHardware();const fallback=formValue('fallbackHardware')||'l40sx1';const allow=$('allowFallback')?.checked??true;if(tryZero)return `ZeroGPU β ${allow?fallback:'no fixed fallback'}`;return `${preferred}${allow&&fallback!==preferred?' β '+fallback+' fallback':''}`}
|
| 16 |
function validateBuildForm(){if(!state.bucketReady)return'Create or check your private run bucket before launching a build.';if(!formValue('modelId'))return'Enter a model card URL or model ID.';if(!formValue('targetSpace'))return'Enter a fresh target Space name.';if(!formValue('piModel'))return'Enter the Pi model to use.';return''}
|
| 17 |
+
function validateValidationForm(){if(!formValue('validateSpace'))return'Enter the target Space ID to validate.';try{JSON.parse(formValue('testArgs')||'[]');JSON.parse(formValue('testKwargs')||'{}')}catch(e){return`Invalid JSON payload: ${e.message}`}return''}
|
| 18 |
+
function setBuildModeNew(){const panel=$('buildModePanel');if(panel){panel.classList.remove('inspecting');panel.innerHTML='<strong>Preparing a new build</strong><p>This form starts a fresh build. Selecting a run only updates the progress panels.</p>'}const status=$('progressStatus');if(status){status.textContent='Idle';status.className='badge neutral'}setText('progressTitle','No run selected')}
|
| 19 |
+
function setBuildModeInspecting(runId){const panel=$('buildModePanel');if(panel){panel.classList.add('inspecting');panel.innerHTML=`<strong>Inspecting selected run</strong><p>You are viewing run <code>${escapeHtml(runId||'')}</code>. The build form is unchanged.</p>`}}
|
| 20 |
+
function resetRunDetails(){for(const [id,value] of [['detailRunId','β'],['detailJobStatus','β'],['detailGateStatus','β'],['detailSpace','β'],['detailHardware','β'],['detailTraces','β'],['homeRunDetailStatus','No run'],['selectedRunId','β'],['selectedModelId','β'],['selectedTargetSpace','β'],['selectedHardware','β'],['runDetailStatus','β']])setText(id,value);setQuickLinks({});const report=$('homeReportPreview');if(report)report.innerHTML='<strong>No run selected</strong><p>Select a run to preview its report and actions.</p>';const full=$('reportPreview');if(full)full.textContent='No report selected.';const manual=$('manualActionPanel');if(manual)manual.hidden=true;const activity=$('activityFeed');if(activity)activity.innerHTML='<div class="empty">No activity yet.</div>'}
|
| 21 |
+
function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.selectedRunDetail=null;state.activeLinks={};state.pollErrors=0;clearSavedRun();document.querySelectorAll('.run-row').forEach(r=>r.classList.remove('selected'));setBuildModeNew();resetRunDetails();renderTimeline(DEFAULT_TIMELINE);setText('progressPercent','0%');const fill=$('progressFill');if(fill)fill.style.width='0%';setText('currentStep','Ready');setText('lastEvent','Choose a model card and launch a build.');setText('elapsed','β');setText('lastPolled','β');setText('hardwarePlan',hardwarePlanLabel());renderMetrics({});showMessage('New build mode ready.','info')}
|
| 22 |
+
function setActiveRun(result){state.runId=result.run_id;if(result.bucket_source)state.bucketSource=result.bucket_source;setText('detailRunId',result.run_id);setText('detailJobStatus',result.status||'RUNNING');setText('detailGateStatus',result.kind||'running');setText('detailSpace',result.target_space||'β');setText('detailHardware',result.selected_hardware||result.preferred_space_hardware||'β');setText('homeRunDetailStatus',result.status||'Running');setBuildModeInspecting(result.run_id);setQuickLinks({...result.links,bucket_source:result.bucket_source,run_id:result.run_id,job_id:result.job_id,job_owner:state.user?.username});saveActiveRun();switchView('progress');startPolling(result.run_id)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
function startPolling(runId){state.runId=runId;state.pollErrors=0;const warn=$('pollWarning');if(warn)warn.hidden=true;if(state.poll)clearInterval(state.poll);saveActiveRun();refreshProgress();state.poll=setInterval(refreshProgress,4000)}
|
| 24 |
+
async function bootstrapRunRecovery(){return restoreActiveRun()}
|
| 25 |
+
async function restoreActiveRun(){try{const resumable=await apiGet(`/api/runs/resumable?bucket_name=${encodeURIComponent(state.bucketName)}`);if(resumable?.run?.run_id){state.runId=resumable.run.run_id;if(resumable.bucket_source)state.bucketSource=resumable.bucket_source;setBuildModeInspecting(state.runId);if(resumable.view)renderRunViewModel(resumable.view,{progress:0,events:[]});setQuickLinks(resumable.view?.links||{});startPolling(state.runId);showMessage(`Resumed latest active run: ${state.runId}`,'info');return true}}catch(e){console.warn('resumable lookup failed',e)}try{const raw=localStorage.getItem('asf.activeRun');if(!raw)return false;const saved=JSON.parse(raw);if(saved?.bucketName)state.bucketName=saved.bucketName;if(saved?.runId){state.runId=saved.runId;setText('detailRunId',saved.runId);setQuickLinks(saved.links||{});setBuildModeInspecting(saved.runId);startPolling(saved.runId);return true}}catch(_){}return false}
|
| 26 |
+
async function loadAppInfo(){setOperation('Checking sign-in and run storageβ¦','loading');try{const me=await apiGet('/api/me');state.user=me;setText('userChip','@'+me.username);const logout=$('logoutLink');if(logout){logout.hidden=false;logout.href=me.logout_url||'/logout'}state.bucketSource=me.username+'/'+state.bucketName;setText('bucketInlineLabel',state.bucketSource);renderAuthWarnings(me);await checkBucket(false);await loadRuns(true);await bootstrapRunRecovery();if(!state.bucketReady)setOperation('Create your private run bucket before launching builds.','warning');else setOperation('')}catch(e){state.user=null;setText('userChip','Sign in');const logout=$('logoutLink');if(logout)logout.hidden=true;renderSignedOut();setBucketReady(false,'Sign in with Hugging Face first.');showMessage('Please sign in with Hugging Face, then refresh this page.',true)}}
|
| 27 |
function renderSignedOut(){const auth=$('authPanel');if(!auth)return;auth.hidden=false;auth.className='auth-panel warning';auth.innerHTML='<strong>Sign in required</strong><p>Use Hugging Face OAuth before launching Jobs or reading your Bucket.</p><a class="link-btn" href="/oauth/huggingface/login">Sign in with Hugging Face β</a>'}
|
| 28 |
function renderAuthWarnings(me){const auth=$('authPanel');if(!auth)return;const missing=me.missing_scopes||[];const warnings=me.warnings||[];if(!missing.length&&!warnings.length){auth.hidden=true;auth.innerHTML='';return}auth.hidden=false;auth.className='auth-panel warning';auth.innerHTML=`<strong>OAuth session warning</strong><p>${escapeHtml([...warnings,missing.length?'Missing scopes: '+missing.join(', '):''].filter(Boolean).join(' β’ '))}</p><a class="link-btn" href="/oauth/huggingface/login">Refresh sign-in β</a>`}
|
| 29 |
+
async function checkBucket(noisy=true){setButtonBusy('checkBucket',true,'Checkingβ¦');try{const data=await apiGet(`/api/bucket/status?bucket_name=${encodeURIComponent(state.bucketName)}`);const source=data.bucket_source||state.bucketName;state.bucketSource=source;setText('bucketInlineLabel',source);setBucketReady(Boolean(data.ok),data.error||'Create your private run bucket before launching builds.');if(noisy)showMessage(data.ok?`Bucket ready: ${source}`:`Bucket not ready: ${data.error||'not found'}`,!data.ok);return data}catch(e){setBucketReady(false,e.message);if(noisy)showMessage(e.message,true);throw e}finally{setButtonBusy('checkBucket',false)}}
|
| 30 |
+
async function createBucket(){setButtonBusy('createBucket',true,'Creatingβ¦');try{const data=await apiPost('/api/bucket/create',{bucket_name:state.bucketName});const source=data.bucket_source||state.bucketName;state.bucketSource=source;setText('bucketInlineLabel',source);setBucketReady(Boolean(data.ok),data.error||'Could not create bucket.');showMessage(data.ok?`Created/ready: ${source}`:`Could not create bucket: ${data.error}`,!data.ok);if(data.ok)loadRuns(true)}catch(e){setBucketReady(false,e.message);showMessage(e.message,true)}finally{setButtonBusy('createBucket',false)}}
|
| 31 |
+
function renderManualAction(p={}){const panel=$('manualActionPanel');if(!panel)return;const view=p.view||{};const sm=view.status_model||{};const status=String(p.status||p.state?.status||p.inference_gate?.status||sm.global_status||'');const manual=Boolean(sm.requires_manual_action||p.manual_hardware_required||p.inference_gate?.manual_hardware_required||p.hardware_strategy?.manual_action_required||status.includes('manual_hardware_required')||status.includes('generated_needs_manual_hardware')||status.includes('waiting_manual_action'));panel.hidden=!manual;if(!manual)return;setText('manualActionTitle','Manual hardware required');setText('manualActionText','Automatic hardware selection was not available. Open Space settings, choose the recommended GPU, then run Space Test.');setLink('manualOpenSettings',(p.links||view.links||state.activeLinks||{}).target_space_settings_url)}
|
| 32 |
+
function prepareValidationFromActiveRun(){const detail=state.selectedRunDetail;const summary=detail?.summary||{};const target=summary.target_space||$('detailSpace')?.textContent||'';if(target&&target!=='β')setInputValue('validateSpace',target);const expected=summary.expected_output_type||detail?.generation_smoke?.expected_output_type||formValue('buildExpectedOutput')||'image';setInputValue('expectedOutput',expected);setInputValue('validateApi','/generate');switchView('validate');showMessage(`Prepared validation form for ${target||'selected Space'}.`,'info')}
|
| 33 |
+
async function launchBuild(){const formError=validateBuildForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching build Jobβ¦');setButtonBusy('launchBuild',true,'Launchingβ¦');try{setText('hardwarePlan',hardwarePlanLabel());const payload={bucket_name:state.bucketName,run_id:formValue('runId')||undefined,model_id:formValue('modelId'),target_space_name:formValue('targetSpace'),pi_model:formValue('piModel'),implementation_mode:formValue('implementationMode'),preferred_space_hardware:effectivePreferredHardware(),fallback_space_hardware:formValue('fallbackHardware'),allow_fixed_gpu_fallback:$('allowFallback')?.checked??true,expected_output_type:formValue('buildExpectedOutput')};const result=await apiPost('/api/build',payload);setActiveRun(result);showMessage(`Build launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchBuild',false)}}
|
| 34 |
+
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Jobβ¦');setButtonBusy('launchValidate',true,'Launchingβ¦');try{const payload={bucket_name:state.bucketName,target_space_id:formValue('validateSpace'),api_name:formValue('validateApi')||'/generate',expected_output_type:formValue('expectedOutput')||'image',test_args_json:formValue('testArgs')||'[]',test_kwargs_json:formValue('testKwargs')||'{}',live_timeout_seconds:Number(formValue('validateTimeout')||1800)};const result=await apiPost('/api/validate',payload);setActiveRun(result);showMessage(`Validation launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchValidate',false)}}
|
| 35 |
+
async function refreshProgress(){if(!state.runId)return;try{const p=await apiGet(`/api/runs/${encodeURIComponent(state.runId)}/progress?bucket_name=${encodeURIComponent(state.bucketName)}`);state.pollErrors=0;const warn=$('pollWarning');if(warn)warn.hidden=true;renderProgress(p);renderEvents(p.events||[]);renderMetrics(p);if(p.state||p.summary){if(p.bucket_source)state.bucketSource=p.bucket_source;setText('detailJobStatus',p.state?.status||p.status||'running');setText('detailGateStatus',p.inference_gate?.status||p.state?.status||p.status||'β');setText('detailSpace',p.state?.target_space||p.summary?.target_space||'β');setText('detailHardware',p.summary?.selected_hardware||p.state?.selected_hardware||p.hardware_strategy?.selected_hardware||'β');setText('detailTraces',Array.isArray(p.events)?String(p.events.length)+' events':'β');setText('homeRunDetailStatus',p.view?.header?.status_label||p.state?.status||p.status||'running');setQuickLinks({...p.links,...p.view?.links,bucket_source:p.bucket_source,run_id:p.run_id});saveActiveRun()}const status=String(p.view?.header?.status||p.status||p.state?.status||'').toLowerCase();if(TERMINAL_STATUSES.has(status)&&state.poll){clearInterval(state.poll);state.poll=null;setOperation(`Run finished with status: ${badgeLabel(status)}`,status.includes('success')||status.includes('succeed')?'success':status.includes('failed')||status.includes('blocker')?'error':'warning')}loadRuns(false)}catch(e){state.pollErrors+=1;console.warn(e);const warn=$('pollWarning');if(warn){warn.textContent=`Progress polling failed ${state.pollErrors} time${state.pollErrors===1?'':'s'}. The Job may still be running; retrying automaticallyβ¦`;warn.hidden=false}}}
|
| 36 |
+
function renderMetrics(p){const root=$('metricsList');if(!root)return;const smoke=p.generation_smoke||{};const gate=p.inference_gate||{};const health=(gate.implementation_signals&&gate.implementation_signals.health_passed)||smoke.health_passed;const smokeOk=smoke.ok||smoke.status==='success';const latency=smoke.latency_seconds||p.summary?.latency_seconds;const duration=smoke.recommended_zerogpu_duration_seconds||smoke.recommended_duration_seconds;root.innerHTML=`<li><span>/health</span><strong class="${health?'metric-ok':''}">${health?'passed':'pending'}</strong></li><li><span>/generate smoke test</span><strong class="${smokeOk?'metric-ok':'metric-warn'}">${smokeOk?'passed':(smoke.status||'pending')}</strong></li><li><span>Latency</span><strong>${latency?fmtSeconds(Math.round(latency)):'β'}</strong></li><li><span>Recommended ZeroGPU duration</span><strong>${duration?fmtSeconds(Math.round(duration)):'β'}</strong></li>`}
|
| 37 |
function renderEvents(events){const root=$('eventsPanel');if(!root)return;root.textContent=(events||[]).slice(-12).map(e=>`${e.ts||''} ${e.step||''} ${e.status||''} ${e.message||''}`).join('\n')||'No events yet. The run may still be scheduling.'}
|
| 38 |
+
async function resumeLatestRun(){try{const data=await apiGet(`/api/runs/resumable?bucket_name=${encodeURIComponent(state.bucketName)}`);if(!data.run){showMessage('No resumable run found in this bucket.','warning');return}state.runId=data.run.run_id;if(data.bucket_source)state.bucketSource=data.bucket_source;if(data.view)renderRunViewModel(data.view,{progress:0,events:[]});setBuildModeInspecting(state.runId);setQuickLinks(data.view?.links||{});startPolling(state.runId);showMessage(`Resumed ${state.runId}`,'success')}catch(e){showMessage(e.message,true)}}
|
| 39 |
+
function switchView(view){state.currentView=view;document.querySelectorAll('.nav-item').forEach(b=>b.classList.toggle('active',b.dataset.view===view));document.querySelectorAll('[data-section]').forEach(s=>s.hidden=s.dataset.section!==view);setOperation('');const warn=$('pollWarning');if(warn)warn.hidden=true;if(view==='progress')loadRuns(false)}
|
| 40 |
function bindNavigation(){document.querySelectorAll('.nav-item').forEach(btn=>btn.addEventListener('click',()=>switchView(btn.dataset.view||'build')));document.querySelectorAll('[data-view-target]').forEach(btn=>btn.addEventListener('click',()=>switchView(btn.dataset.viewTarget||'build')))}
|
| 41 |
+
function bindRunFilters(){document.querySelectorAll('.filter[data-status]').forEach(btn=>btn.addEventListener('click',()=>{state.runStatusFilter=btn.dataset.status||'all';renderRunsFromCache()}));for(const id of ['runSearch','runSearchFull'])$(id)?.addEventListener('input',e=>{state.runSearch=e.target.value||'';for(const other of ['runSearch','runSearchFull'])if($(other)&&$(other)!==e.target)$(other).value=state.runSearch;clearTimeout(state.runSearchTimer);state.runSearchTimer=setTimeout(renderRunsFromCache,80)})}
|
| 42 |
+
function bindActions(){$('launchBuild')?.setAttribute('data-requires-bucket','true');$('launchBuild')?.setAttribute('data-action-button','true');$('launchValidate')?.setAttribute('data-action-button','true');$('launchBuild')?.addEventListener('click',launchBuild);$('launchValidate')?.addEventListener('click',launchValidate);$('prepareNewBuild')?.addEventListener('click',prepareNewBuild);$('manualGoValidate')?.addEventListener('click',prepareValidationFromActiveRun);$('homeValidateBtn')?.addEventListener('click',prepareValidationFromActiveRun);$('selectedValidateBtn')?.addEventListener('click',prepareValidationFromActiveRun);$('checkBucket')?.addEventListener('click',()=>checkBucket(true));$('bucketStatusPill')?.addEventListener('click',()=>checkBucket(true));$('createBucket')?.addEventListener('click',createBucket);$('refreshRuns')?.addEventListener('click',()=>loadRuns(true));$('refreshRunsHome')?.addEventListener('click',()=>loadRuns(true));$('resumeLatestRun')?.addEventListener('click',resumeLatestRun);$('resumeLatestRunFull')?.addEventListener('click',resumeLatestRun);['tryZeroGpu','preferredHardware','fallbackHardware','allowFallback'].forEach(id=>$(id)?.addEventListener('change',()=>setText('hardwarePlan',hardwarePlanLabel())))}
|
| 43 |
+
bindNavigation();bindActions();bindRunFilters();setText('hardwarePlan',hardwarePlanLabel());setBuildModeNew();renderTimeline(DEFAULT_TIMELINE);loadAppInfo();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web/static/components.js
CHANGED
|
@@ -1 +1,11 @@
|
|
| 1 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function $(id){return document.getElementById(id)}
|
| 2 |
+
function formValue(id){return ($(id)?.value||'').trim()}
|
| 3 |
+
function setText(id,value){const el=$(id);if(el)el.textContent=value??'β'}
|
| 4 |
+
function setInputValue(id,value){const el=$(id);if(el)el.value=value||''}
|
| 5 |
+
function escapeHtml(value){return String(value??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}
|
| 6 |
+
function fmtSeconds(s){if(s==null||s==='')return'β';s=Math.max(0,Number(s)||0);const h=Math.floor(s/3600),m=Math.floor((s%3600)/60),r=s%60;if(h)return`${h}h ${m}m`;return m?`${m}m ${r}s`:`${r}s`}
|
| 7 |
+
function shortText(value,max=42){value=String(value||'β');return value.length>max?value.slice(0,max-1)+'β¦':value}
|
| 8 |
+
function formatTime(ts){if(!ts)return'β';try{return new Date(ts).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit',second:'2-digit'})}catch(_){return String(ts).slice(11,19)||'β'}}
|
| 9 |
+
function statusClass(status){status=String(status||'').toLowerCase();if(status.includes('succeed')||status.includes('success')||status.includes('passed')||status==='completed')return'success';if(status.includes('manual')||status.includes('stale')||status.includes('blocked'))return'warn';if(status.includes('failed')||status.includes('error')||status.includes('technical'))return'error';if(status.includes('running')||status.includes('queued')||status.includes('pending')||status.includes('building'))return'running';return'neutral'}
|
| 10 |
+
function setLink(id,url){const el=$(id);if(!el)return;if(url){el.href=url;el.classList.remove('disabled');el.setAttribute('aria-disabled','false')}else{el.removeAttribute('href');el.classList.add('disabled');el.setAttribute('aria-disabled','true')}}
|
| 11 |
+
function badgeLabel(status){return String(status||'unknown').replaceAll('_',' ')}
|
web/static/progress.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
| 1 |
-
function
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
setText('progressTitle',p.current_step_label||'Live job progress');
|
| 10 |
-
setText('progressStatus',p.status||'running');
|
| 11 |
-
setText('lastPolled',new Date().toLocaleTimeString());
|
| 12 |
-
renderTimeline(p.timeline||[]);
|
| 13 |
-
}
|
| 14 |
-
function fmtSeconds(s){if(s==null)return"β";s=Number(s)||0;const h=Math.floor(s/3600),m=Math.floor((s%3600)/60),r=s%60;if(h)return`${h}h ${m}m`;return m?`${m}m ${r}s`:`${r}s`}
|
| 15 |
-
function timelineIcon(status){if(status==='done')return'β';if(status==='running')return'β¦';if(status==='failed')return'!';return'β'}
|
| 16 |
-
function timelineStatus(status){if(status==='done')return'completed';if(status==='running')return'active';if(status==='failed')return'failed';return'pending'}
|
| 17 |
-
function renderTimeline(items){const root=document.getElementById('timeline');if(!root)return;root.innerHTML=(items||[]).map((x,i)=>{const status=x.status||'pending';return `<div class="step ${status}" data-status="${timelineStatus(status)}"><div class="step-dot">${timelineIcon(status)}</div><div>${escapeHtml(x.label||`Step ${i+1}`)}</div></div>`}).join('')}
|
| 18 |
-
function setLink(id,url){const el=document.getElementById(id);if(!el)return;if(url){el.href=url;el.classList.remove('disabled');el.setAttribute('aria-disabled','false')}else{el.removeAttribute('href');el.classList.add('disabled');el.setAttribute('aria-disabled','true')}}
|
|
|
|
| 1 |
+
function timelineIcon(status){status=String(status||'pending');if(status==='done'||status==='completed')return'β';if(status==='running')return'β’';if(status==='failed'||status==='blocked')return'!';if(status==='skipped')return'β·';return'β'}
|
| 2 |
+
function timelineStatus(status){status=String(status||'pending');if(status==='done'||status==='completed')return'completed';if(status==='running')return'running';if(status==='failed')return'failed';if(status==='blocked')return'blocked';return'pending'}
|
| 3 |
+
function renderTimeline(items){const root=$('timeline');if(!root)return;root.innerHTML=(items||[]).map((x,i)=>{const st=timelineStatus(x.status);return `<div class="step ${st}" data-status="${st}"><div class="step-dot">${timelineIcon(x.status)}</div><div><strong>${escapeHtml(x.label||`Step ${i+1}`)}</strong><small>${st}</small></div></div>`}).join('')}
|
| 4 |
+
function renderActivityFeed(items){const root=$('activityFeed');if(!root)return;if(!items.length){root.innerHTML='<div class="empty">No activity yet. Waiting for the first worker event.</div>';return}root.innerHTML=items.slice(-8).reverse().map(item=>`<div class="activity-row ${statusClass(item.severity||item.status)}"><time>${escapeHtml(formatTime(item.ts))}</time><span class="activity-dot"></span><div><strong>${escapeHtml(item.message||'Event received')}</strong><p>${escapeHtml(item.step||'event')} Β· ${escapeHtml(item.status||'')}</p></div><em>${escapeHtml(item.agent||'Factory Agent')}</em></div>`).join('')}
|
| 5 |
+
function renderDiagnostics(d={},header={}){setText('diagBuildStatus',badgeLabel(d.build_status||'pending'));setText('diagBuildStep',header.current_phase_label||'Waiting for run data');const chips=$('statusChips');if(chips){const data=[['Build',d.build_status],['API',d.api_status],['Tests',d.tests_status],['Verdict',d.verdict_status]];chips.innerHTML=data.map(([k,v])=>`<span class="status-chip ${statusClass(v)}"><strong>${escapeHtml(k)}</strong><em>${escapeHtml(badgeLabel(v||'pending'))}</em></span>`).join('')}const issue=$('issueCard');if(issue){const has=Boolean(d.issue&&d.issue.title);issue.hidden=!has;if(has){issue.className='issue-card '+statusClass(d.issue.status||d.issue.title);setText('issueTitle',d.issue.title);setText('issueDetail',d.issue.detail||d.issue.status||'Review logs.')}}const rules=$('zeroGpuChecklist');if(rules)rules.innerHTML=(d.zerogpu_rules||[]).map(r=>`<li class="${statusClass(r.status)}"><span>${escapeHtml(r.label)}</span><strong>${escapeHtml(r.status||'pending')}</strong></li>`).join('')}
|
| 6 |
+
function renderSpaceTestPreview(s={}){const st=$('spaceTestStatus');if(st){st.textContent=badgeLabel(s.status||'Pending');st.className='badge '+statusClass(s.status)}setText('spaceTestVerdict',badgeLabel(s.verdict||'pending'));setText('spaceTestOutput',s.output_artifact?'Output artifact ready':'Waiting for API test')}
|
| 7 |
+
function renderRunViewModel(view,p={}){const header=view.header||{};const sm=view.status_model||{};const progress=Number(sm.global_status==='succeeded'?100:(p.progress||0));const fill=$('progressFill');if(fill)fill.style.width=`${Math.max(0,Math.min(100,progress))}%`;setText('progressPercent',`${Math.round(progress)}%`);setText('progressTitle',header.title||view.run_id||'Live job progress');const statusEl=$('progressStatus');if(statusEl){statusEl.textContent=header.status_label||badgeLabel(header.status||'Running');statusEl.className='badge '+statusClass(header.status)}setText('currentStep',header.current_phase_label||'β');setText('lastEvent',(view.activity||[]).slice(-1)[0]?.message||p.last_event||'Waiting for worker events.');setText('elapsed',fmtSeconds(header.elapsed_seconds||p.elapsed_seconds));setText('lastPolled',new Date().toLocaleTimeString());setText('detailSpace',header.space||'β');setText('detailJobStatus',header.raw_status||header.status||'β');setText('detailGateStatus',sm.verdict||'β');setText('detailTraces',Array.isArray(p.events)?`${p.events.length} events`:'β');setLink('openJob',view.links?.job_url);setLink('openSpace',view.links?.target_space_url);setLink('openSettings',view.links?.target_space_settings_url);setLink('openArtifacts',view.links?.artifacts_url);renderTimeline(view.pipeline||[]);renderActivityFeed(view.activity||[]);renderDiagnostics(view.diagnostics||{},header);renderSpaceTestPreview(view.space_test||{});renderManualAction({view,...p})}
|
| 8 |
+
function renderProgress(p){if(p.view){renderRunViewModel(p.view,p);return}const progress=Number(p.progress||0);const fill=$('progressFill');if(fill)fill.style.width=`${Math.max(0,Math.min(100,progress))}%`;setText('progressPercent',`${progress}%`);setText('currentStep',p.current_step_label||'β');setText('lastEvent',p.last_event||'β');setText('elapsed',fmtSeconds(p.elapsed_seconds));setText('progressTitle',p.current_step_label||'Live job progress');const statusEl=$('progressStatus');if(statusEl){statusEl.textContent=p.status||'running';statusEl.className='badge '+statusClass(p.status)}setText('lastPolled',new Date().toLocaleTimeString());renderTimeline(p.timeline||[])}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web/static/runs.js
CHANGED
|
@@ -1,134 +1,12 @@
|
|
| 1 |
-
function
|
| 2 |
-
function
|
| 3 |
-
|
| 4 |
-
function
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
}
|
| 12 |
-
|
| 13 |
-
function runMatchesStatus(run,status){
|
| 14 |
-
const s=String(run.status||'unknown').toLowerCase();
|
| 15 |
-
if(!status||status==='all')return true;
|
| 16 |
-
if(status==='running')return s.includes('running')||s==='queued'||s==='pending';
|
| 17 |
-
if(status==='full_inference_success')return s.includes('success');
|
| 18 |
-
if(status==='manual_hardware_required')return s.includes('manual')||s.includes('hardware');
|
| 19 |
-
if(status==='technical_blocker')return s.includes('blocker');
|
| 20 |
-
if(status==='failed')return s.includes('failed')||s.includes('error');
|
| 21 |
-
return s===String(status).toLowerCase();
|
| 22 |
-
}
|
| 23 |
-
function runMatchesQuery(run,query){
|
| 24 |
-
if(!query)return true;
|
| 25 |
-
const haystack=[run.run_id,run.model_id,run.target_space,run.status,run.kind,run.selected_hardware].map(x=>String(x||'').toLowerCase()).join(' ');
|
| 26 |
-
return haystack.includes(String(query).toLowerCase());
|
| 27 |
-
}
|
| 28 |
-
function filteredRuns(){
|
| 29 |
-
const query=document.getElementById('runSearch')?.value||'';
|
| 30 |
-
const status=state.runStatusFilter||'all';
|
| 31 |
-
return (state.runsCache||[]).filter(r=>runMatchesStatus(r,status)&&runMatchesQuery(r,query));
|
| 32 |
-
}
|
| 33 |
-
function renderRunStats(){
|
| 34 |
-
const counts={all:state.runsCache.length,running:0,full_inference_success:0,manual_hardware_required:0,failed:0};
|
| 35 |
-
for(const r of state.runsCache){
|
| 36 |
-
if(runMatchesStatus(r,'running'))counts.running+=1;
|
| 37 |
-
if(runMatchesStatus(r,'full_inference_success'))counts.full_inference_success+=1;
|
| 38 |
-
if(runMatchesStatus(r,'manual_hardware_required'))counts.manual_hardware_required+=1;
|
| 39 |
-
if(runMatchesStatus(r,'failed'))counts.failed+=1;
|
| 40 |
-
}
|
| 41 |
-
document.querySelectorAll('.filter[data-status]').forEach(btn=>{
|
| 42 |
-
const status=btn.dataset.status||'all';
|
| 43 |
-
const label={all:'All',running:'Running',full_inference_success:'Success',manual_hardware_required:'Manual',failed:'Failed',technical_blocker:'Blockers'}[status]||status;
|
| 44 |
-
const count=counts[status];
|
| 45 |
-
btn.textContent=typeof count==='number'?`${label} ${count}`:label;
|
| 46 |
-
});
|
| 47 |
-
}
|
| 48 |
-
function renderSelectedLinks(summary={}){
|
| 49 |
-
const detail=state.selectedRunDetail||{};
|
| 50 |
-
const runId=summary.run_id||detail.run_id;
|
| 51 |
-
const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;
|
| 52 |
-
const artifacts=summary.artifacts_url||(typeof artifactUrl==='function'?artifactUrl(runId,bucket):'');
|
| 53 |
-
const jobUrl=inferredJobUrl(summary,detail);
|
| 54 |
-
setLink('selectedOpenJob',jobUrl);
|
| 55 |
-
setLink('selectedOpenSpace',summary.target_space_url);
|
| 56 |
-
setLink('selectedOpenSettings',summary.target_space_url?summary.target_space_url+'/settings':'');
|
| 57 |
-
setLink('selectedOpenArtifacts',artifacts);
|
| 58 |
-
const btn=document.getElementById('selectedValidateBtn');
|
| 59 |
-
if(btn){btn.disabled=!summary.target_space;btn.onclick=()=>{state.selectedRunDetail=state.selectedRunDetail||{summary};prepareValidationFromActiveRun();}}
|
| 60 |
-
}
|
| 61 |
-
function renderBlockers(detail={}){
|
| 62 |
-
const root=document.getElementById('selectedBlockers');if(!root)return;
|
| 63 |
-
const summary=detail.summary||{};const blockers=detail.technical_blockers||{};
|
| 64 |
-
const manual=summary.manual_hardware_required||String(summary.status||'').includes('manual_hardware_required');
|
| 65 |
-
const blockerItems=Array.isArray(blockers.blockers)?blockers.blockers:[];
|
| 66 |
-
if(!manual&&!blockerItems.length){root.hidden=true;root.innerHTML='';return;}
|
| 67 |
-
root.hidden=false;const parts=[];
|
| 68 |
-
if(manual)parts.push('<strong>Manual hardware required</strong><p>Open Space settings, choose the recommended GPU, then run validation.</p>');
|
| 69 |
-
for(const b of blockerItems.slice(0,4)){parts.push(`<strong>${shortText(b.type||b.name||'Technical blocker',32)}</strong><p>${shortText(b.claim||b.reason||b.message||'',120)}</p>`)}
|
| 70 |
-
root.innerHTML=parts.join('');
|
| 71 |
-
}
|
| 72 |
-
function renderRunRows(runs,compact=false){
|
| 73 |
-
return (runs||[]).map(r=>`<button class="run-row" data-run="${escapeHtml(r.run_id)}"><span class="dot ${statusClass(r.status)}"></span><strong class="run-id" title="${escapeHtml(r.run_id)}">${shortText(r.run_id,compact?22:26)}</strong><span title="${escapeHtml(r.model_id||'')}">${shortText(r.model_id,compact?22:24)}</span><em class="pill run-status ${statusClass(r.status)}">${r.status||'unknown'}</em><span title="${escapeHtml(r.target_space||'')}">${shortText(r.target_space,compact?24:28)}</span><span>${r.selected_hardware||'β'}</span></button>`).join('')
|
| 74 |
-
}
|
| 75 |
-
function renderRunsFromCache(){
|
| 76 |
-
const runs=filteredRuns();
|
| 77 |
-
const roots=[['runsTable',false],['runsTableHome',true]];
|
| 78 |
-
for(const [id,compact] of roots){
|
| 79 |
-
const root=document.getElementById(id);
|
| 80 |
-
if(root)root.innerHTML=renderRunRows(runs,compact)||'<div class="empty">No runs match this filter.</div>';
|
| 81 |
-
}
|
| 82 |
-
renderRunStats();
|
| 83 |
-
document.querySelectorAll('.run-row[data-run]').forEach(row=>row.addEventListener('click',()=>selectRun(row.dataset.run)));
|
| 84 |
-
if(state.runId)document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',row.dataset.run===state.runId));
|
| 85 |
-
}
|
| 86 |
-
async function loadRuns(forceNetwork=false){
|
| 87 |
-
const stale=Date.now()-(state.runsLastLoaded||0)>45000;
|
| 88 |
-
const shouldFetch=forceNetwork||!state.runsCache.length||stale;
|
| 89 |
-
try{
|
| 90 |
-
if(shouldFetch){
|
| 91 |
-
document.querySelectorAll('.run-explorer').forEach(x=>x.classList.add('is-refreshing'));
|
| 92 |
-
const data=await apiGet(`/api/runs?limit=100&bucket_name=${encodeURIComponent(state.bucketName)}`);
|
| 93 |
-
state.runsCache=data.runs||[];
|
| 94 |
-
state.runsLastLoaded=Date.now();
|
| 95 |
-
if(data.bucket_source)state.bucketSource=data.bucket_source;
|
| 96 |
-
}
|
| 97 |
-
renderRunsFromCache();
|
| 98 |
-
}catch(e){
|
| 99 |
-
for(const id of ['runsTable','runsTableHome']){const root=document.getElementById(id);if(root)root.textContent='Could not load runs: '+e.message}
|
| 100 |
-
if(forceNetwork)console.warn(e);
|
| 101 |
-
}finally{
|
| 102 |
-
document.querySelectorAll('.run-explorer').forEach(x=>x.classList.remove('is-refreshing'));
|
| 103 |
-
}
|
| 104 |
-
}
|
| 105 |
-
async function selectRun(runId){
|
| 106 |
-
if(!runId)return;
|
| 107 |
-
document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',row.dataset.run===runId));
|
| 108 |
-
setBuildModeInspecting(runId);
|
| 109 |
-
try{
|
| 110 |
-
const detail=await apiGet(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`);
|
| 111 |
-
if(detail.bucket_source)state.bucketSource=detail.bucket_source;
|
| 112 |
-
renderRunDetail(detail);
|
| 113 |
-
startPolling(runId);
|
| 114 |
-
}catch(e){
|
| 115 |
-
const root=document.getElementById('reportPreview');if(root)root.textContent='Could not load run details: '+e.message;
|
| 116 |
-
const home=document.getElementById('homeReportPreview');if(home)home.innerHTML=`<strong>Could not load run details</strong><p>${escapeHtml(e.message)}</p>`;
|
| 117 |
-
}
|
| 118 |
-
}
|
| 119 |
-
function renderRunDetail(detail){
|
| 120 |
-
state.selectedRunDetail=detail;
|
| 121 |
-
const summary=detail.summary||{};const smoke=detail.generation_smoke||{};const files=detail.files||[];
|
| 122 |
-
setText('selectedRunId',detail.run_id||summary.run_id||'β');setText('selectedModelId',summary.model_id||'β');setText('selectedTargetSpace',summary.target_space||'β');setText('selectedHardware',summary.selected_hardware||'β');
|
| 123 |
-
setText('runDetailStatus',summary.status||'unknown');setText('homeRunDetailStatus',summary.status||'unknown');setText('detailRunId',summary.run_id||detail.run_id);setText('detailJobStatus',summary.status||'β');setText('detailGateStatus',(detail.inference_gate||{}).status||summary.status||'β');setText('detailSpace',summary.target_space||'β');setText('detailHardware',summary.selected_hardware||summary.hardware||'β');setText('detailTraces',Array.isArray(detail.events)?String(detail.events.length)+' events':summary.traces_count||'β');
|
| 124 |
-
const statusEl=document.getElementById('runDetailStatus');if(statusEl)statusEl.className='badge '+statusClass(summary.status);const homeStatus=document.getElementById('homeRunDetailStatus');if(homeStatus)homeStatus.className='badge '+statusClass(summary.status);
|
| 125 |
-
const validation=document.getElementById('selectedValidation');if(validation){validation.innerHTML=`<li><span>Health</span><strong>${summary.health_passed?'passed':'β'}</strong></li><li><span>Smoke test</span><strong>${summary.smoke_test_passed?'passed':(smoke.status||'β')}</strong></li><li><span>Latency</span><strong>${summary.latency_seconds?fmtSeconds(Math.round(summary.latency_seconds)):'β'}</strong></li>`}
|
| 126 |
-
const artifactRoot=document.getElementById('artifactList');if(artifactRoot){const interesting=files.filter(f=>!/traces\//.test(f.path||'')).slice(0,18);artifactRoot.innerHTML=interesting.length?interesting.map(f=>`<a href="${f.url}" target="_blank"><span>${f.path}</span><em>${f.size?Math.round(Number(f.size)/1024)+' KB':''}</em></a>`).join(''):'No artifacts indexed yet.'}
|
| 127 |
-
const reportText=(detail.report||'No report available.').slice(0,5000);const report=document.getElementById('reportPreview');if(report)report.textContent=reportText;
|
| 128 |
-
const homeReport=document.getElementById('homeReportPreview');if(homeReport){const firstLine=reportText.split('\n').find(Boolean)||'No report available yet.';homeReport.innerHTML=`<span class="status-dot ${statusClass(summary.status)==='error'?'amber-dot':'green'}"></span><strong>${escapeHtml((detail.inference_gate||{}).status||summary.status||'Run selected')}</strong><p>${escapeHtml(shortText(firstLine,180))}</p>`}
|
| 129 |
-
setQuickLinks({job_url:inferredJobUrl(summary,detail),target_space_url:summary.target_space_url,target_space_settings_url:summary.target_space_url?summary.target_space_url+'/settings':'',artifacts_url:summary.artifacts_url,bucket_source:detail.bucket_source,run_id:summary.run_id||detail.run_id});
|
| 130 |
-
renderSelectedLinks({...summary,bucket_source:detail.bucket_source});renderBlockers(detail);
|
| 131 |
-
const homeBtn=document.getElementById('homeValidateBtn');if(homeBtn){homeBtn.disabled=!summary.target_space;homeBtn.onclick=prepareValidationFromActiveRun}
|
| 132 |
-
}
|
| 133 |
-
let runSearchTimer=null;
|
| 134 |
-
document.getElementById('runSearch')?.addEventListener('input',()=>{clearTimeout(runSearchTimer);runSearchTimer=setTimeout(renderRunsFromCache,90)});
|
|
|
|
| 1 |
+
function inferredJobUrl(summary={}, detail={}){if(summary.job_url)return summary.job_url;const launch=detail.launch||{};if(launch.job_url)return launch.job_url;const jobId=summary.job_id||launch.job_id||detail.state?.job_id;const owner=launch.created_by||detail.state?.created_by||state.user?.username;return jobId&&owner?`https://huggingface.co/jobs/${owner}/${jobId}`:''}
|
| 2 |
+
function runMatchesStatus(run,status){const s=String(run.status||'unknown').toLowerCase();if(!status||status==='all')return true;if(status==='running')return s.includes('running')||s==='queued'||s==='pending'||s==='started'||s==='waiting';if(status==='full_inference_success')return s.includes('success')||s.includes('passed');if(status==='manual_hardware_required')return s.includes('manual')||s.includes('hardware');if(status==='technical_blocker')return s.includes('blocker');if(status==='failed')return s.includes('failed')||s.includes('error');return s===String(status).toLowerCase()}
|
| 3 |
+
function runMatchesQuery(run,query){if(!query)return true;const haystack=[run.run_id,run.model_id,run.target_space,run.status,run.kind,run.selected_hardware].map(x=>String(x||'').toLowerCase()).join(' ');return haystack.includes(String(query).toLowerCase())}
|
| 4 |
+
function filteredRuns(){return (state.runsCache||[]).filter(r=>runMatchesStatus(r,state.runStatusFilter||'all')&&runMatchesQuery(r,state.runSearch||''))}
|
| 5 |
+
function renderRunStats(){const counts={all:state.runsCache.length,running:0,full_inference_success:0,manual_hardware_required:0,failed:0};for(const r of state.runsCache){if(runMatchesStatus(r,'running'))counts.running+=1;if(runMatchesStatus(r,'full_inference_success'))counts.full_inference_success+=1;if(runMatchesStatus(r,'manual_hardware_required'))counts.manual_hardware_required+=1;if(runMatchesStatus(r,'failed'))counts.failed+=1}document.querySelectorAll('.filter[data-status]').forEach(btn=>{const status=btn.dataset.status||'all';const label={all:'All',running:'Running',full_inference_success:'Success',manual_hardware_required:'Manual',failed:'Failed',technical_blocker:'Blockers'}[status]||status;const count=counts[status];btn.textContent=typeof count==='number'?`${label} ${count}`:label;btn.classList.toggle('active',status===state.runStatusFilter)})}
|
| 6 |
+
function renderRunRows(runs,compact=false){return (runs||[]).map(r=>`<button class="run-row" data-run="${escapeHtml(r.run_id)}"><span class="dot ${statusClass(r.status)}"></span><strong class="run-id" title="${escapeHtml(r.run_id)}">${shortText(r.run_id,compact?24:30)}</strong><em class="pill ${statusClass(r.status)}">${escapeHtml(badgeLabel(r.status||'unknown'))}</em><span title="${escapeHtml(r.model_id||'')}">${shortText(r.model_id,compact?28:34)}</span><span title="${escapeHtml(r.target_space||'')}">${shortText(r.target_space,compact?28:34)}</span><span>${escapeHtml(r.selected_hardware||'β')}</span></button>`).join('')}
|
| 7 |
+
function renderRunsFromCache(){const runs=filteredRuns();for(const [id,compact] of [['runsTableHome',true],['runsTable',false]]){const root=$(id);if(root)root.innerHTML=renderRunRows(runs,compact)||'<div class="empty">No runs match this filter.</div>'}renderRunStats();document.querySelectorAll('.run-row[data-run]').forEach(row=>row.addEventListener('click',()=>selectRun(row.dataset.run)));if(state.runId)document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',row.dataset.run===state.runId))}
|
| 8 |
+
async function loadRuns(forceNetwork=false){const stale=Date.now()-(state.runsLastLoaded||0)>45000;const shouldFetch=forceNetwork||!state.runsCache.length||stale;try{if(shouldFetch){document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.add('is-refreshing'));const data=await apiGet(`/api/runs?limit=100&bucket_name=${encodeURIComponent(state.bucketName)}`);state.runsCache=data.runs||[];state.runsLastLoaded=Date.now();if(data.bucket_source)state.bucketSource=data.bucket_source}renderRunsFromCache()}catch(e){for(const id of ['runsTable','runsTableHome']){const root=$(id);if(root)root.textContent='Could not load runs: '+e.message}if(forceNetwork)console.warn(e)}finally{document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.remove('is-refreshing'))}}
|
| 9 |
+
async function selectRun(runId){if(!runId)return;document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',row.dataset.run===runId));setBuildModeInspecting(runId);try{const detail=await apiGet(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`);if(detail.bucket_source)state.bucketSource=detail.bucket_source;renderRunDetail(detail);startPolling(runId);switchView(state.currentView==='runs'?'runs':'build')}catch(e){const report=$('reportPreview');if(report)report.textContent='Could not load run details: '+e.message;const home=$('homeReportPreview');if(home)home.innerHTML=`<strong>Could not load run details</strong><p>${escapeHtml(e.message)}</p>`}}
|
| 10 |
+
function renderSelectedLinks(summary={},detail={}){const runId=summary.run_id||detail.run_id;const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;const artifacts=summary.artifacts_url||(typeof artifactUrl==='function'?artifactUrl(runId,bucket):'');const jobUrl=inferredJobUrl(summary,detail);setLink('selectedOpenJob',jobUrl);setLink('selectedOpenSpace',summary.target_space_url);setLink('selectedOpenSettings',summary.target_space_url?summary.target_space_url+'/settings':'');setLink('selectedOpenArtifacts',artifacts);const btn=$('selectedValidateBtn');if(btn){btn.disabled=!summary.target_space;btn.onclick=prepareValidationFromActiveRun}}
|
| 11 |
+
function renderBlockers(detail={}){const root=$('selectedBlockers');if(!root)return;const summary=detail.summary||{};const blockers=detail.technical_blockers||{};const manual=summary.manual_hardware_required||String(summary.status||'').includes('manual_hardware_required');const blockerItems=Array.isArray(blockers.blockers)?blockers.blockers:[];if(!manual&&!blockerItems.length){root.hidden=true;root.innerHTML='';return}root.hidden=false;const parts=[];if(manual)parts.push('<strong>Manual hardware required</strong><p>Open Space settings, choose the recommended GPU, then run validation.</p>');for(const b of blockerItems.slice(0,4)){parts.push(`<strong>${escapeHtml(shortText(b.type||b.name||'Technical blocker',32))}</strong><p>${escapeHtml(shortText(b.claim||b.reason||b.message||'',120))}</p>`)}root.innerHTML=parts.join('')}
|
| 12 |
+
function renderRunDetail(detail){state.selectedRunDetail=detail;const summary=detail.summary||{};const smoke=detail.generation_smoke||{};const files=detail.files||[];const view=detail.view||{};const header=view.header||{};setText('selectedRunId',detail.run_id||summary.run_id||'β');setText('selectedModelId',summary.model_id||'β');setText('selectedTargetSpace',summary.target_space||'β');setText('selectedHardware',summary.selected_hardware||summary.hardware||'β');setText('runDetailStatus',header.status||summary.status||'unknown');setText('homeRunDetailStatus',header.status_label||summary.status||'unknown');setText('detailRunId',summary.run_id||detail.run_id);setText('detailJobStatus',summary.status||'β');setText('detailGateStatus',(detail.inference_gate||{}).status||summary.status||'β');setText('detailSpace',summary.target_space||'β');setText('detailHardware',summary.selected_hardware||summary.hardware||'β');setText('detailTraces',Array.isArray(detail.events)?String(detail.events.length)+' events':summary.traces_count||'β');const validation=$('selectedValidation');if(validation){validation.innerHTML=`<li><span>Health</span><strong>${summary.health_passed?'passed':'β'}</strong></li><li><span>Smoke test</span><strong>${summary.smoke_test_passed?'passed':(smoke.status||'β')}</strong></li><li><span>Latency</span><strong>${summary.latency_seconds?fmtSeconds(Math.round(summary.latency_seconds)):'β'}</strong></li>`}const artifactRoot=$('artifactList');if(artifactRoot){const interesting=files.filter(f=>!/traces\//.test(f.path||'')).slice(0,18);artifactRoot.innerHTML=interesting.length?interesting.map(f=>`<a href="${escapeHtml(f.url)}" target="_blank"><span>${escapeHtml(f.path)}</span><em>${f.size?Math.round(Number(f.size)/1024)+' KB':''}</em></a>`).join(''):'No artifacts indexed yet.'}const reportText=(detail.report||'No report available.').slice(0,8000);const report=$('reportPreview');if(report)report.textContent=reportText;const homeReport=$('homeReportPreview');if(homeReport){const firstLine=reportText.split('\n').find(Boolean)||'No report available yet.';homeReport.innerHTML=`<strong>${escapeHtml(header.status_label||summary.status||'Run selected')}</strong><p>${escapeHtml(shortText(firstLine,180))}</p>`}setQuickLinks({job_url:inferredJobUrl(summary,detail),target_space_url:summary.target_space_url,target_space_settings_url:summary.target_space_url?summary.target_space_url+'/settings':'',artifacts_url:summary.artifacts_url,bucket_source:detail.bucket_source,run_id:summary.run_id||detail.run_id});renderSelectedLinks({...summary,bucket_source:detail.bucket_source},detail);renderBlockers(detail);renderMetrics({...detail,summary});if(view.schema_version)renderRunViewModel(view,{...detail,progress:detail.progress||0,events:detail.events||[]});const homeBtn=$('homeValidateBtn');if(homeBtn){homeBtn.disabled=!summary.target_space;homeBtn.onclick=prepareValidationFromActiveRun}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|