@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.
Files changed (28) hide show
  1. package/dist/server/app/layout.js +2 -18
  2. package/dist/server/app/library/[[...f_path]]/ViewFolder.js +37 -66
  3. package/dist/server/app/monitoring/MonitoringDashboard.js +167 -215
  4. package/dist/server/app/tasks/[slug]/ViewTask.js +81 -173
  5. package/dist/server/app/tasks/[slug]/runs/[r_id]/ViewRun.js +68 -164
  6. package/dist/server/app/tasks/[slug]/runs/[r_id]/_components/DropdownActions/DropdownActions.js +19 -25
  7. package/dist/server/components/Link.js +6 -15
  8. package/dist/server/components/MLInput.js +19 -22
  9. package/dist/server/components/Navbar/Navbar.js +14 -51
  10. package/dist/server/components/Navbar/NavbarContainer.js +7 -20
  11. package/dist/server/components/PageHeader.js +22 -54
  12. package/dist/server/components/StatusChip.js +5 -5
  13. package/dist/server/components/ui/alert.js +61 -0
  14. package/dist/server/components/ui/badge.js +37 -0
  15. package/dist/server/components/ui/breadcrumb.js +82 -0
  16. package/dist/server/components/ui/button.js +65 -0
  17. package/dist/server/components/ui/card.js +81 -0
  18. package/dist/server/components/ui/dropdown-menu.js +222 -0
  19. package/dist/server/components/ui/input.js +21 -0
  20. package/dist/server/components/ui/label.js +20 -0
  21. package/dist/server/components/ui/select.js +165 -0
  22. package/dist/server/components/ui/stack.js +104 -0
  23. package/dist/server/components/ui/table.js +77 -0
  24. package/dist/server/components/ui/tabs.js +59 -0
  25. package/dist/server/components/ui/typography.js +229 -0
  26. package/dist/server/utils/css/cn.js +11 -0
  27. package/package.json +15 -3
  28. 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
- <head>
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 Icon from "../../../components/Icon";
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
- part1: 'Folder:',
54
- part2: folder.name
55
- }} />
50
+ part1: 'Folder:',
51
+ part2: folder.name
52
+ }} />
56
53
  <Typography level='body-sm'>{folder.description}</Typography>
57
- <Table sx={{
58
- pt: 1,
59
- "--Table-headerUnderlineThickness": "2px",
60
- "--TableCell-height": "25px"
61
- }}>
62
- <thead>
63
- <tr>
64
- <th>Name</th>
65
- <th>Description</th>
66
- <th>Tasks</th>
67
- </tr>
68
- </thead>
69
- <tbody>
70
- {contents.map(content => {
71
- return <React.Fragment key={`${content.type}__${content.slug}`}>
72
- <tr>
73
- <td>
74
- {content.type == 'folder' && <>
75
- <Box sx={{
76
- display: 'flex',
77
- alignItems: 'center',
78
- gap: 1
79
- }}>
80
- {/* <Icon icon='folder' color='#444444'/> */}
81
- <i class="fa-regular fa-folder fa-xl"></i>
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
- </tbody>
107
- </Table>
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 { Box, Typography, Card, CardContent, Grid, Table, Sheet, Button, Select, Option, Input, Chip, IconButton } from '@mui/joy';
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 getStatusColor = status => {
84
+ const getStatusVariant = status => {
84
85
  switch (status) {
85
86
  case 'complete':
86
87
  return 'success';
87
88
  case 'failed':
88
- return 'danger';
89
+ return 'destructive';
89
90
  case 'running':
90
- return 'primary';
91
+ return 'default';
91
92
  case 'pending':
92
- return 'neutral';
93
+ return 'secondary';
93
94
  default:
94
- return 'neutral';
95
+ return 'secondary';
95
96
  }
96
97
  };
97
98
  const getStatusIcon = status => {
98
99
  switch (status) {
99
100
  case 'complete':
100
- return <CheckCircleIcon />;
101
+ return <CheckCircle className="h-3 w-3" />;
101
102
  case 'failed':
102
- return <ErrorIcon />;
103
+ return <AlertCircle className="h-3 w-3" />;
103
104
  case 'running':
104
- return <PlayArrowIcon />;
105
+ return <Play className="h-3 w-3" />;
105
106
  case 'pending':
106
- return <StopIcon />;
107
+ return <Square className="h-3 w-3" />;
107
108
  default:
108
- return <StopIcon />;
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 <Box sx={{
126
- p: 3
127
- }}>
126
+ return <div className="p-6">
128
127
  <Typography level="h2">Loading...</Typography>
129
- </Box>;
128
+ </div>;
130
129
  }
131
- return <Box sx={{
132
- p: 3
133
- }}>
130
+ return <div className="p-6">
134
131
  {/* Header */}
135
- <Box sx={{
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
- <Box sx={{
143
- display: 'flex',
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
- <IconButton variant="outlined" onClick={refreshAll} loading={loading} size="sm">
151
- <RefreshIcon />
152
- </IconButton>
153
- </Box>
154
- </Box>
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 && <Grid container spacing={2} sx={{
158
- mb: 3
159
- }}>
160
- <Grid xs={12} sm={6} md={3}>
161
- <Card>
162
- <CardContent>
163
- <Typography level="title-sm" color="neutral">Total Runs</Typography>
164
- <Typography level="h2">{overview.totalRuns?.toLocaleString()}</Typography>
165
- </CardContent>
166
- </Card>
167
- </Grid>
168
- <Grid xs={12} sm={6} md={3}>
169
- <Card>
170
- <CardContent>
171
- <Typography level="title-sm" color="success">Success Rate</Typography>
172
- <Typography level="h2" color="success">
173
- {overview.successRate ? `${overview.successRate.toFixed(1)}%` : 'N/A'}
174
- </Typography>
175
- </CardContent>
176
- </Card>
177
- </Grid>
178
- <Grid xs={12} sm={6} md={3}>
179
- <Card>
180
- <CardContent>
181
- <Typography level="title-sm" color="primary">Running Tasks</Typography>
182
- <Typography level="h2" color="primary">{overview.runningTasks || 0}</Typography>
183
- </CardContent>
184
- </Card>
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
- <Box sx={{
198
- mb: 3,
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
- }))} size="sm" sx={{
208
- minWidth: 120
209
- }}>
210
- <Option value="all">All Status</Option>
211
- <Option value="complete">Complete</Option>
212
- <Option value="failed">Failed</Option>
213
- <Option value="running">Running</Option>
214
- <Option value="pending">Pending</Option>
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
- <Input placeholder="Filter by task name..." value={filters.task} onChange={e => setFilters(prev => ({
218
- ...prev,
219
- task: e.target.value
220
- }))} size="sm" sx={{
221
- minWidth: 200
222
- }} startDecorator={<SearchIcon />} />
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} onChange={(_, value) => setFilters(prev => ({
200
+ <Select value={filters.dateRange} onValueChange={value => setFilters(prev => ({
225
201
  ...prev,
226
202
  dateRange: value
227
- }))} size="sm" sx={{
228
- minWidth: 100
229
- }}>
230
- <Option value="1h">Last Hour</Option>
231
- <Option value="24h">Last 24h</Option>
232
- <Option value="7d">Last 7 days</Option>
233
- <Option value="30d">Last 30 days</Option>
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
- </Box>
214
+ </div>
236
215
 
237
216
  {/* Task Statistics Table */}
238
- <Box sx={{
239
- mb: 4
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
- <thead>
250
- <tr>
251
- <th>Task Name</th>
252
- <th>Total Runs</th>
253
- <th>Success Rate</th>
254
- <th>Last Run</th>
255
- <th>Status</th>
256
- <th>Avg Duration</th>
257
- </tr>
258
- </thead>
259
- <tbody>
260
- {tasks.map(task => <tr key={task.task}>
261
- <td>
262
- <Typography level="body-sm" fontWeight="md">
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
- </td>
266
- <td>{task.totalRuns}</td>
267
- <td>
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
- </td>
272
- <td>
243
+ </TableCell>
244
+ <TableCell>
273
245
  <Typography level="body-sm">
274
246
  {formatDate(task.lastRun)}
275
247
  </Typography>
276
- </td>
277
- <td>
278
- <Chip color={getStatusColor(task.lastStatus)} size="sm" startDecorator={getStatusIcon(task.lastStatus)}>
248
+ </TableCell>
249
+ <TableCell>
250
+ <Badge variant={getStatusVariant(task.lastStatus)} className="gap-1">
251
+ {getStatusIcon(task.lastStatus)}
279
252
  {task.lastStatus}
280
- </Chip>
281
- </td>
282
- <td>
253
+ </Badge>
254
+ </TableCell>
255
+ <TableCell>
283
256
  <Typography level="body-sm">
284
257
  {formatDuration(task.avgDuration)}
285
258
  </Typography>
286
- </td>
287
- </tr>)}
288
- </tbody>
259
+ </TableCell>
260
+ </TableRow>)}
261
+ </TableBody>
289
262
  </Table>
290
- </Sheet>
291
- </Box>
263
+ </div>
264
+ </div>
292
265
 
293
266
  {/* Recent Runs */}
294
- <Box sx={{
295
- mb: 4
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
- <thead>
307
- <tr>
308
- <th>Task</th>
309
- <th>Status</th>
310
- <th>Started</th>
311
- <th>Duration</th>
312
- <th>Triggered By</th>
313
- </tr>
314
- </thead>
315
- <tbody>
316
- {recentRuns.map(run => <tr key={run.id}>
317
- <td>
318
- <Typography level="body-sm" fontWeight="md">
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
- </td>
322
- <td>
323
- <Chip color={getStatusColor(run.status)} size="sm" startDecorator={getStatusIcon(run.status)}>
286
+ </TableCell>
287
+ <TableCell>
288
+ <Badge variant={getStatusVariant(run.status)} className="gap-1">
289
+ {getStatusIcon(run.status)}
324
290
  {run.status}
325
- </Chip>
326
- </td>
327
- <td>
291
+ </Badge>
292
+ </TableCell>
293
+ <TableCell>
328
294
  <Typography level="body-sm">
329
295
  {formatDate(run.started_at)}
330
296
  </Typography>
331
- </td>
332
- <td>
297
+ </TableCell>
298
+ <TableCell>
333
299
  <Typography level="body-sm">
334
300
  {formatDuration(run.duration)}
335
301
  </Typography>
336
- </td>
337
- <td>
338
- <Chip size="sm" variant="outlined">
302
+ </TableCell>
303
+ <TableCell>
304
+ <Badge variant="outline">
339
305
  {run.triggered_by}
340
- </Chip>
341
- </td>
342
- </tr>)}
343
- </tbody>
306
+ </Badge>
307
+ </TableCell>
308
+ </TableRow>)}
309
+ </TableBody>
344
310
  </Table>
345
- </Sheet>
346
- </Box>
311
+ </div>
312
+ </div>
347
313
 
348
314
  {/* Recent Logs */}
349
- <Box>
350
- <Typography level="h3" sx={{
351
- mb: 2
352
- }}>Recent Logs</Typography>
353
- <Sheet sx={{
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" sx={{
369
- whiteSpace: 'pre-wrap'
370
- }}>
322
+ <Typography level="body-sm" className="whitespace-pre-wrap">
371
323
  {log.content}
372
324
  </Typography>
373
- </Box>)}
374
- </Sheet>
375
- </Box>
376
- </Box>;
325
+ </div>)}
326
+ </div>
327
+ </div>
328
+ </div>;
377
329
  }