Task Manager related components placed in this repo
This commit is contained in:
3
src/app/components/TaskList/TaskList.component.scss
Executable file
3
src/app/components/TaskList/TaskList.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-TaskList {
|
||||
|
||||
}
|
||||
8
src/app/components/TaskList/TaskList.test.ts
Executable file
8
src/app/components/TaskList/TaskList.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import TaskList from "./TaskList"
|
||||
|
||||
describe("TaskList", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
107
src/app/components/TaskList/TaskList.tsx
Executable file
107
src/app/components/TaskList/TaskList.tsx
Executable 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
|
||||
3
src/app/components/TaskList/index.ts
Executable file
3
src/app/components/TaskList/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import TaskList from "./TaskList"
|
||||
|
||||
export default TaskList
|
||||
3
src/app/components/TaskLoginPrompt/TaskLoginPrompt.component.scss
Executable file
3
src/app/components/TaskLoginPrompt/TaskLoginPrompt.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-TaskLoginPrompt {
|
||||
background-color: var(--ar-bg);
|
||||
}
|
||||
8
src/app/components/TaskLoginPrompt/TaskLoginPrompt.test.ts
Executable file
8
src/app/components/TaskLoginPrompt/TaskLoginPrompt.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import TaskLoginPrompt from "./TaskLoginPrompt"
|
||||
|
||||
describe("TaskLoginPrompt", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
61
src/app/components/TaskLoginPrompt/TaskLoginPrompt.tsx
Executable file
61
src/app/components/TaskLoginPrompt/TaskLoginPrompt.tsx
Executable 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
|
||||
3
src/app/components/TaskLoginPrompt/index.ts
Executable file
3
src/app/components/TaskLoginPrompt/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import TaskLoginPrompt from "./TaskLoginPrompt"
|
||||
|
||||
export default TaskLoginPrompt
|
||||
12
src/app/components/TaskViewer/TaskViewer.component.scss
Executable file
12
src/app/components/TaskViewer/TaskViewer.component.scss
Executable 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;
|
||||
}
|
||||
}
|
||||
8
src/app/components/TaskViewer/TaskViewer.test.ts
Executable file
8
src/app/components/TaskViewer/TaskViewer.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import TaskViewer from "./TaskViewer"
|
||||
|
||||
describe("TaskViewer", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
257
src/app/components/TaskViewer/TaskViewer.tsx
Executable file
257
src/app/components/TaskViewer/TaskViewer.tsx
Executable 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
|
||||
3
src/app/components/TaskViewer/index.ts
Executable file
3
src/app/components/TaskViewer/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import TaskViewer from "./TaskViewer"
|
||||
|
||||
export default TaskViewer
|
||||
Reference in New Issue
Block a user