@oneuptime/common 10.0.20 → 10.0.21
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/Server/API/TelemetryAPI.ts +208 -0
- package/Server/API/UserCallAPI.ts +29 -0
- package/Server/API/UserEmailAPI.ts +29 -0
- package/Server/API/UserSmsAPI.ts +29 -0
- package/Server/API/UserWhatsAppAPI.ts +29 -0
- package/Server/Services/LogAggregationService.ts +251 -0
- package/Server/Utils/VM/VMRunner.ts +10 -0
- package/Types/Log/LogQueryParser.ts +252 -0
- package/Types/Log/LogQueryToFilter.ts +131 -0
- package/UI/Components/CopyTextButton/CopyTextButton.tsx +3 -3
- package/UI/Components/LogsViewer/LogsViewer.tsx +166 -93
- package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +58 -0
- package/UI/Components/LogsViewer/components/FacetSection.tsx +119 -0
- package/UI/Components/LogsViewer/components/FacetValueRow.tsx +102 -0
- package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +122 -0
- package/UI/Components/LogsViewer/components/LiveLogsToggle.tsx +4 -4
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +22 -26
- package/UI/Components/LogsViewer/components/LogSearchBar.tsx +360 -0
- package/UI/Components/LogsViewer/components/LogSearchHelp.tsx +128 -0
- package/UI/Components/LogsViewer/components/LogSearchSuggestions.tsx +64 -0
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +199 -0
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +172 -0
- package/UI/Components/LogsViewer/components/LogsFilterCard.tsx +27 -57
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +268 -0
- package/UI/Components/LogsViewer/components/LogsPagination.tsx +12 -10
- package/UI/Components/LogsViewer/components/LogsTable.tsx +33 -32
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +16 -18
- package/UI/Components/LogsViewer/components/severityColors.ts +31 -0
- package/UI/Components/LogsViewer/components/severityTheme.ts +25 -25
- package/UI/Components/LogsViewer/types.ts +20 -0
- package/build/dist/Server/API/TelemetryAPI.js +136 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserCallAPI.js +17 -0
- package/build/dist/Server/API/UserCallAPI.js.map +1 -1
- package/build/dist/Server/API/UserEmailAPI.js +17 -0
- package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
- package/build/dist/Server/API/UserSmsAPI.js +17 -0
- package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
- package/build/dist/Server/API/UserWhatsAppAPI.js +17 -0
- package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +163 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMRunner.js +10 -0
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Types/Log/LogQueryParser.js +200 -0
- package/build/dist/Types/Log/LogQueryParser.js.map +1 -0
- package/build/dist/Types/Log/LogQueryToFilter.js +96 -0
- package/build/dist/Types/Log/LogQueryToFilter.js.map +1 -0
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +3 -3
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +64 -42
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +24 -0
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/FacetSection.js +46 -0
- package/build/dist/UI/Components/LogsViewer/components/FacetSection.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +35 -0
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +64 -0
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LiveLogsToggle.js +4 -4
- package/build/dist/UI/Components/LogsViewer/components/LiveLogsToggle.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +19 -21
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +230 -0
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogSearchHelp.js +84 -0
- package/build/dist/UI/Components/LogsViewer/components/LogSearchHelp.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogSearchSuggestions.js +27 -0
- package/build/dist/UI/Components/LogsViewer/components/LogSearchSuggestions.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +100 -0
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +104 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js +14 -35
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +127 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +9 -9
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +31 -30
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +7 -8
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/severityColors.js +22 -0
- package/build/dist/UI/Components/LogsViewer/components/severityColors.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/severityTheme.js +25 -25
- package/build/dist/UI/Components/LogsViewer/components/severityTheme.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,6 +11,15 @@ import CommonAPI from "./CommonAPI";
|
|
|
11
11
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
12
12
|
import TelemetryType from "../../Types/Telemetry/TelemetryType";
|
|
13
13
|
import TelemetryAttributeService from "../Services/TelemetryAttributeService";
|
|
14
|
+
import LogAggregationService, {
|
|
15
|
+
HistogramBucket,
|
|
16
|
+
HistogramRequest,
|
|
17
|
+
FacetValue,
|
|
18
|
+
FacetRequest,
|
|
19
|
+
} from "../Services/LogAggregationService";
|
|
20
|
+
import ObjectID from "../../Types/ObjectID";
|
|
21
|
+
import OneUptimeDate from "../../Types/Date";
|
|
22
|
+
import { JSONObject } from "../../Types/JSON";
|
|
14
23
|
|
|
15
24
|
const router: ExpressRouter = Express.getRouter();
|
|
16
25
|
|
|
@@ -85,4 +94,203 @@ const getAttributes: GetAttributesFunction = async (
|
|
|
85
94
|
}
|
|
86
95
|
};
|
|
87
96
|
|
|
97
|
+
// --- Log Histogram Endpoint ---
|
|
98
|
+
|
|
99
|
+
router.post(
|
|
100
|
+
"/telemetry/logs/histogram",
|
|
101
|
+
UserMiddleware.getUserMiddleware,
|
|
102
|
+
async (
|
|
103
|
+
req: ExpressRequest,
|
|
104
|
+
res: ExpressResponse,
|
|
105
|
+
next: NextFunction,
|
|
106
|
+
): Promise<void> => {
|
|
107
|
+
try {
|
|
108
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
109
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
110
|
+
|
|
111
|
+
if (!databaseProps?.tenantId) {
|
|
112
|
+
return Response.sendErrorResponse(
|
|
113
|
+
req,
|
|
114
|
+
res,
|
|
115
|
+
new BadDataException("Invalid Project ID"),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const body: JSONObject = req.body as JSONObject;
|
|
120
|
+
|
|
121
|
+
const startTime: Date = body["startTime"]
|
|
122
|
+
? OneUptimeDate.fromString(body["startTime"] as string)
|
|
123
|
+
: OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
|
|
124
|
+
|
|
125
|
+
const endTime: Date = body["endTime"]
|
|
126
|
+
? OneUptimeDate.fromString(body["endTime"] as string)
|
|
127
|
+
: OneUptimeDate.getCurrentDate();
|
|
128
|
+
|
|
129
|
+
const bucketSizeInMinutes: number =
|
|
130
|
+
(body["bucketSizeInMinutes"] as number) ||
|
|
131
|
+
computeDefaultBucketSize(startTime, endTime);
|
|
132
|
+
|
|
133
|
+
const serviceIds: Array<ObjectID> | undefined = body["serviceIds"]
|
|
134
|
+
? (body["serviceIds"] as Array<string>).map((id: string) => {
|
|
135
|
+
return new ObjectID(id);
|
|
136
|
+
})
|
|
137
|
+
: undefined;
|
|
138
|
+
|
|
139
|
+
const severityTexts: Array<string> | undefined = body["severityTexts"]
|
|
140
|
+
? (body["severityTexts"] as Array<string>)
|
|
141
|
+
: undefined;
|
|
142
|
+
|
|
143
|
+
const bodySearchText: string | undefined = body["bodySearchText"]
|
|
144
|
+
? (body["bodySearchText"] as string)
|
|
145
|
+
: undefined;
|
|
146
|
+
|
|
147
|
+
const traceIds: Array<string> | undefined = body["traceIds"]
|
|
148
|
+
? (body["traceIds"] as Array<string>)
|
|
149
|
+
: undefined;
|
|
150
|
+
|
|
151
|
+
const spanIds: Array<string> | undefined = body["spanIds"]
|
|
152
|
+
? (body["spanIds"] as Array<string>)
|
|
153
|
+
: undefined;
|
|
154
|
+
|
|
155
|
+
const request: HistogramRequest = {
|
|
156
|
+
projectId: databaseProps.tenantId,
|
|
157
|
+
startTime,
|
|
158
|
+
endTime,
|
|
159
|
+
bucketSizeInMinutes,
|
|
160
|
+
serviceIds,
|
|
161
|
+
severityTexts,
|
|
162
|
+
bodySearchText,
|
|
163
|
+
traceIds,
|
|
164
|
+
spanIds,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const buckets: Array<HistogramBucket> =
|
|
168
|
+
await LogAggregationService.getHistogram(request);
|
|
169
|
+
|
|
170
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
171
|
+
buckets: buckets as unknown as JSONObject,
|
|
172
|
+
});
|
|
173
|
+
} catch (err: unknown) {
|
|
174
|
+
next(err);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// --- Log Facets Endpoint ---
|
|
180
|
+
|
|
181
|
+
router.post(
|
|
182
|
+
"/telemetry/logs/facets",
|
|
183
|
+
UserMiddleware.getUserMiddleware,
|
|
184
|
+
async (
|
|
185
|
+
req: ExpressRequest,
|
|
186
|
+
res: ExpressResponse,
|
|
187
|
+
next: NextFunction,
|
|
188
|
+
): Promise<void> => {
|
|
189
|
+
try {
|
|
190
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
191
|
+
await CommonAPI.getDatabaseCommonInteractionProps(req);
|
|
192
|
+
|
|
193
|
+
if (!databaseProps?.tenantId) {
|
|
194
|
+
return Response.sendErrorResponse(
|
|
195
|
+
req,
|
|
196
|
+
res,
|
|
197
|
+
new BadDataException("Invalid Project ID"),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const body: JSONObject = req.body as JSONObject;
|
|
202
|
+
|
|
203
|
+
const facetKeys: Array<string> = body["facetKeys"]
|
|
204
|
+
? (body["facetKeys"] as Array<string>)
|
|
205
|
+
: ["severityText", "serviceId"];
|
|
206
|
+
|
|
207
|
+
const startTime: Date = body["startTime"]
|
|
208
|
+
? OneUptimeDate.fromString(body["startTime"] as string)
|
|
209
|
+
: OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
|
|
210
|
+
|
|
211
|
+
const endTime: Date = body["endTime"]
|
|
212
|
+
? OneUptimeDate.fromString(body["endTime"] as string)
|
|
213
|
+
: OneUptimeDate.getCurrentDate();
|
|
214
|
+
|
|
215
|
+
const limit: number = (body["limit"] as number) || 10;
|
|
216
|
+
|
|
217
|
+
const serviceIds: Array<ObjectID> | undefined = body["serviceIds"]
|
|
218
|
+
? (body["serviceIds"] as Array<string>).map((id: string) => {
|
|
219
|
+
return new ObjectID(id);
|
|
220
|
+
})
|
|
221
|
+
: undefined;
|
|
222
|
+
|
|
223
|
+
const severityTexts: Array<string> | undefined = body["severityTexts"]
|
|
224
|
+
? (body["severityTexts"] as Array<string>)
|
|
225
|
+
: undefined;
|
|
226
|
+
|
|
227
|
+
const bodySearchText: string | undefined = body["bodySearchText"]
|
|
228
|
+
? (body["bodySearchText"] as string)
|
|
229
|
+
: undefined;
|
|
230
|
+
|
|
231
|
+
const traceIds: Array<string> | undefined = body["traceIds"]
|
|
232
|
+
? (body["traceIds"] as Array<string>)
|
|
233
|
+
: undefined;
|
|
234
|
+
|
|
235
|
+
const spanIds: Array<string> | undefined = body["spanIds"]
|
|
236
|
+
? (body["spanIds"] as Array<string>)
|
|
237
|
+
: undefined;
|
|
238
|
+
|
|
239
|
+
const facets: Record<string, Array<FacetValue>> = {};
|
|
240
|
+
|
|
241
|
+
for (const facetKey of facetKeys) {
|
|
242
|
+
const request: FacetRequest = {
|
|
243
|
+
projectId: databaseProps.tenantId,
|
|
244
|
+
startTime,
|
|
245
|
+
endTime,
|
|
246
|
+
facetKey,
|
|
247
|
+
limit,
|
|
248
|
+
serviceIds,
|
|
249
|
+
severityTexts,
|
|
250
|
+
bodySearchText,
|
|
251
|
+
traceIds,
|
|
252
|
+
spanIds,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
facets[facetKey] = await LogAggregationService.getFacetValues(request);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
259
|
+
facets: facets as unknown as JSONObject,
|
|
260
|
+
});
|
|
261
|
+
} catch (err: unknown) {
|
|
262
|
+
next(err);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// --- Helpers ---
|
|
268
|
+
|
|
269
|
+
function computeDefaultBucketSize(startTime: Date, endTime: Date): number {
|
|
270
|
+
const diffMs: number = endTime.getTime() - startTime.getTime();
|
|
271
|
+
const diffMinutes: number = diffMs / (1000 * 60);
|
|
272
|
+
|
|
273
|
+
if (diffMinutes <= 60) {
|
|
274
|
+
return 1;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (diffMinutes <= 360) {
|
|
278
|
+
return 5;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (diffMinutes <= 1440) {
|
|
282
|
+
return 15;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (diffMinutes <= 10080) {
|
|
286
|
+
return 60;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (diffMinutes <= 43200) {
|
|
290
|
+
return 360;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return 1440;
|
|
294
|
+
}
|
|
295
|
+
|
|
88
296
|
export default router;
|
|
@@ -136,6 +136,35 @@ export default class UserCallAPI extends BaseAPI<
|
|
|
136
136
|
);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
const item: UserCall | null = await this.service.findOneById({
|
|
140
|
+
id: req.body["itemId"],
|
|
141
|
+
props: {
|
|
142
|
+
isRoot: true,
|
|
143
|
+
},
|
|
144
|
+
select: {
|
|
145
|
+
userId: true,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (!item) {
|
|
150
|
+
return Response.sendErrorResponse(
|
|
151
|
+
req,
|
|
152
|
+
res,
|
|
153
|
+
new BadDataException("Item not found"),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (
|
|
158
|
+
item.userId?.toString() !==
|
|
159
|
+
(req as OneUptimeRequest)?.userAuthorization?.userId?.toString()
|
|
160
|
+
) {
|
|
161
|
+
return Response.sendErrorResponse(
|
|
162
|
+
req,
|
|
163
|
+
res,
|
|
164
|
+
new BadDataException("Invalid user ID"),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
139
168
|
await this.service.resendVerificationCode(req.body.itemId);
|
|
140
169
|
|
|
141
170
|
return Response.sendEmptySuccessResponse(req, res);
|
|
@@ -137,6 +137,35 @@ export default class UserEmailAPI extends BaseAPI<
|
|
|
137
137
|
);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
const item: UserEmail | null = await this.service.findOneById({
|
|
141
|
+
id: req.body["itemId"],
|
|
142
|
+
props: {
|
|
143
|
+
isRoot: true,
|
|
144
|
+
},
|
|
145
|
+
select: {
|
|
146
|
+
userId: true,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (!item) {
|
|
151
|
+
return Response.sendErrorResponse(
|
|
152
|
+
req,
|
|
153
|
+
res,
|
|
154
|
+
new BadDataException("Item not found"),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
item.userId?.toString() !==
|
|
160
|
+
(req as OneUptimeRequest)?.userAuthorization?.userId?.toString()
|
|
161
|
+
) {
|
|
162
|
+
return Response.sendErrorResponse(
|
|
163
|
+
req,
|
|
164
|
+
res,
|
|
165
|
+
new BadDataException("Invalid user ID"),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
140
169
|
await this.service.resendVerificationCode(req.body.itemId);
|
|
141
170
|
|
|
142
171
|
return Response.sendEmptySuccessResponse(req, res);
|
package/Server/API/UserSmsAPI.ts
CHANGED
|
@@ -132,6 +132,35 @@ export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> {
|
|
|
132
132
|
);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
const item: UserSMS | null = await this.service.findOneById({
|
|
136
|
+
id: req.body["itemId"],
|
|
137
|
+
props: {
|
|
138
|
+
isRoot: true,
|
|
139
|
+
},
|
|
140
|
+
select: {
|
|
141
|
+
userId: true,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!item) {
|
|
146
|
+
return Response.sendErrorResponse(
|
|
147
|
+
req,
|
|
148
|
+
res,
|
|
149
|
+
new BadDataException("Item not found"),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
item.userId?.toString() !==
|
|
155
|
+
(req as OneUptimeRequest)?.userAuthorization?.userId?.toString()
|
|
156
|
+
) {
|
|
157
|
+
return Response.sendErrorResponse(
|
|
158
|
+
req,
|
|
159
|
+
res,
|
|
160
|
+
new BadDataException("Invalid user ID"),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
135
164
|
await this.service.resendVerificationCode(req.body.itemId);
|
|
136
165
|
|
|
137
166
|
return Response.sendEmptySuccessResponse(req, res);
|
|
@@ -143,6 +143,35 @@ export default class UserWhatsAppAPI extends BaseAPI<
|
|
|
143
143
|
);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
const item: UserWhatsApp | null = await this.service.findOneById({
|
|
147
|
+
id: req.body["itemId"],
|
|
148
|
+
props: {
|
|
149
|
+
isRoot: true,
|
|
150
|
+
},
|
|
151
|
+
select: {
|
|
152
|
+
userId: true,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (!item) {
|
|
157
|
+
return Response.sendErrorResponse(
|
|
158
|
+
req,
|
|
159
|
+
res,
|
|
160
|
+
new BadDataException("Item not found"),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (
|
|
165
|
+
item.userId?.toString() !==
|
|
166
|
+
(req as OneUptimeRequest)?.userAuthorization?.userId?.toString()
|
|
167
|
+
) {
|
|
168
|
+
return Response.sendErrorResponse(
|
|
169
|
+
req,
|
|
170
|
+
res,
|
|
171
|
+
new BadDataException("Invalid user ID"),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
146
175
|
await this.service.resendVerificationCode(req.body.itemId);
|
|
147
176
|
|
|
148
177
|
return Response.sendEmptySuccessResponse(req, res);
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { SQL, Statement } from "../Utils/AnalyticsDatabase/Statement";
|
|
2
|
+
import LogDatabaseService from "./LogService";
|
|
3
|
+
import TableColumnType from "../../Types/AnalyticsDatabase/TableColumnType";
|
|
4
|
+
import { JSONObject } from "../../Types/JSON";
|
|
5
|
+
import ObjectID from "../../Types/ObjectID";
|
|
6
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
7
|
+
import { DbJSONResponse, Results } from "./AnalyticsDatabaseService";
|
|
8
|
+
|
|
9
|
+
export interface HistogramBucket {
|
|
10
|
+
time: string;
|
|
11
|
+
severity: string;
|
|
12
|
+
count: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface HistogramRequest {
|
|
16
|
+
projectId: ObjectID;
|
|
17
|
+
startTime: Date;
|
|
18
|
+
endTime: Date;
|
|
19
|
+
bucketSizeInMinutes: number;
|
|
20
|
+
serviceIds?: Array<ObjectID> | undefined;
|
|
21
|
+
severityTexts?: Array<string> | undefined;
|
|
22
|
+
bodySearchText?: string | undefined;
|
|
23
|
+
traceIds?: Array<string> | undefined;
|
|
24
|
+
spanIds?: Array<string> | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface FacetValue {
|
|
28
|
+
value: string;
|
|
29
|
+
count: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FacetRequest {
|
|
33
|
+
projectId: ObjectID;
|
|
34
|
+
startTime: Date;
|
|
35
|
+
endTime: Date;
|
|
36
|
+
facetKey: string;
|
|
37
|
+
limit?: number | undefined;
|
|
38
|
+
serviceIds?: Array<ObjectID> | undefined;
|
|
39
|
+
severityTexts?: Array<string> | undefined;
|
|
40
|
+
bodySearchText?: string | undefined;
|
|
41
|
+
traceIds?: Array<string> | undefined;
|
|
42
|
+
spanIds?: Array<string> | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class LogAggregationService {
|
|
46
|
+
private static readonly DEFAULT_FACET_LIMIT: number = 10;
|
|
47
|
+
private static readonly TABLE_NAME: string = "LogItem";
|
|
48
|
+
|
|
49
|
+
@CaptureSpan()
|
|
50
|
+
public static async getHistogram(
|
|
51
|
+
request: HistogramRequest,
|
|
52
|
+
): Promise<Array<HistogramBucket>> {
|
|
53
|
+
const statement: Statement =
|
|
54
|
+
LogAggregationService.buildHistogramStatement(request);
|
|
55
|
+
|
|
56
|
+
const dbResult: Results = await LogDatabaseService.executeQuery(statement);
|
|
57
|
+
const response: DbJSONResponse = await dbResult.json<{
|
|
58
|
+
data?: Array<JSONObject>;
|
|
59
|
+
}>();
|
|
60
|
+
|
|
61
|
+
const rows: Array<JSONObject> = response.data || [];
|
|
62
|
+
|
|
63
|
+
return rows.map((row: JSONObject): HistogramBucket => {
|
|
64
|
+
return {
|
|
65
|
+
time: String(row["bucket"] || ""),
|
|
66
|
+
severity: String(row["severityText"] || "Unspecified"),
|
|
67
|
+
count: Number(row["cnt"] || 0),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@CaptureSpan()
|
|
73
|
+
public static async getFacetValues(
|
|
74
|
+
request: FacetRequest,
|
|
75
|
+
): Promise<Array<FacetValue>> {
|
|
76
|
+
const statement: Statement =
|
|
77
|
+
LogAggregationService.buildFacetStatement(request);
|
|
78
|
+
|
|
79
|
+
const dbResult: Results = await LogDatabaseService.executeQuery(statement);
|
|
80
|
+
const response: DbJSONResponse = await dbResult.json<{
|
|
81
|
+
data?: Array<JSONObject>;
|
|
82
|
+
}>();
|
|
83
|
+
|
|
84
|
+
const rows: Array<JSONObject> = response.data || [];
|
|
85
|
+
|
|
86
|
+
return rows
|
|
87
|
+
.map((row: JSONObject): FacetValue => {
|
|
88
|
+
return {
|
|
89
|
+
value: String(row["val"] || ""),
|
|
90
|
+
count: Number(row["cnt"] || 0),
|
|
91
|
+
};
|
|
92
|
+
})
|
|
93
|
+
.filter((facet: FacetValue): boolean => {
|
|
94
|
+
return facet.value.length > 0;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private static buildHistogramStatement(request: HistogramRequest): Statement {
|
|
99
|
+
const intervalSeconds: number = request.bucketSizeInMinutes * 60;
|
|
100
|
+
|
|
101
|
+
const statement: Statement = SQL`
|
|
102
|
+
SELECT
|
|
103
|
+
toStartOfInterval(time, INTERVAL ${{
|
|
104
|
+
type: TableColumnType.Number,
|
|
105
|
+
value: intervalSeconds,
|
|
106
|
+
}} SECOND) AS bucket,
|
|
107
|
+
severityText,
|
|
108
|
+
count() AS cnt
|
|
109
|
+
FROM ${LogAggregationService.TABLE_NAME}
|
|
110
|
+
WHERE projectId = ${{
|
|
111
|
+
type: TableColumnType.ObjectID,
|
|
112
|
+
value: request.projectId,
|
|
113
|
+
}}
|
|
114
|
+
AND time >= ${{
|
|
115
|
+
type: TableColumnType.Date,
|
|
116
|
+
value: request.startTime,
|
|
117
|
+
}}
|
|
118
|
+
AND time <= ${{
|
|
119
|
+
type: TableColumnType.Date,
|
|
120
|
+
value: request.endTime,
|
|
121
|
+
}}
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
LogAggregationService.appendCommonFilters(statement, request);
|
|
125
|
+
|
|
126
|
+
statement.append(" GROUP BY bucket, severityText ORDER BY bucket ASC");
|
|
127
|
+
|
|
128
|
+
return statement;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private static buildFacetStatement(request: FacetRequest): Statement {
|
|
132
|
+
const limit: number =
|
|
133
|
+
request.limit ?? LogAggregationService.DEFAULT_FACET_LIMIT;
|
|
134
|
+
|
|
135
|
+
const isTopLevelColumn: boolean = LogAggregationService.isTopLevelColumn(
|
|
136
|
+
request.facetKey,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const escapedKey: string = LogAggregationService.escapeSingleQuotes(
|
|
140
|
+
request.facetKey,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const valueExpression: string = isTopLevelColumn
|
|
144
|
+
? `toString(${escapedKey})`
|
|
145
|
+
: `JSONExtractRaw(attributes, '${escapedKey}')`;
|
|
146
|
+
|
|
147
|
+
// Build with raw SQL for the expression part, then parameterize WHERE values
|
|
148
|
+
const statement: Statement = new Statement();
|
|
149
|
+
|
|
150
|
+
statement.append(
|
|
151
|
+
`SELECT ${valueExpression} AS val, count() AS cnt FROM ${LogAggregationService.TABLE_NAME}`,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
statement.append(
|
|
155
|
+
SQL` WHERE projectId = ${{
|
|
156
|
+
type: TableColumnType.ObjectID,
|
|
157
|
+
value: request.projectId,
|
|
158
|
+
}} AND time >= ${{
|
|
159
|
+
type: TableColumnType.Date,
|
|
160
|
+
value: request.startTime,
|
|
161
|
+
}} AND time <= ${{
|
|
162
|
+
type: TableColumnType.Date,
|
|
163
|
+
value: request.endTime,
|
|
164
|
+
}}`,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (!isTopLevelColumn) {
|
|
168
|
+
statement.append(` AND JSONHas(attributes, '${escapedKey}') = 1`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
LogAggregationService.appendCommonFilters(statement, request);
|
|
172
|
+
|
|
173
|
+
statement.append(
|
|
174
|
+
SQL` GROUP BY val ORDER BY cnt DESC LIMIT ${{
|
|
175
|
+
type: TableColumnType.Number,
|
|
176
|
+
value: limit,
|
|
177
|
+
}}`,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return statement;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private static appendCommonFilters(
|
|
184
|
+
statement: Statement,
|
|
185
|
+
request: Pick<
|
|
186
|
+
HistogramRequest,
|
|
187
|
+
"serviceIds" | "severityTexts" | "bodySearchText" | "traceIds" | "spanIds"
|
|
188
|
+
>,
|
|
189
|
+
): void {
|
|
190
|
+
if (request.serviceIds && request.serviceIds.length > 0) {
|
|
191
|
+
const idStrings: Array<string> = request.serviceIds.map(
|
|
192
|
+
(id: ObjectID): string => {
|
|
193
|
+
return `'${id.toString()}'`;
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
statement.append(` AND serviceId IN (${idStrings.join(",")})`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (request.severityTexts && request.severityTexts.length > 0) {
|
|
200
|
+
const sevStrings: Array<string> = request.severityTexts.map(
|
|
201
|
+
(s: string): string => {
|
|
202
|
+
return `'${LogAggregationService.escapeSingleQuotes(s)}'`;
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
statement.append(` AND severityText IN (${sevStrings.join(",")})`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (request.traceIds && request.traceIds.length > 0) {
|
|
209
|
+
const traceStrings: Array<string> = request.traceIds.map(
|
|
210
|
+
(s: string): string => {
|
|
211
|
+
return `'${LogAggregationService.escapeSingleQuotes(s)}'`;
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
statement.append(` AND traceId IN (${traceStrings.join(",")})`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (request.spanIds && request.spanIds.length > 0) {
|
|
218
|
+
const spanStrings: Array<string> = request.spanIds.map(
|
|
219
|
+
(s: string): string => {
|
|
220
|
+
return `'${LogAggregationService.escapeSingleQuotes(s)}'`;
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
statement.append(` AND spanId IN (${spanStrings.join(",")})`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (request.bodySearchText && request.bodySearchText.trim().length > 0) {
|
|
227
|
+
statement.append(
|
|
228
|
+
` AND body ILIKE ${{
|
|
229
|
+
type: TableColumnType.Text,
|
|
230
|
+
value: `%${request.bodySearchText.trim()}%`,
|
|
231
|
+
}}`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private static isTopLevelColumn(key: string): boolean {
|
|
237
|
+
const topLevelColumns: Set<string> = new Set([
|
|
238
|
+
"severityText",
|
|
239
|
+
"serviceId",
|
|
240
|
+
"traceId",
|
|
241
|
+
"spanId",
|
|
242
|
+
]);
|
|
243
|
+
return topLevelColumns.has(key);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private static escapeSingleQuotes(value: string): string {
|
|
247
|
+
return value.replace(/'/g, "\\'");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default LogAggregationService;
|
|
@@ -22,6 +22,16 @@ const BLOCKED_SANDBOX_PROPERTIES: ReadonlySet<string> = new Set([
|
|
|
22
22
|
"__proto__",
|
|
23
23
|
"prototype",
|
|
24
24
|
"mainModule",
|
|
25
|
+
/*
|
|
26
|
+
* Block Playwright methods that can spawn processes or access internals.
|
|
27
|
+
* Prevents RCE via browser.browserType().launch({executablePath:"/bin/sh"})
|
|
28
|
+
* and traversal via page.context().browser().browserType().launch(...)
|
|
29
|
+
*/
|
|
30
|
+
"browserType", // Browser → BrowserType (which has launch/connect)
|
|
31
|
+
"launch", // BrowserType.launch() spawns a child process
|
|
32
|
+
"launchPersistentContext", // BrowserType.launchPersistentContext() spawns a child process
|
|
33
|
+
"connectOverCDP", // BrowserType.connectOverCDP() connects via Chrome DevTools Protocol
|
|
34
|
+
"newCDPSession", // BrowserContext/Page.newCDPSession() opens raw CDP sessions
|
|
25
35
|
]);
|
|
26
36
|
|
|
27
37
|
/**
|