mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
@@ -37,6 +37,12 @@ const OSSFriends: OSSFriend[] = [
|
||||
"PostHog is open-source product analytics, built for developers.",
|
||||
repositoryUrl: URL.fromString("https://github.com/PostHog/posthog"),
|
||||
},
|
||||
{
|
||||
name: "Twenty",
|
||||
description:
|
||||
"Twenty is building an open-source modern CRM. It's alternative to Salesforce and HubSpot.",
|
||||
repositoryUrl: URL.fromString("https://github.com/twentyhq/twenty"),
|
||||
},
|
||||
];
|
||||
|
||||
export default OSSFriends;
|
||||
|
||||
@@ -5,16 +5,6 @@ export interface Review {
|
||||
}
|
||||
|
||||
const reviews: Review[] = [
|
||||
{
|
||||
name: "Alice",
|
||||
text: "OneUptime has been a game-changer for our team. The alerts are timely and the status page keeps our customers informed.",
|
||||
title: "Game-changer!",
|
||||
},
|
||||
{
|
||||
name: "Bob",
|
||||
text: "The incident management feature is top-notch. It has streamlined our response process significantly.",
|
||||
title: "Top-notch incident management",
|
||||
},
|
||||
{
|
||||
name: "Charlie",
|
||||
text: "I love the on-call rotation setup. It has made scheduling so much easier.",
|
||||
@@ -75,6 +65,16 @@ const reviews: Review[] = [
|
||||
text: "The log analysis feature is very detailed and insightful.",
|
||||
title: "Detailed log analysis",
|
||||
},
|
||||
{
|
||||
name: "Anderson, GK2 Cloud",
|
||||
text: "Thanks for building OneUptime, it really is fantastic. We are getting more excited every day!",
|
||||
title: "OneUptime is fantastic!",
|
||||
},
|
||||
{
|
||||
name: "Reg, Skillable",
|
||||
text: "We use OneUptime to reliably monitor endpoint availability globally, and it delivers.",
|
||||
title: "OneUptime delivers!",
|
||||
},
|
||||
{
|
||||
name: "Oscar",
|
||||
text: "OneUptime has made our monitoring process much more efficient.",
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
</svg>
|
||||
|
||||
<span><strong class="font-semibold text-gray-900">
|
||||
We don't store or train on your code.
|
||||
We don't see, store or train on your code.
|
||||
</strong>
|
||||
Regardless whether you are on the free tier or enterprise tier, we don't store or train on your code. No part of your code is sent to us.
|
||||
</span>
|
||||
|
||||
@@ -67,6 +67,9 @@ import logger from "CommonServer/Utils/Logger";
|
||||
import "./Jobs/Probe/SendOwnerAddedNotification";
|
||||
import "./Jobs/Probe/UpdateConnectionStatus";
|
||||
|
||||
// Telemetry Monitors.
|
||||
import "./Jobs/TelemetryMonitor/MonitorTelemetryMonitor";
|
||||
|
||||
const WorkersFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
try {
|
||||
|
||||
@@ -186,8 +186,10 @@ const monitorLogs: MonitorLogsFunction = async (data: {
|
||||
query.body = new Search(logQuery.body);
|
||||
}
|
||||
|
||||
if (logQuery.severityText) {
|
||||
query.severityText = logQuery.severityText;
|
||||
if (logQuery.severityTexts && logQuery.severityTexts.length > 0) {
|
||||
query.severityText = QueryHelper.any(
|
||||
logQuery.severityTexts as Array<string>,
|
||||
);
|
||||
}
|
||||
|
||||
if (logQuery.telemetryServiceIds && logQuery.telemetryServiceIds.length > 0) {
|
||||
|
||||
@@ -849,6 +849,49 @@ export default class OneUptimeDate {
|
||||
return months;
|
||||
}
|
||||
|
||||
public static convertSecondsToDaysHoursMinutesAndSeconds(
|
||||
seconds: number,
|
||||
): string {
|
||||
// should output 2 days, 3 hours, 4 minutes and 5 seconds. If the days are 0, it should not show the days. If the hours are 0, it should not show the hours. If the minutes are 0, it should not show the minutes. If the seconds are 0, it should not show the seconds.
|
||||
|
||||
const days: number = Math.floor(seconds / (24 * 60 * 60));
|
||||
const hours: number = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60));
|
||||
const minutes: number = Math.floor((seconds % (60 * 60)) / 60);
|
||||
const secs: number = seconds % 60;
|
||||
|
||||
let formattedString: string = "";
|
||||
|
||||
if (days > 0) {
|
||||
formattedString += days + " days";
|
||||
}
|
||||
|
||||
if (hours > 0) {
|
||||
if (formattedString.length > 0) {
|
||||
formattedString += ", ";
|
||||
}
|
||||
|
||||
formattedString += hours + " hours";
|
||||
}
|
||||
|
||||
if (minutes > 0) {
|
||||
if (formattedString.length > 0) {
|
||||
formattedString += ", ";
|
||||
}
|
||||
|
||||
formattedString += minutes + " minutes";
|
||||
}
|
||||
|
||||
if (secs > 0) {
|
||||
if (formattedString.length > 0) {
|
||||
formattedString += ", ";
|
||||
}
|
||||
|
||||
formattedString += secs + " seconds";
|
||||
}
|
||||
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
public static convertMinutesToDaysHoursAndMinutes(minutes: number): string {
|
||||
// should output 2 days, 3 hours and 4 minutes. If the days are 0, it should not show the days. If the hours are 0, it should not show the hours. If the minutes are 0, it should not show the minutes.
|
||||
|
||||
|
||||
@@ -196,16 +196,6 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
return "Monitor Destination is required.";
|
||||
}
|
||||
|
||||
if (monitorType === MonitorType.Logs) {
|
||||
if (!value.data.logMonitor) {
|
||||
return "Log Monitor is required";
|
||||
}
|
||||
|
||||
if (!value.data.logMonitor.lastXSecondsOfLogs) {
|
||||
return "Monitor Last Minutes of Logs is required.";
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!value.data.customCode &&
|
||||
(monitorType === MonitorType.CustomJavaScriptCode ||
|
||||
@@ -265,7 +255,9 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
screenSizeTypes: this.data.screenSizeTypes || undefined,
|
||||
browserTypes: this.data.browserTypes || undefined,
|
||||
logMonitor: this.data.logMonitor
|
||||
? MonitorStepLogMonitorUtil.toJSON(this.data.logMonitor)
|
||||
? MonitorStepLogMonitorUtil.toJSON(
|
||||
this.data.logMonitor || MonitorStepLogMonitorUtil.getDefault(),
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
@@ -360,6 +352,10 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
: undefined,
|
||||
}) as any;
|
||||
|
||||
if (monitorStep.data && !monitorStep.data?.logMonitor) {
|
||||
monitorStep.data.logMonitor = MonitorStepLogMonitorUtil.getDefault();
|
||||
}
|
||||
|
||||
return monitorStep;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import Log from "../../Models/AnalyticsModels/Log";
|
||||
import InBetween from "../BaseDatabase/InBetween";
|
||||
import Includes from "../BaseDatabase/Includes";
|
||||
import Query from "../BaseDatabase/Query";
|
||||
import Search from "../BaseDatabase/Search";
|
||||
import OneUptimeDate from "../Date";
|
||||
import Dictionary from "../Dictionary";
|
||||
import { JSONObject } from "../JSON";
|
||||
import LogSeverity from "../Log/LogSeverity";
|
||||
@@ -6,17 +12,59 @@ import ObjectID from "../ObjectID";
|
||||
export default interface MonitorStepLogMonitor {
|
||||
attributes: Dictionary<string | number | boolean>;
|
||||
body: string;
|
||||
severityText: Array<LogSeverity>;
|
||||
severityTexts: Array<LogSeverity>;
|
||||
telemetryServiceIds: Array<ObjectID>;
|
||||
lastXSecondsOfLogs: number;
|
||||
}
|
||||
|
||||
export class MonitorStepLogMonitorUtil {
|
||||
public static toQuery(
|
||||
monitorStepLogMonitor: MonitorStepLogMonitor,
|
||||
): Query<Log> {
|
||||
const query: Query<Log> = {};
|
||||
|
||||
if (
|
||||
monitorStepLogMonitor.telemetryServiceIds &&
|
||||
monitorStepLogMonitor.telemetryServiceIds.length > 0
|
||||
) {
|
||||
query.serviceId = new Includes(monitorStepLogMonitor.telemetryServiceIds);
|
||||
}
|
||||
|
||||
if (
|
||||
monitorStepLogMonitor.attributes &&
|
||||
Object.keys(monitorStepLogMonitor.attributes).length > 0
|
||||
) {
|
||||
query.attributes = monitorStepLogMonitor.attributes;
|
||||
}
|
||||
|
||||
if (
|
||||
monitorStepLogMonitor.severityTexts &&
|
||||
monitorStepLogMonitor.severityTexts.length > 0
|
||||
) {
|
||||
query.severityText = new Includes(monitorStepLogMonitor.severityTexts);
|
||||
}
|
||||
|
||||
if (monitorStepLogMonitor.body) {
|
||||
query.body = new Search(monitorStepLogMonitor.body);
|
||||
}
|
||||
|
||||
if (monitorStepLogMonitor.lastXSecondsOfLogs) {
|
||||
const endDate: Date = OneUptimeDate.getCurrentDate();
|
||||
const startDate: Date = OneUptimeDate.addRemoveSeconds(
|
||||
endDate,
|
||||
monitorStepLogMonitor.lastXSecondsOfLogs * -1,
|
||||
);
|
||||
query.time = new InBetween(startDate, endDate);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public static getDefault(): MonitorStepLogMonitor {
|
||||
return {
|
||||
attributes: {},
|
||||
body: "",
|
||||
severityText: [],
|
||||
severityTexts: [],
|
||||
telemetryServiceIds: [],
|
||||
lastXSecondsOfLogs: 60,
|
||||
};
|
||||
@@ -27,7 +75,7 @@ export class MonitorStepLogMonitorUtil {
|
||||
attributes:
|
||||
(json["attributes"] as Dictionary<string | number | boolean>) || {},
|
||||
body: json["body"] as string,
|
||||
severityText: json["severityText"] as Array<LogSeverity>,
|
||||
severityTexts: json["severityTexts"] as Array<LogSeverity>,
|
||||
telemetryServiceIds: ObjectID.fromJSONArray(
|
||||
json["telemetryServiceIds"] as Array<JSONObject>,
|
||||
),
|
||||
@@ -39,8 +87,8 @@ export class MonitorStepLogMonitorUtil {
|
||||
return {
|
||||
attributes: monitor.attributes,
|
||||
body: monitor.body,
|
||||
severityText: monitor.severityText,
|
||||
telemetryServiceId: ObjectID.toJSONArray(monitor.telemetryServiceIds),
|
||||
severityTexts: monitor.severityTexts,
|
||||
telemetryServiceIds: ObjectID.toJSONArray(monitor.telemetryServiceIds),
|
||||
lastXSecondsOfLogs: monitor.lastXSecondsOfLogs,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,18 +109,19 @@ export class MonitorTypeHelper {
|
||||
title: "Logs",
|
||||
description: "This monitor type lets you monitor logs from any source.",
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Metrics,
|
||||
title: "Metrics",
|
||||
description:
|
||||
"This monitor type lets you monitor metrics from any source.",
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Traces,
|
||||
title: "Traces",
|
||||
description:
|
||||
"This monitor type lets you monitor traces from any source.",
|
||||
},
|
||||
// ,
|
||||
// {
|
||||
// monitorType: MonitorType.Metrics,
|
||||
// title: "Metrics",
|
||||
// description:
|
||||
// "This monitor type lets you monitor metrics from any source.",
|
||||
// },
|
||||
// {
|
||||
// monitorType: MonitorType.Traces,
|
||||
// title: "Traces",
|
||||
// description:
|
||||
// "This monitor type lets you monitor traces from any source.",
|
||||
// },
|
||||
];
|
||||
|
||||
return monitorTypeProps;
|
||||
|
||||
@@ -48,6 +48,10 @@ export default class ObjectID extends DatabaseProperty {
|
||||
}
|
||||
|
||||
public static toJSONArray(ids: Array<ObjectID>): Array<JSONObject> {
|
||||
if (!ids || ids.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ids.map((id: ObjectID) => {
|
||||
if (typeof id === "string") {
|
||||
id = new ObjectID(id);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class MigrationName1722892318363 implements MigrationInterface {
|
||||
public name = "MigrationName1722892318363";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "Monitor" ADD "telemetryMonitorNextMonitorAt" TIMESTAMP WITH TIME ZONE`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "Monitor" ADD "telemetryMonitorLastMonitorAt" TIMESTAMP WITH TIME ZONE`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "Incident" ADD "telemetryQuery" jsonb`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_540a4857bf04eabc59775ca210" ON "Monitor" ("telemetryMonitorNextMonitorAt") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_02bae6cdc6d5b3092ac393fbb3" ON "Monitor" ("telemetryMonitorLastMonitorAt") `,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_02bae6cdc6d5b3092ac393fbb3"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_540a4857bf04eabc59775ca210"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "Incident" DROP COLUMN "telemetryQuery"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "Monitor" DROP COLUMN "telemetryMonitorLastMonitorAt"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "Monitor" DROP COLUMN "telemetryMonitorNextMonitorAt"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import { MigrationName1721754545771 } from "./1721754545771-MigrationName";
|
||||
import { MigrationName1721779190475 } from "./1721779190475-MigrationName";
|
||||
import { MigrationName1722031205897 } from "./1722031205897-MigrationName";
|
||||
import { MigrationName1722543640526 } from "./1722543640526-MigrationName";
|
||||
import { MigrationName1722892318363 } from "./1722892318363-MigrationName";
|
||||
|
||||
export default [
|
||||
InitialMigration,
|
||||
@@ -78,4 +79,5 @@ export default [
|
||||
MigrationName1721779190475,
|
||||
MigrationName1722031205897,
|
||||
MigrationName1722543640526,
|
||||
MigrationName1722892318363,
|
||||
];
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface ComponentProps {
|
||||
initialValue?: Dictionary<string | boolean | number>;
|
||||
keyPlaceholder?: string;
|
||||
valuePlaceholder?: string;
|
||||
addButtonSuffix?: string;
|
||||
addButtonSuffix?: string | undefined;
|
||||
valueTypes?: Array<ValueType>; // by default it'll be Text
|
||||
autoConvertValueTypes?: boolean | undefined;
|
||||
keys?: Array<string> | undefined;
|
||||
|
||||
@@ -48,7 +48,7 @@ export const DefaultValidateFunction: DefaultValidateFunctionType = (
|
||||
return {};
|
||||
};
|
||||
|
||||
export interface BaseComponentProps<T extends GenericObject> {
|
||||
export interface BaseComponentProps<T> {
|
||||
submitButtonStyleType?: ButtonStyleType | undefined;
|
||||
initialValues?: FormValues<T> | undefined;
|
||||
onValidate?: undefined | ((values: FormValues<T>) => JSONObject);
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface ComponentProps {
|
||||
sideLink?: FormFieldSideLink | undefined;
|
||||
description?: string | ReactElement | undefined;
|
||||
isHeading?: boolean | undefined;
|
||||
hideOptionalLabel?: boolean | undefined;
|
||||
}
|
||||
|
||||
const FieldLabelElement: FunctionComponent<ComponentProps> = (
|
||||
@@ -23,7 +24,7 @@ const FieldLabelElement: FunctionComponent<ComponentProps> = (
|
||||
<span>
|
||||
{props.title}{" "}
|
||||
<span className="text-gray-400 text-xs">
|
||||
{props.required ? "" : "(Optional)"}
|
||||
{props.required || props.hideOptionalLabel ? "" : "(Optional)"}
|
||||
</span>
|
||||
</span>
|
||||
{props.sideLink && props.sideLink?.text && props.sideLink?.url && (
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { GetReactElementFunction } from "../../../Types/FunctionTypes";
|
||||
import CategoryCheckbox from "../../CategoryCheckbox/Index";
|
||||
import CheckboxElement, {
|
||||
CategoryCheckboxValue,
|
||||
} from "../../Checkbox/Checkbox";
|
||||
import CodeEditor from "../../CodeEditor/CodeEditor";
|
||||
import DictionaryForm from "../../Dictionary/Dictionary";
|
||||
import Dropdown, { DropdownValue } from "../../Dropdown/Dropdown";
|
||||
import FilePicker from "../../FilePicker/FilePicker";
|
||||
import Input, { InputType } from "../../Input/Input";
|
||||
@@ -238,6 +240,7 @@ const FormField: <T extends GenericObject>(
|
||||
description={getFieldDescription()}
|
||||
sideLink={props.field.sideLink}
|
||||
required={required}
|
||||
hideOptionalLabel={props.field.hideOptionalLabel}
|
||||
isHeading={props.field.styleType === FormFieldStyleType.Heading}
|
||||
/>
|
||||
)}
|
||||
@@ -317,6 +320,26 @@ const FormField: <T extends GenericObject>(
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.field.fieldType === FormFieldSchemaType.Dictionary && (
|
||||
<DictionaryForm
|
||||
keys={props.field.jsonKeysForDictionary}
|
||||
addButtonSuffix={props.field.title}
|
||||
keyPlaceholder={"Key"}
|
||||
valuePlaceholder={"Value"}
|
||||
autoConvertValueTypes={true}
|
||||
initialValue={
|
||||
props.currentValues &&
|
||||
(props.currentValues as any)[props.fieldName]
|
||||
? (props.currentValues as any)[props.fieldName]
|
||||
: props.field.defaultValue || {}
|
||||
}
|
||||
onChange={(value: Dictionary<string | number | boolean>) => {
|
||||
props.field.onChange && props.field.onChange(value);
|
||||
props.setFieldValue(props.fieldName, value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.field.fieldType === FormFieldSchemaType.RadioButton && (
|
||||
<RadioButtons
|
||||
error={props.touched && props.error ? props.error : undefined}
|
||||
|
||||
@@ -97,4 +97,9 @@ export default interface Field<TEntity> {
|
||||
// set this to true if you want to show this field in the form even when the form is in edit mode.
|
||||
doNotShowWhenEditing?: boolean | undefined;
|
||||
doNotShowWhenCreating?: boolean | undefined;
|
||||
|
||||
//
|
||||
jsonKeysForDictionary?: Array<string> | undefined;
|
||||
|
||||
hideOptionalLabel?: boolean | undefined;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ enum FormFieldSchemaType {
|
||||
CustomComponent = "CustomComponent",
|
||||
Checkbox = "Checkbox",
|
||||
CategoryCheckbox = "CategoryCheckbox",
|
||||
Dictionary = "Dictionary",
|
||||
}
|
||||
|
||||
export default FormFieldSchemaType;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { FILE_URL } from "../Config";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import { FileRoute } from "Common/ServiceRoute";
|
||||
|
||||
export default class FileUtil {
|
||||
public static getFileURL(fileId: ObjectID): URL {
|
||||
return URL.fromString(FILE_URL.toString())
|
||||
public static getFileRoute(fileId: ObjectID): Route {
|
||||
return Route.fromString(FileRoute.toString())
|
||||
.addRoute("/image")
|
||||
.addRoute(`/${fileId.toString()}`);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import LogSeverity from "Common/Types/Log/LogSeverity";
|
||||
import MonitorStepLogMonitor from "Common/Types/Monitor/MonitorStepLogMonitor";
|
||||
import FiltersForm from "CommonUI/src/Components/Filters/FiltersForm";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import Query from "CommonUI/src/Utils/BaseDatabase/Query";
|
||||
import DropdownUtil from "CommonUI/src/Utils/Dropdown";
|
||||
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import BasicForm from "CommonUI/src/Components/Forms/BasicForm";
|
||||
import LogSeverity from "Common/Types/Log/LogSeverity";
|
||||
import DropdownUtil from "CommonUI/src/Utils/Dropdown";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
|
||||
import FieldLabelElement from "CommonUI/src/Components/Forms/Fields/FieldLabel";
|
||||
import HorizontalRule from "CommonUI/src/Components/HorizontalRule/HorizontalRule";
|
||||
import LogMonitorPreview from "../../../Monitor/LogMonitor/LogMonitorPreview";
|
||||
|
||||
export interface ComponentProps {
|
||||
monitorStepLogMonitor: MonitorStepLogMonitor;
|
||||
@@ -19,105 +22,179 @@ export interface ComponentProps {
|
||||
const LogMonitorStepForm: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const [monitorStepLogMonitor, setMonitorStepLogMonitor] =
|
||||
React.useState<MonitorStepLogMonitor>(props.monitorStepLogMonitor);
|
||||
|
||||
let showAdvancedOptionsByDefault: boolean = false;
|
||||
|
||||
if (
|
||||
monitorStepLogMonitor.attributes ||
|
||||
monitorStepLogMonitor.severityTexts ||
|
||||
monitorStepLogMonitor.telemetryServiceIds
|
||||
) {
|
||||
showAdvancedOptionsByDefault = true;
|
||||
}
|
||||
|
||||
const [showAdvancedOptions, setShowAdvancedOptions] = React.useState(
|
||||
showAdvancedOptionsByDefault,
|
||||
);
|
||||
|
||||
return (
|
||||
<FiltersForm<MonitorStepLogMonitor>
|
||||
id="logs-filter"
|
||||
showFilter={true}
|
||||
filterData={props.monitorStepLogMonitor}
|
||||
onFilterChanged={(filterData: Query<MonitorStepLogMonitor>) => {
|
||||
props.onMonitorStepLogMonitorChanged(
|
||||
filterData as MonitorStepLogMonitor,
|
||||
);
|
||||
}}
|
||||
filters={[
|
||||
{
|
||||
key: "body",
|
||||
type: FieldType.Text,
|
||||
title: "Search Log Body",
|
||||
},
|
||||
{
|
||||
key: "lastXSecondsOfLogs",
|
||||
type: FieldType.Dropdown,
|
||||
filterDropdownOptions: [
|
||||
{
|
||||
label: "Last 5 seconds",
|
||||
value: 5,
|
||||
<div>
|
||||
<BasicForm
|
||||
id="logs-filter"
|
||||
hideSubmitButton={true}
|
||||
initialValues={monitorStepLogMonitor}
|
||||
onChange={(values: MonitorStepLogMonitor) => {
|
||||
setMonitorStepLogMonitor(values);
|
||||
props.onMonitorStepLogMonitorChanged(values);
|
||||
}}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
body: true,
|
||||
},
|
||||
{
|
||||
label: "Last 10 seconds",
|
||||
value: 10,
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
title: "Monitor Logs that include this text",
|
||||
description:
|
||||
"This monitor will filter all the logs that include this text.",
|
||||
hideOptionalLabel: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
lastXSecondsOfLogs: true,
|
||||
},
|
||||
{
|
||||
label: "Last 30 seconds",
|
||||
value: 30,
|
||||
defaultValue: 60,
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
dropdownOptions: [
|
||||
{
|
||||
label: "Last 5 seconds",
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
label: "Last 10 seconds",
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
label: "Last 30 seconds",
|
||||
value: 30,
|
||||
},
|
||||
{
|
||||
label: "Last 1 minute",
|
||||
value: 60,
|
||||
},
|
||||
{
|
||||
label: "Last 5 minutes",
|
||||
value: 300,
|
||||
},
|
||||
{
|
||||
label: "Last 15 minutes",
|
||||
value: 900,
|
||||
},
|
||||
{
|
||||
label: "Last 30 minutes",
|
||||
value: 1800,
|
||||
},
|
||||
{
|
||||
label: "Last 1 hour",
|
||||
value: 3600,
|
||||
},
|
||||
{
|
||||
label: "Last 6 hours",
|
||||
value: 21600,
|
||||
},
|
||||
{
|
||||
label: "Last 12 hours",
|
||||
value: 43200,
|
||||
},
|
||||
{
|
||||
label: "Last 24 hours",
|
||||
value: 86400,
|
||||
},
|
||||
],
|
||||
title: "Monitor Logs for (time)",
|
||||
description:
|
||||
"We will fetch all the logs that were generated in the last X time.",
|
||||
hideOptionalLabel: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
severityTexts: true,
|
||||
},
|
||||
{
|
||||
label: "Last 1 minute",
|
||||
value: 60,
|
||||
dropdownOptions:
|
||||
DropdownUtil.getDropdownOptionsFromEnum(LogSeverity),
|
||||
fieldType: FormFieldSchemaType.MultiSelectDropdown,
|
||||
title: "Log Severity",
|
||||
description: "Select the severity of the logs you want to monitor.",
|
||||
hideOptionalLabel: true,
|
||||
showIf: () => {
|
||||
return showAdvancedOptions;
|
||||
},
|
||||
{
|
||||
label: "Last 5 minutes",
|
||||
value: 300,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
telemetryServiceIds: true,
|
||||
},
|
||||
{
|
||||
label: "Last 15 minutes",
|
||||
value: 900,
|
||||
fieldType: FormFieldSchemaType.MultiSelectDropdown,
|
||||
dropdownOptions: props.telemetryServices.map(
|
||||
(telemetryService: TelemetryService) => {
|
||||
return {
|
||||
label: telemetryService.name!,
|
||||
value: telemetryService.id?.toString() || "",
|
||||
};
|
||||
},
|
||||
),
|
||||
title: "Filter by Telemetry Service",
|
||||
description: "Select the telemetry services you want to monitor.",
|
||||
hideOptionalLabel: true,
|
||||
showIf: () => {
|
||||
return showAdvancedOptions;
|
||||
},
|
||||
{
|
||||
label: "Last 30 minutes",
|
||||
value: 1800,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
attributes: true,
|
||||
},
|
||||
{
|
||||
label: "Last 1 hour",
|
||||
value: 3600,
|
||||
fieldType: FormFieldSchemaType.Dictionary,
|
||||
title: "Filter by Attributes",
|
||||
jsonKeysForDictionary: props.attributeKeys,
|
||||
description:
|
||||
"You can filter the logs based on the attributes that are attached to the logs.",
|
||||
hideOptionalLabel: true,
|
||||
showIf: () => {
|
||||
return showAdvancedOptions;
|
||||
},
|
||||
{
|
||||
label: "Last 6 hours",
|
||||
value: 21600,
|
||||
},
|
||||
{
|
||||
label: "Last 12 hours",
|
||||
value: 43200,
|
||||
},
|
||||
{
|
||||
label: "Last 24 hours",
|
||||
value: 86400,
|
||||
},
|
||||
],
|
||||
title: "Monitor Logs for Last",
|
||||
isAdvancedFilter: true,
|
||||
},
|
||||
{
|
||||
key: "severityText",
|
||||
filterDropdownOptions:
|
||||
DropdownUtil.getDropdownOptionsFromEnum(LogSeverity),
|
||||
type: FieldType.MultiSelectDropdown,
|
||||
title: "Log Severity",
|
||||
isAdvancedFilter: true,
|
||||
},
|
||||
{
|
||||
key: "telemetryServiceIds",
|
||||
type: FieldType.MultiSelectDropdown,
|
||||
filterDropdownOptions: props.telemetryServices.map(
|
||||
(telemetryService: TelemetryService) => {
|
||||
return {
|
||||
label: telemetryService.name!,
|
||||
value: telemetryService.id?.toString() || "",
|
||||
};
|
||||
},
|
||||
),
|
||||
title: "Filter by Telemetry Service",
|
||||
isAdvancedFilter: true,
|
||||
},
|
||||
{
|
||||
key: "attributes",
|
||||
type: FieldType.JSON,
|
||||
title: "Filter by Attributes",
|
||||
jsonKeys: props.attributeKeys,
|
||||
isAdvancedFilter: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="-ml-3">
|
||||
<Button
|
||||
buttonStyle={ButtonStyleType.SECONDARY_LINK}
|
||||
title={
|
||||
showAdvancedOptions
|
||||
? "Hide Advanced Options"
|
||||
: "Show Advanced Options"
|
||||
}
|
||||
onClick={() => {
|
||||
return setShowAdvancedOptions(!showAdvancedOptions);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<HorizontalRule />
|
||||
<FieldLabelElement
|
||||
title={"Logs Preview"}
|
||||
description={
|
||||
"Here is the preview of the logs that will be monitored based on the filters you have set above."
|
||||
}
|
||||
hideOptionalLabel={true}
|
||||
isHeading={true}
|
||||
/>
|
||||
<div className="mt-5 mb-5">
|
||||
<LogMonitorPreview monitorStepLogMonitor={monitorStepLogMonitor} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -506,13 +506,6 @@ const MonitorStepElement: FunctionComponent<ComponentProps> = (
|
||||
|
||||
{props.monitorType === MonitorType.Logs && (
|
||||
<div className="mt-5">
|
||||
<FieldLabelElement
|
||||
title={"Log Query"}
|
||||
description={
|
||||
"Please select the subset of logs you want to monitor."
|
||||
}
|
||||
required={true}
|
||||
/>
|
||||
<LogMonitorStepForm
|
||||
monitorStepLogMonitor={
|
||||
monitorStep.data?.logMonitor ||
|
||||
@@ -625,14 +618,17 @@ const MonitorStepElement: FunctionComponent<ComponentProps> = (
|
||||
|
||||
<div className="mt-5">
|
||||
{props.monitorType !== MonitorType.IncomingRequest && (
|
||||
<FieldLabelElement
|
||||
title="Monitor Criteria"
|
||||
isHeading={true}
|
||||
description={
|
||||
"Add Monitoring Criteria for this monitor. Monitor different properties."
|
||||
}
|
||||
required={true}
|
||||
/>
|
||||
<>
|
||||
<HorizontalRule />
|
||||
<FieldLabelElement
|
||||
title="Monitor Criteria"
|
||||
isHeading={true}
|
||||
description={
|
||||
"Add Monitoring Criteria for this monitor. Monitor different properties."
|
||||
}
|
||||
required={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<MonitorCriteriaElement
|
||||
monitorType={props.monitorType}
|
||||
|
||||
@@ -62,7 +62,7 @@ const DashboardUserProfile: FunctionComponent<ComponentProps> = (
|
||||
<HeaderIconDropdownButton
|
||||
iconImageUrl={
|
||||
profilePictureId
|
||||
? FileUtil.getFileURL(profilePictureId)
|
||||
? FileUtil.getFileRoute(profilePictureId)
|
||||
: BlankProfilePic
|
||||
}
|
||||
name="User Profile"
|
||||
|
||||
@@ -25,29 +25,45 @@ export interface ComponentProps {
|
||||
spanIds?: Array<string> | undefined;
|
||||
showFilters?: boolean | undefined;
|
||||
noLogsMessage?: string | undefined;
|
||||
logQuery?: Query<Log> | undefined;
|
||||
limit?: number | undefined;
|
||||
}
|
||||
|
||||
const DashboardLogsViewer: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const query: Query<Log> = {};
|
||||
type RefreshQueryFunction = () => Query<Log>;
|
||||
|
||||
if (props.telemetryServiceIds && props.telemetryServiceIds.length > 0) {
|
||||
query.serviceId = new Includes(props.telemetryServiceIds);
|
||||
}
|
||||
const refreshQuery: RefreshQueryFunction = (): Query<Log> => {
|
||||
let query: Query<Log> = {};
|
||||
|
||||
if (props.traceIds && props.traceIds.length > 0) {
|
||||
query.traceId = new Includes(props.traceIds);
|
||||
}
|
||||
if (props.telemetryServiceIds && props.telemetryServiceIds.length > 0) {
|
||||
query.serviceId = new Includes(props.telemetryServiceIds);
|
||||
}
|
||||
|
||||
if (props.spanIds && props.spanIds.length > 0) {
|
||||
query.spanId = new Includes(props.spanIds);
|
||||
}
|
||||
if (props.traceIds && props.traceIds.length > 0) {
|
||||
query.traceId = new Includes(props.traceIds);
|
||||
}
|
||||
|
||||
if (props.spanIds && props.spanIds.length > 0) {
|
||||
query.spanId = new Includes(props.spanIds);
|
||||
}
|
||||
|
||||
if (props.logQuery) {
|
||||
query = {
|
||||
...query,
|
||||
...props.logQuery,
|
||||
};
|
||||
}
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
const [logs, setLogs] = React.useState<Array<Log>>([]);
|
||||
const [error, setError] = React.useState<string>("");
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||
const [filterOptions, setFilterOptions] = React.useState<Query<Log>>(query);
|
||||
const [filterOptions, setFilterOptions] =
|
||||
React.useState<Query<Log>>(refreshQuery());
|
||||
|
||||
const select: Select<Log> = {
|
||||
body: true,
|
||||
@@ -72,6 +88,15 @@ const DashboardLogsViewer: FunctionComponent<ComponentProps> = (
|
||||
});
|
||||
}, [filterOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
setFilterOptions(refreshQuery());
|
||||
}, [
|
||||
props.telemetryServiceIds,
|
||||
props.traceIds,
|
||||
props.spanIds,
|
||||
props.logQuery,
|
||||
]);
|
||||
|
||||
const fetchItems: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
@@ -80,7 +105,7 @@ const DashboardLogsViewer: FunctionComponent<ComponentProps> = (
|
||||
const listResult: ListResult<Log> = await AnalyticsModelAPI.getList<Log>({
|
||||
modelType: Log,
|
||||
query: getQuery(),
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
limit: props.limit || LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
select: select,
|
||||
sort: {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
||||
import MonitorStepLogMonitor, {
|
||||
MonitorStepLogMonitorUtil,
|
||||
} from "Common/Types/Monitor/MonitorStepLogMonitor";
|
||||
import DashboardLogsViewer from "../../Logs/LogsViewer";
|
||||
import Query from "Common/Types/BaseDatabase/Query";
|
||||
import Log from "Common/Models/AnalyticsModels/Log";
|
||||
|
||||
export interface ComponentProps {
|
||||
monitorStepLogMonitor: MonitorStepLogMonitor | undefined;
|
||||
}
|
||||
|
||||
const LogMonitorPreview: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
type RefreshQueryFunction = () => Query<Log>;
|
||||
|
||||
const refreshQuery: RefreshQueryFunction = (): Query<Log> => {
|
||||
if (!props.monitorStepLogMonitor) {
|
||||
return {};
|
||||
}
|
||||
return MonitorStepLogMonitorUtil.toQuery(props.monitorStepLogMonitor);
|
||||
};
|
||||
|
||||
const [logQuery, setLogQuery] = React.useState<Query<Log>>(refreshQuery());
|
||||
|
||||
useEffect(() => {
|
||||
setLogQuery(refreshQuery());
|
||||
}, [props.monitorStepLogMonitor]);
|
||||
|
||||
return (
|
||||
<DashboardLogsViewer
|
||||
id="logs-preview"
|
||||
logQuery={logQuery}
|
||||
limit={10}
|
||||
noLogsMessage="No logs found"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogMonitorPreview;
|
||||
@@ -16,6 +16,21 @@ import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader";
|
||||
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
|
||||
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ListResult from "CommonUI/src/Utils/BaseDatabase/ListResult";
|
||||
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
import DashboardNavigation from "../../../Utils/Navigation";
|
||||
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import SortOrder from "Common/Types/BaseDatabase/SortOrder";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import API from "CommonUI/src/Utils/API/API";
|
||||
import Includes from "Common/Types/BaseDatabase/Includes";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import TelemetryServicesElement from "../../TelemetryService/TelemetryServiceElements";
|
||||
|
||||
export interface ComponentProps {
|
||||
monitorStatusOptions: Array<MonitorStatus>;
|
||||
@@ -25,138 +40,297 @@ export interface ComponentProps {
|
||||
onCallPolicyOptions: Array<OnCallDutyPolicy>;
|
||||
}
|
||||
|
||||
export interface LogMonitorStepView {
|
||||
body: string | undefined;
|
||||
severityTexts: Array<string> | undefined;
|
||||
attributes: JSONObject | undefined;
|
||||
telemetryServices: Array<TelemetryService> | undefined;
|
||||
lastXSecondsOfLogs: number | undefined;
|
||||
}
|
||||
|
||||
const MonitorStepElement: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const [requestDetailsFields, setRequestDetailsFields] = useState<
|
||||
Array<Field<MonitorStepType>>
|
||||
>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const [telemetryServices, setTelemetryServices] = useState<
|
||||
Array<TelemetryService> | undefined
|
||||
>(undefined);
|
||||
|
||||
// this field is used for most monitor types
|
||||
let fields: Array<Field<MonitorStepType>> = [];
|
||||
let logFields: Array<Field<LogMonitorStepView>> = [];
|
||||
|
||||
const logMonitorDetailView: LogMonitorStepView = {
|
||||
body: undefined,
|
||||
severityTexts: undefined,
|
||||
attributes: undefined,
|
||||
telemetryServices: undefined,
|
||||
lastXSecondsOfLogs: undefined,
|
||||
};
|
||||
|
||||
const fetchTelemetryServices: PromiseVoidFunction =
|
||||
async (): Promise<void> => {
|
||||
const telemetryServicesResult: ListResult<TelemetryService> =
|
||||
await ModelAPI.getList<TelemetryService>({
|
||||
modelType: TelemetryService,
|
||||
query: {
|
||||
projectId: DashboardNavigation.getProjectId(),
|
||||
_id: new Includes(
|
||||
props.monitorStep.data?.logMonitor?.telemetryServiceIds || [],
|
||||
),
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
serviceColor: true,
|
||||
},
|
||||
sort: {
|
||||
name: SortOrder.Ascending,
|
||||
},
|
||||
});
|
||||
|
||||
if (telemetryServicesResult instanceof HTTPErrorResponse) {
|
||||
throw telemetryServicesResult;
|
||||
}
|
||||
|
||||
setTelemetryServices(telemetryServicesResult.data);
|
||||
};
|
||||
|
||||
const loadComponent: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (props.monitorType === MonitorType.Logs) {
|
||||
await fetchTelemetryServices();
|
||||
}
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyErrorMessage(err as Error));
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let fields: Array<Field<MonitorStepType>> = [];
|
||||
|
||||
if (props.monitorType === MonitorType.API) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "API URL",
|
||||
description: "URL of the API you want to monitor.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "requestType",
|
||||
title: "Request Type",
|
||||
description: "Whats the type of the API request?",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "requestBody",
|
||||
title: "Request Body",
|
||||
description: "Request Body to send, if any.",
|
||||
fieldType: FieldType.JSON,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "requestHeaders",
|
||||
title: "Request Headers",
|
||||
description: "Request Headers to send, if any.",
|
||||
fieldType: FieldType.DictionaryOfStrings,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.Website) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "Website URL",
|
||||
description: "URL of the website you want to monitor.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.Ping) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "Ping Hostname or IP Address",
|
||||
description:
|
||||
"Hostname or IP Address of the resource you would like us to ping.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.Port) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "Ping Hostname or IP Address",
|
||||
description:
|
||||
"Hostname or IP Address of the resource you would like us to ping.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "monitorDestinationPort",
|
||||
title: "Port",
|
||||
description: "Port of the resource you would like us to ping.",
|
||||
fieldType: FieldType.Port,
|
||||
placeholder: "No port entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.IP) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "IP Address",
|
||||
description: "IP Address of the resource you would like us to ping.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.CustomJavaScriptCode) {
|
||||
fields = [
|
||||
{
|
||||
key: "customCode",
|
||||
title: "JavaScript Code",
|
||||
description: "JavaScript code to run.",
|
||||
fieldType: FieldType.JavaScript,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.SyntheticMonitor) {
|
||||
fields = [
|
||||
{
|
||||
key: "customCode",
|
||||
title: "JavaScript Code",
|
||||
description: "JavaScript code to run.",
|
||||
fieldType: FieldType.JavaScript,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "browserTypes",
|
||||
title: "Browser Types",
|
||||
description: "Browser types to run the synthetic monitor on.",
|
||||
fieldType: FieldType.ArrayOfText,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "screenSizeTypes",
|
||||
title: "Screen Size Types",
|
||||
description: "Screen size types to run the synthetic monitor on.",
|
||||
fieldType: FieldType.ArrayOfText,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
}
|
||||
setRequestDetailsFields(fields);
|
||||
loadComponent();
|
||||
}, [props.monitorType]);
|
||||
|
||||
if (isLoading) {
|
||||
return <ComponentLoader />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage error={error} />;
|
||||
}
|
||||
|
||||
if (props.monitorType === MonitorType.API) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "API URL",
|
||||
description: "URL of the API you want to monitor.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "requestType",
|
||||
title: "Request Type",
|
||||
description: "Whats the type of the API request?",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "requestBody",
|
||||
title: "Request Body",
|
||||
description: "Request Body to send, if any.",
|
||||
fieldType: FieldType.JSON,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "requestHeaders",
|
||||
title: "Request Headers",
|
||||
description: "Request Headers to send, if any.",
|
||||
fieldType: FieldType.DictionaryOfStrings,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.Website) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "Website URL",
|
||||
description: "URL of the website you want to monitor.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.Ping) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "Ping Hostname or IP Address",
|
||||
description:
|
||||
"Hostname or IP Address of the resource you would like us to ping.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.Port) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "Ping Hostname or IP Address",
|
||||
description:
|
||||
"Hostname or IP Address of the resource you would like us to ping.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "monitorDestinationPort",
|
||||
title: "Port",
|
||||
description: "Port of the resource you would like us to ping.",
|
||||
fieldType: FieldType.Port,
|
||||
placeholder: "No port entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.IP) {
|
||||
fields = [
|
||||
{
|
||||
key: "monitorDestination",
|
||||
title: "IP Address",
|
||||
description: "IP Address of the resource you would like us to ping.",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.CustomJavaScriptCode) {
|
||||
fields = [
|
||||
{
|
||||
key: "customCode",
|
||||
title: "JavaScript Code",
|
||||
description: "JavaScript code to run.",
|
||||
fieldType: FieldType.JavaScript,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.SyntheticMonitor) {
|
||||
fields = [
|
||||
{
|
||||
key: "customCode",
|
||||
title: "JavaScript Code",
|
||||
description: "JavaScript code to run.",
|
||||
fieldType: FieldType.JavaScript,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "browserTypes",
|
||||
title: "Browser Types",
|
||||
description: "Browser types to run the synthetic monitor on.",
|
||||
fieldType: FieldType.ArrayOfText,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
{
|
||||
key: "screenSizeTypes",
|
||||
title: "Screen Size Types",
|
||||
description: "Screen size types to run the synthetic monitor on.",
|
||||
fieldType: FieldType.ArrayOfText,
|
||||
placeholder: "No data entered",
|
||||
},
|
||||
];
|
||||
} else if (props.monitorType === MonitorType.Logs) {
|
||||
logFields = [];
|
||||
|
||||
if (props.monitorStep.data?.logMonitor?.body) {
|
||||
logMonitorDetailView.body = props.monitorStep.data?.logMonitor?.body;
|
||||
|
||||
logFields.push({
|
||||
key: "body",
|
||||
title: "Filter Log Message",
|
||||
description: "Filter by log message with this text:",
|
||||
fieldType: FieldType.Text,
|
||||
placeholder: "No log message entered",
|
||||
});
|
||||
}
|
||||
|
||||
if (props.monitorStep.data?.logMonitor?.lastXSecondsOfLogs) {
|
||||
logMonitorDetailView.lastXSecondsOfLogs =
|
||||
props.monitorStep.data?.logMonitor?.lastXSecondsOfLogs;
|
||||
|
||||
logFields.push({
|
||||
key: "lastXSecondsOfLogs",
|
||||
title: "Monitor logs for the last (time)",
|
||||
description: "How many seconds of logs to monitor.",
|
||||
fieldType: FieldType.Element,
|
||||
placeholder: "1 minute",
|
||||
getElement: (item: LogMonitorStepView): ReactElement => {
|
||||
return (
|
||||
<p>
|
||||
{OneUptimeDate.convertSecondsToDaysHoursMinutesAndSeconds(
|
||||
item.lastXSecondsOfLogs || 0,
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (props.monitorStep.data?.logMonitor?.severityTexts) {
|
||||
logMonitorDetailView.severityTexts =
|
||||
props.monitorStep.data?.logMonitor?.severityTexts;
|
||||
|
||||
logFields.push({
|
||||
key: "severityTexts",
|
||||
title: "Log Severity",
|
||||
description: "Severity of the logs to monitor.",
|
||||
fieldType: FieldType.ArrayOfText,
|
||||
placeholder: "No severity entered",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
props.monitorStep.data?.logMonitor?.attributes &&
|
||||
Object.keys(props.monitorStep.data?.logMonitor?.attributes).length > 0
|
||||
) {
|
||||
logMonitorDetailView.attributes =
|
||||
props.monitorStep.data?.logMonitor?.attributes;
|
||||
|
||||
logFields.push({
|
||||
key: "attributes",
|
||||
title: "Log Attributes",
|
||||
description: "Attributes of the logs to monitor.",
|
||||
fieldType: FieldType.JSON,
|
||||
placeholder: "No attributes entered",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
props.monitorStep.data?.logMonitor?.telemetryServiceIds &&
|
||||
props.monitorStep.data?.logMonitor?.telemetryServiceIds.length > 0 &&
|
||||
telemetryServices &&
|
||||
telemetryServices.length > 0
|
||||
) {
|
||||
logMonitorDetailView.telemetryServices = telemetryServices; // set the telemetry services
|
||||
|
||||
logFields.push({
|
||||
key: "telemetryServices",
|
||||
title: "Telemetry Services",
|
||||
description: "Telemetry services to monitor.",
|
||||
fieldType: FieldType.Element,
|
||||
placeholder: "No telemetry services entered",
|
||||
getElement: (): ReactElement => {
|
||||
return (
|
||||
<TelemetryServicesElement telemetryServices={telemetryServices} />
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<FieldLabelElement
|
||||
title={"Request Details"}
|
||||
title={"Monitor Details"}
|
||||
description={
|
||||
"Here are the details of the request we will send to monitor your resource status."
|
||||
}
|
||||
@@ -164,11 +338,20 @@ const MonitorStepElement: FunctionComponent<ComponentProps> = (
|
||||
isHeading={true}
|
||||
/>
|
||||
<div className="mt-5">
|
||||
<Detail
|
||||
id={"monitor-step"}
|
||||
item={props.monitorStep.data as any}
|
||||
fields={requestDetailsFields}
|
||||
/>
|
||||
{fields && fields.length > 0 && (
|
||||
<Detail<MonitorStepType>
|
||||
id={"monitor-step"}
|
||||
item={props.monitorStep.data!}
|
||||
fields={fields}
|
||||
/>
|
||||
)}
|
||||
{logFields && logFields.length > 0 && (
|
||||
<Detail<LogMonitorStepView>
|
||||
id={"monitor-logs"}
|
||||
item={logMonitorDetailView}
|
||||
fields={logFields}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<HorizontalRule />
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import InfoCard from "CommonUI/src/Components/InfoCard/InfoCard";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import TelemetryMonitorSummary from "./Types/TelemetryMonitorSummary";
|
||||
|
||||
export interface ComponentProps {
|
||||
telemetryMonitorSummary?: TelemetryMonitorSummary | undefined;
|
||||
}
|
||||
|
||||
const WebsiteMonitorSummaryView: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div className="flex space-x-3">
|
||||
<InfoCard
|
||||
className="w-1/2 shadow-none border-2 border-gray-100 "
|
||||
title="Monitored At"
|
||||
value={
|
||||
props.telemetryMonitorSummary?.lastCheckedAt
|
||||
? OneUptimeDate.getDateAsLocalFormattedString(
|
||||
props.telemetryMonitorSummary?.lastCheckedAt,
|
||||
)
|
||||
: "-"
|
||||
}
|
||||
/>
|
||||
<InfoCard
|
||||
className="w-1/2 shadow-none border-2 border-gray-100 "
|
||||
title="Next Check At"
|
||||
value={
|
||||
props.telemetryMonitorSummary?.nextCheckAt
|
||||
? OneUptimeDate.getDateAsLocalFormattedString(
|
||||
props.telemetryMonitorSummary?.nextCheckAt,
|
||||
)
|
||||
: "-"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebsiteMonitorSummaryView;
|
||||
@@ -10,6 +10,7 @@ import Card from "CommonUI/src/Components/Card/Card";
|
||||
import { MonitorStepProbeResponse } from "Common/Models/DatabaseModels/MonitorProbe";
|
||||
import Probe from "Common/Models/DatabaseModels/Probe";
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
||||
import TelemetryMonitorSummary from "./Types/TelemetryMonitorSummary";
|
||||
|
||||
export interface ComponentProps {
|
||||
probeMonitorResponses?: Array<MonitorStepProbeResponse> | undefined;
|
||||
@@ -17,6 +18,7 @@ export interface ComponentProps {
|
||||
serverMonitorResponse?: ServerMonitorResponse | undefined;
|
||||
probes?: Array<Probe>;
|
||||
monitorType: MonitorType;
|
||||
telemetryMonitorSummary?: TelemetryMonitorSummary | undefined;
|
||||
}
|
||||
|
||||
const Summary: FunctionComponent<ComponentProps> = (
|
||||
@@ -81,6 +83,7 @@ const Summary: FunctionComponent<ComponentProps> = (
|
||||
probeMonitorResponses={probeResponses}
|
||||
incomingMonitorRequest={props.incomingMonitorRequest}
|
||||
serverMonitorResponse={props.serverMonitorResponse}
|
||||
telemetryMonitorSummary={props.telemetryMonitorSummary}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -12,12 +12,15 @@ import MonitorType, {
|
||||
import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse";
|
||||
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import LogMonitorSummaryView from "./LogMonitorView";
|
||||
import TelemetryMonitorSummary from "./Types/TelemetryMonitorSummary";
|
||||
|
||||
export interface ComponentProps {
|
||||
monitorType: MonitorType;
|
||||
probeMonitorResponses?: Array<ProbeMonitorResponse> | undefined; // this is an array because of multiple monitor steps.
|
||||
incomingMonitorRequest?: IncomingMonitorRequest | undefined;
|
||||
serverMonitorResponse?: ServerMonitorResponse | undefined;
|
||||
telemetryMonitorSummary?: TelemetryMonitorSummary | undefined;
|
||||
}
|
||||
|
||||
const SummaryInfo: FunctionComponent<ComponentProps> = (
|
||||
@@ -140,6 +143,12 @@ const SummaryInfo: FunctionComponent<ComponentProps> = (
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{props.monitorType === MonitorType.Logs && (
|
||||
<LogMonitorSummaryView
|
||||
telemetryMonitorSummary={props.telemetryMonitorSummary}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface TelemetryMonitorSummary {
|
||||
lastCheckedAt?: Date | undefined;
|
||||
nextCheckAt?: Date | undefined;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import TelemetryServiceElement from "./TelemetryServiceElement";
|
||||
import TableColumnListComponent from "CommonUI/src/Components/TableColumnList/TableColumnListComponent";
|
||||
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
telemetryServices: Array<TelemetryService>;
|
||||
onNavigateComplete?: (() => void) | undefined;
|
||||
}
|
||||
|
||||
const TelemetryServicesElement: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<TableColumnListComponent
|
||||
items={props.telemetryServices}
|
||||
moreText="more services"
|
||||
getEachElement={(telemetryService: TelemetryService) => {
|
||||
return (
|
||||
<TelemetryServiceElement
|
||||
telemetryService={telemetryService}
|
||||
onNavigateComplete={props.onNavigateComplete}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
noItemsMessage="No services."
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TelemetryServicesElement;
|
||||
@@ -48,24 +48,24 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{props.project?.isFeatureFlagMonitorGroupsEnabled ? (
|
||||
<SideMenuSection title="Monitor Groups">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "All Groups",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.MONITOR_GROUPS] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Squares}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</SideMenuSection>
|
||||
|
||||
{props.project?.isFeatureFlagMonitorGroupsEnabled ? (
|
||||
<SideMenuSection title="Monitor Groups">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "All Groups",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.MONITOR_GROUPS] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Squares}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<SideMenuSection title="Not Being Monitored">
|
||||
<CountModelSideMenuItem<Monitor>
|
||||
link={{
|
||||
|
||||
@@ -3,7 +3,7 @@ import DisabledWarning from "../../../Components/Monitor/DisabledWarning";
|
||||
import IncomingMonitorLink from "../../../Components/Monitor/IncomingRequestMonitor/IncomingMonitorLink";
|
||||
import { MonitorCharts } from "../../../Components/Monitor/MonitorCharts/MonitorChart";
|
||||
import ServerMonitorDocumentation from "../../../Components/Monitor/ServerMonitor/Documentation";
|
||||
import Metrics from "../../../Components/Monitor/SummaryView/Summary";
|
||||
import Summary from "../../../Components/Monitor/SummaryView/Summary";
|
||||
import ProbeUtil from "../../../Utils/Probe";
|
||||
import PageComponentProps from "../../PageComponentProps";
|
||||
import InBetween from "Common/Types/BaseDatabase/InBetween";
|
||||
@@ -63,6 +63,7 @@ import React, {
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import LogMonitorPreview from "../../../Components/Monitor/LogMonitor/LogMonitorPreview";
|
||||
|
||||
const MonitorView: FunctionComponent<PageComponentProps> = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID();
|
||||
@@ -184,6 +185,9 @@ const MonitorView: FunctionComponent<PageComponentProps> = (): ReactElement => {
|
||||
serverMonitorResponse: true,
|
||||
isNoProbeEnabledOnThisMonitor: true,
|
||||
isAllProbesDisconnectedFromThisMonitor: true,
|
||||
telemetryMonitorLastMonitorAt: true,
|
||||
telemetryMonitorNextMonitorAt: true,
|
||||
monitorSteps: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -600,14 +604,39 @@ const MonitorView: FunctionComponent<PageComponentProps> = (): ReactElement => {
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Metrics
|
||||
<Summary
|
||||
monitorType={monitorType!}
|
||||
probes={probes}
|
||||
incomingMonitorRequest={incomingMonitorRequest}
|
||||
probeMonitorResponses={probeResponses}
|
||||
serverMonitorResponse={serverMonitorResponse}
|
||||
telemetryMonitorSummary={{
|
||||
lastCheckedAt: monitor?.telemetryMonitorLastMonitorAt,
|
||||
nextCheckAt: monitor?.telemetryMonitorNextMonitorAt,
|
||||
}}
|
||||
/>
|
||||
|
||||
{monitor?.monitorType === MonitorType.Logs &&
|
||||
monitor.monitorSteps &&
|
||||
monitor.monitorSteps.data?.monitorStepsInstanceArray &&
|
||||
monitor.monitorSteps.data?.monitorStepsInstanceArray.length > 0 && (
|
||||
<div>
|
||||
<Card
|
||||
title={"Logs Preview"}
|
||||
description={
|
||||
"Preview of the logs that match the filter of this monitor."
|
||||
}
|
||||
>
|
||||
<LogMonitorPreview
|
||||
monitorStepLogMonitor={
|
||||
monitor.monitorSteps.data?.monitorStepsInstanceArray[0]?.data
|
||||
?.logMonitor
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldFetchMonitorMetrics && getMonitorMetricsChartGroup()}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -261,7 +261,8 @@ export default class CriteriaFilterUtil {
|
||||
i.value === FilterType.GreaterThan ||
|
||||
i.value === FilterType.LessThan ||
|
||||
i.value === FilterType.LessThanOrEqualTo ||
|
||||
i.value === FilterType.GreaterThanOrEqualTo
|
||||
i.value === FilterType.GreaterThanOrEqualTo ||
|
||||
i.value === FilterType.EqualTo
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user