@microlight/core 0.2.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/README.md +77 -0
- package/bin/microlight-core.js +70 -0
- package/dist/scripts/generate-folder-index.js +120 -0
- package/dist/scripts/generate-task-imports.js +64 -0
- package/dist/scripts/generate-task-index.js +61 -0
- package/dist/scripts/prepareFolders.js +119 -0
- package/dist/scripts/prepareServer.js +34 -0
- package/dist/scripts/prepareTasks.js +114 -0
- package/dist/server/app/api/tasks/[slug]/route.js +54 -0
- package/dist/server/app/layout.js +41 -0
- package/dist/server/app/library/[[...f_path]]/ViewFolder.js +113 -0
- package/dist/server/app/library/[[...f_path]]/page.js +42 -0
- package/dist/server/app/page.js +4 -0
- package/dist/server/app/tasks/[slug]/ViewTask.js +252 -0
- package/dist/server/app/tasks/[slug]/action.js +44 -0
- package/dist/server/app/tasks/[slug]/page.js +33 -0
- package/dist/server/app/tasks/[slug]/runs/[r_id]/ViewRun.js +230 -0
- package/dist/server/app/tasks/[slug]/runs/[r_id]/_components/DropdownActions/DropdownActions.js +46 -0
- package/dist/server/app/tasks/[slug]/runs/[r_id]/_components/DropdownActions/action.js +35 -0
- package/dist/server/app/tasks/[slug]/runs/[r_id]/page.js +43 -0
- package/dist/server/components/Icon.js +22 -0
- package/dist/server/components/Link.js +52 -0
- package/dist/server/components/MLInput.js +29 -0
- package/dist/server/components/Navbar/Navbar.js +38 -0
- package/dist/server/components/Navbar/NavbarContainer.js +26 -0
- package/dist/server/components/PageHeader.js +87 -0
- package/dist/server/components/StatusChip.js +11 -0
- package/dist/server/components/Test.js +5 -0
- package/dist/server/components/TopLoader.js +8 -0
- package/dist/server/database/microlight/index.js +52 -0
- package/dist/server/database/microlight/tables/Logs.model.js +34 -0
- package/dist/server/database/microlight/tables/Runs.model.js +61 -0
- package/dist/server/instrumentation.js +16 -0
- package/dist/server/lib/executeRun.js +80 -0
- package/dist/server/lib/generateDisplayFunctions.js +89 -0
- package/dist/server/lib/getAllTasks.js +32 -0
- package/dist/server/lib/getTaskDetails.js +17 -0
- package/dist/server/lib/loadSchedules.js +77 -0
- package/dist/server/tasks/1.intro/hello_world2.task.js +21 -0
- package/dist/server/tasks/1.intro/microlight.folder.js +5 -0
- package/dist/server/tasks/1.intro/ml.task.js +31 -0
- package/dist/server/tasks/1.intro/scheduled.task.js +18 -0
- package/dist/server/tasks/1.intro/takes_time.task.js +28 -0
- package/dist/server/tasks/1.intro/test/microlight.folder.js +5 -0
- package/dist/server/tasks/1.intro/test/takes_time2.task.js +28 -0
- package/dist/server/tasks/index.js +33 -0
- package/dist/server/tasks/microlight.folder.js +5 -0
- package/index.js +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { executeTask } from "../../../tasks/[slug]/action";
|
|
2
|
+
export async function POST(request, {
|
|
3
|
+
params
|
|
4
|
+
}) {
|
|
5
|
+
try {
|
|
6
|
+
// Extract the slug from the route parameters
|
|
7
|
+
const {
|
|
8
|
+
slug
|
|
9
|
+
} = params;
|
|
10
|
+
|
|
11
|
+
// Extract query parameters
|
|
12
|
+
const url = new URL(request.url);
|
|
13
|
+
const date = url.searchParams.get('date');
|
|
14
|
+
const filename = url.searchParams.get('filename');
|
|
15
|
+
|
|
16
|
+
// Create FormData-like object with the query parameters
|
|
17
|
+
const formData = new FormData();
|
|
18
|
+
formData.append('date', date);
|
|
19
|
+
formData.append('filename', filename);
|
|
20
|
+
|
|
21
|
+
// Create task object using the dynamic slug
|
|
22
|
+
const task = {
|
|
23
|
+
slug: slug
|
|
24
|
+
// Add other required task properties here
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Execute the task using the same function
|
|
28
|
+
const result = await executeTask({
|
|
29
|
+
formData,
|
|
30
|
+
task
|
|
31
|
+
});
|
|
32
|
+
if (result.success) {
|
|
33
|
+
return Response.json({
|
|
34
|
+
success: true,
|
|
35
|
+
runId: result.run.id,
|
|
36
|
+
redirectUrl: `/tasks/${slug}/runs/${result.run.id}`
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
return Response.json({
|
|
40
|
+
success: false,
|
|
41
|
+
error: 'Task execution failed'
|
|
42
|
+
}, {
|
|
43
|
+
status: 400
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return Response.json({
|
|
48
|
+
success: false,
|
|
49
|
+
error: error.message
|
|
50
|
+
}, {
|
|
51
|
+
status: 500
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Inter } from "next/font/google";
|
|
2
|
+
const inter = Inter({
|
|
3
|
+
subsets: ["latin"]
|
|
4
|
+
});
|
|
5
|
+
import TopLoader from "../components/TopLoader";
|
|
6
|
+
// import ServiceWorkerRegistration from "@/components/ServiceWorkerRegistration";
|
|
7
|
+
import NavbarContainer from "../components/Navbar/NavbarContainer";
|
|
8
|
+
export const metadata = {
|
|
9
|
+
title: "Microlight",
|
|
10
|
+
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
|
+
};
|
|
23
|
+
export default function RootLayout({
|
|
24
|
+
children
|
|
25
|
+
}) {
|
|
26
|
+
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
|
+
}}>
|
|
34
|
+
{/* <ServiceWorkerRegistration /> */}
|
|
35
|
+
<TopLoader />
|
|
36
|
+
<NavbarContainer>
|
|
37
|
+
{children}
|
|
38
|
+
</NavbarContainer>
|
|
39
|
+
</body>
|
|
40
|
+
</html>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Table, Box, Container, Typography } from '@mui/joy';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import Icon from "../../../components/Icon";
|
|
6
|
+
|
|
7
|
+
// import Link from '@/components/Link';
|
|
8
|
+
|
|
9
|
+
import { Link } from 'switchless';
|
|
10
|
+
import PageHeader from "../../../components/PageHeader";
|
|
11
|
+
function generateBreadcrumbs({
|
|
12
|
+
params
|
|
13
|
+
}) {
|
|
14
|
+
let breadcrumbs = [];
|
|
15
|
+
let b = {
|
|
16
|
+
text: "Library"
|
|
17
|
+
};
|
|
18
|
+
if (params.f_path?.length) {
|
|
19
|
+
let slug = '/library';
|
|
20
|
+
b.href = '/library';
|
|
21
|
+
breadcrumbs.push(b);
|
|
22
|
+
params.f_path.forEach(function (name, i) {
|
|
23
|
+
let b = {
|
|
24
|
+
text: name
|
|
25
|
+
};
|
|
26
|
+
if (i != params.f_path.length - 1) {
|
|
27
|
+
slug += '/' + name;
|
|
28
|
+
b.href = slug;
|
|
29
|
+
}
|
|
30
|
+
breadcrumbs.push(b);
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
breadcrumbs.push(b);
|
|
34
|
+
}
|
|
35
|
+
return breadcrumbs;
|
|
36
|
+
}
|
|
37
|
+
export default function ViewFolder({
|
|
38
|
+
params,
|
|
39
|
+
folder,
|
|
40
|
+
contents,
|
|
41
|
+
fileList
|
|
42
|
+
}) {
|
|
43
|
+
// params.f_path will be an array containing all segments after /library/
|
|
44
|
+
// e.g. for /library/level1/level2/level3
|
|
45
|
+
// params.f_path = ['level1', 'level2', 'level3']
|
|
46
|
+
const breadcrumbs = generateBreadcrumbs({
|
|
47
|
+
params
|
|
48
|
+
});
|
|
49
|
+
const dir = params.f_path ? '/' + params.f_path?.join('/') : '';
|
|
50
|
+
return <>
|
|
51
|
+
<Container>
|
|
52
|
+
<PageHeader breadcrumbs={breadcrumbs} header={{
|
|
53
|
+
part1: 'Folder:',
|
|
54
|
+
part2: folder.name
|
|
55
|
+
}} />
|
|
56
|
+
<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>;
|
|
103
|
+
})}
|
|
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
|
+
</>;
|
|
113
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import ViewFolder from "./ViewFolder";
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { notFound } from 'next/navigation';
|
|
4
|
+
import folderMap from "../../../../folderMap";
|
|
5
|
+
const project_folder = process.cwd() + '/src/tasks/';
|
|
6
|
+
async function getFolderDetails({
|
|
7
|
+
params
|
|
8
|
+
}) {
|
|
9
|
+
const dir = params?.f_path?.join('/') || '';
|
|
10
|
+
console.log('\n\n\n\n======');
|
|
11
|
+
console.log("dir - ", dir);
|
|
12
|
+
let folderConfig = {};
|
|
13
|
+
folderConfig = folderMap[dir];
|
|
14
|
+
console.log(folderConfig);
|
|
15
|
+
// console.log('\n\n\n\n\n===========');
|
|
16
|
+
// console.log(dir)
|
|
17
|
+
// console.log(folderMap)
|
|
18
|
+
// console.log(folderConfig)
|
|
19
|
+
return folderConfig;
|
|
20
|
+
}
|
|
21
|
+
export default async function Page({
|
|
22
|
+
params
|
|
23
|
+
}) {
|
|
24
|
+
params = await params;
|
|
25
|
+
let folder = {
|
|
26
|
+
contents: []
|
|
27
|
+
};
|
|
28
|
+
let contents = [];
|
|
29
|
+
try {
|
|
30
|
+
folder = await getFolderDetails({
|
|
31
|
+
params
|
|
32
|
+
});
|
|
33
|
+
} catch (e) {
|
|
34
|
+
if (e.code === 'MODULE_NOT_FOUND') notFound();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// console.log('\n\n\n======');
|
|
38
|
+
|
|
39
|
+
// When accessing /library, params.f_path will be undefined
|
|
40
|
+
// When accessing /library/a/b, params.f_path will be ['a', 'b']
|
|
41
|
+
return <ViewFolder params={params} folder={folder} contents={folder.contents} />;
|
|
42
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import OpenInNew from '@mui/icons-material/OpenInNew';
|
|
4
|
+
import { Container, Typography, Box, Card, ButtonGroup, Button, Table, Chip, Link as MuiLink } from '@mui/joy';
|
|
5
|
+
import PageHeader from "../../../components/PageHeader";
|
|
6
|
+
import MLInput from "../../../components/MLInput";
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
import { executeTask } from "./action";
|
|
9
|
+
import { redirect } from 'next/navigation';
|
|
10
|
+
import StatusChip from "../../../components/StatusChip";
|
|
11
|
+
import cronstrue from 'cronstrue';
|
|
12
|
+
import Link from "../../../components/Link";
|
|
13
|
+
function generateBreadcrumbs({
|
|
14
|
+
task
|
|
15
|
+
}) {
|
|
16
|
+
let breadcrumbs = [{
|
|
17
|
+
text: "Library",
|
|
18
|
+
href: "/library"
|
|
19
|
+
}];
|
|
20
|
+
|
|
21
|
+
// Add task path segments to breadcrumbs if available
|
|
22
|
+
if (task._folderPath) {
|
|
23
|
+
const f_path = task._folderPath.split('/');
|
|
24
|
+
let folderPath = '/library';
|
|
25
|
+
f_path.forEach((folder, index) => {
|
|
26
|
+
folderPath += '/' + folder;
|
|
27
|
+
breadcrumbs.push({
|
|
28
|
+
text: folder,
|
|
29
|
+
href: folderPath
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
breadcrumbs.push({
|
|
33
|
+
text: task.slug
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return breadcrumbs;
|
|
37
|
+
}
|
|
38
|
+
export default function ViewTask({
|
|
39
|
+
params,
|
|
40
|
+
task,
|
|
41
|
+
runs,
|
|
42
|
+
searchParams
|
|
43
|
+
}) {
|
|
44
|
+
const breadcrumbs = generateBreadcrumbs({
|
|
45
|
+
task
|
|
46
|
+
});
|
|
47
|
+
const [loading, setLoading] = useState(false);
|
|
48
|
+
const RightButtons = function () {
|
|
49
|
+
return <>
|
|
50
|
+
{task?.links?.map(link => {
|
|
51
|
+
return <>
|
|
52
|
+
<MuiLink underline="none" variant="outlined" color="neutral" target='_blank' href={link.href} startDecorator={<i class="fa-solid fa-up-right-from-square"></i>} sx={{
|
|
53
|
+
mx: 0.5,
|
|
54
|
+
px: 1,
|
|
55
|
+
py: 0.5,
|
|
56
|
+
borderRadius: 'md'
|
|
57
|
+
}}>
|
|
58
|
+
{link.title}
|
|
59
|
+
</MuiLink>
|
|
60
|
+
</>;
|
|
61
|
+
})}
|
|
62
|
+
</>;
|
|
63
|
+
};
|
|
64
|
+
const handleSubmit = async event => {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
setLoading(true);
|
|
67
|
+
const form = event.currentTarget;
|
|
68
|
+
const formData = new FormData(form);
|
|
69
|
+
let result = await executeTask({
|
|
70
|
+
formData,
|
|
71
|
+
task
|
|
72
|
+
});
|
|
73
|
+
if (result.success)
|
|
74
|
+
// console.log('something went wrong');
|
|
75
|
+
console.log(`/tasks/${task.slug}/runs/${result.run.id}`);
|
|
76
|
+
redirect(`/tasks/${task.slug}/runs/${result.run.id}`);
|
|
77
|
+
};
|
|
78
|
+
return <Container>
|
|
79
|
+
<PageHeader breadcrumbs={breadcrumbs} header={{
|
|
80
|
+
part1: 'Task:',
|
|
81
|
+
part2: task.name
|
|
82
|
+
}} RightButtons={RightButtons} />
|
|
83
|
+
<Typography level="body-sm">{task.description}</Typography>
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
<Card sx={{
|
|
87
|
+
mt: 2,
|
|
88
|
+
backgroundColor: 'transparent',
|
|
89
|
+
maxWidth: 400
|
|
90
|
+
}}>
|
|
91
|
+
{/* {Object.keys(task.inputs)} */}
|
|
92
|
+
<form onSubmit={handleSubmit}>
|
|
93
|
+
{Object.keys(task.inputs).map(slug => <>
|
|
94
|
+
<MLInput key={slug} slug={slug} def={task.inputs[slug]} searchParams={searchParams} />
|
|
95
|
+
</>)}
|
|
96
|
+
<ButtonGroup spacing={1}>
|
|
97
|
+
{/* <Button type="submit" fullWidth color="primary" variant="solid" startDecorator={<FilterAltIcon />}>Apply filter</Button> */}
|
|
98
|
+
<Button loading={loading} disabled={loading} type="submit" color='primary' variant="solid">Execute task</Button>
|
|
99
|
+
{/* <Button loading={loading} disabled={loading} type="submit" color='primary' sx={{bgcolor:'#6435c9',borderRadius:3}} variant="solid">Execute task</Button> */}
|
|
100
|
+
{/* <Button variant="outlined" onClick={handleReset}>Reset</Button> */}
|
|
101
|
+
{/* <Button disabled={loading.apply} loading={loading.reset} fullWidth variant="outlined" color="primary" onClick={handleReset} >Reset</Button> */}
|
|
102
|
+
</ButtonGroup>
|
|
103
|
+
</form>
|
|
104
|
+
</Card>
|
|
105
|
+
<Typography level="title-lg" sx={{
|
|
106
|
+
mt: 3
|
|
107
|
+
}}>Schedules:</Typography>
|
|
108
|
+
|
|
109
|
+
<Table variant='outlined' aria-label="task runs table" size='md' sx={{
|
|
110
|
+
mt: 1,
|
|
111
|
+
maxWidth: 800,
|
|
112
|
+
'& th': {
|
|
113
|
+
height: {
|
|
114
|
+
sm: "22px",
|
|
115
|
+
md: "26px",
|
|
116
|
+
lg: "30px"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
'& td': {
|
|
120
|
+
height: {
|
|
121
|
+
sm: "23px",
|
|
122
|
+
md: "27px",
|
|
123
|
+
lg: "31px"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}}>
|
|
127
|
+
<thead>
|
|
128
|
+
<tr>
|
|
129
|
+
{/* <th ></th> */}
|
|
130
|
+
<th style={{
|
|
131
|
+
width: 100
|
|
132
|
+
}}>Schedule</th>
|
|
133
|
+
<th style={{
|
|
134
|
+
width: 150
|
|
135
|
+
}}>Description</th>
|
|
136
|
+
<th style={{
|
|
137
|
+
width: 300
|
|
138
|
+
}}>Payload</th>
|
|
139
|
+
<th>Is Enabled?</th>
|
|
140
|
+
{/* <th>User</th> */}
|
|
141
|
+
</tr>
|
|
142
|
+
</thead>
|
|
143
|
+
<tbody>
|
|
144
|
+
{task?.schedules?.map((schedule, index) => <tr key={index}>
|
|
145
|
+
<td>{schedule.schedule}</td>
|
|
146
|
+
<td>
|
|
147
|
+
{cronstrue.toString(schedule.schedule)}
|
|
148
|
+
</td>
|
|
149
|
+
<td style={{
|
|
150
|
+
overflow: 'auto',
|
|
151
|
+
'&::-webkit-scrollbar': {
|
|
152
|
+
display: 'none'
|
|
153
|
+
},
|
|
154
|
+
height: 0,
|
|
155
|
+
scrollbarWidth: 'none',
|
|
156
|
+
// Firefox
|
|
157
|
+
msOverflowStyle: 'none' // IE and Edge
|
|
158
|
+
}}>
|
|
159
|
+
<pre style={{
|
|
160
|
+
margin: 0
|
|
161
|
+
}}>{JSON.stringify(schedule?.inputs, null, 2).slice(2, -2)}</pre>
|
|
162
|
+
</td>
|
|
163
|
+
<td>
|
|
164
|
+
{schedule.is_enabled ? 'enabled' : 'disabled'}
|
|
165
|
+
</td>
|
|
166
|
+
|
|
167
|
+
{/* <td>{run.user}</td> */}
|
|
168
|
+
</tr>)}
|
|
169
|
+
</tbody>
|
|
170
|
+
</Table>
|
|
171
|
+
<Typography level="title-lg" sx={{
|
|
172
|
+
mt: 3
|
|
173
|
+
}}>Recent runs:</Typography>
|
|
174
|
+
{/* <pre>{JSON.stringify(runs,null,2)}</pre> */}
|
|
175
|
+
|
|
176
|
+
<Table variant='outlined' aria-label="task runs table" size='md' sx={{
|
|
177
|
+
mt: 1,
|
|
178
|
+
maxWidth: 800,
|
|
179
|
+
'& th': {
|
|
180
|
+
height: {
|
|
181
|
+
sm: "22px",
|
|
182
|
+
md: "26px",
|
|
183
|
+
lg: "30px"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
'& td': {
|
|
187
|
+
height: {
|
|
188
|
+
sm: "23px",
|
|
189
|
+
md: "27px",
|
|
190
|
+
lg: "31px"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}}>
|
|
194
|
+
<thead>
|
|
195
|
+
<tr>
|
|
196
|
+
{/* <th ></th> */}
|
|
197
|
+
<th style={{
|
|
198
|
+
width: 100
|
|
199
|
+
}}>Created At</th>
|
|
200
|
+
<th style={{
|
|
201
|
+
width: 150
|
|
202
|
+
}}>ID</th>
|
|
203
|
+
<th style={{
|
|
204
|
+
width: 300
|
|
205
|
+
}}>Payload</th>
|
|
206
|
+
<th>Status</th>
|
|
207
|
+
<th style={{
|
|
208
|
+
width: '58px'
|
|
209
|
+
}}>Duration</th>
|
|
210
|
+
<th>By</th>
|
|
211
|
+
{/* <th>User</th> */}
|
|
212
|
+
</tr>
|
|
213
|
+
</thead>
|
|
214
|
+
<tbody>
|
|
215
|
+
{runs.map(run => <tr key={run.id}>
|
|
216
|
+
<td>{new Date(run.updated_at).toLocaleString()}</td>
|
|
217
|
+
<td>
|
|
218
|
+
<Link href={`/tasks/${params.slug}/runs/${run.id}`} level="body-sm">
|
|
219
|
+
{task.slug} #{run.id}
|
|
220
|
+
</Link>
|
|
221
|
+
</td>
|
|
222
|
+
<td style={{
|
|
223
|
+
overflow: 'auto',
|
|
224
|
+
'&::-webkit-scrollbar': {
|
|
225
|
+
display: 'none'
|
|
226
|
+
},
|
|
227
|
+
height: 0,
|
|
228
|
+
scrollbarWidth: 'none',
|
|
229
|
+
// Firefox
|
|
230
|
+
msOverflowStyle: 'none' // IE and Edge
|
|
231
|
+
}}>
|
|
232
|
+
<pre style={{
|
|
233
|
+
margin: 0
|
|
234
|
+
}}>{JSON.stringify(run?.inputs, null, 1).slice(2, -2)}</pre>
|
|
235
|
+
</td>
|
|
236
|
+
<td>
|
|
237
|
+
<StatusChip status={run.status} />
|
|
238
|
+
</td>
|
|
239
|
+
<td style={{
|
|
240
|
+
textAlign: 'right'
|
|
241
|
+
}}>{run.duration / 1000 || 0}s</td>
|
|
242
|
+
<td>{run.triggered_by || 'user'}</td>
|
|
243
|
+
{/* <td>{run.user}</td> */}
|
|
244
|
+
</tr>)}
|
|
245
|
+
</tbody>
|
|
246
|
+
</Table>
|
|
247
|
+
{/* Add your task execution UI components here */}
|
|
248
|
+
|
|
249
|
+
{/* Uncomment for debugging */}
|
|
250
|
+
{/* <pre>{JSON.stringify(task, null, 2)}</pre> */}
|
|
251
|
+
</Container>;
|
|
252
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import async from 'async';
|
|
4
|
+
import microlightDB from "../../../database/microlight";
|
|
5
|
+
import executeRun from "../../../lib/executeRun";
|
|
6
|
+
import { redirect } from 'next/navigation';
|
|
7
|
+
import { revalidatePath } from 'next/cache';
|
|
8
|
+
export async function executeTask({
|
|
9
|
+
formData,
|
|
10
|
+
task
|
|
11
|
+
}) {
|
|
12
|
+
const workflow = {
|
|
13
|
+
createRun: async function () {
|
|
14
|
+
let run = await microlightDB.Runs.create({
|
|
15
|
+
task: task.slug,
|
|
16
|
+
logs: {},
|
|
17
|
+
inputs: Object.fromEntries(formData?.entries()),
|
|
18
|
+
triggered_by: 'user',
|
|
19
|
+
status: 'pending'
|
|
20
|
+
}, {
|
|
21
|
+
returning: true
|
|
22
|
+
});
|
|
23
|
+
return run.toJSON();
|
|
24
|
+
},
|
|
25
|
+
startRun: ['createRun', async function (results) {
|
|
26
|
+
process.nextTick(() => executeRun(results.createRun));
|
|
27
|
+
return;
|
|
28
|
+
}]
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
const results = await async.auto(workflow);
|
|
32
|
+
console.log(results);
|
|
33
|
+
revalidatePath(`/tasks/${task.slug}`);
|
|
34
|
+
return {
|
|
35
|
+
success: true,
|
|
36
|
+
run: results.createRun
|
|
37
|
+
};
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
error: e
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import getTaskDetails from "../../../lib/getTaskDetails";
|
|
2
|
+
import ViewTask from "./ViewTask";
|
|
3
|
+
import async from 'async';
|
|
4
|
+
import microlightDB from "../../../database/microlight";
|
|
5
|
+
import { orderBy } from "lodash";
|
|
6
|
+
export default async function Page({
|
|
7
|
+
params,
|
|
8
|
+
searchParams
|
|
9
|
+
}) {
|
|
10
|
+
params = await params;
|
|
11
|
+
searchParams = await searchParams;
|
|
12
|
+
const workflow = {
|
|
13
|
+
getTask: async function () {
|
|
14
|
+
const task = await getTaskDetails({
|
|
15
|
+
params
|
|
16
|
+
});
|
|
17
|
+
delete task.fn;
|
|
18
|
+
return task;
|
|
19
|
+
},
|
|
20
|
+
getRuns: async function () {
|
|
21
|
+
let runs = await microlightDB.Runs.findAll({
|
|
22
|
+
where: {
|
|
23
|
+
task: params.slug
|
|
24
|
+
},
|
|
25
|
+
order: [['updated_at', 'DESC']]
|
|
26
|
+
});
|
|
27
|
+
runs = runs.map(r => r.toJSON());
|
|
28
|
+
return runs;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
let results = await async.auto(workflow);
|
|
32
|
+
return <ViewTask params={params} task={results.getTask} runs={results.getRuns} searchParams={searchParams} />;
|
|
33
|
+
}
|