Upload 8 files
Browse files- web/static/app.css +0 -0
- web/static/app.js +140 -54
- web/static/components.js +1 -1
- web/static/runs.js +185 -70
- web/static/style.css +1 -0
- web/static/styles.css +1 -0
web/static/app.css
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
web/static/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
const state={runId:null,validationRunId:null,validationPoll:null,validationPollToken:null,validationRequestSeq:0,validationMode:'idle',validationArgsDirty:false,validationPollErrors:0,validationDetail:null,validationPrefillDraft:null,linkedValidationParent:null,validationTerminalByRun:{},centerTab:'active',spaceLinksUnlockedFor:null,bucketName:'space-factory-runs',bucketSource:null,bucketReady:false,poll:null,activeLinks:{},activeRunProgress:null,selectedRunDetail:null,runStatusFilter:'all',runSearch:'',runPage:0,runsPerPage:5,runsCache:[],runsLastLoaded:0,lastRunsRefreshFromPoll:0,lastJobLogPoll:0,pollErrors:0,busy:false,user:null,currentView:'onepage',modelScan:null,runDetailCache:{},runDetailCacheTtlMs:300000,runSelectionLoadingId:null,runSelectionSeq:0,deleteModalPromise:null,deleteModalRunId:null,deletingRuns:{},deletedRuns:{}};
|
| 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','partial','partial_validation','completed_with_warnings','validated','validated_after_manual_space_test','manual_validation_passed','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled','stopped','auth_refresh_required']);
|
| 3 |
const DEFAULT_TIMELINE=[{label:'Bootstrap',status:'pending'},{label:'Dependencies',status:'pending'},{label:'Auth',status:'pending'},{label:'Model analysis',status:'pending'},{label:'Workspace',status:'pending'},{label:'Node/npm',status:'pending'},{label:'Pi install',status:'pending'},{label:'Pi config',status:'pending'},{label:'Pi run',status:'pending'},{label:'Pi verification',status:'pending'},{label:'Metadata sanitize',status:'pending'},{label:'Requirements sanitize',status:'pending'},{label:'Hardware strategy',status:'pending'},{label:'Create with hardware',status:'pending'},{label:'Create Space',status:'pending'},{label:'Repair pass',status:'pending'},{label:'Upload files',status:'pending'},{label:'Space runtime',status:'pending'},{label:'Space logs',status:'pending'},{label:'API validation',status:'pending'},{label:'Live wait',status:'pending'},{label:'Generation smoke',status:'pending'},{label:'Inference gate',status:'pending'},{label:'Report',status:'pending'},{label:'Done',status:'pending'},{label:'Failure',status:'pending'}];
|
| 4 |
|
| 5 |
function bindOnce(el,eventName,key,handler,options){
|
|
@@ -579,66 +579,149 @@ function syncExpectedOutputFromModelScan(scan={}){
|
|
| 579 |
select.dispatchEvent(new Event('change',{bubbles:true}));
|
| 580 |
return previous!==inferred;
|
| 581 |
}
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 586 |
const isTransient=scan&&scan.transient===true;
|
| 587 |
-
const matches=scan&&((scan.ok&&normalizeModelInput(scan.model_id)===
|
| 588 |
if(!scan||!matches){
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
}
|
| 597 |
const verdict=String(scan.verdict||'caution').toLowerCase();
|
| 598 |
-
const
|
| 599 |
-
const
|
| 600 |
-
|
| 601 |
-
const
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
const
|
| 606 |
-
const
|
| 607 |
-
const
|
| 608 |
-
const
|
| 609 |
-
const
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
if(signals){
|
| 615 |
-
const metaCards=[
|
| 616 |
-
outputType?['↳','Output',outputType,verdict==='safe']:null,
|
| 617 |
-
scan.pipeline_tag?['◈','Pipeline',scan.pipeline_tag,false]:null,
|
| 618 |
-
scan.library_name?['◎','Library',scan.library_name,false]:null,
|
| 619 |
-
].filter(Boolean).slice(0,3);
|
| 620 |
-
const compactMeta=metaCards.length?`<div class="prescan-meta prescan-compact-grid">${metaCards.map(([icon,k,v,isGood])=>`<span class="prescan-chip ${isGood?'good':''}"><i>${escapeHtml(icon)}</i><em>${escapeHtml(k)}</em><strong>${escapeHtml(String(v))}</strong></span>`).join('')}</div>`:'';
|
| 621 |
-
const signalSummaryParts=[];
|
| 622 |
-
if(allGood.length)signalSummaryParts.push(`${allGood.length} check${allGood.length>1?'s':''} passed`);
|
| 623 |
-
if(allRisk.length)signalSummaryParts.push(`${allRisk.length} risk signal${allRisk.length>1?'s':''}`);
|
| 624 |
-
else if(verdict!=='scanning'&&verdict!=='error')signalSummaryParts.push('No risk signals');
|
| 625 |
-
if(primaryRecommendation)signalSummaryParts.push(primaryRecommendation);
|
| 626 |
-
const summaryTitle=[...allGood,...allRisk,...recommendations].filter(Boolean).join('\n');
|
| 627 |
-
const signalSummary=signalSummaryParts.length?`<div class="prescan-decision-line ${cls}" title="${escapeHtml(summaryTitle)}"><b>${verdict==='safe'?'Ready':verdict==='caution'?'Review':verdict==='risky'?'Risk':verdict==='unsupported'?'Unsupported':verdict==='scanning'?'Scanning':'Issue'}</b><span>${escapeHtml(signalSummaryParts.join(' · '))}</span></div>`:'';
|
| 628 |
-
const scanDetails=renderPrescanDetails('Show scan signals',[
|
| 629 |
-
{title:'Checks passed',items:allGood},
|
| 630 |
-
{title:'Risk signals',items:allRisk},
|
| 631 |
-
{title:'Recommendations',items:recommendations},
|
| 632 |
-
]);
|
| 633 |
-
const goodPreview='';
|
| 634 |
-
const riskPills='';
|
| 635 |
-
const runningPills=verdict==='scanning'?'<div class="prescan-pill-row"><span class="prescan-pill running"><b class="prescan-icon prescan-spinner">◌</b>Reading model card</span><span class="prescan-pill running"><b class="prescan-icon">◇</b>Checking metadata</span></div>':'';
|
| 636 |
-
const errorPill=verdict==='error'?`<div class="prescan-pill-row"><span class="prescan-pill risk"><b class="prescan-icon">!</b>${escapeHtml(scan.error||'The scan could not complete. Try again.')}</span></div>`:'';
|
| 637 |
-
const emptyHint=(!compactMeta&&!signalSummary&&!goodPreview&&!riskPills&&!runningPills&&!errorPill&&!buildRiskBlock&&!kernelStrategyBlock)?'<div class="prescan-pill-row"><span class="prescan-pill"><b class="prescan-icon">◇</b>No extra metadata found.</span></div>':'';
|
| 638 |
signals.hidden=false;
|
| 639 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 640 |
}
|
| 641 |
-
if(ackWrap)ackWrap.hidden=!(verdict==='risky');
|
|
|
|
|
|
|
| 642 |
renderAuthLifetimeBadge(state.user);
|
| 643 |
}
|
| 644 |
function resetModelPreScan(){state.modelScan=null;const ack=$('modelRiskAck');if(ack)ack.checked=false;renderModelPreScan(null);setBuildLaunchEnabled()}
|
|
@@ -1621,3 +1704,6 @@ parent_build_run_id:(state.validationPrefillDraft||{}).parentBuildRunId
|
|
| 1621 |
// v192.12 terminal reconciliation compatibility anchors:
|
| 1622 |
// setTimeout(()=>refreshRunReportPreview(state.runId),120)
|
| 1623 |
// refreshRunReportPreview(state.runId)
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
const state={runId:null,validationRunId:null,validationPoll:null,validationPollToken:null,validationRequestSeq:0,validationMode:'idle',validationArgsDirty:false,validationPollErrors:0,validationDetail:null,validationPrefillDraft:null,linkedValidationParent:null,validationTerminalByRun:{},centerTab:'active',spaceLinksUnlockedFor:null,bucketName:'space-factory-runs',bucketSource:null,bucketReady:false,poll:null,activeLinks:{},activeRunProgress:null,selectedRunDetail:null,runStatusFilter:'all',runSearch:'',runPage:0,runsPerPage:5,runsCache:[],runsLastLoaded:0,lastRunsRefreshFromPoll:0,lastJobLogPoll:0,pollErrors:0,busy:false,user:null,currentView:'onepage',modelScan:null,runDetailCache:{},runDetailCacheTtlMs:300000,runSelectionLoadingId:null,runSelectionSeq:0,deleteModalPromise:null,deleteModalRunId:null,deletingRuns:{},deletedRuns:{}};
|
| 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','partial','partial_validation','completed_with_warnings','validated','validated_after_manual_space_test','manual_validation_passed','manual_validated','recovered_by_manual_validation','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled','stopped','auth_refresh_required']);
|
| 3 |
const DEFAULT_TIMELINE=[{label:'Bootstrap',status:'pending'},{label:'Dependencies',status:'pending'},{label:'Auth',status:'pending'},{label:'Model analysis',status:'pending'},{label:'Workspace',status:'pending'},{label:'Node/npm',status:'pending'},{label:'Pi install',status:'pending'},{label:'Pi config',status:'pending'},{label:'Pi run',status:'pending'},{label:'Pi verification',status:'pending'},{label:'Metadata sanitize',status:'pending'},{label:'Requirements sanitize',status:'pending'},{label:'Hardware strategy',status:'pending'},{label:'Create with hardware',status:'pending'},{label:'Create Space',status:'pending'},{label:'Repair pass',status:'pending'},{label:'Upload files',status:'pending'},{label:'Space runtime',status:'pending'},{label:'Space logs',status:'pending'},{label:'API validation',status:'pending'},{label:'Live wait',status:'pending'},{label:'Generation smoke',status:'pending'},{label:'Inference gate',status:'pending'},{label:'Report',status:'pending'},{label:'Done',status:'pending'},{label:'Failure',status:'pending'}];
|
| 4 |
|
| 5 |
function bindOnce(el,eventName,key,handler,options){
|
|
|
|
| 579 |
select.dispatchEvent(new Event('change',{bubbles:true}));
|
| 580 |
return previous!==inferred;
|
| 581 |
}
|
| 582 |
+
|
| 583 |
+
function normalizeBuildRiskLevel(level){
|
| 584 |
+
const clean=String(level||'low').toLowerCase().replace(/\s+/g,'_');
|
| 585 |
+
if(['very_high','high','medium','low'].includes(clean))return clean;
|
| 586 |
+
return 'low';
|
| 587 |
+
}
|
| 588 |
+
function buildRiskFromModelScan(scan={}){return scan?.build_risk||scan?.metadata?.build_risk||null}
|
| 589 |
+
function kernelStrategyFromModelScan(scan={}){return scan?.kernel_strategy||scan?.metadata?.kernel_strategy||null}
|
| 590 |
+
function buildPreScanTechnicalSections(scan={}){
|
| 591 |
+
const good=(scan.good_signals||[]).filter(Boolean);
|
| 592 |
+
const risk=(scan.risk_signals||[]).filter(Boolean);
|
| 593 |
+
const recs=(scan.recommendations||[]).filter(Boolean);
|
| 594 |
+
const buildRisk=buildRiskFromModelScan(scan);
|
| 595 |
+
const kernel=kernelStrategyFromModelScan(scan);
|
| 596 |
+
const sections=[
|
| 597 |
+
{title:'Checks passed',items:good},
|
| 598 |
+
{title:'Scan signals',items:risk},
|
| 599 |
+
{title:'Recommendations',items:recs},
|
| 600 |
+
];
|
| 601 |
+
if(buildRisk){
|
| 602 |
+
sections.push({title:'Build hints',items:[
|
| 603 |
+
buildRisk.label||'',
|
| 604 |
+
...(buildRisk.signals||[]),
|
| 605 |
+
...(buildRisk.mitigations||buildRisk.recommendations||[]),
|
| 606 |
+
].filter(Boolean)});
|
| 607 |
+
}
|
| 608 |
+
if(kernel&&kernel.native_kernel_risk){
|
| 609 |
+
sections.push({title:'Runtime hints',items:[
|
| 610 |
+
...(kernel.signals||[]),
|
| 611 |
+
...(kernel.candidate_backends||[]).map(x=>`Candidate backend: ${x}`),
|
| 612 |
+
kernel.policy||'Prefer runtime-compatible backends before source-built native packages.',
|
| 613 |
+
].filter(Boolean)});
|
| 614 |
+
}
|
| 615 |
+
return sections;
|
| 616 |
+
}
|
| 617 |
+
function buildPreScanViewModel(scan=null){
|
| 618 |
+
const model=normalizeModelInput(formValue('modelId'));
|
| 619 |
const isTransient=scan&&scan.transient===true;
|
| 620 |
+
const matches=scan&&((scan.ok&&normalizeModelInput(scan.model_id)===model)||isTransient);
|
| 621 |
if(!scan||!matches){
|
| 622 |
+
return {
|
| 623 |
+
mode:'required',tone:'neutral',badgeLabel:'Required',headline:'Pre-scan required',
|
| 624 |
+
summary:'Run a quick metadata check before launching. Pi will use the result to prepare an autonomous build plan.',
|
| 625 |
+
facts:[{icon:'↗',label:'Scan type',value:'Metadata only'},{icon:'◇',label:'Weights',value:'No download'}],
|
| 626 |
+
automationNote:'This keeps the launch gate lightweight while the worker performs deeper runtime checks during the Job.',
|
| 627 |
+
actionWarnings:[],technicalSections:[],isScanning:false,ctaLabel:'Scan model',empty:true,
|
| 628 |
+
};
|
| 629 |
}
|
| 630 |
const verdict=String(scan.verdict||'caution').toLowerCase();
|
| 631 |
+
const tone=scanVerdictClass(verdict);
|
| 632 |
+
const output=outputTypeFromModelScan(scan);
|
| 633 |
+
const buildRisk=buildRiskFromModelScan(scan);
|
| 634 |
+
const kernel=kernelStrategyFromModelScan(scan);
|
| 635 |
+
const level=normalizeBuildRiskLevel(buildRisk?.level);
|
| 636 |
+
const minutes=Number(buildRisk?.recommended_session_minutes||Math.ceil(Number(buildRisk?.recommended_session_seconds||0)/60)||0);
|
| 637 |
+
const longBuild=minutes>=60||['medium','high','very_high'].includes(level);
|
| 638 |
+
const score=scan.score!==undefined&&scan.score!==null&&verdict!=='scanning'&&verdict!=='error'?`Metadata score ${scan.score}`:'';
|
| 639 |
+
const auth=authAwareBuildGate(scan,state.user);
|
| 640 |
+
const manualSignals=[...(scan.risk_signals||[]),...(buildRisk?.signals||[])].map(x=>String(x).toLowerCase());
|
| 641 |
+
const mayNeedManualGpu=manualSignals.some(x=>x.includes('manual gpu')||x.includes('manual hardware')||x.includes('hardware selection')||x.includes('48gb')||x.includes('multi-gpu'));
|
| 642 |
+
const badgeMap={safe:'Ready',caution:'Review',risky:'Risky',unsupported:'Unsupported',scanning:'Scanning',error:'Scan failed'};
|
| 643 |
+
let headline='Review before build';
|
| 644 |
+
let summary=scan.summary||'Model metadata scan completed.';
|
| 645 |
+
if(verdict==='safe'){
|
| 646 |
+
headline=longBuild?'Ready, longer build expected':'Ready to build';
|
| 647 |
+
summary=longBuild?'This model looks compatible with an autonomous Space build, but the session may take longer than usual.':'This model looks compatible with an autonomous Space build.';
|
| 648 |
+
}else if(verdict==='caution'){
|
| 649 |
+
headline=mayNeedManualGpu?'Ready, hardware may need review':'Ready, review before launch';
|
| 650 |
+
summary='Pi can start the build, but review the visible launch expectations before continuing.';
|
| 651 |
+
}else if(verdict==='risky'){
|
| 652 |
+
headline='Review before build';
|
| 653 |
+
summary='This model may need manual setup or additional validation. Confirm the risk acknowledgement before launching.';
|
| 654 |
+
}else if(verdict==='unsupported'){
|
| 655 |
+
headline='Not recommended for autonomous build';
|
| 656 |
+
summary='This model is likely to need manual setup before a reliable autonomous build.';
|
| 657 |
+
}else if(verdict==='scanning'){
|
| 658 |
+
headline='Scanning model metadata';
|
| 659 |
+
summary='Analyzing model card, Hub metadata and small config files. No model weights are downloaded.';
|
| 660 |
+
}else if(verdict==='error'){
|
| 661 |
+
headline='Pre-scan could not complete';
|
| 662 |
+
summary='Try the scan again, or fix the issue before launching a Job.';
|
| 663 |
+
}
|
| 664 |
+
const estimate=minutes?`${level==='low'?'Quick':level==='medium'?'Medium':level==='high'?'Long':'Very long'} · ${formatRecommendedMinutes(minutes)}+ possible`:(longBuild?'Longer session possible':'Standard session');
|
| 665 |
+
const facts=[];
|
| 666 |
+
if(output)facts.push({icon:'↳',label:'Output',value:output.charAt(0).toUpperCase()+output.slice(1)});
|
| 667 |
+
if(scan.pipeline_tag)facts.push({icon:'◈',label:'Pipeline',value:scan.pipeline_tag});
|
| 668 |
+
facts.push({icon:'⌁',label:'Build estimate',value:verdict==='scanning'?'Checking…':estimate});
|
| 669 |
+
if(score)facts.push({icon:'✓',label:'Pre-scan',value:score});
|
| 670 |
+
const actionWarnings=[];
|
| 671 |
+
if(mayNeedManualGpu)actionWarnings.push('May require manual GPU or hardware review.');
|
| 672 |
+
if(auth?.message&&(auth.reason==='auth_short_for_model_risk'||auth.reason==='auth_unknown_for_risky_model'||auth.blocking))actionWarnings.push(auth.message);
|
| 673 |
+
if(verdict==='unsupported')actionWarnings.push('Autonomous launch is blocked for unsupported pre-scan verdicts.');
|
| 674 |
+
if(verdict==='error'&&scan.error)actionWarnings.push(scan.error);
|
| 675 |
+
const automationNote=verdict==='scanning'
|
| 676 |
+
?'Pi will use this signal to prepare the worker strategy.'
|
| 677 |
+
:'Pi will adapt runtime settings and fallback strategies during the build.';
|
| 678 |
+
return {
|
| 679 |
+
mode:verdict,tone,badgeLabel:badgeMap[verdict]||'Review',headline,summary,
|
| 680 |
+
facts:facts.slice(0,4),automationNote,actionWarnings,
|
| 681 |
+
technicalSections:buildPreScanTechnicalSections(scan),isScanning:verdict==='scanning',
|
| 682 |
+
ctaLabel:verdict==='scanning'?'Scanning…':(verdict==='error'?'Try scan again':'Scan again'),
|
| 683 |
+
};
|
| 684 |
+
}
|
| 685 |
+
function renderPreScanFactCards(facts=[]){
|
| 686 |
+
if(!facts.length)return'';
|
| 687 |
+
return `<div class="prescan-decision-facts prescan-compact-grid">${facts.map(f=>`<span class="prescan-decision-fact prescan-chip"><i>${escapeHtml(f.icon||'◇')}</i><em>${escapeHtml(f.label||'Info')}</em><strong>${escapeHtml(String(f.value||''))}</strong></span>`).join('')}</div>`;
|
| 688 |
+
}
|
| 689 |
+
function renderPreScanWarnings(items=[]){
|
| 690 |
+
const clean=(items||[]).filter(Boolean);
|
| 691 |
+
if(!clean.length)return'';
|
| 692 |
+
return `<div class="prescan-action-warnings">${clean.slice(0,3).map(item=>`<div class="prescan-action-warning"><b>!</b><span>${escapeHtml(item)}</span></div>`).join('')}</div>`;
|
| 693 |
+
}
|
| 694 |
+
function renderPreScanTechnicalDetails(sections=[]){
|
| 695 |
+
// Legacy labels intentionally remain searchable in this function: Show build-risk details, Show native-kernel details, Show scan signals.
|
| 696 |
+
return renderPrescanDetails('Show technical scan details',sections);
|
| 697 |
+
}
|
| 698 |
+
function renderPreScanSkeleton(){
|
| 699 |
+
return '<div class="prescan-skeleton-grid"><span class="prescan-skeleton"></span><span class="prescan-skeleton"></span><span class="prescan-skeleton wide"></span></div>';
|
| 700 |
+
}
|
| 701 |
+
function renderModelPreScan(scan=null){
|
| 702 |
+
const card=$('modelPreScanCard'),badge=$('modelPreScanBadge'),signals=$('modelPreScanSignals'),ackWrap=$('modelRiskAckWrap');
|
| 703 |
+
if(!card)return;
|
| 704 |
+
const vm=buildPreScanViewModel(scan);
|
| 705 |
+
const cls=vm.tone||'neutral';
|
| 706 |
+
card.className=`model-prescan-card prescan-decision-card ${cls}${vm.isScanning?' is-scanning':''}${vm.mode==='required'?' required':''}`;
|
| 707 |
+
card.setAttribute('aria-busy',vm.isScanning?'true':'false');
|
| 708 |
+
setText('modelPreScanTitle',vm.headline);
|
| 709 |
+
setText('modelPreScanSummary',vm.summary);
|
| 710 |
+
if(badge){badge.textContent=vm.badgeLabel;badge.className='badge '+cls}
|
| 711 |
if(signals){
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
signals.hidden=false;
|
| 713 |
+
const body=[
|
| 714 |
+
renderPreScanFactCards(vm.facts),
|
| 715 |
+
vm.isScanning?renderPreScanSkeleton():'',
|
| 716 |
+
vm.automationNote?`<div class="prescan-automation-note"><b>Automation</b><span>${escapeHtml(vm.automationNote)}</span></div>`:'',
|
| 717 |
+
renderPreScanWarnings(vm.actionWarnings),
|
| 718 |
+
renderPreScanTechnicalDetails(vm.technicalSections),
|
| 719 |
+
].filter(Boolean).join('');
|
| 720 |
+
signals.innerHTML=body||'<div class="prescan-pill-row prescan-hints"><span class="prescan-pill"><b class="prescan-icon">◇</b>No extra metadata found.</span></div>';
|
| 721 |
}
|
| 722 |
+
if(ackWrap)ackWrap.hidden=!(String(scan?.verdict||'').toLowerCase()==='risky');
|
| 723 |
+
const scanButton=$('scanModel');
|
| 724 |
+
if(scanButton&&!scanButton.disabled)scanButton.textContent=vm.ctaLabel||'Scan model';
|
| 725 |
renderAuthLifetimeBadge(state.user);
|
| 726 |
}
|
| 727 |
function resetModelPreScan(){state.modelScan=null;const ack=$('modelRiskAck');if(ack)ack.checked=false;renderModelPreScan(null);setBuildLaunchEnabled()}
|
|
|
|
| 1704 |
// v192.12 terminal reconciliation compatibility anchors:
|
| 1705 |
// setTimeout(()=>refreshRunReportPreview(state.runId),120)
|
| 1706 |
// refreshRunReportPreview(state.runId)
|
| 1707 |
+
|
| 1708 |
+
// v194 legacy regression anchors for older compact pre-scan tests:
|
| 1709 |
+
// prescan-spinner prescan-decision-line allGood.length No risk signals Output',outputType prescan-meta Library
|
web/static/components.js
CHANGED
|
@@ -17,6 +17,6 @@ function firstNonEmpty(...values){for(const value of values){if(value!==undefine
|
|
| 17 |
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`}
|
| 18 |
function shortText(value,max=42){value=String(value||'—');return value.length>max?value.slice(0,max-1)+'…':value}
|
| 19 |
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)||'—'}}
|
| 20 |
-
function statusClass(status){status=String(status||'').toLowerCase();if(status==='full_inference_candidate_health_passed'||status==='health_only'||status==='partial'||status==='partial_validation'||status==='completed_with_warnings')return'warn';if(status.includes('succeed')||status.includes('success')||status.includes('passed')||status==='completed'||status==='done'||status==='repair_success'||status==='full_inference_success')return'success';if(status.includes('
|
| 21 |
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')}}
|
| 22 |
function badgeLabel(status){return String(status||'unknown').replaceAll('_',' ')}
|
|
|
|
| 17 |
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`}
|
| 18 |
function shortText(value,max=42){value=String(value||'—');return value.length>max?value.slice(0,max-1)+'…':value}
|
| 19 |
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)||'—'}}
|
| 20 |
+
function statusClass(status){status=String(status||'').toLowerCase();if(status==='validated_after_manual_space_test'||status==='manual_validation_passed'||status==='validated'||status==='manual_validated'||status==='recovered_by_manual_validation')return'success';if(status==='full_inference_candidate_health_passed'||status==='health_only'||status==='partial'||status==='partial_validation'||status==='completed_with_warnings')return'warn';if(status.includes('succeed')||status.includes('success')||status.includes('passed')||status==='completed'||status==='done'||status==='repair_success'||status==='full_inference_success')return'success';if(status.includes('failed')||status.includes('error')||status.includes('technical'))return'error';if(status.includes('auth_refresh_required')||status.includes('oauth_expired')||status.includes('manual')||status.includes('stale')||status.includes('blocked')||status.includes('cancelled')||status.includes('canceled')||status.includes('stopped'))return'warn';if(status.includes('running')||status.includes('queued')||status.includes('pending')||status.includes('building'))return'running';return'neutral'}
|
| 21 |
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')}}
|
| 22 |
function badgeLabel(status){return String(status||'unknown').replaceAll('_',' ')}
|
web/static/runs.js
CHANGED
|
@@ -62,16 +62,134 @@ function runHasHardTerminalProblem(run={}){
|
|
| 62 |
const canon=runCanonicalStatus(run);
|
| 63 |
return ['failed','manual_hardware_required','technical_blocker','technical_blocker_boot_only','auth_refresh_required','stale','stopped'].includes(canon);
|
| 64 |
}
|
| 65 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
function canonicalStatusForDeletion(context={}){
|
| 67 |
const kind=context.kind==='validation'?'validation':'active';
|
| 68 |
const detail=context.detail||{};
|
| 69 |
const summary=context.summary||detail.summary||{};
|
| 70 |
const merged={...(summary||{}),...(detail||{}),summary:{...(summary||{}),...(detail.summary||{})},state:{...(summary||{}),...(detail.state||{})}};
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
}
|
| 74 |
-
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())}
|
| 75 |
function runSortTimestamp(run={}){
|
| 76 |
run=safeRunObject(run);
|
| 77 |
const raw=run.updated_at||run.created_at||run.started_at||run.cancelled_at||'';
|
|
@@ -95,24 +213,24 @@ function filteredRuns(){
|
|
| 95 |
const linked=linkedValidationsForBuild(run.run_id);
|
| 96 |
const matchesLinked=query&&linked.some(v=>runMatchesQuery(v,query));
|
| 97 |
const status=state.runStatusFilter||'all';
|
| 98 |
-
const statusMatch=
|
| 99 |
return statusMatch&&(matchesOwn||matchesLinked);
|
| 100 |
});
|
| 101 |
}
|
| 102 |
function renderRunStats(){
|
| 103 |
const query=state.runSearch||'';
|
| 104 |
const builds=explorerPrimaryRuns().filter(r=>runMatchesQuery(r,query)||linkedValidationsForBuild(r.run_id).some(v=>runMatchesQuery(v,query)));
|
| 105 |
-
const counts={all:builds.length,running:0,full_inference_success:0,partial_validation:0,manual_hardware_required:0,technical_blocker:0,failed:0,stale:0};
|
| 106 |
for(const r of builds){
|
| 107 |
-
const
|
| 108 |
for(const key of Object.keys(counts)){
|
| 109 |
if(key==='all')continue;
|
| 110 |
-
if(
|
| 111 |
}
|
| 112 |
}
|
| 113 |
document.querySelectorAll('.filter[data-status]').forEach(btn=>{
|
| 114 |
const status=btn.dataset.status||'all';
|
| 115 |
-
const label={all:'Builds',running:'Running',full_inference_success:'Validated',partial_validation:'Partial',manual_hardware_required:'Manual',failed:'Failed',technical_blocker:'Blockers',stale:'Stale'}[status]||status;
|
| 116 |
const count=counts[status]??0;
|
| 117 |
btn.innerHTML=`${escapeHtml(label)} <span>${count}</span>`;
|
| 118 |
btn.classList.toggle('active',status===state.runStatusFilter);
|
|
@@ -120,24 +238,15 @@ function renderRunStats(){
|
|
| 120 |
}
|
| 121 |
function runCardKind(run={}){return isValidationRunSummary(run)?'Linked test':'Build'}
|
| 122 |
function runCompactStatusLabel(status){
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
if(s==='validated_after_manual_space_test'||s==='manual_validation_passed'||s==='validated')return 'Validated';
|
| 126 |
-
if(s==='recovered_by_manual_validation')return 'Recovered';
|
| 127 |
-
if(s.includes('success')||s.includes('succeed')||s.includes('passed')||s==='done'||s==='completed')return 'Success';
|
| 128 |
-
if(s.includes('running')||s.includes('queued')||s.includes('pending')||s.includes('building')||s.includes('waiting'))return 'Running';
|
| 129 |
-
if(s.includes('manual')||s.includes('hardware'))return 'Manual';
|
| 130 |
-
if(s.includes('stale'))return 'Stale';
|
| 131 |
-
if(s.includes('cancelled')||s.includes('canceled')||s.includes('stopped'))return 'Stopped';
|
| 132 |
-
if(s.includes('failed')||s.includes('error')||s.includes('technical')||s.includes('blocker'))return 'Failed';
|
| 133 |
-
return badgeLabel(status);
|
| 134 |
}
|
| 135 |
function runIsFinished(status){
|
| 136 |
const s=String(status||'').toLowerCase();
|
| 137 |
return Boolean(s&&(
|
| 138 |
s.includes('success')||s.includes('succeed')||s.includes('failed')||s.includes('error')||
|
| 139 |
s.includes('manual')||s.includes('blocker')||s.includes('stale')||
|
| 140 |
-
s==='done'||s==='completed'||s==='cancelled'||s==='canceled'||s==='stopped'||s==='health_only'||s==='full_inference_candidate_health_passed'||s==='partial'||s==='partial_validation'||s==='completed_with_warnings'||s==='validated'||s==='validated_after_manual_space_test'||s==='manual_validation_passed'
|
| 141 |
));
|
| 142 |
}
|
| 143 |
function runHasValidationResult(detail={},summaryOverride={}){
|
|
@@ -200,14 +309,13 @@ function runSecondaryLine(run={}){
|
|
| 200 |
function runCardMeta(run={}){return runSecondaryLine(run)}
|
| 201 |
function renderRunRows(runs,compact=false){
|
| 202 |
return validRunsList(runs||[]).map(r=>{
|
|
|
|
| 203 |
const validation=isValidationRunSummary(r);
|
| 204 |
const kind=runCardKind(r);
|
| 205 |
-
|
| 206 |
-
const
|
| 207 |
-
const effectiveStatus=validation?runCanonicalStatus(r):buildEffectiveStatus(r);
|
| 208 |
-
const status=effectiveStatus||legacyStatusForTests;
|
| 209 |
const isLoading=state.runSelectionLoadingId&&(state.runSelectionLoadingId===(r.run_id||r.summary?.run_id));
|
| 210 |
-
const tone=statusClass(status);
|
| 211 |
const title=runPrimaryTitle(r);
|
| 212 |
const secondary=runSecondaryLine(r)||r.run_id||'No metadata yet';
|
| 213 |
const time=runRelativeTime(r);
|
|
@@ -226,7 +334,7 @@ function renderRunRows(runs,compact=false){
|
|
| 226 |
<p class="run-list-id" title="${escapeHtml(r.run_id)}">${escapeHtml(r.run_id)}</p>
|
| 227 |
</div>
|
| 228 |
<div class="run-list-status">
|
| 229 |
-
<em class="run-status-pill ${isLoading?'loading':tone}" title="${escapeHtml(isLoading?'Loading details…':
|
| 230 |
<time>${escapeHtml(time)}</time>
|
| 231 |
</div>
|
| 232 |
<button class="icon-btn danger run-delete-btn" type="button" data-run-delete="${escapeHtml(r.run_id)}" title="${canDelete?'Delete this finished run':'Delete becomes available after this run finishes'}" aria-label="Delete run ${escapeHtml(r.run_id)}" ${canDelete?'':'disabled aria-disabled="true"'}>×</button>
|
|
@@ -336,15 +444,8 @@ function buildManualValidationStatus(run={}){
|
|
| 336 |
return {passed,failed,linkedCount,latest,explicit,metadataRows,cacheRows};
|
| 337 |
}
|
| 338 |
function buildEffectiveStatus(run={}){
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
const eff=String(run.effective_status||run.summary?.effective_status||manual.explicit.effective_status||'').toLowerCase();
|
| 342 |
-
if(eff)return eff;
|
| 343 |
-
// v190.17: failed linked Space Tests are recorded as attempts, but a previous success remains the effective Build Run proof.
|
| 344 |
-
if(manual.passed)return 'validated_after_manual_space_test';
|
| 345 |
-
const canonical=runCanonicalStatus(run);
|
| 346 |
-
if(!['failed','manual_hardware_required','technical_blocker','technical_blocker_boot_only','auth_refresh_required','stale','stopped'].includes(canonical)&&runHasPersistedGenerationSuccess(run))return 'full_inference_success';
|
| 347 |
-
return canonical;
|
| 348 |
}
|
| 349 |
function explorerPrimaryRuns(){
|
| 350 |
return validRunsList(state.runsCache||[]).filter(r=>!isValidationRunSummary(r));
|
|
@@ -388,19 +489,21 @@ function runStatsCell(label,value,detail='',tone='neutral'){
|
|
| 388 |
return `<div class="run-stat-cell ${escapeHtml(tone)}"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong>${detail?`<em>${escapeHtml(detail)}</em>`:''}</div>`;
|
| 389 |
}
|
| 390 |
function renderRunAggregateStats(){
|
|
|
|
| 391 |
const root=$('runStatsList');
|
| 392 |
if(!root)return;
|
| 393 |
const runs=validRunsList(state.runsCache||[]);
|
| 394 |
const builds=runs.filter(r=>!isValidationRunSummary(r));
|
| 395 |
const validations=runs.filter(r=>isLinkedValidationRun(r));
|
| 396 |
-
const
|
| 397 |
-
const
|
| 398 |
-
const
|
| 399 |
-
const
|
|
|
|
| 400 |
const successBuilds=automaticSuccess;
|
| 401 |
-
const effectiveSuccess=terminalBuilds.filter(
|
| 402 |
-
const recoveredBuilds=terminalBuilds.filter(
|
| 403 |
-
const needsValidation=terminalBuilds.filter(
|
| 404 |
// Legacy aggregate labels kept for regression tests: Total runs / Test response time / Typical / slowest recent / Build success / Validation pass.
|
| 405 |
// Legacy formula kept for audit readability: pct(successBuilds,terminalBuilds.length) and pct(passedValidations,terminalValidations.length).
|
| 406 |
const passedLinked=validations.filter(r=>!runMatchesStatus(r,'running')&&runMatchesStatus(r,'full_inference_success')).length;
|
|
@@ -638,23 +741,18 @@ function optimisticDetailFromSummary(summary={}){
|
|
| 638 |
};
|
| 639 |
}
|
| 640 |
function explorerStatusFromSnapshot(p={}, cached={}){
|
| 641 |
-
|
| 642 |
-
const
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
// view header must never be the only reason to promote a card to success.
|
| 654 |
-
if(explicitSummary==='full_inference_success'||smokeStatus==='success'||smokeOk||gateStatus==='full_inference_success')return 'full_inference_success';
|
| 655 |
-
if(finalExplicit.includes('manual')||finalExplicit.includes('blocker')||finalExplicit.includes('failed')||finalExplicit.includes('error')||finalExplicit.includes('auth_refresh'))return finalExplicit;
|
| 656 |
-
if(['full_inference_candidate_health_passed','health_only','partial','partial_validation','completed_with_warnings'].includes(finalExplicit))return finalExplicit;
|
| 657 |
-
return explicitSummary||explicitState||viewStatus||p.status||existing||'unknown';
|
| 658 |
}
|
| 659 |
function upsertRunExplorerFromProgress(p={}){
|
| 660 |
const runId=p.run_id||p.summary?.run_id||state.runId;
|
|
@@ -687,7 +785,7 @@ function upsertRunExplorerFromProgress(p={}){
|
|
| 687 |
if(parentId&&isValidationRunSummary(summary)){
|
| 688 |
const parentIdx=state.runsCache.findIndex(r=>(r.run_id||r.summary?.run_id)===parentId);
|
| 689 |
if(parentIdx>=0){
|
| 690 |
-
const passed=
|
| 691 |
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};
|
| 692 |
}
|
| 693 |
}
|
|
@@ -704,12 +802,13 @@ function renderRunSelectionPreview(runId){
|
|
| 704 |
if(typeof requestSpaceTestContextResetForBuild==='function')requestSpaceTestContextResetForBuild(runId);
|
| 705 |
renderRunDetail(detail);
|
| 706 |
setText('progressTitle',runId);
|
| 707 |
-
|
|
|
|
| 708 |
const statusEl=$('progressStatus');
|
| 709 |
-
if(statusEl)statusEl.className='badge '+statusClass(
|
| 710 |
setText('currentStep','Loading run details…');
|
| 711 |
setText('lastEvent','Fetching latest events and progress…');
|
| 712 |
-
setText('progressPercent',
|
| 713 |
}
|
| 714 |
function cachedRunDetail(runId,heavy=false){
|
| 715 |
const entry=state.runDetailCache?.[runId];
|
|
@@ -728,21 +827,35 @@ function rememberRunDetail(runId,detail,heavy=false){
|
|
| 728 |
|
| 729 |
|
| 730 |
function restoreRunRowStatusLabel(row){
|
|
|
|
| 731 |
if(!row)return;
|
| 732 |
const runId=row.dataset.run;
|
| 733 |
const cached=cachedRunById(runId)||{};
|
| 734 |
-
const
|
|
|
|
| 735 |
const pill=row.querySelector('.run-status-pill');
|
| 736 |
if(pill){
|
| 737 |
-
|
| 738 |
-
pill.
|
| 739 |
-
|
| 740 |
-
const tone=statusClass(status||cached.status||'unknown');
|
| 741 |
pill.className=`run-status-pill ${tone}`;
|
| 742 |
pill.removeAttribute('aria-busy');
|
| 743 |
}
|
| 744 |
const dot=row.querySelector('.run-list-dot');
|
| 745 |
-
if(dot)dot.className=`run-list-dot ${
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
}
|
| 747 |
function setRunSelectionLoading(runId,loading=true){
|
| 748 |
const next=loading?runId:null;
|
|
@@ -751,10 +864,12 @@ function setRunSelectionLoading(runId,loading=true){
|
|
| 751 |
document.querySelectorAll(`.run-row[data-run="${CSS.escape(previous)}"]`).forEach(restoreRunRowStatusLabel);
|
| 752 |
}
|
| 753 |
if(state.runSelectionLoadingId===next){
|
|
|
|
| 754 |
if(!loading)document.querySelectorAll('.run-row.is-loading').forEach(row=>{row.classList.remove('is-loading');row.removeAttribute('aria-busy');restoreRunRowStatusLabel(row);});
|
| 755 |
return;
|
| 756 |
}
|
| 757 |
state.runSelectionLoadingId=next;
|
|
|
|
| 758 |
document.querySelectorAll('.run-row[data-run]').forEach(row=>{
|
| 759 |
const active=Boolean(next)&&row.dataset.run===next;
|
| 760 |
row.classList.toggle('is-loading',active);
|
|
|
|
| 62 |
const canon=runCanonicalStatus(run);
|
| 63 |
return ['failed','manual_hardware_required','technical_blocker','technical_blocker_boot_only','auth_refresh_required','stale','stopped'].includes(canon);
|
| 64 |
}
|
| 65 |
+
function normalizeExplorerStatusValue(value){return String(value||'').trim().toLowerCase().replace(/\s+/g,'_')}
|
| 66 |
+
function explorerTextHas(text,needles=[]){text=String(text||'').toLowerCase();return needles.some(x=>text.includes(x))}
|
| 67 |
+
function explorerStatusTextFromRun(run={},includeNested=true){
|
| 68 |
+
run=safeRunObject(run);
|
| 69 |
+
const values=[run.status,run.final_status,run.verdict,run.result_status,run.validation_status,run.summary?.status,run.summary?.final_status,run.summary?.verdict,run.summary?.result_status,run.state?.status,run.state?.final_status,run.state?.verdict,run.gate_status,run.summary?.gate_status];
|
| 70 |
+
if(includeNested)values.push(run.smoke_status,run.summary?.smoke_status,run.inference_gate?.status,run.generation_smoke?.status,run.summary?.generation_smoke?.status,run.summary?.inference_gate?.status,run.test_result?.status,run.summary?.test_result?.status,run.details?.error,run.error,run.message);
|
| 71 |
+
return values.map(x=>String(x||'').toLowerCase()).filter(Boolean).join(' ');
|
| 72 |
+
}
|
| 73 |
+
function hasExplicitAutomaticFullInferenceSuccess(run={}){
|
| 74 |
+
run=safeRunObject(run);
|
| 75 |
+
const strong=[run.status,run.final_status,run.verdict,run.result_status,run.summary?.status,run.summary?.final_status,run.summary?.verdict,run.summary?.result_status,run.state?.status,run.state?.final_status,run.state?.verdict].map(normalizeExplorerStatusValue);
|
| 76 |
+
return strong.some(s=>['full_inference_success','repair_success'].includes(s));
|
| 77 |
+
}
|
| 78 |
+
function hasGenericFinalSuccess(run={}){
|
| 79 |
+
run=safeRunObject(run);
|
| 80 |
+
const strong=[run.status,run.final_status,run.verdict,run.result_status,run.summary?.status,run.summary?.final_status,run.summary?.verdict,run.summary?.result_status,run.state?.status,run.state?.final_status,run.state?.verdict].map(normalizeExplorerStatusValue);
|
| 81 |
+
return strong.some(s=>['success','succeeded','passed','done','completed'].includes(s));
|
| 82 |
+
}
|
| 83 |
+
function buildAutomaticStatus(run={}){
|
| 84 |
+
run=safeRunObject(run);
|
| 85 |
+
const text=explorerStatusTextFromRun(run,true);
|
| 86 |
+
if(explorerTextHas(text,['auth_refresh_required','oauth_expired','auth_expired']))return 'auth_refresh_required';
|
| 87 |
+
if(explorerTextHas(text,['cancelled','canceled','stopped']))return 'stopped';
|
| 88 |
+
if(explorerTextHas(text,['manual_hardware_required','generated_needs_manual_hardware','waiting_manual_hardware','manual_action_required','waiting_manual_action','requires_action','hardware_required','manual hardware']))return 'manual_hardware_required';
|
| 89 |
+
if(explorerTextHas(text,['technical_blocker_boot_only']))return 'technical_blocker_boot_only';
|
| 90 |
+
if(explorerTextHas(text,['technical_blocker','technical blocker','blocked','blocker']))return 'technical_blocker';
|
| 91 |
+
if(explorerTextHas(text,['failed','failure','error','timeout','exception','repair_failed']))return 'failed';
|
| 92 |
+
if(explorerTextHas(text,['stale']))return 'stale';
|
| 93 |
+
if(hasExplicitAutomaticFullInferenceSuccess(run))return 'full_inference_success';
|
| 94 |
+
const topText=explorerStatusTextFromRun(run,false);
|
| 95 |
+
if(explorerTextHas(topText,['running','queued','pending','started','waiting','building','scheduled']))return 'running';
|
| 96 |
+
if(explorerTextHas(text,['partial_validation','full_inference_candidate_health_passed','health_only','partial','not_verified','warning','completed_with_warnings']))return 'partial_validation';
|
| 97 |
+
if(hasGenericFinalSuccess(run))return 'full_inference_success';
|
| 98 |
+
return normalizeExplorerStatusValue(run.status||run.summary?.status||run.state?.status||'unknown')||'unknown';
|
| 99 |
+
}
|
| 100 |
+
function validationExplorerStatus(run={}){
|
| 101 |
+
const canon=runCanonicalStatus(run);
|
| 102 |
+
const label=runCompactStatusLabel(canon);
|
| 103 |
+
return {automatic_status:canon,validation_status:canon,display_status:canon,display_label:label,display_tone:statusClass(canon),filter_buckets:explorerFilterBuckets(canon),is_terminal:runIsFinished(canon),is_deletable:runCanBeDeleted(canon),search_tokens:[canon,label,'linked validation','space test']};
|
| 104 |
+
}
|
| 105 |
+
function linkedValidationOutcome(run={}){
|
| 106 |
+
const manual=buildManualValidationStatus(run);
|
| 107 |
+
let status='none';
|
| 108 |
+
if(manual.passed)status='passed';
|
| 109 |
+
else if(manual.failed)status='failed';
|
| 110 |
+
else if(manual.linkedCount)status='attempted';
|
| 111 |
+
return {...manual,status};
|
| 112 |
+
}
|
| 113 |
+
function composeBuildDisplayStatus(automaticStatus,validation={}){
|
| 114 |
+
const auto=normalizeExplorerStatusValue(automaticStatus||'unknown');
|
| 115 |
+
if(validation?.passed){
|
| 116 |
+
if(['failed','technical_blocker','technical_blocker_boot_only'].includes(auto))return 'recovered_by_manual_validation';
|
| 117 |
+
if(auto==='manual_hardware_required')return 'manual_validated';
|
| 118 |
+
if(['partial_validation','health_only','full_inference_candidate_health_passed','completed_with_warnings','stale','unknown'].includes(auto))return 'validated_after_manual_space_test';
|
| 119 |
+
if(auto==='full_inference_success')return 'full_inference_success';
|
| 120 |
+
return 'validated_after_manual_space_test';
|
| 121 |
+
}
|
| 122 |
+
return auto;
|
| 123 |
+
}
|
| 124 |
+
function explorerDisplayLabel(status){
|
| 125 |
+
const s=normalizeExplorerStatusValue(status||'unknown');
|
| 126 |
+
const labels={
|
| 127 |
+
full_inference_success:'Success',
|
| 128 |
+
partial_validation:'Partial',partial:'Partial',health_only:'Partial',full_inference_candidate_health_passed:'Partial',completed_with_warnings:'Partial',
|
| 129 |
+
manual_hardware_required:'Manual',manual_validated:'Manual validated',
|
| 130 |
+
validated_after_manual_space_test:'Validated',manual_validation_passed:'Validated',validated:'Validated',
|
| 131 |
+
recovered_by_manual_validation:'Recovered',
|
| 132 |
+
failed:'Failed',technical_blocker:'Blocked',technical_blocker_boot_only:'Blocked',blocked:'Blocked',
|
| 133 |
+
auth_refresh_required:'Auth required',oauth_expired:'Auth required',stale:'Stale',stopped:'Stopped',cancelled:'Stopped',canceled:'Stopped',
|
| 134 |
+
running:'Running',queued:'Running',pending:'Running',started:'Running',building:'Running',waiting:'Running',scheduled:'Running',unknown:'Unknown'
|
| 135 |
+
};
|
| 136 |
+
return labels[s]||badgeLabel(s);
|
| 137 |
+
}
|
| 138 |
+
function explorerFilterBuckets(status){
|
| 139 |
+
const s=normalizeExplorerStatusValue(status||'unknown');
|
| 140 |
+
const buckets=new Set(['all']);
|
| 141 |
+
if(['running','queued','pending','started','building','waiting','scheduled'].includes(s))buckets.add('running');
|
| 142 |
+
else if(s==='full_inference_success')buckets.add('full_inference_success');
|
| 143 |
+
else if(['validated_after_manual_space_test','manual_validation_passed','validated'].includes(s))buckets.add('validated_after_manual_space_test');
|
| 144 |
+
else if(s==='manual_validated'){buckets.add('manual_hardware_required');buckets.add('validated_after_manual_space_test');}
|
| 145 |
+
else if(s==='recovered_by_manual_validation'){buckets.add('recovered_by_manual_validation');buckets.add('validated_after_manual_space_test');}
|
| 146 |
+
else if(['partial_validation','partial','health_only','full_inference_candidate_health_passed','completed_with_warnings'].includes(s))buckets.add('partial_validation');
|
| 147 |
+
else if(['manual_hardware_required','generated_needs_manual_hardware','waiting_manual_hardware','manual_action_required','waiting_manual_action','requires_action','hardware_required'].includes(s))buckets.add('manual_hardware_required');
|
| 148 |
+
else if(['technical_blocker','technical_blocker_boot_only','blocked','auth_refresh_required','oauth_expired'].includes(s)){buckets.add('technical_blocker');buckets.add('failed');}
|
| 149 |
+
else if(['failed','failure','error','timeout','exception','repair_failed'].includes(s))buckets.add('failed');
|
| 150 |
+
else if(s==='stale')buckets.add('stale');
|
| 151 |
+
else if(['stopped','cancelled','canceled'].includes(s))buckets.add('stopped');
|
| 152 |
+
else buckets.add(s||'unknown');
|
| 153 |
+
return buckets;
|
| 154 |
+
}
|
| 155 |
+
function buildExplorerStatus(run={}){
|
| 156 |
+
run=safeRunObject(run);
|
| 157 |
+
if(isValidationRunSummary(run))return validationExplorerStatus(run);
|
| 158 |
+
const automaticStatus=buildAutomaticStatus(run);
|
| 159 |
+
const validation=linkedValidationOutcome(run);
|
| 160 |
+
const displayStatus=composeBuildDisplayStatus(automaticStatus,validation);
|
| 161 |
+
const label=explorerDisplayLabel(displayStatus);
|
| 162 |
+
const buckets=explorerFilterBuckets(displayStatus);
|
| 163 |
+
const isTerminal=runIsFinished(displayStatus)||(!buckets.has('running')&&displayStatus!=='unknown')||displayStatus==='unknown';
|
| 164 |
+
const tokens=[
|
| 165 |
+
run.run_id,run.model_id,run.target_space,run.kind,run.selected_hardware,run.requested_hardware,run.preferred_space_hardware,
|
| 166 |
+
automaticStatus,displayStatus,label,run.status,run.effective_status,run.summary?.status,run.summary?.effective_status,run.state?.status,
|
| 167 |
+
validation.status,validation.latest?.run_id,validation.latest?.validation_run_id,validation.latest?.hardware_used_for_validation,validation.explicit?.hardware_used_for_validation,
|
| 168 |
+
validation.passed?'linked space test passed validated space test success':'',validation.failed?'linked space test failed validation failed':'',
|
| 169 |
+
validation.linkedCount?'linked validation space test':'',displayStatus==='manual_validated'?'manual gpu manual hardware validated':'',displayStatus==='recovered_by_manual_validation'?'recovered recovery':'',
|
| 170 |
+
];
|
| 171 |
+
return {raw_status:run.status||run.summary?.status||run.state?.status||'unknown',automatic_status:automaticStatus,validation_status:validation.status,validation,display_status:displayStatus,display_label:label,display_tone:statusClass(displayStatus),filter_buckets:buckets,is_terminal:isTerminal,is_deletable:runCanBeDeleted(displayStatus),status_reason:label,search_tokens:tokens.map(x=>String(x||'').toLowerCase()).filter(Boolean)};
|
| 172 |
+
}
|
| 173 |
+
function runMatchesStatus(run,status){
|
| 174 |
+
// v193 legacy audit tokens: const canon=runCanonicalStatus(run); canon==='full_inference_success'; if(status==='partial_validation')return canon==='partial_validation';
|
| 175 |
+
if(!status||status==='all')return true;
|
| 176 |
+
const wanted=normalizeExplorerStatusValue(status);
|
| 177 |
+
const explorer=isValidationRunSummary(run)?validationExplorerStatus(run):buildExplorerStatus(run);
|
| 178 |
+
return explorer.filter_buckets?.has(wanted)||explorer.display_status===wanted||explorer.validation_status===wanted;
|
| 179 |
+
}
|
| 180 |
function canonicalStatusForDeletion(context={}){
|
| 181 |
const kind=context.kind==='validation'?'validation':'active';
|
| 182 |
const detail=context.detail||{};
|
| 183 |
const summary=context.summary||detail.summary||{};
|
| 184 |
const merged={...(summary||{}),...(detail||{}),summary:{...(summary||{}),...(detail.summary||{})},state:{...(summary||{}),...(detail.state||{})}};
|
| 185 |
+
return kind==='validation'?validationExplorerStatus(merged).display_status:buildExplorerStatus(merged).display_status;
|
| 186 |
+
}
|
| 187 |
+
function runMatchesQuery(run,query){
|
| 188 |
+
run=safeRunObject(run);if(!query)return true;
|
| 189 |
+
const explorer=isValidationRunSummary(run)?validationExplorerStatus(run):buildExplorerStatus(run);
|
| 190 |
+
const haystack=[run.run_id,run.model_id,run.target_space,run.status,run.kind,run.selected_hardware,run.requested_hardware,run.preferred_space_hardware,...(explorer.search_tokens||[])].map(x=>String(x||'').toLowerCase()).join(' ');
|
| 191 |
+
return haystack.includes(String(query).toLowerCase());
|
| 192 |
}
|
|
|
|
| 193 |
function runSortTimestamp(run={}){
|
| 194 |
run=safeRunObject(run);
|
| 195 |
const raw=run.updated_at||run.created_at||run.started_at||run.cancelled_at||'';
|
|
|
|
| 213 |
const linked=linkedValidationsForBuild(run.run_id);
|
| 214 |
const matchesLinked=query&&linked.some(v=>runMatchesQuery(v,query));
|
| 215 |
const status=state.runStatusFilter||'all';
|
| 216 |
+
const statusMatch=runMatchesStatus(run,status);
|
| 217 |
return statusMatch&&(matchesOwn||matchesLinked);
|
| 218 |
});
|
| 219 |
}
|
| 220 |
function renderRunStats(){
|
| 221 |
const query=state.runSearch||'';
|
| 222 |
const builds=explorerPrimaryRuns().filter(r=>runMatchesQuery(r,query)||linkedValidationsForBuild(r.run_id).some(v=>runMatchesQuery(v,query)));
|
| 223 |
+
const counts={all:builds.length,running:0,full_inference_success:0,validated_after_manual_space_test:0,recovered_by_manual_validation:0,partial_validation:0,manual_hardware_required:0,technical_blocker:0,failed:0,stale:0};
|
| 224 |
for(const r of builds){
|
| 225 |
+
const explorer=buildExplorerStatus(r);
|
| 226 |
for(const key of Object.keys(counts)){
|
| 227 |
if(key==='all')continue;
|
| 228 |
+
if(explorer.filter_buckets?.has(key))counts[key]+=1;
|
| 229 |
}
|
| 230 |
}
|
| 231 |
document.querySelectorAll('.filter[data-status]').forEach(btn=>{
|
| 232 |
const status=btn.dataset.status||'all';
|
| 233 |
+
const label={all:'Builds',running:'Running',full_inference_success:'Success',validated_after_manual_space_test:'Validated',recovered_by_manual_validation:'Recovered',partial_validation:'Partial',manual_hardware_required:'Manual',failed:'Failed',technical_blocker:'Blockers',stale:'Stale'}[status]||status;
|
| 234 |
const count=counts[status]??0;
|
| 235 |
btn.innerHTML=`${escapeHtml(label)} <span>${count}</span>`;
|
| 236 |
btn.classList.toggle('active',status===state.runStatusFilter);
|
|
|
|
| 238 |
}
|
| 239 |
function runCardKind(run={}){return isValidationRunSummary(run)?'Linked test':'Build'}
|
| 240 |
function runCompactStatusLabel(status){
|
| 241 |
+
// v193 compact labels still include legacy branch semantics: return 'Success' and return 'Failed'.
|
| 242 |
+
return explorerDisplayLabel(status);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
}
|
| 244 |
function runIsFinished(status){
|
| 245 |
const s=String(status||'').toLowerCase();
|
| 246 |
return Boolean(s&&(
|
| 247 |
s.includes('success')||s.includes('succeed')||s.includes('failed')||s.includes('error')||
|
| 248 |
s.includes('manual')||s.includes('blocker')||s.includes('stale')||
|
| 249 |
+
s==='done'||s==='completed'||s==='cancelled'||s==='canceled'||s==='stopped'||s==='health_only'||s==='full_inference_candidate_health_passed'||s==='partial'||s==='partial_validation'||s==='completed_with_warnings'||s==='validated'||s==='validated_after_manual_space_test'||s==='manual_validation_passed'||s==='manual_validated'||s==='recovered_by_manual_validation'||s==='auth_refresh_required'||s==='oauth_expired'
|
| 250 |
));
|
| 251 |
}
|
| 252 |
function runHasValidationResult(detail={},summaryOverride={}){
|
|
|
|
| 309 |
function runCardMeta(run={}){return runSecondaryLine(run)}
|
| 310 |
function renderRunRows(runs,compact=false){
|
| 311 |
return validRunsList(runs||[]).map(r=>{
|
| 312 |
+
// v193 legacy validation token: const status=runCanonicalStatus(r)||r.status||'unknown'
|
| 313 |
const validation=isValidationRunSummary(r);
|
| 314 |
const kind=runCardKind(r);
|
| 315 |
+
const explorerStatus=validation?validationExplorerStatus(r):buildExplorerStatus(r);
|
| 316 |
+
const status=explorerStatus.display_status;
|
|
|
|
|
|
|
| 317 |
const isLoading=state.runSelectionLoadingId&&(state.runSelectionLoadingId===(r.run_id||r.summary?.run_id));
|
| 318 |
+
const tone=explorerStatus.display_tone||statusClass(status);
|
| 319 |
const title=runPrimaryTitle(r);
|
| 320 |
const secondary=runSecondaryLine(r)||r.run_id||'No metadata yet';
|
| 321 |
const time=runRelativeTime(r);
|
|
|
|
| 334 |
<p class="run-list-id" title="${escapeHtml(r.run_id)}">${escapeHtml(r.run_id)}</p>
|
| 335 |
</div>
|
| 336 |
<div class="run-list-status">
|
| 337 |
+
<em class="run-status-pill ${isLoading?'loading':tone}" title="${escapeHtml(isLoading?'Loading details…':explorerStatus.display_label)}">${escapeHtml(isLoading?'Loading…':explorerStatus.display_label)}</em>
|
| 338 |
<time>${escapeHtml(time)}</time>
|
| 339 |
</div>
|
| 340 |
<button class="icon-btn danger run-delete-btn" type="button" data-run-delete="${escapeHtml(r.run_id)}" title="${canDelete?'Delete this finished run':'Delete becomes available after this run finishes'}" aria-label="Delete run ${escapeHtml(r.run_id)}" ${canDelete?'':'disabled aria-disabled="true"'}>×</button>
|
|
|
|
| 444 |
return {passed,failed,linkedCount,latest,explicit,metadataRows,cacheRows};
|
| 445 |
}
|
| 446 |
function buildEffectiveStatus(run={}){
|
| 447 |
+
// Kept for legacy callers, but now delegates to the deterministic Runs Explorer status model.
|
| 448 |
+
return buildExplorerStatus(run).display_status;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
}
|
| 450 |
function explorerPrimaryRuns(){
|
| 451 |
return validRunsList(state.runsCache||[]).filter(r=>!isValidationRunSummary(r));
|
|
|
|
| 489 |
return `<div class="run-stat-cell ${escapeHtml(tone)}"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong>${detail?`<em>${escapeHtml(detail)}</em>`:''}</div>`;
|
| 490 |
}
|
| 491 |
function renderRunAggregateStats(){
|
| 492 |
+
// v193 legacy stats tokens: const terminalBuilds=builds.filter(r=>!runMatchesStatus(r,'running')); buildEffectiveStatus
|
| 493 |
const root=$('runStatsList');
|
| 494 |
if(!root)return;
|
| 495 |
const runs=validRunsList(state.runsCache||[]);
|
| 496 |
const builds=runs.filter(r=>!isValidationRunSummary(r));
|
| 497 |
const validations=runs.filter(r=>isLinkedValidationRun(r));
|
| 498 |
+
const buildStatuses=builds.map(r=>({run:r,status:buildExplorerStatus(r)}));
|
| 499 |
+
const runningBuilds=buildStatuses.filter(x=>x.status.filter_buckets?.has('running')).length;
|
| 500 |
+
const failedBuilds=buildStatuses.filter(x=>x.status.filter_buckets?.has('failed')).length;
|
| 501 |
+
const terminalBuilds=buildStatuses.filter(x=>!x.status.filter_buckets?.has('running'));
|
| 502 |
+
const automaticSuccess=terminalBuilds.filter(x=>x.status.automatic_status==='full_inference_success').length;
|
| 503 |
const successBuilds=automaticSuccess;
|
| 504 |
+
const effectiveSuccess=terminalBuilds.filter(x=>['full_inference_success','validated_after_manual_space_test','manual_validated','recovered_by_manual_validation'].includes(x.status.display_status)).length;
|
| 505 |
+
const recoveredBuilds=terminalBuilds.filter(x=>x.status.display_status==='recovered_by_manual_validation').length;
|
| 506 |
+
const needsValidation=terminalBuilds.filter(x=>runNeedsLinkedValidation(x.run)).length;
|
| 507 |
// Legacy aggregate labels kept for regression tests: Total runs / Test response time / Typical / slowest recent / Build success / Validation pass.
|
| 508 |
// Legacy formula kept for audit readability: pct(successBuilds,terminalBuilds.length) and pct(passedValidations,terminalValidations.length).
|
| 509 |
const passedLinked=validations.filter(r=>!runMatchesStatus(r,'running')&&runMatchesStatus(r,'full_inference_success')).length;
|
|
|
|
| 741 |
};
|
| 742 |
}
|
| 743 |
function explorerStatusFromSnapshot(p={}, cached={}){
|
| 744 |
+
// v193: the coarse Active Run view header must never be the only reason to promote Success. Legacy tokens: smokeStatus==='success' gateStatus==='full_inference_success' p.generation_smoke?.ok===true return explicitSummary||explicitState||viewStatus return 'full_inference_success'
|
| 745 |
+
const merged={
|
| 746 |
+
...(cached||{}),
|
| 747 |
+
...(p.summary||{}),
|
| 748 |
+
status:p.summary?.status||p.state?.status||p.status||cached.status||cached.summary?.status||'unknown',
|
| 749 |
+
summary:{...(cached.summary||{}),...(p.summary||{})},
|
| 750 |
+
state:{...(cached.state||{}),...(p.state||{})},
|
| 751 |
+
inference_gate:p.inference_gate||p.summary?.inference_gate||cached.inference_gate||cached.summary?.inference_gate||{},
|
| 752 |
+
generation_smoke:p.generation_smoke||p.summary?.generation_smoke||cached.generation_smoke||cached.summary?.generation_smoke||{},
|
| 753 |
+
test_result:p.test_result||p.summary?.test_result||cached.test_result||cached.summary?.test_result||{},
|
| 754 |
+
};
|
| 755 |
+
return buildAutomaticStatus(merged);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 756 |
}
|
| 757 |
function upsertRunExplorerFromProgress(p={}){
|
| 758 |
const runId=p.run_id||p.summary?.run_id||state.runId;
|
|
|
|
| 785 |
if(parentId&&isValidationRunSummary(summary)){
|
| 786 |
const parentIdx=state.runsCache.findIndex(r=>(r.run_id||r.summary?.run_id)===parentId);
|
| 787 |
if(parentIdx>=0){
|
| 788 |
+
const passed=validationExplorerStatus(summary).display_status==='full_inference_success';
|
| 789 |
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};
|
| 790 |
}
|
| 791 |
}
|
|
|
|
| 802 |
if(typeof requestSpaceTestContextResetForBuild==='function')requestSpaceTestContextResetForBuild(runId);
|
| 803 |
renderRunDetail(detail);
|
| 804 |
setText('progressTitle',runId);
|
| 805 |
+
const explorer=buildExplorerStatus(cached);
|
| 806 |
+
setText('progressStatus',explorer.display_label||'Loading');
|
| 807 |
const statusEl=$('progressStatus');
|
| 808 |
+
if(statusEl)statusEl.className='badge '+(explorer.display_tone||statusClass(explorer.display_status||''));
|
| 809 |
setText('currentStep','Loading run details…');
|
| 810 |
setText('lastEvent','Fetching latest events and progress…');
|
| 811 |
+
setText('progressPercent',buildExplorerStatus(cached).display_status==='full_inference_success'?'100%':'—');
|
| 812 |
}
|
| 813 |
function cachedRunDetail(runId,heavy=false){
|
| 814 |
const entry=state.runDetailCache?.[runId];
|
|
|
|
| 827 |
|
| 828 |
|
| 829 |
function restoreRunRowStatusLabel(row){
|
| 830 |
+
// v193 legacy loading token: const label=runCompactStatusLabel(status||cached.status||'unknown'); pill.textContent=label
|
| 831 |
if(!row)return;
|
| 832 |
const runId=row.dataset.run;
|
| 833 |
const cached=cachedRunById(runId)||{};
|
| 834 |
+
const explorer=isValidationRunSummary(cached)?validationExplorerStatus(cached):buildExplorerStatus(cached);
|
| 835 |
+
const status=explorer.display_status||cached.status||'unknown';
|
| 836 |
const pill=row.querySelector('.run-status-pill');
|
| 837 |
if(pill){
|
| 838 |
+
pill.textContent=explorer.display_label||runCompactStatusLabel(status);
|
| 839 |
+
pill.title=explorer.display_label||badgeLabel(status);
|
| 840 |
+
const tone=explorer.display_tone||statusClass(status);
|
|
|
|
| 841 |
pill.className=`run-status-pill ${tone}`;
|
| 842 |
pill.removeAttribute('aria-busy');
|
| 843 |
}
|
| 844 |
const dot=row.querySelector('.run-list-dot');
|
| 845 |
+
if(dot)dot.className=`run-list-dot ${explorer.display_tone||statusClass(status)}`;
|
| 846 |
+
}
|
| 847 |
+
function setCenterWorkspaceLoading(runId,loading=true){
|
| 848 |
+
const workspace=document.querySelector('.center-stack');
|
| 849 |
+
if(!workspace)return;
|
| 850 |
+
const active=Boolean(loading&&runId);
|
| 851 |
+
workspace.classList.toggle('is-run-loading',active);
|
| 852 |
+
if(active){
|
| 853 |
+
workspace.setAttribute('aria-busy','true');
|
| 854 |
+
workspace.dataset.loadingRun=runId;
|
| 855 |
+
}else{
|
| 856 |
+
workspace.removeAttribute('aria-busy');
|
| 857 |
+
delete workspace.dataset.loadingRun;
|
| 858 |
+
}
|
| 859 |
}
|
| 860 |
function setRunSelectionLoading(runId,loading=true){
|
| 861 |
const next=loading?runId:null;
|
|
|
|
| 864 |
document.querySelectorAll(`.run-row[data-run="${CSS.escape(previous)}"]`).forEach(restoreRunRowStatusLabel);
|
| 865 |
}
|
| 866 |
if(state.runSelectionLoadingId===next){
|
| 867 |
+
setCenterWorkspaceLoading(next,Boolean(next));
|
| 868 |
if(!loading)document.querySelectorAll('.run-row.is-loading').forEach(row=>{row.classList.remove('is-loading');row.removeAttribute('aria-busy');restoreRunRowStatusLabel(row);});
|
| 869 |
return;
|
| 870 |
}
|
| 871 |
state.runSelectionLoadingId=next;
|
| 872 |
+
setCenterWorkspaceLoading(next,Boolean(next));
|
| 873 |
document.querySelectorAll('.run-row[data-run]').forEach(row=>{
|
| 874 |
const active=Boolean(next)&&row.dataset.run===next;
|
| 875 |
row.classList.toggle('is-loading',active);
|
web/static/style.css
CHANGED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
/* Deprecated placeholder. app.css is the only loaded ASF stylesheet. */
|
web/static/styles.css
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
|
| 2 |
/* v190.20 eval archive owner controls */
|
| 3 |
.eval-archive-actions .eval-flush-button{
|
|
|
|
| 1 |
+
/* Deprecated inactive stylesheet. Kept only for legacy reference; index.html loads app.css. */
|
| 2 |
|
| 3 |
/* v190.20 eval archive owner controls */
|
| 4 |
.eval-archive-actions .eval-flush-button{
|