mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-01-16 23:00:51 +00:00
feat: Add CardSelect component and integrate with Monitor creation form
This commit is contained in:
parent
3631a48d83
commit
d2c2f66b66
7 changed files with 165 additions and 3 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import BadDataException from "../Exception/BadDataException";
|
||||
import IconProp from "../Icon/IconProp";
|
||||
|
||||
enum MonitorType {
|
||||
Manual = "Manual",
|
||||
|
|
@ -30,6 +31,7 @@ export interface MonitorTypeProps {
|
|||
monitorType: MonitorType;
|
||||
description: string;
|
||||
title: string;
|
||||
icon: IconProp;
|
||||
}
|
||||
|
||||
export class MonitorTypeHelper {
|
||||
|
|
@ -53,24 +55,28 @@ export class MonitorTypeHelper {
|
|||
title: "API",
|
||||
description:
|
||||
"This monitor type lets you monitor any API - GET, POST, PUT, DELETE or more.",
|
||||
icon: IconProp.Code,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Manual,
|
||||
title: "Manual",
|
||||
description:
|
||||
"This monitor is a static monitor and will not actually monitor anything. It will however help you to integrate OneUptime with external monitoring tools and utilities.",
|
||||
icon: IconProp.Wrench,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Website,
|
||||
title: "Website",
|
||||
description:
|
||||
"This monitor type lets you monitor landing pages like home page of your company / blog or more.",
|
||||
icon: IconProp.Globe,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Ping,
|
||||
title: "Ping",
|
||||
description:
|
||||
"This monitor type does the basic ping test of an endpoint.",
|
||||
icon: IconProp.Signal,
|
||||
},
|
||||
/*
|
||||
* {
|
||||
|
|
@ -78,6 +84,7 @@ export class MonitorTypeHelper {
|
|||
* title: 'Kubernetes',
|
||||
* description:
|
||||
* 'This monitor types lets you monitor Kubernetes clusters.',
|
||||
* icon: IconProp.Cube,
|
||||
* },
|
||||
*/
|
||||
{
|
||||
|
|
@ -85,70 +92,82 @@ export class MonitorTypeHelper {
|
|||
title: "IP",
|
||||
description:
|
||||
"This monitor type lets you monitor any IPv4 or IPv6 addresses.",
|
||||
icon: IconProp.AltGlobe,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.IncomingRequest,
|
||||
title: "Incoming Request",
|
||||
description:
|
||||
"This monitor type lets you ping OneUptime from any external device or service with a custom payload.",
|
||||
icon: IconProp.Webhook,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.IncomingEmail,
|
||||
title: "Incoming Email",
|
||||
description:
|
||||
"This monitor type triggers alerts when emails are received at a unique email address with matching criteria.",
|
||||
icon: IconProp.Email,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Port,
|
||||
title: "Port",
|
||||
description: "This monitor type lets you monitor any TCP or UDP port.",
|
||||
icon: IconProp.Terminal,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Server,
|
||||
title: "Server / VM",
|
||||
description:
|
||||
"This monitor type lets you monitor any server, VM, or any machine.",
|
||||
icon: IconProp.Database,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.SSLCertificate,
|
||||
title: "SSL Certificate",
|
||||
description:
|
||||
"This monitor type lets you monitor SSL certificates of any domain.",
|
||||
icon: IconProp.ShieldCheck,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.SyntheticMonitor,
|
||||
title: "Synthetic Monitor",
|
||||
description:
|
||||
"This monitor type lets you monitor your web application UI.",
|
||||
icon: IconProp.Window,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.CustomJavaScriptCode,
|
||||
title: "Custom JavaScript Code",
|
||||
description:
|
||||
"This monitor type lets you run custom JavaScript code on a schedule.",
|
||||
icon: IconProp.Code,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Logs,
|
||||
title: "Logs",
|
||||
description: "This monitor type lets you monitor logs from any source.",
|
||||
icon: IconProp.Logs,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Exceptions,
|
||||
title: "Exceptions",
|
||||
description:
|
||||
"This monitor type lets you monitor exceptions and error groups from any source.",
|
||||
icon: IconProp.Bug,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Traces,
|
||||
title: "Traces",
|
||||
description:
|
||||
"This monitor type lets you monitor traces from any source.",
|
||||
icon: IconProp.Activity,
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Metrics,
|
||||
title: "Metrics",
|
||||
description:
|
||||
"This monitor type lets you monitor metrics from any source.",
|
||||
icon: IconProp.ChartBar,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
105
Common/UI/Components/CardSelect/CardSelect.tsx
Normal file
105
Common/UI/Components/CardSelect/CardSelect.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import IconProp from "../../../Types/Icon/IconProp";
|
||||
import Icon, { SizeProp } from "../Icon/Icon";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
export interface CardSelectOption {
|
||||
value: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: IconProp;
|
||||
}
|
||||
|
||||
export interface ComponentProps {
|
||||
options: Array<CardSelectOption>;
|
||||
value?: string | undefined;
|
||||
onChange: (value: string) => void;
|
||||
error?: string | undefined;
|
||||
tabIndex?: number | undefined;
|
||||
dataTestId?: string | undefined;
|
||||
}
|
||||
|
||||
const CardSelect: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<div data-testid={props.dataTestId}>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{props.options.map((option: CardSelectOption, index: number) => {
|
||||
const isSelected: boolean = props.value === option.value;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
tabIndex={props.tabIndex ? props.tabIndex + index : index}
|
||||
onClick={() => {
|
||||
props.onChange(option.value);
|
||||
}}
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
props.onChange(option.value);
|
||||
}
|
||||
}}
|
||||
className={`relative flex cursor-pointer rounded-lg border p-4 shadow-sm focus:outline-none transition-all duration-200 hover:border-indigo-500 hover:shadow-md ${
|
||||
isSelected
|
||||
? "border-indigo-600 ring-2 ring-indigo-600 bg-indigo-50"
|
||||
: "border-gray-300 bg-white"
|
||||
}`}
|
||||
role="radio"
|
||||
aria-checked={isSelected}
|
||||
data-testid={`card-select-option-${option.value}`}
|
||||
>
|
||||
<div className="flex w-full items-start">
|
||||
<div
|
||||
className={`flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg ${
|
||||
isSelected ? "bg-indigo-600" : "bg-gray-100"
|
||||
}`}
|
||||
>
|
||||
<Icon
|
||||
icon={option.icon}
|
||||
size={SizeProp.Large}
|
||||
className={`h-5 w-5 ${
|
||||
isSelected ? "text-white" : "text-gray-600"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-4 flex-1">
|
||||
<span
|
||||
className={`block text-sm font-semibold ${
|
||||
isSelected ? "text-indigo-900" : "text-gray-900"
|
||||
}`}
|
||||
>
|
||||
{option.title}
|
||||
</span>
|
||||
<span
|
||||
className={`mt-1 block text-sm ${
|
||||
isSelected ? "text-indigo-700" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{option.description}
|
||||
</span>
|
||||
</div>
|
||||
{isSelected && (
|
||||
<div className="flex-shrink-0 ml-2">
|
||||
<Icon
|
||||
icon={IconProp.CheckCircle}
|
||||
size={SizeProp.Large}
|
||||
className="h-5 w-5 text-indigo-600"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{props.error && (
|
||||
<p className="mt-2 text-sm text-red-600" role="alert">
|
||||
{props.error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardSelect;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import Dictionary from "../../../../Types/Dictionary";
|
||||
import { GetReactElementFunction } from "../../../Types/FunctionTypes";
|
||||
import CardSelect from "../../CardSelect/CardSelect";
|
||||
import CategoryCheckbox from "../../CategoryCheckbox/Index";
|
||||
import CheckboxElement, {
|
||||
CategoryCheckboxValue,
|
||||
|
|
@ -366,6 +367,25 @@ const FormField: <T extends GenericObject>(
|
|||
/>
|
||||
)}
|
||||
|
||||
{props.field.fieldType === FormFieldSchemaType.CardSelect && (
|
||||
<CardSelect
|
||||
error={props.touched && props.error ? props.error : undefined}
|
||||
tabIndex={index}
|
||||
dataTestId={props.field.dataTestId}
|
||||
onChange={(value: string) => {
|
||||
onChange(value);
|
||||
props.setFieldValue(props.fieldName, value);
|
||||
}}
|
||||
options={props.field.cardSelectOptions || []}
|
||||
value={
|
||||
props.currentValues &&
|
||||
(props.currentValues as any)[props.fieldName]
|
||||
? (props.currentValues as any)[props.fieldName]
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.field.fieldType === FormFieldSchemaType.ObjectID && (
|
||||
<IDGenerator
|
||||
tabIndex={index}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
CategoryCheckboxOption,
|
||||
CheckboxCategory,
|
||||
} from "../../CategoryCheckbox/CategoryCheckboxTypes";
|
||||
import { CardSelectOption } from "../../CardSelect/CardSelect";
|
||||
import { DropdownOption } from "../../Dropdown/Dropdown";
|
||||
import { RadioButton } from "../../RadioButtons/GroupRadioButtons";
|
||||
import FormFieldSchemaType from "./FormFieldSchemaType";
|
||||
|
|
@ -50,6 +51,7 @@ export default interface Field<TEntity> {
|
|||
stepId?: string | undefined;
|
||||
required?: boolean | ((item: FormValues<TEntity>) => boolean) | undefined;
|
||||
dropdownOptions?: Array<DropdownOption> | undefined;
|
||||
cardSelectOptions?: Array<CardSelectOption> | undefined;
|
||||
fetchDropdownOptions?:
|
||||
| ((item: FormValues<TEntity>) => Promise<Array<DropdownOption>>)
|
||||
| undefined;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ enum FormFieldSchemaType {
|
|||
Checkbox = "Checkbox",
|
||||
CategoryCheckbox = "CategoryCheckbox",
|
||||
Dictionary = "Dictionary",
|
||||
CardSelect = "CardSelect",
|
||||
}
|
||||
|
||||
export default FormFieldSchemaType;
|
||||
|
|
|
|||
|
|
@ -67,11 +67,11 @@ const MonitorCreate: FunctionComponent<
|
|||
monitorType: true,
|
||||
},
|
||||
title: "Monitor Type",
|
||||
description: "Select the type of monitor you want to create",
|
||||
stepId: "monitor-info",
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
fieldType: FormFieldSchemaType.CardSelect,
|
||||
required: true,
|
||||
placeholder: "Select Monitor Type",
|
||||
dropdownOptions: MonitorTypeUtil.monitorTypesAsDropdownOptions(),
|
||||
cardSelectOptions: MonitorTypeUtil.monitorTypesAsCardSelectOptions(),
|
||||
},
|
||||
{
|
||||
field: {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
MonitorTypeHelper,
|
||||
MonitorTypeProps,
|
||||
} from "Common/Types/Monitor/MonitorType";
|
||||
import { CardSelectOption } from "Common/UI/Components/CardSelect/CardSelect";
|
||||
import { DropdownOption } from "Common/UI/Components/Dropdown/Dropdown";
|
||||
|
||||
export default class MonitorTypeUtil {
|
||||
|
|
@ -16,4 +17,18 @@ export default class MonitorTypeUtil {
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
public static monitorTypesAsCardSelectOptions(): Array<CardSelectOption> {
|
||||
const monitorTypes: Array<MonitorTypeProps> =
|
||||
MonitorTypeHelper.getAllMonitorTypeProps();
|
||||
|
||||
return monitorTypes.map((props: MonitorTypeProps) => {
|
||||
return {
|
||||
value: props.monitorType,
|
||||
title: props.title,
|
||||
description: props.description,
|
||||
icon: props.icon,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue