Task Manager related components placed in this repo

This commit is contained in:
2024-10-06 00:07:08 +05:30
parent 1350f41496
commit eaeb00c564
12 changed files with 476 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
.ar-TaskList {
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import TaskList from "./TaskList"
describe("TaskList", () => {
it("renders without error", () => {
})
})

View File

@@ -0,0 +1,107 @@
import { useEffect, useState } from "react"
import {
ArButtonVariants,
ArPlacement,
ArSizes,
FunctionType,
Task,
TaskListProps,
} from "@armco/types"
import { Button, SidePanel } from ".."
import "./TaskList.component.scss"
interface TaskItemProps {
setSelectedTask: FunctionType
task: Task
}
const TaskItem = (props: TaskItemProps) => {
const { setSelectedTask, task } = props
return (
<Button
variant={ArButtonVariants.LINK}
onClick={() => setSelectedTask(task)}
content={task.name}
/>
)
}
const TaskList = (props: TaskListProps): JSX.Element => {
const { list, taskFetcher } = props
const [selectedTask, setSelectedTask] = useState<Task>()
const [taskViewerDisplayed, displayTaskViewer] = useState<boolean>()
const listRenders =
list &&
list.map((task) => {
return <TaskItem task={task} setSelectedTask={setSelectedTask} />
})
const hasListItems = list && list.length > 0
useEffect(() => {
if (selectedTask) {
displayTaskViewer(true)
}
}, [selectedTask])
return (
<div
className={`ar-TaskList h-100 container d-flex px-0 w-100
${!hasListItems ? " border bg" : ""}`}
>
{hasListItems ? (
<div
className={`d-flex h-100 flex-column flex-1${
taskViewerDisplayed ? " me-3" : ""
}`}
>
<div className="px-3 py-2 mb-3 bg border">
<Button
classes="float-end"
content="Create"
preIcon="md/MdAddCircleOutline"
variant={ArButtonVariants.SUCCESS}
size={ArSizes.SMALL}
onClick={() => displayTaskViewer(true)}
/>
</div>
<div className="ar-TaskList__main border p-3 flex-1 bg">
{listRenders}
</div>
</div>
) : (
<>
<div className="ar-TaskList__no-tasks-prompt m-3 flex-grow-1">
<small className="ar-TaskList__no-tasks-help-text d-inline-block mb-3">
You have not created any tasks yet, create your first one now.
</small>
<Button
content="Create"
preIcon="md/MdAddCircleOutline"
variant={ArButtonVariants.SUCCESS}
size={ArSizes.SMALL}
onClick={() => displayTaskViewer(true)}
/>
</div>
</>
)}
{taskViewerDisplayed && (
<SidePanel
placement={ArPlacement.RIGHT}
componentName="TaskViewer"
componentProps={{
hideSelf: () => {
setSelectedTask(undefined)
displayTaskViewer(false)
},
isNew: !selectedTask,
selectedTask,
taskFetcher,
}}
/>
)}
</div>
)
}
export default TaskList

View File

@@ -0,0 +1,3 @@
import TaskList from "./TaskList"
export default TaskList

View File

@@ -0,0 +1,3 @@
.ar-TaskLoginPrompt {
background-color: var(--ar-bg);
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import TaskLoginPrompt from "./TaskLoginPrompt"
describe("TaskLoginPrompt", () => {
it("renders without error", () => {
})
})

View File

@@ -0,0 +1,61 @@
import { ArButtonVariants, TaskLoginPromptProps } from "@armco/types"
import { Button, LoadableIcon, setRightPanelContent, useAppDispatch } from ".."
import "./TaskLoginPrompt.component.scss"
const TaskLoginPrompt = (props: TaskLoginPromptProps): JSX.Element => {
const dispatch = useAppDispatch()
return (
<div className="ar-TaskLoginPrompt p-3 border container">
<div className="flex-h-center flex-column">
<h6 className="flex-v-center" style={{ color: "#ffbf00" }}>
<LoadableIcon
classes="me-2"
icon="fa/FaExclamationTriangle"
color="#ffbf00"
/>
Please login to continue
</h6>
<small>
This is the tasker where you can create and schedule simple tasks,
represented by an endpoint.
</small>
<small>
Created tasks can be run by configuring a crontab format, or can be
run manually.
</small>
<small>
Typical use cases:{" "}
<span className="fw-bold">
API health check, cloud configuration refresh, cache refresh, etc.
</span>
</small>
<small>
Understand that this feature only calls an endpoint at provided
intervals, maintaining and managing these APIs is entirely your
responsibility.
</small>
<small className="mb-3 fw-bold">
It is necessary that this feature is used responsibly. Please only
provide endpoints that you own or administer.
</small>
<small className="mb-3">
<strong>
In order to be able to save and schedule these tasks, you need to
create an account, if one doesn't exist already and login using the
same. Please use the button below to continue.
</strong>
</small>
</div>
<Button
content="Login"
variant={ArButtonVariants.SUCCESS}
postIcon="io5/IoArrowForwardCircle"
onClick={() => {
dispatch(setRightPanelContent({ name: "LoginProvider" }))
}}
/>
</div>
)
}
export default TaskLoginPrompt

View File

@@ -0,0 +1,3 @@
import TaskLoginPrompt from "./TaskLoginPrompt"
export default TaskLoginPrompt

View File

@@ -0,0 +1,12 @@
.ar-TaskViewer {
min-width: 20rem;
background-color: var(--ar-bg-base);
header {
height: unset;
}
.ar-TaskViewer__close-button {
cursor: pointer;
top: 1rem;
right: 1rem;
}
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import TaskViewer from "./TaskViewer"
describe("TaskViewer", () => {
it("renders without error", () => {
})
})

View File

@@ -0,0 +1,257 @@
import { ChangeEvent, useEffect, useState } from "react"
import { v4 as uuid } from "uuid"
import {
ArAlertType,
ArButtonVariants,
ArSizes,
CronType,
JobStatus,
ObjectType,
Task,
TaskViewerProps,
User,
} from "@armco/types"
import {
API_CONFIG,
Button,
Card,
Checkbox,
CronTab,
DatePicker,
Dropdown,
ENDPOINTS,
getUser,
LoadableIcon,
Network,
notify,
TextArea,
TextInput,
useAppDispatch,
useAppSelector,
Validator,
} from ".."
import "./TaskViewer.component.scss"
const emptyTask = (user?: User) => ({
owner: user?.email || "",
schedule: { cron: "" },
target: { endpoint: "" },
name: "",
status: JobStatus.STOPPED,
})
const TaskViewer = (props: TaskViewerProps): JSX.Element => {
const { classes, hideSelf, isNew, selectedTask, taskFetcher } = props
const user = useAppSelector<User | undefined>(getUser)
const [formData, setFormData] = useState<
| {
[key: string]: string | number | boolean | CronType
}
| Task
>({})
const dispatch = useAppDispatch()
useEffect(() => {
if (selectedTask) {
setFormData(selectedTask)
}
}, [selectedTask])
const change = (field: string, value: string | number | boolean) => {
setFormData({ ...formData, [field]: value })
}
const submitTaskDetails = () => {
const payload: Task = formData
? (JSON.parse(JSON.stringify(formData)) as Task)
: emptyTask(user)
let cron: CronType | string = formData.cron as CronType
if (cron) {
if (!payload.schedule) {
payload.schedule = { cron: "" }
}
payload.schedule.cron =
(cron.min || "*") +
(cron.hour || "*") +
(cron.day || "*") +
(cron.week || "*") +
(cron.month || "*")
}
payload.name = formData.name as string
payload.client = formData.client as string
payload.description = formData.description as string
payload.target = {
endpoint: (typeof formData.target === "string"
? formData.target
: (formData as Task).target.endpoint) as string,
}
const operation = isNew ? Network.post : Network.put
if (Validator.validateTask(payload)) {
operation(
API_CONFIG.TASKER[process.env.NODE_ENV] +
ENDPOINTS.TASKER.ROOT +
ENDPOINTS.TASKER.SAVE,
{ job: payload },
)
.then((res) => {
if (res.status === (isNew ? 201 : 200)) {
dispatch(
notify({
show: true,
message: `${
isNew ? "Registered" : "Updated"
} cron task successfully`,
type: ArAlertType.SUCCESS,
uid: uuid(),
}),
)
taskFetcher()
hideSelf()
}
})
.catch((e) => {
if (e.status === 409) {
dispatch(
notify({
show: true,
message: "Job with give name already exists!",
type: ArAlertType.ERROR,
uid: uuid(),
}),
)
} else {
dispatch(
notify({
show: true,
message: "Failed to register the task",
type: ArAlertType.ERROR,
uid: uuid(),
}),
)
}
})
} else {
dispatch(
notify({
show: true,
message: "Please review and fix the task details",
type: ArAlertType.ERROR,
uid: uuid(),
}),
)
}
}
return (
<div
className={`ar-TaskViewer p-3 overflow-auto h-100 position-relative border${
classes ? " " + classes : ""
}`}
>
<LoadableIcon
classes="ar-TaskViewer__close-button position-absolute"
icon="io5/IoClose"
onClick={hideSelf}
/>
<header className="mb-3 fw-bold h6">Task Details</header>
<Card>
<TextInput
classes="mb-3 w-100"
label="Name"
onChange={(e) => change("name", e.target.value)}
value={formData.name as string}
required
/>
<TextInput
classes="mb-3 w-100"
label="Client"
onChange={(e) => change("client", e.target.value)}
value={formData.client as string}
required
/>
<TextArea
classes="mb-3"
label="Description"
onChange={(e: ChangeEvent<unknown>) =>
change("description", (e.target as HTMLTextAreaElement).value)
}
value={formData.description as string}
/>
<TextInput
classes="w-100"
label="Target"
type="url"
onChange={(e) => change("target", e.target.value)}
required
value={
formData.target
? ((typeof formData.target === "string"
? formData.target
: (formData as Task).target.endpoint) as string)
: ""
}
/>
</Card>
<div className="my-3 border" />
<Card classes="mb-3">
<strong className="d-inline-block mb-3">
Provide scheduling details
</strong>
{/* <Date classes="mb-3 w-100" isRangeSelector /> */}
<div className="flex-v-center mb-3">
<span className="me-3 fw-bold">Every</span>
<div className="d-inline-block me-3">
<TextInput
type="number"
min={1}
onChange={(e) => change("frequency", e.target.value)}
value={formData.frequency as string}
/>
</div>
<div className="d-inline-block">
<Dropdown
options={[
{ label: "minutes" },
{ label: "hours" },
{ label: "days" },
{ label: "weeks" },
{ label: "months" },
]}
onSelectionChanged={(input: {
value: string
data: ObjectType
}) => change("name", input.value)}
/>
</div>
</div>
<div className="flex-v-center mb-3">
<span className="me-3 fw-bold">Starting</span>
<DatePicker />
</div>
</Card>
<Card>
<strong className="d-inline-block mb-3">
Or provide a crontab in the format * * * * 3 * (takes precedence)
</strong>
{/* <TextInput classes="w-100" label="CronTab" /> */}
<CronTab
onChange={(cronState) => change("cron", cronState)}
value={formData.schedule as { cron: string }}
/>
</Card>
<div className="my-3 border" />
<div className="d-flex justify-content-between">
<Checkbox label="Start Immediately?" onChange={() => {}} />
<Button
classes="float-end"
content="Submit"
variant={ArButtonVariants.SUCCESS}
size={ArSizes.SMALL}
onClick={submitTaskDetails}
/>
</div>
</div>
)
}
export default TaskViewer

View File

@@ -0,0 +1,3 @@
import TaskViewer from "./TaskViewer"
export default TaskViewer