@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.
Files changed (49) hide show
  1. package/README.md +77 -0
  2. package/bin/microlight-core.js +70 -0
  3. package/dist/scripts/generate-folder-index.js +120 -0
  4. package/dist/scripts/generate-task-imports.js +64 -0
  5. package/dist/scripts/generate-task-index.js +61 -0
  6. package/dist/scripts/prepareFolders.js +119 -0
  7. package/dist/scripts/prepareServer.js +34 -0
  8. package/dist/scripts/prepareTasks.js +114 -0
  9. package/dist/server/app/api/tasks/[slug]/route.js +54 -0
  10. package/dist/server/app/layout.js +41 -0
  11. package/dist/server/app/library/[[...f_path]]/ViewFolder.js +113 -0
  12. package/dist/server/app/library/[[...f_path]]/page.js +42 -0
  13. package/dist/server/app/page.js +4 -0
  14. package/dist/server/app/tasks/[slug]/ViewTask.js +252 -0
  15. package/dist/server/app/tasks/[slug]/action.js +44 -0
  16. package/dist/server/app/tasks/[slug]/page.js +33 -0
  17. package/dist/server/app/tasks/[slug]/runs/[r_id]/ViewRun.js +230 -0
  18. package/dist/server/app/tasks/[slug]/runs/[r_id]/_components/DropdownActions/DropdownActions.js +46 -0
  19. package/dist/server/app/tasks/[slug]/runs/[r_id]/_components/DropdownActions/action.js +35 -0
  20. package/dist/server/app/tasks/[slug]/runs/[r_id]/page.js +43 -0
  21. package/dist/server/components/Icon.js +22 -0
  22. package/dist/server/components/Link.js +52 -0
  23. package/dist/server/components/MLInput.js +29 -0
  24. package/dist/server/components/Navbar/Navbar.js +38 -0
  25. package/dist/server/components/Navbar/NavbarContainer.js +26 -0
  26. package/dist/server/components/PageHeader.js +87 -0
  27. package/dist/server/components/StatusChip.js +11 -0
  28. package/dist/server/components/Test.js +5 -0
  29. package/dist/server/components/TopLoader.js +8 -0
  30. package/dist/server/database/microlight/index.js +52 -0
  31. package/dist/server/database/microlight/tables/Logs.model.js +34 -0
  32. package/dist/server/database/microlight/tables/Runs.model.js +61 -0
  33. package/dist/server/instrumentation.js +16 -0
  34. package/dist/server/lib/executeRun.js +80 -0
  35. package/dist/server/lib/generateDisplayFunctions.js +89 -0
  36. package/dist/server/lib/getAllTasks.js +32 -0
  37. package/dist/server/lib/getTaskDetails.js +17 -0
  38. package/dist/server/lib/loadSchedules.js +77 -0
  39. package/dist/server/tasks/1.intro/hello_world2.task.js +21 -0
  40. package/dist/server/tasks/1.intro/microlight.folder.js +5 -0
  41. package/dist/server/tasks/1.intro/ml.task.js +31 -0
  42. package/dist/server/tasks/1.intro/scheduled.task.js +18 -0
  43. package/dist/server/tasks/1.intro/takes_time.task.js +28 -0
  44. package/dist/server/tasks/1.intro/test/microlight.folder.js +5 -0
  45. package/dist/server/tasks/1.intro/test/takes_time2.task.js +28 -0
  46. package/dist/server/tasks/index.js +33 -0
  47. package/dist/server/tasks/microlight.folder.js +5 -0
  48. package/index.js +1 -0
  49. package/package.json +46 -0
@@ -0,0 +1,230 @@
1
+ 'use client';
2
+
3
+ import PageHeader from "../../../../../components/PageHeader";
4
+ import { Container, Table, Link, Chip, Typography, Sheet, Alert } from "@mui/joy";
5
+ import { useRouter } from 'next/navigation';
6
+ import { useState, useEffect } from 'react';
7
+ import StatusChip from "../../../../../components/StatusChip";
8
+ import DropdownActions from "./_components/DropdownActions/DropdownActions";
9
+ function generateBreadcrumbs({
10
+ task,
11
+ params
12
+ }) {
13
+ let breadcrumbs = [{
14
+ text: "Library",
15
+ href: "/library"
16
+ }];
17
+ // Add task path segments to breadcrumbs if available
18
+ if (task._folderPath) {
19
+ const f_path = task._folderPath.split('/');
20
+ let folderPath = '/library';
21
+ f_path.forEach((folder, index) => {
22
+ folderPath += '/' + folder;
23
+ breadcrumbs.push({
24
+ text: folder,
25
+ href: folderPath
26
+ });
27
+ });
28
+ breadcrumbs.push({
29
+ text: task.slug,
30
+ href: '/tasks/' + task.slug
31
+ });
32
+ breadcrumbs.push({
33
+ text: params.r_id
34
+ });
35
+ }
36
+ return breadcrumbs;
37
+ }
38
+ export default function ViewRun({
39
+ params,
40
+ task,
41
+ run,
42
+ logs
43
+ }) {
44
+ const breadcrumbs = generateBreadcrumbs({
45
+ task,
46
+ params
47
+ });
48
+ const router = useRouter();
49
+ let RightButtons = function () {
50
+ return <>
51
+ <DropdownActions run={run} />
52
+ </>;
53
+ };
54
+ useEffect(() => {
55
+ let intervalId;
56
+ if (run.status === 'running' || run.status === 'pending') {
57
+ intervalId = setInterval(() => {
58
+ // Refresh the page data using router.refresh()
59
+ router.refresh();
60
+ }, 500);
61
+ }
62
+ return () => {
63
+ if (intervalId) {
64
+ clearInterval(intervalId);
65
+ }
66
+ };
67
+ }, [run]);
68
+ return <>
69
+ <Container>
70
+ <PageHeader breadcrumbs={breadcrumbs} header={{
71
+ part1: 'Task Run:',
72
+ part2: task.name
73
+ }} RightButtons={RightButtons}
74
+ // icon={<SendIcon sx={{color: '#6435c9'}} />}
75
+ />
76
+ <Table variant='outlined' borderAxis="bothBetween" aria-label="task runs table" size='md' sx={{
77
+ mt: 1,
78
+ maxWidth: 500,
79
+ '& th': {
80
+ height: {
81
+ sm: "22px",
82
+ md: "26px",
83
+ lg: "30px"
84
+ }
85
+ },
86
+ '& td': {
87
+ height: {
88
+ sm: "23px",
89
+ md: "27px",
90
+ lg: "31px"
91
+ }
92
+ }
93
+ }}>
94
+ <thead>
95
+ <tr>
96
+ {/* <th ></th> */}
97
+ <th>Created At</th>
98
+ <th>Started At</th>
99
+ {/* <th >ID</th> */}
100
+ <th>Status</th>
101
+ <th style={{
102
+ width: '58px'
103
+ }}>Duration</th>
104
+ <th>By</th>
105
+ {/* <th>User</th> */}
106
+ </tr>
107
+ </thead>
108
+ <tbody>
109
+ <tr key={run.id}>
110
+ <td>{new Date(run.created_at).toLocaleString()}</td>
111
+ <td>{run.started_at && new Date(run.started_at).toLocaleString()}</td>
112
+
113
+ <td><StatusChip status={run.status} /></td>
114
+ <td style={{
115
+ textAlign: 'right'
116
+ }}>{run.duration / 1000 || 0}s</td>
117
+ <td>{run.by || 'user'}</td>
118
+ {/* <td>{run.user}</td> */}
119
+ </tr>
120
+ </tbody>
121
+ </Table>
122
+ <Typography level="title-lg" sx={{
123
+ mt: 3,
124
+ mb: 1
125
+ }}>Payload:</Typography>
126
+ <pre>{JSON.stringify(run.inputs, null, 2)}</pre>
127
+ <Typography level="title-lg" sx={{
128
+ mt: 3,
129
+ mb: 1
130
+ }}>Logs:</Typography>
131
+ <Sheet sx={{
132
+ bgcolor: 'white'
133
+ }}>
134
+ <Table borderAxis="xBetween" size="md" variant="outlined" sx={{
135
+ border: '1px solid #d9dada',
136
+ '--TableCell-borderColor': '#ebebeb'
137
+ }}>
138
+ <tbody>
139
+ {logs.map((log, index) => <tr key={index}>
140
+ <td style={{
141
+ width: 90,
142
+ padding: 7,
143
+ paddingLeft: 14,
144
+ verticalAlign: 'top',
145
+ height: 'auto'
146
+ }}>
147
+ <Typography level="body-sm" fontFamily="monospace">
148
+ {new Date(log.created_at).toLocaleTimeString()}
149
+ </Typography>
150
+ </td>
151
+ <td style={{
152
+ padding: 7,
153
+ verticalAlign: 'top',
154
+ height: 'auto'
155
+ }}>
156
+ {log.type == 'markdown' ? <Alert variant='soft' color="primary" sx={{
157
+ p: 2,
158
+ pb: 0,
159
+ // bgcolor:'#e3fbe3',
160
+ bgcolor: '#f8ffff',
161
+ color: '#276f85',
162
+ border: '1px solid #a8d4dd',
163
+ '& h4, & h3,': {
164
+ mt: 0
165
+ }
166
+ }}>
167
+ <div className='' dangerouslySetInnerHTML={{
168
+ __html: log.content
169
+ }} />
170
+ </Alert> : log.type == 'json' ? <pre className="json-renderer" style={{
171
+ padding: 0,
172
+ margin: 0
173
+ }}>
174
+ {JSON.stringify(JSON.parse(log.content), null, 2)}
175
+ </pre> : log.type == 'error' ? <Alert variant='soft' color="danger" sx={{
176
+ p: 2,
177
+ pb: 0,
178
+ overflow: 'auto',
179
+ '&::-webkit-scrollbar': {
180
+ display: 'none'
181
+ },
182
+ scrollbarWidth: 'none',
183
+ // Firefox
184
+ msOverflowStyle: 'none',
185
+ // IE and Edge
186
+ // bgcolor:'#e3fbe3',
187
+ // bgcolor:'#f8ffff',
188
+ // color:'#276f85',
189
+ border: '1px solid #e0b4b4',
190
+ '& h4, & h3,': {
191
+ mt: 0
192
+ }
193
+ }}>
194
+ <div>
195
+ <h4>Error : {JSON.parse(log.content)?.message}</h4>
196
+ {JSON.parse(log.content)?.stack}
197
+
198
+ </div>
199
+ </Alert> : log.type == 'warn' ? <Alert variant='soft' color="warning" sx={{
200
+ py: 1,
201
+ my: -0.4
202
+ }}>
203
+ {log.content}
204
+ </Alert> : log.type == 'info' ? <Alert variant='soft' color="primary" sx={{
205
+ py: 1,
206
+ my: -0.4
207
+ }}>
208
+ {log.content}
209
+ </Alert> : log.type == 'danger' ? <Alert variant='soft' color="danger" sx={{
210
+ py: 1,
211
+ my: -0.4
212
+ }}>
213
+ {log.content}
214
+ </Alert> : log.type == 'success' ? <Alert variant='soft' color="success" sx={{
215
+ py: 1,
216
+ my: -0.4
217
+ }}>
218
+ {log.content}
219
+ </Alert> : <Typography level="body-sm" fontFamily="monospace">{log.content}</Typography>}
220
+ </td>
221
+ </tr>)}
222
+ </tbody>
223
+ </Table>
224
+ </Sheet>
225
+ <br />
226
+ {/* <pre>{JSON.stringify(task,null,2)}</pre> */}
227
+ {/* <pre>{JSON.stringify(run,null,2)}</pre> */}
228
+ </Container>
229
+ </>;
230
+ }
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ import { Menu, MenuItem, MenuButton, Dropdown } from '@mui/joy';
4
+ import * as React from 'react';
5
+ import Button from '@mui/joy/Button';
6
+ // import Divider from '@mui/joy/Divider';
7
+
8
+ import { deleteRun } from "./action";
9
+ export default function DropdownActions({
10
+ run
11
+ }) {
12
+ async function onClickCreateVersion() {}
13
+ async function onClickCopyRerunAsCurl() {
14
+ let href = window.location.href;
15
+ let baseUrl = href.split('/runs/')[0].replace('/tasks/', '/api/tasks/');
16
+
17
+ // Convert input object to URL query parameters
18
+ const queryParams = new URLSearchParams();
19
+ Object.entries(run?.inputs).forEach(([key, value]) => {
20
+ queryParams.append(key, value);
21
+ });
22
+ const curlCommand = `curl -X POST "${baseUrl}?${queryParams.toString()}"`;
23
+ await navigator.clipboard.writeText(curlCommand);
24
+ }
25
+ return <>
26
+ <Dropdown size='sm'>
27
+ <MenuButton aria-label="master-data more options" variant="outlined" color="primary" size="sm" sx={{
28
+ // Add these styles to match IconButton
29
+ aspectRatio: '1',
30
+ // borderRadius: '50%',
31
+ p: 0.5,
32
+ display: 'inline-flex',
33
+ alignItems: 'center',
34
+ justifyContent: 'center'
35
+ }} onClick={event => {
36
+ event.preventDefault();
37
+ event.stopPropagation();
38
+ }}><i class="fa-solid fa-ellipsis-vertical fa-lg"></i></MenuButton>
39
+ <Menu placement='bottom-end'>
40
+ <MenuItem disabled onClick={onClickCreateVersion}>Rerun job</MenuItem>
41
+ <MenuItem onClick={onClickCopyRerunAsCurl}>Copy rerun as curl</MenuItem>
42
+ {/* <MenuItem color='danger' onClick={()=>{setOpen(true);}}>Delete Part</MenuItem> */}
43
+ </Menu>
44
+ </Dropdown>
45
+ </>;
46
+ }
@@ -0,0 +1,35 @@
1
+ "use server";
2
+
3
+ // import analytics from '@/lib/analytics';
4
+ // import db from '@/database';
5
+
6
+ // export async function deleteRun(run) {
7
+ // // const user = await loginRequired();
8
+ // // analytics.track({event: 'part_delete', user: user.id, org: part.org});
9
+
10
+ // // Check for existing versions
11
+ // const versions = await db.PartVersions.findAll({
12
+ // where: { quote: part.id},
13
+ // raw: true
14
+ // });
15
+
16
+ // if (versions.length) {
17
+ // return {
18
+ // status: 'failed',
19
+ // message: 'Part contains versions. Please delete the versions first.'
20
+ // };
21
+ // } else {
22
+ // try {
23
+ // await db.Parts.destroy({
24
+ // where: { id: part.id }
25
+ // });
26
+
27
+ // return {
28
+ // status: 'done',
29
+ // message: 'Part deleted.'
30
+ // };
31
+ // } catch (error) {
32
+ // throw error;
33
+ // }
34
+ // }
35
+ // }
@@ -0,0 +1,43 @@
1
+ import getTaskDetails from "../../../../../lib/getTaskDetails";
2
+ import ViewRun from "./ViewRun";
3
+ import async from 'async';
4
+ import microlightDB from "../../../../../database/microlight";
5
+ import { notFound } from "next/navigation";
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
+ getRun: async function () {
21
+ let run = await microlightDB.Runs.findOne({
22
+ where: {
23
+ task: params.slug,
24
+ id: params.r_id
25
+ }
26
+ });
27
+ if (!run) return notFound();
28
+ return run.toJSON();
29
+ },
30
+ getLogs: async function () {
31
+ let logs = await microlightDB.Logs.findAll({
32
+ where: {
33
+ run: params.r_id
34
+ },
35
+ order: [['created_at', 'ASC']]
36
+ });
37
+ logs = logs.map(l => l.toJSON());
38
+ return logs;
39
+ }
40
+ };
41
+ let results = await async.auto(workflow);
42
+ return <ViewRun params={params} searchParams={searchParams} task={results.getTask} run={results.getRun} logs={results.getLogs} />;
43
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * there is an issue with mui icons.
3
+ * manually setting the icons.
4
+ * the svgs of icons are picked up from fontawesome
5
+ * https://fontawesome.com/search?ic=free
6
+ */
7
+ import React from 'react';
8
+ import { AspectRatio } from '@mui/joy';
9
+ export default function Icon({
10
+ color,
11
+ icon
12
+ }) {
13
+ return <>
14
+ <AspectRatio ratio="1" sx={{
15
+ width: 20
16
+ }} variant="plain">
17
+ {icon == 'send' && <svg fill={color} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M498.1 5.6c10.1 7 15.4 19.1 13.5 31.2l-64 416c-1.5 9.7-7.4 18.2-16 23s-18.9 5.4-28 1.6L284 427.7l-68.5 74.1c-8.9 9.7-22.9 12.9-35.2 8.1S160 493.2 160 480l0-83.6c0-4 1.5-7.8 4.2-10.8L331.8 202.8c5.8-6.3 5.6-16-.4-22s-15.7-6.4-22-.7L106 360.8 17.7 316.6C7.1 311.3 .3 300.7 0 288.9s5.9-22.8 16.1-28.7l448-256c10.7-6.1 23.9-5.5 34 1.4z" /></svg>}
18
+ {icon == 'folder' && <svg fill={color} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 96C0 60.7 28.7 32 64 32l132.1 0c19.1 0 37.4 7.6 50.9 21.1L289.9 96 448 96c35.3 0 64 28.7 64 64l0 256c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM64 80c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l384 0c8.8 0 16-7.2 16-16l0-256c0-8.8-7.2-16-16-16l-161.4 0c-10.6 0-20.8-4.2-28.3-11.7L213.1 87c-4.5-4.5-10.6-7-17-7L64 80z" /></svg>}
19
+ {icon == 'ellipsis-vertical' && <svg fill={color} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 512"><path d="M64 360a56 56 0 1 0 0 112 56 56 0 1 0 0-112zm0-160a56 56 0 1 0 0 112 56 56 0 1 0 0-112zM120 96A56 56 0 1 0 8 96a56 56 0 1 0 112 0z" /></svg>}
20
+ </AspectRatio>
21
+ </>;
22
+ }
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { Link as JoyLink } from '@mui/joy';
5
+ import NextLink from 'next/link';
6
+ import PropTypes from 'prop-types';
7
+ export default function Link({
8
+ href = '/',
9
+ sx = {},
10
+ target = '_self',
11
+ onClick = () => {},
12
+ children,
13
+ Component = null
14
+ }) {
15
+ if (Component) {
16
+ return <NextLink href={href} target={target} passHref>
17
+ <Component onClick={e => {
18
+ if (onClick) {
19
+ onClick(e);
20
+ }
21
+ }} sx={sx}>
22
+ {children}
23
+ </Component>
24
+ </NextLink>;
25
+ }
26
+ const defaultSx = {
27
+ backgroundColor: "transparent",
28
+ "&:hover": {
29
+ backgroundColor: "none"
30
+ }
31
+ };
32
+ return <JoyLink color="neutral" component={NextLink} underline="none" target={target} href={href} sx={{
33
+ ...defaultSx,
34
+ ...sx
35
+ }} onClick={e => {
36
+ if (onClick) {
37
+ onClick(e);
38
+ }
39
+ }}>
40
+ {children}
41
+ </JoyLink>;
42
+ }
43
+
44
+ // For Storybook Documentation
45
+ Link.propTypes = {
46
+ children: PropTypes.node.isRequired,
47
+ sx: PropTypes.object,
48
+ onClick: PropTypes.func,
49
+ Component: PropTypes.elementType,
50
+ href: PropTypes.string.isRequired,
51
+ target: PropTypes.oneOf(['_self', '_blank', '_parent', '_top'])
52
+ };
@@ -0,0 +1,29 @@
1
+ import { FormControl, FormLabel, Input, Select, Option } from "@mui/joy";
2
+ export default function MLInput({
3
+ def,
4
+ slug,
5
+ searchParams
6
+ }) {
7
+ return <>
8
+ <FormControl required={def.required} sx={{
9
+ mb: 2
10
+ }}>
11
+ <FormLabel sx={{
12
+ mb: 0.25
13
+ }}>{def.name}:</FormLabel>
14
+ {['string', 'number', 'date'].includes(def.type) && <Input size="sm" name={slug} placeholder={def.placeholder} defaultValue={searchParams[slug] || def.default || ""} type={def.type} />}
15
+ {def.type === 'file' &&
16
+ // Placeholder for future file input implementation
17
+ <Input {...commonProps} type="file"
18
+ // Add any file-specific props here
19
+ />}
20
+ {def.type === 'dropdown' && <Select size="sm" name={slug} placeholder={def.placeholder} defaultValue={searchParams[slug] || def.default || ""}>
21
+ {def.options?.map(option => <Option key={option.value} value={option.value}>
22
+ {option.label || option.value}
23
+ </Option>)}
24
+ </Select>}
25
+ </FormControl>
26
+ {/* string input - {slug} <br/> */}
27
+ {/* <pre>{JSON.stringify(def,null,2)}</pre> */}
28
+ </>;
29
+ }
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+
3
+ // import Logo from '../Logo';
4
+ import { Box, Sheet } from '@mui/joy';
5
+ export default function Navbar({
6
+ user,
7
+ signOut
8
+ }) {
9
+ return <Sheet component="nav" sx={{
10
+ px: 1,
11
+ // py: 0.5,
12
+ display: 'flex',
13
+ alignItems: 'center',
14
+ justifyContent: 'space-between',
15
+ // boxShadow: 'sm',
16
+ borderBottom: '1px solid',
17
+ borderColor: 'divider',
18
+ position: 'fixed',
19
+ top: 0,
20
+ left: 0,
21
+ right: 0,
22
+ height: '40px',
23
+ zIndex: 1000,
24
+ bgcolor: 'background.surface'
25
+ // bgcolor: 'white',
26
+ }}>
27
+ <Box sx={{
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ gap: 1
31
+ }}>
32
+ {/* <Logo offering='Transactions' /> */}
33
+ Microlight
34
+ </Box>
35
+
36
+ </Sheet>;
37
+ }
38
+ ;
@@ -0,0 +1,26 @@
1
+ import Navbar from "./Navbar";
2
+ import { Box } from "@mui/joy";
3
+ export default function NavbarContainer({
4
+ children
5
+ }) {
6
+ return <>
7
+ <Navbar />
8
+ <Box component="main" sx={{
9
+ pt: '40px',
10
+ display: 'flex'
11
+ }}>
12
+ <Box sx={{
13
+ flex: 1,
14
+ px: {
15
+ xs: 1,
16
+ sm: 1.5
17
+ },
18
+ pt: 0.5,
19
+ pb: 0,
20
+ minWidth: 0
21
+ }}>
22
+ {children}
23
+ </Box>
24
+ </Box>
25
+ </>;
26
+ }
@@ -0,0 +1,87 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { Box, Typography, Breadcrumbs } from '@mui/joy';
5
+ import { Link as JoyLink } from '@mui/joy';
6
+ import NextLink from 'next/link';
7
+
8
+ // Create a basic link component in switchless and import it here
9
+ const BreadcrumbLink = ({
10
+ href,
11
+ children
12
+ }) => {
13
+ return <JoyLink color="primary" component={NextLink} href={href}>
14
+ {children}
15
+ </JoyLink>;
16
+ };
17
+ const BreadcrumbsCustom = function ({
18
+ breadcrumbs
19
+ }) {
20
+ return <Breadcrumbs separator="›" aria-label="breadcrumbs" sx={{
21
+ p: 0
22
+ }}>
23
+ {breadcrumbs.map((b, index) => <React.Fragment key={index}>
24
+ {b.href ? <BreadcrumbLink key={b.href} href={b.href}>
25
+ {b.text}
26
+ </BreadcrumbLink> : <Typography key={b.text}>{b.text}</Typography>}
27
+ </React.Fragment>)}
28
+ </Breadcrumbs>;
29
+ };
30
+ export default function PageHeader({
31
+ header = "PageHeader",
32
+ RightButtons = null,
33
+ level = 'h3',
34
+ headerLevel = null,
35
+ breadcrumbs = null
36
+ }) {
37
+ if (headerLevel) level = headerLevel;
38
+ const renderHeader = () => {
39
+ if (React.isValidElement(header)) {
40
+ return header; // Return React component as is
41
+ } else if (typeof header === 'string') {
42
+ return <Typography level={level}>{header}</Typography>;
43
+ } else if (typeof header === 'object') {
44
+ const headerParts = Object.values(header);
45
+ return <Typography level={level}>
46
+ {headerParts.length === 1 ? <span>
47
+ {headerParts[0]}
48
+ </span> : headerParts.map((part, index) => <span key={index} style={{
49
+ opacity: index === 1 ? 1 : 0.5
50
+ }}>
51
+ {part}{index < headerParts.length - 1 && ' '}
52
+ </span>)}
53
+ </Typography>;
54
+ }
55
+ return null;
56
+ };
57
+ return <Box data-cy='page-header' sx={{
58
+ display: 'flex',
59
+ flexDirection: {
60
+ xs: 'column',
61
+ sm: 'row'
62
+ },
63
+ alignItems: {
64
+ xs: 'flex-start',
65
+ sm: 'center'
66
+ },
67
+ gap: {
68
+ xs: 1,
69
+ sm: 1
70
+ },
71
+ pt: 0.5
72
+ }}>
73
+ <Box sx={{
74
+ flexGrow: 1
75
+ }}>
76
+ {breadcrumbs && <BreadcrumbsCustom breadcrumbs={breadcrumbs} />}
77
+ {renderHeader()}
78
+ </Box>
79
+ {RightButtons && <Box sx={{
80
+ flexGrow: 0,
81
+ width: 'auto',
82
+ margin: "auto 0"
83
+ }}>
84
+ {typeof RightButtons === 'function' ? <RightButtons /> : RightButtons}
85
+ </Box>}
86
+ </Box>;
87
+ }
@@ -0,0 +1,11 @@
1
+ import { Chip } from '@mui/joy';
2
+ function chipStatusColor(status) {
3
+ if (status === 'complete') return 'success';else if (status === 'pending') return 'warning';else if (status === 'running') return 'primary';else return 'danger';
4
+ }
5
+ export default function StatusChip({
6
+ status
7
+ }) {
8
+ return <Chip variant="soft" color={chipStatusColor(status)} size="sm">
9
+ {status || 'pending'}
10
+ </Chip>;
11
+ }
@@ -0,0 +1,5 @@
1
+ export default function Test() {
2
+ return <>
3
+ this is a test component
4
+ </>;
5
+ }
@@ -0,0 +1,8 @@
1
+ "use client";
2
+
3
+ import { AppProgressBar } from 'next-nprogress-bar';
4
+ export default function TopLoader() {
5
+ return <AppProgressBar color="#0d99fd" height="4px" options={{
6
+ showSpinner: false
7
+ }} />;
8
+ }