Compare commits

..

47 Commits

Author SHA1 Message Date
Simon Larsen
a18207b04f feat: add isManualExecution flag to workflow execution and related components 2025-02-26 14:08:11 +00:00
Simon Larsen
cf7c39d96c feat: improve error handling in manually run workflow API by adding try-catch block 2025-02-26 13:16:23 +00:00
Simon Larsen
4738f8f930 feat: enhance error handling in workflow execution by introducing HTTP response type checks 2025-02-26 13:16:05 +00:00
Simon Larsen
390a4cbbf0 feat: implement channel joining functionality and enhance channel name retrieval in Slack utility methods 2025-02-25 22:20:55 +00:00
Simon Larsen
4bbbb9b473 feat: enhance Slack integration with new user invitation methods and bot user ID retrieval 2025-02-25 19:49:44 +00:00
Simon Larsen
482c7c142e feat: expand bot permissions in Slack app manifest for enhanced functionality 2025-02-25 18:56:59 +00:00
Simon Larsen
9d581fc176 feat: update Slack app manifest to refine user and bot scopes 2025-02-25 17:07:19 +00:00
Simon Larsen
d680d09dc6 feat: add response validation and error logging in Slack utility methods 2025-02-25 16:33:24 +00:00
Simon Larsen
1ab55e1c19 refactor: streamline API request headers in Slack utility methods for improved readability 2025-02-25 16:30:42 +00:00
Simon Larsen
427514ceb3 feat: add detailed debug logging to Slack utility methods for improved traceability 2025-02-25 16:21:26 +00:00
Simon Larsen
e443288074 refactor: format debug logging statements in WorkspaceNotificationRuleService for consistency 2025-02-25 16:08:53 +00:00
Simon Larsen
d14686a288 feat: enhance logging in WorkspaceNotificationRuleService for better traceability 2025-02-25 16:00:37 +00:00
Simon Larsen
f1b08dab84 feat: add detailed logging to WorkspaceNotificationRuleService methods 2025-02-25 15:24:32 +00:00
Simon Larsen
74a73411ed feat: add workspaceType to the service model properties in WorkspaceProjectAuthTokenService 2025-02-25 15:19:32 +00:00
Simon Larsen
ab76ecd04f fix: uncomment and restore workspace notification logic in IncidentService 2025-02-25 14:59:38 +00:00
Simon Larsen
5816be7041 feat: add placeholder and conditional display logic for new channel creation in NotificationRuleViewElement 2025-02-25 14:49:32 +00:00
Simon Larsen
300bbbc326 fix: uncomment Workspace Connections section in SideMenu components 2025-02-25 13:20:57 +00:00
Simon Larsen
9c76bd283d fix: correct spelling of 'oneuptime' in various files 2025-02-25 13:10:04 +00:00
Simon Larsen
cb263de054 fix: initialize cardbuttons array with props.cardButtons if available 2025-02-25 12:25:01 +00:00
Simon Larsen
06f3ba40dc refactor: clean up code formatting in OnCallPolicyExecutionLogs components 2025-02-24 21:32:15 +00:00
Simon Larsen
f5febd029a fix: update incidentId to alertId in OnCallPolicyExecutionLogs component 2025-02-24 21:28:36 +00:00
Simon Larsen
09fde3a29b feat: add On Call Policy Execution Logs pages and update breadcrumbs and routes 2025-02-24 21:26:28 +00:00
Simon Larsen
e628968b97 feat: add triggeredByUserId to OnCallDutyPolicyExecutionLog and update related services and migrations 2025-02-24 21:18:03 +00:00
Simon Larsen
12b31ca237 feat: add on-call policy execution modal to Alert and Incident feeds 2025-02-24 21:15:58 +00:00
Simon Larsen
f2de1695f5 feat: add triggeredByUserId to OnCallDutyPolicyExecutionLog and create permission for log creation 2025-02-24 20:54:04 +00:00
Simon Larsen
ef8c36d176 fix: improve string concatenation for log messages in workflow services 2025-02-24 18:49:39 +00:00
Simon Larsen
2ea10bfbdd feat: enhance date formatting options to include seconds in logs 2025-02-24 18:45:46 +00:00
Simon Larsen
4dcd691ef7 fix: add missing semicolons in WebhookTrigger class methods 2025-02-24 18:33:53 +00:00
Simon Larsen
61ff89af8f fix: add error handling to webhook trigger routes 2025-02-24 18:33:24 +00:00
Simon Larsen
35b3dbfd8b refactor: clean up code formatting and improve readability in StatusPageAPI, MasterPage, and NavBar components 2025-02-24 18:07:19 +00:00
Simon Larsen
9174a04795 feat: implement conditional fetching for incidents, announcements, and scheduled maintenance events based on status page settings 2025-02-24 18:06:13 +00:00
Simon Larsen
4a80b1c8fd feat: add showIncidentsOnStatusPage, showAnnouncementsOnStatusPage, and showScheduledMaintenanceEventsOnStatusPage props to StatusPage components 2025-02-24 18:00:17 +00:00
Simon Larsen
95c2bee9a9 feat: add toggle fields for showIncidentsOnStatusPage, showAnnouncementsOnStatusPage, and showScheduledMaintenanceEventsOnStatusPage in StatusPage settings 2025-02-24 17:51:13 +00:00
Simon Larsen
3d0f0a62fe feat: add showIncidentsOnStatusPage, showAnnouncementsOnStatusPage, and showScheduledMaintenanceEventsOnStatusPage fields to StatusPage model 2025-02-24 17:48:23 +00:00
Simon Larsen
b23af6c6a9 feat: add validation for startDate and endDate in uptime API and implement worst status calculation 2025-02-24 17:42:02 +00:00
Simon Larsen
434305acc9 feat: enhance uptime API to support startDate and endDate parameters and update response structure 2025-02-24 17:13:55 +00:00
Simon Larsen
a225314c65 refactor: update getMonitorStatusTimelineForStatusPage to use startDate and endDate parameters 2025-02-24 16:18:50 +00:00
Simon Larsen
a8e20574c7 Merge branch 'sp-api' 2025-02-22 17:26:22 +00:00
Simon Larsen
c667fddc64 chore: update Node.js base image to version 23.8 in Dockerfile templates 2025-02-21 22:38:02 +00:00
Simon Larsen
c2ce1ea140 feat: add server monitor ingest hostname and port to Helm chart configuration 2025-02-21 21:35:49 +00:00
Simon Larsen
8b1680fab5 fix: rename package from probe-ingest to server-monitor-ingest in package.json 2025-02-21 20:31:06 +00:00
Simon Larsen
fe64380384 feat: add server monitor ingest service with Docker and configuration updates 2025-02-21 20:23:27 +00:00
Simon Larsen
449a1db573 feat: add visibility flag for status page in StatusPageAPI 2025-02-21 19:34:33 +00:00
Simon Larsen
e4c05c09f0 refactor: clean up code formatting and improve readability in Incident and ScheduledMaintenance models 2025-02-21 19:29:04 +00:00
Simon Larsen
d4107e049f feat: add settings page for scheduled maintenance events with visibility control 2025-02-21 19:28:10 +00:00
Simon Larsen
3811f9648c feat: add settings page for incident management and visibility control 2025-02-21 19:14:05 +00:00
Simon Larsen
f4415dafc7 feat: add TLS configuration to MailService for handling unauthorized certificates 2025-02-20 19:20:01 +00:00
109 changed files with 8226 additions and 655 deletions

View File

@@ -255,6 +255,21 @@ jobs:
- name: build docker image
run: sudo docker build -f ./ProbeIngest/Dockerfile .
docker-build-server-monitor-ingest:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preinstall
run: npm run prerun
# build image probe api
- name: build docker image
run: sudo docker build -f ./ServerMonitorIngest/Dockerfile .
docker-build-open-telemetry-ingest:
runs-on: ubuntu-latest
env:

View File

@@ -217,6 +217,18 @@ jobs:
- run: cd Common && npm install
- run: cd ProbeIngest && npm install && npm run compile && npm run dep-check
compile-server-monitor-ingest:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: latest
- run: cd Common && npm install
- run: cd ServerMonitorIngest && npm install && npm run compile && npm run dep-check
compile-open-telemetry-ingest:
runs-on: ubuntu-latest
env:

View File

@@ -613,6 +613,69 @@ jobs:
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
server-monitor-ingest-docker-image-deploy:
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
oneuptime/server-monitor-ingest
ghcr.io/oneuptime/server-monitor-ingest
tags: |
type=raw,value=release,enable=true
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}},pattern={{version}},enable=true
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- uses: actions/setup-node@v4
with:
node-version: latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Generate Dockerfile from Dockerfile.tpl
run: npm run prerun
# Build and deploy probe-ingest.
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.2.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
file: ./ServerMonitorIngest/Dockerfile
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
open-telemetry-ingest-docker-image-deploy:
needs: [generate-build-number]
runs-on: ubuntu-latest
@@ -1590,7 +1653,7 @@ jobs:
test-e2e-release-saas:
runs-on: ubuntu-latest
needs: [open-telemetry-ingest-docker-image-deploy, copilot-docker-image-deploy, fluent-ingest-docker-image-deploy, docs-docker-image-deploy, api-reference-docker-image-deploy, workflow-docker-image-deploy, llm-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, probe-ingest-docker-image-deploy, isolated-vm-docker-image-deploy, home-docker-image-deploy, worker-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy, incoming-request-ingest-docker-image-deploy]
needs: [open-telemetry-ingest-docker-image-deploy, copilot-docker-image-deploy, fluent-ingest-docker-image-deploy, docs-docker-image-deploy, api-reference-docker-image-deploy, workflow-docker-image-deploy, llm-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, probe-ingest-docker-image-deploy, server-monitor-ingest-docker-image-deploy, isolated-vm-docker-image-deploy, home-docker-image-deploy, worker-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy, incoming-request-ingest-docker-image-deploy]
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
@@ -1643,7 +1706,7 @@ jobs:
test-e2e-release-self-hosted:
runs-on: ubuntu-latest
# After all the jobs runs
needs: [open-telemetry-ingest-docker-image-deploy, copilot-docker-image-deploy, incoming-request-ingest-docker-image-deploy, fluent-ingest-docker-image-deploy, docs-docker-image-deploy, api-reference-docker-image-deploy, workflow-docker-image-deploy, llm-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, probe-ingest-docker-image-deploy, isolated-vm-docker-image-deploy, home-docker-image-deploy, worker-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy]
needs: [open-telemetry-ingest-docker-image-deploy, copilot-docker-image-deploy, incoming-request-ingest-docker-image-deploy, fluent-ingest-docker-image-deploy, docs-docker-image-deploy, api-reference-docker-image-deploy, workflow-docker-image-deploy, llm-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, probe-ingest-docker-image-deploy, server-monitor-ingest-docker-image-deploy, isolated-vm-docker-image-deploy, home-docker-image-deploy, worker-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy]
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:

View File

@@ -664,6 +664,72 @@ jobs:
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
server-monitor-ingest-docker-image-deploy:
needs: generate-build-number
runs-on: ubuntu-latest
steps:
- name: Docker Meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
oneuptime/server-monitor-ingest
ghcr.io/oneuptime/server-monitor-ingest
tags: |
type=raw,value=test,enable=true
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}}-test,pattern={{version}},enable=true
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- uses: actions/setup-node@v4
with:
node-version: latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Generate Dockerfile from Dockerfile.tpl
run: npm run prerun
# Build and deploy ServerMonitorIngest.
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.2.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
file: ./ServerMonitorIngest/Dockerfile
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
incoming-request-ingest-docker-image-deploy:
needs: generate-build-number
runs-on: ubuntu-latest
@@ -1530,7 +1596,7 @@ jobs:
test-helm-chart:
runs-on: ubuntu-latest
needs: [infrastructure-agent-deploy, llm-docker-image-deploy, open-telemetry-ingest-docker-image-deploy, copilot-docker-image-deploy, docs-docker-image-deploy, worker-docker-image-deploy, workflow-docker-image-deploy, isolated-vm-docker-image-deploy, home-docker-image-deploy, api-reference-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, probe-ingest-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy, fluent-ingest-docker-image-deploy, incoming-request-ingest-docker-image-deploy]
needs: [infrastructure-agent-deploy, llm-docker-image-deploy, open-telemetry-ingest-docker-image-deploy, copilot-docker-image-deploy, docs-docker-image-deploy, worker-docker-image-deploy, workflow-docker-image-deploy, isolated-vm-docker-image-deploy, home-docker-image-deploy, api-reference-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, probe-ingest-docker-image-deploy, server-monitor-ingest-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy, fluent-ingest-docker-image-deploy, incoming-request-ingest-docker-image-deploy]
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:

14
.vscode/launch.json vendored
View File

@@ -175,6 +175,20 @@
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/ServerMonitorIngest",
"name": "ServerMonitorIngest: Debug with Docker",
"port": 9941,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/IncomingRequestIngest",

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.2-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.7.3-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.7.3-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.2-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -35,6 +35,7 @@ import fsp from "fs/promises";
import Handlebars from "handlebars";
import nodemailer, { Transporter } from "nodemailer";
import Path from "path";
import * as tls from "tls";
export default class MailService {
public static isSMTPConfigValid(obj: JSONObject): boolean {
@@ -204,10 +205,19 @@ export default class MailService {
timeout?: number | undefined;
},
): Transporter {
let tlsOptions: tls.ConnectionOptions | undefined = undefined;
if (!emailServer.secure) {
tlsOptions = {
rejectUnauthorized: false,
};
}
const privateMailer: Transporter = nodemailer.createTransport({
host: emailServer.host.toString(),
port: emailServer.port.toNumber(),
secure: emailServer.secure,
tls: tlsOptions,
auth:
emailServer.username && emailServer.password
? {

View File

@@ -1139,4 +1139,37 @@ export default class Incident extends BaseModel {
nullable: true,
})
public postUpdatesToWorkspaceChannels?: Array<WorkspaceChannel> = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectIncident,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectIncident,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.EditProjectIncident,
],
})
@TableColumn({
isDefaultValueColumn: true,
type: TableColumnType.Boolean,
title: "Should be visible on status page?",
description: "Should this incident be visible on the status page?",
})
@Column({
type: ColumnType.Boolean,
default: true,
nullable: true,
})
public isVisibleOnStatusPage?: boolean = undefined;
}

View File

@@ -35,7 +35,12 @@ import Alert from "./Alert";
@EnableDocumentation()
@TenantColumn("projectId")
@TableAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -58,7 +63,12 @@ import Alert from "./Alert";
})
export default class OnCallDutyPolicyExecutionLog extends BaseModel {
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -89,7 +99,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public project?: Project = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -114,7 +129,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public projectId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -146,7 +166,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public onCallDutyPolicy?: OnCallDutyPolicy = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -172,7 +197,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public onCallDutyPolicyId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -204,7 +234,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public triggeredByIncident?: Incident = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -228,7 +263,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public triggeredByIncidentId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -259,7 +299,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public triggeredByAlert?: Alert = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -283,7 +328,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public triggeredByAlertId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -307,7 +357,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public status?: OnCallDutyPolicyStatus = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -331,7 +386,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public statusMessage?: string = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -355,7 +415,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public userNotificationEventType?: UserNotificationEventType = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -387,7 +452,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public createdByUser?: User = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -410,7 +480,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public createdByUserId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [],
update: [],
})
@@ -437,7 +512,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public deletedByUser?: User = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -460,7 +540,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public deletedByUserId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -493,7 +578,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public acknowledgedByUser?: User = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -516,7 +606,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public acknowledgedByUserId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -535,7 +630,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public acknowledgedAt?: Date = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [],
update: [],
})
@@ -562,7 +662,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public acknowledgedByTeam?: Team = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -585,7 +690,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public acknowledgedByTeamId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [],
update: [],
})
@@ -604,7 +714,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public lastExecutedEscalationRuleOrder?: number = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [],
update: [],
})
@@ -623,7 +738,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public lastEscalationRuleExecutedAt?: Date = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -655,7 +775,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
@@ -680,7 +805,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public lastExecutedEscalationRuleId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [],
update: [],
})
@@ -700,7 +830,12 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
public executeNextEscalationRuleInMinutes?: number = undefined;
@ColumnAccessControl({
create: [],
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [],
update: [],
})
@@ -719,4 +854,67 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
default: 1,
})
public onCallPolicyExecutionRepeatCount?: number = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectOnCallDutyPolicyExecutionLog,
],
update: [],
})
@TableColumn({
manyToOneRelationColumn: "triggeredByUserId",
type: TableColumnType.Entity,
modelType: User,
title: "Triggered by User",
description: "Relation to User who triggered on-clal policy",
})
@ManyToOne(
() => {
return User;
},
{
eager: false,
nullable: true,
onDelete: "SET NULL",
orphanedRowAction: "nullify",
},
)
@JoinColumn({ name: "triggeredByUserId" })
public triggeredByUser?: User = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectOnCallDutyPolicyExecutionLog,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectOnCallDutyPolicyExecutionLog,
],
update: [],
})
@TableColumn({
type: TableColumnType.ObjectID,
title: "Triggered by User ID",
description: "User ID who triggered this on-call policy",
})
@Column({
type: ColumnType.ObjectID,
nullable: true,
transformer: ObjectID.getDatabaseTransformer(),
})
public triggeredByUserId?: ObjectID = undefined;
}

View File

@@ -998,4 +998,37 @@ export default class ScheduledMaintenance extends BaseModel {
nullable: true,
})
public postUpdatesToWorkspaceChannels?: Array<WorkspaceChannel> = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectScheduledMaintenance,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectScheduledMaintenance,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.EditProjectScheduledMaintenance,
],
})
@TableColumn({
isDefaultValueColumn: true,
type: TableColumnType.Boolean,
title: "Should be visible on status page?",
description: "Should this incident be visible on the status page?",
})
@Column({
type: ColumnType.Boolean,
default: true,
nullable: true,
})
public isVisibleOnStatusPage?: boolean = undefined;
}

View File

@@ -2006,4 +2006,118 @@ export default class StatusPage extends BaseModel {
type: ColumnType.VeryLongText,
})
public subscriberEmailNotificationFooterText?: string = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectStatusPage,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectStatusPage,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.EditProjectStatusPage,
],
})
@TableColumn({
isDefaultValueColumn: true,
type: TableColumnType.Boolean,
title: "Show Incidents on Status Page",
description: "Show Incidents on Status Page?",
})
@Column({
type: ColumnType.Boolean,
default: true,
nullable: false,
})
@ColumnBillingAccessControl({
read: PlanType.Free,
update: PlanType.Growth,
create: PlanType.Free,
})
public showIncidentsOnStatusPage?: boolean = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectStatusPage,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectStatusPage,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.EditProjectStatusPage,
],
})
@TableColumn({
isDefaultValueColumn: true,
type: TableColumnType.Boolean,
title: "Show Announcements on Status Page",
description: "Show Announcements on Status Page?",
})
@Column({
type: ColumnType.Boolean,
default: true,
nullable: false,
})
@ColumnBillingAccessControl({
read: PlanType.Free,
update: PlanType.Growth,
create: PlanType.Free,
})
public showAnnouncementsOnStatusPage?: boolean = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CreateProjectStatusPage,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectStatusPage,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.EditProjectStatusPage,
],
})
@TableColumn({
isDefaultValueColumn: true,
type: TableColumnType.Boolean,
title: "Show Scheduled Maintenance Events on Status Page",
description: "Show Scheduled Maintenance Events on Status Page?",
})
@Column({
type: ColumnType.Boolean,
default: true,
nullable: false,
})
@ColumnBillingAccessControl({
read: PlanType.Free,
update: PlanType.Growth,
create: PlanType.Free,
})
public showScheduledMaintenanceEventsOnStatusPage?: boolean = undefined;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1740164199817 implements MigrationInterface {
public name = "MigrationName1740164199817";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "Incident" ADD "isVisibleOnStatusPage" boolean DEFAULT true`,
);
await queryRunner.query(
`ALTER TABLE "ScheduledMaintenance" ADD "isVisibleOnStatusPage" boolean DEFAULT true`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "ScheduledMaintenance" DROP COLUMN "isVisibleOnStatusPage"`,
);
await queryRunner.query(
`ALTER TABLE "Incident" DROP COLUMN "isVisibleOnStatusPage"`,
);
}
}

View File

@@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1740419151825 implements MigrationInterface {
public name = "MigrationName1740419151825";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "StatusPage" ADD "showIncidentsOnStatusPage" boolean NOT NULL DEFAULT true`,
);
await queryRunner.query(
`ALTER TABLE "StatusPage" ADD "showAnnouncementsOnStatusPage" boolean NOT NULL DEFAULT true`,
);
await queryRunner.query(
`ALTER TABLE "StatusPage" ADD "showScheduledMaintenanceEventsOnStatusPage" boolean NOT NULL DEFAULT true`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "StatusPage" DROP COLUMN "showScheduledMaintenanceEventsOnStatusPage"`,
);
await queryRunner.query(
`ALTER TABLE "StatusPage" DROP COLUMN "showAnnouncementsOnStatusPage"`,
);
await queryRunner.query(
`ALTER TABLE "StatusPage" DROP COLUMN "showIncidentsOnStatusPage"`,
);
}
}

View File

@@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1740430229844 implements MigrationInterface {
public name = "MigrationName1740430229844";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD "triggeredByUserId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_0ed55adc637e8ed7a524f942b18" FOREIGN KEY ("triggeredByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_0ed55adc637e8ed7a524f942b18"`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP COLUMN "triggeredByUserId"`,
);
}
}

View File

@@ -105,6 +105,9 @@ import { MigrationName1739217257089 } from "./1739217257089-MigrationName";
import { MigrationName1739282331053 } from "./1739282331053-MigrationName";
import { MigrationName1739374537088 } from "./1739374537088-MigrationName";
import { MigrationName1739569321582 } from "./1739569321582-MigrationName";
import { MigrationName1740164199817 } from "./1740164199817-MigrationName";
import { MigrationName1740419151825 } from "./1740419151825-MigrationName";
import { MigrationName1740430229844 } from "./1740430229844-MigrationName";
export default [
InitialMigration,
@@ -214,4 +217,7 @@ export default [
MigrationName1739282331053,
MigrationName1739374537088,
MigrationName1739569321582,
MigrationName1740164199817,
MigrationName1740419151825,
MigrationName1740430229844,
];

View File

@@ -475,31 +475,31 @@ ${createdItem.remediationNotes || "No remediation notes provided."}`,
}
}
// // send message to workspaces - slack, teams, etc.
// const createdChannels: {
// channelsCreated: Array<WorkspaceChannel>;
// } | null = await this.notifyWorkspaceOnIncidentCreate({
// projectId: createdItem.projectId,
// incidentId: createdItem.id!,
// incidentNumber: createdItem.incidentNumber!,
// });
// send message to workspaces - slack, teams, etc.
const createdChannels: {
channelsCreated: Array<WorkspaceChannel>;
} | null = await this.notifyWorkspaceOnIncidentCreate({
projectId: createdItem.projectId,
incidentId: createdItem.id!,
incidentNumber: createdItem.incidentNumber!,
});
// if (
// createdChannels &&
// createdChannels.channelsCreated &&
// createdChannels.channelsCreated.length > 0
// ) {
// // update incident with these channels.
// await this.updateOneById({
// id: createdItem.id!,
// data: {
// postUpdatesToWorkspaceChannels: createdChannels.channelsCreated,
// },
// props: {
// isRoot: true,
// },
// });
// }
if (
createdChannels &&
createdChannels.channelsCreated &&
createdChannels.channelsCreated.length > 0
) {
// update incident with these channels.
await this.updateOneById({
id: createdItem.id!,
data: {
postUpdatesToWorkspaceChannels: createdChannels.channelsCreated,
},
props: {
isRoot: true,
},
});
}
return createdItem;
}

View File

@@ -33,6 +33,14 @@ export class Service extends DatabaseService<Model> {
createBy.data.status = OnCallDutyPolicyStatus.Scheduled;
}
if (!createBy.data.statusMessage) {
createBy.data.statusMessage = "Scheduled.";
}
if (createBy.props.userId) {
createBy.data.triggeredByUserId = createBy.props.userId;
}
createBy.data.onCallPolicyExecutionRepeatCount = 1;
return { createBy, carryForward: null };

View File

@@ -380,12 +380,11 @@ export class Service extends DatabaseService<StatusPage> {
public async getMonitorStatusTimelineForStatusPage(data: {
monitorIds: Array<ObjectID>;
historyDays: number;
startDate: Date;
endDate: Date;
}): Promise<Array<MonitorStatusTimeline>> {
const startDate: Date = OneUptimeDate.getSomeDaysAgo(
data.historyDays || 14,
);
const endDate: Date = OneUptimeDate.getCurrentDate();
const startDate: Date = data.startDate;
const endDate: Date = data.endDate;
let monitorStatusTimelines: Array<MonitorStatusTimeline> = [];
@@ -756,9 +755,9 @@ export class Service extends DatabaseService<StatusPage> {
const numberOfDays: number = data.historyDays || 14;
const currentDate: Date = OneUptimeDate.getCurrentDate();
const endDate: Date = OneUptimeDate.getCurrentDate();
const startDate: Date = OneUptimeDate.getSomeDaysAgo(numberOfDays);
const startAndEndDate: string = `${numberOfDays} days (${OneUptimeDate.getDateAsLocalFormattedString(startDate, true)} - ${OneUptimeDate.getDateAsLocalFormattedString(currentDate, true)})`;
const startAndEndDate: string = `${numberOfDays} days (${OneUptimeDate.getDateAsLocalFormattedString(startDate, true)} - ${OneUptimeDate.getDateAsLocalFormattedString(endDate, true)})`;
if (statusPageResources.length === 0) {
return {
@@ -786,7 +785,8 @@ export class Service extends DatabaseService<StatusPage> {
const timeline: Array<MonitorStatusTimeline> =
await this.getMonitorStatusTimelineForStatusPage({
monitorIds: monitors.monitorsOnStatusPage,
historyDays: data.historyDays,
startDate: startDate,
endDate: endDate,
});
const reportItems: Array<StatusPageReportItem> = [];

View File

@@ -29,8 +29,12 @@ import WorkspaceUserAuthTokenService from "./WorkspaceUserAuthTokenService";
import WorkspaceMessagePayload, {
WorkspaceMessageBlock,
} from "../../Types/Workspace/WorkspaceMessagePayload";
import WorkspaceProjectAuthToken from "../../Models/DatabaseModels/WorkspaceProjectAuthToken";
import WorkspaceProjectAuthToken, {
MiscData,
SlackMiscData,
} from "../../Models/DatabaseModels/WorkspaceProjectAuthToken";
import WorkspaceProjectAuthTokenService from "./WorkspaceProjectAuthTokenService";
import logger from "../Utils/Logger";
export interface NotificationFor {
incidentId?: ObjectID | undefined;
@@ -44,6 +48,31 @@ export class Service extends DatabaseService<Model> {
super(Model);
}
public getBotUserIdFromprojectAuthToken(data: {
projectAuthToken: WorkspaceProjectAuthToken;
workspaceType: WorkspaceType;
}): string {
const miscData: MiscData | undefined = data.projectAuthToken.miscData;
if (!miscData) {
throw new BadDataException("Misc data not found in project auth token");
}
if (data.workspaceType === WorkspaceType.Slack) {
const userId: string = (miscData as SlackMiscData).botUserId;
if (!userId) {
throw new BadDataException(
"Bot user ID not found in project auth token",
);
}
return userId;
}
throw new BadDataException("Workspace type not supported");
}
public async createInviteAndPostToChannelsBasedOnRules(data: {
projectId: ObjectID;
notificationRuleEventType: NotificationRuleEventType;
@@ -53,6 +82,11 @@ export class Service extends DatabaseService<Model> {
}): Promise<{
channelsCreated: Array<WorkspaceChannel>;
} | null> {
logger.debug(
"WorkspaceNotificationRuleService.createInviteAndPostToChannelsBasedOnRules",
);
logger.debug(data);
const channelsCreated: Array<WorkspaceChannel> = [];
const projectAuths: Array<WorkspaceProjectAuthToken> =
@@ -60,6 +94,9 @@ export class Service extends DatabaseService<Model> {
projectId: data.projectId,
});
logger.debug("projectAuths");
logger.debug(projectAuths);
if (!projectAuths || projectAuths.length === 0) {
// do nothing.
return null;
@@ -85,10 +122,14 @@ export class Service extends DatabaseService<Model> {
notificationFor: data.notificationFor,
});
logger.debug("notificationRules");
logger.debug(notificationRules);
if (!notificationRules || notificationRules.length === 0) {
return null;
}
logger.debug("Creating channels based on rules");
const createdWorkspaceChannels: Array<WorkspaceChannel> =
await this.createChannelsBasedOnRules({
projectOrUserAuthTokenForWorkspasce: authToken,
@@ -100,6 +141,10 @@ export class Service extends DatabaseService<Model> {
notificationEventType: data.notificationRuleEventType,
});
logger.debug("createdWorkspaceChannels");
logger.debug(createdWorkspaceChannels);
logger.debug("Inviting users and teams to channels based on rules");
await this.inviteUsersAndTeamsToChannelsBasedOnRules({
projectId: data.projectId,
projectOrUserAuthTokenForWorkspasce: authToken,
@@ -114,6 +159,7 @@ export class Service extends DatabaseService<Model> {
),
});
logger.debug("Getting existing channel names from notification rules");
const existingChannelNames: Array<string> =
this.getExistingChannelNamesFromNotificationRules({
notificationRules: notificationRules.map((rule: Model) => {
@@ -121,14 +167,25 @@ export class Service extends DatabaseService<Model> {
}),
}) || [];
// add created channel names to existing channel names.
logger.debug("Existing channel names:");
logger.debug(existingChannelNames);
logger.debug("Adding created channel names to existing channel names");
for (const channel of createdWorkspaceChannels) {
if (!existingChannelNames.includes(channel.name)) {
existingChannelNames.push(channel.name);
}
}
logger.debug("Final list of channel names to post messages to:");
logger.debug(existingChannelNames);
logger.debug("Posting messages to workspace channels");
await this.postToWorkspaceChannels({
workspaceUserId: this.getBotUserIdFromprojectAuthToken({
projectAuthToken: projectAuth,
workspaceType: workspaceType,
}),
projectOrUserAuthTokenForWorkspasce: authToken,
workspaceType: workspaceType,
workspaceMessagePayload: {
@@ -138,23 +195,34 @@ export class Service extends DatabaseService<Model> {
},
});
logger.debug("Channels created:");
logger.debug(createdWorkspaceChannels);
channelsCreated.push(...createdWorkspaceChannels);
}
logger.debug("Returning created channels");
return {
channelsCreated: channelsCreated,
};
}
public async postToWorkspaceChannels(data: {
workspaceUserId: string;
projectOrUserAuthTokenForWorkspasce: string;
workspaceType: WorkspaceType;
workspaceMessagePayload: WorkspaceMessagePayload;
}): Promise<void> {
logger.debug("postToWorkspaceChannels called with data:");
logger.debug(data);
await WorkspaceUtil.getWorkspaceTypeUtil(data.workspaceType).sendMessage({
userId: data.workspaceUserId,
workspaceMessagePayload: data.workspaceMessagePayload,
authToken: data.projectOrUserAuthTokenForWorkspasce,
});
logger.debug("Message posted to workspace channels successfully");
}
public async inviteUsersAndTeamsToChannelsBasedOnRules(data: {
@@ -164,11 +232,17 @@ export class Service extends DatabaseService<Model> {
notificationRules: Array<CreateChannelNotificationRule>;
channelNames: Array<string>;
}): Promise<void> {
logger.debug("inviteUsersAndTeamsToChannelsBasedOnRules called with data:");
logger.debug(data);
const inviteUserIds: Array<ObjectID> =
await this.getUsersIdsToInviteToChannel({
notificationRules: data.notificationRules,
});
logger.debug("User IDs to invite:");
logger.debug(inviteUserIds);
const workspaceUserIds: Array<string> = [];
for (const userId of inviteUserIds) {
@@ -176,7 +250,7 @@ export class Service extends DatabaseService<Model> {
await this.getWorkspaceUserIdFromOneUptimeUserId({
projectId: data.projectId,
workspaceType: data.workspaceType,
oneupitmeUserId: userId,
oneuptimeUserId: userId,
});
if (workspaceUserId) {
@@ -184,6 +258,9 @@ export class Service extends DatabaseService<Model> {
}
}
logger.debug("Workspace User IDs to invite:");
logger.debug(workspaceUserIds);
await WorkspaceUtil.getWorkspaceTypeUtil(
data.workspaceType,
).inviteUsersToChannels({
@@ -193,19 +270,24 @@ export class Service extends DatabaseService<Model> {
workspaceUserIds: workspaceUserIds,
},
});
logger.debug("Users invited to channels successfully");
}
public async getWorkspaceUserIdFromOneUptimeUserId(data: {
projectId: ObjectID;
workspaceType: WorkspaceType;
oneupitmeUserId: ObjectID;
oneuptimeUserId: ObjectID;
}): Promise<string | null> {
logger.debug("getWorkspaceUserIdFromOneUptimeUserId called with data:");
logger.debug(data);
const userAuth: WorkspaceUserAuthToken | null =
await WorkspaceUserAuthTokenService.findOneBy({
query: {
projectId: data.projectId,
workspaceType: data.workspaceType,
userId: data.oneupitmeUserId,
userId: data.oneuptimeUserId,
},
select: {
workspaceUserId: true,
@@ -216,9 +298,13 @@ export class Service extends DatabaseService<Model> {
});
if (!userAuth) {
logger.debug("No userAuth found for given data");
return null;
}
logger.debug("Found userAuth:");
logger.debug(userAuth);
return userAuth.workspaceUserId?.toString() || null;
}
@@ -229,6 +315,9 @@ export class Service extends DatabaseService<Model> {
channelNameSiffix: string;
notificationEventType: NotificationRuleEventType;
}): Promise<Array<WorkspaceChannel>> {
logger.debug("createChannelsBasedOnRules called with data:");
logger.debug(data);
const createdWorkspaceChannels: Array<WorkspaceChannel> = [];
const createdChannelNames: Array<string> = [];
@@ -239,17 +328,23 @@ export class Service extends DatabaseService<Model> {
notificationEventType: data.notificationEventType,
});
logger.debug("New channel names to be created:");
logger.debug(newChannelNames);
if (!newChannelNames || newChannelNames.length === 0) {
logger.debug("No new channel names found. Returning empty array.");
return [];
}
for (const newChannelName of newChannelNames) {
// if already created then skip it.
if (createdChannelNames.includes(newChannelName)) {
logger.debug(
`Channel name ${newChannelName} already created. Skipping.`,
);
continue;
}
// create channel.
logger.debug(`Creating new channel with name: ${newChannelName}`);
const channel: WorkspaceChannel =
await WorkspaceUtil.getWorkspaceTypeUtil(
data.workspaceType,
@@ -258,17 +353,25 @@ export class Service extends DatabaseService<Model> {
channelName: newChannelName,
});
createdChannelNames.push(channel.name);
logger.debug("Channel created:");
logger.debug(channel);
createdChannelNames.push(channel.name);
createdWorkspaceChannels.push(channel);
}
logger.debug("Returning created workspace channels:");
logger.debug(createdWorkspaceChannels);
return createdWorkspaceChannels;
}
public async getUsersIdsToInviteToChannel(data: {
notificationRules: Array<CreateChannelNotificationRule>;
}): Promise<Array<ObjectID>> {
logger.debug("getUsersIdsToInviteToChannel called with data:");
logger.debug(data);
const inviteUserIds: Array<ObjectID> = [];
for (const notificationRule of data.notificationRules) {
@@ -282,6 +385,9 @@ export class Service extends DatabaseService<Model> {
const userIds: Array<ObjectID> =
workspaceRules.inviteUsersToNewChannel || [];
logger.debug("User IDs to invite from rule:");
logger.debug(userIds);
for (const userId of userIds) {
if (
!inviteUserIds.find((id: ObjectID) => {
@@ -304,9 +410,15 @@ export class Service extends DatabaseService<Model> {
return new ObjectID(teamId.toString());
});
logger.debug("Team IDs to invite from rule:");
logger.debug(teamIds);
const usersInTeam: Array<User> =
await TeamMemberService.getUsersInTeams(teamIds);
logger.debug("Users in teams:");
logger.debug(usersInTeam);
for (const user of usersInTeam) {
if (
!inviteUserIds.find((id: ObjectID) => {
@@ -323,12 +435,20 @@ export class Service extends DatabaseService<Model> {
}
}
logger.debug("Final list of user IDs to invite:");
logger.debug(inviteUserIds);
return inviteUserIds;
}
public getExistingChannelNamesFromNotificationRules(data: {
notificationRules: Array<BaseNotificationRule>;
}): Array<string> {
logger.debug(
"getExistingChannelNamesFromNotificationRules called with data:",
);
logger.debug(data);
const channelNames: Array<string> = [];
for (const notificationRule of data.notificationRules) {
@@ -338,20 +458,25 @@ export class Service extends DatabaseService<Model> {
const existingChannelNames: Array<string> =
workspaceRules.existingChannelNames.split(",");
logger.debug("Existing channel names from rule:");
logger.debug(existingChannelNames);
for (const channelName of existingChannelNames) {
if (!channelName) {
// if channel name is empty then skip it.
logger.debug("Empty channel name found. Skipping.");
continue;
}
if (!channelNames.includes(channelName)) {
// if channel name is not already added then add it.
channelNames.push(channelName);
}
}
}
}
logger.debug("Final list of existing channel names:");
logger.debug(channelNames);
return channelNames;
}
@@ -360,11 +485,17 @@ export class Service extends DatabaseService<Model> {
notificationRules: Array<CreateChannelNotificationRule>;
channelNameSiffix: string;
}): Array<string> {
logger.debug("getNewChannelNamesFromNotificationRules called with data:");
logger.debug(data);
const channelNames: Array<string> = [];
for (const notificationRule of data.notificationRules) {
const workspaceRules: CreateChannelNotificationRule = notificationRule;
logger.debug("Processing notification rule:");
logger.debug(workspaceRules);
if (
workspaceRules.shouldCreateNewChannel &&
workspaceRules.newChannelTemplateName
@@ -373,16 +504,30 @@ export class Service extends DatabaseService<Model> {
workspaceRules.newChannelTemplateName ||
`oneuptime-${data.notificationEventType.toLowerCase()}-`;
logger.debug("New channel template name:");
logger.debug(newChannelName);
// add suffix and then check if it is already added or not.
const channelName: string = newChannelName + data.channelNameSiffix;
logger.debug("Final channel name with suffix:");
logger.debug(channelName);
if (!channelNames.includes(channelName)) {
// if channel name is not already added then add it.
channelNames.push(channelName);
logger.debug(`Channel name ${channelName} added to the list.`);
} else {
logger.debug(
`Channel name ${channelName} already exists in the list. Skipping.`,
);
}
}
}
logger.debug("Final list of new channel names:");
logger.debug(channelNames);
return channelNames;
}
@@ -391,7 +536,10 @@ export class Service extends DatabaseService<Model> {
workspaceType: WorkspaceType;
notificationRuleEventType: NotificationRuleEventType;
}): Promise<Array<Model>> {
return await this.findBy({
logger.debug("getNotificationRules called with data:");
logger.debug(data);
const notificationRules: Array<Model> = await this.findBy({
query: {
projectId: data.projectId,
workspaceType: data.workspaceType,
@@ -406,6 +554,11 @@ export class Service extends DatabaseService<Model> {
skip: 0,
limit: LIMIT_PER_PROJECT,
});
logger.debug("Notification rules retrieved:");
logger.debug(notificationRules);
return notificationRules;
}
private async getValuesBasedOnNotificationFor(data: {
@@ -416,7 +569,13 @@ export class Service extends DatabaseService<Model> {
| Array<string>
| undefined;
}> {
logger.debug("getValuesBasedOnNotificationFor called with data:");
logger.debug(data);
if (data.notificationFor.incidentId) {
logger.debug("Fetching incident details for incident ID:");
logger.debug(data.notificationFor.incidentId);
const incident: Incident | null = await IncidentService.findOneById({
id: data.notificationFor.incidentId,
select: {
@@ -433,9 +592,14 @@ export class Service extends DatabaseService<Model> {
});
if (!incident) {
logger.debug("Incident not found for ID:");
logger.debug(data.notificationFor.incidentId);
throw new BadDataException("Incident ID not found");
}
logger.debug("Incident details retrieved:");
logger.debug(incident);
const monitorLabels: Array<Label> =
await MonitorService.getLabelsForMonitors({
monitorIds:
@@ -444,6 +608,9 @@ export class Service extends DatabaseService<Model> {
}) || [],
});
logger.debug("Monitor labels retrieved:");
logger.debug(monitorLabels);
return {
[NotificationRuleConditionCheckOn.MonitorName]: undefined,
[NotificationRuleConditionCheckOn.IncidentTitle]: incident.title || "",
@@ -482,6 +649,9 @@ export class Service extends DatabaseService<Model> {
}
if (data.notificationFor.alertId) {
logger.debug("Fetching alert details for alert ID:");
logger.debug(data.notificationFor.alertId);
const alert: Alert | null = await AlertService.findOneById({
id: data.notificationFor.alertId,
select: {
@@ -498,14 +668,22 @@ export class Service extends DatabaseService<Model> {
});
if (!alert) {
logger.debug("Alert not found for ID:");
logger.debug(data.notificationFor.alertId);
throw new BadDataException("Alert ID not found");
}
logger.debug("Alert details retrieved:");
logger.debug(alert);
const monitorLabels: Array<Label> =
await MonitorService.getLabelsForMonitors({
monitorIds: alert?.monitor?.id ? [alert?.monitor?.id] : [],
});
logger.debug("Monitor labels retrieved:");
logger.debug(monitorLabels);
return {
[NotificationRuleConditionCheckOn.MonitorName]: undefined,
[NotificationRuleConditionCheckOn.IncidentTitle]: undefined,
@@ -543,6 +721,9 @@ export class Service extends DatabaseService<Model> {
}
if (data.notificationFor.scheduledMaintenanceId) {
logger.debug("Fetching scheduled maintenance details for ID:");
logger.debug(data.notificationFor.scheduledMaintenanceId);
const scheduledMaintenance: ScheduledMaintenance | null =
await ScheduledMaintenanceService.findOneById({
id: data.notificationFor.scheduledMaintenanceId,
@@ -559,9 +740,14 @@ export class Service extends DatabaseService<Model> {
});
if (!scheduledMaintenance) {
logger.debug("Scheduled maintenance not found for ID:");
logger.debug(data.notificationFor.scheduledMaintenanceId);
throw new BadDataException("Scheduled Maintenance ID not found");
}
logger.debug("Scheduled maintenance details retrieved:");
logger.debug(scheduledMaintenance);
const monitorLabels: Array<Label> =
await MonitorService.getLabelsForMonitors({
monitorIds:
@@ -572,6 +758,9 @@ export class Service extends DatabaseService<Model> {
) || [],
});
logger.debug("Monitor labels retrieved:");
logger.debug(monitorLabels);
return {
[NotificationRuleConditionCheckOn.MonitorName]: undefined,
[NotificationRuleConditionCheckOn.IncidentTitle]: undefined,
@@ -611,6 +800,9 @@ export class Service extends DatabaseService<Model> {
}
if (data.notificationFor.monitorStatusTimelineId) {
logger.debug("Fetching monitor status timeline details for ID:");
logger.debug(data.notificationFor.monitorStatusTimelineId);
const monitorStatusTimeline: MonitorStatusTimeline | null =
await MonitorStatusTimelineService.findOneById({
id: data.notificationFor.monitorStatusTimelineId,
@@ -628,6 +820,8 @@ export class Service extends DatabaseService<Model> {
});
if (!monitorStatusTimeline) {
logger.debug("Monitor status timeline not found for ID:");
logger.debug(data.notificationFor.monitorStatusTimelineId);
throw new BadDataException("Monitor Status Timeline ID not found");
}

View File

@@ -24,6 +24,7 @@ export class Service extends DatabaseService<Model> {
authToken: true,
workspaceProjectId: true,
miscData: true,
workspaceType: true,
},
props: {
isRoot: true,

View File

@@ -222,6 +222,7 @@ export default class OnTriggerBaseModel<
returnValues: {
data: requestData,
},
isManualExecution: false,
};
promises.push(props.executeWorkflow(executeWorkflow));

View File

@@ -53,6 +53,7 @@ export default class WebhookTrigger extends TriggerCode {
const executeWorkflow: ExecuteWorkflowType = {
workflowId: new ObjectID(workflow._id!),
returnValues: {},
isManualExecution: false,
};
if (
@@ -122,6 +123,7 @@ export default class WebhookTrigger extends TriggerCode {
const executeWorkflow: ExecuteWorkflowType = {
workflowId: new ObjectID(workflow._id!),
returnValues: {},
isManualExecution: false,
};
if (

View File

@@ -1,4 +1,8 @@
import { ExpressRequest, ExpressResponse } from "../../../Utils/Express";
import {
ExpressRequest,
ExpressResponse,
NextFunction,
} from "../../../Utils/Express";
import Response from "../../../Utils/Response";
import { RunOptions, RunReturnType } from "../ComponentCode";
import TriggerCode, { ExecuteWorkflowType, InitProps } from "../TriggerCode";
@@ -48,15 +52,23 @@ export default class WebhookTrigger extends TriggerCode {
public override async init(props: InitProps): Promise<void> {
props.router.get(
`/trigger/:workflowId`,
async (req: ExpressRequest, res: ExpressResponse) => {
await this.initTrigger(req, res, props);
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
await this.initTrigger(req, res, props);
} catch (e) {
next(e);
}
},
);
props.router.post(
`/trigger/:workflowId`,
async (req: ExpressRequest, res: ExpressResponse) => {
await this.initTrigger(req, res, props);
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
await this.initTrigger(req, res, props);
} catch (e) {
next(e);
}
},
);
}
@@ -77,6 +89,7 @@ export default class WebhookTrigger extends TriggerCode {
"request-params": req.query,
"request-body": req.body,
},
isManualExecution: false,
};
await props.executeWorkflow(executeWorkflow);

View File

@@ -10,6 +10,8 @@ import { Port } from "Common/Types/Workflow/Component";
export interface ExecuteWorkflowType {
workflowId: ObjectID;
returnValues: JSONObject;
// is this workflow triggered manually or not
isManualExecution: boolean;
}
export interface InitProps {

View File

@@ -6,4 +6,5 @@ export interface RunProps {
workflowId: ObjectID;
workflowLogId: ObjectID | null;
timeout: number;
isManualExecution: boolean;
}

View File

@@ -15,125 +15,269 @@ import WorkspaceBase, { WorkspaceChannel } from "../WorkspaceBase";
import WorkspaceType from "../../../../Types/Workspace/WorkspaceType";
export default class SlackUtil extends WorkspaceBase {
public static override async inviteUserToChannel(data: {
public static override async joinChannel(data: {
authToken: string;
channelName: string;
channelId: string;
}): Promise<void> {
logger.debug("Joining channel with data:");
logger.debug(data);
// Join channel
const response = await API.post(
URL.fromString("https://slack.com/api/conversations.join"),
{
channel: data.channelId,
},
{
Authorization: `Bearer ${data.authToken}`,
["Content-Type"]: "application/x-www-form-urlencoded",
},
);
logger.debug("Response from Slack API for joining channel:");
logger.debug(response);
if (response instanceof HTTPErrorResponse) {
logger.error("Error response from Slack API:");
logger.error(response);
throw response;
}
if ((response.jsonData as JSONObject)?.["ok"] !== true) {
logger.error("Invalid response from Slack API:");
logger.error(response.jsonData);
throw new BadRequestException("Invalid response");
}
logger.debug("Channel joined successfully with data:");
logger.debug(data);
}
public static override async inviteUserToChannelByChannelId(data: {
authToken: string;
channelId: string;
workspaceUserId: string;
}): Promise<void> {
const channelId: string = (
await this.getWorkspaceChannelFromChannelId({
authToken: data.authToken,
channelId: data.channelName,
})
).id;
logger.debug("Inviting user to channel with data:");
logger.debug(data);
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post(
URL.fromString("https://slack.com/api/conversations.invite"),
{
channel: channelId,
channel: data.channelId,
users: data.workspaceUserId,
},
{
Authorization: `Bearer ${data.authToken}`,
["Content-Type"]: "application/x-www-form-urlencoded",
},
);
logger.debug("Response from Slack API for inviting user:");
logger.debug(response);
if (response instanceof HTTPErrorResponse) {
logger.error("Error response from Slack API:");
logger.error(response);
throw response;
}
if ((response.jsonData as JSONObject)?.["ok"] !== true) {
logger.error("Invalid response from Slack API:");
logger.error(response.jsonData);
throw new BadRequestException("Invalid response");
}
logger.debug("User invited to channel successfully.");
}
public static override async inviteUserToChannelByChannelName(data: {
authToken: string;
channelName: string;
workspaceUserId: string;
}): Promise<void> {
if (data.channelName && data.channelName.startsWith("#")) {
// trim # from channel name
data.channelName = data.channelName.substring(1);
}
logger.debug("Inviting user to channel with data:");
logger.debug(data);
const channelId: string = (
await this.getWorkspaceChannelFromChannelName({
authToken: data.authToken,
channelName: data.channelName,
})
).id;
return this.inviteUserToChannelByChannelId({
authToken: data.authToken,
channelId: channelId,
workspaceUserId: data.workspaceUserId,
});
}
public static override async createChannelsIfDoesNotExist(data: {
authToken: string;
channelNames: Array<string>;
}): Promise<Array<WorkspaceChannel>> {
// check existing channels and only create if they dont exist.
logger.debug("Creating channels if they do not exist with data:");
logger.debug(data);
const workspaceChannels: Array<WorkspaceChannel> = [];
const existingWorkspaceChannels: Dictionary<WorkspaceChannel> =
await this.getAllWorkspaceChannels({
authToken: data.authToken,
});
for (const channelName of data.channelNames) {
logger.debug("Existing workspace channels:");
logger.debug(existingWorkspaceChannels);
for (let channelName of data.channelNames) {
// if channel name starts with #, remove it
if (channelName && channelName.startsWith("#")) {
channelName = channelName.substring(1);
}
if (existingWorkspaceChannels[channelName]) {
logger.debug(`Channel ${channelName} already exists.`);
workspaceChannels.push(existingWorkspaceChannels[channelName]!);
continue;
}
logger.debug(`Channel ${channelName} does not exist. Creating channel.`);
const channel: WorkspaceChannel = await this.createChannel({
authToken: data.authToken,
channelName: channelName,
});
if (channel) {
logger.debug(`Channel ${channelName} created successfully.`);
workspaceChannels.push(channel);
}
}
logger.debug("Channels created or found:");
logger.debug(workspaceChannels);
return workspaceChannels;
}
public static override async getWorkspaceChannelFromChannelName(data: {
authToken: string;
channelName: string;
}): Promise<WorkspaceChannel> {
logger.debug("Getting workspace channel ID from channel name with data:");
logger.debug(data);
const channels: Dictionary<WorkspaceChannel> =
await this.getAllWorkspaceChannels({
authToken: data.authToken,
});
logger.debug("All workspace channels:");
logger.debug(channels);
if (!channels[data.channelName]) {
logger.error("Channel not found.");
throw new Error("Channel not found.");
}
logger.debug("Workspace channel ID obtained:");
logger.debug(channels[data.channelName]!.id);
return channels[data.channelName]!;
}
public static override async getWorkspaceChannelFromChannelId(data: {
authToken: string;
channelId: string;
}): Promise<WorkspaceChannel> {
logger.debug("Getting workspace channel from channel ID with data:");
logger.debug(data);
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.get<JSONObject>(
await API.post<JSONObject>(
URL.fromString("https://slack.com/api/conversations.info"),
{
headers: {
Authorization: `Bearer ${data.authToken}`,
},
params: {
channel: data.channelId,
},
channel: data.channelId,
},
{
Authorization: `Bearer ${data.authToken}`,
["Content-Type"]: "application/x-www-form-urlencoded",
},
);
logger.debug("Response from Slack API for getting channel info:");
logger.debug(response);
if (response instanceof HTTPErrorResponse) {
logger.error("Error response from Slack API:");
logger.error(response);
throw response;
}
// check for ok response
if ((response.jsonData as JSONObject)?.["ok"] !== true) {
logger.error("Invalid response from Slack API:");
logger.error(response.jsonData);
throw new BadRequestException("Invalid response");
}
if (
!((response.jsonData as JSONObject)?.["channel"] as JSONObject)?.["name"]
) {
logger.error("Invalid response from Slack API:");
logger.error(response.jsonData);
throw new Error("Invalid response");
}
return {
const channel: WorkspaceChannel = {
name: ((response.jsonData as JSONObject)["channel"] as JSONObject)[
"name"
] as string,
id: data.channelId,
workspaceType: WorkspaceType.Slack,
};
logger.debug("Workspace channel obtained:");
logger.debug(channel);
return channel;
}
public static override async getAllWorkspaceChannels(data: {
authToken: string;
}): Promise<Dictionary<WorkspaceChannel>> {
logger.debug("Getting all workspace channels with data:");
logger.debug(data);
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.get<JSONObject>(
await API.post<JSONObject>(
URL.fromString("https://slack.com/api/conversations.list"),
{},
{
headers: {
Authorization: `Bearer ${data.authToken}`,
},
Authorization: `Bearer ${data.authToken}`,
["Content-Type"]: "application/x-www-form-urlencoded",
},
);
logger.debug("Response from Slack API for getting all channels:");
logger.debug(response);
if (response instanceof HTTPErrorResponse) {
logger.error("Error response from Slack API:");
logger.error(response);
throw response;
}
// check for ok response
if ((response.jsonData as JSONObject)?.["ok"] !== true) {
logger.error("Invalid response from Slack API:");
logger.error(response.jsonData);
throw new BadRequestException("Invalid response");
}
const channels: Dictionary<WorkspaceChannel> = {};
for (const channel of (response.jsonData as JSONObject)[
@@ -150,29 +294,42 @@ export default class SlackUtil extends WorkspaceBase {
};
}
logger.debug("All workspace channels obtained:");
logger.debug(channels);
return channels;
}
public static override async sendMessage(data: {
workspaceMessagePayload: WorkspaceMessagePayload;
authToken: string; // which auth token should we use to send.
userId: string;
}): Promise<void> {
logger.debug("Notify Slack");
logger.debug("Sending message to Slack with data:");
logger.debug(data);
const blocks: Array<JSONObject> = this.getBlocksFromWorkspaceMessagePayload(
data.workspaceMessagePayload,
);
logger.debug("Blocks generated from workspace message payload:");
logger.debug(blocks);
const existingWorkspaceChannels: Dictionary<WorkspaceChannel> =
await this.getAllWorkspaceChannels({
authToken: data.authToken,
});
logger.debug("Existing workspace channels:");
logger.debug(existingWorkspaceChannels);
const channelIdsToPostTo: Array<string> = [];
for (const channelName of data.workspaceMessagePayload.channelNames) {
// get channel ids from existingWorkspaceChannels. IF channel doesn't exist, create it if createChannelsIfItDoesNotExist is true.
for (let channelName of data.workspaceMessagePayload.channelNames) {
if (channelName && channelName.startsWith("#")) {
// trim # from channel name
channelName = channelName.substring(1);
}
let channel: WorkspaceChannel | null = null;
if (existingWorkspaceChannels[channelName]) {
@@ -186,15 +343,35 @@ export default class SlackUtil extends WorkspaceBase {
}
}
logger.debug("Channel IDs to post to:");
logger.debug(channelIdsToPostTo);
for (const channelId of channelIdsToPostTo) {
try {
// try catch here to prevent failure of one channel to prevent posting to other channels.
// check if the user is in the channel.
const isUserInChannel = await this.isUserInChannel({
authToken: data.authToken,
channelId: channelId,
userId: data.userId,
});
if (!isUserInChannel) {
// add user to the channel
await this.joinChannel({
authToken: data.authToken,
channelId: channelId,
});
}
await this.sendPayloadBlocksToChannel({
authToken: data.authToken,
channelId: channelId,
blocks: blocks,
});
logger.debug(`Message sent to channel ID ${channelId} successfully.`);
} catch (e) {
logger.error(`Error sending message to channel ID ${channelId}:`);
logger.error(e);
}
}
@@ -205,6 +382,9 @@ export default class SlackUtil extends WorkspaceBase {
channelId: string;
blocks: Array<JSONObject>;
}): Promise<void> {
logger.debug("Sending payload blocks to channel with data:");
logger.debug(JSON.stringify(data, null, 2));
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post(
URL.fromString("https://slack.com/api/chat.postMessage"),
@@ -214,22 +394,35 @@ export default class SlackUtil extends WorkspaceBase {
},
{
Authorization: `Bearer ${data.authToken}`,
["Content-Type"]: "application/json",
},
);
logger.debug("Response from Slack API for sending message:");
logger.debug(response);
if (response instanceof HTTPErrorResponse) {
logger.error("Error response from Slack API:");
logger.error(response);
throw response;
}
if ((response.jsonData as JSONObject)?.["ok"] !== true) {
logger.error("Invalid response from Slack API:");
logger.error(response.jsonData);
throw new BadRequestException("Invalid response");
}
logger.debug("Payload blocks sent to channel successfully.");
}
public static override async createChannel(data: {
authToken: string;
channelName: string;
}): Promise<WorkspaceChannel> {
logger.debug("Creating channel with data:");
logger.debug(data);
const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
await API.post(
URL.fromString("https://slack.com/api/conversations.create"),
@@ -238,21 +431,36 @@ export default class SlackUtil extends WorkspaceBase {
},
{
Authorization: `Bearer ${data.authToken}`,
["Content-Type"]: "application/x-www-form-urlencoded",
},
);
logger.debug("Response from Slack API for creating channel:");
logger.debug(response);
if (response instanceof HTTPErrorResponse) {
logger.error("Error response from Slack API:");
logger.error(response);
throw response;
}
// check for ok response
if ((response.jsonData as JSONObject)?.["ok"] !== true) {
logger.error("Invalid response from Slack API:");
logger.error(response.jsonData);
throw new BadRequestException("Invalid response");
}
if (
!((response.jsonData as JSONObject)?.["channel"] as JSONObject)?.["id"] ||
!((response.jsonData as JSONObject)?.["channel"] as JSONObject)?.["name"]
) {
logger.error("Invalid response from Slack API:");
logger.error(response.jsonData);
throw new Error("Invalid response");
}
return {
const channel: WorkspaceChannel = {
id: ((response.jsonData as JSONObject)["channel"] as JSONObject)[
"id"
] as string,
@@ -261,36 +469,127 @@ export default class SlackUtil extends WorkspaceBase {
] as string,
workspaceType: WorkspaceType.Slack,
};
logger.debug("Channel created successfully:");
logger.debug(channel);
return channel;
}
public static override getHeaderBlock(data: {
payloadHeaderBlock: WorkspacePayloadHeader;
}): JSONObject {
return {
logger.debug("Getting header block with data:");
logger.debug(data);
const headerBlock: JSONObject = {
type: "header",
text: {
type: "plain_text",
text: data.payloadHeaderBlock.text,
},
};
logger.debug("Header block generated:");
logger.debug(headerBlock);
return headerBlock;
}
public static override getMarkdownBlock(data: {
payloadMarkdownBlock: WorkspacePayloadMarkdown;
}): JSONObject {
return {
logger.debug("Getting markdown block with data:");
logger.debug(data);
const markdownBlock: JSONObject = {
type: "section",
text: {
type: "mrkdwn",
text: data.payloadMarkdownBlock.text,
},
};
logger.debug("Markdown block generated:");
logger.debug(markdownBlock);
return markdownBlock;
}
public static override async isUserInChannel(data: {
authToken: string;
channelId: string;
userId: string;
}): Promise<boolean> {
const members: Array<string> = [];
logger.debug("Checking if user is in channel with data:");
logger.debug(data);
let cursor: string | undefined = undefined;
do {
// check if the user is in the channel, return true if they are, false if they are not
const requestBody: JSONObject = {
channel: data.channelId,
limit: 1000,
};
if (cursor) {
requestBody["cursor"] = cursor;
}
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post<JSONObject>(
URL.fromString("https://slack.com/api/conversations.members"),
requestBody,
{
Authorization: `Bearer ${data.authToken}`,
["Content-Type"]: "application/x-www-form-urlencoded",
},
);
logger.debug("Response from Slack API for getting channel members:");
logger.debug(response);
if (response instanceof HTTPErrorResponse) {
logger.error("Error response from Slack API:");
logger.error(response);
throw response;
}
// check for ok response
if ((response.jsonData as JSONObject)?.["ok"] !== true) {
logger.error("Invalid response from Slack API:");
logger.error(response.jsonData);
throw new BadRequestException("Invalid response");
}
// check if the user is in the channel
const membersOnThisPage: Array<string> = (
response.jsonData as JSONObject
)["members"] as Array<string>;
members.push(...membersOnThisPage);
cursor = (
(response.jsonData as JSONObject)["response_metadata"] as JSONObject
)?.["next_cursor"] as string;
} while (cursor);
if (members.includes(data.userId)) {
return true;
}
return false;
}
public static override getButtonBlock(data: {
payloadButtonBlock: WorkspaceMessagePayloadButton;
}): JSONObject {
return {
logger.debug("Getting button block with data:");
logger.debug(data);
const buttonBlock: JSONObject = {
type: "button",
text: {
type: "plain_text",
@@ -299,27 +598,34 @@ export default class SlackUtil extends WorkspaceBase {
value: data.payloadButtonBlock.title,
action_id: data.payloadButtonBlock.title,
};
logger.debug("Button block generated:");
logger.debug(buttonBlock);
return buttonBlock;
}
public static override async sendMessageToChannelViaIncomingWebhook(data: {
url: URL;
text: string;
}): Promise<HTTPResponse<JSONObject> | HTTPErrorResponse> {
let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = null;
logger.debug("Sending message to channel via incoming webhook with data:");
logger.debug(data);
// https://api.slack.com/messaging/webhooks#advanced_message_formatting
apiResult = await API.post(data.url, {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `${data.text}`,
const apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null =
await API.post(data.url, {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `${data.text}`,
},
},
},
],
});
],
});
logger.debug("Response from Slack API for sending message via webhook:");
logger.debug(apiResult);
return apiResult;
}
}

View File

@@ -45,12 +45,17 @@
],
"scopes": {
"user": [
"identity.email",
"identity.basic",
"email"
"users:read"
],
"bot": [
"commands"
"commands",
"channels:history",
"channels:join",
"channels:manage",
"channels:read",
"channels:write.invites",
"channels:write.topic",
"chat:write"
]
}
},

View File

@@ -21,6 +21,13 @@ export interface WorkspaceChannel {
}
export default class WorkspaceBase {
public static async joinChannel(_data: {
authToken: string;
channelId: string;
}): Promise<void> {
throw new NotImplementedException();
}
public static async sendPayloadBlocksToChannel(_data: {
authToken: string;
channelId: string;
@@ -50,7 +57,7 @@ export default class WorkspaceBase {
workspaceUserIds: Array<string>;
}): Promise<void> {
for (const userId of data.workspaceUserIds) {
await this.inviteUserToChannel({
await this.inviteUserToChannelByChannelName({
authToken: data.authToken,
channelName: data.channelName,
workspaceUserId: userId,
@@ -58,7 +65,7 @@ export default class WorkspaceBase {
}
}
public static async inviteUserToChannel(_data: {
public static async inviteUserToChannelByChannelName(_data: {
authToken: string;
channelName: string;
workspaceUserId: string;
@@ -66,6 +73,12 @@ export default class WorkspaceBase {
throw new NotImplementedException();
}
public static async inviteUserToChannelByChannelId(_data: {
authToken: string;
channelId: string;
workspaceUserId: string;
}): Promise<void> {}
public static async createChannelsIfDoesNotExist(_data: {
authToken: string;
channelNames: Array<string>;
@@ -83,6 +96,7 @@ export default class WorkspaceBase {
public static async sendMessage(_data: {
workspaceMessagePayload: WorkspaceMessagePayload;
authToken: string; // which auth token should we use to send.
userId: string;
}): Promise<void> {
throw new NotImplementedException();
}
@@ -93,6 +107,13 @@ export default class WorkspaceBase {
throw new NotImplementedException();
}
public static async getWorkspaceChannelFromChannelName(_data: {
authToken: string;
channelName: string;
}): Promise<WorkspaceChannel> {
throw new NotImplementedException();
}
public static async createChannel(_data: {
authToken: string;
channelName: string;
@@ -166,4 +187,12 @@ export default class WorkspaceBase {
}): Promise<HTTPResponse<JSONObject> | HTTPErrorResponse> {
throw new NotImplementedException();
}
public static async isUserInChannel(_data: {
authToken: string;
channelId: string;
userId: string;
}): Promise<boolean> {
throw new NotImplementedException();
}
}

View File

@@ -819,26 +819,36 @@ export default class OneUptimeDate {
return moment(date).isBefore(endDate);
}
public static getCurrentDateAsFormattedString(): string {
return this.getDateAsFormattedString(new Date());
public static getCurrentDateAsFormattedString(options?: {
onlyShowDate?: boolean;
showSeconds?: boolean;
}): string {
return this.getDateAsFormattedString(new Date(), options);
}
public static getDateAsFormattedString(
date: string | Date,
onlyShowDate?: boolean,
options?: {
onlyShowDate?: boolean;
showSeconds?: boolean;
},
): string {
date = this.fromString(date);
let formatstring: string = "MMM DD YYYY, HH:mm";
if (onlyShowDate) {
if (options?.showSeconds) {
formatstring = "MMM DD YYYY, HH:mm:ss";
}
if (options?.onlyShowDate) {
formatstring = "MMM DD, YYYY";
}
return (
moment(date).format(formatstring) +
" " +
(onlyShowDate ? "" : this.getCurrentTimezoneString())
(options?.onlyShowDate ? "" : this.getCurrentTimezoneString())
);
}

View File

@@ -504,6 +504,7 @@ enum Permission {
ReadProjectOnCallDutyPolicyExecutionLogTimeline = "ReadProjectOnCallDutyPolicyExecutionLogTimeline",
ReadProjectOnCallDutyPolicyExecutionLog = "ReadProjectOnCallDutyPolicyExecutionLog",
CreateProjectOnCallDutyPolicyExecutionLog = "CreateProjectOnCallDutyPolicyExecutionLog",
// Resource Permissions (Team Permission)
CreateProjectOnCallDutyPolicyEscalationRule = "CreateProjectOnCallDutyPolicyEscalationRule",
@@ -2200,8 +2201,14 @@ export class PermissionHelper {
{
permission: Permission.ReadProjectOnCallDutyPolicyExecutionLog,
title: "Read On-Call Duty Policy Execution Log",
description:
"This permission can read teams in on-call duty execution log.",
description: "This permission can read on-call duty execution log.",
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CreateProjectOnCallDutyPolicyExecutionLog,
title: "Create On-Call Duty Policy Execution Log",
description: "This permission can create on-call duty execution log.",
isAssignableToTenant: true,
isAccessControlPermission: false,
},

View File

@@ -90,6 +90,8 @@ export default interface ComponentMetadata {
outPorts: Array<Port>;
tableName?: string | undefined;
documentationLink?: Route;
// this is used in trigger component to show the manual execution button
runWorkflowManuallyArguments?: Array<Argument> | undefined;
}
export interface ComponentCategory {

View File

@@ -16,7 +16,37 @@ const components: Array<ComponentMetadata> = [
iconProp: IconProp.AltGlobe,
componentType: ComponentType.Trigger,
documentationLink: Route.fromString("/workflow/docs/Webhook.md"),
arguments: [],
arguments: [
],
runWorkflowManuallyArguments: [
{
id: "request-headers",
name: "Request Headers",
description: "Request Headers for this request",
type: ComponentInputType.StringDictionary,
required: false,
placeholder: '{"header1": "value1", "header2": "value2", ....}',
},
{
id: "request-params",
name: "Request Query Params",
description: "Request Query Params for this request",
type: ComponentInputType.StringDictionary,
required: false,
placeholder: '{"query1": "value1", "query2": "value2", ....}',
},
{
id: "request-body",
name: "Request Body",
description: "Request Body",
type: ComponentInputType.JSON,
required: false,
placeholder: '{"key1": "value1", "key2": "value2", ....}',
},
],
returnValues: [
{
id: "request-headers",

View File

@@ -9,6 +9,29 @@ import StatusPageGroup from "../../Models/DatabaseModels/StatusPageGroup";
import UptimeUtil from "../Uptime/UptimeUtil";
export default class StatusPageResourceUptimeUtil {
public static getWorstMonitorStatus(data: {
monitorStatuses: Array<MonitorStatus>;
}): MonitorStatus {
let worstStatus: MonitorStatus = new MonitorStatus();
worstStatus.name = "Operational";
worstStatus.color = Green;
for (const status of data.monitorStatuses) {
if (
(worstStatus &&
worstStatus.priority &&
status.priority &&
status.priority > worstStatus.priority) ||
!worstStatus ||
!worstStatus.priority
) {
worstStatus = status;
}
}
return worstStatus;
}
public static getMonitorStatusTimelineForResource(data: {
statusPageResource: StatusPageResource;
monitorStatusTimelines: Array<MonitorStatusTimeline>;

View File

@@ -355,4 +355,30 @@ export default class UptimeUtil {
precision,
});
}
public static calculateAvgUptimePercentage(data: {
uptimePercentages: Array<number>;
precision: UptimePrecision;
}): number {
// calculate percentage.
const { uptimePercentages, precision } = data;
if (uptimePercentages.length === 0) {
return 100;
}
let totalUptimePercentage: number = 0;
for (const uptimePercentage of uptimePercentages) {
totalUptimePercentage += uptimePercentage;
}
const percentage: number = totalUptimePercentage / uptimePercentages.length;
return this.roundToPrecision({
number: percentage,
precision,
});
}
}

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.7.3-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -23,6 +23,9 @@ import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchem
import { FormType } from "Common/UI/Components/Forms/ModelForm";
import AlertInternalNote from "Common/Models/DatabaseModels/AlertInternalNote";
import { ModalWidth } from "Common/UI/Components/Modal/Modal";
import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType";
import OnCallDutyPolicyExecutionLog from "Common/Models/DatabaseModels/OnCallDutyPolicyExecutionLog";
import OnCallDutyPolicy from "Common/Models/DatabaseModels/OnCallDutyPolicy";
export interface ComponentProps {
alertId: ObjectID;
@@ -34,6 +37,8 @@ const AlertFeedElement: FunctionComponent<ComponentProps> = (
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | undefined>(undefined);
const [feedItems, setFeedItems] = React.useState<FeedItemProps[]>([]);
const [showOnCallPolicyModal, setShowOnCallPolicyModal] =
React.useState<boolean>(false);
const [showPrivateNoteModal, setShowPrivateNoteModal] =
React.useState<boolean>(false);
@@ -189,6 +194,14 @@ const AlertFeedElement: FunctionComponent<ComponentProps> = (
"This is the timeline and feed for this alert. You can see all the updates and information about this alert here."
}
buttons={[
{
title: "Execute On-Call Policy",
buttonStyle: ButtonStyleType.NORMAL,
icon: IconProp.Call,
onClick: () => {
setShowOnCallPolicyModal(true);
},
},
{
title: "Add Private Note",
buttonStyle: ButtonStyleType.NORMAL,
@@ -217,6 +230,58 @@ const AlertFeedElement: FunctionComponent<ComponentProps> = (
/>
)}
{showOnCallPolicyModal && (
<ModelFormModal
modelType={OnCallDutyPolicyExecutionLog}
modalWidth={ModalWidth.Normal}
name={"execute-on-call-policy"}
title={"Execute On-Call Policy"}
description={
"Execute the on-call policy for this alert. This will notify the on-call team members and start the on-call process."
}
onClose={() => {
setShowOnCallPolicyModal(false);
}}
submitButtonText="Execute Policy"
onBeforeCreate={async (model: OnCallDutyPolicyExecutionLog) => {
model.triggeredByAlertId = props.alertId!;
model.userNotificationEventType =
UserNotificationEventType.AlertCreated;
return model;
}}
onSuccess={() => {
setShowOnCallPolicyModal(false);
fetchItems().catch((err: unknown) => {
setError(API.getFriendlyMessage(err as Exception));
});
}}
formProps={{
name: "create-on-call-policy-log",
modelType: OnCallDutyPolicyExecutionLog,
id: "create-on-call-policy-log",
fields: [
{
field: {
onCallDutyPolicy: true,
},
title: "Select On-Call Policy",
description:
"Select the on-call policy to execute for this alert.",
fieldType: FormFieldSchemaType.Dropdown,
dropdownModal: {
type: OnCallDutyPolicy,
labelField: "name",
valueField: "_id",
},
required: true,
placeholder: "Select On-Call Policy",
},
],
formType: FormType.Create,
}}
/>
)}
{showPrivateNoteModal && (
<ModelFormModal
modalWidth={ModalWidth.Large}

View File

@@ -25,6 +25,9 @@ import { FormType } from "Common/UI/Components/Forms/ModelForm";
import OneUptimeDate from "Common/Types/Date";
import IncidentInternalNote from "Common/Models/DatabaseModels/IncidentInternalNote";
import { ModalWidth } from "Common/UI/Components/Modal/Modal";
import OnCallDutyPolicyExecutionLog from "Common/Models/DatabaseModels/OnCallDutyPolicyExecutionLog";
import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType";
import OnCallDutyPolicy from "Common/Models/DatabaseModels/OnCallDutyPolicy";
export interface ComponentProps {
incidentId: ObjectID;
@@ -36,6 +39,8 @@ const IncidentFeedElement: FunctionComponent<ComponentProps> = (
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | undefined>(undefined);
const [feedItems, setFeedItems] = React.useState<FeedItemProps[]>([]);
const [showOnCallPolicyModal, setShowOnCallPolicyModal] =
React.useState<boolean>(false);
const [showPublicNoteModal, setShowPublicNoteModal] =
React.useState<boolean>(false);
@@ -230,6 +235,14 @@ const IncidentFeedElement: FunctionComponent<ComponentProps> = (
"This is the timeline and feed for this incident. You can see all the updates and information about this incident here."
}
buttons={[
{
title: "Execute On-Call Policy",
buttonStyle: ButtonStyleType.NORMAL,
icon: IconProp.Call,
onClick: () => {
setShowOnCallPolicyModal(true);
},
},
{
title: "Add Public Note",
buttonStyle: ButtonStyleType.NORMAL,
@@ -265,6 +278,59 @@ const IncidentFeedElement: FunctionComponent<ComponentProps> = (
noItemsMessage="Looks like there are no items in this feed for this incident."
/>
)}
{showOnCallPolicyModal && (
<ModelFormModal
modelType={OnCallDutyPolicyExecutionLog}
modalWidth={ModalWidth.Normal}
name={"execute-on-call-policy"}
title={"Execute On-Call Policy"}
description={
"Execute the on-call policy for this incident. This will notify the on-call team members and start the on-call process."
}
onClose={() => {
setShowOnCallPolicyModal(false);
}}
submitButtonText="Execute Policy"
onBeforeCreate={async (model: OnCallDutyPolicyExecutionLog) => {
model.triggeredByIncidentId = props.incidentId!;
model.userNotificationEventType =
UserNotificationEventType.IncidentCreated;
return model;
}}
onSuccess={() => {
setShowOnCallPolicyModal(false);
fetchItems().catch((err: unknown) => {
setError(API.getFriendlyMessage(err as Exception));
});
}}
formProps={{
name: "create-on-call-policy-log",
modelType: OnCallDutyPolicyExecutionLog,
id: "create-on-call-policy-log",
fields: [
{
field: {
onCallDutyPolicy: true,
},
title: "Select On-Call Policy",
description:
"Select the on-call policy to execute for this incident.",
fieldType: FormFieldSchemaType.Dropdown,
dropdownModal: {
type: OnCallDutyPolicy,
labelField: "name",
valueField: "_id",
},
required: true,
placeholder: "Select On-Call Policy",
},
],
formType: FormType.Create,
}}
/>
)}
{showPublicNoteModal && (
<ModelFormModal
modelType={IncidentPublicNote}

View File

@@ -47,7 +47,9 @@ export interface ComponentProps {
const MonitorsTable: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
let cardbuttons: Array<CardButtonSchema> = [];
let cardbuttons: Array<CardButtonSchema> = props.cardButtons
? [...props.cardButtons]
: [];
if (!props.disableCreate) {
// then add a card button that takes to monitor create page

View File

@@ -25,6 +25,8 @@ import AlertView from "../../../Components/Alert/Alert";
export interface ComponentProps {
onCallDutyPolicyId?: ObjectID | undefined; // if this is undefined. then it'll show logs for all policies.
incidentId?: ObjectID | undefined;
alertId?: ObjectID | undefined;
}
const ExecutionLogsTable: FunctionComponent<ComponentProps> = (
@@ -42,6 +44,14 @@ const ExecutionLogsTable: FunctionComponent<ComponentProps> = (
query.onCallDutyPolicyId = props.onCallDutyPolicyId.toString();
}
if (props.incidentId) {
query.triggeredByIncidentId = props.incidentId;
}
if (props.alertId) {
query.triggeredByAlertId = props.alertId;
}
let columns: Columns<OnCallDutyPolicyExecutionLog> = [];
let filters: Array<Filter<OnCallDutyPolicyExecutionLog>> = [];

View File

@@ -163,7 +163,7 @@ const NotificationRuleForm: FunctionComponent<ComponentProps> = (
required: true,
description: `If your new channel name is "oneuptime-${props.eventType.toLowerCase()}-", then we will append the ${props.eventType} number in the end so, it'll look like "oneuptime-${props.eventType.toLowerCase()}-X".`,
fieldType: FormFieldSchemaType.Text,
placeholder: `oneupitme-${props.eventType.toLowerCase()}-`,
placeholder: `oneuptime-${props.eventType.toLowerCase()}-`,
},
{
field: {

View File

@@ -134,6 +134,15 @@ const NotificationRuleViewElement: FunctionComponent<ComponentProps> = (
title: `${props.workspaceType} Channel Template Name`,
description: `If your new channel name is "oneuptime-${props.eventType.toLowerCase()}-", then we will append the ${props.eventType} in the end so, it'll look like "oneuptime-${props.eventType.toLowerCase()}-X".`,
fieldType: FieldType.Text,
placeholder: `oneuptime-${props.eventType.toLowerCase()}-`,
showIf: (
formValue:
| IncidentNotificationRule
| AlertNotificationRule
| ScheduledMaintenanceNotificationRule,
) => {
return formValue.shouldCreateNewChannel || false;
},
},
{
key: "shouldInviteOwnersToNewChannel",

View File

@@ -0,0 +1,15 @@
import PageComponentProps from "../../PageComponentProps";
import ObjectID from "Common/Types/ObjectID";
import Navigation from "Common/UI/Utils/Navigation";
import React, { FunctionComponent, ReactElement } from "react";
import ExecutionLogsTable from "../../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable";
const IncidentDelete: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return <ExecutionLogsTable alertId={modelId} />;
};
export default IncidentDelete;

View File

@@ -85,12 +85,27 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
/>
</SideMenuSection>
<SideMenuSection title="On Call">
<SideMenuItem
link={{
title: "On Call Executions",
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ALERT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS
] as Route,
{ modelId: props.modelId },
),
}}
icon={IconProp.Call}
/>
</SideMenuSection>
<SideMenuSection title="Alert Notes">
<SideMenuItem
link={{
title: "Private Notes",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ALERT_INTERNAL_NOTE] as Route,
RouteMap[PageMap.ALERT_VIEW_INTERNAL_NOTE] as Route,
{ modelId: props.modelId },
),
}}

View File

@@ -0,0 +1,15 @@
import PageComponentProps from "../../PageComponentProps";
import ObjectID from "Common/Types/ObjectID";
import Navigation from "Common/UI/Utils/Navigation";
import React, { FunctionComponent, ReactElement } from "react";
import ExecutionLogsTable from "../../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable";
const IncidentDelete: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return <ExecutionLogsTable incidentId={modelId} />;
};
export default IncidentDelete;

View File

@@ -5,6 +5,7 @@ import Incident from "Common/Models/DatabaseModels/Incident";
import React, { FunctionComponent, ReactElement } from "react";
import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail";
import FieldType from "Common/UI/Components/Types/FieldType";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
const IncidentDelete: FunctionComponent<
PageComponentProps
@@ -20,6 +21,19 @@ const IncidentDelete: FunctionComponent<
"Why did this incident happen? Here is the root cause of this incident.",
}}
isEditable={true}
editButtonText="Edit Root Cause"
formFields={[
{
field: {
description: true,
},
title: "Root Cause",
fieldType: FormFieldSchemaType.Markdown,
required: false,
placeholder: "Root Cause",
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 1,
modelType: Incident,

View File

@@ -0,0 +1,53 @@
import PageComponentProps from "../../PageComponentProps";
import ObjectID from "Common/Types/ObjectID";
import Navigation from "Common/UI/Utils/Navigation";
import Incident from "Common/Models/DatabaseModels/Incident";
import React, { FunctionComponent, ReactElement } from "react";
import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail";
import FieldType from "Common/UI/Components/Types/FieldType";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
const IncidentDelete: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return (
<CardModelDetail
name="Incident Settings"
cardProps={{
title: "Incident Settings",
description: "Manage settings for this incident here.",
}}
isEditable={true}
editButtonText="Edit Settings"
formFields={[
{
field: {
isVisibleOnStatusPage: true,
},
title: "Visible on Status Page",
fieldType: FormFieldSchemaType.Toggle,
required: false,
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 1,
modelType: Incident,
id: "model-detail-incident-settings",
fields: [
{
field: {
isVisibleOnStatusPage: true,
},
title: "Visible on Status Page",
fieldType: FieldType.Boolean,
},
],
modelId: modelId,
}}
/>
);
};
export default IncidentDelete;

View File

@@ -85,12 +85,27 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
/>
</SideMenuSection>
<SideMenuSection title="On Call">
<SideMenuItem
link={{
title: "On Call Executions",
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.INCIDENT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS
] as Route,
{ modelId: props.modelId },
),
}}
icon={IconProp.Call}
/>
</SideMenuSection>
<SideMenuSection title="Incident Notes">
<SideMenuItem
link={{
title: "Private Notes",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route,
RouteMap[PageMap.INCIDENT_VIEW_INTERNAL_NOTE] as Route,
{ modelId: props.modelId },
),
}}
@@ -100,7 +115,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
link={{
title: "Public Notes",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_PUBLIC_NOTE] as Route,
RouteMap[PageMap.INCIDENT_VIEW_PUBLIC_NOTE] as Route,
{ modelId: props.modelId },
),
}}
@@ -120,6 +135,17 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
icon={IconProp.TableCells}
/>
<SideMenuItem
link={{
title: "Settings",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_SETTINGS] as Route,
{ modelId: props.modelId },
),
}}
icon={IconProp.Settings}
/>
<SideMenuItem
link={{
title: "Delete Incident",

View File

@@ -0,0 +1,53 @@
import PageComponentProps from "../../PageComponentProps";
import ObjectID from "Common/Types/ObjectID";
import Navigation from "Common/UI/Utils/Navigation";
import ScheduledMaintenance from "Common/Models/DatabaseModels/ScheduledMaintenance";
import React, { FunctionComponent, ReactElement } from "react";
import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
import FieldType from "Common/UI/Components/Types/FieldType";
const ScheduledMaintenanceDelete: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return (
<CardModelDetail
name="Scheduled Maintenance Settings"
cardProps={{
title: "Scheduled Maintenance Settings",
description: "Manage your scheduled maintenance event settings here.",
}}
editButtonText="Edit Settings"
isEditable={true}
formFields={[
{
field: {
isVisibleOnStatusPage: true,
},
title: "Visible on Status Page",
fieldType: FormFieldSchemaType.Toggle,
required: false,
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 1,
modelType: ScheduledMaintenance,
id: "model-detail-incident-settings",
fields: [
{
field: {
isVisibleOnStatusPage: true,
},
title: "Visible on Status Page",
fieldType: FieldType.Boolean,
},
],
modelId: modelId,
}}
/>
);
};
export default ScheduledMaintenanceDelete;

View File

@@ -100,6 +100,17 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
icon={IconProp.TableCells}
/>
<SideMenuItem
link={{
title: "Settings",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW_SETTINGS] as Route,
{ modelId: props.modelId },
),
}}
icon={IconProp.Settings}
/>
<SideMenuItem
link={{
title: "Delete Event",

View File

@@ -32,7 +32,7 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
/>
</SideMenuSection>
{/* <SideMenuSection title="Workspace Connections">
<SideMenuSection title="Workspace Connections">
<SideMenuItem
link={{
title: "Slack",
@@ -42,7 +42,7 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
}}
icon={IconProp.Slack}
/>
</SideMenuSection> */}
</SideMenuSection>
<SideMenuSection title="Monitors">
<SideMenuItem

View File

@@ -23,6 +23,14 @@ const StatusPageDelete: FunctionComponent<
editButtonText="Edit Settings"
isEditable={true}
formFields={[
{
field: {
showIncidentsOnStatusPage: true,
},
title: "Show Incidents",
fieldType: FormFieldSchemaType.Toggle,
required: false,
},
{
field: {
showIncidentHistoryInDays: true,
@@ -46,6 +54,14 @@ const StatusPageDelete: FunctionComponent<
modelType: StatusPage,
id: "model-detail-status-page",
fields: [
{
field: {
showIncidentsOnStatusPage: true,
},
fieldType: FieldType.Boolean,
title: "Show Incidents",
placeholder: "No",
},
{
field: {
showIncidentHistoryInDays: true,
@@ -75,6 +91,14 @@ const StatusPageDelete: FunctionComponent<
editButtonText="Edit Settings"
isEditable={true}
formFields={[
{
field: {
showAnnouncementsOnStatusPage: true,
},
title: "Show Announcements",
fieldType: FormFieldSchemaType.Toggle,
required: false,
},
{
field: {
showAnnouncementHistoryInDays: true,
@@ -90,6 +114,14 @@ const StatusPageDelete: FunctionComponent<
modelType: StatusPage,
id: "model-detail-status-page",
fields: [
{
field: {
showAnnouncementsOnStatusPage: true,
},
fieldType: FieldType.Boolean,
title: "Show Announcements",
placeholder: "No",
},
{
field: {
showAnnouncementHistoryInDays: true,
@@ -111,6 +143,14 @@ const StatusPageDelete: FunctionComponent<
editButtonText="Edit Settings"
isEditable={true}
formFields={[
{
field: {
showScheduledMaintenanceEventsOnStatusPage: true,
},
title: "Show Scheduled Maintenance Events",
fieldType: FormFieldSchemaType.Toggle,
required: false,
},
{
field: {
showScheduledEventHistoryInDays: true,
@@ -134,6 +174,14 @@ const StatusPageDelete: FunctionComponent<
modelType: StatusPage,
id: "model-detail-status-page",
fields: [
{
field: {
showScheduledMaintenanceEventsOnStatusPage: true,
},
fieldType: FieldType.Boolean,
title: "Show Scheduled Maintenance Events",
placeholder: "No",
},
{
field: {
showScheduledEventHistoryInDays: true,

View File

@@ -78,7 +78,7 @@ const DashboardSideMenu: () => ReactElement = (): ReactElement => {
subItemLink={subItemMenuLink}
/>
</SideMenuSection>
{/* <SideMenuSection title="Workspace Connections">
<SideMenuSection title="Workspace Connections">
<SideMenuItem
link={{
title: "Slack",
@@ -88,7 +88,7 @@ const DashboardSideMenu: () => ReactElement = (): ReactElement => {
}}
icon={IconProp.Slack}
/>
</SideMenuSection> */}
</SideMenuSection>
</SideMenu>
);
};

View File

@@ -35,6 +35,8 @@ import React, {
} from "react";
import { Edge, Node } from "reactflow";
import { useAsyncEffect } from "use-async-effect";
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
import HTTPResponse from "Common/Types/API/HTTPResponse";
const Delete: FunctionComponent<PageComponentProps> = (): ReactElement => {
const [isLoading, setIsLoading] = useState<boolean>(true);
@@ -310,14 +312,19 @@ const Delete: FunctionComponent<PageComponentProps> = (): ReactElement => {
}}
onRun={async (component: NodeDataProp) => {
try {
await API.post(
URL.fromString(WORKFLOW_URL.toString()).addRoute(
"/manual/run/" + modelId.toString(),
),
{
data: component.returnValues,
},
);
const result: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post(
URL.fromString(WORKFLOW_URL.toString()).addRoute(
"/manual/run/" + modelId.toString(),
),
{
data: component.returnValues,
},
);
if (result instanceof HTTPErrorResponse) {
throw result;
}
setShowRunSuccessConfirmation(true);
} catch (err) {

View File

@@ -24,6 +24,13 @@ const AlertView: LazyExoticComponent<FunctionComponent<ComponentProps>> = lazy(
return import("../Pages/Alerts/View/Index");
},
);
const AlertOnCallPolicyExecutionLogs: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Alerts/View/OnCallPolicyExecutionLogs");
});
const AlertViewDelete: LazyExoticComponent<FunctionComponent<ComponentProps>> =
lazy(() => {
return import("../Pages/Alerts/View/Delete");
@@ -178,12 +185,12 @@ const AlertsRoutes: FunctionComponent<ComponentProps> = (
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.ALERT_INTERNAL_NOTE)}
path={RouteUtil.getLastPathForKey(PageMap.ALERT_VIEW_INTERNAL_NOTE)}
element={
<Suspense fallback={Loader}>
<AlertInternalNote
{...props}
pageRoute={RouteMap[PageMap.ALERT_INTERNAL_NOTE] as Route}
pageRoute={RouteMap[PageMap.ALERT_VIEW_INTERNAL_NOTE] as Route}
/>
</Suspense>
}
@@ -212,6 +219,24 @@ const AlertsRoutes: FunctionComponent<ComponentProps> = (
</Suspense>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(
PageMap.ALERT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS,
)}
element={
<Suspense fallback={Loader}>
<AlertOnCallPolicyExecutionLogs
{...props}
pageRoute={
RouteMap[
PageMap.ALERT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS
] as Route
}
/>
</Suspense>
}
/>
</PageRoute>
</Routes>
);

View File

@@ -20,6 +20,19 @@ const Incidents: LazyExoticComponent<FunctionComponent<ComponentProps>> = lazy(
return import("../Pages/Incidents/Incidents");
},
);
const IncidentViewSettings: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Incidents/View/Settings");
});
const IncidentViewOnCallPolicyExecutionLogs: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Incidents/View/OnCallPolicyExecutionLogs");
});
const IncidentView: LazyExoticComponent<FunctionComponent<ComponentProps>> =
lazy(() => {
return import("../Pages/Incidents/View/Index");
@@ -162,6 +175,18 @@ const IncidentsRoutes: FunctionComponent<ComponentProps> = (
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.INCIDENT_VIEW_SETTINGS)}
element={
<Suspense fallback={Loader}>
<IncidentViewSettings
{...props}
pageRoute={RouteMap[PageMap.INCIDENT_VIEW_SETTINGS] as Route}
/>
</Suspense>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(
PageMap.INCIDENT_VIEW_STATE_TIMELINE,
@@ -215,12 +240,16 @@ const IncidentsRoutes: FunctionComponent<ComponentProps> = (
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.INCIDENT_INTERNAL_NOTE)}
path={RouteUtil.getLastPathForKey(
PageMap.INCIDENT_VIEW_INTERNAL_NOTE,
)}
element={
<Suspense fallback={Loader}>
<IncidentInternalNote
{...props}
pageRoute={RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route}
pageRoute={
RouteMap[PageMap.INCIDENT_VIEW_INTERNAL_NOTE] as Route
}
/>
</Suspense>
}
@@ -243,12 +272,12 @@ const IncidentsRoutes: FunctionComponent<ComponentProps> = (
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.INCIDENT_PUBLIC_NOTE)}
path={RouteUtil.getLastPathForKey(PageMap.INCIDENT_VIEW_PUBLIC_NOTE)}
element={
<Suspense fallback={Loader}>
<IncidentPublicNote
{...props}
pageRoute={RouteMap[PageMap.INCIDENT_PUBLIC_NOTE] as Route}
pageRoute={RouteMap[PageMap.INCIDENT_VIEW_PUBLIC_NOTE] as Route}
/>
</Suspense>
}
@@ -265,6 +294,24 @@ const IncidentsRoutes: FunctionComponent<ComponentProps> = (
</Suspense>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(
PageMap.INCIDENT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS,
)}
element={
<Suspense fallback={Loader}>
<IncidentViewOnCallPolicyExecutionLogs
{...props}
pageRoute={
RouteMap[
PageMap.INCIDENT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS
] as Route
}
/>
</Suspense>
}
/>
</PageRoute>
</Routes>
);

View File

@@ -42,6 +42,13 @@ const ScheduledMaintenanceEventViewOwner: LazyExoticComponent<
> = lazy(() => {
return import("../Pages/ScheduledMaintenanceEvents/View/Owners");
});
const ScheduledMaintenanceEventsViewSettings: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/ScheduledMaintenanceEvents/View/Settings");
});
const ScheduledMaintenanceEventViewStateTimeline: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
@@ -191,6 +198,21 @@ const ScheduledMaintenanceEventsRoutes: FunctionComponent<ComponentProps> = (
</Suspense>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(
PageMap.SCHEDULED_MAINTENANCE_VIEW_SETTINGS,
)}
element={
<Suspense fallback={Loader}>
<ScheduledMaintenanceEventsViewSettings
{...props}
pageRoute={
RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW_SETTINGS] as Route
}
/>
</Suspense>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(
PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE,

View File

@@ -28,7 +28,11 @@ export function getAlertsBreadcrumbs(path: string): Array<Link> | undefined {
"View Alert",
"Owners",
]),
...BuildBreadcrumbLinksByTitles(PageMap.ALERT_INTERNAL_NOTE, [
...BuildBreadcrumbLinksByTitles(
PageMap.ALERT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS,
["Project", "Alerts", "View Alert", "On Call Executions"],
),
...BuildBreadcrumbLinksByTitles(PageMap.ALERT_VIEW_INTERNAL_NOTE, [
"Project",
"Alerts",
"View Alert",

View File

@@ -56,13 +56,18 @@ export function getIncidentsBreadcrumbs(path: string): Array<Link> | undefined {
"View Incident",
"Owners",
]),
...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_INTERNAL_NOTE, [
...BuildBreadcrumbLinksByTitles(
PageMap.INCIDENT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS,
["Project", "Incidents", "View Incident", "On Call Executions"],
),
...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_INTERNAL_NOTE, [
"Project",
"Incidents",
"View Incident",
"Private Notes",
]),
...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_PUBLIC_NOTE, [
...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_PUBLIC_NOTE, [
"Project",
"Incidents",
"View Incident",
@@ -80,6 +85,12 @@ export function getIncidentsBreadcrumbs(path: string): Array<Link> | undefined {
"View Incident",
"Delete Incident",
]),
...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_SETTINGS, [
"Project",
"Incidents",
"View Incident",
"Settings",
]),
};
return breadcrumpLinksMap[path];
}

View File

@@ -71,6 +71,15 @@ export function getScheduleMaintenanceBreadcrumbs(
"Custom Fields",
],
),
...BuildBreadcrumbLinksByTitles(
PageMap.SCHEDULED_MAINTENANCE_VIEW_SETTINGS,
[
"Project",
"Scheduled Maintenance Events",
"View Scheduled Maintenance Event",
"Settings",
],
),
...BuildBreadcrumbLinksByTitles(PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE, [
"Project",
"Scheduled Maintenance Events",

View File

@@ -74,14 +74,16 @@ enum PageMap {
INCIDENT_CREATE = "INCIDENT_CREATE",
INCIDENT_VIEW = "INCIDENT_VIEW",
INCIDENT_VIEW_DELETE = "INCIDENT_VIEW_DELETE",
INCIDENT_VIEW_SETTINGS = "INCIDENT_VIEW_SETTINGS",
INCIDENT_VIEW_STATE_TIMELINE = "INCIDENT_VIEW_STATE_TIMELINE",
INCIDENT_VIEW_ROOT_CAUSE = "INCIDENT_VIEW_ROOT_CAUSE",
INCIDENT_VIEW_REMEDIATION = "INCIDENT_VIEW_REMEDIATION",
INCIDENT_VIEW_DESCRIPTION = "INCIDENT_VIEW_DESCRIPTION",
INCIDENT_INTERNAL_NOTE = "INCIDENT_INTERNAL_NOTE",
INCIDENT_PUBLIC_NOTE = "INCIDENT_PUBLIC_NOTE",
INCIDENT_VIEW_INTERNAL_NOTE = "INCIDENT_VIEW_INTERNAL_NOTE",
INCIDENT_VIEW_PUBLIC_NOTE = "INCIDENT_VIEW_PUBLIC_NOTE",
INCIDENT_VIEW_CUSTOM_FIELDS = "INCIDENT_VIEW_CUSTOM_FIELDS",
INCIDENT_VIEW_OWNERS = "INCIDENT_VIEW_OWNERS",
INCIDENT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS = "INCIDENT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS",
ALERTS_ROOT = "ALERTS_ROOT",
ALERTS = "ALERTS",
@@ -89,12 +91,13 @@ enum PageMap {
ALERT_VIEW = "ALERT_VIEW",
ALERT_VIEW_DELETE = "ALERT_VIEW_DELETE",
ALERT_VIEW_STATE_TIMELINE = "ALERT_VIEW_STATE_TIMELINE",
ALERT_INTERNAL_NOTE = "ALERT_INTERNAL_NOTE",
ALERT_VIEW_INTERNAL_NOTE = "ALERT_VIEW_INTERNAL_NOTE",
ALERT_VIEW_CUSTOM_FIELDS = "ALERT_VIEW_CUSTOM_FIELDS",
ALERT_VIEW_OWNERS = "ALERT_VIEW_OWNERS",
ALERT_VIEW_DESCRIPTION = "ALERT_VIEW_DESCRIPTION",
ALERT_VIEW_ROOT_CAUSE = "ALERT_VIEW_ROOT_CAUSE",
ALERT_VIEW_REMEDIATION = "ALERT_VIEW_REMEDIATION",
ALERT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS = "ALERT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS",
SCHEDULED_MAINTENANCE_EVENTS_ROOT = "SCHEDULED_MAINTENANCE_EVENTS_ROOT",
SCHEDULED_MAINTENANCE_EVENTS = "SCHEDULED_MAINTENANCE_EVENTS",
@@ -108,6 +111,7 @@ enum PageMap {
SCHEDULED_MAINTENANCE_PUBLIC_NOTE = "SCHEDULED_MAINTENANCE_PUBLIC_NOTE",
SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS = "SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS",
SCHEDULED_MAINTENANCE_VIEW_OWNERS = "SCHEDULED_MAINTENANCE_VIEW_OWNERS",
SCHEDULED_MAINTENANCE_VIEW_SETTINGS = "SCHEDULED_MAINTENANCE_VIEW_SETTINGS",
MONITORS = "MONITORS",
MONITORS_ROOT = "MONITORS_ROOT",

View File

@@ -147,10 +147,12 @@ export const IncidentsRoutePath: Dictionary<string> = {
[PageMap.INCIDENT_VIEW_ROOT_CAUSE]: `${RouteParams.ModelID}/root-cause`,
[PageMap.INCIDENT_VIEW_DESCRIPTION]: `${RouteParams.ModelID}/description`,
[PageMap.INCIDENT_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`,
[PageMap.INCIDENT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS]: `${RouteParams.ModelID}/on-call-policy-execution-logs`,
[PageMap.INCIDENT_VIEW_DELETE]: `${RouteParams.ModelID}/delete`,
[PageMap.INCIDENT_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`,
[PageMap.INCIDENT_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`,
[PageMap.INCIDENT_INTERNAL_NOTE]: `${RouteParams.ModelID}/internal-notes`,
[PageMap.INCIDENT_PUBLIC_NOTE]: `${RouteParams.ModelID}/public-notes`,
[PageMap.INCIDENT_VIEW_INTERNAL_NOTE]: `${RouteParams.ModelID}/internal-notes`,
[PageMap.INCIDENT_VIEW_PUBLIC_NOTE]: `${RouteParams.ModelID}/public-notes`,
};
export const AlertsRoutePath: Dictionary<string> = {
@@ -158,12 +160,13 @@ export const AlertsRoutePath: Dictionary<string> = {
[PageMap.ALERT_VIEW]: `${RouteParams.ModelID}`,
[PageMap.ALERT_VIEW_STATE_TIMELINE]: `${RouteParams.ModelID}/state-timeline`,
[PageMap.ALERT_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`,
[PageMap.ALERT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS]: `${RouteParams.ModelID}/on-call-policy-execution-logs`,
[PageMap.ALERT_VIEW_DELETE]: `${RouteParams.ModelID}/delete`,
[PageMap.ALERT_VIEW_DESCRIPTION]: `${RouteParams.ModelID}/description`,
[PageMap.ALERT_VIEW_ROOT_CAUSE]: `${RouteParams.ModelID}/root-cause`,
[PageMap.ALERT_VIEW_REMEDIATION]: `${RouteParams.ModelID}/remediation`,
[PageMap.ALERT_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`,
[PageMap.ALERT_INTERNAL_NOTE]: `${RouteParams.ModelID}/internal-notes`,
[PageMap.ALERT_VIEW_INTERNAL_NOTE]: `${RouteParams.ModelID}/internal-notes`,
};
export const ScheduledMaintenanceEventsRoutePath: Dictionary<string> = {
@@ -177,6 +180,7 @@ export const ScheduledMaintenanceEventsRoutePath: Dictionary<string> = {
[PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE]: `${RouteParams.ModelID}/internal-notes`,
[PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE]: `${RouteParams.ModelID}/public-notes`,
[PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`,
[PageMap.SCHEDULED_MAINTENANCE_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`,
};
export const SettingsRoutePath: Dictionary<string> = {
@@ -438,6 +442,12 @@ const RouteMap: Dictionary<Route> = {
}`,
),
[PageMap.ALERT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS]: new Route(
`/dashboard/${RouteParams.ProjectID}/alerts/${
AlertsRoutePath[PageMap.ALERT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS]
}`,
),
[PageMap.ALERT_VIEW_DELETE]: new Route(
`/dashboard/${RouteParams.ProjectID}/alerts/${
AlertsRoutePath[PageMap.ALERT_VIEW_DELETE]
@@ -468,9 +478,9 @@ const RouteMap: Dictionary<Route> = {
}`,
),
[PageMap.ALERT_INTERNAL_NOTE]: new Route(
[PageMap.ALERT_VIEW_INTERNAL_NOTE]: new Route(
`/dashboard/${RouteParams.ProjectID}/alerts/${
AlertsRoutePath[PageMap.ALERT_INTERNAL_NOTE]
AlertsRoutePath[PageMap.ALERT_VIEW_INTERNAL_NOTE]
}`,
),
@@ -549,27 +559,39 @@ const RouteMap: Dictionary<Route> = {
}`,
),
[PageMap.INCIDENT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS]: new Route(
`/dashboard/${RouteParams.ProjectID}/incidents/${
IncidentsRoutePath[PageMap.INCIDENT_VIEW_ON_CALL_POLICY_EXECUTION_LOGS]
}`,
),
[PageMap.INCIDENT_VIEW_DELETE]: new Route(
`/dashboard/${RouteParams.ProjectID}/incidents/${
IncidentsRoutePath[PageMap.INCIDENT_VIEW_DELETE]
}`,
),
[PageMap.INCIDENT_VIEW_SETTINGS]: new Route(
`/dashboard/${RouteParams.ProjectID}/incidents/${
IncidentsRoutePath[PageMap.INCIDENT_VIEW_SETTINGS]
}`,
),
[PageMap.INCIDENT_VIEW_CUSTOM_FIELDS]: new Route(
`/dashboard/${RouteParams.ProjectID}/incidents/${
IncidentsRoutePath[PageMap.INCIDENT_VIEW_CUSTOM_FIELDS]
}`,
),
[PageMap.INCIDENT_INTERNAL_NOTE]: new Route(
[PageMap.INCIDENT_VIEW_INTERNAL_NOTE]: new Route(
`/dashboard/${RouteParams.ProjectID}/incidents/${
IncidentsRoutePath[PageMap.INCIDENT_INTERNAL_NOTE]
IncidentsRoutePath[PageMap.INCIDENT_VIEW_INTERNAL_NOTE]
}`,
),
[PageMap.INCIDENT_PUBLIC_NOTE]: new Route(
[PageMap.INCIDENT_VIEW_PUBLIC_NOTE]: new Route(
`/dashboard/${RouteParams.ProjectID}/incidents/${
IncidentsRoutePath[PageMap.INCIDENT_PUBLIC_NOTE]
IncidentsRoutePath[PageMap.INCIDENT_VIEW_PUBLIC_NOTE]
}`,
),
@@ -651,6 +673,14 @@ const RouteMap: Dictionary<Route> = {
}`,
),
[PageMap.SCHEDULED_MAINTENANCE_VIEW_SETTINGS]: new Route(
`/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${
ScheduledMaintenanceEventsRoutePath[
PageMap.SCHEDULED_MAINTENANCE_VIEW_SETTINGS
]
}`,
),
[PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE]: new Route(
`/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${
ScheduledMaintenanceEventsRoutePath[

View File

@@ -158,6 +158,104 @@ This is the response from the API:
}
```
#### Uptime API
This API will fetch all the uptime of all the resources on status page.
To get overall uptime of all the resources, you can make a POST request to the following endpoint:
```bash
curl -X POST https://oneuptime.com/status-page-api/uptime/:statusPageId
```
**Request Body (optional):**
You can send startDate and endDate as request body.
```
{
"startDate": "2021-09-01T00:00:00Z",
"endDate": "2021-09-30T23:59:59Z"
}
```
These dates should not be more than 90 days apart. If you do not provide the dates, the API will return the uptime for the last 14 days.
**Example Response:**
This is the example response from the API:
```json
{
"statusPageResourceUptimes": [
{
"statusPageResourceId": {
"_type": "ObjectID",
"value": "cfffa3c3-fdf3-4cd7-9585-d6d408a14663"
},
"uptimePercent": 99.98,
"statusPageResourceName": "Status Page Resource Name",
"currentStatus": {
"_id": "cc80b385-4190-42a3-ae8b-9b391e90d79f",
"isPermissionIf": {},
"name": "Operational",
"color": {
"_type": "Color",
"value": "#2ab57d"
},
"isOperationalState": true,
"priority": 1
}
}
],
"groupUptimes": [
{
"statusPageGroupId": {
"_type": "ObjectID",
"value": "df7632c4-c5c0-453c-88bf-9ee3d68d45f2"
},
"uptimePercent": 99.98,
"statusPageResourceUptimes": [
{
"statusPageResourceId": {
"_type": "ObjectID",
"value": "8175534f-aa77-456c-ad5b-b8e7b85876aa"
},
"uptimePercent": 99.98,
"statusPageResourceName": "dfg",
"currentStatus": {
"_id": "cc80b385-4190-42a3-ae8b-9b391e90d79f",
"isPermissionIf": {},
"name": "Operational",
"color": {
"_type": "Color",
"value": "#2ab57d"
},
"isOperationalState": true,
"priority": 1
}
}
],
"statusPageGroupName": "Group Name",
"currentStatus": {
"_id": "cc80b385-4190-42a3-ae8b-9b391e90d79f",
"isPermissionIf": {},
"name": "Operational",
"color": {
"_type": "Color",
"value": "#2ab57d"
},
"isOperationalState": true,
"priority": 1
}
}
],
"startDate": "2021-09-01T00:00:00Z",
"endDate": "2021-09-30T23:59:59Z"
}
```
### Incident API
This API will fetch all the incidents that are on the status page. To get all the incidents on the status page, you can make a POST request to the following endpoint:

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.2-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:22.9
FROM public.ecr.aws/docker/library/node:23.8
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -1,4 +1,4 @@
FROM public.ecr.aws/docker/library/node:21.7.3-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global

View File

@@ -4,6 +4,6 @@ This repository is used to host all the helm-charts for OneUptime.
### Helm Packages
- *oneuptime* : Chart for deploying OneUpitme app. [Read Docs here](oneuptime/README.md)
- *oneuptime* : Chart for deploying OneUptime app. [Read Docs here](oneuptime/README.md)

View File

@@ -16,5 +16,5 @@ You should then be able to access OneUptime cluster with that IP. Please make su
We would like to hear your feedback to make this product better for you and for other users, please email us at hello@oneuptime.com.
- If you notice a bug, we will fix it for you.
- If you need a feature, we will add that to the roadmap and let you know the estimated time to ship.
- If you are an enterprise customer, we offer dedicated engineering support to build oneupitme features you need to integrate OneUptime for your organization. Please contact us at sales@oneuptime.com
- If you are an enterprise customer, we offer dedicated engineering support to build oneuptime features you need to integrate OneUptime for your organization. Please contact us at sales@oneuptime.com
We would love to hear your feedback. Email: hello@oneuptime.com

View File

@@ -56,6 +56,8 @@ Usage:
value: {{ $.Values.analytics.host }}
- name: SERVER_ACCOUNTS_HOSTNAME
value: {{ $.Release.Name }}-accounts.{{ $.Release.Namespace }}.svc.{{ $.Values.global.clusterDomain }}
- name: SERVER_SERVER_MONITOR_INGEST_HOSTNAME
value: {{ $.Release.Name }}-server-monitor-ingest.{{ $.Release.Namespace }}.svc.{{ $.Values.global.clusterDomain }}
- name: SERVER_ISOLATED_VM_HOSTNAME
value: {{ $.Release.Name }}-isolated-vm.{{ $.Release.Namespace }}.svc.{{ $.Values.global.clusterDomain }}
- name: SERVER_WORKFLOW_HOSTNAME
@@ -95,6 +97,8 @@ Usage:
value: {{ $.Values.port.app | squote }}
- name: PROBE_INGEST_PORT
value: {{ $.Values.port.probeIngest | squote }}
- name: SERVER_MONITOR_INGEST_PORT
value: {{ $.Values.port.serverMonitorIngest | squote }}
- name: OPEN_TELEMETRY_INGEST_PORT
value: {{ $.Values.port.openTelemetryIngest | squote }}
- name: INCOMING_REQUEST_INGEST_PORT

View File

@@ -0,0 +1,113 @@
# OneUptime server-monitor-ingest Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ printf "%s-%s" $.Release.Name "server-monitor-ingest" }}
namespace: {{ $.Release.Namespace }}
labels:
app: {{ printf "%s-%s" $.Release.Name "server-monitor-ingest" }}
app.kubernetes.io/part-of: oneuptime
app.kubernetes.io/managed-by: Helm
appname: oneuptime
date: "{{ now | unixEpoch }}"
spec:
selector:
matchLabels:
app: {{ printf "%s-%s" $.Release.Name "server-monitor-ingest" }}
{{- if $.Values.deployment.serverMonitorIngest.replicaCount }}
replicas: {{ $.Values.deployment.serverMonitorIngest.replicaCount }}
{{- else }}
replicas: {{ $.Values.deployment.replicaCount }}
{{- end }}
template:
metadata:
labels:
app: {{ printf "%s-%s" $.Release.Name "server-monitor-ingest" }}
date: "{{ now | unixEpoch }}"
appname: oneuptime
spec:
volumes:
- name: greenlockrc
emptyDir:
sizeLimit: "1Gi"
{{- if $.Values.podSecurityContext }}
securityContext: {{- $.Values.podSecurityContext | toYaml | nindent 8 }}
{{- end }}
{{- if $.Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml $.Values.imagePullSecrets | nindent 8 }}
{{- end }}
{{- if $.Values.affinity }}
affinity: {{- $.Values.affinity | toYaml | nindent 8 }}
{{- end }}
{{- if $.Values.tolerations }}
tolerations: {{- $.Values.tolerations | toYaml | nindent 8 }}
{{- end }}
{{- if $.Values.nodeSelector }}
nodeSelector: {{- $.Values.nodeSelector | toYaml | nindent 8 }}
{{- end }}
containers:
- image: {{ printf "%s/%s/%s:%s" $.Values.image.registry $.Values.image.repository "server-monitor-ingest" $.Values.image.tag }}
name: {{ printf "%s-%s" $.Release.Name "server-monitor-ingest" }}
{{- if $.Values.startupProbe.enabled }}
# Startup probe
startupProbe:
httpGet:
path: /status/live
port: {{ $.Values.port.serverMonitorIngest }}
periodSeconds: {{ $.Values.startupProbe.periodSeconds }}
failureThreshold: {{ $.Values.startupProbe.failureThreshold }}
{{- end }}
{{- if $.Values.livenessProbe.enabled }}
# Liveness probe
livenessProbe:
httpGet:
path: /status/live
port: {{ $.Values.port.serverMonitorIngest }}
periodSeconds: {{ $.Values.livenessProbe.periodSeconds }}
timeoutSeconds: {{ $.Values.livenessProbe.timeoutSeconds }}
initialDelaySeconds: {{ $.Values.livenessProbe.initialDelaySeconds }}
{{- end }}
{{- if $.Values.readinessProbe.enabled }}
# Readyness Probe
readinessProbe:
httpGet:
path: /status/ready
port: {{ $.Values.port.serverMonitorIngest }}
periodSeconds: {{ $.Values.readinessProbe.periodSeconds }}
initialDelaySeconds: {{ $.Values.readinessProbe.initialDelaySeconds }}
timeoutSeconds: {{ $.Values.readinessProbe.timeoutSeconds }}
{{- end }}
{{- if $.Values.containerSecurityContext }}
securityContext: {{- $.Values.containerSecurityContext | toYaml | nindent 12 }}
{{- end }}
imagePullPolicy: {{ $.Values.image.pullPolicy }}
env:
{{- include "oneuptime.env.common" . | nindent 12 }}
{{- include "oneuptime.env.commonServer" . | nindent 12 }}
{{- include "oneuptime.env.oneuptimeSecret" . | nindent 12 }}
- name: OPENTELEMETRY_EXPORTER_OTLP_HEADERS
value: {{ $.Values.openTelemetryExporter.headers }}
- name: PORT
value: {{ $.Values.port.serverMonitorIngest | quote }}
- name: DISABLE_TELEMETRY
value: {{ $.Values.serverMonitorIngest.disableTelemetryCollection | quote }}
ports:
- containerPort: {{ $.Values.port.serverMonitorIngest }}
protocol: TCP
name: http
restartPolicy: {{ $.Values.image.restartPolicy }}
---
# OneUptime server-monitor-ingest Service
{{- $serverMonitorIngestPorts := dict "port" $.Values.port.serverMonitorIngest -}}
{{- $serverMonitorIngestServiceArgs := dict "ServiceName" "server-monitor-ingest" "Ports" $serverMonitorIngestPorts "Release" $.Release "Values" $.Values -}}
{{- include "oneuptime.service" $serverMonitorIngestServiceArgs }}
---
# OneUptime server-monitor-ingest autoscaler
{{- $serverMonitorIngestAutoScalerArgs := dict "ServiceName" "server-monitor-ingest" "Release" $.Release "Values" $.Values -}}
{{- include "oneuptime.autoscaler" $serverMonitorIngestAutoScalerArgs }}
---

View File

@@ -31,6 +31,8 @@ deployment:
replicaCount: 1
probeIngest:
replicaCount:
serverMonitorIngest:
replicaCount:
openTelemetryIngest:
replicaCount:
fluentIngest:
@@ -221,6 +223,7 @@ probes:
port:
app: 3002
probeIngest: 3400
serverMonitorIngest: 3404
openTelemetryIngest: 3403
fluentIngest: 3401
incomingRequestIngest: 3402
@@ -472,6 +475,9 @@ incomingRequestIngest:
isolatedVM:
disableTelemetryCollection: false
serverMonitorIngest:
disableTelemetryCollection: false
slackApp:
clientId:

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.7.3-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:22.9
FROM public.ecr.aws/docker/library/node:23.8
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -14,6 +14,11 @@ upstream open-telemetry-ingest {
server ${SERVER_OPEN_TELEMETRY_INGEST_HOSTNAME}:${OPEN_TELEMETRY_INGEST_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
upstream server-monitor-ingest {
server ${SERVER_SERVER_MONITOR_INGEST_HOSTNAME}:${SERVER_MONITOR_INGEST_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
upstream incoming-request-ingest {
server ${SERVER_INCOMING_REQUEST_INGEST_HOSTNAME}:${INCOMING_REQUEST_INGEST_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
@@ -630,7 +635,7 @@ server {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://probe-ingest/server-monitor;
proxy_pass http://server-monitor-ingest/server-monitor;
client_max_body_size 50M;
}

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:22.9
FROM public.ecr.aws/docker/library/node:23.8
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:22.9
FROM public.ecr.aws/docker/library/node:23.8
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:22.9
FROM public.ecr.aws/docker/library/node:23.8
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -1,7 +1,6 @@
import MonitorAPI from "./API/Monitor";
import ProbeIngest from "./API/Probe";
import RegisterAPI from "./API/Register";
import ServerMonitorAPI from "./API/ServerMonitor";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import { ClickhouseAppInstance } from "Common/Server/Infrastructure/ClickhouseDatabase";
import PostgresAppInstance from "Common/Server/Infrastructure/PostgresDatabase";
@@ -22,7 +21,6 @@ const APP_NAME: string = "probe-ingest";
app.use([`/${APP_NAME}`, "/ingestor", "/"], RegisterAPI);
app.use([`/${APP_NAME}`, "/ingestor", "/"], MonitorAPI);
app.use([`/${APP_NAME}`, "/ingestor", "/"], ProbeIngest);
app.use([`/${APP_NAME}`, "/ingestor", "/"], ServerMonitorAPI);
const init: PromiseVoidFunction = async (): Promise<void> => {
try {

View File

@@ -0,0 +1,56 @@
.git
node_modules
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
node_modules
.idea
# testing
/coverage
# production
/build
# misc
.DS_Store
env.js
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock
Untitled-1
*.local.sh
*.local.yaml
run
stop
nohup.out*
encrypted-credentials.tar
encrypted-credentials/
_README.md
# Important Add production values to gitignore.
values-saas-production.yaml
kubernetes/values-saas-production.yaml
/private
/tls_cert.pem
/tls_key.pem
/keys
temp_readme.md
tests/coverage
settings.json
GoSDK/tester/

1
ServerMonitorIngest/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.js text eol=lf

16
ServerMonitorIngest/.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
#/backend/node_modules
/kubernetes
/node_modules
.idea
# misc
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock

View File

@@ -0,0 +1,83 @@
#
# OneUptime-ServerMonitorIngest Dockerfile
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:23.8
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5
RUN npm config set fetch-retry-mintimeout 100000
RUN npm config set fetch-retry-maxtimeout 600000
ARG GIT_SHA
ARG APP_VERSION
ENV GIT_SHA=${GIT_SHA}
ENV APP_VERSION=${APP_VERSION}
ENV NODE_OPTIONS="--use-openssl-ca"
## Add Intermediate Certs
COPY ./SslCertificates /usr/local/share/ca-certificates
RUN update-ca-certificates
# IF APP_VERSION is not set, set it to 1.0.0
RUN if [ -z "$APP_VERSION" ]; then export APP_VERSION=1.0.0; fi
RUN apt-get update
# Install bash.
RUN apt-get install bash -y && apt-get install curl -y && apt-get install iputils-ping -y
# Install python
RUN apt-get update && apt-get install -y .gyp python3 make g++
# Install playwright dependencies
RUN apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libgtk-3-0 libpango-1.0-0 libcairo2 libgdk-pixbuf2.0-0 libasound2 libatspi2.0-0
#Use bash shell by default
SHELL ["/bin/bash", "-c"]
# Install iputils
RUN apt-get install net-tools -y
RUN mkdir -p /usr/src
WORKDIR /usr/src/Common
COPY ./Common/package*.json /usr/src/Common/
# Set version in ./Common/package.json to the APP_VERSION
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/Common/package.json
RUN npm install
COPY ./Common /usr/src/Common
ENV PRODUCTION=true
WORKDIR /usr/src/app
RUN npx playwright install --with-deps
# Install app dependencies
COPY ./ServerMonitorIngest/package*.json /usr/src/app/
RUN npm install
# Expose ports.
# - 3404: OneUptime-server-monitor-ingest
EXPOSE 3404
{{ if eq .Env.ENVIRONMENT "development" }}
#Run the app
CMD [ "npm", "run", "dev" ]
{{ else }}
# Copy app source
COPY ./ServerMonitorIngest /usr/src/app
# Bundle app source
RUN npm run compile
#Run the app
CMD [ "npm", "start" ]
{{ end }}

View File

@@ -0,0 +1,69 @@
import ServerMonitorAPI from "./API/ServerMonitor";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import { ClickhouseAppInstance } from "Common/Server/Infrastructure/ClickhouseDatabase";
import PostgresAppInstance from "Common/Server/Infrastructure/PostgresDatabase";
import Redis from "Common/Server/Infrastructure/Redis";
import InfrastructureStatus from "Common/Server/Infrastructure/Status";
import Express, { ExpressApplication } from "Common/Server/Utils/Express";
import logger from "Common/Server/Utils/Logger";
import Realtime from "Common/Server/Utils/Realtime";
import App from "Common/Server/Utils/StartServer";
import Telemetry from "Common/Server/Utils/Telemetry";
const app: ExpressApplication = Express.getExpressApp();
const APP_NAME: string = "server-monitor-ingest";
app.use([`/${APP_NAME}`, "/"], ServerMonitorAPI);
const init: PromiseVoidFunction = async (): Promise<void> => {
try {
const statusCheck: PromiseVoidFunction = async (): Promise<void> => {
return await InfrastructureStatus.checkStatusWithRetry({
checkClickhouseStatus: true,
checkPostgresStatus: true,
checkRedisStatus: true,
retryCount: 3,
});
};
// Initialize telemetry
Telemetry.init({
serviceName: APP_NAME,
});
// init the app
await App.init({
appName: APP_NAME,
statusOptions: {
liveCheck: statusCheck,
readyCheck: statusCheck,
},
});
// connect to the database.
await PostgresAppInstance.connect();
// connect redis
await Redis.connect();
await ClickhouseAppInstance.connect(
ClickhouseAppInstance.getDatasourceOptions(),
);
await Realtime.init();
// add default routes
await App.addDefaultRoutes();
} catch (err) {
logger.error("App Init Failed:");
logger.error(err);
throw err;
}
};
init().catch((err: Error) => {
logger.error(err);
logger.error("Exiting node process");
process.exit(1);
});

View File

@@ -0,0 +1,32 @@
{
"preset": "ts-jest",
"testPathIgnorePatterns": [
"node_modules",
"dist"
],
"verbose": true,
"globals": {
"ts-jest": {
"tsconfig": "tsconfig.json",
"babelConfig": false
}
},
"moduleFileExtensions": ["ts", "js", "json"],
"transform": {
".(ts|tsx)": "ts-jest"
},
"testEnvironment": "node",
"collectCoverage": false,
"coverageReporters": ["text", "lcov"],
"testRegex": "./Tests/(.*).test.ts",
"collectCoverageFrom": ["./**/*.(tsx||ts)"],
"coverageThreshold": {
"global": {
"lines": 0,
"functions": 0,
"branches": 0,
"statements": 0
}
}
}

View File

@@ -0,0 +1,5 @@
{
"watch": ["./","../Common/Server", "../Common/Types", "../Common/Utils", "../Common/Models"],
"ext": "ts,json,tsx,env,js,jsx,hbs",
"exec": "node --inspect=0.0.0.0:9229 --require ts-node/register Index.ts"
}

4698
ServerMonitorIngest/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
{
"name": "@oneuptime/server-monitor-ingest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node --require ts-node/register Index.ts",
"compile": "tsc",
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
"dev": "npx nodemon",
"audit": "npm audit --audit-level=low",
"dep-check": "npm install -g depcheck && depcheck ./ --skip-missing=true",
"test": "jest --passWithNoTests"
},
"author": "OneUptime <hello@oneuptime.com> (https://oneuptime.com/)",
"license": "Apache-2.0",
"dependencies": {
"Common": "file:../Common",
"ts-node": "^10.9.1"
},
"devDependencies": {
"@types/jest": "^27.5.0",
"@types/node": "^17.0.31",
"jest": "^28.1.0",
"nodemon": "^2.0.20",
"ts-jest": "^28.0.2"
}
}

View File

@@ -0,0 +1,115 @@
{
"ts-node": {
// these options are overrides used only by ts-node
// same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable
"compilerOptions": {
"module": "commonjs",
"resolveJsonModule": true,
}
},
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react" /* Specify what JSX code is generated. */,
"experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
"emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
// "module": "es2022" /* Specify what module code is generated. */,
"rootDir": "" /* Specify the root folder within your source files. */,
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": [
"node_modules/@types"
] /* Specify multiple folders that act like `./node_modules/@types`. */,
"types": [
"node",
"jest"
] /* Specify type package names to be included without being referenced in a source file. */,
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "build/dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied `any` type.. */,
"strictNullChecks": true /* When type checking, take into account `null` and `undefined`. */,
"strictFunctionTypes": true /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */,
"strictBindCallApply": true /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */,
"strictPropertyInitialization": true /* Check for class properties that are declared but not set in the constructor. */,
"noImplicitThis": true /* Enable error reporting when `this` is given the type `any`. */,
"useUnknownInCatchVariables": true /* Type catch clause variables as 'unknown' instead of 'any'. */,
"alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
"noUnusedLocals": true /* Enable error reporting when a local variables aren't read. */,
"noUnusedParameters": true /* Raise an error when a function parameter isn't read */,
"exactOptionalPropertyTypes": true /* Interpret optional property types as written, rather than adding 'undefined'. */,
"noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */,
"noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */,
"noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */,
"noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */,
"noPropertyAccessFromIndexSignature": true /* Enforces using indexed accessors for keys declared using an indexed type */,
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
"resolveJsonModule": true
}
}

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.7.3-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -34,6 +34,7 @@ import React, {
useState,
} from "react";
import useAsyncEffect from "use-async-effect";
import StatusPage from "Common/Models/DatabaseModels/StatusPage";
export interface ComponentProps {
children: ReactElement | Array<ReactElement>;
@@ -58,6 +59,8 @@ const DashboardMasterPage: FunctionComponent<ComponentProps> = (
const [headerHtml, setHeaderHtml] = useState<null | string>(null);
const [footerHtml, setFooterHTML] = useState<null | string>(null);
const [statusPage, setStatusPage] = useState<StatusPage | null>(null);
const [hidePoweredByOneUptimeBranding, setHidePoweredByOneUptimeBranding] =
useState<boolean>(false);
@@ -170,11 +173,16 @@ const DashboardMasterPage: FunctionComponent<ComponentProps> = (
);
setMasterPageData(response.data);
// set status page.
const statusPage: StatusPage = BaseModel.fromJSONObject(
(response.data["statusPage"] as JSONObject) || [],
StatusPage,
);
setStatusPage(statusPage);
// setfavicon.
const favIcon: File | null = JSONFunctions.getJSONValueInPath(
response.data || {},
"statusPage.faviconFile",
) as File | null;
const favIcon: File | undefined = statusPage.faviconFile;
if (favIcon && favIcon.file) {
const link: any = document.createElement("link");
link.rel = "icon";
@@ -183,36 +191,23 @@ const DashboardMasterPage: FunctionComponent<ComponentProps> = (
}
// setcss.
const css: string | null = JSONFunctions.getJSONValueInPath(
response.data || {},
"statusPage.customCSS",
) as string | null;
const css: string | null = statusPage.customCSS || null;
if (css) {
const style: any = document.createElement("style");
style.innerText = css;
(document as any).getElementsByTagName("head")[0].appendChild(style);
}
const headHtml: string | null = JSONFunctions.getJSONValueInPath(
response.data || {},
"statusPage.headerHTML",
) as string | null;
const headHtml: string | null = statusPage.headerHTML || null;
const hidePoweredByOneUptimeBranding: boolean | null =
JSONFunctions.getJSONValueInPath(
response.data || {},
"statusPage.hidePoweredByOneUptimeBranding",
) as boolean | null;
statusPage.hidePoweredByOneUptimeBranding || false;
setHidePoweredByOneUptimeBranding(
Boolean(hidePoweredByOneUptimeBranding),
);
const footHTML: string | null = JSONFunctions.getJSONValueInPath(
response.data || {},
"statusPage.footerHTML",
) as string | null;
const footHTML: string | null = statusPage.footerHTML || null;
if (headHtml) {
setHeaderHtml(headHtml);
@@ -314,6 +309,15 @@ const DashboardMasterPage: FunctionComponent<ComponentProps> = (
isPreview={true}
enableEmailSubscribers={props.enableEmailSubscribers}
enableSMSSubscribers={props.enableSMSSubscribers}
showIncidentsOnStatusPage={
statusPage?.showIncidentsOnStatusPage || false
}
showAnnouncementsOnStatusPage={
statusPage?.showAnnouncementsOnStatusPage || false
}
showScheduledMaintenanceEventsOnStatusPage={
statusPage?.showScheduledMaintenanceEventsOnStatusPage || false
}
/>
{props.children}
{!footerHtml ? (

View File

@@ -12,6 +12,9 @@ export interface ComponentProps {
isPrivateStatusPage: boolean;
enableEmailSubscribers: boolean;
enableSMSSubscribers: boolean;
showIncidentsOnStatusPage: boolean;
showAnnouncementsOnStatusPage: boolean;
showScheduledMaintenanceEventsOnStatusPage: boolean;
}
const DashboardNavbar: FunctionComponent<ComponentProps> = (
@@ -35,41 +38,53 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
)}
></NavBarItem>
<NavBarItem
id="incidents-nav-bar-item"
title="Incidents"
icon={IconProp.Alert}
exact={true}
route={RouteUtil.populateRouteParams(
props.isPreview
? (RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route)
: (RouteMap[PageMap.INCIDENT_LIST] as Route),
)}
></NavBarItem>
{props.showIncidentsOnStatusPage ? (
<NavBarItem
id="incidents-nav-bar-item"
title="Incidents"
icon={IconProp.Alert}
exact={true}
route={RouteUtil.populateRouteParams(
props.isPreview
? (RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route)
: (RouteMap[PageMap.INCIDENT_LIST] as Route),
)}
></NavBarItem>
) : (
<></>
)}
<NavBarItem
id="announcements-nav-bar-item"
title="Announcements"
icon={IconProp.Announcement}
exact={true}
route={RouteUtil.populateRouteParams(
props.isPreview
? (RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_LIST] as Route)
: (RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route),
)}
></NavBarItem>
{props.showAnnouncementsOnStatusPage ? (
<NavBarItem
id="announcements-nav-bar-item"
title="Announcements"
icon={IconProp.Announcement}
exact={true}
route={RouteUtil.populateRouteParams(
props.isPreview
? (RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_LIST] as Route)
: (RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route),
)}
></NavBarItem>
) : (
<></>
)}
<NavBarItem
id="scheduled-events-nav-bar-item"
title="Scheduled Events"
icon={IconProp.Clock}
exact={true}
route={RouteUtil.populateRouteParams(
props.isPreview
? (RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_LIST] as Route)
: (RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route),
)}
></NavBarItem>
{props.showScheduledMaintenanceEventsOnStatusPage ? (
<NavBarItem
id="scheduled-events-nav-bar-item"
title="Scheduled Events"
icon={IconProp.Clock}
exact={true}
route={RouteUtil.populateRouteParams(
props.isPreview
? (RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_LIST] as Route)
: (RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route),
)}
></NavBarItem>
) : (
<></>
)}
{props.enableEmailSubscribers || props.enableSMSSubscribers ? (
<NavBarItem

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.7.3-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -1,4 +1,4 @@
FROM public.ecr.aws/docker/library/node:21.7.3-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.2-alpine3.18
FROM public.ecr.aws/docker/library/node:23.8-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

View File

@@ -36,7 +36,9 @@ RunCron(
workflowStatus: WorkflowStatus.Error,
logs: `${
stalledWorkflowLog.logs
} \n ${OneUptimeDate.getCurrentDateAsFormattedString()}: Workflow was not picked up by the runner and has timed out.`,
} \n ${OneUptimeDate.getCurrentDateAsFormattedString({
showSeconds: true,
})}: Workflow was not picked up by the runner and has timed out.`,
},
props: {
isRoot: true,

Some files were not shown because too many files have changed in this diff Show More