diff --git a/App/FeatureSet/Home/Utils/OSSFriends.ts b/App/FeatureSet/Home/Utils/OSSFriends.ts index 7efb1ae906..d81d5aacbf 100644 --- a/App/FeatureSet/Home/Utils/OSSFriends.ts +++ b/App/FeatureSet/Home/Utils/OSSFriends.ts @@ -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; diff --git a/App/FeatureSet/Home/Utils/Reviews.ts b/App/FeatureSet/Home/Utils/Reviews.ts index 17ce032749..bf00257756 100644 --- a/App/FeatureSet/Home/Utils/Reviews.ts +++ b/App/FeatureSet/Home/Utils/Reviews.ts @@ -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.", diff --git a/App/FeatureSet/Home/Views/Partials/copilot.ejs b/App/FeatureSet/Home/Views/Partials/copilot.ejs index b86d998c80..eb2f82583f 100644 --- a/App/FeatureSet/Home/Views/Partials/copilot.ejs +++ b/App/FeatureSet/Home/Views/Partials/copilot.ejs @@ -111,7 +111,7 @@ - We don't store or train on your code. + We don't see, store or train on your code. 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. diff --git a/App/FeatureSet/Workers/Index.ts b/App/FeatureSet/Workers/Index.ts index 50154aac31..8118b51507 100644 --- a/App/FeatureSet/Workers/Index.ts +++ b/App/FeatureSet/Workers/Index.ts @@ -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 => { try { diff --git a/App/FeatureSet/Workers/Jobs/TelemetryMonitor/MonitorTelemetryMonitor.ts b/App/FeatureSet/Workers/Jobs/TelemetryMonitor/MonitorTelemetryMonitor.ts index 7663c5d6c9..28d5b14a46 100644 --- a/App/FeatureSet/Workers/Jobs/TelemetryMonitor/MonitorTelemetryMonitor.ts +++ b/App/FeatureSet/Workers/Jobs/TelemetryMonitor/MonitorTelemetryMonitor.ts @@ -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, + ); } if (logQuery.telemetryServiceIds && logQuery.telemetryServiceIds.length > 0) { diff --git a/Common/Types/Date.ts b/Common/Types/Date.ts index 523c6192cb..48f5f0377f 100644 --- a/Common/Types/Date.ts +++ b/Common/Types/Date.ts @@ -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. diff --git a/Common/Types/Monitor/MonitorStep.ts b/Common/Types/Monitor/MonitorStep.ts index 86358f4472..5b803a1526 100644 --- a/Common/Types/Monitor/MonitorStep.ts +++ b/Common/Types/Monitor/MonitorStep.ts @@ -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; } diff --git a/Common/Types/Monitor/MonitorStepLogMonitor.ts b/Common/Types/Monitor/MonitorStepLogMonitor.ts index 92901ade82..d6ce80860e 100644 --- a/Common/Types/Monitor/MonitorStepLogMonitor.ts +++ b/Common/Types/Monitor/MonitorStepLogMonitor.ts @@ -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; body: string; - severityText: Array; + severityTexts: Array; telemetryServiceIds: Array; lastXSecondsOfLogs: number; } export class MonitorStepLogMonitorUtil { + public static toQuery( + monitorStepLogMonitor: MonitorStepLogMonitor, + ): Query { + const query: Query = {}; + + 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) || {}, body: json["body"] as string, - severityText: json["severityText"] as Array, + severityTexts: json["severityTexts"] as Array, telemetryServiceIds: ObjectID.fromJSONArray( json["telemetryServiceIds"] as Array, ), @@ -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, }; } diff --git a/Common/Types/Monitor/MonitorType.ts b/Common/Types/Monitor/MonitorType.ts index 54b12b061f..63d601841d 100644 --- a/Common/Types/Monitor/MonitorType.ts +++ b/Common/Types/Monitor/MonitorType.ts @@ -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; diff --git a/Common/Types/ObjectID.ts b/Common/Types/ObjectID.ts index 95192d53ee..7a1a01c3c3 100644 --- a/Common/Types/ObjectID.ts +++ b/Common/Types/ObjectID.ts @@ -48,6 +48,10 @@ export default class ObjectID extends DatabaseProperty { } public static toJSONArray(ids: Array): Array { + if (!ids || ids.length === 0) { + return []; + } + return ids.map((id: ObjectID) => { if (typeof id === "string") { id = new ObjectID(id); diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1722892318363-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1722892318363-MigrationName.ts new file mode 100644 index 0000000000..56f5361239 --- /dev/null +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1722892318363-MigrationName.ts @@ -0,0 +1,41 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class MigrationName1722892318363 implements MigrationInterface { + public name = "MigrationName1722892318363"; + + public async up(queryRunner: QueryRunner): Promise { + 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 { + 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"`, + ); + } +} diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/Index.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/Index.ts index 39bc401aac..a38ceb8cc6 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/Index.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/Index.ts @@ -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, ]; diff --git a/CommonUI/src/Components/Dictionary/Dictionary.tsx b/CommonUI/src/Components/Dictionary/Dictionary.tsx index 9fa8feba5d..f467e60952 100644 --- a/CommonUI/src/Components/Dictionary/Dictionary.tsx +++ b/CommonUI/src/Components/Dictionary/Dictionary.tsx @@ -26,7 +26,7 @@ export interface ComponentProps { initialValue?: Dictionary; keyPlaceholder?: string; valuePlaceholder?: string; - addButtonSuffix?: string; + addButtonSuffix?: string | undefined; valueTypes?: Array; // by default it'll be Text autoConvertValueTypes?: boolean | undefined; keys?: Array | undefined; diff --git a/CommonUI/src/Components/Forms/BasicForm.tsx b/CommonUI/src/Components/Forms/BasicForm.tsx index 366e3f5595..491ea1eb9c 100644 --- a/CommonUI/src/Components/Forms/BasicForm.tsx +++ b/CommonUI/src/Components/Forms/BasicForm.tsx @@ -48,7 +48,7 @@ export const DefaultValidateFunction: DefaultValidateFunctionType = ( return {}; }; -export interface BaseComponentProps { +export interface BaseComponentProps { submitButtonStyleType?: ButtonStyleType | undefined; initialValues?: FormValues | undefined; onValidate?: undefined | ((values: FormValues) => JSONObject); diff --git a/CommonUI/src/Components/Forms/Fields/FieldLabel.tsx b/CommonUI/src/Components/Forms/Fields/FieldLabel.tsx index b0b18c4e96..637873e38c 100644 --- a/CommonUI/src/Components/Forms/Fields/FieldLabel.tsx +++ b/CommonUI/src/Components/Forms/Fields/FieldLabel.tsx @@ -8,6 +8,7 @@ export interface ComponentProps { sideLink?: FormFieldSideLink | undefined; description?: string | ReactElement | undefined; isHeading?: boolean | undefined; + hideOptionalLabel?: boolean | undefined; } const FieldLabelElement: FunctionComponent = ( @@ -23,7 +24,7 @@ const FieldLabelElement: FunctionComponent = ( {props.title}{" "} - {props.required ? "" : "(Optional)"} + {props.required || props.hideOptionalLabel ? "" : "(Optional)"} {props.sideLink && props.sideLink?.text && props.sideLink?.url && ( diff --git a/CommonUI/src/Components/Forms/Fields/FormField.tsx b/CommonUI/src/Components/Forms/Fields/FormField.tsx index 37096901a2..f4b645383c 100644 --- a/CommonUI/src/Components/Forms/Fields/FormField.tsx +++ b/CommonUI/src/Components/Forms/Fields/FormField.tsx @@ -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: ( description={getFieldDescription()} sideLink={props.field.sideLink} required={required} + hideOptionalLabel={props.field.hideOptionalLabel} isHeading={props.field.styleType === FormFieldStyleType.Heading} /> )} @@ -317,6 +320,26 @@ const FormField: ( /> )} + {props.field.fieldType === FormFieldSchemaType.Dictionary && ( + ) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + /> + )} + {props.field.fieldType === FormFieldSchemaType.RadioButton && ( { // 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 | undefined; + + hideOptionalLabel?: boolean | undefined; } diff --git a/CommonUI/src/Components/Forms/Types/FormFieldSchemaType.ts b/CommonUI/src/Components/Forms/Types/FormFieldSchemaType.ts index 057d5afbfc..0dd1e585aa 100644 --- a/CommonUI/src/Components/Forms/Types/FormFieldSchemaType.ts +++ b/CommonUI/src/Components/Forms/Types/FormFieldSchemaType.ts @@ -34,6 +34,7 @@ enum FormFieldSchemaType { CustomComponent = "CustomComponent", Checkbox = "Checkbox", CategoryCheckbox = "CategoryCheckbox", + Dictionary = "Dictionary", } export default FormFieldSchemaType; diff --git a/CommonUI/src/Utils/File.ts b/CommonUI/src/Utils/File.ts index 577b85f0b3..95839a633e 100644 --- a/CommonUI/src/Utils/File.ts +++ b/CommonUI/src/Utils/File.ts @@ -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()}`); } diff --git a/Dashboard/src/Components/Form/Monitor/LogMonitor/LogMonitorStepFrom.tsx b/Dashboard/src/Components/Form/Monitor/LogMonitor/LogMonitorStepFrom.tsx index a4bd244b0e..dc9572002f 100644 --- a/Dashboard/src/Components/Form/Monitor/LogMonitor/LogMonitorStepFrom.tsx +++ b/Dashboard/src/Components/Form/Monitor/LogMonitor/LogMonitorStepFrom.tsx @@ -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 = ( props: ComponentProps, ): ReactElement => { + const [monitorStepLogMonitor, setMonitorStepLogMonitor] = + React.useState(props.monitorStepLogMonitor); + + let showAdvancedOptionsByDefault: boolean = false; + + if ( + monitorStepLogMonitor.attributes || + monitorStepLogMonitor.severityTexts || + monitorStepLogMonitor.telemetryServiceIds + ) { + showAdvancedOptionsByDefault = true; + } + + const [showAdvancedOptions, setShowAdvancedOptions] = React.useState( + showAdvancedOptionsByDefault, + ); + return ( - - id="logs-filter" - showFilter={true} - filterData={props.monitorStepLogMonitor} - onFilterChanged={(filterData: Query) => { - 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, +
+ { + 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, - }, - ]} - /> + }, + ]} + /> +
+
+
+ + +
+ +
+
+
); }; diff --git a/Dashboard/src/Components/Form/Monitor/MonitorStep.tsx b/Dashboard/src/Components/Form/Monitor/MonitorStep.tsx index 300578f064..84192f20b1 100644 --- a/Dashboard/src/Components/Form/Monitor/MonitorStep.tsx +++ b/Dashboard/src/Components/Form/Monitor/MonitorStep.tsx @@ -506,13 +506,6 @@ const MonitorStepElement: FunctionComponent = ( {props.monitorType === MonitorType.Logs && (
- = (
{props.monitorType !== MonitorType.IncomingRequest && ( - + <> + + + )} = ( | undefined; showFilters?: boolean | undefined; noLogsMessage?: string | undefined; + logQuery?: Query | undefined; + limit?: number | undefined; } const DashboardLogsViewer: FunctionComponent = ( props: ComponentProps, ): ReactElement => { - const query: Query = {}; + type RefreshQueryFunction = () => Query; - if (props.telemetryServiceIds && props.telemetryServiceIds.length > 0) { - query.serviceId = new Includes(props.telemetryServiceIds); - } + const refreshQuery: RefreshQueryFunction = (): Query => { + let query: Query = {}; - 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>([]); const [error, setError] = React.useState(""); const [isLoading, setIsLoading] = React.useState(false); - const [filterOptions, setFilterOptions] = React.useState>(query); + const [filterOptions, setFilterOptions] = + React.useState>(refreshQuery()); const select: Select = { body: true, @@ -72,6 +88,15 @@ const DashboardLogsViewer: FunctionComponent = ( }); }, [filterOptions]); + useEffect(() => { + setFilterOptions(refreshQuery()); + }, [ + props.telemetryServiceIds, + props.traceIds, + props.spanIds, + props.logQuery, + ]); + const fetchItems: PromiseVoidFunction = async (): Promise => { setError(""); setIsLoading(true); @@ -80,7 +105,7 @@ const DashboardLogsViewer: FunctionComponent = ( const listResult: ListResult = await AnalyticsModelAPI.getList({ modelType: Log, query: getQuery(), - limit: LIMIT_PER_PROJECT, + limit: props.limit || LIMIT_PER_PROJECT, skip: 0, select: select, sort: { diff --git a/Dashboard/src/Components/Monitor/LogMonitor/LogMonitorPreview.tsx b/Dashboard/src/Components/Monitor/LogMonitor/LogMonitorPreview.tsx new file mode 100644 index 0000000000..09e7f45d9a --- /dev/null +++ b/Dashboard/src/Components/Monitor/LogMonitor/LogMonitorPreview.tsx @@ -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 = ( + props: ComponentProps, +): ReactElement => { + type RefreshQueryFunction = () => Query; + + const refreshQuery: RefreshQueryFunction = (): Query => { + if (!props.monitorStepLogMonitor) { + return {}; + } + return MonitorStepLogMonitorUtil.toQuery(props.monitorStepLogMonitor); + }; + + const [logQuery, setLogQuery] = React.useState>(refreshQuery()); + + useEffect(() => { + setLogQuery(refreshQuery()); + }, [props.monitorStepLogMonitor]); + + return ( + + ); +}; + +export default LogMonitorPreview; diff --git a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorStep.tsx b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorStep.tsx index 7f85ed4c2a..6ea4f00734 100644 --- a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorStep.tsx +++ b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorStep.tsx @@ -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; @@ -25,138 +40,297 @@ export interface ComponentProps { onCallPolicyOptions: Array; } +export interface LogMonitorStepView { + body: string | undefined; + severityTexts: Array | undefined; + attributes: JSONObject | undefined; + telemetryServices: Array | undefined; + lastXSecondsOfLogs: number | undefined; +} + const MonitorStepElement: FunctionComponent = ( props: ComponentProps, ): ReactElement => { - const [requestDetailsFields, setRequestDetailsFields] = useState< - Array> - >([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + const [telemetryServices, setTelemetryServices] = useState< + Array | undefined + >(undefined); + + // this field is used for most monitor types + let fields: Array> = []; + let logFields: Array> = []; + + const logMonitorDetailView: LogMonitorStepView = { + body: undefined, + severityTexts: undefined, + attributes: undefined, + telemetryServices: undefined, + lastXSecondsOfLogs: undefined, + }; + + const fetchTelemetryServices: PromiseVoidFunction = + async (): Promise => { + const telemetryServicesResult: ListResult = + await ModelAPI.getList({ + 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 => { + setIsLoading(true); + try { + if (props.monitorType === MonitorType.Logs) { + await fetchTelemetryServices(); + } + } catch (err) { + setError(API.getFriendlyErrorMessage(err as Error)); + } + + setIsLoading(false); + }; useEffect(() => { - let fields: Array> = []; - - 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 ; + } + + if (error) { + return ; + } + + 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 ( +

+ {OneUptimeDate.convertSecondsToDaysHoursMinutesAndSeconds( + item.lastXSecondsOfLogs || 0, + )} +

+ ); + }, + }); + } + + 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 ( + + ); + }, + }); + } + } + return (
= ( isHeading={true} />
- + {fields && fields.length > 0 && ( + + id={"monitor-step"} + item={props.monitorStep.data!} + fields={fields} + /> + )} + {logFields && logFields.length > 0 && ( + + id={"monitor-logs"} + item={logMonitorDetailView} + fields={logFields} + /> + )}
diff --git a/Dashboard/src/Components/Monitor/SummaryView/LogMonitorView.tsx b/Dashboard/src/Components/Monitor/SummaryView/LogMonitorView.tsx new file mode 100644 index 0000000000..e7b5761b89 --- /dev/null +++ b/Dashboard/src/Components/Monitor/SummaryView/LogMonitorView.tsx @@ -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 = ( + props: ComponentProps, +): ReactElement => { + return ( +
+
+ + +
+
+ ); +}; + +export default WebsiteMonitorSummaryView; diff --git a/Dashboard/src/Components/Monitor/SummaryView/Summary.tsx b/Dashboard/src/Components/Monitor/SummaryView/Summary.tsx index 79e971eca4..16d8122f14 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/Summary.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/Summary.tsx @@ -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 | undefined; @@ -17,6 +18,7 @@ export interface ComponentProps { serverMonitorResponse?: ServerMonitorResponse | undefined; probes?: Array; monitorType: MonitorType; + telemetryMonitorSummary?: TelemetryMonitorSummary | undefined; } const Summary: FunctionComponent = ( @@ -81,6 +83,7 @@ const Summary: FunctionComponent = ( probeMonitorResponses={probeResponses} incomingMonitorRequest={props.incomingMonitorRequest} serverMonitorResponse={props.serverMonitorResponse} + telemetryMonitorSummary={props.telemetryMonitorSummary} />
diff --git a/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx b/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx index 271cd3877b..dd9c43f6bc 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx @@ -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 | undefined; // this is an array because of multiple monitor steps. incomingMonitorRequest?: IncomingMonitorRequest | undefined; serverMonitorResponse?: ServerMonitorResponse | undefined; + telemetryMonitorSummary?: TelemetryMonitorSummary | undefined; } const SummaryInfo: FunctionComponent = ( @@ -140,6 +143,12 @@ const SummaryInfo: FunctionComponent = ( ) : ( <> )} + + {props.monitorType === MonitorType.Logs && ( + + )}
); }; diff --git a/Dashboard/src/Components/Monitor/SummaryView/Types/TelemetryMonitorSummary.ts b/Dashboard/src/Components/Monitor/SummaryView/Types/TelemetryMonitorSummary.ts new file mode 100644 index 0000000000..ad2e8bc78c --- /dev/null +++ b/Dashboard/src/Components/Monitor/SummaryView/Types/TelemetryMonitorSummary.ts @@ -0,0 +1,4 @@ +export default interface TelemetryMonitorSummary { + lastCheckedAt?: Date | undefined; + nextCheckAt?: Date | undefined; +} diff --git a/Dashboard/src/Components/TelemetryService/TelemetryServiceElements.tsx b/Dashboard/src/Components/TelemetryService/TelemetryServiceElements.tsx new file mode 100644 index 0000000000..6f2343817b --- /dev/null +++ b/Dashboard/src/Components/TelemetryService/TelemetryServiceElements.tsx @@ -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; + onNavigateComplete?: (() => void) | undefined; +} + +const TelemetryServicesElement: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + return ( + { + return ( + + ); + }} + noItemsMessage="No services." + /> + ); +}; + +export default TelemetryServicesElement; diff --git a/Dashboard/src/Pages/Monitor/SideMenu.tsx b/Dashboard/src/Pages/Monitor/SideMenu.tsx index e0fb5b043d..59d6ff79b6 100644 --- a/Dashboard/src/Pages/Monitor/SideMenu.tsx +++ b/Dashboard/src/Pages/Monitor/SideMenu.tsx @@ -48,24 +48,24 @@ const DashboardSideMenu: FunctionComponent = ( }, }} /> - - {props.project?.isFeatureFlagMonitorGroupsEnabled ? ( - - - - ) : ( - <> - )} + {props.project?.isFeatureFlagMonitorGroupsEnabled ? ( + + + + ) : ( + <> + )} + link={{ diff --git a/Dashboard/src/Pages/Monitor/View/Index.tsx b/Dashboard/src/Pages/Monitor/View/Index.tsx index d4d3b74459..ccbdeeb6d4 100644 --- a/Dashboard/src/Pages/Monitor/View/Index.tsx +++ b/Dashboard/src/Pages/Monitor/View/Index.tsx @@ -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 = (): ReactElement => { const modelId: ObjectID = Navigation.getLastParamAsObjectID(); @@ -184,6 +185,9 @@ const MonitorView: FunctionComponent = (): ReactElement => { serverMonitorResponse: true, isNoProbeEnabledOnThisMonitor: true, isAllProbesDisconnectedFromThisMonitor: true, + telemetryMonitorLastMonitorAt: true, + telemetryMonitorNextMonitorAt: true, + monitorSteps: true, }, }); @@ -600,14 +604,39 @@ const MonitorView: FunctionComponent = (): ReactElement => { /> - + {monitor?.monitorType === MonitorType.Logs && + monitor.monitorSteps && + monitor.monitorSteps.data?.monitorStepsInstanceArray && + monitor.monitorSteps.data?.monitorStepsInstanceArray.length > 0 && ( +
+ + + +
+ )} + {shouldFetchMonitorMetrics && getMonitorMetricsChartGroup()} ); diff --git a/Dashboard/src/Utils/Form/Monitor/CriteriaFilter.ts b/Dashboard/src/Utils/Form/Monitor/CriteriaFilter.ts index 70d00cf6d5..150a2c0444 100644 --- a/Dashboard/src/Utils/Form/Monitor/CriteriaFilter.ts +++ b/Dashboard/src/Utils/Form/Monitor/CriteriaFilter.ts @@ -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 ); }); }