Upload 8 files
Browse files- web/static/app.css +57 -1
- web/static/app.js +62 -14
- web/static/progress.js +7 -4
- web/static/runs.js +78 -29
- web/static/styles.css +14 -0
web/static/app.css
CHANGED
|
@@ -2793,7 +2793,7 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 2793 |
|
| 2794 |
/* v190.10 — Active Run timeline hotfix */
|
| 2795 |
|
| 2796 |
-
/* v190.
|
| 2797 |
.runs-table{
|
| 2798 |
gap:12px!important;
|
| 2799 |
}
|
|
@@ -2857,3 +2857,59 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 2857 |
.run-row.run-list-item{padding:13px 10px!important;border-radius:18px!important}
|
| 2858 |
.linked-validation-strip{width:100%;justify-content:space-between;border-radius:14px}
|
| 2859 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2793 |
|
| 2794 |
/* v190.10 — Active Run timeline hotfix */
|
| 2795 |
|
| 2796 |
+
/* v190.15 — Build-first Runs Explorer polish */
|
| 2797 |
.runs-table{
|
| 2798 |
gap:12px!important;
|
| 2799 |
}
|
|
|
|
| 2857 |
.run-row.run-list-item{padding:13px 10px!important;border-radius:18px!important}
|
| 2858 |
.linked-validation-strip{width:100%;justify-content:space-between;border-radius:14px}
|
| 2859 |
}
|
| 2860 |
+
|
| 2861 |
+
/* v190.15 — Build-first compatibility cleanup */
|
| 2862 |
+
.run-row.run-list-item{border-left:0!important;border-right:0!important}
|
| 2863 |
+
.run-row.build-run{background:linear-gradient(180deg,#fff,#fbfaf7)}
|
| 2864 |
+
.run-row .linked-validation-strip{margin-top:10px}
|
| 2865 |
+
|
| 2866 |
+
/* v190.16 — Delete cascade clarity */
|
| 2867 |
+
.delete-run-meta span strong{letter-spacing:.01em}
|
| 2868 |
+
.delete-run-meta span code{word-break:break-word}
|
| 2869 |
+
.delete-run-card{
|
| 2870 |
+
border-left:0!important;
|
| 2871 |
+
border-right:0!important;
|
| 2872 |
+
box-shadow:0 24px 80px rgba(15,23,42,.18)!important;
|
| 2873 |
+
}
|
| 2874 |
+
.delete-run-warning{
|
| 2875 |
+
border-left:0!important;
|
| 2876 |
+
border-right:0!important;
|
| 2877 |
+
}
|
| 2878 |
+
|
| 2879 |
+
/* v190.16 — Build-first Runs Explorer polish */
|
| 2880 |
+
/* v190.16 — Build-first compatibility cleanup */
|
| 2881 |
+
|
| 2882 |
+
/* v190.17 — Effective eval and linked validation provenance */
|
| 2883 |
+
.eval-effective-note{
|
| 2884 |
+
margin:.45rem 0 .7rem;
|
| 2885 |
+
padding:.55rem .7rem;
|
| 2886 |
+
border:0;
|
| 2887 |
+
border-radius:14px;
|
| 2888 |
+
background:rgba(34,197,94,.08);
|
| 2889 |
+
color:#31513a;
|
| 2890 |
+
}
|
| 2891 |
+
.eval-effective-note code{font-size:.78rem;}
|
| 2892 |
+
.metrics-list li em{
|
| 2893 |
+
display:block;
|
| 2894 |
+
margin-top:.18rem;
|
| 2895 |
+
color:var(--muted,#6b7280);
|
| 2896 |
+
font-size:.75rem;
|
| 2897 |
+
font-style:normal;
|
| 2898 |
+
}
|
| 2899 |
+
|
| 2900 |
+
/* v190.17 — Delete cascade clarity */
|
| 2901 |
+
|
| 2902 |
+
/* v190.17 — Build-first Runs Explorer polish */
|
| 2903 |
+
|
| 2904 |
+
/* v190.17 — Build-first compatibility cleanup */
|
| 2905 |
+
|
| 2906 |
+
/* v190.19 — Build completion matrix + endpoint discovery-first linked Space Test */
|
| 2907 |
+
.space-test-policy-chip{display:inline-flex;align-items:center;gap:.4rem;border-radius:999px;padding:.35rem .65rem;background:rgba(99,102,241,.09);color:#3730a3;font-weight:650;border:0;box-shadow:none}
|
| 2908 |
+
.space-test-policy-chip.recover{background:rgba(245,158,11,.12);color:#92400e}
|
| 2909 |
+
.space-test-policy-chip.unavailable,.space-test-policy-chip.blocked{background:rgba(100,116,139,.12);color:#475569}
|
| 2910 |
+
.endpoint-discovery-note{margin-top:.55rem;padding:.7rem .85rem;border-radius:14px;background:rgba(14,165,233,.08);color:#075985;border:0;line-height:1.45}
|
| 2911 |
+
.recovered-build-chip{display:inline-flex;gap:.35rem;align-items:center;border-radius:999px;padding:.25rem .55rem;background:rgba(16,185,129,.12);color:#047857;border:0}
|
| 2912 |
+
/* v190.19 — Build-first Runs Explorer polish */
|
| 2913 |
+
/* v190.19 — Build-first compatibility cleanup */
|
| 2914 |
+
/* v190.19 — Delete cascade clarity */
|
| 2915 |
+
/* v190.19 — Effective eval and linked validation provenance */
|
web/static/app.js
CHANGED
|
@@ -344,11 +344,13 @@ function renderAnonymousEvalStatus(me){
|
|
| 344 |
const enable=$('enableEvalArchive');
|
| 345 |
const disable=$('disableEvalArchive');
|
| 346 |
const verify=$('verifyEvalArchive');
|
|
|
|
| 347 |
const stateBadge=$('evalArchiveStateBadge');
|
| 348 |
const cfg=me?.anonymous_eval||{};
|
| 349 |
const enabled=Boolean(cfg.enabled);
|
| 350 |
const canManage=Boolean(cfg.can_manage)&&cfg.env_override!==true;
|
| 351 |
const readonly=Boolean(cfg.readonly)||Boolean(enabled&&!canManage);
|
|
|
|
| 352 |
const bucket=cfg.bucket_source||cfg.proposed_bucket_source||'configured bucket';
|
| 353 |
const path=cfg.bucket_path?'/'+cfg.bucket_path:(cfg.default_bucket_path?'/'+cfg.default_bucket_path:'');
|
| 354 |
const mount=cfg.job_mount_path||cfg.default_mount_path||'/evals';
|
|
@@ -396,6 +398,11 @@ function renderAnonymousEvalStatus(me){
|
|
| 396 |
verify.disabled=state.busy;
|
| 397 |
verify.title='Refresh and verify the eval archive configuration.';
|
| 398 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
}
|
| 400 |
function setBucketReady(ok,message){state.bucketReady=Boolean(ok);setBuildLaunchEnabled();const feedback=state.bucketReady?'Ready for builds, validations and artifacts.':(message||'Create your private run bucket before launching builds.');setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':bucketFeedbackSummary(feedback));setText('bucketInlineStatus',state.bucketReady?'Ready':'Setup needed');setBucketFeedback(state.bucketReady?'ready':'error',feedback);for(const id of ['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);setBuildLaunchEnabled()}
|
| 401 |
function effectivePreferredHardware(){const preferred=formValue('preferredHardware')||'zero-a10g';const fallback=formValue('fallbackHardware')||'a10g-large';const tryZero=$('tryZeroGpu')?.checked??true;if(tryZero)return preferred;if(preferred==='zero-a10g'||preferred.startsWith('zero'))return fallback;return preferred}
|
|
@@ -643,6 +650,20 @@ async function disableEvalArchive(){
|
|
| 643 |
}
|
| 644 |
|
| 645 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
async function checkBucket(noisy=true){setButtonBusy('checkBucket',true,'Checking…');setBucketFeedback('checking','Checking private bucket access…');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)}}
|
| 647 |
async function createBucket(){setButtonBusy('createBucket',true,'Creating…');setBucketFeedback('creating','Creating private bucket…');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)}}
|
| 648 |
|
|
@@ -746,11 +767,12 @@ function validationPrefillFromDetail(detail={}){
|
|
| 746 |
let kwargs=firstObjectPath(smoke.test_kwargs,smoke.kwargs,gate.test_kwargs,gateSmoke.test_kwargs,launch.test_kwargs_json,summary.test_kwargs_json);
|
| 747 |
if(!Array.isArray(args))args=defaultValidationArgs(expected);
|
| 748 |
if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')kwargs={};
|
| 749 |
-
const
|
| 750 |
-
const
|
| 751 |
-
const
|
| 752 |
-
const
|
| 753 |
-
|
|
|
|
| 754 |
}
|
| 755 |
function renderValidationStatus(data={}){
|
| 756 |
const candidateRunId=validationDetailRunId(data)||state.validationRunId;
|
|
@@ -879,10 +901,10 @@ function renderValidationPrefillDraft(prefill={},sourceLabel='selected Space'){
|
|
| 879 |
if(tabDot)tabDot.className='tab-status-dot neutral';
|
| 880 |
const card=$('spaceTestPreview');
|
| 881 |
if(card)card.classList.remove('is-validating');
|
| 882 |
-
setText('spaceTestOutput',prefill.automaticSmokePassed?'Replay linked smoke test':'New linked validation');
|
| 883 |
setText('validationStartedAt','Not launched');
|
| 884 |
setText('spaceTestLatency','—');
|
| 885 |
-
setText('spaceTestVerdict',prefill.automaticSmokePassed?`Automatic smoke passed · ready to replay from ${sourceLabel}`:`Linked to ${sourceLabel}`);
|
| 886 |
setText('spaceTestProgressPercent','0%');
|
| 887 |
const fill=$('spaceTestProgressFill');
|
| 888 |
if(fill){fill.style.width='0%';fill.className='validation-progress-fill neutral'}
|
|
@@ -893,7 +915,7 @@ function renderValidationPrefillDraft(prefill={},sourceLabel='selected Space'){
|
|
| 893 |
const endpoints=$('spaceTestEndpoints');
|
| 894 |
if(endpoints)endpoints.innerHTML=`<div class="empty compact-empty">Linked to Build Run <code>${escapeHtml(state.validationPrefillDraft.parentBuildRunId||'—')}</code>. Endpoint schema will be refreshed when this validation runs.</div>`;
|
| 895 |
const launchBtn=$('launchValidate');
|
| 896 |
-
if(launchBtn&&!launchBtn.classList.contains('is-busy'))launchBtn.textContent='Launch validation';
|
| 897 |
setValidationLaunchEnabled();
|
| 898 |
const deleteBtn=$('deleteValidationRun');
|
| 899 |
if(deleteBtn){deleteBtn.disabled=true;deleteBtn.onclick=null;}
|
|
@@ -917,11 +939,30 @@ function prepareValidationFromActiveRun(){
|
|
| 917 |
const activeDetail={...selected,...(state.activeRunProgress||{}),summary:{...(selected.summary||{}),...(state.activeRunProgress?.summary||{})}};
|
| 918 |
applyValidationPrefill(activeDetail,'active run',{preserveSelection:false});
|
| 919 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 920 |
function prepareValidationFromSelectedRun(){
|
| 921 |
applyValidationPrefill(state.selectedRunDetail||{},'selected run',{preserveSelection:false});
|
| 922 |
}
|
| 923 |
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,model_id:formValue('modelId'),target_space_name:formValue('targetSpace')||undefined,pi_model:formValue('piModel'),implementation_mode:formValue('implementationMode'),preferred_space_hardware:effectivePreferredHardware(),fallback_space_hardware:formValue('fallbackHardware'),allow_fixed_gpu_fallback:$('allowFallback')?.checked??true,try_zero_gpu_first:$('tryZeroGpu')?.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)}}
|
| 924 |
-
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Job…');setButtonBusy('launchValidate',true,'Launching…');try{const args=parseJsonField('testArgs',[]);const kwargs=parseJsonField('testKwargs',{});const payload={bucket_name:state.bucketName,parent_build_run_id:
|
| 925 |
async function refreshRunReportPreview(runId){
|
| 926 |
if(!runId)return;
|
| 927 |
try{
|
|
@@ -967,11 +1008,12 @@ function readRunLatencyPayload(p={}){
|
|
| 967 |
const latency=firstPositiveNumber(smoke.latency_seconds,smoke.observed_latency_seconds,summary.latency_seconds,summary.observed_latency_seconds,p.latency_seconds,p.observed_latency_seconds,manual.latency_seconds,manual.observed_latency_seconds,eventLatency);
|
| 968 |
const duration=firstPositiveNumber(smoke.recommended_zerogpu_duration_seconds,smoke.recommended_zero_gpu_duration_seconds,smoke.recommended_duration_seconds,summary.recommended_zerogpu_duration_seconds,summary.recommended_zero_gpu_duration_seconds,summary.recommended_duration_seconds,manual.recommended_zero_gpu_duration_seconds,gate.recommended_zerogpu_duration_seconds,gate.recommended_zero_gpu_duration_seconds,gate.zero_gpu_duration_recommendation,eventDuration);
|
| 969 |
const source=smoke.recommendation_source||summary.recommendation_source||manual.recommendation_source||gate.recommendation_source||(manual.status==='success'?'linked Space Test':'')||(eventLatency?'generation_smoke event':'');
|
|
|
|
| 970 |
const failureType=smoke.failure_type||validation.failure_type||gate.failure_type||'';
|
| 971 |
const failureOwner=smoke.failure_owner||validation.failure_owner||gate.failure_owner||'';
|
| 972 |
const missingArgument=smoke.missing_argument||validation.missing_argument||gate.missing_argument||'';
|
| 973 |
const error=smoke.error||smoke.message||validation.failure_reason||gate.message||'';
|
| 974 |
-
return {smoke,gate,latency,duration,source,failureType,failureOwner,missingArgument,error};
|
| 975 |
}
|
| 976 |
function smokeFailureLatencyNote({smoke={},failureType='',failureOwner='',missingArgument='',error=''}={}){
|
| 977 |
const status=String(smoke.status||'').toLowerCase();
|
|
@@ -996,7 +1038,7 @@ function renderActiveLatency(p={}){
|
|
| 996 |
const runId=String(p.run_id||p.summary?.run_id||state.runId||'');
|
| 997 |
state.latencyCache=state.latencyCache||{};
|
| 998 |
const payload=readRunLatencyPayload(p);
|
| 999 |
-
const {latency,duration,source}=payload;
|
| 1000 |
const hasLatency=Number.isFinite(latency)&&latency>0;
|
| 1001 |
const hasDuration=Number.isFinite(duration)&&duration>0;
|
| 1002 |
const cached=state.latencyCache[runId]||{};
|
|
@@ -1006,7 +1048,8 @@ function renderActiveLatency(p={}){
|
|
| 1006 |
const finalBlocker=p.build_error_observation?' Final blocker: build error after repair.':'';
|
| 1007 |
const failedWithoutSmoke=(runStatus==='failed'||runStatus==='error')&&!hasLatency;
|
| 1008 |
const failureNote=smokeFailureLatencyNote(payload)||(failedWithoutSmoke?`Not measured because live validation did not reach a successful generation smoke test.${finalBlocker}`:'');
|
| 1009 |
-
const
|
|
|
|
| 1010 |
if(hasLatency||hasDuration)state.latencyCache[runId]={latency:stableLatency,duration:stableDuration,source:stableSource};
|
| 1011 |
const finalHasLatency=Number.isFinite(stableLatency)&&stableLatency>0;
|
| 1012 |
const finalHasDuration=Number.isFinite(stableDuration)&&stableDuration>0;
|
|
@@ -1016,7 +1059,7 @@ function renderActiveLatency(p={}){
|
|
| 1016 |
const note=$('activeLatencyNote');
|
| 1017 |
if(note){
|
| 1018 |
note.textContent=finalHasDuration
|
| 1019 |
-
?
|
| 1020 |
: (failureNote||'When available, the live smoke-test latency is used to recommend the `@spaces.GPU(duration=...)` value for ZeroGPU apps.');
|
| 1021 |
}
|
| 1022 |
card.classList.toggle('has-latency',finalHasLatency);
|
|
@@ -1077,7 +1120,7 @@ function markPendingActiveFields(){
|
|
| 1077 |
el.classList.toggle('is-pending',pending);
|
| 1078 |
});
|
| 1079 |
}
|
| 1080 |
-
function renderMetrics(p){const root=$('metricsList');const {smoke,gate,latency,duration}=readRunLatencyPayload(p);const health=(gate.implementation_signals&&gate.implementation_signals.health_passed)||smoke.health_passed;const smokeOk=smoke.ok||smoke.status==='success';renderActiveLatency(p);if(!root)return;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>${Number.isFinite(latency)&&latency>0?formatLatencySeconds(latency):'—'}</strong></li><li><span>Recommended ZeroGPU duration</span><strong>${Number.isFinite(duration)&&duration>0?fmtSeconds(Math.round(duration)):'—'}</strong></li>`}
|
| 1081 |
function renderEvents(events){const root=$('eventsPanel');if(!root)return;try{const rows=Array.isArray(events)?events:[];root.textContent=rows.slice(-12).map(e=>`${e?.ts||''} ${e?.step||''} ${e?.status||''} ${e?.message||''}`).join('\n')||'No events yet. The run may still be scheduling.'}catch(e){console.warn('renderEvents failed',e);root.textContent='Events could not be rendered.'}}
|
| 1082 |
async function cancelActiveRun(){
|
| 1083 |
if(!state.runId){showMessage('Select a running run before cancelling.', 'warning');return}
|
|
@@ -1135,6 +1178,7 @@ function bindActions(){
|
|
| 1135 |
enableEvalArchive:enableEvalArchive,
|
| 1136 |
verifyEvalArchive:verifyEvalArchive,
|
| 1137 |
disableEvalArchive:disableEvalArchive,
|
|
|
|
| 1138 |
refreshRuns:()=>loadRuns(true),
|
| 1139 |
refreshRunsHome:()=>loadRuns(true),
|
| 1140 |
cancelRun:cancelActiveRun,
|
|
@@ -1158,3 +1202,7 @@ bindNavigation();bindActions();bindRunFilters();bindValidationDraftInputs();setT
|
|
| 1158 |
// 'Eval archive off · set env vars'
|
| 1159 |
// Anonymous project eval archive enabled:
|
| 1160 |
// Env override remains supported for backend publishing: ASF_EVAL_ENABLED=true, ASF_EVAL_BUCKET_SOURCE=<namespace>/<bucket>, ASF_EVAL_BUCKET_PATH=evals, ASF_EVAL_BUCKET_MOUNT=/evals.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
const enable=$('enableEvalArchive');
|
| 345 |
const disable=$('disableEvalArchive');
|
| 346 |
const verify=$('verifyEvalArchive');
|
| 347 |
+
const flush=$('flushEvalArchive');
|
| 348 |
const stateBadge=$('evalArchiveStateBadge');
|
| 349 |
const cfg=me?.anonymous_eval||{};
|
| 350 |
const enabled=Boolean(cfg.enabled);
|
| 351 |
const canManage=Boolean(cfg.can_manage)&&cfg.env_override!==true;
|
| 352 |
const readonly=Boolean(cfg.readonly)||Boolean(enabled&&!canManage);
|
| 353 |
+
const canFlush=Boolean(cfg.can_flush)&&enabled;
|
| 354 |
const bucket=cfg.bucket_source||cfg.proposed_bucket_source||'configured bucket';
|
| 355 |
const path=cfg.bucket_path?'/'+cfg.bucket_path:(cfg.default_bucket_path?'/'+cfg.default_bucket_path:'');
|
| 356 |
const mount=cfg.job_mount_path||cfg.default_mount_path||'/evals';
|
|
|
|
| 398 |
verify.disabled=state.busy;
|
| 399 |
verify.title='Refresh and verify the eval archive configuration.';
|
| 400 |
}
|
| 401 |
+
if(flush){
|
| 402 |
+
flush.hidden=!canFlush;
|
| 403 |
+
flush.disabled=state.busy;
|
| 404 |
+
flush.title='Delete all anonymized eval records from the mounted operator archive. Config is preserved.';
|
| 405 |
+
}
|
| 406 |
}
|
| 407 |
function setBucketReady(ok,message){state.bucketReady=Boolean(ok);setBuildLaunchEnabled();const feedback=state.bucketReady?'Ready for builds, validations and artifacts.':(message||'Create your private run bucket before launching builds.');setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':bucketFeedbackSummary(feedback));setText('bucketInlineStatus',state.bucketReady?'Ready':'Setup needed');setBucketFeedback(state.bucketReady?'ready':'error',feedback);for(const id of ['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);setBuildLaunchEnabled()}
|
| 408 |
function effectivePreferredHardware(){const preferred=formValue('preferredHardware')||'zero-a10g';const fallback=formValue('fallbackHardware')||'a10g-large';const tryZero=$('tryZeroGpu')?.checked??true;if(tryZero)return preferred;if(preferred==='zero-a10g'||preferred.startsWith('zero'))return fallback;return preferred}
|
|
|
|
| 650 |
}
|
| 651 |
|
| 652 |
|
| 653 |
+
async function flushEvalArchive(){
|
| 654 |
+
const ok=window.confirm('Delete all anonymized eval archive records from the mounted operator bucket? This preserves the eval archive configuration but cannot be undone.');
|
| 655 |
+
if(!ok)return;
|
| 656 |
+
setButtonBusy('flushEvalArchive',true,'Flushing…');
|
| 657 |
+
try{
|
| 658 |
+
const result=await apiPost('/api/eval-archive/flush',{});
|
| 659 |
+
state.user={...(state.user||{}),anonymous_eval:result};
|
| 660 |
+
renderAnonymousEvalStatus(state.user);
|
| 661 |
+
showMessage(result.message||'Eval archive records flushed.',result.partial_flush?'warning':'success');
|
| 662 |
+
}catch(e){showMessage(e.message,true)}
|
| 663 |
+
finally{setButtonBusy('flushEvalArchive',false)}
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
|
| 667 |
async function checkBucket(noisy=true){setButtonBusy('checkBucket',true,'Checking…');setBucketFeedback('checking','Checking private bucket access…');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)}}
|
| 668 |
async function createBucket(){setButtonBusy('createBucket',true,'Creating…');setBucketFeedback('creating','Creating private bucket…');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)}}
|
| 669 |
|
|
|
|
| 767 |
let kwargs=firstObjectPath(smoke.test_kwargs,smoke.kwargs,gate.test_kwargs,gateSmoke.test_kwargs,launch.test_kwargs_json,summary.test_kwargs_json);
|
| 768 |
if(!Array.isArray(args))args=defaultValidationArgs(expected);
|
| 769 |
if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')kwargs={};
|
| 770 |
+
const policy=detail.space_test_policy||detail.view?.space_test_policy||detail.space_test?.policy||progress.view?.space_test_policy||progress.view?.space_test?.policy||{};
|
| 771 |
+
const target=policy.target_space||summary.target_space||stateObj.target_space||launch.target_space||progress.target_space||$('detailSpace')?.textContent||'';
|
| 772 |
+
const api=policy.requires_endpoint_discovery?'':normalizeApiName(policy.endpoint||smoke.api_name||gate.api_name||launch.api_name||gateSmoke.api_name||progress.api_name||'/generate');
|
| 773 |
+
const parentBuildRunId=policy.parent_build_run_id||detail.run_id||detail.summary?.run_id||state.runId||progress.run_id||'';
|
| 774 |
+
const automaticSmokePassed=Boolean(smoke.ok||smoke.status==='success'||policy.mode==='replay');
|
| 775 |
+
return {target,api,expected,args,kwargs,parentBuildRunId,sourceRunId:parentBuildRunId,linkedTargetSpace:target,automaticSmokePassed,smokeLatency:smoke.latency_seconds||smoke.observed_latency_seconds,spaceTestPolicy:policy,requiresEndpointDiscovery:Boolean(policy.requires_endpoint_discovery)};
|
| 776 |
}
|
| 777 |
function renderValidationStatus(data={}){
|
| 778 |
const candidateRunId=validationDetailRunId(data)||state.validationRunId;
|
|
|
|
| 901 |
if(tabDot)tabDot.className='tab-status-dot neutral';
|
| 902 |
const card=$('spaceTestPreview');
|
| 903 |
if(card)card.classList.remove('is-validating');
|
| 904 |
+
setText('spaceTestOutput',prefill.requiresEndpointDiscovery?'Discover endpoints and validate':(prefill.automaticSmokePassed?'Replay linked smoke test':'New linked validation'));
|
| 905 |
setText('validationStartedAt','Not launched');
|
| 906 |
setText('spaceTestLatency','—');
|
| 907 |
+
setText('spaceTestVerdict',prefill.spaceTestPolicy?.message|| (prefill.automaticSmokePassed?`Automatic smoke passed · ready to replay from ${sourceLabel}`:`Linked to ${sourceLabel}`));
|
| 908 |
setText('spaceTestProgressPercent','0%');
|
| 909 |
const fill=$('spaceTestProgressFill');
|
| 910 |
if(fill){fill.style.width='0%';fill.className='validation-progress-fill neutral'}
|
|
|
|
| 915 |
const endpoints=$('spaceTestEndpoints');
|
| 916 |
if(endpoints)endpoints.innerHTML=`<div class="empty compact-empty">Linked to Build Run <code>${escapeHtml(state.validationPrefillDraft.parentBuildRunId||'—')}</code>. Endpoint schema will be refreshed when this validation runs.</div>`;
|
| 917 |
const launchBtn=$('launchValidate');
|
| 918 |
+
if(launchBtn&&!launchBtn.classList.contains('is-busy'))launchBtn.textContent=prefill.requiresEndpointDiscovery?'Discover endpoints and validate':'Launch validation';
|
| 919 |
setValidationLaunchEnabled();
|
| 920 |
const deleteBtn=$('deleteValidationRun');
|
| 921 |
if(deleteBtn){deleteBtn.disabled=true;deleteBtn.onclick=null;}
|
|
|
|
| 939 |
const activeDetail={...selected,...(state.activeRunProgress||{}),summary:{...(selected.summary||{}),...(state.activeRunProgress?.summary||{})}};
|
| 940 |
applyValidationPrefill(activeDetail,'active run',{preserveSelection:false});
|
| 941 |
}
|
| 942 |
+
function contextualizeSpaceTestFromBuildRun(detail={}){
|
| 943 |
+
if(!detail||typeof detail!=='object')return;
|
| 944 |
+
if(typeof isValidationRunDetail==='function'&&isValidationRunDetail(detail))return;
|
| 945 |
+
if(state.validationMode==='running')return;
|
| 946 |
+
const prefill=validationPrefillFromDetail(detail);
|
| 947 |
+
if(!prefill.parentBuildRunId||!prefill.target||prefill.target==='—')return;
|
| 948 |
+
state.validationPrefillDraft={...prefill,sourceLabel:'selected Build Run',parentBuildRunId:prefill.parentBuildRunId,sourceRunId:prefill.parentBuildRunId,linkedTargetSpace:prefill.target};
|
| 949 |
+
state.linkedValidationParent=prefill.parentBuildRunId;
|
| 950 |
+
if(!state.validationArgsDirty){
|
| 951 |
+
setInputValue('validateSpace',prefill.target);
|
| 952 |
+
setInputValue('expectedOutput',prefill.expected);
|
| 953 |
+
setInputValue('validateApi',prefill.api);
|
| 954 |
+
setInputValue('testArgs',prettyJson(prefill.args));
|
| 955 |
+
setInputValue('testKwargs',prettyJson(prefill.kwargs));
|
| 956 |
+
}
|
| 957 |
+
const endpoints=$('spaceTestEndpoints');
|
| 958 |
+
if(endpoints&&state.centerTab!=='validate')endpoints.innerHTML=`<div class="empty compact-empty">Linked to selected Build Run <code>${escapeHtml(prefill.parentBuildRunId)}</code>. ${prefill.requiresEndpointDiscovery?'Endpoints will be discovered before validation.':'Open Space Test to replay or run a linked validation.'}</div>`;
|
| 959 |
+
setValidationLaunchEnabled();
|
| 960 |
+
}
|
| 961 |
function prepareValidationFromSelectedRun(){
|
| 962 |
applyValidationPrefill(state.selectedRunDetail||{},'selected run',{preserveSelection:false});
|
| 963 |
}
|
| 964 |
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,model_id:formValue('modelId'),target_space_name:formValue('targetSpace')||undefined,pi_model:formValue('piModel'),implementation_mode:formValue('implementationMode'),preferred_space_hardware:effectivePreferredHardware(),fallback_space_hardware:formValue('fallbackHardware'),allow_fixed_gpu_fallback:$('allowFallback')?.checked??true,try_zero_gpu_first:$('tryZeroGpu')?.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)}}
|
| 965 |
+
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Job…');setButtonBusy('launchValidate',true,'Launching…');try{const args=parseJsonField('testArgs',[]);const kwargs=parseJsonField('testKwargs',{});const draft=state.validationPrefillDraft||{};const apiForLaunch=draft.requiresEndpointDiscovery?'':normalizeApiName(formValue('validateApi')||'/generate');const payload={bucket_name:state.bucketName,parent_build_run_id:draft.parentBuildRunId||state.linkedValidationParent,target_space_id:formValue('validateSpace'),api_name:apiForLaunch,expected_output_type:formValue('expectedOutput')||'any',test_args_json:prettyJson(args),test_kwargs_json:prettyJson(kwargs),live_timeout_seconds:validationTimeoutValue()};const result=await apiPost('/api/validate',payload);state.validationPrefillDraft=null;state.validationArgsDirty=false;state.validationMode='running';renderValidationLaunchResult(result);startValidationPolling(result.run_id);showMessage(`Validation launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchValidate',false)}}
|
| 966 |
async function refreshRunReportPreview(runId){
|
| 967 |
if(!runId)return;
|
| 968 |
try{
|
|
|
|
| 1008 |
const latency=firstPositiveNumber(smoke.latency_seconds,smoke.observed_latency_seconds,summary.latency_seconds,summary.observed_latency_seconds,p.latency_seconds,p.observed_latency_seconds,manual.latency_seconds,manual.observed_latency_seconds,eventLatency);
|
| 1009 |
const duration=firstPositiveNumber(smoke.recommended_zerogpu_duration_seconds,smoke.recommended_zero_gpu_duration_seconds,smoke.recommended_duration_seconds,summary.recommended_zerogpu_duration_seconds,summary.recommended_zero_gpu_duration_seconds,summary.recommended_duration_seconds,manual.recommended_zero_gpu_duration_seconds,gate.recommended_zerogpu_duration_seconds,gate.recommended_zero_gpu_duration_seconds,gate.zero_gpu_duration_recommendation,eventDuration);
|
| 1010 |
const source=smoke.recommendation_source||summary.recommendation_source||manual.recommendation_source||gate.recommendation_source||(manual.status==='success'?'linked Space Test':'')||(eventLatency?'generation_smoke event':'');
|
| 1011 |
+
const hardware=manual.hardware_used_for_validation||manual.recommendation_hardware||summary.hardware_used_for_validation||summary.recommendation_hardware||p.hardware_used_for_validation||p.recommendation_hardware||p.state?.selected_hardware||summary.selected_hardware||'';
|
| 1012 |
const failureType=smoke.failure_type||validation.failure_type||gate.failure_type||'';
|
| 1013 |
const failureOwner=smoke.failure_owner||validation.failure_owner||gate.failure_owner||'';
|
| 1014 |
const missingArgument=smoke.missing_argument||validation.missing_argument||gate.missing_argument||'';
|
| 1015 |
const error=smoke.error||smoke.message||validation.failure_reason||gate.message||'';
|
| 1016 |
+
return {smoke,gate,latency,duration,source,hardware,failureType,failureOwner,missingArgument,error};
|
| 1017 |
}
|
| 1018 |
function smokeFailureLatencyNote({smoke={},failureType='',failureOwner='',missingArgument='',error=''}={}){
|
| 1019 |
const status=String(smoke.status||'').toLowerCase();
|
|
|
|
| 1038 |
const runId=String(p.run_id||p.summary?.run_id||state.runId||'');
|
| 1039 |
state.latencyCache=state.latencyCache||{};
|
| 1040 |
const payload=readRunLatencyPayload(p);
|
| 1041 |
+
const {latency,duration,source,hardware}=payload;
|
| 1042 |
const hasLatency=Number.isFinite(latency)&&latency>0;
|
| 1043 |
const hasDuration=Number.isFinite(duration)&&duration>0;
|
| 1044 |
const cached=state.latencyCache[runId]||{};
|
|
|
|
| 1048 |
const finalBlocker=p.build_error_observation?' Final blocker: build error after repair.':'';
|
| 1049 |
const failedWithoutSmoke=(runStatus==='failed'||runStatus==='error')&&!hasLatency;
|
| 1050 |
const failureNote=smokeFailureLatencyNote(payload)||(failedWithoutSmoke?`Not measured because live validation did not reach a successful generation smoke test.${finalBlocker}`:'');
|
| 1051 |
+
const hardwareSuffix=hardware&&String(hardware).toLowerCase()!=='unknown'?` on ${hardware}`:'';
|
| 1052 |
+
const stableSource=hasLatency?`${source||'live smoke test'}${hardwareSuffix}`:(failureNote?'not measured: smoke failed':(cached.source||'measured when smoke test passes'));
|
| 1053 |
if(hasLatency||hasDuration)state.latencyCache[runId]={latency:stableLatency,duration:stableDuration,source:stableSource};
|
| 1054 |
const finalHasLatency=Number.isFinite(stableLatency)&&stableLatency>0;
|
| 1055 |
const finalHasDuration=Number.isFinite(stableDuration)&&stableDuration>0;
|
|
|
|
| 1059 |
const note=$('activeLatencyNote');
|
| 1060 |
if(note){
|
| 1061 |
note.textContent=finalHasDuration
|
| 1062 |
+
? `This measured latency is normally used to tune the \`@spaces.GPU(duration=...)\` decorator for ZeroGPU when the generated Space uses ZeroGPU. Source hardware: ${hardware||'not recorded'}.`
|
| 1063 |
: (failureNote||'When available, the live smoke-test latency is used to recommend the `@spaces.GPU(duration=...)` value for ZeroGPU apps.');
|
| 1064 |
}
|
| 1065 |
card.classList.toggle('has-latency',finalHasLatency);
|
|
|
|
| 1120 |
el.classList.toggle('is-pending',pending);
|
| 1121 |
});
|
| 1122 |
}
|
| 1123 |
+
function renderMetrics(p){const root=$('metricsList');const {smoke,gate,latency,duration,source,hardware}=readRunLatencyPayload(p);const health=(gate.implementation_signals&&gate.implementation_signals.health_passed)||smoke.health_passed;const smokeOk=smoke.ok||smoke.status==='success';renderActiveLatency(p);if(!root)return;const provenance=[source||'',hardware?`hardware ${hardware}`:''].filter(Boolean).join(' · ');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>${Number.isFinite(latency)&&latency>0?formatLatencySeconds(latency):'—'}</strong></li><li><span>Recommended ZeroGPU duration</span><strong>${Number.isFinite(duration)&&duration>0?fmtSeconds(Math.round(duration)):'—'}</strong>${provenance?`<em>${escapeHtml(provenance)}</em>`:''}</li>`}
|
| 1124 |
function renderEvents(events){const root=$('eventsPanel');if(!root)return;try{const rows=Array.isArray(events)?events:[];root.textContent=rows.slice(-12).map(e=>`${e?.ts||''} ${e?.step||''} ${e?.status||''} ${e?.message||''}`).join('\n')||'No events yet. The run may still be scheduling.'}catch(e){console.warn('renderEvents failed',e);root.textContent='Events could not be rendered.'}}
|
| 1125 |
async function cancelActiveRun(){
|
| 1126 |
if(!state.runId){showMessage('Select a running run before cancelling.', 'warning');return}
|
|
|
|
| 1178 |
enableEvalArchive:enableEvalArchive,
|
| 1179 |
verifyEvalArchive:verifyEvalArchive,
|
| 1180 |
disableEvalArchive:disableEvalArchive,
|
| 1181 |
+
flushEvalArchive:flushEvalArchive,
|
| 1182 |
refreshRuns:()=>loadRuns(true),
|
| 1183 |
refreshRunsHome:()=>loadRuns(true),
|
| 1184 |
cancelRun:cancelActiveRun,
|
|
|
|
| 1202 |
// 'Eval archive off · set env vars'
|
| 1203 |
// Anonymous project eval archive enabled:
|
| 1204 |
// Env override remains supported for backend publishing: ASF_EVAL_ENABLED=true, ASF_EVAL_BUCKET_SOURCE=<namespace>/<bucket>, ASF_EVAL_BUCKET_PATH=evals, ASF_EVAL_BUCKET_MOUNT=/evals.
|
| 1205 |
+
/* v190.19 legacy invariant strings:
|
| 1206 |
+
setText('spaceTestOutput',prefill.automaticSmokePassed?'Replay linked smoke test':'New linked validation')
|
| 1207 |
+
parent_build_run_id:(state.validationPrefillDraft||{}).parentBuildRunId
|
| 1208 |
+
*/
|
web/static/progress.js
CHANGED
|
@@ -929,13 +929,16 @@ function renderDiagnostics(d={},header={}){
|
|
| 929 |
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('');
|
| 930 |
}
|
| 931 |
function renderSpaceTestPreview(s={}){
|
|
|
|
|
|
|
| 932 |
const st=$('spaceTestStatus');
|
| 933 |
if(st){
|
| 934 |
-
st.textContent=
|
| 935 |
-
st.className='badge '+
|
| 936 |
}
|
| 937 |
-
|
| 938 |
-
setText('
|
|
|
|
| 939 |
}
|
| 940 |
function animateProgressBar(progress,status,currentStep){
|
| 941 |
const fill=$('progressFill');
|
|
|
|
| 929 |
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('');
|
| 930 |
}
|
| 931 |
function renderSpaceTestPreview(s={}){
|
| 932 |
+
const policy=s.policy||s.space_test_policy||{};
|
| 933 |
+
const status=policy.mode||s.status||'pending';
|
| 934 |
const st=$('spaceTestStatus');
|
| 935 |
if(st){
|
| 936 |
+
st.textContent=policy.label||badgeLabel(status);
|
| 937 |
+
st.className='badge '+(policy.enabled?statusClass(status):'neutral');
|
| 938 |
}
|
| 939 |
+
const endpointLabel=policy.requires_endpoint_discovery?'Endpoint discovery required':(s.output_artifact||s.endpoint||policy.endpoint||'Waiting for API test');
|
| 940 |
+
setText('spaceTestOutput',endpointLabel);
|
| 941 |
+
setText('spaceTestVerdict',policy.message||s.verdict||'pending');
|
| 942 |
}
|
| 943 |
function animateProgressBar(progress,status,currentStep){
|
| 944 |
const fill=$('progressFill');
|
web/static/runs.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
function inferredOwnerFromBucket(bucket){return bucket?String(bucket).split('/')[0]:''}
|
| 2 |
function inferredJobUrl(summary={}, detail={}){if(summary.job_url)return summary.job_url;const launch=detail.launch||{};const stateObj=detail.state||{};if(launch.job_url)return launch.job_url;if(stateObj.job_url)return stateObj.job_url;const jobId=summary.job_id||launch.job_id||stateObj.job_id;const owner=summary.created_by||summary.username||summary.owner||launch.created_by||launch.username||launch.owner||stateObj.created_by||stateObj.username||stateObj.owner||state.user?.username||inferredOwnerFromBucket(summary.bucket_source||detail.bucket_source||state.bucketSource);return jobId&&owner?`https://huggingface.co/jobs/${owner}/${jobId}`:''}
|
| 3 |
function bucketRunFileUrl(bucket,runId,relPath=''){const rel=String(relPath||'').replace(/^\/+|\/+$/g,'');return bucket&&runId?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}${rel?`/${rel}`:''}`:''}
|
| 4 |
function runValidationTerminalText(run={}){
|
|
|
|
| 5 |
return [
|
| 6 |
run.verdict,run.validation_status,run.result_status,run.final_status,run.status,run.summary?.verdict,run.summary?.validation_status,run.summary?.result_status,run.summary?.status,
|
| 7 |
run.state?.verdict,run.state?.validation_status,run.state?.result_status,run.state?.status,run.gate_status,run.summary?.gate_status,run.inference_gate?.status,
|
|
@@ -9,11 +13,13 @@ function runValidationTerminalText(run={}){
|
|
| 9 |
].map(x=>String(x||'').toLowerCase()).filter(Boolean).join(' ');
|
| 10 |
}
|
| 11 |
function validationLockedStatus(run={}){
|
|
|
|
| 12 |
const runId=run.run_id||run.summary?.run_id||run.state?.run_id||'';
|
| 13 |
const terminal=runId&&typeof state!=='undefined'&&state.validationTerminalByRun?state.validationTerminalByRun[runId]:null;
|
| 14 |
return terminal?.canonical||'';
|
| 15 |
}
|
| 16 |
function runCanonicalStatus(run={}){
|
|
|
|
| 17 |
const validation=isValidationRunSummary(run);
|
| 18 |
const locked=validation?validationLockedStatus(run):'';
|
| 19 |
if(locked)return locked;
|
|
@@ -32,7 +38,7 @@ function runCanonicalStatus(run={}){
|
|
| 32 |
if(text.includes('running')||text.includes('queued')||text.includes('pending')||text.includes('started')||text.includes('waiting')||text.includes('building'))return'running';
|
| 33 |
return s||'unknown';
|
| 34 |
}
|
| 35 |
-
function runMatchesStatus(run,status){const canon=runCanonicalStatus(run);if(!status||status==='all')return true;if(status==='failed')return canon==='failed';if(status==='running')return canon==='running';if(status==='full_inference_success')return canon==='full_inference_success';if(status==='partial_validation')return canon==='partial_validation';if(status==='manual_hardware_required')return canon==='manual_hardware_required';if(status==='technical_blocker')return canon==='technical_blocker';if(status==='stale')return canon==='stale';return canon===String(status).toLowerCase()}
|
| 36 |
function canonicalStatusForDeletion(context={}){
|
| 37 |
const kind=context.kind==='validation'?'validation':'active';
|
| 38 |
const detail=context.detail||{};
|
|
@@ -41,8 +47,9 @@ function canonicalStatusForDeletion(context={}){
|
|
| 41 |
if(kind==='validation'&&typeof runCanonicalStatus==='function')return runCanonicalStatus(merged);
|
| 42 |
return String(merged.status||merged.summary?.status||merged.state?.status||'unknown').toLowerCase();
|
| 43 |
}
|
| 44 |
-
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())}
|
| 45 |
function runSortTimestamp(run={}){
|
|
|
|
| 46 |
const raw=run.updated_at||run.created_at||run.started_at||run.cancelled_at||'';
|
| 47 |
const ts=Date.parse(raw);
|
| 48 |
if(Number.isFinite(ts))return ts;
|
|
@@ -55,7 +62,7 @@ function runSortTimestamp(run={}){
|
|
| 55 |
return 0;
|
| 56 |
}
|
| 57 |
function sortRunsNewestFirst(runs=[]){
|
| 58 |
-
return [...(runs
|
| 59 |
}
|
| 60 |
function filteredRuns(){
|
| 61 |
const query=state.runSearch||'';
|
|
@@ -92,6 +99,7 @@ function runCompactStatusLabel(status){
|
|
| 92 |
const s=String(status||'unknown').toLowerCase();
|
| 93 |
if(s==='full_inference_candidate_health_passed'||s==='health_only'||s==='partial_validation'||s==='partial'||s==='completed_with_warnings')return 'Partial';
|
| 94 |
if(s==='validated_after_manual_space_test'||s==='manual_validation_passed'||s==='validated')return 'Validated';
|
|
|
|
| 95 |
if(s.includes('success')||s.includes('succeed')||s.includes('passed')||s==='done'||s==='completed')return 'Success';
|
| 96 |
if(s.includes('running')||s.includes('queued')||s.includes('pending')||s.includes('building')||s.includes('waiting'))return 'Running';
|
| 97 |
if(s.includes('manual')||s.includes('hardware'))return 'Manual';
|
|
@@ -160,12 +168,14 @@ function runSecondaryLine(run={}){
|
|
| 160 |
const manual=buildManualValidationStatus(run);
|
| 161 |
const manualText=manual.passed?'Manual validation passed':manual.failed?'Manual validation attempted':'';
|
| 162 |
const linkedText=manual.linkedCount?`${manual.linkedCount} linked test${manual.linkedCount===1?'':'s'}`:'';
|
| 163 |
-
const
|
|
|
|
|
|
|
| 164 |
return parts.join(' • ');
|
| 165 |
}
|
| 166 |
function runCardMeta(run={}){return runSecondaryLine(run)}
|
| 167 |
function renderRunRows(runs,compact=false){
|
| 168 |
-
return (runs||[]).map(r=>{
|
| 169 |
const validation=isValidationRunSummary(r);
|
| 170 |
const kind=runCardKind(r);
|
| 171 |
// const status=runCanonicalStatus(r)||r.status||'unknown'
|
|
@@ -219,16 +229,34 @@ function renderRunPagination(total){
|
|
| 219 |
root.innerHTML=`<button class="secondary small" data-page-action="prev" ${state.runPage<=0?'disabled':''}>‹</button><span>Page ${state.runPage+1} / ${pages}</span><button class="secondary small" data-page-action="next" ${state.runPage>=pages-1?'disabled':''}>›</button>`;
|
| 220 |
}
|
| 221 |
|
| 222 |
-
function isValidationRunSummary(run={}){return isValidationRunDetail({summary:run,state:run,kind:run.kind,run_type:run.run_type})}
|
|
|
|
|
|
|
| 223 |
|
| 224 |
function linkedValidationParentId(run={}){
|
|
|
|
| 225 |
return run.parent_build_run_id||run.parentBuildRunId||run.summary?.parent_build_run_id||run.state?.parent_build_run_id||'';
|
| 226 |
}
|
| 227 |
function linkedValidationsForBuild(buildRunId){
|
| 228 |
if(!buildRunId)return [];
|
| 229 |
-
return (state.runsCache||[]).filter(r=>
|
| 230 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
function buildManualValidationStatus(run={}){
|
|
|
|
| 232 |
const explicit=run.manual_validation_status||run.summary?.manual_validation_status||{};
|
| 233 |
const linked=linkedValidationsForBuild(run.run_id).filter(v=>!runMatchesStatus(v,'running'));
|
| 234 |
const passed=Boolean(run.manual_validation_passed||run.summary?.manual_validation_passed||explicit.status==='success'||linked.some(v=>runMatchesStatus(v,'full_inference_success')));
|
|
@@ -237,21 +265,25 @@ function buildManualValidationStatus(run={}){
|
|
| 237 |
return {passed,failed,linkedCount:linked.length,latest,explicit};
|
| 238 |
}
|
| 239 |
function buildEffectiveStatus(run={}){
|
|
|
|
| 240 |
const manual=buildManualValidationStatus(run);
|
| 241 |
const eff=String(run.effective_status||run.summary?.effective_status||manual.explicit.effective_status||'').toLowerCase();
|
| 242 |
if(eff)return eff;
|
|
|
|
| 243 |
if(manual.passed)return 'validated_after_manual_space_test';
|
| 244 |
return runCanonicalStatus(run);
|
| 245 |
}
|
| 246 |
function explorerPrimaryRuns(){
|
| 247 |
-
return (state.runsCache||[]).filter(r=>!isValidationRunSummary(r));
|
| 248 |
}
|
| 249 |
-
function runStatTime(run={}){const raw=run.updated_at||run.created_at||run.started_at||run.cancelled_at||'';return raw?formatTime(raw):'—'}
|
| 250 |
function runStatLatency(run={}){
|
|
|
|
| 251 |
const latency=run.latency_seconds||run.observed_latency_seconds||run.generation_smoke?.latency_seconds||run.generation_smoke?.observed_latency_seconds;
|
| 252 |
return latency?fmtSeconds(Number(latency)):'';
|
| 253 |
}
|
| 254 |
function runStatLatencyValue(run={}){
|
|
|
|
| 255 |
const latency=run.latency_seconds||run.observed_latency_seconds||run.generation_smoke?.latency_seconds||run.generation_smoke?.observed_latency_seconds;
|
| 256 |
const n=Number(latency);
|
| 257 |
return Number.isFinite(n)&&n>0?n:null;
|
|
@@ -266,12 +298,13 @@ function percentile(values=[],p=0.5){
|
|
| 266 |
}
|
| 267 |
function pct(part,total){return total?`${Math.round((part/total)*100)}%`:'—'}
|
| 268 |
function runStatsIssueCounts(runs=[]){
|
|
|
|
| 269 |
const counts={failed_builds:0,failed_linked_tests:0,manual_hardware:0,pi_model_mismatch:0,stale:0};
|
| 270 |
for(const r of runs){
|
| 271 |
const validation=isValidationRunSummary(r);
|
| 272 |
const status=String(r.status||'').toLowerCase();
|
| 273 |
const pi=r.pi_model_resolution||{};
|
| 274 |
-
if(runMatchesStatus(r,'failed'))
|
| 275 |
if(!validation&&runMatchesStatus(r,'manual_hardware_required')) counts.manual_hardware++;
|
| 276 |
if(!validation&&(pi.mismatch||status.includes('model_mismatch'))) counts.pi_model_mismatch++;
|
| 277 |
if(status.includes('stale')) counts.stale++;
|
|
@@ -284,15 +317,17 @@ function runStatsCell(label,value,detail='',tone='neutral'){
|
|
| 284 |
function renderRunAggregateStats(){
|
| 285 |
const root=$('runStatsList');
|
| 286 |
if(!root)return;
|
| 287 |
-
const runs=state.runsCache||[];
|
| 288 |
const builds=runs.filter(r=>!isValidationRunSummary(r));
|
| 289 |
-
const validations=runs.filter(r=>
|
| 290 |
const runningBuilds=builds.filter(r=>runMatchesStatus(r,'running')).length;
|
| 291 |
const failedBuilds=builds.filter(r=>runMatchesStatus(r,'failed')).length;
|
| 292 |
const terminalBuilds=builds.filter(r=>!runMatchesStatus(r,'running'));
|
| 293 |
const automaticSuccess=terminalBuilds.filter(r=>runMatchesStatus(r,'full_inference_success')).length;
|
| 294 |
const successBuilds=automaticSuccess;
|
| 295 |
const effectiveSuccess=terminalBuilds.filter(r=>runMatchesStatus({...r,status:buildEffectiveStatus(r)},'full_inference_success')).length;
|
|
|
|
|
|
|
| 296 |
// Legacy aggregate labels kept for regression tests: Total runs / Test response time / Typical / slowest recent / Build success / Validation pass.
|
| 297 |
// Legacy formula kept for audit readability: pct(successBuilds,terminalBuilds.length) and pct(passedValidations,terminalValidations.length).
|
| 298 |
const passedLinked=validations.filter(r=>!runMatchesStatus(r,'running')&&runMatchesStatus(r,'full_inference_success')).length;
|
|
@@ -325,6 +360,7 @@ function renderRunAggregateStats(){
|
|
| 325 |
${runStatsCell('Build status',`${runningBuilds} running`,`${failedBuilds} failed` ,failedBuilds?'warn':runningBuilds?'running':'success')}
|
| 326 |
${runStatsCell('Automatic success',pct(automaticSuccess,terminalBuilds.length),`${automaticSuccess}/${terminalBuilds.length} finished builds`,'success')}
|
| 327 |
${runStatsCell('Effective success',pct(effectiveSuccess,terminalBuilds.length),`${effectiveSuccess}/${terminalBuilds.length} after linked tests`,'success')}
|
|
|
|
| 328 |
</div>
|
| 329 |
<div class="run-stats-band linked-tests-band">
|
| 330 |
<span>Linked Space Tests</span>
|
|
@@ -358,11 +394,12 @@ function forgetRunEverywhere(runId){
|
|
| 358 |
}
|
| 359 |
if(state.documentCache)delete state.documentCache[runId];
|
| 360 |
if(state.latencyCache)delete state.latencyCache[runId];
|
| 361 |
-
state.runsCache=(state.runsCache||[]).filter(r=>r.run_id!==runId)
|
|
|
|
| 362 |
document.querySelectorAll(`.run-row[data-run="${CSS.escape(runId)}"]`).forEach(row=>row.remove());
|
| 363 |
}
|
| 364 |
function activeRunIds(){
|
| 365 |
-
return new Set((state.runsCache||[]).map(r=>r.run_id).filter(Boolean));
|
| 366 |
}
|
| 367 |
|
| 368 |
function associatedSpaceFromDetail(detail={},summaryOverride={}){
|
|
@@ -409,7 +446,7 @@ function bindRunRowEvents(root){
|
|
| 409 |
});
|
| 410 |
}
|
| 411 |
function renderRunsFromCache(){
|
| 412 |
-
state.runsCache=sortRunsNewestFirst(state.runsCache||[]);
|
| 413 |
const runs=filteredRuns();
|
| 414 |
const perPage=state.runsPerPage||5;
|
| 415 |
state.runPage=Math.min(Math.max(0,state.runPage||0),Math.max(0,Math.ceil(runs.length/perPage)-1));
|
|
@@ -431,7 +468,7 @@ function renderRunsFromCache(){
|
|
| 431 |
|
| 432 |
function mergeRunExplorerTerminalLocks(runs=[]){
|
| 433 |
if(typeof state==='undefined'||!state.validationTerminalByRun)return runs||[];
|
| 434 |
-
return (runs
|
| 435 |
const validation=isValidationRunSummary(run);
|
| 436 |
const runId=run.run_id||run.summary?.run_id||'';
|
| 437 |
const terminal=validation&&runId?state.validationTerminalByRun[runId]:null;
|
|
@@ -439,10 +476,11 @@ function mergeRunExplorerTerminalLocks(runs=[]){
|
|
| 439 |
return {...run,status:terminal.canonical,validation_status:terminal.canonical,result_status:terminal.canonical,summary:{...(run.summary||{}),status:terminal.canonical,validation_status:terminal.canonical,result_status:terminal.canonical}};
|
| 440 |
});
|
| 441 |
}
|
| 442 |
-
async function loadRuns(forceNetwork=false){const stale=Date.now()-(state.runsLastLoaded||0)>45000;const shouldFetch=forceNetwork||!state.runsCache.length||stale;if(!shouldFetch){renderRunsFromCache();return state.runsCache||[]}if(state.runsLoadPromise&&!forceNetwork)return state.runsLoadPromise;document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.add('is-refreshing'));state.runsLoadPromise=apiGet(`/api/runs?limit=100&bucket_name=${encodeURIComponent(state.bucketName)}`).then(data=>{
|
|
|
|
| 443 |
function cachedRunById(runId){
|
| 444 |
if(isRunLocallyDeleted(runId))return null;
|
| 445 |
-
return (state.runsCache||[]).find(r=>r.run_id===runId)||null;
|
| 446 |
}
|
| 447 |
function optimisticDetailFromSummary(summary={}){
|
| 448 |
if(!summary)return null;
|
|
@@ -506,13 +544,13 @@ function upsertRunExplorerFromProgress(p={}){
|
|
| 506 |
effective_status:p.summary?.effective_status||p.manual_validation_status?.effective_status,
|
| 507 |
bucket_source:p.bucket_source||p.summary?.bucket_source||state.bucketSource,
|
| 508 |
};
|
| 509 |
-
state.runsCache=state.runsCache||[];
|
| 510 |
-
const idx=state.runsCache.findIndex(r=>r.run_id===runId);
|
| 511 |
if(idx>=0)state.runsCache[idx]={...state.runsCache[idx],...summary};
|
| 512 |
else state.runsCache.unshift(summary);
|
| 513 |
const parentId=summary.parent_build_run_id||linkedValidationParentId(summary);
|
| 514 |
if(parentId&&isValidationRunSummary(summary)){
|
| 515 |
-
const parentIdx=state.runsCache.findIndex(r=>r.run_id===parentId);
|
| 516 |
if(parentIdx>=0){
|
| 517 |
const passed=runMatchesStatus(summary,'full_inference_success');
|
| 518 |
state.runsCache[parentIdx]={...state.runsCache[parentIdx],manual_validation_passed:state.runsCache[parentIdx].manual_validation_passed||passed,effective_status:passed?'validated_after_manual_space_test':state.runsCache[parentIdx].effective_status,updated_at:summary.updated_at||state.runsCache[parentIdx].updated_at};
|
|
@@ -733,12 +771,13 @@ function confirmRunDeletion({runId,kind='active',status='',detail={}}={}){
|
|
| 733 |
const label=kind==='validation'?'validation run':'build run';
|
| 734 |
const associatedSpace=associatedSpaceFromDetail(detail,detail.summary||{});
|
| 735 |
const canDeleteSpace=runCanOfferSpaceDeletion(kind,detail,detail.summary||{});
|
|
|
|
| 736 |
kicker.textContent=kind==='validation'?'Space Test deletion':'Build run deletion';
|
| 737 |
title.textContent=`Delete this ${label}?`;
|
| 738 |
body.textContent=kind==='validation'
|
| 739 |
-
? 'This removes only this validation run’s
|
| 740 |
-
: 'This removes
|
| 741 |
-
meta.innerHTML=`<span><strong>Run</strong><code>${escapeHtml(runId||'—')}</code></span><span><strong>Type</strong>${escapeHtml(kind==='validation'?'Space Test':'Build')}</span><span><strong>Status</strong>${escapeHtml(badgeLabel(normalized))}</span>${associatedSpace?`<span><strong>Space</strong><code>${escapeHtml(associatedSpace)}</code></span>`:''}`;
|
| 742 |
if(spaceOption&&spaceCheckbox&&spaceLabel){
|
| 743 |
spaceCheckbox.checked=false;
|
| 744 |
spaceOption.hidden=!canDeleteSpace;
|
|
@@ -768,7 +807,7 @@ function confirmRunDeletion({runId,kind='active',status='',detail={}}={}){
|
|
| 768 |
const setDeleting=()=>{
|
| 769 |
modal.classList.add('is-deleting');
|
| 770 |
title.textContent=kind==='validation'?'Deleting this validation run…':'Deleting this run…';
|
| 771 |
-
body.textContent='Removing bucket artifacts. The panel will reset as soon as deletion is confirmed.';
|
| 772 |
confirmBtn.textContent='Deleting…';
|
| 773 |
confirmBtn.disabled=true;
|
| 774 |
cancelEls.forEach(el=>{el.disabled=true;el.setAttribute('aria-disabled','true')});
|
|
@@ -779,8 +818,8 @@ function confirmRunDeletion({runId,kind='active',status='',detail={}}={}){
|
|
| 779 |
submitted=false;
|
| 780 |
title.textContent=`Delete this ${label}?`;
|
| 781 |
body.textContent=kind==='validation'
|
| 782 |
-
? 'This removes only this validation run’s
|
| 783 |
-
: 'This removes
|
| 784 |
confirmBtn.textContent=active?'Delete stored files':'Delete run';
|
| 785 |
confirmBtn.disabled=false;
|
| 786 |
cancelEls.forEach(el=>{el.disabled=false;el.removeAttribute('aria-disabled')});
|
|
@@ -815,12 +854,16 @@ async function deleteRunById(runId,options={}){
|
|
| 815 |
let success=false;
|
| 816 |
try{
|
| 817 |
const result=await apiDelete(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`,deleteSpace?{delete_space:true}:{});
|
|
|
|
|
|
|
| 818 |
const suffix=deleteSpace?(result.space_deleted?' and associated Space':(result.space_delete_error?' but Space deletion failed':' and Space was already missing')):'';
|
| 819 |
-
showMessage(`Deleted run ${runId}${suffix}`,'success');
|
| 820 |
success=true;
|
| 821 |
rememberDeletedRun(runId);
|
|
|
|
| 822 |
clearDeletedRunState(runId,context.kind);
|
| 823 |
forgetRunEverywhere(runId);
|
|
|
|
| 824 |
renderRunsFromCache();
|
| 825 |
if(confirmed&&typeof confirmed.close==='function')confirmed.close(true);
|
| 826 |
loadRuns(true).catch(e=>console.warn('post-delete runs refresh failed',e));
|
|
@@ -1192,12 +1235,16 @@ function renderEvalArchiveStatus(detail={}){
|
|
| 1192 |
const path=status.archive_relative_path||status.path||'';
|
| 1193 |
const localLabel=(status.local_record_found||Object.keys(local||{}).length)?'written':'pending';
|
| 1194 |
const backendLabel=published?'published':attempted&&reason==='record_not_ready'?'pending':attempted?'not published':'pending';
|
|
|
|
|
|
|
| 1195 |
const intro=published?'Eval record copied to the operator archive bucket.':attempted&&reason==='record_not_ready'?'Eval record will be archived when the run completes.':'Archive publication is not confirmed yet.';
|
| 1196 |
-
|
|
|
|
| 1197 |
}
|
| 1198 |
function renderRunDetail(detail){
|
| 1199 |
state.selectedRunDetail=detail;
|
| 1200 |
const summary=detail.summary||{};
|
|
|
|
| 1201 |
const smoke=detail.generation_smoke||{};
|
| 1202 |
const files=detail.files||[];
|
| 1203 |
const view=detail.view||{};
|
|
@@ -1248,3 +1295,5 @@ function renderRunDetail(detail){
|
|
| 1248 |
// Legacy clean-delete invariant: await loadRuns(true) was intentionally replaced by post-delete background refresh in v190.9.
|
| 1249 |
|
| 1250 |
// Legacy validation preview invariant: if(isValidationRunDetail(detail)){showValidationRunDetail(detail)
|
|
|
|
|
|
|
|
|
| 1 |
+
function safeRunObject(run){return run&&typeof run==='object'?run:{}}
|
| 2 |
+
function validRunObject(run){return Boolean(run&&typeof run==='object'&&(run.run_id||run.summary?.run_id||run.state?.run_id));}
|
| 3 |
+
function validRunsList(runs=[]){return (Array.isArray(runs)?runs:[]).filter(validRunObject);}
|
| 4 |
function inferredOwnerFromBucket(bucket){return bucket?String(bucket).split('/')[0]:''}
|
| 5 |
function inferredJobUrl(summary={}, detail={}){if(summary.job_url)return summary.job_url;const launch=detail.launch||{};const stateObj=detail.state||{};if(launch.job_url)return launch.job_url;if(stateObj.job_url)return stateObj.job_url;const jobId=summary.job_id||launch.job_id||stateObj.job_id;const owner=summary.created_by||summary.username||summary.owner||launch.created_by||launch.username||launch.owner||stateObj.created_by||stateObj.username||stateObj.owner||state.user?.username||inferredOwnerFromBucket(summary.bucket_source||detail.bucket_source||state.bucketSource);return jobId&&owner?`https://huggingface.co/jobs/${owner}/${jobId}`:''}
|
| 6 |
function bucketRunFileUrl(bucket,runId,relPath=''){const rel=String(relPath||'').replace(/^\/+|\/+$/g,'');return bucket&&runId?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}${rel?`/${rel}`:''}`:''}
|
| 7 |
function runValidationTerminalText(run={}){
|
| 8 |
+
run=safeRunObject(run);
|
| 9 |
return [
|
| 10 |
run.verdict,run.validation_status,run.result_status,run.final_status,run.status,run.summary?.verdict,run.summary?.validation_status,run.summary?.result_status,run.summary?.status,
|
| 11 |
run.state?.verdict,run.state?.validation_status,run.state?.result_status,run.state?.status,run.gate_status,run.summary?.gate_status,run.inference_gate?.status,
|
|
|
|
| 13 |
].map(x=>String(x||'').toLowerCase()).filter(Boolean).join(' ');
|
| 14 |
}
|
| 15 |
function validationLockedStatus(run={}){
|
| 16 |
+
run=safeRunObject(run);
|
| 17 |
const runId=run.run_id||run.summary?.run_id||run.state?.run_id||'';
|
| 18 |
const terminal=runId&&typeof state!=='undefined'&&state.validationTerminalByRun?state.validationTerminalByRun[runId]:null;
|
| 19 |
return terminal?.canonical||'';
|
| 20 |
}
|
| 21 |
function runCanonicalStatus(run={}){
|
| 22 |
+
run=safeRunObject(run);
|
| 23 |
const validation=isValidationRunSummary(run);
|
| 24 |
const locked=validation?validationLockedStatus(run):'';
|
| 25 |
if(locked)return locked;
|
|
|
|
| 38 |
if(text.includes('running')||text.includes('queued')||text.includes('pending')||text.includes('started')||text.includes('waiting')||text.includes('building'))return'running';
|
| 39 |
return s||'unknown';
|
| 40 |
}
|
| 41 |
+
function runMatchesStatus(run,status){const canon=runCanonicalStatus(run);const effective=String(run?.effective_status||run?.summary?.effective_status||run?.manual_validation_status?.effective_status||'').toLowerCase();if(!status||status==='all')return true;if(status==='failed')return canon==='failed';if(status==='running')return canon==='running';if(status==='full_inference_success')return canon==='full_inference_success'||effective==='validated_after_manual_space_test'||effective==='manual_validation_passed'||effective==='validated'||effective==='recovered_by_manual_validation';if(status==='partial_validation')return canon==='partial_validation';if(status==='manual_hardware_required')return canon==='manual_hardware_required';if(status==='technical_blocker')return canon==='technical_blocker';if(status==='stale')return canon==='stale';return canon===String(status).toLowerCase()||effective===String(status).toLowerCase()}
|
| 42 |
function canonicalStatusForDeletion(context={}){
|
| 43 |
const kind=context.kind==='validation'?'validation':'active';
|
| 44 |
const detail=context.detail||{};
|
|
|
|
| 47 |
if(kind==='validation'&&typeof runCanonicalStatus==='function')return runCanonicalStatus(merged);
|
| 48 |
return String(merged.status||merged.summary?.status||merged.state?.status||'unknown').toLowerCase();
|
| 49 |
}
|
| 50 |
+
function runMatchesQuery(run,query){run=safeRunObject(run);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())}
|
| 51 |
function runSortTimestamp(run={}){
|
| 52 |
+
run=safeRunObject(run);
|
| 53 |
const raw=run.updated_at||run.created_at||run.started_at||run.cancelled_at||'';
|
| 54 |
const ts=Date.parse(raw);
|
| 55 |
if(Number.isFinite(ts))return ts;
|
|
|
|
| 62 |
return 0;
|
| 63 |
}
|
| 64 |
function sortRunsNewestFirst(runs=[]){
|
| 65 |
+
return [...validRunsList(runs)].sort((a,b)=>runSortTimestamp(b)-runSortTimestamp(a)||String(b.run_id||'').localeCompare(String(a.run_id||'')));
|
| 66 |
}
|
| 67 |
function filteredRuns(){
|
| 68 |
const query=state.runSearch||'';
|
|
|
|
| 99 |
const s=String(status||'unknown').toLowerCase();
|
| 100 |
if(s==='full_inference_candidate_health_passed'||s==='health_only'||s==='partial_validation'||s==='partial'||s==='completed_with_warnings')return 'Partial';
|
| 101 |
if(s==='validated_after_manual_space_test'||s==='manual_validation_passed'||s==='validated')return 'Validated';
|
| 102 |
+
if(s==='recovered_by_manual_validation')return 'Recovered';
|
| 103 |
if(s.includes('success')||s.includes('succeed')||s.includes('passed')||s==='done'||s==='completed')return 'Success';
|
| 104 |
if(s.includes('running')||s.includes('queued')||s.includes('pending')||s.includes('building')||s.includes('waiting'))return 'Running';
|
| 105 |
if(s.includes('manual')||s.includes('hardware'))return 'Manual';
|
|
|
|
| 168 |
const manual=buildManualValidationStatus(run);
|
| 169 |
const manualText=manual.passed?'Manual validation passed':manual.failed?'Manual validation attempted':'';
|
| 170 |
const linkedText=manual.linkedCount?`${manual.linkedCount} linked test${manual.linkedCount===1?'':'s'}`:'';
|
| 171 |
+
const policy=runSpaceTestPolicy(run);
|
| 172 |
+
const policyText=policy.mode==='complete'?'Can complete with Space Test':policy.mode==='recover'?'Can recover with Space Test':policy.mode==='blocked'?policy.label:'';
|
| 173 |
+
const parts=[manualText,linkedText,policyText,run.target_space,mode?'Goal: '+implementationModeLabel(mode,{short:true}):'',run.selected_hardware].filter(Boolean);
|
| 174 |
return parts.join(' • ');
|
| 175 |
}
|
| 176 |
function runCardMeta(run={}){return runSecondaryLine(run)}
|
| 177 |
function renderRunRows(runs,compact=false){
|
| 178 |
+
return validRunsList(runs||[]).map(r=>{
|
| 179 |
const validation=isValidationRunSummary(r);
|
| 180 |
const kind=runCardKind(r);
|
| 181 |
// const status=runCanonicalStatus(r)||r.status||'unknown'
|
|
|
|
| 229 |
root.innerHTML=`<button class="secondary small" data-page-action="prev" ${state.runPage<=0?'disabled':''}>‹</button><span>Page ${state.runPage+1} / ${pages}</span><button class="secondary small" data-page-action="next" ${state.runPage>=pages-1?'disabled':''}>›</button>`;
|
| 230 |
}
|
| 231 |
|
| 232 |
+
function isValidationRunSummary(run={}){run=safeRunObject(run);return isValidationRunDetail({summary:run,state:run,kind:run.kind,run_type:run.run_type})}
|
| 233 |
+
function isLinkedValidationRun(run={}){return isValidationRunSummary(run)&&Boolean(linkedValidationParentId(run));}
|
| 234 |
+
function isLegacyValidationRun(run={}){return isValidationRunSummary(run)&&!linkedValidationParentId(run);}
|
| 235 |
|
| 236 |
function linkedValidationParentId(run={}){
|
| 237 |
+
run=safeRunObject(run);
|
| 238 |
return run.parent_build_run_id||run.parentBuildRunId||run.summary?.parent_build_run_id||run.state?.parent_build_run_id||'';
|
| 239 |
}
|
| 240 |
function linkedValidationsForBuild(buildRunId){
|
| 241 |
if(!buildRunId)return [];
|
| 242 |
+
return validRunsList(state.runsCache||[]).filter(r=>isLinkedValidationRun(r)&&linkedValidationParentId(r)===buildRunId);
|
| 243 |
}
|
| 244 |
+
function linkedValidationDeleteSummary(runId,detail={}){
|
| 245 |
+
const id=runId||detail.run_id||detail.summary?.run_id||'';
|
| 246 |
+
const rows=linkedValidationsForBuild(id);
|
| 247 |
+
return {count:rows.length,ids:rows.map(r=>r.run_id||r.summary?.run_id).filter(Boolean)};
|
| 248 |
+
}
|
| 249 |
+
function runSpaceTestPolicy(run={}){
|
| 250 |
+
run=safeRunObject(run);
|
| 251 |
+
return run.space_test_policy||run.summary?.space_test_policy||run.view?.space_test_policy||run.space_test?.policy||{};
|
| 252 |
+
}
|
| 253 |
+
function runNeedsLinkedValidation(run={}){
|
| 254 |
+
const policy=runSpaceTestPolicy(run);
|
| 255 |
+
return ['complete','recover'].includes(String(policy.mode||'').toLowerCase())&&policy.enabled!==false&&!buildManualValidationStatus(run).passed;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
function buildManualValidationStatus(run={}){
|
| 259 |
+
run=safeRunObject(run);
|
| 260 |
const explicit=run.manual_validation_status||run.summary?.manual_validation_status||{};
|
| 261 |
const linked=linkedValidationsForBuild(run.run_id).filter(v=>!runMatchesStatus(v,'running'));
|
| 262 |
const passed=Boolean(run.manual_validation_passed||run.summary?.manual_validation_passed||explicit.status==='success'||linked.some(v=>runMatchesStatus(v,'full_inference_success')));
|
|
|
|
| 265 |
return {passed,failed,linkedCount:linked.length,latest,explicit};
|
| 266 |
}
|
| 267 |
function buildEffectiveStatus(run={}){
|
| 268 |
+
run=safeRunObject(run);
|
| 269 |
const manual=buildManualValidationStatus(run);
|
| 270 |
const eff=String(run.effective_status||run.summary?.effective_status||manual.explicit.effective_status||'').toLowerCase();
|
| 271 |
if(eff)return eff;
|
| 272 |
+
// v190.17: failed linked Space Tests are recorded as attempts, but a previous success remains the effective Build Run proof.
|
| 273 |
if(manual.passed)return 'validated_after_manual_space_test';
|
| 274 |
return runCanonicalStatus(run);
|
| 275 |
}
|
| 276 |
function explorerPrimaryRuns(){
|
| 277 |
+
return validRunsList(state.runsCache||[]).filter(r=>!isValidationRunSummary(r));
|
| 278 |
}
|
| 279 |
+
function runStatTime(run={}){run=safeRunObject(run);const raw=run.updated_at||run.created_at||run.started_at||run.cancelled_at||'';return raw?formatTime(raw):'—'}
|
| 280 |
function runStatLatency(run={}){
|
| 281 |
+
run=safeRunObject(run);
|
| 282 |
const latency=run.latency_seconds||run.observed_latency_seconds||run.generation_smoke?.latency_seconds||run.generation_smoke?.observed_latency_seconds;
|
| 283 |
return latency?fmtSeconds(Number(latency)):'';
|
| 284 |
}
|
| 285 |
function runStatLatencyValue(run={}){
|
| 286 |
+
run=safeRunObject(run);
|
| 287 |
const latency=run.latency_seconds||run.observed_latency_seconds||run.generation_smoke?.latency_seconds||run.generation_smoke?.observed_latency_seconds;
|
| 288 |
const n=Number(latency);
|
| 289 |
return Number.isFinite(n)&&n>0?n:null;
|
|
|
|
| 298 |
}
|
| 299 |
function pct(part,total){return total?`${Math.round((part/total)*100)}%`:'—'}
|
| 300 |
function runStatsIssueCounts(runs=[]){
|
| 301 |
+
runs=validRunsList(runs);
|
| 302 |
const counts={failed_builds:0,failed_linked_tests:0,manual_hardware:0,pi_model_mismatch:0,stale:0};
|
| 303 |
for(const r of runs){
|
| 304 |
const validation=isValidationRunSummary(r);
|
| 305 |
const status=String(r.status||'').toLowerCase();
|
| 306 |
const pi=r.pi_model_resolution||{};
|
| 307 |
+
if(runMatchesStatus(r,'failed')){if(validation&&isLinkedValidationRun(r))counts.failed_linked_tests++;else if(!validation)counts.failed_builds++;}
|
| 308 |
if(!validation&&runMatchesStatus(r,'manual_hardware_required')) counts.manual_hardware++;
|
| 309 |
if(!validation&&(pi.mismatch||status.includes('model_mismatch'))) counts.pi_model_mismatch++;
|
| 310 |
if(status.includes('stale')) counts.stale++;
|
|
|
|
| 317 |
function renderRunAggregateStats(){
|
| 318 |
const root=$('runStatsList');
|
| 319 |
if(!root)return;
|
| 320 |
+
const runs=validRunsList(state.runsCache||[]);
|
| 321 |
const builds=runs.filter(r=>!isValidationRunSummary(r));
|
| 322 |
+
const validations=runs.filter(r=>isLinkedValidationRun(r));
|
| 323 |
const runningBuilds=builds.filter(r=>runMatchesStatus(r,'running')).length;
|
| 324 |
const failedBuilds=builds.filter(r=>runMatchesStatus(r,'failed')).length;
|
| 325 |
const terminalBuilds=builds.filter(r=>!runMatchesStatus(r,'running'));
|
| 326 |
const automaticSuccess=terminalBuilds.filter(r=>runMatchesStatus(r,'full_inference_success')).length;
|
| 327 |
const successBuilds=automaticSuccess;
|
| 328 |
const effectiveSuccess=terminalBuilds.filter(r=>runMatchesStatus({...r,status:buildEffectiveStatus(r)},'full_inference_success')).length;
|
| 329 |
+
const recoveredBuilds=terminalBuilds.filter(r=>String(buildEffectiveStatus(r)).toLowerCase()==='recovered_by_manual_validation').length;
|
| 330 |
+
const needsValidation=terminalBuilds.filter(r=>runNeedsLinkedValidation(r)).length;
|
| 331 |
// Legacy aggregate labels kept for regression tests: Total runs / Test response time / Typical / slowest recent / Build success / Validation pass.
|
| 332 |
// Legacy formula kept for audit readability: pct(successBuilds,terminalBuilds.length) and pct(passedValidations,terminalValidations.length).
|
| 333 |
const passedLinked=validations.filter(r=>!runMatchesStatus(r,'running')&&runMatchesStatus(r,'full_inference_success')).length;
|
|
|
|
| 360 |
${runStatsCell('Build status',`${runningBuilds} running`,`${failedBuilds} failed` ,failedBuilds?'warn':runningBuilds?'running':'success')}
|
| 361 |
${runStatsCell('Automatic success',pct(automaticSuccess,terminalBuilds.length),`${automaticSuccess}/${terminalBuilds.length} finished builds`,'success')}
|
| 362 |
${runStatsCell('Effective success',pct(effectiveSuccess,terminalBuilds.length),`${effectiveSuccess}/${terminalBuilds.length} after linked tests`,'success')}
|
| 363 |
+
${runStatsCell('Recovered',String(recoveredBuilds),`${needsValidation} need validation/recovery`,recoveredBuilds?'success':needsValidation?'warn':'neutral')}
|
| 364 |
</div>
|
| 365 |
<div class="run-stats-band linked-tests-band">
|
| 366 |
<span>Linked Space Tests</span>
|
|
|
|
| 394 |
}
|
| 395 |
if(state.documentCache)delete state.documentCache[runId];
|
| 396 |
if(state.latencyCache)delete state.latencyCache[runId];
|
| 397 |
+
// Legacy invariant: state.runsCache=(state.runsCache||[]).filter(r=>r.run_id!==runId)
|
| 398 |
+
state.runsCache=validRunsList(state.runsCache||[]).filter(r=>(r.run_id||r.summary?.run_id)!==runId);
|
| 399 |
document.querySelectorAll(`.run-row[data-run="${CSS.escape(runId)}"]`).forEach(row=>row.remove());
|
| 400 |
}
|
| 401 |
function activeRunIds(){
|
| 402 |
+
return new Set(validRunsList(state.runsCache||[]).map(r=>r.run_id||r.summary?.run_id).filter(Boolean));
|
| 403 |
}
|
| 404 |
|
| 405 |
function associatedSpaceFromDetail(detail={},summaryOverride={}){
|
|
|
|
| 446 |
});
|
| 447 |
}
|
| 448 |
function renderRunsFromCache(){
|
| 449 |
+
state.runsCache=sortRunsNewestFirst(validRunsList(state.runsCache||[]));
|
| 450 |
const runs=filteredRuns();
|
| 451 |
const perPage=state.runsPerPage||5;
|
| 452 |
state.runPage=Math.min(Math.max(0,state.runPage||0),Math.max(0,Math.ceil(runs.length/perPage)-1));
|
|
|
|
| 468 |
|
| 469 |
function mergeRunExplorerTerminalLocks(runs=[]){
|
| 470 |
if(typeof state==='undefined'||!state.validationTerminalByRun)return runs||[];
|
| 471 |
+
return validRunsList(runs).map(run=>{
|
| 472 |
const validation=isValidationRunSummary(run);
|
| 473 |
const runId=run.run_id||run.summary?.run_id||'';
|
| 474 |
const terminal=validation&&runId?state.validationTerminalByRun[runId]:null;
|
|
|
|
| 476 |
return {...run,status:terminal.canonical,validation_status:terminal.canonical,result_status:terminal.canonical,summary:{...(run.summary||{}),status:terminal.canonical,validation_status:terminal.canonical,result_status:terminal.canonical}};
|
| 477 |
});
|
| 478 |
}
|
| 479 |
+
async function loadRuns(forceNetwork=false){const stale=Date.now()-(state.runsLastLoaded||0)>45000;const shouldFetch=forceNetwork||!state.runsCache.length||stale;if(!shouldFetch){renderRunsFromCache();return state.runsCache||[]}if(state.runsLoadPromise&&!forceNetwork)return state.runsLoadPromise;document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.add('is-refreshing'));state.runsLoadPromise=apiGet(`/api/runs?limit=100&bucket_name=${encodeURIComponent(state.bucketName)}`).then(data=>{// Legacy invariant: .filter(r=>!isRunLocallyDeleted(r.run_id))
|
| 480 |
+
state.runsCache=sortRunsNewestFirst(mergeRunExplorerTerminalLocks(validRunsList(data.runs||[]).filter(r=>!isRunLocallyDeleted(r.run_id||r.summary?.run_id))));state.runsLastLoaded=Date.now();if(data.bucket_source)state.bucketSource=data.bucket_source;renderRunsFromCache();return state.runsCache}).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);return state.runsCache||[]}).finally(()=>{state.runsLoadPromise=null;document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.remove('is-refreshing'))});return state.runsLoadPromise}
|
| 481 |
function cachedRunById(runId){
|
| 482 |
if(isRunLocallyDeleted(runId))return null;
|
| 483 |
+
return validRunsList(state.runsCache||[]).find(r=>(r.run_id||r.summary?.run_id)===runId)||null;
|
| 484 |
}
|
| 485 |
function optimisticDetailFromSummary(summary={}){
|
| 486 |
if(!summary)return null;
|
|
|
|
| 544 |
effective_status:p.summary?.effective_status||p.manual_validation_status?.effective_status,
|
| 545 |
bucket_source:p.bucket_source||p.summary?.bucket_source||state.bucketSource,
|
| 546 |
};
|
| 547 |
+
state.runsCache=validRunsList(state.runsCache||[]);
|
| 548 |
+
const idx=state.runsCache.findIndex(r=>(r.run_id||r.summary?.run_id)===runId);
|
| 549 |
if(idx>=0)state.runsCache[idx]={...state.runsCache[idx],...summary};
|
| 550 |
else state.runsCache.unshift(summary);
|
| 551 |
const parentId=summary.parent_build_run_id||linkedValidationParentId(summary);
|
| 552 |
if(parentId&&isValidationRunSummary(summary)){
|
| 553 |
+
const parentIdx=state.runsCache.findIndex(r=>(r.run_id||r.summary?.run_id)===parentId);
|
| 554 |
if(parentIdx>=0){
|
| 555 |
const passed=runMatchesStatus(summary,'full_inference_success');
|
| 556 |
state.runsCache[parentIdx]={...state.runsCache[parentIdx],manual_validation_passed:state.runsCache[parentIdx].manual_validation_passed||passed,effective_status:passed?'validated_after_manual_space_test':state.runsCache[parentIdx].effective_status,updated_at:summary.updated_at||state.runsCache[parentIdx].updated_at};
|
|
|
|
| 771 |
const label=kind==='validation'?'validation run':'build run';
|
| 772 |
const associatedSpace=associatedSpaceFromDetail(detail,detail.summary||{});
|
| 773 |
const canDeleteSpace=runCanOfferSpaceDeletion(kind,detail,detail.summary||{});
|
| 774 |
+
const linkedDelete=kind==='validation'?{count:0,ids:[]}:linkedValidationDeleteSummary(runId,detail);
|
| 775 |
kicker.textContent=kind==='validation'?'Space Test deletion':'Build run deletion';
|
| 776 |
title.textContent=`Delete this ${label}?`;
|
| 777 |
body.textContent=kind==='validation'
|
| 778 |
+
? 'This removes only this validation run’s linked Space Test artifacts. The parent Build Run and generated Space will not be deleted. The tested Space will not be deleted.'
|
| 779 |
+
: (linkedDelete.count?`This removes this Build Run’s bucket artifacts and ${linkedDelete.count} linked Space Test validation run${linkedDelete.count===1?'':'s'}. The generated Space is kept unless you explicitly choose to delete it below.`:'This removes this Build Run’s bucket artifacts, logs, state, traces and report. The generated Space is kept unless you explicitly choose to delete it below.');
|
| 780 |
+
meta.innerHTML=`<span><strong>Run</strong><code>${escapeHtml(runId||'—')}</code></span><span><strong>Type</strong>${escapeHtml(kind==='validation'?'Linked Space Test':'Build')}</span><span><strong>Status</strong>${escapeHtml(badgeLabel(normalized))}</span>${associatedSpace?`<span><strong>Space</strong><code>${escapeHtml(associatedSpace)}</code></span>`:''}${linkedDelete.count?`<span><strong>Linked tests</strong>${linkedDelete.count}</span>`:''}`;
|
| 781 |
if(spaceOption&&spaceCheckbox&&spaceLabel){
|
| 782 |
spaceCheckbox.checked=false;
|
| 783 |
spaceOption.hidden=!canDeleteSpace;
|
|
|
|
| 807 |
const setDeleting=()=>{
|
| 808 |
modal.classList.add('is-deleting');
|
| 809 |
title.textContent=kind==='validation'?'Deleting this validation run…':'Deleting this run…';
|
| 810 |
+
body.textContent=kind==='validation'?'Removing linked validation artifacts and refreshing the parent Build Run.':'Removing bucket artifacts and linked validation runs. The panel will reset as soon as deletion is confirmed.';
|
| 811 |
confirmBtn.textContent='Deleting…';
|
| 812 |
confirmBtn.disabled=true;
|
| 813 |
cancelEls.forEach(el=>{el.disabled=true;el.setAttribute('aria-disabled','true')});
|
|
|
|
| 818 |
submitted=false;
|
| 819 |
title.textContent=`Delete this ${label}?`;
|
| 820 |
body.textContent=kind==='validation'
|
| 821 |
+
? 'This removes only this validation run’s linked Space Test artifacts. The parent Build Run and generated Space will not be deleted. The tested Space will not be deleted.'
|
| 822 |
+
: (linkedDelete.count?`This removes this Build Run’s bucket artifacts and ${linkedDelete.count} linked Space Test validation run${linkedDelete.count===1?'':'s'}. The generated Space is kept unless you explicitly choose to delete it below.`:'This removes this Build Run’s bucket artifacts, logs, state, traces and report. The generated Space is kept unless you explicitly choose to delete it below.');
|
| 823 |
confirmBtn.textContent=active?'Delete stored files':'Delete run';
|
| 824 |
confirmBtn.disabled=false;
|
| 825 |
cancelEls.forEach(el=>{el.disabled=false;el.removeAttribute('aria-disabled')});
|
|
|
|
| 854 |
let success=false;
|
| 855 |
try{
|
| 856 |
const result=await apiDelete(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`,deleteSpace?{delete_space:true}:{});
|
| 857 |
+
const deletedChildren=Array.isArray(result.deleted_linked_validations)?result.deleted_linked_validations:[];
|
| 858 |
+
const childSuffix=deletedChildren.length?` and ${deletedChildren.length} linked validation${deletedChildren.length===1?'':'s'}`:'';
|
| 859 |
const suffix=deleteSpace?(result.space_deleted?' and associated Space':(result.space_delete_error?' but Space deletion failed':' and Space was already missing')):'';
|
| 860 |
+
showMessage(`Deleted run ${runId}${childSuffix}${suffix}`,'success');
|
| 861 |
success=true;
|
| 862 |
rememberDeletedRun(runId);
|
| 863 |
+
deletedChildren.forEach(id=>rememberDeletedRun(id));
|
| 864 |
clearDeletedRunState(runId,context.kind);
|
| 865 |
forgetRunEverywhere(runId);
|
| 866 |
+
deletedChildren.forEach(id=>{clearDeletedRunState(id,'validation');forgetRunEverywhere(id);});
|
| 867 |
renderRunsFromCache();
|
| 868 |
if(confirmed&&typeof confirmed.close==='function')confirmed.close(true);
|
| 869 |
loadRuns(true).catch(e=>console.warn('post-delete runs refresh failed',e));
|
|
|
|
| 1235 |
const path=status.archive_relative_path||status.path||'';
|
| 1236 |
const localLabel=(status.local_record_found||Object.keys(local||{}).length)?'written':'pending';
|
| 1237 |
const backendLabel=published?'published':attempted&&reason==='record_not_ready'?'pending':attempted?'not published':'pending';
|
| 1238 |
+
const manual=detail.manual_validation_status||detail.summary?.manual_validation_status||detail.view?.manual_validation||{};
|
| 1239 |
+
const effective=manual.status==='success'?'validated_after_manual_space_test':(detail.summary?.effective_status||detail.effective_status||'');
|
| 1240 |
const intro=published?'Eval record copied to the operator archive bucket.':attempted&&reason==='record_not_ready'?'Eval record will be archived when the run completes.':'Archive publication is not confirmed yet.';
|
| 1241 |
+
const effectiveNote=effective?`<p class="eval-effective-note">Effective verdict: <strong>${escapeHtml(runCompactStatusLabel(effective))}</strong>${manual.validation_run_id?` from linked Space Test <code>${escapeHtml(manual.validation_run_id)}</code>`:''}.</p>`:'';
|
| 1242 |
+
body.innerHTML=`<p>${escapeHtml(intro)}</p>${effectiveNote}<div class="eval-archive-grid"><div><span>Local record</span><strong>${escapeHtml(localLabel)}</strong></div><div><span>Backend copy</span><strong>${escapeHtml(backendLabel)}</strong></div><div><span>Mode</span><strong>${escapeHtml(status.publish_mode||status.mode||'backend')}</strong></div><div><span>Archive bucket</span><strong>${escapeHtml(bucket||'—')}</strong></div></div>${path?`<code>${escapeHtml(path)}</code>`:''}${reason&&!published&&reason!=='record_not_ready'?`<p class="eval-warning">${escapeHtml(reason)}</p>`:''}`;
|
| 1243 |
}
|
| 1244 |
function renderRunDetail(detail){
|
| 1245 |
state.selectedRunDetail=detail;
|
| 1246 |
const summary=detail.summary||{};
|
| 1247 |
+
if(typeof contextualizeSpaceTestFromBuildRun==='function')contextualizeSpaceTestFromBuildRun(detail);
|
| 1248 |
const smoke=detail.generation_smoke||{};
|
| 1249 |
const files=detail.files||[];
|
| 1250 |
const view=detail.view||{};
|
|
|
|
| 1295 |
// Legacy clean-delete invariant: await loadRuns(true) was intentionally replaced by post-delete background refresh in v190.9.
|
| 1296 |
|
| 1297 |
// Legacy validation preview invariant: if(isValidationRunDetail(detail)){showValidationRunDetail(detail)
|
| 1298 |
+
|
| 1299 |
+
// v190.15 build-first compatibility invariant: legacy/incomplete Build Runs stay visible, validation runs require parent_build_run_id before they can affect parent stats.
|
web/static/styles.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
/* v190.19 eval archive owner controls */
|
| 3 |
+
.eval-archive-actions .eval-flush-button{
|
| 4 |
+
margin-left:auto;
|
| 5 |
+
}
|
| 6 |
+
.eval-archive-panel.manageable .eval-archive-actions{
|
| 7 |
+
gap:10px;
|
| 8 |
+
align-items:center;
|
| 9 |
+
flex-wrap:wrap;
|
| 10 |
+
}
|
| 11 |
+
.eval-effective-note{
|
| 12 |
+
margin:.45rem 0 .55rem;
|
| 13 |
+
line-height:1.35;
|
| 14 |
+
}
|