mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
Merge branch 'master' into mob-phase-1
This commit is contained in:
@@ -32,6 +32,7 @@ import Reseller from "Common/Models/DatabaseModels/Reseller";
|
||||
import User from "Common/Models/DatabaseModels/User";
|
||||
import React, { useState } from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
|
||||
const RegisterPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = SIGNUP_API_URL;
|
||||
@@ -172,6 +173,36 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!BILLING_ENABLED) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
selfHostedCompanyName: true,
|
||||
},
|
||||
overrideFieldKey: "selfHostedCompanyName",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Acme, Inc.",
|
||||
required: false,
|
||||
title: "Company Name",
|
||||
dataTestId: "selfHostedCompanyName",
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
disableSpellCheck: true,
|
||||
},
|
||||
{
|
||||
overrideField: {
|
||||
selfHostedPhoneNumber: true,
|
||||
},
|
||||
overrideFieldKey: "selfHostedPhoneNumber",
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: false,
|
||||
placeholder: "+11234567890",
|
||||
title: "Phone Number",
|
||||
dataTestId: "selfHostedPhoneNumber",
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
field: {
|
||||
@@ -206,6 +237,25 @@ const RegisterPage: () => JSX.Element = () => {
|
||||
},
|
||||
]);
|
||||
|
||||
if (!IsBillingEnabled) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
overrideField: {
|
||||
notifySelfHosted: true,
|
||||
},
|
||||
overrideFieldKey: "notifySelfHosted",
|
||||
|
||||
fieldType: FormFieldSchemaType.Checkbox,
|
||||
required: false,
|
||||
defaultValue: true,
|
||||
title: "Notify me about security patches and new releases",
|
||||
dataTestId: "notifySelfHosted",
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
spanFullRow: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if (isCaptchaEnabled) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ import WhatsAppLogAPI from "./WhatsAppLogAPI";
|
||||
// Import API
|
||||
import ResellerPlanAPI from "Common/Server/API/ResellerPlanAPI";
|
||||
import EnterpriseLicenseAPI from "Common/Server/API/EnterpriseLicenseAPI";
|
||||
import OpenSourceDeploymentAPI from "Common/Server/API/OpenSourceDeploymentAPI";
|
||||
import MonitorAPI from "Common/Server/API/MonitorAPI";
|
||||
import ShortLinkAPI from "Common/Server/API/ShortLinkAPI";
|
||||
import StatusPageAPI from "Common/Server/API/StatusPageAPI";
|
||||
@@ -2007,6 +2008,10 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new EnterpriseLicenseAPI().getRouter(),
|
||||
);
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new OpenSourceDeploymentAPI().getRouter(),
|
||||
);
|
||||
app.use(`/${APP_NAME.toLocaleLowerCase()}`, new SlackAPI().getRouter());
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
|
||||
@@ -17,9 +17,11 @@ import ObjectID from "Common/Types/ObjectID";
|
||||
import PositiveNumber from "Common/Types/PositiveNumber";
|
||||
import DatabaseConfig from "Common/Server/DatabaseConfig";
|
||||
import {
|
||||
AppVersion,
|
||||
EncryptionSecret,
|
||||
IsBillingEnabled,
|
||||
} from "Common/Server/EnvironmentConfig";
|
||||
import API from "Common/Utils/API";
|
||||
import AccessTokenService from "Common/Server/Services/AccessTokenService";
|
||||
import EmailVerificationTokenService from "Common/Server/Services/EmailVerificationTokenService";
|
||||
import MailService from "Common/Server/Services/MailService";
|
||||
@@ -271,6 +273,28 @@ router.post(
|
||||
|
||||
logger.info("User signed up: " + savedUser.email?.toString());
|
||||
|
||||
if (!IsBillingEnabled && miscDataProps["notifySelfHosted"] === true) {
|
||||
const instanceUrl: string = new URL(httpProtocol, host).toString();
|
||||
|
||||
API.post({
|
||||
url: URL.fromString(
|
||||
"https://oneuptime.com/api/open-source-deployment/register",
|
||||
),
|
||||
data: {
|
||||
email: savedUser.email?.toString() || "",
|
||||
name: savedUser.name?.toString() || "",
|
||||
companyName:
|
||||
(miscDataProps["selfHostedCompanyName"] as string) || undefined,
|
||||
companyPhoneNumber:
|
||||
(miscDataProps["selfHostedPhoneNumber"] as string) || undefined,
|
||||
oneuptimeVersion: AppVersion,
|
||||
instanceUrl: instanceUrl,
|
||||
},
|
||||
}).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
return Response.sendEntityResponse(req, res, savedUser, User);
|
||||
}
|
||||
|
||||
|
||||
16
App/package-lock.json
generated
16
App/package-lock.json
generated
@@ -1609,13 +1609,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz",
|
||||
"integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -2388,9 +2388,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
|
||||
@@ -543,6 +543,31 @@ export default class AlertEpisode extends BaseModel {
|
||||
})
|
||||
public resolvedAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadAlertEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.Date,
|
||||
title: "All Alerts Resolved At",
|
||||
description:
|
||||
"When all alerts in this episode were first detected as resolved. Used for resolve delay calculation.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Date,
|
||||
nullable: true,
|
||||
unique: false,
|
||||
})
|
||||
public allAlertsResolvedAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
|
||||
@@ -542,6 +542,31 @@ export default class IncidentEpisode extends BaseModel {
|
||||
})
|
||||
public resolvedAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadIncidentEpisode,
|
||||
Permission.ReadAllProjectResources,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.Date,
|
||||
title: "All Incidents Resolved At",
|
||||
description:
|
||||
"When all incidents in this episode were first detected as resolved. Used for resolve delay calculation.",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.Date,
|
||||
nullable: true,
|
||||
unique: false,
|
||||
})
|
||||
public allIncidentsResolvedAt?: Date = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
|
||||
@@ -98,6 +98,7 @@ import ProjectSmtpConfig from "./ProjectSmtpConfig";
|
||||
import ProjectSSO from "./ProjectSso";
|
||||
import PromoCode from "./PromoCode";
|
||||
import EnterpriseLicense from "./EnterpriseLicense";
|
||||
import OpenSourceDeployment from "./OpenSourceDeployment";
|
||||
import Reseller from "./Reseller";
|
||||
import ResellerPlan from "./ResellerPlan";
|
||||
// ScheduledMaintenances
|
||||
@@ -411,6 +412,7 @@ const AllModelTypes: Array<{
|
||||
|
||||
PromoCode,
|
||||
EnterpriseLicense,
|
||||
OpenSourceDeployment,
|
||||
|
||||
GlobalConfig,
|
||||
|
||||
|
||||
140
Common/Models/DatabaseModels/OpenSourceDeployment.ts
Normal file
140
Common/Models/DatabaseModels/OpenSourceDeployment.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
|
||||
import Route from "../../Types/API/Route";
|
||||
import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
|
||||
import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
|
||||
import ColumnLength from "../../Types/Database/ColumnLength";
|
||||
import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
import TableMetadata from "../../Types/Database/TableMetadata";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import { Column, Entity } from "typeorm";
|
||||
|
||||
@TableAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
delete: [],
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/open-source-deployment"))
|
||||
@TableMetadata({
|
||||
tableName: "OpenSourceDeployment",
|
||||
singularName: "Open Source Deployment",
|
||||
pluralName: "Open Source Deployments",
|
||||
icon: IconProp.Globe,
|
||||
tableDescription:
|
||||
"Open source deployment registrations from self-hosted instances.",
|
||||
})
|
||||
@Entity({
|
||||
name: "OpenSourceDeployment",
|
||||
})
|
||||
export default class OpenSourceDeployment extends BaseModel {
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.Email,
|
||||
title: "Email",
|
||||
description: "Email address of the user who registered.",
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.Email,
|
||||
length: ColumnLength.Email,
|
||||
})
|
||||
public email?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Name",
|
||||
description: "Full name of the user who registered.",
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public name?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Company Name",
|
||||
description: "Company name of the user who registered.",
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public companyName?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.Phone,
|
||||
title: "Company Phone Number",
|
||||
description: "Phone number of the user who registered.",
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.Phone,
|
||||
length: ColumnLength.Phone,
|
||||
})
|
||||
public companyPhoneNumber?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Version",
|
||||
description: "OneUptime version of the self-hosted instance.",
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public oneuptimeVersion?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Instance URL",
|
||||
description: "URL of the self-hosted instance.",
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public instanceUrl?: string = undefined;
|
||||
}
|
||||
73
Common/Server/API/OpenSourceDeploymentAPI.ts
Normal file
73
Common/Server/API/OpenSourceDeploymentAPI.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import OpenSourceDeployment from "../../Models/DatabaseModels/OpenSourceDeployment";
|
||||
import { JSONObject } from "../../Types/JSON";
|
||||
import URL from "../../Types/API/URL";
|
||||
import API from "../../Utils/API";
|
||||
import OpenSourceDeploymentService, {
|
||||
Service as OpenSourceDeploymentServiceType,
|
||||
} from "../Services/OpenSourceDeploymentService";
|
||||
import { OpenSourceDeploymentWebhookUrl } from "../EnvironmentConfig";
|
||||
import logger from "../Utils/Logger";
|
||||
import Response from "../Utils/Response";
|
||||
import {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
NextFunction,
|
||||
} from "../Utils/Express";
|
||||
import BaseAPI from "./BaseAPI";
|
||||
|
||||
export default class OpenSourceDeploymentAPI extends BaseAPI<
|
||||
OpenSourceDeployment,
|
||||
OpenSourceDeploymentServiceType
|
||||
> {
|
||||
public constructor() {
|
||||
super(OpenSourceDeployment, OpenSourceDeploymentService);
|
||||
|
||||
this.router.post(
|
||||
`${new this.entityType().getCrudApiPath()?.toString()}/register`,
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
try {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const deployment: OpenSourceDeployment = new OpenSourceDeployment();
|
||||
|
||||
deployment.email = (body["email"] as string) || "";
|
||||
deployment.name = (body["name"] as string) || "";
|
||||
deployment.companyName = (body["companyName"] as string) || "";
|
||||
deployment.companyPhoneNumber =
|
||||
(body["companyPhoneNumber"] as string) || "";
|
||||
deployment.oneuptimeVersion =
|
||||
(body["oneuptimeVersion"] as string) || "unknown";
|
||||
deployment.instanceUrl = (body["instanceUrl"] as string) || "";
|
||||
|
||||
await OpenSourceDeploymentService.create({
|
||||
data: deployment,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (OpenSourceDeploymentWebhookUrl) {
|
||||
API.post({
|
||||
url: URL.fromString(OpenSourceDeploymentWebhookUrl),
|
||||
data: {
|
||||
email: deployment.email?.toString() || "",
|
||||
name: deployment.name?.toString() || "",
|
||||
companyName: deployment.companyName?.toString() || "",
|
||||
companyPhoneNumber:
|
||||
deployment.companyPhoneNumber?.toString() || "",
|
||||
oneuptimeVersion: deployment.oneuptimeVersion?.toString() || "",
|
||||
instanceUrl: deployment.instanceUrl?.toString() || "",
|
||||
},
|
||||
}).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,9 @@ export const EncryptionSecret: ObjectID = new ObjectID(
|
||||
process.env["ENCRYPTION_SECRET"] || "secret",
|
||||
);
|
||||
|
||||
export const OpenSourceDeploymentWebhookUrl: string =
|
||||
process.env["OPEN_SOURCE_DEPLOYMENT_WEBHOOK_URL"] || "";
|
||||
|
||||
export const AirtableApiKey: string = process.env["AIRTABLE_API_KEY"] || "";
|
||||
|
||||
export const AirtableBaseId: string = process.env["AIRTABLE_BASE_ID"] || "";
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class MigrationName1770728946893 implements MigrationInterface {
|
||||
public name = "MigrationName1770728946893";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "IncidentEpisode" ADD "allIncidentsResolvedAt" TIMESTAMP WITH TIME ZONE`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "AlertEpisode" ADD "allAlertsResolvedAt" TIMESTAMP WITH TIME ZONE`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_0610406e5c436c20a5068b1006" ON "IncidentEpisode" ("allIncidentsResolvedAt") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_ea5d1f899fe52445dd6e0d0d55" ON "AlertEpisode" ("allAlertsResolvedAt") `,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_ea5d1f899fe52445dd6e0d0d55"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_0610406e5c436c20a5068b1006"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "AlertEpisode" DROP COLUMN "allAlertsResolvedAt"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "IncidentEpisode" DROP COLUMN "allIncidentsResolvedAt"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class MigrationName1770732721195 implements MigrationInterface {
|
||||
public name = "MigrationName1770732721195";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "OpenSourceDeployment" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "email" character varying(100) NOT NULL, "name" character varying(100) NOT NULL, "companyName" character varying(100), "companyPhoneNumber" character varying(30), "oneuptimeVersion" character varying(100) NOT NULL, "instanceUrl" character varying(100), CONSTRAINT "PK_cf6728c16db1c0c1b89f8ffc6dd" PRIMARY KEY ("_id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "OpenSourceDeployment"`);
|
||||
}
|
||||
}
|
||||
@@ -254,6 +254,8 @@ import { MigrationName1770237245069 } from "./1770237245069-MigrationName";
|
||||
import { MigrationName1770237245070 } from "./1770237245070-MigrationName";
|
||||
import { MigrationName1770407024682 } from "./1770407024682-MigrationName";
|
||||
import { MigrationName1770668054908 } from "./1770668054908-MigrationName";
|
||||
import { MigrationName1770728946893 } from "./1770728946893-MigrationName";
|
||||
import { MigrationName1770732721195 } from "./1770732721195-MigrationName";
|
||||
|
||||
export default [
|
||||
InitialMigration,
|
||||
@@ -512,4 +514,6 @@ export default [
|
||||
MigrationName1770237245070,
|
||||
MigrationName1770407024682,
|
||||
MigrationName1770668054908,
|
||||
MigrationName1770728946893,
|
||||
MigrationName1770732721195,
|
||||
];
|
||||
|
||||
@@ -94,26 +94,16 @@ export class Service extends DatabaseService<Model> {
|
||||
});
|
||||
|
||||
// Update episode's alertCount and lastAlertAddedAt
|
||||
Promise.resolve()
|
||||
.then(async () => {
|
||||
try {
|
||||
await AlertEpisodeService.updateAlertCount(
|
||||
createdItem.alertEpisodeId!,
|
||||
);
|
||||
await AlertEpisodeService.updateLastAlertAddedAt(
|
||||
createdItem.alertEpisodeId!,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error updating episode counts in AlertEpisodeMemberService.onCreateSuccess: ${error}`,
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
logger.error(
|
||||
`Critical error in AlertEpisodeMemberService.onCreateSuccess: ${error}`,
|
||||
);
|
||||
});
|
||||
try {
|
||||
await AlertEpisodeService.updateAlertCount(createdItem.alertEpisodeId!);
|
||||
await AlertEpisodeService.updateLastAlertAddedAt(
|
||||
createdItem.alertEpisodeId!,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error updating episode counts in AlertEpisodeMemberService.onCreateSuccess: ${error}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Get alert details for feed
|
||||
const alert: Alert | null = await AlertService.findOneById({
|
||||
|
||||
@@ -815,11 +815,12 @@ export class Service extends DatabaseService<Model> {
|
||||
},
|
||||
});
|
||||
|
||||
// Clear resolved timestamp
|
||||
// Clear resolved timestamp and allAlertsResolvedAt
|
||||
await this.updateOneById({
|
||||
id: episodeId,
|
||||
data: {
|
||||
resolvedAt: undefined as any,
|
||||
resolvedAt: null,
|
||||
allAlertsResolvedAt: null,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
|
||||
@@ -688,6 +688,7 @@ class AlertGroupingEngineServiceClass {
|
||||
newEpisode.alertGroupingRuleId = rule.id!;
|
||||
newEpisode.groupingKey = groupingKey;
|
||||
newEpisode.isManuallyCreated = false;
|
||||
newEpisode.lastAlertAddedAt = OneUptimeDate.getCurrentDate();
|
||||
|
||||
// Set severity from alert
|
||||
if (alert.alertSeverityId) {
|
||||
|
||||
@@ -96,26 +96,18 @@ export class Service extends DatabaseService<Model> {
|
||||
});
|
||||
|
||||
// Update episode's incidentCount and lastIncidentAddedAt
|
||||
Promise.resolve()
|
||||
.then(async () => {
|
||||
try {
|
||||
await IncidentEpisodeService.updateIncidentCount(
|
||||
createdItem.incidentEpisodeId!,
|
||||
);
|
||||
await IncidentEpisodeService.updateLastIncidentAddedAt(
|
||||
createdItem.incidentEpisodeId!,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error updating episode counts in IncidentEpisodeMemberService.onCreateSuccess: ${error}`,
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
logger.error(
|
||||
`Critical error in IncidentEpisodeMemberService.onCreateSuccess: ${error}`,
|
||||
);
|
||||
});
|
||||
try {
|
||||
await IncidentEpisodeService.updateIncidentCount(
|
||||
createdItem.incidentEpisodeId!,
|
||||
);
|
||||
await IncidentEpisodeService.updateLastIncidentAddedAt(
|
||||
createdItem.incidentEpisodeId!,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error updating episode counts in IncidentEpisodeMemberService.onCreateSuccess: ${error}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Get incident details for feed
|
||||
const incident: Incident | null = await IncidentService.findOneById({
|
||||
|
||||
@@ -609,6 +609,18 @@ export class Service extends DatabaseService<Model> {
|
||||
},
|
||||
cascadeToIncidents: cascadeToIncidents,
|
||||
});
|
||||
|
||||
// Clear resolved timestamp and allIncidentsResolvedAt when episode is reopened
|
||||
await this.updateOneById({
|
||||
id: episodeId,
|
||||
data: {
|
||||
resolvedAt: null,
|
||||
allIncidentsResolvedAt: null,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@CaptureSpan()
|
||||
|
||||
@@ -757,6 +757,7 @@ class IncidentGroupingEngineServiceClass {
|
||||
newEpisode.incidentGroupingRuleId = rule.id!;
|
||||
newEpisode.groupingKey = groupingKey;
|
||||
newEpisode.isManuallyCreated = false;
|
||||
newEpisode.lastIncidentAddedAt = OneUptimeDate.getCurrentDate();
|
||||
|
||||
// Set severity from incident
|
||||
if (incident.incidentSeverityId) {
|
||||
|
||||
@@ -79,6 +79,7 @@ import ProjectSmtpConfigService from "./ProjectSmtpConfigService";
|
||||
import ProjectSsoService from "./ProjectSsoService";
|
||||
import PromoCodeService from "./PromoCodeService";
|
||||
import EnterpriseLicenseService from "./EnterpriseLicenseService";
|
||||
import OpenSourceDeploymentService from "./OpenSourceDeploymentService";
|
||||
import ResellerPlanService from "./ResellerPlanService";
|
||||
import ResellerService from "./ResellerService";
|
||||
import ScheduledMaintenanceCustomFieldService from "./ScheduledMaintenanceCustomFieldService";
|
||||
@@ -210,6 +211,7 @@ const services: Array<BaseService> = [
|
||||
AcmeCertificateService,
|
||||
PromoCodeService,
|
||||
EnterpriseLicenseService,
|
||||
OpenSourceDeploymentService,
|
||||
|
||||
ResellerService,
|
||||
ResellerPlanService,
|
||||
|
||||
10
Common/Server/Services/OpenSourceDeploymentService.ts
Normal file
10
Common/Server/Services/OpenSourceDeploymentService.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import DatabaseService from "./DatabaseService";
|
||||
import OpenSourceDeployment from "../../Models/DatabaseModels/OpenSourceDeployment";
|
||||
|
||||
export class Service extends DatabaseService<OpenSourceDeployment> {
|
||||
public constructor() {
|
||||
super(OpenSourceDeployment);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
@@ -646,7 +646,14 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
|
||||
})
|
||||
.map((field: Field<T>, i: number) => {
|
||||
return (
|
||||
<div key={getFieldName(field)}>
|
||||
<div
|
||||
key={getFieldName(field)}
|
||||
className={
|
||||
field.spanFullRow
|
||||
? `md:col-span-${props.showAsColumns || 1}`
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{
|
||||
<FormField<T>
|
||||
field={field}
|
||||
|
||||
@@ -124,4 +124,7 @@ export default interface Field<TEntity> {
|
||||
disableSpellCheck?: boolean | undefined;
|
||||
|
||||
getSummaryElement?: (item: FormValues<TEntity>) => ReactElement | undefined;
|
||||
|
||||
// If true, this field will span the full row in multi-column layouts.
|
||||
spanFullRow?: boolean | undefined;
|
||||
}
|
||||
|
||||
45
Common/package-lock.json
generated
45
Common/package-lock.json
generated
@@ -423,7 +423,6 @@
|
||||
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.26.0",
|
||||
@@ -926,7 +925,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-5.23.0.tgz",
|
||||
"integrity": "sha512-iI/Ssl8T5ZEn9s899Qz67m92M6RU8thf/aqD7cUHB2yHmkCjqbw7s7NaODTsyArAsnyu7DGJMWm7EhbfFXDNgQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@bull-board/api": "5.23.0"
|
||||
}
|
||||
@@ -2564,7 +2562,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -5728,7 +5725,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
|
||||
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -5935,7 +5931,8 @@
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz",
|
||||
"integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "2.0.11",
|
||||
@@ -6105,7 +6102,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -6508,13 +6504,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
|
||||
"integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -7027,7 +7023,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001669",
|
||||
"electron-to-chromium": "^1.5.41",
|
||||
@@ -7865,7 +7860,6 @@
|
||||
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
|
||||
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
@@ -8287,7 +8281,6 @@
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -9755,9 +9748,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -9818,9 +9811,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
@@ -11421,7 +11414,6 @@
|
||||
"integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "^28.1.3",
|
||||
"@jest/types": "^28.1.3",
|
||||
@@ -15230,7 +15222,6 @@
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.9.1",
|
||||
"pg-pool": "^3.10.1",
|
||||
@@ -15585,7 +15576,6 @@
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@@ -15902,7 +15892,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -15987,7 +15976,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -16508,8 +16496,7 @@
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/refractor": {
|
||||
"version": "5.0.0",
|
||||
@@ -19922,7 +19909,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
@@ -19931,8 +19917,7 @@
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
|
||||
"integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "4.5.5",
|
||||
|
||||
2181
Dashboard/package-lock.json
generated
2181
Dashboard/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -225,6 +225,9 @@ Usage:
|
||||
value: {{ default "" $.Values.captcha.secretKey | quote }}
|
||||
|
||||
|
||||
- name: OPEN_SOURCE_DEPLOYMENT_WEBHOOK_URL
|
||||
value: {{ default "" $.Values.openSourceDeployment.webhookUrl | quote }}
|
||||
|
||||
- name: NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER
|
||||
value: {{ $.Values.notifications.webhooks.slack.onCreateUser }}
|
||||
|
||||
|
||||
@@ -524,6 +524,10 @@ externalClickhouse:
|
||||
cert:
|
||||
key:
|
||||
|
||||
openSourceDeployment:
|
||||
# This webhook is called when a new self-hosted open source deployment registers.
|
||||
webhookUrl:
|
||||
|
||||
# Notification webhooks when certain events happen in the system. (usually they are slack webhooks)
|
||||
notifications:
|
||||
webhooks:
|
||||
|
||||
23
Probe/package-lock.json
generated
23
Probe/package-lock.json
generated
@@ -1429,13 +1429,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -2236,15 +2236,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
@@ -2255,9 +2256,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
|
||||
2177
StatusPage/package-lock.json
generated
2177
StatusPage/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,7 @@ RunCron(
|
||||
projectId: true,
|
||||
alertGroupingRuleId: true,
|
||||
lastAlertAddedAt: true,
|
||||
allAlertsResolvedAt: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -134,7 +135,6 @@ const checkAndResolveEpisode: CheckAndResolveEpisodeFunction = async (
|
||||
|
||||
// Check if all alerts are in resolved state or higher
|
||||
let allResolved: boolean = true;
|
||||
let lastResolvedAt: Date | null = null;
|
||||
|
||||
for (const alertId of alertIds) {
|
||||
const alert: Alert | null = await AlertService.findOneById({
|
||||
@@ -143,7 +143,6 @@ const checkAndResolveEpisode: CheckAndResolveEpisodeFunction = async (
|
||||
currentAlertState: {
|
||||
order: true,
|
||||
},
|
||||
updatedAt: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -160,31 +159,61 @@ const checkAndResolveEpisode: CheckAndResolveEpisodeFunction = async (
|
||||
allResolved = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Track the latest resolved time among alerts
|
||||
if (alert.updatedAt) {
|
||||
if (!lastResolvedAt || alert.updatedAt > lastResolvedAt) {
|
||||
lastResolvedAt = alert.updatedAt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allResolved) {
|
||||
// If any alert is unresolved, clear allAlertsResolvedAt
|
||||
if (episode.allAlertsResolvedAt) {
|
||||
await AlertEpisodeService.updateOneById({
|
||||
id: episode.id,
|
||||
data: {
|
||||
allAlertsResolvedAt: null,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`AlertEpisode:AutoResolve - Episode ${episode.id} has unresolved alerts`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// All alerts are resolved. Check if resolve delay has passed (only if enabled)
|
||||
if (enableResolveDelay && resolveDelayMinutes > 0 && lastResolvedAt) {
|
||||
const timeSinceLastResolved: number =
|
||||
OneUptimeDate.getDifferenceInMinutes(
|
||||
lastResolvedAt,
|
||||
OneUptimeDate.getCurrentDate(),
|
||||
);
|
||||
// All alerts are resolved. Set allAlertsResolvedAt if not already set.
|
||||
if (!episode.allAlertsResolvedAt) {
|
||||
await AlertEpisodeService.updateOneById({
|
||||
id: episode.id,
|
||||
data: {
|
||||
allAlertsResolvedAt: OneUptimeDate.getCurrentDate(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (timeSinceLastResolved < resolveDelayMinutes) {
|
||||
// If resolve delay is enabled, return and wait for the delay
|
||||
if (enableResolveDelay && resolveDelayMinutes > 0) {
|
||||
logger.debug(
|
||||
`AlertEpisode:AutoResolve - Episode ${episode.id} all alerts resolved, starting resolve delay (${resolveDelayMinutes} minutes)`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if resolve delay has passed (only if enabled)
|
||||
if (
|
||||
enableResolveDelay &&
|
||||
resolveDelayMinutes > 0 &&
|
||||
episode.allAlertsResolvedAt
|
||||
) {
|
||||
const timeSinceAllResolved: number = OneUptimeDate.getDifferenceInMinutes(
|
||||
episode.allAlertsResolvedAt,
|
||||
OneUptimeDate.getCurrentDate(),
|
||||
);
|
||||
|
||||
if (timeSinceAllResolved < resolveDelayMinutes) {
|
||||
logger.debug(
|
||||
`AlertEpisode:AutoResolve - Episode ${episode.id} waiting for resolve delay (${resolveDelayMinutes} minutes)`,
|
||||
);
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import RunCron from "../../Utils/Cron";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import { EVERY_MINUTE } from "Common/Utils/CronTime";
|
||||
import IncidentEpisodeService from "Common/Server/Services/IncidentEpisodeService";
|
||||
import IncidentEpisodeMemberService from "Common/Server/Services/IncidentEpisodeMemberService";
|
||||
import IncidentStateService from "Common/Server/Services/IncidentStateService";
|
||||
import IncidentGroupingRuleService from "Common/Server/Services/IncidentGroupingRuleService";
|
||||
import IncidentService from "Common/Server/Services/IncidentService";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import IncidentEpisode from "Common/Models/DatabaseModels/IncidentEpisode";
|
||||
import IncidentGroupingRule from "Common/Models/DatabaseModels/IncidentGroupingRule";
|
||||
import IncidentState from "Common/Models/DatabaseModels/IncidentState";
|
||||
import Incident from "Common/Models/DatabaseModels/Incident";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
@@ -33,7 +36,9 @@ RunCron(
|
||||
select: {
|
||||
_id: true,
|
||||
projectId: true,
|
||||
incidentGroupingRuleId: true,
|
||||
lastIncidentAddedAt: true,
|
||||
allIncidentsResolvedAt: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -71,6 +76,31 @@ const checkAndResolveEpisode: CheckAndResolveEpisodeFunction = async (
|
||||
return;
|
||||
}
|
||||
|
||||
// Get resolve delay from the grouping rule if exists and enabled
|
||||
let resolveDelayMinutes: number = 0;
|
||||
let enableResolveDelay: boolean = false;
|
||||
|
||||
if (episode.incidentGroupingRuleId) {
|
||||
const rule: IncidentGroupingRule | null =
|
||||
await IncidentGroupingRuleService.findOneById({
|
||||
id: episode.incidentGroupingRuleId,
|
||||
select: {
|
||||
enableResolveDelay: true,
|
||||
resolveDelayMinutes: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (rule) {
|
||||
enableResolveDelay = rule.enableResolveDelay || false;
|
||||
if (enableResolveDelay && rule.resolveDelayMinutes) {
|
||||
resolveDelayMinutes = rule.resolveDelayMinutes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all incidents in this episode
|
||||
const incidentIds: ObjectID[] =
|
||||
await IncidentEpisodeMemberService.getIncidentsInEpisode(episode.id);
|
||||
@@ -116,7 +146,6 @@ const checkAndResolveEpisode: CheckAndResolveEpisodeFunction = async (
|
||||
currentIncidentState: {
|
||||
order: true,
|
||||
},
|
||||
updatedAt: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -136,12 +165,65 @@ const checkAndResolveEpisode: CheckAndResolveEpisodeFunction = async (
|
||||
}
|
||||
|
||||
if (!allResolved) {
|
||||
// If any incident is unresolved, clear allIncidentsResolvedAt
|
||||
if (episode.allIncidentsResolvedAt) {
|
||||
await IncidentEpisodeService.updateOneById({
|
||||
id: episode.id,
|
||||
data: {
|
||||
allIncidentsResolvedAt: null,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`IncidentEpisode:AutoResolve - Episode ${episode.id} has unresolved incidents`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// All incidents are resolved. Set allIncidentsResolvedAt if not already set.
|
||||
if (!episode.allIncidentsResolvedAt) {
|
||||
await IncidentEpisodeService.updateOneById({
|
||||
id: episode.id,
|
||||
data: {
|
||||
allIncidentsResolvedAt: OneUptimeDate.getCurrentDate(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
// If resolve delay is enabled, return and wait for the delay
|
||||
if (enableResolveDelay && resolveDelayMinutes > 0) {
|
||||
logger.debug(
|
||||
`IncidentEpisode:AutoResolve - Episode ${episode.id} all incidents resolved, starting resolve delay (${resolveDelayMinutes} minutes)`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if resolve delay has passed (only if enabled)
|
||||
if (
|
||||
enableResolveDelay &&
|
||||
resolveDelayMinutes > 0 &&
|
||||
episode.allIncidentsResolvedAt
|
||||
) {
|
||||
const timeSinceAllResolved: number = OneUptimeDate.getDifferenceInMinutes(
|
||||
episode.allIncidentsResolvedAt,
|
||||
OneUptimeDate.getCurrentDate(),
|
||||
);
|
||||
|
||||
if (timeSinceAllResolved < resolveDelayMinutes) {
|
||||
logger.debug(
|
||||
`IncidentEpisode:AutoResolve - Episode ${episode.id} waiting for resolve delay (${resolveDelayMinutes} minutes)`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the episode
|
||||
logger.info(
|
||||
`IncidentEpisode:AutoResolve - Resolving episode ${episode.id} as all incidents are resolved`,
|
||||
|
||||
@@ -2,13 +2,12 @@ import RunCron from "../../Utils/Cron";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import { EVERY_FIVE_MINUTE } from "Common/Utils/CronTime";
|
||||
import IncidentEpisodeService from "Common/Server/Services/IncidentEpisodeService";
|
||||
import IncidentGroupingRuleService from "Common/Server/Services/IncidentGroupingRuleService";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import IncidentEpisode from "Common/Models/DatabaseModels/IncidentEpisode";
|
||||
import IncidentGroupingRule from "Common/Models/DatabaseModels/IncidentGroupingRule";
|
||||
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
|
||||
|
||||
// Default inactivity timeout in minutes (24 hours)
|
||||
const DEFAULT_INACTIVITY_TIMEOUT_MINUTES: number = 1440;
|
||||
|
||||
RunCron(
|
||||
"IncidentEpisode:ResolveInactiveEpisodes",
|
||||
{
|
||||
@@ -31,6 +30,7 @@ RunCron(
|
||||
select: {
|
||||
_id: true,
|
||||
projectId: true,
|
||||
incidentGroupingRuleId: true,
|
||||
lastIncidentAddedAt: true,
|
||||
},
|
||||
props: {
|
||||
@@ -68,9 +68,38 @@ const checkAndResolveInactiveEpisode: CheckAndResolveInactiveEpisodeFunction =
|
||||
return;
|
||||
}
|
||||
|
||||
// Use default inactivity timeout since there's no grouping rule for incidents
|
||||
const inactivityTimeoutMinutes: number =
|
||||
DEFAULT_INACTIVITY_TIMEOUT_MINUTES;
|
||||
// Get inactivity timeout from the grouping rule (only if enabled)
|
||||
let inactivityTimeoutMinutes: number = 0;
|
||||
let enableInactivityTimeout: boolean = false;
|
||||
|
||||
if (episode.incidentGroupingRuleId) {
|
||||
const rule: IncidentGroupingRule | null =
|
||||
await IncidentGroupingRuleService.findOneById({
|
||||
id: episode.incidentGroupingRuleId,
|
||||
select: {
|
||||
enableInactivityTimeout: true,
|
||||
inactivityTimeoutMinutes: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (rule) {
|
||||
enableInactivityTimeout = rule.enableInactivityTimeout || false;
|
||||
if (
|
||||
enableInactivityTimeout &&
|
||||
rule.inactivityTimeoutMinutes !== undefined
|
||||
) {
|
||||
inactivityTimeoutMinutes = rule.inactivityTimeoutMinutes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If inactivity timeout is not enabled or is 0, don't resolve inactive episodes
|
||||
if (!enableInactivityTimeout || inactivityTimeoutMinutes <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if episode has been inactive for too long
|
||||
const lastIncidentAddedAt: Date =
|
||||
|
||||
@@ -276,9 +276,13 @@ LETS_ENCRYPT_ACCOUNT_KEY=
|
||||
# This is the number of active monitors allowed in the free plan.
|
||||
ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN=10
|
||||
|
||||
# Open Source Deployment Webhook
|
||||
# This webhook is called when a new self-hosted open source deployment registers.
|
||||
OPEN_SOURCE_DEPLOYMENT_WEBHOOK_URL=
|
||||
|
||||
# Notifications Webhook (Slack)
|
||||
|
||||
# This webhook notifies slack when the new user signs up or is created.
|
||||
# This webhook notifies slack when the new user signs up or is created.
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER=
|
||||
# This webhook notifies slack when the new project is created.
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_PROJECT=
|
||||
|
||||
@@ -127,6 +127,9 @@ x-common-runtime-variables: &common-runtime-variables
|
||||
|
||||
DISABLE_AUTOMATIC_ALERT_CREATION: ${DISABLE_AUTOMATIC_ALERT_CREATION}
|
||||
|
||||
# Open Source Deployment Webhook
|
||||
OPEN_SOURCE_DEPLOYMENT_WEBHOOK_URL: ${OPEN_SOURCE_DEPLOYMENT_WEBHOOK_URL}
|
||||
|
||||
# Notification Webhooks
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER: ${NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER}
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_PROJECT: ${NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_PROJECT}
|
||||
|
||||
Reference in New Issue
Block a user