@oneuptime/common 10.0.16 → 10.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/Server/EnvironmentConfig.ts +1 -47
  2. package/Server/Infrastructure/Queue.ts +0 -3
  3. package/Server/Services/ScheduledMaintenanceService.ts +2 -18
  4. package/Server/Services/StatusPageResourceService.ts +98 -1
  5. package/Server/Types/Markdown.ts +112 -7
  6. package/Server/Utils/VM/VMAPI.ts +3 -32
  7. package/Server/Utils/VM/VMRunner.ts +283 -23
  8. package/ServiceRoute.ts +0 -4
  9. package/Tests/Server/Utils/VM/VMAPI.test.ts +2 -17
  10. package/Types/Monitor/MonitorCriteriaInstance.ts +2 -1
  11. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +1 -1
  12. package/UI/Config.ts +1 -13
  13. package/build/dist/Server/EnvironmentConfig.js +1 -8
  14. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  15. package/build/dist/Server/Infrastructure/Queue.js +0 -3
  16. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  17. package/build/dist/Server/Services/ScheduledMaintenanceService.js +2 -16
  18. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  19. package/build/dist/Server/Services/StatusPageResourceService.js +89 -1
  20. package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
  21. package/build/dist/Server/Types/Markdown.js +88 -7
  22. package/build/dist/Server/Types/Markdown.js.map +1 -1
  23. package/build/dist/Server/Utils/VM/VMAPI.js +2 -17
  24. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  25. package/build/dist/Server/Utils/VM/VMRunner.js +215 -16
  26. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  27. package/build/dist/ServiceRoute.js +0 -1
  28. package/build/dist/ServiceRoute.js.map +1 -1
  29. package/build/dist/Tests/Server/Utils/VM/VMAPI.test.js +2 -15
  30. package/build/dist/Tests/Server/Utils/VM/VMAPI.test.js.map +1 -1
  31. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +2 -1
  32. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  33. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +1 -1
  34. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  35. package/build/dist/UI/Config.js +2 -5
  36. package/build/dist/UI/Config.js.map +1 -1
  37. package/package.json +1 -1
@@ -175,41 +175,19 @@ export const AppApiHostname: Hostname = Hostname.fromString(
175
175
  }`,
176
176
  );
177
177
 
178
- export const ProbeIngestHostname: Hostname = Hostname.fromString(
179
- `${process.env["SERVER_PROBE_INGEST_HOSTNAME"] || "localhost"}:${
180
- process.env["PROBE_INGEST_PORT"] || 80
181
- }`,
182
- );
183
-
184
178
  export const OpenTelemetryIngestHostname: Hostname = Hostname.fromString(
185
179
  `${process.env["SERVER_TELEMETRY_HOSTNAME"] || "localhost"}:${
186
180
  process.env["TELEMETRY_PORT"] || 80
187
181
  }`,
188
182
  );
189
183
 
190
- export const IncomingRequestIngestHostname: Hostname = Hostname.fromString(
191
- `${process.env["SERVER_INCOMING_REQUEST_INGEST_HOSTNAME"] || "localhost"}:${
192
- process.env["INCOMING_REQUEST_INGEST_PORT"] || 80
193
- }`,
194
- );
195
-
196
- export const IsolatedVMHostname: Hostname = Hostname.fromString(
197
- `${process.env["SERVER_ISOLATED_VM_HOSTNAME"] || "localhost"}:${
198
- process.env["ISOLATED_VM_PORT"] || 80
199
- }`,
200
- );
201
-
202
184
  export const WorkerHostname: Hostname = Hostname.fromString(
203
185
  `${process.env["SERVER_WORKER_HOSTNAME"] || "localhost"}:${
204
186
  process.env["WORKER_PORT"] || 80
205
187
  }`,
206
188
  );
207
189
 
208
- export const WorkflowHostname: Hostname = Hostname.fromString(
209
- `${process.env["SERVER_WORKFLOW_HOSTNAME"] || "localhost"}:${
210
- process.env["WORKFLOW_PORT"] || 80
211
- }`,
212
- );
190
+ export const WorkflowHostname: Hostname = WorkerHostname;
213
191
 
214
192
  export const HomeHostname: Hostname = Hostname.fromString(
215
193
  `${process.env["SERVER_HOME_HOSTNAME"] || "localhost"}:${
@@ -217,30 +195,6 @@ export const HomeHostname: Hostname = Hostname.fromString(
217
195
  }`,
218
196
  );
219
197
 
220
- export const AccountsHostname: Hostname = Hostname.fromString(
221
- `${process.env["SERVER_ACCOUNTS_HOSTNAME"] || "localhost"}:${
222
- process.env["ACCOUNTS_PORT"] || 80
223
- }`,
224
- );
225
-
226
- export const DashboardHostname: Hostname = Hostname.fromString(
227
- `${process.env["SERVER_DASHBOARD_HOSTNAME"] || "localhost"}:${
228
- process.env["DASHBOARD_PORT"] || 80
229
- }`,
230
- );
231
-
232
- export const AdminDashboardHostname: Hostname = Hostname.fromString(
233
- `${process.env["SERVER_ADMIN_DASHBOARD_HOSTNAME"] || "localhost"}:${
234
- process.env["ADMIN_DASHBOARD_PORT"] || 80
235
- }`,
236
- );
237
-
238
- export const DocsHostname: Hostname = Hostname.fromString(
239
- `${process.env["SERVER_DOCS_HOSTNAME"] || "localhost"}:${
240
- process.env["DOCS_PORT"] || 80
241
- }`,
242
- );
243
-
244
198
  export const Env: string = process.env["NODE_ENV"] || "production";
245
199
 
246
200
  // Redis does not require password.
@@ -14,9 +14,6 @@ export enum QueueName {
14
14
  Workflow = "Workflow",
15
15
  Worker = "Worker",
16
16
  Telemetry = "Telemetry",
17
- IncomingRequestIngest = "IncomingRequestIngest",
18
- ServerMonitorIngest = "ServerMonitorIngest",
19
- ProbeIngest = "ProbeIngest",
20
17
  }
21
18
 
22
19
  export type QueueJob = Job;
@@ -97,24 +97,8 @@ export class Service extends DatabaseService<Model> {
97
97
  let statusPageResources: Array<StatusPageResource> = [];
98
98
 
99
99
  if (event.monitors && event.monitors.length > 0) {
100
- statusPageResources = await StatusPageResourceService.findBy({
101
- query: {
102
- monitorId: QueryHelper.any(
103
- event.monitors
104
- .filter((m: Monitor) => {
105
- return m._id;
106
- })
107
- .map((m: Monitor) => {
108
- return new ObjectID(m._id!);
109
- }),
110
- ),
111
- },
112
- props: {
113
- isRoot: true,
114
- ignoreHooks: true,
115
- },
116
- skip: 0,
117
- limit: LIMIT_PER_PROJECT,
100
+ statusPageResources = await StatusPageResourceService.findByMonitors({
101
+ monitors: event.monitors,
118
102
  select: {
119
103
  _id: true,
120
104
  displayName: true,
@@ -3,21 +3,118 @@ import DeleteBy from "../Types/Database/DeleteBy";
3
3
  import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks";
4
4
  import Query from "../Types/Database/Query";
5
5
  import QueryHelper from "../Types/Database/QueryHelper";
6
+ import Select from "../Types/Database/Select";
6
7
  import UpdateBy from "../Types/Database/UpdateBy";
7
8
  import DatabaseService from "./DatabaseService";
9
+ import MonitorGroupResourceService from "./MonitorGroupResourceService";
8
10
  import SortOrder from "../../Types/BaseDatabase/SortOrder";
9
- import LIMIT_MAX from "../../Types/Database/LimitMax";
11
+ import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
10
12
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
11
13
  import BadDataException from "../../Types/Exception/BadDataException";
12
14
  import ObjectID from "../../Types/ObjectID";
13
15
  import PositiveNumber from "../../Types/PositiveNumber";
14
16
  import Model from "../../Models/DatabaseModels/StatusPageResource";
17
+ import Monitor from "../../Models/DatabaseModels/Monitor";
18
+ import MonitorGroupResource from "../../Models/DatabaseModels/MonitorGroupResource";
15
19
 
16
20
  export class Service extends DatabaseService<Model> {
17
21
  public constructor() {
18
22
  super(Model);
19
23
  }
20
24
 
25
+ @CaptureSpan()
26
+ public async findByMonitors(data: {
27
+ monitors?: Array<Monitor>;
28
+ monitorIds?: Array<ObjectID>;
29
+ select: Select<Model>;
30
+ }): Promise<Array<Model>> {
31
+ let resolvedMonitorIds: Array<ObjectID>;
32
+
33
+ if (data.monitorIds && data.monitorIds.length > 0) {
34
+ resolvedMonitorIds = data.monitorIds;
35
+ } else if (data.monitors && data.monitors.length > 0) {
36
+ resolvedMonitorIds = data.monitors
37
+ .filter((m: Monitor) => {
38
+ return m._id;
39
+ })
40
+ .map((m: Monitor) => {
41
+ return new ObjectID(m._id!);
42
+ });
43
+ } else {
44
+ return [];
45
+ }
46
+
47
+ if (resolvedMonitorIds.length === 0) {
48
+ return [];
49
+ }
50
+
51
+ // Find status page resources directly linked to monitors
52
+ const statusPageResources: Array<Model> = await this.findBy({
53
+ query: {
54
+ monitorId: QueryHelper.any(resolvedMonitorIds),
55
+ },
56
+ props: {
57
+ isRoot: true,
58
+ ignoreHooks: true,
59
+ },
60
+ skip: 0,
61
+ limit: LIMIT_PER_PROJECT,
62
+ select: data.select,
63
+ });
64
+
65
+ // Find monitor groups that contain the affected monitors
66
+ const monitorGroupResources: Array<MonitorGroupResource> =
67
+ await MonitorGroupResourceService.findBy({
68
+ query: {
69
+ monitorId: QueryHelper.any(resolvedMonitorIds),
70
+ },
71
+ props: {
72
+ isRoot: true,
73
+ ignoreHooks: true,
74
+ },
75
+ select: {
76
+ monitorGroupId: true,
77
+ },
78
+ skip: 0,
79
+ limit: LIMIT_PER_PROJECT,
80
+ });
81
+
82
+ const monitorGroupIds: Array<ObjectID> = monitorGroupResources
83
+ .map((r: MonitorGroupResource) => {
84
+ return r.monitorGroupId!;
85
+ })
86
+ .filter((id: ObjectID) => {
87
+ return Boolean(id);
88
+ });
89
+
90
+ if (monitorGroupIds.length > 0) {
91
+ const groupStatusPageResources: Array<Model> = await this.findBy({
92
+ query: {
93
+ monitorGroupId: QueryHelper.any(monitorGroupIds),
94
+ },
95
+ props: {
96
+ isRoot: true,
97
+ ignoreHooks: true,
98
+ },
99
+ skip: 0,
100
+ limit: LIMIT_PER_PROJECT,
101
+ select: data.select,
102
+ });
103
+
104
+ // Merge and deduplicate
105
+ for (const resource of groupStatusPageResources) {
106
+ const alreadyExists: boolean = statusPageResources.some((r: Model) => {
107
+ return r._id === resource._id;
108
+ });
109
+ if (!alreadyExists) {
110
+ statusPageResources.push(resource);
111
+ }
112
+ }
113
+ }
114
+
115
+ return statusPageResources;
116
+ }
117
+
21
118
  @CaptureSpan()
22
119
  protected override async onBeforeCreate(
23
120
  createBy: CreateBy<Model>,
@@ -124,6 +124,17 @@ export default class Markdown {
124
124
  return renderer;
125
125
  }
126
126
 
127
+ private static slugify(text: string): string {
128
+ return text
129
+ .toLowerCase()
130
+ .replace(/<[^>]*>/g, "")
131
+ .replace(/&[^;]+;/g, "")
132
+ .replace(/[^\w\s-]/g, "")
133
+ .replace(/\s+/g, "-")
134
+ .replace(/-+/g, "-")
135
+ .replace(/^-|-$/g, "");
136
+ }
137
+
127
138
  private static getDocsRenderer(): Renderer {
128
139
  if (this.docsRenderer !== null) {
129
140
  return this.docsRenderer;
@@ -136,6 +147,75 @@ export default class Markdown {
136
147
  };
137
148
 
138
149
  renderer.blockquote = function (quote) {
150
+ const calloutMatch: RegExpMatchArray | null = quote.match(
151
+ /<p[^>]*>\s*<strong>(Note|Warning|Tip|Danger|Info|Caution):?<\/strong>/i,
152
+ );
153
+
154
+ if (calloutMatch) {
155
+ const type: string = calloutMatch[1]!.toLowerCase();
156
+ const configMap: Record<
157
+ string,
158
+ { border: string; bg: string; icon: string; label: string }
159
+ > = {
160
+ note: {
161
+ border: "border-blue-400",
162
+ bg: "bg-blue-50",
163
+ icon: `<svg class="h-5 w-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>`,
164
+ label: "Note",
165
+ },
166
+ info: {
167
+ border: "border-blue-400",
168
+ bg: "bg-blue-50",
169
+ icon: `<svg class="h-5 w-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>`,
170
+ label: "Info",
171
+ },
172
+ tip: {
173
+ border: "border-green-400",
174
+ bg: "bg-green-50",
175
+ icon: `<svg class="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>`,
176
+ label: "Tip",
177
+ },
178
+ warning: {
179
+ border: "border-yellow-400",
180
+ bg: "bg-yellow-50",
181
+ icon: `<svg class="h-5 w-5 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>`,
182
+ label: "Warning",
183
+ },
184
+ caution: {
185
+ border: "border-yellow-400",
186
+ bg: "bg-yellow-50",
187
+ icon: `<svg class="h-5 w-5 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>`,
188
+ label: "Caution",
189
+ },
190
+ danger: {
191
+ border: "border-red-400",
192
+ bg: "bg-red-50",
193
+ icon: `<svg class="h-5 w-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>`,
194
+ label: "Danger",
195
+ },
196
+ };
197
+
198
+ const config: {
199
+ border: string;
200
+ bg: string;
201
+ icon: string;
202
+ label: string;
203
+ } = configMap[type] || configMap["note"]!;
204
+
205
+ const content: string = quote.replace(
206
+ /<p[^>]*>\s*<strong>(Note|Warning|Tip|Danger|Info|Caution):?<\/strong>\s*/i,
207
+ '<p class="mt-2 mb-2 leading-8 text-gray-600">',
208
+ );
209
+
210
+ return `<div class="callout callout-${type} my-4 rounded-r-lg border-l-4 ${config.border} ${config.bg} p-4">
211
+ <div class="flex items-center gap-2 mb-1">
212
+ ${config.icon}
213
+ <span class="text-sm font-semibold text-gray-700">${config.label}</span>
214
+ </div>
215
+ <div class="leading-7 text-gray-600 text-sm">${content}</div>
216
+ </div>`;
217
+ }
218
+
139
219
  return `<blockquote class="p-4 pt-1 pb-1 my-4 border-s-4 border-indigo-500">
140
220
  <div class="leading-8 text-gray-600">${quote}</div>
141
221
  </blockquote>`;
@@ -147,25 +227,50 @@ export default class Markdown {
147
227
 
148
228
  renderer.code = function (code, language) {
149
229
  if (language === "mermaid") {
150
- return `<div class="mermaid">${code}</div>`;
230
+ return `<div class="mermaid-wrapper overflow-x-auto my-6"><div class="mermaid">${code}</div></div>`;
151
231
  }
152
232
  const escaped: string = Markdown.escapeHtml(code);
153
233
  return `<pre><code class="language-${language}">${escaped}</code></pre>`;
154
234
  };
155
235
 
156
236
  renderer.heading = function (text, level) {
237
+ const slug: string = Markdown.slugify(text);
238
+ const anchor: string =
239
+ level === 2 || level === 3
240
+ ? `<a href="#${slug}" class="anchor-link" aria-hidden="true">#</a>`
241
+ : "";
242
+
157
243
  if (level === 1) {
158
- return `<h1 class="my-5 mt-8 text-4xl font-bold tracking-tight text-gray-800">${text}</h1>`;
244
+ return `<h1 id="${slug}" class="my-5 mt-8 text-4xl font-bold tracking-tight text-gray-800">${text}</h1>`;
159
245
  } else if (level === 2) {
160
- return `<h2 class="my-5 mt-8 text-3xl font-bold tracking-tight text-gray-800">${text}</h2>`;
246
+ return `<h2 id="${slug}" class="group my-5 mt-8 text-3xl font-bold tracking-tight text-gray-800">${text} ${anchor}</h2>`;
161
247
  } else if (level === 3) {
162
- return `<h3 class="my-5 mt-8 text-2xl font-bold tracking-tight text-gray-800">${text}</h3>`;
248
+ return `<h3 id="${slug}" class="group my-5 mt-8 text-2xl font-bold tracking-tight text-gray-800">${text} ${anchor}</h3>`;
163
249
  } else if (level === 4) {
164
- return `<h4 class="my-5 mt-8 text-xl font-bold tracking-tight text-gray-800">${text}</h4>`;
250
+ return `<h4 id="${slug}" class="my-5 mt-8 text-xl font-bold tracking-tight text-gray-800">${text}</h4>`;
165
251
  } else if (level === 5) {
166
- return `<h5 class="my-5 mt-8 text-lg font-bold tracking-tight text-gray-800">${text}</h5>`;
252
+ return `<h5 id="${slug}" class="my-5 mt-8 text-lg font-bold tracking-tight text-gray-800">${text}</h5>`;
167
253
  }
168
- return `<h6 class="my-5 tracking-tight font-bold text-gray-800">${text}</h6>`;
254
+ return `<h6 id="${slug}" class="my-5 tracking-tight font-bold text-gray-800">${text}</h6>`;
255
+ };
256
+
257
+ renderer.table = function (header, body) {
258
+ return `<div class="docs-table-wrapper overflow-x-auto my-6 rounded-lg border border-slate-200">
259
+ <table class="min-w-full text-sm text-left">${header}${body}</table>
260
+ </div>`;
261
+ };
262
+
263
+ renderer.tablerow = function (content) {
264
+ return `<tr class="border-b border-slate-200 last:border-b-0 hover:bg-slate-50/50 transition-colors">${content}</tr>`;
265
+ };
266
+
267
+ renderer.tablecell = function (content, flags) {
268
+ const tag: string = flags.header ? "th" : "td";
269
+ const align: string = flags.align ? ` text-${flags.align}` : "";
270
+ const headerClass: string = flags.header
271
+ ? " font-semibold text-slate-900 bg-slate-50"
272
+ : " text-slate-600";
273
+ return `<${tag} class="px-4 py-2.5${align}${headerClass}">${content}</${tag}>`;
169
274
  };
170
275
 
171
276
  // Inline code
@@ -1,15 +1,8 @@
1
- import { IsolatedVMHostname } from "../../../Server/EnvironmentConfig";
2
- import ClusterKeyAuthorization from "../../Middleware/ClusterKeyAuthorization";
3
- import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
4
- import HTTPResponse from "../../../Types/API/HTTPResponse";
5
- import Protocol from "../../../Types/API/Protocol";
6
- import Route from "../../../Types/API/Route";
7
- import URL from "../../../Types/API/URL";
8
1
  import ReturnResult from "../../../Types/IsolatedVM/ReturnResult";
9
2
  import { JSONObject, JSONValue } from "../../../Types/JSON";
10
- import API from "../../../Utils/API";
11
3
  import logger from "../Logger";
12
4
  import CaptureSpan from "../Telemetry/CaptureSpan";
5
+ import VMRunner from "./VMRunner";
13
6
 
14
7
  export default class VMUtil {
15
8
  @CaptureSpan()
@@ -17,32 +10,10 @@ export default class VMUtil {
17
10
  code: string;
18
11
  options: {
19
12
  args?: JSONObject | undefined;
20
- timeout?: number | undefined;
13
+ timeout?: number;
21
14
  };
22
15
  }): Promise<ReturnResult> {
23
- const returnResultHttpResponse:
24
- | HTTPErrorResponse
25
- | HTTPResponse<JSONObject> = await API.post<JSONObject>({
26
- url: new URL(
27
- Protocol.HTTP,
28
- IsolatedVMHostname,
29
- new Route("/isolated-vm/run-code"),
30
- ),
31
- data: {
32
- ...data,
33
- },
34
- headers: {
35
- ...ClusterKeyAuthorization.getClusterKeyHeaders(),
36
- },
37
- });
38
-
39
- if (returnResultHttpResponse instanceof HTTPErrorResponse) {
40
- throw returnResultHttpResponse;
41
- }
42
-
43
- const returnResult: ReturnResult = returnResultHttpResponse.data as any;
44
-
45
- return returnResult;
16
+ return VMRunner.runCodeInSandbox(data);
46
17
  }
47
18
 
48
19
  @CaptureSpan()