@oneuptime/common 11.0.10 → 11.0.13
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.
- package/Models/DatabaseModels/CephCluster.ts +134 -0
- package/Models/DatabaseModels/CloudResource.ts +134 -0
- package/Models/DatabaseModels/DockerHost.ts +134 -0
- package/Models/DatabaseModels/DockerSwarmCluster.ts +134 -0
- package/Models/DatabaseModels/Host.ts +134 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +134 -0
- package/Models/DatabaseModels/PodmanHost.ts +134 -0
- package/Models/DatabaseModels/ProxmoxCluster.ts +134 -0
- package/Models/DatabaseModels/RumApplication.ts +134 -0
- package/Models/DatabaseModels/ServerlessFunction.ts +134 -0
- package/Models/DatabaseModels/Service.ts +134 -0
- package/Models/DatabaseModels/StatusPage.ts +0 -42
- package/README.md +1 -1
- package/Scripts/generate-service-worker.js +46 -36
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +4 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782600000000-AddArchiveToResources.ts +331 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782700000000-RemoveDeprecatedEnableSubscribersFromStatusPage.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Readme.md +3 -3
- package/Server/Infrastructure/Queue.ts +122 -0
- package/Server/Services/AIAgentService.ts +9 -4
- package/Server/Services/AnalyticsDatabaseService.ts +89 -56
- package/Server/Services/CephClusterService.ts +6 -4
- package/Server/Services/CloudResourceService.ts +6 -4
- package/Server/Services/DatabaseService.ts +126 -0
- package/Server/Services/DockerHostService.ts +6 -4
- package/Server/Services/DockerSwarmClusterService.ts +6 -4
- package/Server/Services/HostService.ts +6 -4
- package/Server/Services/KubernetesClusterService.ts +6 -4
- package/Server/Services/PodmanHostService.ts +6 -4
- package/Server/Services/ProbeService.ts +10 -4
- package/Server/Services/ProxmoxClusterService.ts +6 -4
- package/Server/Services/RumApplicationService.ts +6 -4
- package/Server/Services/ServerlessFunctionService.ts +6 -4
- package/Server/Services/ServiceService.ts +17 -4
- package/Server/Types/Markdown.ts +103 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +5 -2
- package/Server/Utils/CodeRepository/CodeRepository.ts +78 -0
- package/Server/Utils/Execute.ts +6 -0
- package/Server/Utils/Logger.ts +66 -0
- package/Server/Utils/Monitor/MonitorResource.ts +24 -10
- package/Server/Utils/Telemetry/EntityRegistry.ts +10 -5
- package/Server/Utils/Workspace/Slack/app-manifest.json +86 -92
- package/Tests/Server/Services/UpdateColumnsByIdWithoutHooks.test.ts +154 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +87 -9
- package/Tests/Types/DiskSize.test.ts +49 -0
- package/Types/Dashboard/DashboardComponents/DashboardTableComponent.ts +11 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/Monitor/MonitorType.ts +15 -31
- package/Types/Monitor/schema.json +32 -35
- package/UI/Components/BulkUpdate/BulkArchiveActions.tsx +150 -0
- package/UI/Components/Icon/Icon.tsx +42 -126
- package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -5
- package/UI/esbuild-config.js +136 -110
- package/Utils/ClientAPI/README.md +1 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js +136 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResource.js +136 -0
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +136 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +136 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +136 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +136 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanHost.js +136 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +136 -0
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +136 -0
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js +136 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +136 -0
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +0 -44
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +4 -2
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782600000000-AddArchiveToResources.js +120 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782600000000-AddArchiveToResources.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782700000000-RemoveDeprecatedEnableSubscribersFromStatusPage.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782700000000-RemoveDeprecatedEnableSubscribersFromStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +63 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/AIAgentService.js +9 -4
- package/build/dist/Server/Services/AIAgentService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +52 -17
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +6 -4
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +6 -4
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +101 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +6 -4
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +6 -4
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +6 -4
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +6 -4
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +6 -4
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +10 -4
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +6 -4
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +6 -4
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +6 -4
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +17 -4
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +87 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +3 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/CodeRepository.js +60 -0
- package/build/dist/Server/Utils/CodeRepository/CodeRepository.js.map +1 -1
- package/build/dist/Server/Utils/Execute.js +1 -3
- package/build/dist/Server/Utils/Execute.js.map +1 -1
- package/build/dist/Server/Utils/Logger.js +49 -1
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +24 -10
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +10 -5
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +3 -9
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardTableComponent.js.map +1 -1
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +15 -31
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js +96 -0
- package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js.map +1 -0
- package/build/dist/UI/Components/Icon/Icon.js +17 -41
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -3
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/jest.config.json +47 -66
- package/package.json +1 -1
- package/tsconfig.json +109 -113
|
@@ -193,12 +193,14 @@ export class Service extends DatabaseService<Model> {
|
|
|
193
193
|
data.agentVersion = extra.agentVersion;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
|
|
196
|
+
/*
|
|
197
|
+
* Heartbeat write: a single-statement UPDATE with no hooks and no
|
|
198
|
+
* `version` bump, avoiding the hot-row Postgres lock convoy that the
|
|
199
|
+
* full updateOneById pipeline causes. See ServiceService.updateLastSeen.
|
|
200
|
+
*/
|
|
201
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
197
202
|
id: clusterId,
|
|
198
203
|
data: data,
|
|
199
|
-
props: {
|
|
200
|
-
isRoot: true,
|
|
201
|
-
},
|
|
202
204
|
});
|
|
203
205
|
}
|
|
204
206
|
|
|
@@ -236,12 +236,14 @@ export class Service extends DatabaseService<Model> {
|
|
|
236
236
|
data.agentVersion = extra.agentVersion;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
/*
|
|
240
|
+
* Heartbeat write: a single-statement UPDATE with no hooks and no
|
|
241
|
+
* `version` bump, avoiding the hot-row Postgres lock convoy that the
|
|
242
|
+
* full updateOneById pipeline causes. See ServiceService.updateLastSeen.
|
|
243
|
+
*/
|
|
244
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
240
245
|
id: hostId,
|
|
241
246
|
data: data,
|
|
242
|
-
props: {
|
|
243
|
-
isRoot: true,
|
|
244
|
-
},
|
|
245
247
|
});
|
|
246
248
|
}
|
|
247
249
|
|
|
@@ -120,14 +120,20 @@ export class Service extends DatabaseService<Model> {
|
|
|
120
120
|
|
|
121
121
|
const now: Date = OneUptimeDate.getCurrentDate();
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
/*
|
|
124
|
+
* Heartbeat write: a single-statement UPDATE with no hooks and no
|
|
125
|
+
* `version` bump. This fires from the probe-auth middleware on every
|
|
126
|
+
* authenticated probe request, so it must not head the hot-row Postgres
|
|
127
|
+
* lock convoy the full updateOneById pipeline causes. A lastAlive-only
|
|
128
|
+
* write never sets connectionStatus, so the connection-status-change
|
|
129
|
+
* hooks (onBeforeUpdate/onUpdateSuccess) are inert here. See
|
|
130
|
+
* ServiceService.updateLastSeen.
|
|
131
|
+
*/
|
|
132
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
124
133
|
id: probeId,
|
|
125
134
|
data: {
|
|
126
135
|
lastAlive: now,
|
|
127
136
|
},
|
|
128
|
-
props: {
|
|
129
|
-
isRoot: true,
|
|
130
|
-
},
|
|
131
137
|
});
|
|
132
138
|
}
|
|
133
139
|
|
|
@@ -250,12 +250,14 @@ export class Service extends DatabaseService<Model> {
|
|
|
250
250
|
data.guestsWithoutBackupCount = extra.guestsWithoutBackupCount;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
/*
|
|
254
|
+
* Heartbeat write: a single-statement UPDATE with no hooks and no
|
|
255
|
+
* `version` bump, avoiding the hot-row Postgres lock convoy that the
|
|
256
|
+
* full updateOneById pipeline causes. See ServiceService.updateLastSeen.
|
|
257
|
+
*/
|
|
258
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
254
259
|
id: clusterId,
|
|
255
260
|
data: data,
|
|
256
|
-
props: {
|
|
257
|
-
isRoot: true,
|
|
258
|
-
},
|
|
259
261
|
});
|
|
260
262
|
}
|
|
261
263
|
|
|
@@ -185,12 +185,14 @@ export class Service extends DatabaseService<Model> {
|
|
|
185
185
|
data.sdkLanguage = extra.sdkLanguage;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
/*
|
|
189
|
+
* Heartbeat write: a single-statement UPDATE with no hooks and no
|
|
190
|
+
* `version` bump, avoiding the hot-row Postgres lock convoy that the
|
|
191
|
+
* full updateOneById pipeline causes. See ServiceService.updateLastSeen.
|
|
192
|
+
*/
|
|
193
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
189
194
|
id: rumApplicationId,
|
|
190
195
|
data: data,
|
|
191
|
-
props: {
|
|
192
|
-
isRoot: true,
|
|
193
|
-
},
|
|
194
196
|
});
|
|
195
197
|
}
|
|
196
198
|
|
|
@@ -229,12 +229,14 @@ export class Service extends DatabaseService<Model> {
|
|
|
229
229
|
data.runtimeVersion = extra.runtimeVersion;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
|
|
232
|
+
/*
|
|
233
|
+
* Heartbeat write: a single-statement UPDATE with no hooks and no
|
|
234
|
+
* `version` bump, avoiding the hot-row Postgres lock convoy that the
|
|
235
|
+
* full updateOneById pipeline causes. See ServiceService.updateLastSeen.
|
|
236
|
+
*/
|
|
237
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
233
238
|
id: serverlessFunctionId,
|
|
234
239
|
data: data,
|
|
235
|
-
props: {
|
|
236
|
-
isRoot: true,
|
|
237
|
-
},
|
|
238
240
|
});
|
|
239
241
|
}
|
|
240
242
|
|
|
@@ -198,12 +198,25 @@ export class Service extends DatabaseService<Model> {
|
|
|
198
198
|
data.cloudAccountId = extra.cloudAccountId;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
/*
|
|
202
|
+
* Heartbeat write. This is the single hottest UPDATE in the system —
|
|
203
|
+
* every telemetry batch for a service re-resolves the same serviceId —
|
|
204
|
+
* so it must NOT go through the full updateOneById pipeline (permission
|
|
205
|
+
* SELECT -> _findBy SELECT -> save() with `version = version + 1` ->
|
|
206
|
+
* workflow/realtime/audit hooks). That pipeline holds the Service row's
|
|
207
|
+
* write lock across several round trips and, when an event loop stalls
|
|
208
|
+
* mid-`save()` transaction, leaves it open as the head of a lock convoy
|
|
209
|
+
* that starves the Postgres connection pool. lastSeenAt is an internal
|
|
210
|
+
* liveness timestamp with no onBeforeUpdate/onUpdateSuccess hooks on this
|
|
211
|
+
* model (connected / disconnected status is derived from it at read
|
|
212
|
+
* time), so a bare single-statement UPDATE is both correct and far
|
|
213
|
+
* cheaper. This also intentionally drops the per-update workflow trigger
|
|
214
|
+
* Service's @EnableWorkflow({ update: true }) fired on every heartbeat —
|
|
215
|
+
* a liveness ping should not trigger user workflows.
|
|
216
|
+
*/
|
|
217
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
202
218
|
id: serviceId,
|
|
203
219
|
data: data,
|
|
204
|
-
props: {
|
|
205
|
-
isRoot: true,
|
|
206
|
-
},
|
|
207
220
|
});
|
|
208
221
|
}
|
|
209
222
|
|
package/Server/Types/Markdown.ts
CHANGED
|
@@ -7,6 +7,8 @@ export enum MarkdownContentType {
|
|
|
7
7
|
Docs,
|
|
8
8
|
Blog,
|
|
9
9
|
Email,
|
|
10
|
+
// Compact rendering tuned for small surfaces (e.g. the blog validation modal).
|
|
11
|
+
BlogValidation,
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export default class Markdown {
|
|
@@ -73,6 +75,7 @@ export default class Markdown {
|
|
|
73
75
|
private static blogRenderer: Renderer | null = null;
|
|
74
76
|
private static docsRenderer: Renderer | null = null;
|
|
75
77
|
private static emailRenderer: Renderer | null = null;
|
|
78
|
+
private static blogValidationRenderer: Renderer | null = null;
|
|
76
79
|
|
|
77
80
|
@CaptureSpan()
|
|
78
81
|
public static async convertToHTML(
|
|
@@ -93,6 +96,10 @@ export default class Markdown {
|
|
|
93
96
|
renderer = this.getEmailRenderer();
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
if (contentType === MarkdownContentType.BlogValidation) {
|
|
100
|
+
renderer = this.getBlogValidationRenderer();
|
|
101
|
+
}
|
|
102
|
+
|
|
96
103
|
/*
|
|
97
104
|
* Escape raw HTML tokens in the markdown source to prevent XSS.
|
|
98
105
|
* This only affects raw HTML that users type directly (e.g. <img onerror=...>),
|
|
@@ -132,6 +139,102 @@ export default class Markdown {
|
|
|
132
139
|
return renderer;
|
|
133
140
|
}
|
|
134
141
|
|
|
142
|
+
/*
|
|
143
|
+
* Compact renderer for small surfaces like the blog validation modal. Smaller,
|
|
144
|
+
* quieter headings (no oversized doc titles, no permalink anchors), tight
|
|
145
|
+
* paragraph/list rhythm, and subtle code styling so a long report stays
|
|
146
|
+
* readable in a constrained, scrollable panel.
|
|
147
|
+
*/
|
|
148
|
+
private static getBlogValidationRenderer(): Renderer {
|
|
149
|
+
if (this.blogValidationRenderer !== null) {
|
|
150
|
+
return this.blogValidationRenderer;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const renderer: Renderer = new Renderer();
|
|
154
|
+
|
|
155
|
+
renderer.heading = function (text, level) {
|
|
156
|
+
if (level <= 2) {
|
|
157
|
+
return `<h3 class="mt-7 mb-3 pt-6 border-t border-gray-100 first:mt-0 first:pt-0 first:border-t-0 text-[0.7rem] font-semibold uppercase tracking-wider text-emerald-700">${text}</h3>`;
|
|
158
|
+
}
|
|
159
|
+
if (level === 3) {
|
|
160
|
+
return `<h4 class="mt-5 mb-1.5 text-sm font-semibold text-gray-900">${text}</h4>`;
|
|
161
|
+
}
|
|
162
|
+
return `<h5 class="mt-4 mb-1 text-sm font-semibold text-gray-700">${text}</h5>`;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
renderer.paragraph = function (text) {
|
|
166
|
+
return `<p class="my-2.5 text-sm leading-relaxed text-gray-600">${text}</p>`;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
renderer.list = function (body, ordered, start) {
|
|
170
|
+
const tag: string = ordered ? "ol" : "ul";
|
|
171
|
+
const cls: string = ordered
|
|
172
|
+
? "list-decimal pl-5 my-2 space-y-1.5 text-sm text-gray-600 marker:text-gray-400"
|
|
173
|
+
: "list-disc pl-5 my-2 space-y-1.5 text-sm text-gray-600 marker:text-gray-300";
|
|
174
|
+
const startAttr: string =
|
|
175
|
+
ordered && start !== 1 ? ` start="${start}"` : "";
|
|
176
|
+
return `<${tag}${startAttr} class="${cls}">${body}</${tag}>`;
|
|
177
|
+
};
|
|
178
|
+
renderer.listitem = function (text) {
|
|
179
|
+
return `<li class="leading-relaxed pl-1">${text}</li>`;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
renderer.strong = function (text) {
|
|
183
|
+
return `<strong class="font-semibold text-gray-800">${text}</strong>`;
|
|
184
|
+
};
|
|
185
|
+
renderer.em = function (text) {
|
|
186
|
+
return `<em class="italic">${text}</em>`;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
renderer.blockquote = function (quote) {
|
|
190
|
+
return `<blockquote class="my-3 border-s-2 border-emerald-200 ps-3 text-sm text-gray-600">${quote}</blockquote>`;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
renderer.hr = function () {
|
|
194
|
+
return '<hr class="my-5 border-t border-gray-100" />';
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
renderer.codespan = function (code) {
|
|
198
|
+
const escaped: string = Markdown.escapeHtml(code);
|
|
199
|
+
return `<code class="rounded bg-gray-100 px-1.5 py-0.5 text-[0.8125rem] text-gray-700 font-mono break-words">${escaped}</code>`;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
renderer.code = function (code, language) {
|
|
203
|
+
const escaped: string = Markdown.escapeHtml(code);
|
|
204
|
+
return `<pre class="my-3 overflow-x-auto rounded-lg bg-gray-50 border border-gray-100 p-3 text-xs leading-relaxed"><code class="language-${language} font-mono text-gray-700">${escaped}</code></pre>`;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
renderer.table = function (header, body) {
|
|
208
|
+
return `<div class="my-4 overflow-x-auto rounded-lg border border-gray-100"><table class="min-w-full text-sm text-left">${header}${body}</table></div>`;
|
|
209
|
+
};
|
|
210
|
+
renderer.tablerow = function (content) {
|
|
211
|
+
return `<tr class="border-b border-gray-100 last:border-b-0">${content}</tr>`;
|
|
212
|
+
};
|
|
213
|
+
renderer.tablecell = function (content, flags) {
|
|
214
|
+
const tag: string = flags.header ? "th" : "td";
|
|
215
|
+
const align: string = flags.align ? ` text-${flags.align}` : "";
|
|
216
|
+
const headerClass: string = flags.header
|
|
217
|
+
? " font-semibold text-gray-900 bg-gray-50"
|
|
218
|
+
: " text-gray-600";
|
|
219
|
+
return `<${tag} class="px-3 py-2${align}${headerClass}">${content}</${tag}>`;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
renderer.link = function (href, _title, text) {
|
|
223
|
+
if (!href) {
|
|
224
|
+
return text as string;
|
|
225
|
+
}
|
|
226
|
+
const isExternal: boolean = href.startsWith("http");
|
|
227
|
+
const rel: string = isExternal
|
|
228
|
+
? ' target="_blank" rel="noopener noreferrer"'
|
|
229
|
+
: "";
|
|
230
|
+
return `<a href="${href}"${rel} class="text-emerald-700 font-medium underline decoration-emerald-200 underline-offset-2 hover:decoration-emerald-400 break-words">${text}</a>`;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
this.blogValidationRenderer = renderer;
|
|
234
|
+
|
|
235
|
+
return renderer;
|
|
236
|
+
}
|
|
237
|
+
|
|
135
238
|
private static slugify(text: string): string {
|
|
136
239
|
return text
|
|
137
240
|
.toLowerCase()
|
|
@@ -87,7 +87,7 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
87
87
|
.append(setStatement)
|
|
88
88
|
.append(
|
|
89
89
|
SQL`
|
|
90
|
-
WHERE TRUE
|
|
90
|
+
WHERE TRUE `,
|
|
91
91
|
)
|
|
92
92
|
.append(whereStatement);
|
|
93
93
|
/* eslint-enable prettier/prettier */
|
|
@@ -193,8 +193,11 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
193
193
|
return record;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
private escapeStringLiteral(raw: string): string {
|
|
196
|
+
private escapeStringLiteral(raw: string | undefined | null): string {
|
|
197
197
|
// escape String literal based on https://clickhouse.com/docs/en/sql-reference/syntax#string
|
|
198
|
+
if (raw === undefined || raw === null) {
|
|
199
|
+
return "''";
|
|
200
|
+
}
|
|
198
201
|
return `'${raw.replace(/'|\\/g, "\\$&")}'`;
|
|
199
202
|
}
|
|
200
203
|
|
|
@@ -385,6 +385,84 @@ export default class CodeRepositoryUtil {
|
|
|
385
385
|
return hash.trim();
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Returns the author (name + email) and the list of files changed for every
|
|
390
|
+
* non-merge commit in the repository, newest first. Optionally limited to a
|
|
391
|
+
* pathspec (e.g. "posts"). Used to derive per-directory contributors.
|
|
392
|
+
*
|
|
393
|
+
* Note: a shallow clone (`--depth 1`) only has the latest commit, so this
|
|
394
|
+
* returns a single contributor unless the repo was cloned with history.
|
|
395
|
+
*/
|
|
396
|
+
@CaptureSpan()
|
|
397
|
+
public static async getCommitAuthorsWithFiles(data: {
|
|
398
|
+
repoPath: string;
|
|
399
|
+
path?: string | undefined;
|
|
400
|
+
}): Promise<
|
|
401
|
+
Array<{
|
|
402
|
+
authorName: string;
|
|
403
|
+
authorEmail: string;
|
|
404
|
+
files: Array<string>;
|
|
405
|
+
}>
|
|
406
|
+
> {
|
|
407
|
+
/*
|
|
408
|
+
* ASCII record (0x1e) / unit (0x1f) separators never occur in author names,
|
|
409
|
+
* emails or file paths, so they delimit commits and fields unambiguously.
|
|
410
|
+
*/
|
|
411
|
+
const recordSeparator: string = "\x1e";
|
|
412
|
+
const unitSeparator: string = "\x1f";
|
|
413
|
+
|
|
414
|
+
const args: Array<string> = [
|
|
415
|
+
"log",
|
|
416
|
+
"--no-merges",
|
|
417
|
+
`--pretty=format:${recordSeparator}%an${unitSeparator}%ae`,
|
|
418
|
+
"--name-only",
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
if (data.path) {
|
|
422
|
+
args.push("--", data.path);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const stdout: string = await Execute.executeCommandFile({
|
|
426
|
+
command: "git",
|
|
427
|
+
args,
|
|
428
|
+
cwd: path.resolve(data.repoPath),
|
|
429
|
+
// git log over a full repo can easily exceed the 1 MB default.
|
|
430
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const commits: Array<{
|
|
434
|
+
authorName: string;
|
|
435
|
+
authorEmail: string;
|
|
436
|
+
files: Array<string>;
|
|
437
|
+
}> = [];
|
|
438
|
+
|
|
439
|
+
for (const record of stdout.split(recordSeparator)) {
|
|
440
|
+
if (!record.trim()) {
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const lines: Array<string> = record.split("\n");
|
|
445
|
+
const header: string = lines.shift() || "";
|
|
446
|
+
const [authorName, authorEmail] = header.split(unitSeparator);
|
|
447
|
+
|
|
448
|
+
const files: Array<string> = lines
|
|
449
|
+
.map((line: string) => {
|
|
450
|
+
return line.trim();
|
|
451
|
+
})
|
|
452
|
+
.filter((line: string) => {
|
|
453
|
+
return line.length > 0;
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
commits.push({
|
|
457
|
+
authorName: (authorName || "").trim(),
|
|
458
|
+
authorEmail: (authorEmail || "").trim(),
|
|
459
|
+
files,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return commits;
|
|
464
|
+
}
|
|
465
|
+
|
|
388
466
|
@CaptureSpan()
|
|
389
467
|
public static async listFilesInDirectory(data: {
|
|
390
468
|
directoryPath: string;
|
package/Server/Utils/Execute.ts
CHANGED
|
@@ -54,6 +54,11 @@ export default class Execute {
|
|
|
54
54
|
command: string;
|
|
55
55
|
args: Array<string>;
|
|
56
56
|
cwd: string;
|
|
57
|
+
/*
|
|
58
|
+
* Override the child_process default stdout/stderr cap (1 MB). Commands that
|
|
59
|
+
* can emit large output (e.g. `git log` over a whole repo) should raise this.
|
|
60
|
+
*/
|
|
61
|
+
maxBuffer?: number | undefined;
|
|
57
62
|
}): Promise<string> {
|
|
58
63
|
return new Promise(
|
|
59
64
|
(
|
|
@@ -65,6 +70,7 @@ export default class Execute {
|
|
|
65
70
|
data.args,
|
|
66
71
|
{
|
|
67
72
|
cwd: data.cwd,
|
|
73
|
+
...(data.maxBuffer ? { maxBuffer: data.maxBuffer } : {}),
|
|
68
74
|
},
|
|
69
75
|
(err: ExecException | null, stdout: string, stderr: string) => {
|
|
70
76
|
if (err) {
|
package/Server/Utils/Logger.ts
CHANGED
|
@@ -8,6 +8,12 @@ import ConfigLogLevel from "../Types/ConfigLogLevel";
|
|
|
8
8
|
|
|
9
9
|
export type LogBody = string | JSONObject | Exception | Error | unknown;
|
|
10
10
|
|
|
11
|
+
export interface RecentLogEntry {
|
|
12
|
+
time: string;
|
|
13
|
+
level: string;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
export interface LogAttributes {
|
|
12
18
|
userId?: string | undefined;
|
|
13
19
|
projectId?: string | undefined;
|
|
@@ -50,6 +56,56 @@ export function getLogAttributesFromRequest(
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
export default class logger {
|
|
59
|
+
/*
|
|
60
|
+
* In-memory ring buffer of the most recent log lines this process emitted.
|
|
61
|
+
* The app writes logs to stdout only (no on-disk file), so this is the one
|
|
62
|
+
* way the master-admin support bundle / health dashboard can surface this
|
|
63
|
+
* instance's own recent logs for debugging — there is no path to the
|
|
64
|
+
* container's stdout or to other containers' logs from inside the process.
|
|
65
|
+
*/
|
|
66
|
+
private static recentLogs: Array<RecentLogEntry> = [];
|
|
67
|
+
private static readonly maxRecentLogs: number = 1000;
|
|
68
|
+
private static readonly recentLogTrimSlack: number = 256;
|
|
69
|
+
private static readonly maxRecentLogMessageLength: number = 4000;
|
|
70
|
+
|
|
71
|
+
private static record(level: string, body: LogBody): void {
|
|
72
|
+
try {
|
|
73
|
+
const message: string = this.serializeLogBody(body);
|
|
74
|
+
|
|
75
|
+
this.recentLogs.push({
|
|
76
|
+
time: new Date().toISOString(),
|
|
77
|
+
level: level,
|
|
78
|
+
message:
|
|
79
|
+
message.length > this.maxRecentLogMessageLength
|
|
80
|
+
? `${message.substring(0, this.maxRecentLogMessageLength)}… (truncated)`
|
|
81
|
+
: message,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* Trim in batches rather than on every push so this stays O(1) amortised
|
|
86
|
+
* on the logging hot path: only re-slice once we have grown past a soft
|
|
87
|
+
* ceiling above the target size.
|
|
88
|
+
*/
|
|
89
|
+
if (
|
|
90
|
+
this.recentLogs.length >
|
|
91
|
+
this.maxRecentLogs + this.recentLogTrimSlack
|
|
92
|
+
) {
|
|
93
|
+
this.recentLogs = this.recentLogs.slice(-this.maxRecentLogs);
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// Never let log capture break the app.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Newest-last snapshot of the recent-log ring buffer (optionally the last N).
|
|
101
|
+
public static getRecentLogs(limit?: number): Array<RecentLogEntry> {
|
|
102
|
+
if (!limit || limit >= this.recentLogs.length) {
|
|
103
|
+
return [...this.recentLogs];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return this.recentLogs.slice(this.recentLogs.length - limit);
|
|
107
|
+
}
|
|
108
|
+
|
|
53
109
|
public static getLogLevel(): ConfigLogLevel {
|
|
54
110
|
if (!LogLevel) {
|
|
55
111
|
return ConfigLogLevel.INFO;
|
|
@@ -98,6 +154,8 @@ export default class logger {
|
|
|
98
154
|
// eslint-disable-next-line no-console
|
|
99
155
|
console.info(message);
|
|
100
156
|
|
|
157
|
+
this.record("INFO", message);
|
|
158
|
+
|
|
101
159
|
this.emit({
|
|
102
160
|
body: message,
|
|
103
161
|
severityNumber: SeverityNumber.INFO,
|
|
@@ -118,6 +176,8 @@ export default class logger {
|
|
|
118
176
|
// eslint-disable-next-line no-console
|
|
119
177
|
console.error(message);
|
|
120
178
|
|
|
179
|
+
this.record("ERROR", message);
|
|
180
|
+
|
|
121
181
|
this.emit({
|
|
122
182
|
body: message,
|
|
123
183
|
severityNumber: SeverityNumber.ERROR,
|
|
@@ -137,6 +197,8 @@ export default class logger {
|
|
|
137
197
|
// eslint-disable-next-line no-console
|
|
138
198
|
console.warn(message);
|
|
139
199
|
|
|
200
|
+
this.record("WARN", message);
|
|
201
|
+
|
|
140
202
|
this.emit({
|
|
141
203
|
body: message,
|
|
142
204
|
severityNumber: SeverityNumber.WARN,
|
|
@@ -152,6 +214,8 @@ export default class logger {
|
|
|
152
214
|
// eslint-disable-next-line no-console
|
|
153
215
|
console.debug(message);
|
|
154
216
|
|
|
217
|
+
this.record("DEBUG", message);
|
|
218
|
+
|
|
155
219
|
this.emit({
|
|
156
220
|
body: message,
|
|
157
221
|
severityNumber: SeverityNumber.DEBUG,
|
|
@@ -203,6 +267,8 @@ export default class logger {
|
|
|
203
267
|
// eslint-disable-next-line no-console
|
|
204
268
|
console.trace(message);
|
|
205
269
|
|
|
270
|
+
this.record("TRACE", message);
|
|
271
|
+
|
|
206
272
|
this.emit({
|
|
207
273
|
body: message,
|
|
208
274
|
severityNumber: SeverityNumber.DEBUG,
|
|
@@ -316,17 +316,27 @@ export default class MonitorResourceUtil {
|
|
|
316
316
|
* the monitor to flap between Online and Offline every minute.
|
|
317
317
|
*/
|
|
318
318
|
if (!serverMonitorResponse.onlyCheckRequestReceivedAt) {
|
|
319
|
-
|
|
319
|
+
/*
|
|
320
|
+
* Heartbeat write: single-statement UPDATE, no hooks and no
|
|
321
|
+
* `version` bump. This DELIBERATELY drops the per-update workflow
|
|
322
|
+
* trigger and audit-log entry that Monitor's @EnableWorkflow /
|
|
323
|
+
* @EnableAuditLog fire on every changed update — the old
|
|
324
|
+
* `ignoreHooks: true` did NOT suppress those (they are gated on
|
|
325
|
+
* the model flag, not on ignoreHooks), so a heartbeat used to
|
|
326
|
+
* spam an on-update workflow + an audit row every ingest. A
|
|
327
|
+
* liveness ping should do neither. onUpdateSuccess is inert here
|
|
328
|
+
* regardless (gated on status/interval/steps/name/etc., none of
|
|
329
|
+
* which are written). Also skips the pre-SELECT that would reload
|
|
330
|
+
* the large serverMonitorResponse jsonb row. See
|
|
331
|
+
* ServiceService.updateLastSeen.
|
|
332
|
+
*/
|
|
333
|
+
await MonitorService.updateColumnsByIdWithoutHooks({
|
|
320
334
|
id: monitor.id!,
|
|
321
335
|
data: {
|
|
322
336
|
serverMonitorRequestReceivedAt:
|
|
323
337
|
serverMonitorResponse.requestReceivedAt!,
|
|
324
338
|
serverMonitorResponse,
|
|
325
339
|
},
|
|
326
|
-
props: {
|
|
327
|
-
isRoot: true,
|
|
328
|
-
ignoreHooks: true,
|
|
329
|
-
},
|
|
330
340
|
});
|
|
331
341
|
|
|
332
342
|
logger.debug(
|
|
@@ -344,7 +354,15 @@ export default class MonitorResourceUtil {
|
|
|
344
354
|
`${dataToProcess.monitorId.toString()} - Incoming request received at ${incomingMonitorRequest.incomingRequestReceivedAt}`,
|
|
345
355
|
);
|
|
346
356
|
|
|
347
|
-
|
|
357
|
+
/*
|
|
358
|
+
* Heartbeat write: single-statement UPDATE, no hooks and no
|
|
359
|
+
* `version` bump. As with the server-monitor branch above, this
|
|
360
|
+
* deliberately drops the per-update workflow trigger + audit-log
|
|
361
|
+
* entry Monitor would otherwise fire on every heartbeat, and skips
|
|
362
|
+
* the pre-SELECT of the large incomingMonitorRequest jsonb row. See
|
|
363
|
+
* ServiceService.updateLastSeen.
|
|
364
|
+
*/
|
|
365
|
+
await MonitorService.updateColumnsByIdWithoutHooks({
|
|
348
366
|
id: monitor.id!,
|
|
349
367
|
data: {
|
|
350
368
|
incomingRequestMonitorHeartbeatCheckedAt:
|
|
@@ -353,10 +371,6 @@ export default class MonitorResourceUtil {
|
|
|
353
371
|
JSON.stringify(incomingMonitorRequest),
|
|
354
372
|
) as IncomingMonitorRequest,
|
|
355
373
|
} as any,
|
|
356
|
-
props: {
|
|
357
|
-
isRoot: true,
|
|
358
|
-
ignoreHooks: true,
|
|
359
|
-
},
|
|
360
374
|
});
|
|
361
375
|
|
|
362
376
|
logger.debug(
|
|
@@ -271,11 +271,17 @@ export async function reconcileByNaturalKey<
|
|
|
271
271
|
});
|
|
272
272
|
|
|
273
273
|
if (existing) {
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
/*
|
|
275
|
+
* Throttled bump of lastSeenAt (+ any caller-supplied merge fields).
|
|
276
|
+
* Heartbeat write: single-statement UPDATE, no hooks and no `version`
|
|
277
|
+
* bump (TelemetryEntity/Relationship enable no update workflow/realtime/
|
|
278
|
+
* audit). buildBump returns only plain values — lastSeenAt plus, at most,
|
|
279
|
+
* the descriptiveAttributes / labels JSON columns — which the primitive
|
|
280
|
+
* persists via the driver transformer path. See ServiceService.updateLastSeen.
|
|
281
|
+
*/
|
|
282
|
+
await data.service.updateColumnsByIdWithoutHooks({
|
|
276
283
|
id: existing.id!,
|
|
277
284
|
data: buildBump(existing),
|
|
278
|
-
props: { isRoot: true },
|
|
279
285
|
});
|
|
280
286
|
return;
|
|
281
287
|
}
|
|
@@ -306,10 +312,9 @@ export async function reconcileByNaturalKey<
|
|
|
306
312
|
logger.debug(
|
|
307
313
|
`EntityRegistry: create raced for ${data.describe} (concurrent insert); bumping lastSeenAt on the winning row.`,
|
|
308
314
|
);
|
|
309
|
-
await data.service.
|
|
315
|
+
await data.service.updateColumnsByIdWithoutHooks({
|
|
310
316
|
id: winner.id!,
|
|
311
317
|
data: buildBump(winner),
|
|
312
|
-
props: { isRoot: true },
|
|
313
318
|
});
|
|
314
319
|
return;
|
|
315
320
|
}
|