feat: Add monitorId to MonitorTest model and related migrations

This commit is contained in:
Simon Larsen
2025-05-19 18:46:25 +01:00
parent 2d9f9d41d0
commit cb545e445a
7 changed files with 244 additions and 53 deletions

View File

@@ -21,6 +21,7 @@ import Permission from "../../Types/Permission";
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
import { MonitorStepProbeResponse } from "./MonitorProbe";
import Probe from "./Probe";
import Monitor from "./Monitor";
@TenantColumn("projectId")
@TableAccessControl({
@@ -475,4 +476,70 @@ export default class MonitorTest extends BaseModel {
default: true,
})
public isInQueue?: boolean = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectMonitor,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectMonitor,
],
update: [],
})
@Index()
@TableColumn({
type: TableColumnType.ObjectID,
required: false,
canReadOnRelationQuery: true,
title: "Monitor ID",
description: "ID of the Monitor this test is related to.",
})
@Column({
type: ColumnType.ObjectID,
nullable: true,
transformer: ObjectID.getDatabaseTransformer(),
})
public monitorId?: ObjectID = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectMonitor,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectMonitor,
],
update: [],
})
@TableColumn({
manyToOneRelationColumn: "monitorId",
type: TableColumnType.Entity,
modelType: Monitor,
title: "Monitor",
description: "Relation to Monitor Resource in which this test belongs.",
})
@ManyToOne(
() => {
return Monitor;
},
{
eager: false,
nullable: true,
onDelete: "SET NULL",
orphanedRowAction: "nullify",
},
)
@JoinColumn({ name: "monitorId" })
public monitor?: Monitor = undefined;
}

View File

@@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1747674762672 implements MigrationInterface {
public name = "MigrationName1747674762672";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "MonitorTest" ADD "monitorId" uuid`);
await queryRunner.query(
`CREATE INDEX "IDX_4650119024eca8c91608effb95" ON "MonitorTest" ("monitorId") `,
);
await queryRunner.query(
`ALTER TABLE "MonitorTest" ADD CONSTRAINT "FK_4650119024eca8c91608effb959" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "MonitorTest" DROP CONSTRAINT "FK_4650119024eca8c91608effb959"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_4650119024eca8c91608effb95"`,
);
await queryRunner.query(
`ALTER TABLE "MonitorTest" DROP COLUMN "monitorId"`,
);
}
}

View File

@@ -132,6 +132,7 @@ import { MigrationName1743714801105 } from "./1743714801105-MigrationName";
import { MigrationName1744804990712 } from "./1744804990712-MigrationName";
import { MigrationName1744809770336 } from "./1744809770336-MigrationName";
import { MigrationName1747305098533 } from "./1747305098533-MigrationName";
import { MigrationName1747674762672 } from "./1747674762672-MigrationName";
export default [
InitialMigration,
@@ -268,4 +269,5 @@ export default [
MigrationName1744804990712,
MigrationName1744809770336,
MigrationName1747305098533,
MigrationName1747674762672,
];

View File

@@ -26,6 +26,7 @@ import { MonitorStepProbeResponse } from "Common/Models/DatabaseModels/MonitorPr
import SummaryInfo from "../../Monitor/SummaryView/SummaryInfo";
export interface ComponentProps {
monitorId?: ObjectID | undefined;
monitorSteps: MonitorSteps;
monitorType: MonitorType;
probes: Array<Probe>;
@@ -70,6 +71,9 @@ const MonitorTestForm: FunctionComponent<ComponentProps> = (
monitorTestObj.probeId = probeId;
monitorTestObj.monitorType = props.monitorType;
monitorTestObj.isInQueue = true;
if (props.monitorId) {
monitorTestObj.monitorId = props.monitorId;
}
// save the monitor test to the database.

View File

@@ -124,6 +124,7 @@ const MonitorCriteria: FunctionComponent<
description: "Here is the criteria we use to monitor this resource.",
rightElement: monitorSteps ? (
<MonitorTestForm
monitorId={modelId}
buttonSize={ButtonSize.Normal}
monitorSteps={monitorSteps}
monitorType={monitorType}

View File

@@ -526,6 +526,7 @@ router.post(
monitorSteps: true,
_id: true,
projectId: true,
monitorId: true,
},
props: {
isRoot: true,
@@ -555,13 +556,38 @@ router.post(
await Promise.all(updatePromises);
logger.debug("Populating secrets");
logger.debug(monitorTests);
// check if the monitor needs secrets to be filled.
let monitorTestsWithSecretPopulated: Array<MonitorTest> = [];
const monitorTestsWithSecretsPopulatePromises: Array<
Promise<MonitorTest>
> = [];
for (const monitorTest of monitorTests) {
monitorTestsWithSecretsPopulatePromises.push(
MonitorUtil.populateSecretsOnMonitorTest(monitorTest),
);
}
monitorTestsWithSecretPopulated = await Promise.all(
monitorTestsWithSecretsPopulatePromises,
);
logger.debug("Populated secrets");
logger.debug(monitorTestsWithSecretPopulated);
// return the list of monitors to be monitored
logger.debug("Sending response");
return Response.sendEntityArrayResponse(
req,
res,
monitorTests,
new PositiveNumber(monitorTests.length),
monitorTestsWithSecretPopulated,
new PositiveNumber(monitorTestsWithSecretPopulated.length),
MonitorTest,
);
} catch (err) {

View File

@@ -2,7 +2,6 @@ import Hostname from "Common/Types/API/Hostname";
import URL from "Common/Types/API/URL";
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
import Dictionary from "Common/Types/Dictionary";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import IP from "Common/Types/IP/IP";
import { JSONObject } from "Common/Types/JSON";
import JSONFunctions from "Common/Types/JSONFunctions";
@@ -11,53 +10,56 @@ import MonitorSecretService from "Common/Server/Services/MonitorSecretService";
import VMUtil from "Common/Server/Utils/VM/VMAPI";
import Monitor from "Common/Models/DatabaseModels/Monitor";
import MonitorSecret from "Common/Models/DatabaseModels/MonitorSecret";
import MonitorTest from "Common/Models/DatabaseModels/MonitorTest";
import ObjectID from "Common/Types/ObjectID";
import MonitorSteps from "Common/Types/Monitor/MonitorSteps";
export default class MonitorUtil {
public static async populateSecrets(monitor: Monitor): Promise<Monitor> {
public static async loadMonitorSecrets(
monitorId: ObjectID,
): Promise<MonitorSecret[]> {
const secrets: Array<MonitorSecret> = await MonitorSecretService.findBy({
query: {
monitors: [monitorId] as any,
},
select: {
secretValue: true,
name: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
props: {
isRoot: true,
},
});
return secrets;
}
public static async populateSecretsInMonitorSteps(data: {
monitorSteps: MonitorSteps;
monitorType: MonitorType;
monitorId: ObjectID;
}): Promise<MonitorSteps> {
const isSecretsLoaded: boolean = false;
let monitorSecrets: MonitorSecret[] = [];
const loadSecrets: PromiseVoidFunction = async (): Promise<void> => {
if (isSecretsLoaded) {
return;
}
const monitorSteps: MonitorSteps = data.monitorSteps;
const monitorType: MonitorType = data.monitorType;
const monitorId: ObjectID = data.monitorId;
if (!monitor.id) {
return;
}
const secrets: Array<MonitorSecret> = await MonitorSecretService.findBy({
query: {
monitors: [monitor.id] as any,
},
select: {
secretValue: true,
name: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
props: {
isRoot: true,
},
});
monitorSecrets = secrets;
};
if (!monitor.monitorSteps) {
return monitor;
}
if (monitor.monitorType === MonitorType.API) {
for (const monitorStep of monitor.monitorSteps?.data
?.monitorStepsInstanceArray || []) {
if (monitorType === MonitorType.API) {
for (const monitorStep of monitorSteps?.data?.monitorStepsInstanceArray ||
[]) {
if (
monitorStep.data?.requestHeaders &&
this.hasSecrets(
JSONFunctions.toString(monitorStep.data.requestHeaders),
)
) {
await loadSecrets();
if (!isSecretsLoaded) {
monitorSecrets = await MonitorUtil.loadMonitorSecrets(monitorId);
}
monitorStep.data.requestHeaders =
(await MonitorUtil.fillSecretsInStringOrJSON({
@@ -68,7 +70,9 @@ export default class MonitorUtil {
monitorStep.data?.requestBody &&
this.hasSecrets(JSONFunctions.toString(monitorStep.data.requestBody))
) {
await loadSecrets();
if (!isSecretsLoaded) {
monitorSecrets = await MonitorUtil.loadMonitorSecrets(monitorId);
}
monitorStep.data.requestBody =
(await MonitorUtil.fillSecretsInStringOrJSON({
@@ -80,15 +84,15 @@ export default class MonitorUtil {
}
if (
monitor.monitorType === MonitorType.API ||
monitor.monitorType === MonitorType.IP ||
monitor.monitorType === MonitorType.Ping ||
monitor.monitorType === MonitorType.Port ||
monitor.monitorType === MonitorType.Website ||
monitor.monitorType === MonitorType.SSLCertificate
monitorType === MonitorType.API ||
monitorType === MonitorType.IP ||
monitorType === MonitorType.Ping ||
monitorType === MonitorType.Port ||
monitorType === MonitorType.Website ||
monitorType === MonitorType.SSLCertificate
) {
for (const monitorStep of monitor.monitorSteps?.data
?.monitorStepsInstanceArray || []) {
for (const monitorStep of monitorSteps?.data?.monitorStepsInstanceArray ||
[]) {
if (
monitorStep.data?.monitorDestination &&
this.hasSecrets(
@@ -96,7 +100,9 @@ export default class MonitorUtil {
)
) {
// replace secret in monitorDestination.
await loadSecrets();
if (!isSecretsLoaded) {
monitorSecrets = await MonitorUtil.loadMonitorSecrets(monitorId);
}
monitorStep.data.monitorDestination =
(await MonitorUtil.fillSecretsInStringOrJSON({
@@ -108,17 +114,19 @@ export default class MonitorUtil {
}
if (
monitor.monitorType === MonitorType.SyntheticMonitor ||
monitor.monitorType === MonitorType.CustomJavaScriptCode
monitorType === MonitorType.SyntheticMonitor ||
monitorType === MonitorType.CustomJavaScriptCode
) {
for (const monitorStep of monitor.monitorSteps?.data
?.monitorStepsInstanceArray || []) {
for (const monitorStep of monitorSteps?.data?.monitorStepsInstanceArray ||
[]) {
if (
monitorStep.data?.customCode &&
this.hasSecrets(JSONFunctions.toString(monitorStep.data.customCode))
) {
// replace secret in script
await loadSecrets();
if (!isSecretsLoaded) {
monitorSecrets = await MonitorUtil.loadMonitorSecrets(monitorId);
}
monitorStep.data.customCode =
(await MonitorUtil.fillSecretsInStringOrJSON({
@@ -129,6 +137,62 @@ export default class MonitorUtil {
}
}
return monitorSteps;
}
public static async populateSecretsOnMonitorTest(
monitorTest: MonitorTest,
): Promise<MonitorTest> {
const monitorId: ObjectID | undefined = monitorTest.monitorId;
if (!monitorId) {
return monitorTest;
}
if (!monitorTest.monitorSteps) {
return monitorTest;
}
if (!monitorTest.monitorSteps.data) {
return monitorTest;
}
if (!monitorTest.monitorType) {
return monitorTest;
}
monitorTest.monitorSteps = await MonitorUtil.populateSecretsInMonitorSteps({
monitorSteps: monitorTest.monitorSteps,
monitorType: monitorTest.monitorType,
monitorId: monitorId,
});
return monitorTest;
}
public static async populateSecrets(monitor: Monitor): Promise<Monitor> {
if (!monitor.id) {
return monitor;
}
if (!monitor.monitorSteps) {
return monitor;
}
if (!monitor.monitorSteps.data) {
return monitor;
}
if (!monitor.monitorType) {
return monitor;
}
monitor.monitorSteps = await MonitorUtil.populateSecretsInMonitorSteps({
monitorSteps: monitor.monitorSteps,
monitorType: monitor.monitorType,
monitorId: monitor.id,
});
return monitor;
}