@@ -10,10 +10,102 @@ import { TabView } from "../components/TabView";
1010import { Modal } from "../components/Modal" ;
1111import "../styles/page.css" ;
1212
13+ type WorkflowDiagnostics = WorkspaceWorkflowSummary [ "diagnostics" ] ;
14+
15+ const formatDateTime = ( value ?: string | null , fallback = "-" ) => {
16+ if ( ! value ) {
17+ return fallback ;
18+ }
19+ const date = new Date ( value ) ;
20+ if ( Number . isNaN ( date . getTime ( ) ) ) {
21+ return fallback ;
22+ }
23+ return date . toLocaleString ( ) ;
24+ } ;
25+
26+ const formatDuration = ( durationMs ?: number | null ) => {
27+ if ( typeof durationMs !== "number" || Number . isNaN ( durationMs ) ) {
28+ return "-" ;
29+ }
30+ if ( durationMs < 1000 ) {
31+ return `${ durationMs } ms` ;
32+ }
33+ const seconds = durationMs / 1000 ;
34+ if ( seconds < 60 ) {
35+ return `${ seconds . toFixed ( seconds >= 10 ? 0 : 1 ) } s` ;
36+ }
37+ const minutes = Math . floor ( seconds / 60 ) ;
38+ const remainingSeconds = Math . round ( seconds % 60 ) ;
39+ return `${ minutes } m ${ remainingSeconds } s` ;
40+ } ;
41+
42+ const formatExitCode = ( value ?: number | null ) => {
43+ if ( typeof value !== "number" || Number . isNaN ( value ) ) {
44+ return "-" ;
45+ }
46+ return String ( value ) ;
47+ } ;
48+
49+ const formatErrorText = ( value ?: string | null ) => {
50+ if ( ! value || ! value . trim ( ) ) {
51+ return "-" ;
52+ }
53+ const normalized = value . trim ( ) ;
54+ if ( normalized . length <= 80 ) {
55+ return normalized ;
56+ }
57+ return `${ normalized . slice ( 0 , 77 ) } ...` ;
58+ } ;
59+
60+ const formatWorkflowLastRun = ( workflow : WorkspaceWorkflowSummary ) => {
61+ if ( ! workflow . schedule || ! workflow . schedule . trim ( ) ) {
62+ return "n.a." ;
63+ }
64+
65+ if ( ! workflow . lastRun ) {
66+ return "-" ;
67+ }
68+
69+ return formatDateTime ( workflow . lastRun ) ;
70+ } ;
71+
72+ const renderWorkflowDiagnosticsSummary = ( diagnostics : WorkflowDiagnostics ) => {
73+ if ( ! diagnostics ) {
74+ return < span className = "meta-secondary" > No diagnostics yet</ span > ;
75+ }
76+
77+ return (
78+ < details className = "workflow-diagnostics" >
79+ < summary className = "workflow-diagnostics__summary" >
80+ < span
81+ className = { `badge ${ diagnostics . running ? "success" : "" } ` . trim ( ) }
82+ >
83+ { diagnostics . running ? "Running" : "Idle" }
84+ </ span >
85+ < span className = "badge" >
86+ Exit { formatExitCode ( diagnostics . lastExitCode ) }
87+ </ span >
88+ < span className = "badge" >
89+ { formatDuration ( diagnostics . lastDurationMs ) }
90+ </ span >
91+ </ summary >
92+ < div className = "meta-secondary" >
93+ < span > Started: { formatDateTime ( diagnostics . lastStartedAt ) } </ span >
94+ < span > Finished: { formatDateTime ( diagnostics . lastFinishedAt ) } </ span >
95+ </ div >
96+ < div className = "meta-secondary" >
97+ Error: { formatErrorText ( diagnostics . lastError ) }
98+ </ div >
99+ </ details >
100+ ) ;
101+ } ;
102+
13103export const TasksPage : React . FC = ( ) => {
14104 const [ tasks , setTasks ] = useState < ArtefactSummary [ ] > ( [ ] ) ;
15105 const [ activeTab , setActiveTab ] = useState ( "tasks" ) ;
16- const [ workspaceWorkflows , setWorkspaceWorkflows ] = useState < WorkspaceWorkflowSummary [ ] > ( [ ] ) ;
106+ const [ workspaceWorkflows , setWorkspaceWorkflows ] = useState <
107+ WorkspaceWorkflowSummary [ ]
108+ > ( [ ] ) ;
17109 const [ createOpen , setCreateOpen ] = useState ( false ) ;
18110 const [ newName , setNewName ] = useState ( "" ) ;
19111 const navigate = useNavigate ( ) ;
@@ -22,29 +114,12 @@ export const TasksPage: React.FC = () => {
22114 return task . type === "template" ;
23115 }
24116 const frontmatterType = task . frontmatter ?. type ;
25- return typeof frontmatterType === "string" && frontmatterType === "template" ;
117+ return (
118+ typeof frontmatterType === "string" && frontmatterType === "template"
119+ ) ;
26120 } ;
27121 const templateTasks = tasks . filter ( isTemplate ) ;
28- const documentTasks = tasks . filter (
29- ( task ) => ! isTemplate ( task ) ,
30- ) ;
31- const formatWorkflowLastRun = ( workflow : WorkspaceWorkflowSummary ) => {
32- if ( ! workflow . schedule || ! workflow . schedule . trim ( ) ) {
33- return "n.a." ;
34- }
35-
36- if ( ! workflow . lastRun ) {
37- return "-" ;
38- }
39-
40- const lastRunDate = new Date ( workflow . lastRun ) ;
41- if ( Number . isNaN ( lastRunDate . getTime ( ) ) ) {
42- return "-" ;
43- }
44-
45- return lastRunDate . toLocaleString ( ) ;
46- } ;
47-
122+ const documentTasks = tasks . filter ( ( task ) => ! isTemplate ( task ) ) ;
48123 const getRepositoryName = ( repository : string ) => {
49124 const segments = repository . split ( / [ \\ / ] / ) . filter ( Boolean ) ;
50125 return segments [ segments . length - 1 ] || repository ;
@@ -95,7 +170,8 @@ export const TasksPage: React.FC = () => {
95170 < Panel title = "Workflows" >
96171 { workspaceWorkflows . length === 0 ? (
97172 < div className = "empty" >
98- No workflows found in repository .made/workflows.yml files.
173+ No workflows found in repository .made/workflows.yml
174+ files.
99175 </ div >
100176 ) : (
101177 < table className = "git-table" >
@@ -106,11 +182,14 @@ export const TasksPage: React.FC = () => {
106182 < th > Name</ th >
107183 < th > Repository</ th >
108184 < th > Last run</ th >
185+ < th > Diagnostics</ th >
109186 </ tr >
110187 </ thead >
111188 < tbody >
112189 { workspaceWorkflows . map ( ( workflow ) => {
113- const repositoryName = getRepositoryName ( workflow . repository ) ;
190+ const repositoryName = getRepositoryName (
191+ workflow . repository ,
192+ ) ;
114193 return (
115194 < tr key = { `${ workflow . repository } :${ workflow . id } ` } >
116195 < td >
@@ -131,6 +210,11 @@ export const TasksPage: React.FC = () => {
131210 </ td >
132211 < td > { repositoryName } </ td >
133212 < td > { formatWorkflowLastRun ( workflow ) } </ td >
213+ < td >
214+ { renderWorkflowDiagnosticsSummary (
215+ workflow . diagnostics ,
216+ ) }
217+ </ td >
134218 </ tr >
135219 ) ;
136220 } ) }
@@ -164,8 +248,7 @@ export const TasksPage: React.FC = () => {
164248 { String ( task . frontmatter . schedule ) }
165249 </ span >
166250 ) }
167- { typeof task . frontmatter ?. type ===
168- "string" && (
251+ { typeof task . frontmatter ?. type === "string" && (
169252 < span className = "badge" >
170253 { String ( task . frontmatter . type ) }
171254 </ span >
@@ -192,8 +275,7 @@ export const TasksPage: React.FC = () => {
192275 { String ( task . frontmatter . schedule ) }
193276 </ span >
194277 ) }
195- { typeof task . frontmatter ?. type ===
196- "string" && (
278+ { typeof task . frontmatter ?. type === "string" && (
197279 < span className = "badge" >
198280 { String ( task . frontmatter . type ) }
199281 </ span >
0 commit comments