@oneuptime/common 8.0.5438 → 8.0.5462
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/StatusPage.ts +80 -0
- package/Server/API/StatusPageAPI.ts +138 -52
- package/Server/EnvironmentConfig.ts +34 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/OpenTelemetryIngestService.ts +1 -39
- package/Server/Services/StatusPageService.ts +117 -0
- package/Server/Services/TelemetryUsageBillingService.ts +208 -15
- package/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +5 -0
- package/Server/Utils/Telemetry/Telemetry.ts +129 -81
- package/Server/Utils/VM/VMRunner.ts +3 -4
- package/UI/Components/Dictionary/Dictionary.tsx +3 -0
- package/UI/Components/Forms/Fields/FieldLabel.tsx +7 -3
- package/UI/Components/LogsViewer/LogItem.tsx +12 -4
- package/UI/Components/LogsViewer/LogsViewer.tsx +131 -29
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -2
- package/UI/Components/ModelFilter/Filter.ts +1 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +2 -1
- package/UI/Components/Table/TableRow.tsx +89 -77
- package/UI/esbuild-config.js +32 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +82 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +157 -74
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +14 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +0 -30
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +95 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +168 -8
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js +4 -0
- package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +84 -60
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +2 -2
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +3 -3
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FieldLabel.js +2 -1
- package/build/dist/UI/Components/Forms/Fields/FieldLabel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogItem.js +5 -3
- package/build/dist/UI/Components/LogsViewer/LogItem.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +73 -22
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +18 -6
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { MeteredPlanUtil } from "../Types/Billing/MeteredPlan/AllMeteredPlans";
|
|
2
2
|
import TelemetryMeteredPlan from "../Types/Billing/MeteredPlan/TelemetryMeteredPlan";
|
|
3
|
-
import QueryHelper from "../Types/Database/QueryHelper";
|
|
4
3
|
import DatabaseService from "./DatabaseService";
|
|
5
4
|
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
6
|
-
import LIMIT_MAX from "../../Types/Database/LimitMax";
|
|
5
|
+
import LIMIT_MAX, { LIMIT_INFINITY } from "../../Types/Database/LimitMax";
|
|
7
6
|
import OneUptimeDate from "../../Types/Date";
|
|
8
7
|
import Decimal from "../../Types/Decimal";
|
|
9
8
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
10
9
|
import ProductType from "../../Types/MeteredPlan/ProductType";
|
|
11
10
|
import ObjectID from "../../Types/ObjectID";
|
|
12
|
-
import Model
|
|
13
|
-
|
|
11
|
+
import Model, {
|
|
12
|
+
DEFAULT_RETENTION_IN_DAYS,
|
|
13
|
+
} from "../../Models/DatabaseModels/TelemetryUsageBilling";
|
|
14
|
+
import TelemetryServiceService from "./TelemetryServiceService";
|
|
15
|
+
import SpanService from "./SpanService";
|
|
16
|
+
import LogService from "./LogService";
|
|
17
|
+
import MetricService from "./MetricService";
|
|
18
|
+
import AnalyticsQueryHelper from "../Types/AnalyticsDatabase/QueryHelper";
|
|
19
|
+
import DiskSize from "../../Types/DiskSize";
|
|
20
|
+
import logger from "../Utils/Logger";
|
|
21
|
+
import PositiveNumber from "../../Types/PositiveNumber";
|
|
22
|
+
import TelemetryServiceModel from "../../Models/DatabaseModels/TelemetryService";
|
|
23
|
+
import {
|
|
24
|
+
AverageSpanRowSizeInBytes,
|
|
25
|
+
AverageLogRowSizeInBytes,
|
|
26
|
+
AverageMetricRowSizeInBytes,
|
|
27
|
+
IsBillingEnabled,
|
|
28
|
+
} from "../EnvironmentConfig";
|
|
14
29
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
15
30
|
|
|
16
31
|
export class Service extends DatabaseService<Model> {
|
|
@@ -31,9 +46,6 @@ export class Service extends DatabaseService<Model> {
|
|
|
31
46
|
projectId: data.projectId,
|
|
32
47
|
productType: data.productType,
|
|
33
48
|
isReportedToBillingProvider: false,
|
|
34
|
-
createdAt: QueryHelper.lessThan(
|
|
35
|
-
OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -1),
|
|
36
|
-
), // we need to get everything that's not today.
|
|
37
49
|
},
|
|
38
50
|
skip: 0,
|
|
39
51
|
limit: LIMIT_MAX, /// because a project can have MANY telemetry services.
|
|
@@ -47,6 +59,159 @@ export class Service extends DatabaseService<Model> {
|
|
|
47
59
|
});
|
|
48
60
|
}
|
|
49
61
|
|
|
62
|
+
@CaptureSpan()
|
|
63
|
+
public async stageTelemetryUsageForProject(data: {
|
|
64
|
+
projectId: ObjectID;
|
|
65
|
+
productType: ProductType;
|
|
66
|
+
usageDate?: Date;
|
|
67
|
+
}): Promise<void> {
|
|
68
|
+
if (!IsBillingEnabled) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const usageDate: Date = data.usageDate
|
|
73
|
+
? OneUptimeDate.fromString(data.usageDate)
|
|
74
|
+
: OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -1);
|
|
75
|
+
|
|
76
|
+
const averageRowSizeInBytes: number = this.getAverageRowSizeForProduct(
|
|
77
|
+
data.productType,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (averageRowSizeInBytes <= 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const usageDayString: string = OneUptimeDate.getDateString(usageDate);
|
|
85
|
+
const startOfDay: Date = OneUptimeDate.getStartOfDay(usageDate);
|
|
86
|
+
const endOfDay: Date = OneUptimeDate.getEndOfDay(usageDate);
|
|
87
|
+
|
|
88
|
+
const telemetryServices: Array<TelemetryServiceModel> =
|
|
89
|
+
await TelemetryServiceService.findBy({
|
|
90
|
+
query: {
|
|
91
|
+
projectId: data.projectId,
|
|
92
|
+
},
|
|
93
|
+
select: {
|
|
94
|
+
_id: true,
|
|
95
|
+
retainTelemetryDataForDays: true,
|
|
96
|
+
},
|
|
97
|
+
skip: 0,
|
|
98
|
+
limit: LIMIT_MAX,
|
|
99
|
+
props: {
|
|
100
|
+
isRoot: true,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (!telemetryServices || telemetryServices.length === 0) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const telemetryService of telemetryServices) {
|
|
109
|
+
if (!telemetryService?.id) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const existingEntry: Model | null = await this.findOneBy({
|
|
114
|
+
query: {
|
|
115
|
+
projectId: data.projectId,
|
|
116
|
+
productType: data.productType,
|
|
117
|
+
telemetryServiceId: telemetryService.id,
|
|
118
|
+
day: usageDayString,
|
|
119
|
+
},
|
|
120
|
+
select: {
|
|
121
|
+
_id: true,
|
|
122
|
+
},
|
|
123
|
+
props: {
|
|
124
|
+
isRoot: true,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (existingEntry) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let rowCount: number = 0;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
if (data.productType === ProductType.Traces) {
|
|
136
|
+
const count: PositiveNumber = await SpanService.countBy({
|
|
137
|
+
query: {
|
|
138
|
+
projectId: data.projectId,
|
|
139
|
+
serviceId: telemetryService.id,
|
|
140
|
+
startTime: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
|
|
141
|
+
},
|
|
142
|
+
skip: 0,
|
|
143
|
+
limit: LIMIT_INFINITY,
|
|
144
|
+
props: {
|
|
145
|
+
isRoot: true,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
rowCount = count.toNumber();
|
|
150
|
+
} else if (data.productType === ProductType.Logs) {
|
|
151
|
+
const count: PositiveNumber = await LogService.countBy({
|
|
152
|
+
query: {
|
|
153
|
+
projectId: data.projectId,
|
|
154
|
+
serviceId: telemetryService.id,
|
|
155
|
+
time: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
|
|
156
|
+
},
|
|
157
|
+
skip: 0,
|
|
158
|
+
limit: LIMIT_INFINITY,
|
|
159
|
+
props: {
|
|
160
|
+
isRoot: true,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
rowCount = count.toNumber();
|
|
165
|
+
} else if (data.productType === ProductType.Metrics) {
|
|
166
|
+
const count: PositiveNumber = await MetricService.countBy({
|
|
167
|
+
query: {
|
|
168
|
+
projectId: data.projectId,
|
|
169
|
+
serviceId: telemetryService.id,
|
|
170
|
+
time: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
|
|
171
|
+
},
|
|
172
|
+
skip: 0,
|
|
173
|
+
limit: LIMIT_INFINITY,
|
|
174
|
+
props: {
|
|
175
|
+
isRoot: true,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
rowCount = count.toNumber();
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logger.error(
|
|
183
|
+
`Failed to compute telemetry usage for service ${telemetryService.id?.toString()}:`,
|
|
184
|
+
);
|
|
185
|
+
logger.error(error as Error);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (rowCount <= 0) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const estimatedBytes: number = rowCount * averageRowSizeInBytes;
|
|
194
|
+
const estimatedGigabytes: number = DiskSize.byteSizeToGB(estimatedBytes);
|
|
195
|
+
|
|
196
|
+
if (!Number.isFinite(estimatedGigabytes) || estimatedGigabytes <= 0) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const dataRetentionInDays: number =
|
|
201
|
+
telemetryService.retainTelemetryDataForDays ||
|
|
202
|
+
DEFAULT_RETENTION_IN_DAYS;
|
|
203
|
+
|
|
204
|
+
await this.updateUsageBilling({
|
|
205
|
+
projectId: data.projectId,
|
|
206
|
+
productType: data.productType,
|
|
207
|
+
telemetryServiceId: telemetryService.id,
|
|
208
|
+
dataIngestedInGB: estimatedGigabytes,
|
|
209
|
+
retentionInDays: dataRetentionInDays,
|
|
210
|
+
usageDate: usageDate,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
50
215
|
@CaptureSpan()
|
|
51
216
|
public async updateUsageBilling(data: {
|
|
52
217
|
projectId: ObjectID;
|
|
@@ -54,6 +219,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
54
219
|
telemetryServiceId: ObjectID;
|
|
55
220
|
dataIngestedInGB: number;
|
|
56
221
|
retentionInDays: number;
|
|
222
|
+
usageDate?: Date;
|
|
57
223
|
}): Promise<void> {
|
|
58
224
|
if (
|
|
59
225
|
data.productType !== ProductType.Traces &&
|
|
@@ -70,6 +236,12 @@ export class Service extends DatabaseService<Model> {
|
|
|
70
236
|
data.productType,
|
|
71
237
|
) as TelemetryMeteredPlan;
|
|
72
238
|
|
|
239
|
+
const usageDate: Date = data.usageDate
|
|
240
|
+
? OneUptimeDate.fromString(data.usageDate)
|
|
241
|
+
: OneUptimeDate.getCurrentDate();
|
|
242
|
+
|
|
243
|
+
const usageDayString: string = OneUptimeDate.getDateString(usageDate);
|
|
244
|
+
|
|
73
245
|
const totalCostOfThisOperationInUSD: number =
|
|
74
246
|
serverMeteredPlan.getTotalCostInUSD({
|
|
75
247
|
dataIngestedInGB: data.dataIngestedInGB,
|
|
@@ -82,10 +254,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
82
254
|
productType: data.productType,
|
|
83
255
|
telemetryServiceId: data.telemetryServiceId,
|
|
84
256
|
isReportedToBillingProvider: false,
|
|
85
|
-
|
|
86
|
-
OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -1),
|
|
87
|
-
OneUptimeDate.getCurrentDate(),
|
|
88
|
-
),
|
|
257
|
+
day: usageDayString,
|
|
89
258
|
},
|
|
90
259
|
select: {
|
|
91
260
|
_id: true,
|
|
@@ -135,11 +304,9 @@ export class Service extends DatabaseService<Model> {
|
|
|
135
304
|
usageBilling.telemetryServiceId = data.telemetryServiceId;
|
|
136
305
|
usageBilling.retainTelemetryDataForDays = data.retentionInDays;
|
|
137
306
|
usageBilling.isReportedToBillingProvider = false;
|
|
138
|
-
usageBilling.createdAt =
|
|
307
|
+
usageBilling.createdAt = usageDate;
|
|
139
308
|
|
|
140
|
-
usageBilling.day =
|
|
141
|
-
OneUptimeDate.getCurrentDate(),
|
|
142
|
-
);
|
|
309
|
+
usageBilling.day = usageDayString;
|
|
143
310
|
|
|
144
311
|
usageBilling.totalCostInUSD = new Decimal(totalCostOfThisOperationInUSD);
|
|
145
312
|
|
|
@@ -151,6 +318,32 @@ export class Service extends DatabaseService<Model> {
|
|
|
151
318
|
});
|
|
152
319
|
}
|
|
153
320
|
}
|
|
321
|
+
|
|
322
|
+
private getAverageRowSizeForProduct(productType: ProductType): number {
|
|
323
|
+
const fallbackSize: number = 1024;
|
|
324
|
+
|
|
325
|
+
// Narrow to telemetry product types before indexing to satisfy TypeScript
|
|
326
|
+
if (
|
|
327
|
+
productType !== ProductType.Traces &&
|
|
328
|
+
productType !== ProductType.Logs &&
|
|
329
|
+
productType !== ProductType.Metrics
|
|
330
|
+
) {
|
|
331
|
+
return fallbackSize;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const value: number =
|
|
335
|
+
{
|
|
336
|
+
[ProductType.Traces]: AverageSpanRowSizeInBytes,
|
|
337
|
+
[ProductType.Logs]: AverageLogRowSizeInBytes,
|
|
338
|
+
[ProductType.Metrics]: AverageMetricRowSizeInBytes,
|
|
339
|
+
}[productType] ?? fallbackSize;
|
|
340
|
+
|
|
341
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
342
|
+
return fallbackSize;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
154
347
|
}
|
|
155
348
|
|
|
156
349
|
export default new Service();
|
|
@@ -56,6 +56,11 @@ export default class TelemetryMeteredPlan extends ServerMeteredPlan {
|
|
|
56
56
|
): Promise<void> {
|
|
57
57
|
// get all unreported logs
|
|
58
58
|
|
|
59
|
+
await TelemetryUsageBillingService.stageTelemetryUsageForProject({
|
|
60
|
+
projectId: projectId,
|
|
61
|
+
productType: this.productType,
|
|
62
|
+
});
|
|
63
|
+
|
|
59
64
|
const usageBillings: Array<TelemetryUsageBilling> =
|
|
60
65
|
await TelemetryUsageBillingService.getUnreportedUsageBilling({
|
|
61
66
|
projectId: projectId,
|
|
@@ -143,10 +143,10 @@ export default class TelemetryUtil {
|
|
|
143
143
|
let { prefixKeysWithString } = data;
|
|
144
144
|
|
|
145
145
|
if (prefixKeysWithString) {
|
|
146
|
-
prefixKeysWithString = prefixKeysWithString
|
|
146
|
+
prefixKeysWithString = `${prefixKeysWithString}.`;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
const finalObj: Dictionary<AttributeType | Array<AttributeType>> = {};
|
|
150
150
|
const attributes: JSONArray = items;
|
|
151
151
|
|
|
152
152
|
if (!attributes) {
|
|
@@ -154,25 +154,40 @@ export default class TelemetryUtil {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
for (const attribute of attributes) {
|
|
157
|
-
if (attribute["key"]
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
157
|
+
if (!attribute["key"] || typeof attribute["key"] !== "string") {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const keyWithPrefix: string = `${prefixKeysWithString}${attribute["key"]}`;
|
|
162
|
+
|
|
163
|
+
const value = this.getAttributeValues(
|
|
164
|
+
keyWithPrefix,
|
|
165
|
+
attribute["value"],
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (value === null) {
|
|
169
|
+
finalObj[keyWithPrefix] = null;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
finalObj[keyWithPrefix] = value;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (typeof value === "object") {
|
|
179
|
+
for (const [nestedKey, nestedValue] of Object.entries(
|
|
180
|
+
value as Dictionary<AttributeType | Array<AttributeType>>,
|
|
181
|
+
)) {
|
|
182
|
+
finalObj[nestedKey] = nestedValue as
|
|
183
|
+
| AttributeType
|
|
184
|
+
| Array<AttributeType>;
|
|
174
185
|
}
|
|
186
|
+
|
|
187
|
+
continue;
|
|
175
188
|
}
|
|
189
|
+
|
|
190
|
+
finalObj[keyWithPrefix] = value as AttributeType;
|
|
176
191
|
}
|
|
177
192
|
|
|
178
193
|
return finalObj;
|
|
@@ -181,73 +196,106 @@ export default class TelemetryUtil {
|
|
|
181
196
|
public static getAttributeValues(
|
|
182
197
|
prefixKeysWithString: string,
|
|
183
198
|
value: JSONValue,
|
|
184
|
-
):
|
|
199
|
+
):
|
|
200
|
+
| AttributeType
|
|
201
|
+
| Dictionary<AttributeType | Array<AttributeType>>
|
|
202
|
+
| Array<AttributeType>
|
|
203
|
+
| null {
|
|
185
204
|
let finalObj:
|
|
186
|
-
| Dictionary<AttributeType
|
|
205
|
+
| Dictionary<AttributeType | Array<AttributeType>>
|
|
187
206
|
| AttributeType
|
|
188
|
-
| Array<AttributeType>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
"
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
207
|
+
| Array<AttributeType>
|
|
208
|
+
| null = null;
|
|
209
|
+
const jsonValue: JSONObject = value as JSONObject;
|
|
210
|
+
|
|
211
|
+
if (jsonValue && typeof jsonValue === "object") {
|
|
212
|
+
if (Object.prototype.hasOwnProperty.call(jsonValue, "stringValue")) {
|
|
213
|
+
const stringValue: JSONValue = jsonValue["stringValue"];
|
|
214
|
+
finalObj =
|
|
215
|
+
stringValue !== undefined && stringValue !== null
|
|
216
|
+
? (stringValue as string)
|
|
217
|
+
: "";
|
|
218
|
+
} else if (Object.prototype.hasOwnProperty.call(jsonValue, "intValue")) {
|
|
219
|
+
const intValue: JSONValue = jsonValue["intValue"];
|
|
220
|
+
if (intValue !== undefined && intValue !== null) {
|
|
221
|
+
finalObj = intValue as number;
|
|
222
|
+
}
|
|
223
|
+
} else if (
|
|
224
|
+
Object.prototype.hasOwnProperty.call(jsonValue, "doubleValue")
|
|
225
|
+
) {
|
|
226
|
+
const doubleValue: JSONValue = jsonValue["doubleValue"];
|
|
227
|
+
if (doubleValue !== undefined && doubleValue !== null) {
|
|
228
|
+
finalObj = doubleValue as number;
|
|
229
|
+
}
|
|
230
|
+
} else if (Object.prototype.hasOwnProperty.call(jsonValue, "boolValue")) {
|
|
231
|
+
finalObj = jsonValue["boolValue"] as boolean;
|
|
232
|
+
} else if (
|
|
233
|
+
jsonValue["arrayValue"] &&
|
|
234
|
+
(jsonValue["arrayValue"] as JSONObject)["values"]
|
|
235
|
+
) {
|
|
236
|
+
const values: JSONArray = (jsonValue["arrayValue"] as JSONObject)[
|
|
237
|
+
"values"
|
|
238
|
+
] as JSONArray;
|
|
239
|
+
finalObj = values.map((v: JSONObject) => {
|
|
240
|
+
return this.getAttributeValues(prefixKeysWithString, v) as AttributeType;
|
|
241
|
+
}) as Array<AttributeType>;
|
|
242
|
+
} else if (
|
|
243
|
+
jsonValue["mapValue"] &&
|
|
244
|
+
(jsonValue["mapValue"] as JSONObject)["fields"]
|
|
245
|
+
) {
|
|
246
|
+
const fields: JSONObject = (jsonValue["mapValue"] as JSONObject)[
|
|
247
|
+
"fields"
|
|
248
|
+
] as JSONObject;
|
|
249
|
+
|
|
250
|
+
const flattenedFields: Dictionary<AttributeType | Array<AttributeType>> =
|
|
251
|
+
{};
|
|
252
|
+
for (const key in fields) {
|
|
253
|
+
const nestedPrefix: string = `${prefixKeysWithString}.${key}`;
|
|
254
|
+
const nestedValue = this.getAttributeValues(
|
|
255
|
+
nestedPrefix,
|
|
256
|
+
fields[key],
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
if (nestedValue === null) {
|
|
260
|
+
flattenedFields[nestedPrefix] = null;
|
|
261
|
+
continue;
|
|
230
262
|
}
|
|
231
|
-
|
|
232
|
-
|
|
263
|
+
|
|
264
|
+
if (Array.isArray(nestedValue)) {
|
|
265
|
+
flattenedFields[nestedPrefix] = nestedValue;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (typeof nestedValue === "object") {
|
|
270
|
+
for (const [nestedKey, nestedEntry] of Object.entries(
|
|
271
|
+
nestedValue as Dictionary<AttributeType | Array<AttributeType>>,
|
|
272
|
+
)) {
|
|
273
|
+
flattenedFields[nestedKey] = nestedEntry as
|
|
274
|
+
| AttributeType
|
|
275
|
+
| Array<AttributeType>;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
flattenedFields[nestedPrefix] = nestedValue as AttributeType;
|
|
233
282
|
}
|
|
283
|
+
|
|
284
|
+
finalObj = flattenedFields;
|
|
285
|
+
} else if (
|
|
286
|
+
jsonValue["kvlistValue"] &&
|
|
287
|
+
(jsonValue["kvlistValue"] as JSONObject)["values"]
|
|
288
|
+
) {
|
|
289
|
+
const values: JSONArray = (jsonValue["kvlistValue"] as JSONObject)[
|
|
290
|
+
"values"
|
|
291
|
+
] as JSONArray;
|
|
292
|
+
finalObj = this.getAttributes({
|
|
293
|
+
prefixKeysWithString,
|
|
294
|
+
items: values,
|
|
295
|
+
});
|
|
296
|
+
} else if ("nullValue" in jsonValue) {
|
|
297
|
+
finalObj = null;
|
|
234
298
|
}
|
|
235
|
-
finalObj = flattenedFields;
|
|
236
|
-
}
|
|
237
|
-
// kvlistValue
|
|
238
|
-
else if (
|
|
239
|
-
value["kvlistValue"] &&
|
|
240
|
-
(value["kvlistValue"] as JSONObject)["values"]
|
|
241
|
-
) {
|
|
242
|
-
const values: JSONArray = (value["kvlistValue"] as JSONObject)[
|
|
243
|
-
"values"
|
|
244
|
-
] as JSONArray;
|
|
245
|
-
finalObj = this.getAttributes({
|
|
246
|
-
prefixKeysWithString,
|
|
247
|
-
items: values,
|
|
248
|
-
}) as Dictionary<AttributeType>;
|
|
249
|
-
} else if (value["nullValue"]) {
|
|
250
|
-
finalObj = null;
|
|
251
299
|
}
|
|
252
300
|
|
|
253
301
|
return finalObj;
|
|
@@ -48,10 +48,9 @@ export default class VMRunner {
|
|
|
48
48
|
|
|
49
49
|
vm.createContext(sandbox); // Contextify the object.
|
|
50
50
|
|
|
51
|
-
const script: string =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})()` || "";
|
|
51
|
+
const script: string = `(async()=>{
|
|
52
|
+
${code}
|
|
53
|
+
})()`;
|
|
55
54
|
|
|
56
55
|
const returnVal: any = await vm.runInContext(script, sandbox, {
|
|
57
56
|
timeout: options.timeout || 5000,
|
|
@@ -148,6 +148,7 @@ const DictionaryForm: FunctionComponent<ComponentProps> = (
|
|
|
148
148
|
title="Key"
|
|
149
149
|
required={true}
|
|
150
150
|
hideOptionalLabel={true}
|
|
151
|
+
className="block text-xs text-gray-500 font-normal flex justify-between"
|
|
151
152
|
/>
|
|
152
153
|
</div>
|
|
153
154
|
<AutocompleteTextInput
|
|
@@ -173,6 +174,7 @@ const DictionaryForm: FunctionComponent<ComponentProps> = (
|
|
|
173
174
|
title="Type"
|
|
174
175
|
hideOptionalLabel={true}
|
|
175
176
|
required={true}
|
|
177
|
+
className="block text-xs text-gray-500 font-normal flex justify-between"
|
|
176
178
|
/>
|
|
177
179
|
</div>
|
|
178
180
|
<Dropdown
|
|
@@ -206,6 +208,7 @@ const DictionaryForm: FunctionComponent<ComponentProps> = (
|
|
|
206
208
|
title="Value"
|
|
207
209
|
hideOptionalLabel={true}
|
|
208
210
|
required={true}
|
|
211
|
+
className="block text-xs text-gray-500 font-normal flex justify-between"
|
|
209
212
|
/>
|
|
210
213
|
</div>
|
|
211
214
|
{item.type === ValueType.Text && (
|
|
@@ -9,6 +9,7 @@ export interface ComponentProps {
|
|
|
9
9
|
description?: string | ReactElement | undefined;
|
|
10
10
|
isHeading?: boolean | undefined;
|
|
11
11
|
hideOptionalLabel?: boolean | undefined;
|
|
12
|
+
className?: string | undefined;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const FieldLabelElement: FunctionComponent<ComponentProps> = (
|
|
@@ -17,9 +18,12 @@ const FieldLabelElement: FunctionComponent<ComponentProps> = (
|
|
|
17
18
|
return (
|
|
18
19
|
<>
|
|
19
20
|
<label
|
|
20
|
-
className={
|
|
21
|
-
props.
|
|
22
|
-
|
|
21
|
+
className={
|
|
22
|
+
props.className ||
|
|
23
|
+
`block ${
|
|
24
|
+
props.isHeading ? "text-lg" : "text-sm"
|
|
25
|
+
} font-medium text-gray-700 flex justify-between`
|
|
26
|
+
}
|
|
23
27
|
>
|
|
24
28
|
<span>
|
|
25
29
|
{props.title}{" "}
|
|
@@ -265,7 +265,7 @@ const LogItem: FunctionComponent<ComponentProps> = (
|
|
|
265
265
|
if (isCollapsed) {
|
|
266
266
|
return (
|
|
267
267
|
<div
|
|
268
|
-
className={`group relative text-slate-200 flex items-center gap-2 cursor-pointer hover:bg-slate-800/40 px-2 py-0.5 border border-transparent border-l ${leftBorderColor} rounded-sm transition-colors duration-100`}
|
|
268
|
+
className={`group relative text-slate-200 flex items-center gap-2 cursor-pointer hover:bg-slate-800/40 px-2 py-0.5 border border-transparent border-l ${leftBorderColor} rounded-sm transition-colors duration-100 font-mono`}
|
|
269
269
|
onClick={toggleCollapsed}
|
|
270
270
|
role="button"
|
|
271
271
|
aria-expanded={!isCollapsed}
|
|
@@ -311,11 +311,14 @@ const LogItem: FunctionComponent<ComponentProps> = (
|
|
|
311
311
|
className={`${bodyColor} font-mono text-[13px] md:text-sm leading-5 tracking-tight subpixel-antialiased flex-1 min-w-0`}
|
|
312
312
|
>
|
|
313
313
|
{isBodyInJSON ? (
|
|
314
|
-
<div className="truncate" title={logBodyMinified}>
|
|
314
|
+
<div className="truncate font-mono" title={logBodyMinified}>
|
|
315
315
|
{logBodyMinified}
|
|
316
316
|
</div>
|
|
317
317
|
) : (
|
|
318
|
-
<div
|
|
318
|
+
<div
|
|
319
|
+
className="truncate font-mono"
|
|
320
|
+
title={props.log.body?.toString()}
|
|
321
|
+
>
|
|
319
322
|
{props.log.body?.toString()}
|
|
320
323
|
</div>
|
|
321
324
|
)}
|
|
@@ -339,7 +342,12 @@ const LogItem: FunctionComponent<ComponentProps> = (
|
|
|
339
342
|
className={`group relative text-slate-200 bg-slate-950/70 border ${leftBorderColor} border-l border-slate-900 rounded-sm p-2 hover:border-slate-700 transition-colors`}
|
|
340
343
|
>
|
|
341
344
|
{/* Header with Service Name and Close Indicator */}
|
|
342
|
-
<div
|
|
345
|
+
<div
|
|
346
|
+
className="flex items-center justify-between mb-1 pb-1 border-b border-slate-800/80"
|
|
347
|
+
onClick={() => {
|
|
348
|
+
toggleCollapsed();
|
|
349
|
+
}}
|
|
350
|
+
>
|
|
343
351
|
{serviceName && (
|
|
344
352
|
<div
|
|
345
353
|
className="text-[13px] font-semibold"
|