@microlight/core 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/app/layout.js +2 -18
- package/dist/server/app/library/[[...f_path]]/ViewFolder.js +37 -66
- package/dist/server/app/monitoring/MonitoringDashboard.js +167 -215
- package/dist/server/app/tasks/[slug]/ViewTask.js +81 -173
- package/dist/server/app/tasks/[slug]/runs/[r_id]/ViewRun.js +68 -164
- package/dist/server/app/tasks/[slug]/runs/[r_id]/_components/DropdownActions/DropdownActions.js +19 -25
- package/dist/server/components/Link.js +6 -15
- package/dist/server/components/MLInput.js +19 -22
- package/dist/server/components/Navbar/Navbar.js +14 -51
- package/dist/server/components/Navbar/NavbarContainer.js +7 -20
- package/dist/server/components/PageHeader.js +22 -54
- package/dist/server/components/StatusChip.js +5 -5
- package/dist/server/components/ui/alert.js +61 -0
- package/dist/server/components/ui/badge.js +37 -0
- package/dist/server/components/ui/breadcrumb.js +82 -0
- package/dist/server/components/ui/button.js +65 -0
- package/dist/server/components/ui/card.js +81 -0
- package/dist/server/components/ui/dropdown-menu.js +222 -0
- package/dist/server/components/ui/input.js +21 -0
- package/dist/server/components/ui/label.js +20 -0
- package/dist/server/components/ui/select.js +165 -0
- package/dist/server/components/ui/stack.js +104 -0
- package/dist/server/components/ui/table.js +77 -0
- package/dist/server/components/ui/tabs.js +59 -0
- package/dist/server/components/ui/typography.js +229 -0
- package/dist/server/utils/css/cn.js +11 -0
- package/package.json +15 -3
- package/dist/server/components/Icon.js +0 -22
|
@@ -2,35 +2,19 @@ import { Inter } from "next/font/google";
|
|
|
2
2
|
const inter = Inter({
|
|
3
3
|
subsets: ["latin"]
|
|
4
4
|
});
|
|
5
|
+
import "./globals.css";
|
|
5
6
|
import TopLoader from "../components/TopLoader";
|
|
6
7
|
// import ServiceWorkerRegistration from "@/components/ServiceWorkerRegistration";
|
|
7
8
|
import NavbarContainer from "../components/Navbar/NavbarContainer";
|
|
8
9
|
export const metadata = {
|
|
9
10
|
title: "Microlight",
|
|
10
11
|
description: "Simple single server task runner"
|
|
11
|
-
// icons: {
|
|
12
|
-
// icon: [
|
|
13
|
-
// { url: '/favicon.ico' },
|
|
14
|
-
// { url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
|
|
15
|
-
// { url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
|
|
16
|
-
// { url: '/favicon-48x48.png', sizes: '48x48', type: 'image/png' },
|
|
17
|
-
// ],
|
|
18
|
-
// apple: [
|
|
19
|
-
// { url: '/apple-touch-icon.png' },
|
|
20
|
-
// ],
|
|
21
|
-
// },
|
|
22
12
|
};
|
|
23
13
|
export default function RootLayout({
|
|
24
14
|
children
|
|
25
15
|
}) {
|
|
26
16
|
return <html lang="en">
|
|
27
|
-
<
|
|
28
|
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
|
|
29
|
-
</head>
|
|
30
|
-
<body className={inter.className} style={{
|
|
31
|
-
margin: 0,
|
|
32
|
-
padding: 0
|
|
33
|
-
}}>
|
|
17
|
+
<body className={`${inter.className} m-0 p-0`}>
|
|
34
18
|
{/* <ServiceWorkerRegistration /> */}
|
|
35
19
|
<TopLoader />
|
|
36
20
|
<NavbarContainer>
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Table, Box, Container, Typography } from '@mui/joy';
|
|
4
3
|
import React from 'react';
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
// import Link from '@/components/Link';
|
|
8
|
-
|
|
9
|
-
import { Link } from 'switchless';
|
|
4
|
+
import { Folder, Send } from 'lucide-react';
|
|
5
|
+
import Link from "../../../components/Link";
|
|
10
6
|
import PageHeader from "../../../components/PageHeader";
|
|
7
|
+
import { Typography } from "../../../components/ui/typography";
|
|
8
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../../components/ui/table";
|
|
11
9
|
function generateBreadcrumbs({
|
|
12
10
|
params
|
|
13
11
|
}) {
|
|
@@ -47,67 +45,40 @@ export default function ViewFolder({
|
|
|
47
45
|
params
|
|
48
46
|
});
|
|
49
47
|
const dir = params.f_path ? '/' + params.f_path?.join('/') : '';
|
|
50
|
-
return
|
|
51
|
-
<Container>
|
|
48
|
+
return <div className="max-w-7xl mx-auto">
|
|
52
49
|
<PageHeader breadcrumbs={breadcrumbs} header={{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
part1: 'Folder:',
|
|
51
|
+
part2: folder.name
|
|
52
|
+
}} />
|
|
56
53
|
<Typography level='body-sm'>{folder.description}</Typography>
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<Link href={'/library' + '/' + content.slug}>{content.name}</Link>
|
|
83
|
-
</Box>
|
|
84
|
-
</>}
|
|
85
|
-
{content.type == 'task' && <>
|
|
86
|
-
<Box sx={{
|
|
87
|
-
display: 'flex',
|
|
88
|
-
alignItems: 'center',
|
|
89
|
-
gap: 1
|
|
90
|
-
}}>
|
|
91
|
-
{/* <Icon icon='send' color='#6435c9'/> */}
|
|
92
|
-
<i class="fa-solid fa-paper-plane fa-xl" style={{
|
|
93
|
-
color: '#6435c9'
|
|
94
|
-
}}></i>
|
|
95
|
-
<Link href={'/tasks/' + content.slug}>{content.name}</Link>
|
|
96
|
-
</Box>
|
|
97
|
-
</>}
|
|
98
|
-
</td>
|
|
99
|
-
<td>{content.description}</td>
|
|
100
|
-
<td></td>
|
|
101
|
-
</tr>
|
|
102
|
-
</React.Fragment>;
|
|
54
|
+
<div className="mt-2">
|
|
55
|
+
<Table>
|
|
56
|
+
<TableHeader>
|
|
57
|
+
<TableRow>
|
|
58
|
+
<TableHead>Name</TableHead>
|
|
59
|
+
<TableHead>Description</TableHead>
|
|
60
|
+
<TableHead>Tasks</TableHead>
|
|
61
|
+
</TableRow>
|
|
62
|
+
</TableHeader>
|
|
63
|
+
<TableBody>
|
|
64
|
+
{contents.map(content => {
|
|
65
|
+
return <TableRow key={`${content.type}__${content.slug}`}>
|
|
66
|
+
<TableCell>
|
|
67
|
+
{content.type == 'folder' && <div className="flex items-center gap-2">
|
|
68
|
+
<Folder className="h-5 w-5 text-muted-foreground" />
|
|
69
|
+
<Link href={'/library' + '/' + content.slug}>{content.name}</Link>
|
|
70
|
+
</div>}
|
|
71
|
+
{content.type == 'task' && <div className="flex items-center gap-2">
|
|
72
|
+
<Send className="h-5 w-5 text-[#6435c9]" />
|
|
73
|
+
<Link href={'/tasks/' + content.slug}>{content.name}</Link>
|
|
74
|
+
</div>}
|
|
75
|
+
</TableCell>
|
|
76
|
+
<TableCell>{content.description}</TableCell>
|
|
77
|
+
<TableCell></TableCell>
|
|
78
|
+
</TableRow>;
|
|
103
79
|
})}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{/* <pre>{JSON.stringify(folder,null,2)}</pre> */}
|
|
110
|
-
{/* <pre>{JSON.stringify(contents,null,2)}</pre> */}
|
|
111
|
-
</Container>
|
|
112
|
-
</>;
|
|
80
|
+
</TableBody>
|
|
81
|
+
</Table>
|
|
82
|
+
</div>
|
|
83
|
+
</div>;
|
|
113
84
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import RefreshIcon from '@mui/icons-material/Refresh';
|
|
6
|
-
import SearchIcon from '@mui/icons-material/Search';
|
|
7
|
-
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
|
8
|
-
import StopIcon from '@mui/icons-material/Stop';
|
|
9
|
-
import ErrorIcon from '@mui/icons-material/Error';
|
|
10
|
-
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
4
|
+
import { RefreshCw, Search, Play, Square, AlertCircle, CheckCircle } from 'lucide-react';
|
|
11
5
|
import { getOverviewData, getTasksData, getRunsData, getLogsData } from "./action";
|
|
6
|
+
import { Typography } from "../../components/ui/typography";
|
|
7
|
+
import { Card, CardContent } from "../../components/ui/card";
|
|
8
|
+
import { Button } from "../../components/ui/button";
|
|
9
|
+
import { Input } from "../../components/ui/input";
|
|
10
|
+
import { Badge } from "../../components/ui/badge";
|
|
11
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/ui/select";
|
|
12
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table";
|
|
12
13
|
export default function MonitoringDashboard() {
|
|
13
14
|
const [overview, setOverview] = useState(null);
|
|
14
15
|
const [tasks, setTasks] = useState([]);
|
|
@@ -80,32 +81,32 @@ export default function MonitoringDashboard() {
|
|
|
80
81
|
if (interval) clearInterval(interval);
|
|
81
82
|
};
|
|
82
83
|
}, [autoRefresh]);
|
|
83
|
-
const
|
|
84
|
+
const getStatusVariant = status => {
|
|
84
85
|
switch (status) {
|
|
85
86
|
case 'complete':
|
|
86
87
|
return 'success';
|
|
87
88
|
case 'failed':
|
|
88
|
-
return '
|
|
89
|
+
return 'destructive';
|
|
89
90
|
case 'running':
|
|
90
|
-
return '
|
|
91
|
+
return 'default';
|
|
91
92
|
case 'pending':
|
|
92
|
-
return '
|
|
93
|
+
return 'secondary';
|
|
93
94
|
default:
|
|
94
|
-
return '
|
|
95
|
+
return 'secondary';
|
|
95
96
|
}
|
|
96
97
|
};
|
|
97
98
|
const getStatusIcon = status => {
|
|
98
99
|
switch (status) {
|
|
99
100
|
case 'complete':
|
|
100
|
-
return <
|
|
101
|
+
return <CheckCircle className="h-3 w-3" />;
|
|
101
102
|
case 'failed':
|
|
102
|
-
return <
|
|
103
|
+
return <AlertCircle className="h-3 w-3" />;
|
|
103
104
|
case 'running':
|
|
104
|
-
return <
|
|
105
|
+
return <Play className="h-3 w-3" />;
|
|
105
106
|
case 'pending':
|
|
106
|
-
return <
|
|
107
|
+
return <Square className="h-3 w-3" />;
|
|
107
108
|
default:
|
|
108
|
-
return <
|
|
109
|
+
return <Square className="h-3 w-3" />;
|
|
109
110
|
}
|
|
110
111
|
};
|
|
111
112
|
const formatDuration = duration => {
|
|
@@ -122,256 +123,207 @@ export default function MonitoringDashboard() {
|
|
|
122
123
|
return new Date(dateString).toLocaleString();
|
|
123
124
|
};
|
|
124
125
|
if (loading && !overview) {
|
|
125
|
-
return <
|
|
126
|
-
p: 3
|
|
127
|
-
}}>
|
|
126
|
+
return <div className="p-6">
|
|
128
127
|
<Typography level="h2">Loading...</Typography>
|
|
129
|
-
</
|
|
128
|
+
</div>;
|
|
130
129
|
}
|
|
131
|
-
return <
|
|
132
|
-
p: 3
|
|
133
|
-
}}>
|
|
130
|
+
return <div className="p-6">
|
|
134
131
|
{/* Header */}
|
|
135
|
-
<
|
|
136
|
-
mb: 3,
|
|
137
|
-
display: 'flex',
|
|
138
|
-
justifyContent: 'space-between',
|
|
139
|
-
alignItems: 'center'
|
|
140
|
-
}}>
|
|
132
|
+
<div className="mb-6 flex justify-between items-center">
|
|
141
133
|
<Typography level="h2">Task Monitoring Dashboard</Typography>
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
gap: 1,
|
|
145
|
-
alignItems: 'center'
|
|
146
|
-
}}>
|
|
147
|
-
<Button variant={autoRefresh ? 'solid' : 'outlined'} color={autoRefresh ? 'primary' : 'neutral'} onClick={() => setAutoRefresh(!autoRefresh)} size="sm">
|
|
134
|
+
<div className="flex gap-2 items-center">
|
|
135
|
+
<Button variant={autoRefresh ? 'default' : 'outline'} onClick={() => setAutoRefresh(!autoRefresh)} size="sm">
|
|
148
136
|
{autoRefresh ? 'Auto-refresh ON' : 'Auto-refresh OFF'}
|
|
149
137
|
</Button>
|
|
150
|
-
<
|
|
151
|
-
<
|
|
152
|
-
</
|
|
153
|
-
</
|
|
154
|
-
</
|
|
138
|
+
<Button variant="outline" onClick={refreshAll} loading={loading} size="icon">
|
|
139
|
+
<RefreshCw className="h-4 w-4" />
|
|
140
|
+
</Button>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
155
143
|
|
|
156
144
|
{/* Overview Cards */}
|
|
157
|
-
{overview && <
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
</
|
|
176
|
-
</
|
|
177
|
-
</
|
|
178
|
-
<
|
|
179
|
-
<
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
</Grid>
|
|
186
|
-
<Grid xs={12} sm={6} md={3}>
|
|
187
|
-
<Card>
|
|
188
|
-
<CardContent>
|
|
189
|
-
<Typography level="title-sm" color="danger">Failed (24h)</Typography>
|
|
190
|
-
<Typography level="h2" color="danger">{overview.recentFailures || 0}</Typography>
|
|
191
|
-
</CardContent>
|
|
192
|
-
</Card>
|
|
193
|
-
</Grid>
|
|
194
|
-
</Grid>}
|
|
145
|
+
{overview && <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
|
146
|
+
<Card>
|
|
147
|
+
<CardContent className="pt-4">
|
|
148
|
+
<Typography level="title-sm" color="muted">Total Runs</Typography>
|
|
149
|
+
<Typography level="h2">{overview.totalRuns?.toLocaleString()}</Typography>
|
|
150
|
+
</CardContent>
|
|
151
|
+
</Card>
|
|
152
|
+
<Card>
|
|
153
|
+
<CardContent className="pt-4">
|
|
154
|
+
<Typography level="title-sm" color="success">Success Rate</Typography>
|
|
155
|
+
<Typography level="h2" color="success">
|
|
156
|
+
{overview.successRate ? `${overview.successRate.toFixed(1)}%` : 'N/A'}
|
|
157
|
+
</Typography>
|
|
158
|
+
</CardContent>
|
|
159
|
+
</Card>
|
|
160
|
+
<Card>
|
|
161
|
+
<CardContent className="pt-4">
|
|
162
|
+
<Typography level="title-sm" color="primary">Running Tasks</Typography>
|
|
163
|
+
<Typography level="h2" color="primary">{overview.runningTasks || 0}</Typography>
|
|
164
|
+
</CardContent>
|
|
165
|
+
</Card>
|
|
166
|
+
<Card>
|
|
167
|
+
<CardContent className="pt-4">
|
|
168
|
+
<Typography level="title-sm" color="danger">Failed (24h)</Typography>
|
|
169
|
+
<Typography level="h2" color="danger">{overview.recentFailures || 0}</Typography>
|
|
170
|
+
</CardContent>
|
|
171
|
+
</Card>
|
|
172
|
+
</div>}
|
|
195
173
|
|
|
196
174
|
{/* Filters */}
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
display: 'flex',
|
|
200
|
-
gap: 2,
|
|
201
|
-
alignItems: 'center',
|
|
202
|
-
flexWrap: 'wrap'
|
|
203
|
-
}}>
|
|
204
|
-
<Select value={filters.status} onChange={(_, value) => setFilters(prev => ({
|
|
175
|
+
<div className="mb-6 flex gap-4 items-center flex-wrap">
|
|
176
|
+
<Select value={filters.status} onValueChange={value => setFilters(prev => ({
|
|
205
177
|
...prev,
|
|
206
178
|
status: value
|
|
207
|
-
}))}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
<
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
179
|
+
}))}>
|
|
180
|
+
<SelectTrigger size="sm" className="w-[140px]">
|
|
181
|
+
<SelectValue />
|
|
182
|
+
</SelectTrigger>
|
|
183
|
+
<SelectContent>
|
|
184
|
+
<SelectItem value="all">All Status</SelectItem>
|
|
185
|
+
<SelectItem value="complete">Complete</SelectItem>
|
|
186
|
+
<SelectItem value="failed">Failed</SelectItem>
|
|
187
|
+
<SelectItem value="running">Running</SelectItem>
|
|
188
|
+
<SelectItem value="pending">Pending</SelectItem>
|
|
189
|
+
</SelectContent>
|
|
215
190
|
</Select>
|
|
216
191
|
|
|
217
|
-
<
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
192
|
+
<div className="relative">
|
|
193
|
+
<Search className="absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
194
|
+
<Input placeholder="Filter by task name..." value={filters.task} onChange={e => setFilters(prev => ({
|
|
195
|
+
...prev,
|
|
196
|
+
task: e.target.value
|
|
197
|
+
}))} size="sm" className="pl-8 w-[200px]" />
|
|
198
|
+
</div>
|
|
223
199
|
|
|
224
|
-
<Select value={filters.dateRange}
|
|
200
|
+
<Select value={filters.dateRange} onValueChange={value => setFilters(prev => ({
|
|
225
201
|
...prev,
|
|
226
202
|
dateRange: value
|
|
227
|
-
}))}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
203
|
+
}))}>
|
|
204
|
+
<SelectTrigger size="sm" className="w-[120px]">
|
|
205
|
+
<SelectValue />
|
|
206
|
+
</SelectTrigger>
|
|
207
|
+
<SelectContent>
|
|
208
|
+
<SelectItem value="1h">Last Hour</SelectItem>
|
|
209
|
+
<SelectItem value="24h">Last 24h</SelectItem>
|
|
210
|
+
<SelectItem value="7d">Last 7 days</SelectItem>
|
|
211
|
+
<SelectItem value="30d">Last 30 days</SelectItem>
|
|
212
|
+
</SelectContent>
|
|
234
213
|
</Select>
|
|
235
|
-
</
|
|
214
|
+
</div>
|
|
236
215
|
|
|
237
216
|
{/* Task Statistics Table */}
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
<Typography level="h3" sx={{
|
|
242
|
-
mb: 2
|
|
243
|
-
}}>Task Statistics</Typography>
|
|
244
|
-
<Sheet sx={{
|
|
245
|
-
borderRadius: 'sm',
|
|
246
|
-
overflow: 'auto'
|
|
247
|
-
}}>
|
|
217
|
+
<div className="mb-8">
|
|
218
|
+
<Typography level="h3" className="mb-4">Task Statistics</Typography>
|
|
219
|
+
<div className="rounded-md border overflow-auto">
|
|
248
220
|
<Table>
|
|
249
|
-
<
|
|
250
|
-
<
|
|
251
|
-
<
|
|
252
|
-
<
|
|
253
|
-
<
|
|
254
|
-
<
|
|
255
|
-
<
|
|
256
|
-
<
|
|
257
|
-
</
|
|
258
|
-
</
|
|
259
|
-
<
|
|
260
|
-
{tasks.map(task => <
|
|
261
|
-
<
|
|
262
|
-
<Typography level="body-sm"
|
|
221
|
+
<TableHeader>
|
|
222
|
+
<TableRow>
|
|
223
|
+
<TableHead>Task Name</TableHead>
|
|
224
|
+
<TableHead>Total Runs</TableHead>
|
|
225
|
+
<TableHead>Success Rate</TableHead>
|
|
226
|
+
<TableHead>Last Run</TableHead>
|
|
227
|
+
<TableHead>Status</TableHead>
|
|
228
|
+
<TableHead>Avg Duration</TableHead>
|
|
229
|
+
</TableRow>
|
|
230
|
+
</TableHeader>
|
|
231
|
+
<TableBody>
|
|
232
|
+
{tasks.map(task => <TableRow key={task.task}>
|
|
233
|
+
<TableCell>
|
|
234
|
+
<Typography level="body-sm" weight="medium">
|
|
263
235
|
{task.task}
|
|
264
236
|
</Typography>
|
|
265
|
-
</
|
|
266
|
-
<
|
|
267
|
-
<
|
|
237
|
+
</TableCell>
|
|
238
|
+
<TableCell>{task.totalRuns}</TableCell>
|
|
239
|
+
<TableCell>
|
|
268
240
|
<Typography level="body-sm" color={task.successRate > 90 ? 'success' : task.successRate > 70 ? 'warning' : 'danger'}>
|
|
269
241
|
{task.successRate.toFixed(1)}%
|
|
270
242
|
</Typography>
|
|
271
|
-
</
|
|
272
|
-
<
|
|
243
|
+
</TableCell>
|
|
244
|
+
<TableCell>
|
|
273
245
|
<Typography level="body-sm">
|
|
274
246
|
{formatDate(task.lastRun)}
|
|
275
247
|
</Typography>
|
|
276
|
-
</
|
|
277
|
-
<
|
|
278
|
-
<
|
|
248
|
+
</TableCell>
|
|
249
|
+
<TableCell>
|
|
250
|
+
<Badge variant={getStatusVariant(task.lastStatus)} className="gap-1">
|
|
251
|
+
{getStatusIcon(task.lastStatus)}
|
|
279
252
|
{task.lastStatus}
|
|
280
|
-
</
|
|
281
|
-
</
|
|
282
|
-
<
|
|
253
|
+
</Badge>
|
|
254
|
+
</TableCell>
|
|
255
|
+
<TableCell>
|
|
283
256
|
<Typography level="body-sm">
|
|
284
257
|
{formatDuration(task.avgDuration)}
|
|
285
258
|
</Typography>
|
|
286
|
-
</
|
|
287
|
-
</
|
|
288
|
-
</
|
|
259
|
+
</TableCell>
|
|
260
|
+
</TableRow>)}
|
|
261
|
+
</TableBody>
|
|
289
262
|
</Table>
|
|
290
|
-
</
|
|
291
|
-
</
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
292
265
|
|
|
293
266
|
{/* Recent Runs */}
|
|
294
|
-
<
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
<Typography level="h3" sx={{
|
|
298
|
-
mb: 2
|
|
299
|
-
}}>Recent Runs</Typography>
|
|
300
|
-
<Sheet sx={{
|
|
301
|
-
borderRadius: 'sm',
|
|
302
|
-
overflow: 'auto',
|
|
303
|
-
maxHeight: 400
|
|
304
|
-
}}>
|
|
267
|
+
<div className="mb-8">
|
|
268
|
+
<Typography level="h3" className="mb-4">Recent Runs</Typography>
|
|
269
|
+
<div className="rounded-md border overflow-auto max-h-[400px]">
|
|
305
270
|
<Table>
|
|
306
|
-
<
|
|
307
|
-
<
|
|
308
|
-
<
|
|
309
|
-
<
|
|
310
|
-
<
|
|
311
|
-
<
|
|
312
|
-
<
|
|
313
|
-
</
|
|
314
|
-
</
|
|
315
|
-
<
|
|
316
|
-
{recentRuns.map(run => <
|
|
317
|
-
<
|
|
318
|
-
<Typography level="body-sm"
|
|
271
|
+
<TableHeader>
|
|
272
|
+
<TableRow>
|
|
273
|
+
<TableHead>Task</TableHead>
|
|
274
|
+
<TableHead>Status</TableHead>
|
|
275
|
+
<TableHead>Started</TableHead>
|
|
276
|
+
<TableHead>Duration</TableHead>
|
|
277
|
+
<TableHead>Triggered By</TableHead>
|
|
278
|
+
</TableRow>
|
|
279
|
+
</TableHeader>
|
|
280
|
+
<TableBody>
|
|
281
|
+
{recentRuns.map(run => <TableRow key={run.id}>
|
|
282
|
+
<TableCell>
|
|
283
|
+
<Typography level="body-sm" weight="medium">
|
|
319
284
|
{run.task}
|
|
320
285
|
</Typography>
|
|
321
|
-
</
|
|
322
|
-
<
|
|
323
|
-
<
|
|
286
|
+
</TableCell>
|
|
287
|
+
<TableCell>
|
|
288
|
+
<Badge variant={getStatusVariant(run.status)} className="gap-1">
|
|
289
|
+
{getStatusIcon(run.status)}
|
|
324
290
|
{run.status}
|
|
325
|
-
</
|
|
326
|
-
</
|
|
327
|
-
<
|
|
291
|
+
</Badge>
|
|
292
|
+
</TableCell>
|
|
293
|
+
<TableCell>
|
|
328
294
|
<Typography level="body-sm">
|
|
329
295
|
{formatDate(run.started_at)}
|
|
330
296
|
</Typography>
|
|
331
|
-
</
|
|
332
|
-
<
|
|
297
|
+
</TableCell>
|
|
298
|
+
<TableCell>
|
|
333
299
|
<Typography level="body-sm">
|
|
334
300
|
{formatDuration(run.duration)}
|
|
335
301
|
</Typography>
|
|
336
|
-
</
|
|
337
|
-
<
|
|
338
|
-
<
|
|
302
|
+
</TableCell>
|
|
303
|
+
<TableCell>
|
|
304
|
+
<Badge variant="outline">
|
|
339
305
|
{run.triggered_by}
|
|
340
|
-
</
|
|
341
|
-
</
|
|
342
|
-
</
|
|
343
|
-
</
|
|
306
|
+
</Badge>
|
|
307
|
+
</TableCell>
|
|
308
|
+
</TableRow>)}
|
|
309
|
+
</TableBody>
|
|
344
310
|
</Table>
|
|
345
|
-
</
|
|
346
|
-
</
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
347
313
|
|
|
348
314
|
{/* Recent Logs */}
|
|
349
|
-
<
|
|
350
|
-
<Typography level="h3"
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
borderRadius: 'sm',
|
|
355
|
-
p: 2,
|
|
356
|
-
maxHeight: 300,
|
|
357
|
-
overflow: 'auto',
|
|
358
|
-
bgcolor: 'neutral.50'
|
|
359
|
-
}}>
|
|
360
|
-
{logs.map(log => <Box key={log.id} sx={{
|
|
361
|
-
mb: 1,
|
|
362
|
-
fontFamily: 'monospace',
|
|
363
|
-
fontSize: 'sm'
|
|
364
|
-
}}>
|
|
365
|
-
<Typography level="body-xs" color="neutral">
|
|
315
|
+
<div>
|
|
316
|
+
<Typography level="h3" className="mb-4">Recent Logs</Typography>
|
|
317
|
+
<div className="rounded-md border p-4 max-h-[300px] overflow-auto bg-muted/30">
|
|
318
|
+
{logs.map(log => <div key={log.id} className="mb-2 font-mono text-sm">
|
|
319
|
+
<Typography level="body-xs" color="muted">
|
|
366
320
|
[{formatDate(log.created_at)}] [{log.type}]
|
|
367
321
|
</Typography>
|
|
368
|
-
<Typography level="body-sm"
|
|
369
|
-
whiteSpace: 'pre-wrap'
|
|
370
|
-
}}>
|
|
322
|
+
<Typography level="body-sm" className="whitespace-pre-wrap">
|
|
371
323
|
{log.content}
|
|
372
324
|
</Typography>
|
|
373
|
-
</
|
|
374
|
-
</
|
|
375
|
-
</
|
|
376
|
-
</
|
|
325
|
+
</div>)}
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
</div>;
|
|
377
329
|
}
|