@axiom-lattice/gateway 2.1.28 → 2.1.30
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/.turbo/turbo-build.log +12 -8
- package/AGENTS.md +50 -0
- package/CHANGELOG.md +20 -0
- package/dist/chunk-FSASG3SB.mjs +94 -0
- package/dist/chunk-FSASG3SB.mjs.map +1 -0
- package/dist/config-F3FCBSPH.mjs +9 -0
- package/dist/config-F3FCBSPH.mjs.map +1 -0
- package/dist/index.js +1996 -209
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1878 -186
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/controllers/auth.ts +443 -0
- package/src/controllers/database-configs.ts +432 -0
- package/src/controllers/metrics-configs.ts +989 -0
- package/src/controllers/run.ts +6 -0
- package/src/controllers/tenants.ts +121 -0
- package/src/controllers/users.ts +135 -0
- package/src/controllers/workspace.ts +598 -0
- package/src/index.ts +2 -10
- package/src/routes/index.ts +21 -0
- package/src/services/agent_service.ts +71 -5
|
@@ -0,0 +1,989 @@
|
|
|
1
|
+
import { FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
+
import {
|
|
3
|
+
getStoreLattice,
|
|
4
|
+
metricsServerManager,
|
|
5
|
+
SemanticMetricsClient,
|
|
6
|
+
} from "@axiom-lattice/core";
|
|
7
|
+
import type {
|
|
8
|
+
MetricsServerConfigStore,
|
|
9
|
+
MetricsServerConfigEntry,
|
|
10
|
+
CreateMetricsServerConfigRequest,
|
|
11
|
+
UpdateMetricsServerConfigRequest,
|
|
12
|
+
MetricsServerConfig,
|
|
13
|
+
DataSource,
|
|
14
|
+
SemanticMetricsQueryRequest,
|
|
15
|
+
SemanticMetricsServerConfig,
|
|
16
|
+
} from "@axiom-lattice/protocols";
|
|
17
|
+
import { randomUUID } from "crypto";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Metrics Server Config Controller
|
|
21
|
+
* Handles metrics server configuration CRUD operations
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get tenant ID from request headers
|
|
26
|
+
*/
|
|
27
|
+
function getTenantId(request: FastifyRequest): string {
|
|
28
|
+
return (request.headers["x-tenant-id"] as string) || "default";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Metrics server config list response
|
|
33
|
+
*/
|
|
34
|
+
interface MetricsServerConfigListResponse {
|
|
35
|
+
success: boolean;
|
|
36
|
+
message: string;
|
|
37
|
+
data: {
|
|
38
|
+
records: MetricsServerConfigEntry[];
|
|
39
|
+
total: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Metrics server config response
|
|
45
|
+
*/
|
|
46
|
+
interface MetricsServerConfigResponse {
|
|
47
|
+
success: boolean;
|
|
48
|
+
message: string;
|
|
49
|
+
data?: MetricsServerConfigEntry;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Test connection response
|
|
54
|
+
*/
|
|
55
|
+
interface TestConnectionResponse {
|
|
56
|
+
success: boolean;
|
|
57
|
+
message: string;
|
|
58
|
+
data?: {
|
|
59
|
+
connected: boolean;
|
|
60
|
+
latency?: number;
|
|
61
|
+
error?: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* List available metrics response
|
|
67
|
+
*/
|
|
68
|
+
interface ListMetricsResponse {
|
|
69
|
+
success: boolean;
|
|
70
|
+
message: string;
|
|
71
|
+
data?: {
|
|
72
|
+
metrics: Array<{
|
|
73
|
+
name: string;
|
|
74
|
+
type?: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
}>;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Query metric data response
|
|
82
|
+
*/
|
|
83
|
+
interface QueryMetricDataResponse {
|
|
84
|
+
success: boolean;
|
|
85
|
+
message: string;
|
|
86
|
+
data?: {
|
|
87
|
+
metricName: string;
|
|
88
|
+
dataPoints: Array<{
|
|
89
|
+
timestamp: number;
|
|
90
|
+
value: number;
|
|
91
|
+
labels?: Record<string, string>;
|
|
92
|
+
}>;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Data sources list response
|
|
98
|
+
*/
|
|
99
|
+
interface DataSourcesResponse {
|
|
100
|
+
success: boolean;
|
|
101
|
+
message: string;
|
|
102
|
+
data?: {
|
|
103
|
+
datasources: DataSource[];
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Datasource metrics response
|
|
109
|
+
*/
|
|
110
|
+
interface DatasourceMetricsResponse {
|
|
111
|
+
success: boolean;
|
|
112
|
+
message: string;
|
|
113
|
+
data?: {
|
|
114
|
+
index: {
|
|
115
|
+
datasourceId: number;
|
|
116
|
+
datasourceName: string;
|
|
117
|
+
catalogVersion: string;
|
|
118
|
+
domainCategories: string[];
|
|
119
|
+
metrics: Array<{
|
|
120
|
+
metricName: string;
|
|
121
|
+
displayName: string;
|
|
122
|
+
domain: string;
|
|
123
|
+
shortDesc: string;
|
|
124
|
+
searchKeywords: string[];
|
|
125
|
+
registered: boolean;
|
|
126
|
+
}>;
|
|
127
|
+
};
|
|
128
|
+
details: Array<{
|
|
129
|
+
datasourceId: number;
|
|
130
|
+
metricName: string;
|
|
131
|
+
displayName: string;
|
|
132
|
+
domain: string;
|
|
133
|
+
description: string;
|
|
134
|
+
dataType: string;
|
|
135
|
+
format: string;
|
|
136
|
+
defaultTimeContext: {
|
|
137
|
+
timeDimension: string;
|
|
138
|
+
label: string;
|
|
139
|
+
granularity: string;
|
|
140
|
+
window: string;
|
|
141
|
+
supportedGrains: string[];
|
|
142
|
+
};
|
|
143
|
+
supportedDimensions: Array<{
|
|
144
|
+
dim_id: string;
|
|
145
|
+
field_name: string;
|
|
146
|
+
type: string;
|
|
147
|
+
filter_operators?: string[];
|
|
148
|
+
filter_example?: object;
|
|
149
|
+
group_by_example?: string;
|
|
150
|
+
}>;
|
|
151
|
+
aiAgentContext: {
|
|
152
|
+
polarity: string;
|
|
153
|
+
synonyms: string[];
|
|
154
|
+
thresholds: Array<{
|
|
155
|
+
metric: string;
|
|
156
|
+
operator: string;
|
|
157
|
+
value: number;
|
|
158
|
+
level: string;
|
|
159
|
+
}>;
|
|
160
|
+
diagnosticWorkflow: {
|
|
161
|
+
trigger: {
|
|
162
|
+
any_of: Array<{
|
|
163
|
+
metric: string;
|
|
164
|
+
operator: string;
|
|
165
|
+
value: number;
|
|
166
|
+
}>;
|
|
167
|
+
};
|
|
168
|
+
actions: Array<{
|
|
169
|
+
type: string;
|
|
170
|
+
metric?: string;
|
|
171
|
+
dimensions?: string[];
|
|
172
|
+
intent: string;
|
|
173
|
+
}>;
|
|
174
|
+
analysis_logic: string;
|
|
175
|
+
};
|
|
176
|
+
humanReadableExplanation: string;
|
|
177
|
+
};
|
|
178
|
+
}>;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Semantic query response
|
|
184
|
+
*/
|
|
185
|
+
interface SemanticQueryResponse {
|
|
186
|
+
success: boolean;
|
|
187
|
+
message: string;
|
|
188
|
+
data?: {
|
|
189
|
+
datasourceId: string | number;
|
|
190
|
+
metrics: string[];
|
|
191
|
+
dataPoints: Array<{
|
|
192
|
+
timestamp?: number;
|
|
193
|
+
value: number;
|
|
194
|
+
metricName?: string;
|
|
195
|
+
labels?: Record<string, string>;
|
|
196
|
+
}>;
|
|
197
|
+
metadata?: {
|
|
198
|
+
rowCount?: number;
|
|
199
|
+
queryTimeMs?: number;
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get all metrics server configs for a tenant
|
|
206
|
+
*/
|
|
207
|
+
export async function getMetricsServerConfigList(
|
|
208
|
+
request: FastifyRequest,
|
|
209
|
+
reply: FastifyReply
|
|
210
|
+
): Promise<MetricsServerConfigListResponse> {
|
|
211
|
+
const tenantId = getTenantId(request);
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
215
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
216
|
+
|
|
217
|
+
const configs = await store.getAllConfigs(tenantId);
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
success: true,
|
|
221
|
+
message: "Metrics server configurations retrieved successfully",
|
|
222
|
+
data: {
|
|
223
|
+
records: configs,
|
|
224
|
+
total: configs.length,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error("Failed to get metrics server configs:", error);
|
|
229
|
+
return {
|
|
230
|
+
success: false,
|
|
231
|
+
message: "Failed to retrieve metrics server configurations",
|
|
232
|
+
data: {
|
|
233
|
+
records: [],
|
|
234
|
+
total: 0,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get metrics server config by key
|
|
242
|
+
*/
|
|
243
|
+
export async function getMetricsServerConfig(
|
|
244
|
+
request: FastifyRequest,
|
|
245
|
+
reply: FastifyReply
|
|
246
|
+
): Promise<MetricsServerConfigResponse> {
|
|
247
|
+
const tenantId = getTenantId(request);
|
|
248
|
+
const { key } = request.params as { key: string };
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
252
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
253
|
+
|
|
254
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
255
|
+
|
|
256
|
+
if (!config) {
|
|
257
|
+
return {
|
|
258
|
+
success: false,
|
|
259
|
+
message: "Metrics server configuration not found",
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
success: true,
|
|
265
|
+
message: "Metrics server configuration retrieved successfully",
|
|
266
|
+
data: config,
|
|
267
|
+
};
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error("Failed to get metrics server config:", error);
|
|
270
|
+
return {
|
|
271
|
+
success: false,
|
|
272
|
+
message: "Failed to retrieve metrics server configuration",
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Create new metrics server config
|
|
279
|
+
*/
|
|
280
|
+
export async function createMetricsServerConfig(
|
|
281
|
+
request: FastifyRequest,
|
|
282
|
+
reply: FastifyReply
|
|
283
|
+
): Promise<MetricsServerConfigResponse> {
|
|
284
|
+
const tenantId = getTenantId(request);
|
|
285
|
+
const body = request.body as CreateMetricsServerConfigRequest & {
|
|
286
|
+
id?: string;
|
|
287
|
+
selectedDataSources?: string[];
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
292
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
293
|
+
|
|
294
|
+
// Check if key already exists
|
|
295
|
+
const existing = await store.getConfigByKey(tenantId, body.key);
|
|
296
|
+
if (existing) {
|
|
297
|
+
reply.code(409);
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
message: "Metrics server configuration with this key already exists",
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (body.config.type === "semantic" && !body.selectedDataSources) {
|
|
305
|
+
reply.code(400);
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
message: "selectedDataSources is required for semantic metrics servers",
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const id = body.id || randomUUID();
|
|
313
|
+
const configData: CreateMetricsServerConfigRequest = {
|
|
314
|
+
key: body.key,
|
|
315
|
+
name: body.name,
|
|
316
|
+
description: body.description,
|
|
317
|
+
config: body.config.type === "semantic"
|
|
318
|
+
? {
|
|
319
|
+
...body.config,
|
|
320
|
+
selectedDataSources: body.selectedDataSources || [],
|
|
321
|
+
} as SemanticMetricsServerConfig
|
|
322
|
+
: body.config,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const config = await store.createConfig(tenantId, id, configData);
|
|
326
|
+
|
|
327
|
+
// Auto-register to MetricsServerManager
|
|
328
|
+
try {
|
|
329
|
+
metricsServerManager.registerServer(config.key, config.config);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.warn("Failed to auto-register metrics server:", error);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
reply.code(201);
|
|
335
|
+
return {
|
|
336
|
+
success: true,
|
|
337
|
+
message: "Metrics server configuration created successfully",
|
|
338
|
+
data: config,
|
|
339
|
+
};
|
|
340
|
+
} catch (error) {
|
|
341
|
+
console.error("Failed to create metrics server config:", error);
|
|
342
|
+
return {
|
|
343
|
+
success: false,
|
|
344
|
+
message: "Failed to create metrics server configuration",
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Update metrics server config
|
|
351
|
+
*/
|
|
352
|
+
export async function updateMetricsServerConfig(
|
|
353
|
+
request: FastifyRequest,
|
|
354
|
+
reply: FastifyReply
|
|
355
|
+
): Promise<MetricsServerConfigResponse> {
|
|
356
|
+
const tenantId = getTenantId(request);
|
|
357
|
+
const { key } = request.params as { key: string };
|
|
358
|
+
const updates = request.body as Partial<UpdateMetricsServerConfigRequest> & {
|
|
359
|
+
selectedDataSources?: string[];
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
364
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
365
|
+
|
|
366
|
+
const existing = await store.getConfigByKey(tenantId, key);
|
|
367
|
+
if (!existing) {
|
|
368
|
+
reply.code(404);
|
|
369
|
+
return {
|
|
370
|
+
success: false,
|
|
371
|
+
message: "Metrics server configuration not found",
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const isSemantic = updates.config?.type === "semantic" ||
|
|
376
|
+
(existing.config.type === "semantic" && !updates.config?.type);
|
|
377
|
+
|
|
378
|
+
if (isSemantic && updates.selectedDataSources !== undefined) {
|
|
379
|
+
updates.config = {
|
|
380
|
+
...existing.config,
|
|
381
|
+
...updates.config,
|
|
382
|
+
type: "semantic",
|
|
383
|
+
selectedDataSources: updates.selectedDataSources,
|
|
384
|
+
} as SemanticMetricsServerConfig;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const updated = await store.updateConfig(tenantId, existing.id, updates);
|
|
388
|
+
|
|
389
|
+
if (!updated) {
|
|
390
|
+
return {
|
|
391
|
+
success: false,
|
|
392
|
+
message: "Failed to update metrics server configuration",
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (updates.config) {
|
|
397
|
+
try {
|
|
398
|
+
metricsServerManager.registerServer(updated.key, updated.config);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.warn("Failed to re-register metrics server:", error);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
success: true,
|
|
406
|
+
message: "Metrics server configuration updated successfully",
|
|
407
|
+
data: updated,
|
|
408
|
+
};
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error("Failed to update metrics server config:", error);
|
|
411
|
+
return {
|
|
412
|
+
success: false,
|
|
413
|
+
message: "Failed to update metrics server configuration",
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Delete metrics server config by key or id
|
|
420
|
+
*/
|
|
421
|
+
export async function deleteMetricsServerConfig(
|
|
422
|
+
request: FastifyRequest,
|
|
423
|
+
reply: FastifyReply
|
|
424
|
+
): Promise<MetricsServerConfigResponse> {
|
|
425
|
+
const tenantId = getTenantId(request);
|
|
426
|
+
const { keyOrId } = request.params as { keyOrId: string };
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
430
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
431
|
+
|
|
432
|
+
// Try to find by key first, then by id
|
|
433
|
+
let config = await store.getConfigByKey(tenantId, keyOrId);
|
|
434
|
+
let configKey = keyOrId;
|
|
435
|
+
|
|
436
|
+
if (!config) {
|
|
437
|
+
// Try to find by id
|
|
438
|
+
config = await store.getConfigById(tenantId, keyOrId);
|
|
439
|
+
if (config) {
|
|
440
|
+
configKey = config.key;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (!config) {
|
|
445
|
+
reply.code(404);
|
|
446
|
+
return {
|
|
447
|
+
success: false,
|
|
448
|
+
message: "Metrics server configuration not found",
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const deleted = await store.deleteConfig(tenantId, config.id);
|
|
453
|
+
|
|
454
|
+
if (!deleted) {
|
|
455
|
+
return {
|
|
456
|
+
success: false,
|
|
457
|
+
message: "Failed to delete metrics server configuration",
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Remove from MetricsServerManager
|
|
462
|
+
try {
|
|
463
|
+
if (metricsServerManager.hasServer(configKey)) {
|
|
464
|
+
metricsServerManager.removeServer(configKey);
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.warn("Failed to remove from MetricsServerManager:", error);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
success: true,
|
|
472
|
+
message: "Metrics server configuration deleted successfully",
|
|
473
|
+
};
|
|
474
|
+
} catch (error) {
|
|
475
|
+
console.error("Failed to delete metrics server config:", error);
|
|
476
|
+
return {
|
|
477
|
+
success: false,
|
|
478
|
+
message: "Failed to delete metrics server configuration",
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Test metrics server connection
|
|
485
|
+
*/
|
|
486
|
+
export async function testMetricsServerConnection(
|
|
487
|
+
request: FastifyRequest,
|
|
488
|
+
reply: FastifyReply
|
|
489
|
+
): Promise<TestConnectionResponse> {
|
|
490
|
+
const tenantId = getTenantId(request);
|
|
491
|
+
const { key } = request.params as { key: string };
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
495
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
496
|
+
|
|
497
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
498
|
+
if (!config) {
|
|
499
|
+
reply.code(404);
|
|
500
|
+
return {
|
|
501
|
+
success: false,
|
|
502
|
+
message: "Metrics server configuration not found",
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Register temporarily for testing
|
|
507
|
+
const testKey = `__test_${key}_${Date.now()}`;
|
|
508
|
+
metricsServerManager.registerServer(testKey, config.config);
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const client = metricsServerManager.getClient(testKey);
|
|
512
|
+
const result = await client.testConnection();
|
|
513
|
+
|
|
514
|
+
// Cleanup
|
|
515
|
+
metricsServerManager.removeServer(testKey);
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
success: true,
|
|
519
|
+
message: result.connected ? "Connection test successful" : "Connection test failed",
|
|
520
|
+
data: result,
|
|
521
|
+
};
|
|
522
|
+
} catch (error) {
|
|
523
|
+
// Cleanup on error
|
|
524
|
+
try {
|
|
525
|
+
metricsServerManager.removeServer(testKey);
|
|
526
|
+
} catch {}
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
success: true,
|
|
530
|
+
message: "Connection test failed",
|
|
531
|
+
data: {
|
|
532
|
+
connected: false,
|
|
533
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error("Failed to test metrics server connection:", error);
|
|
539
|
+
return {
|
|
540
|
+
success: false,
|
|
541
|
+
message: "Failed to test metrics server connection",
|
|
542
|
+
data: {
|
|
543
|
+
connected: false,
|
|
544
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* List available metrics from a server
|
|
552
|
+
*/
|
|
553
|
+
export async function listAvailableMetrics(
|
|
554
|
+
request: FastifyRequest,
|
|
555
|
+
reply: FastifyReply
|
|
556
|
+
): Promise<ListMetricsResponse> {
|
|
557
|
+
const tenantId = getTenantId(request);
|
|
558
|
+
const { key } = request.params as { key: string };
|
|
559
|
+
|
|
560
|
+
try {
|
|
561
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
562
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
563
|
+
|
|
564
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
565
|
+
if (!config) {
|
|
566
|
+
reply.code(404);
|
|
567
|
+
return {
|
|
568
|
+
success: false,
|
|
569
|
+
message: "Metrics server configuration not found",
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Ensure server is registered
|
|
574
|
+
if (!metricsServerManager.hasServer(key)) {
|
|
575
|
+
metricsServerManager.registerServer(key, config.config);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const client = metricsServerManager.getClient(key);
|
|
579
|
+
const metrics = await client.listMetrics();
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
success: true,
|
|
583
|
+
message: "Metrics retrieved successfully",
|
|
584
|
+
data: {
|
|
585
|
+
metrics: metrics.map(m => ({
|
|
586
|
+
name: m.name,
|
|
587
|
+
type: m.type,
|
|
588
|
+
description: m.description,
|
|
589
|
+
})),
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
} catch (error) {
|
|
593
|
+
console.error("Failed to list metrics:", error);
|
|
594
|
+
return {
|
|
595
|
+
success: false,
|
|
596
|
+
message: "Failed to retrieve metrics",
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Query metric data from a server
|
|
603
|
+
*/
|
|
604
|
+
export async function queryMetricsData(
|
|
605
|
+
request: FastifyRequest,
|
|
606
|
+
reply: FastifyReply
|
|
607
|
+
): Promise<QueryMetricDataResponse> {
|
|
608
|
+
const tenantId = getTenantId(request);
|
|
609
|
+
const { key } = request.params as { key: string };
|
|
610
|
+
const { metricName, startTime, endTime, step, labels } = request.body as {
|
|
611
|
+
metricName: string;
|
|
612
|
+
startTime?: number;
|
|
613
|
+
endTime?: number;
|
|
614
|
+
step?: number;
|
|
615
|
+
labels?: Record<string, string>;
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
620
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
621
|
+
|
|
622
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
623
|
+
if (!config) {
|
|
624
|
+
reply.code(404);
|
|
625
|
+
return {
|
|
626
|
+
success: false,
|
|
627
|
+
message: "Metrics server configuration not found",
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (!metricName) {
|
|
632
|
+
reply.code(400);
|
|
633
|
+
return {
|
|
634
|
+
success: false,
|
|
635
|
+
message: "metricName is required",
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Ensure server is registered
|
|
640
|
+
if (!metricsServerManager.hasServer(key)) {
|
|
641
|
+
metricsServerManager.registerServer(key, config.config);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const client = metricsServerManager.getClient(key);
|
|
645
|
+
const result = await client.queryMetricData(metricName, {
|
|
646
|
+
startTime,
|
|
647
|
+
endTime,
|
|
648
|
+
step,
|
|
649
|
+
labels,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
return {
|
|
653
|
+
success: true,
|
|
654
|
+
message: "Metric data retrieved successfully",
|
|
655
|
+
data: {
|
|
656
|
+
metricName: result.metricName,
|
|
657
|
+
dataPoints: result.dataPoints,
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
} catch (error) {
|
|
661
|
+
console.error("Failed to query metric data:", error);
|
|
662
|
+
return {
|
|
663
|
+
success: false,
|
|
664
|
+
message: "Failed to query metric data",
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Get data sources for a semantic metrics server
|
|
671
|
+
* GET /api/metrics-configs/:key/datasources
|
|
672
|
+
*/
|
|
673
|
+
export async function getDataSources(
|
|
674
|
+
request: FastifyRequest,
|
|
675
|
+
reply: FastifyReply
|
|
676
|
+
): Promise<DataSourcesResponse> {
|
|
677
|
+
const tenantId = getTenantId(request);
|
|
678
|
+
const { key } = request.params as { key: string };
|
|
679
|
+
|
|
680
|
+
try {
|
|
681
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
682
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
683
|
+
|
|
684
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
685
|
+
if (!config) {
|
|
686
|
+
reply.code(404);
|
|
687
|
+
return {
|
|
688
|
+
success: false,
|
|
689
|
+
message: "Metrics server configuration not found",
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (config.config.type !== "semantic") {
|
|
694
|
+
reply.code(400);
|
|
695
|
+
return {
|
|
696
|
+
success: false,
|
|
697
|
+
message: "This endpoint is only available for semantic metrics servers",
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const semanticConfig = config.config as SemanticMetricsServerConfig;
|
|
702
|
+
|
|
703
|
+
const client = new SemanticMetricsClient(semanticConfig);
|
|
704
|
+
const datasources = await client.getDataSources();
|
|
705
|
+
|
|
706
|
+
return {
|
|
707
|
+
success: true,
|
|
708
|
+
message: "Data sources retrieved successfully",
|
|
709
|
+
data: {
|
|
710
|
+
datasources,
|
|
711
|
+
},
|
|
712
|
+
};
|
|
713
|
+
} catch (error) {
|
|
714
|
+
console.error("Failed to get data sources:", error);
|
|
715
|
+
return {
|
|
716
|
+
success: false,
|
|
717
|
+
message: "Failed to retrieve data sources",
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Get metrics for a specific data source
|
|
724
|
+
* GET /api/metrics-configs/:key/datasources/:datasourceId/metrics
|
|
725
|
+
*/
|
|
726
|
+
export async function getDatasourceMetrics(
|
|
727
|
+
request: FastifyRequest,
|
|
728
|
+
reply: FastifyReply
|
|
729
|
+
): Promise<DatasourceMetricsResponse> {
|
|
730
|
+
const tenantId = getTenantId(request);
|
|
731
|
+
const { key, datasourceId } = request.params as { key: string; datasourceId: string };
|
|
732
|
+
|
|
733
|
+
try {
|
|
734
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
735
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
736
|
+
|
|
737
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
738
|
+
if (!config) {
|
|
739
|
+
reply.code(404);
|
|
740
|
+
return {
|
|
741
|
+
success: false,
|
|
742
|
+
message: "Metrics server configuration not found",
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (config.config.type !== "semantic") {
|
|
747
|
+
reply.code(400);
|
|
748
|
+
return {
|
|
749
|
+
success: false,
|
|
750
|
+
message: "This endpoint is only available for semantic metrics servers",
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const semanticConfig = config.config as SemanticMetricsServerConfig;
|
|
755
|
+
|
|
756
|
+
const client = new SemanticMetricsClient(semanticConfig);
|
|
757
|
+
const metrics = await client.getDatasourceMetrics(datasourceId);
|
|
758
|
+
|
|
759
|
+
return {
|
|
760
|
+
success: true,
|
|
761
|
+
message: "Datasource metrics retrieved successfully",
|
|
762
|
+
data: metrics,
|
|
763
|
+
};
|
|
764
|
+
} catch (error) {
|
|
765
|
+
console.error("Failed to get datasource metrics:", error);
|
|
766
|
+
return {
|
|
767
|
+
success: false,
|
|
768
|
+
message: "Failed to retrieve datasource metrics",
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Query semantic metrics data
|
|
775
|
+
* POST /api/metrics-configs/:key/semantic-query
|
|
776
|
+
*/
|
|
777
|
+
export async function querySemanticMetrics(
|
|
778
|
+
request: FastifyRequest,
|
|
779
|
+
reply: FastifyReply
|
|
780
|
+
): Promise<SemanticQueryResponse> {
|
|
781
|
+
const tenantId = getTenantId(request);
|
|
782
|
+
const { key } = request.params as { key: string };
|
|
783
|
+
const body = request.body as SemanticMetricsQueryRequest;
|
|
784
|
+
|
|
785
|
+
try {
|
|
786
|
+
const storeLattice = getStoreLattice("default", "metrics");
|
|
787
|
+
const store: MetricsServerConfigStore = storeLattice.store;
|
|
788
|
+
|
|
789
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
790
|
+
if (!config) {
|
|
791
|
+
reply.code(404);
|
|
792
|
+
return {
|
|
793
|
+
success: false,
|
|
794
|
+
message: "Metrics server configuration not found",
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (config.config.type !== "semantic") {
|
|
799
|
+
reply.code(400);
|
|
800
|
+
return {
|
|
801
|
+
success: false,
|
|
802
|
+
message: "This endpoint is only available for semantic metrics servers",
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (!body.datasourceId || !body.metrics || body.metrics.length === 0) {
|
|
807
|
+
reply.code(400);
|
|
808
|
+
return {
|
|
809
|
+
success: false,
|
|
810
|
+
message: "datasourceId and metrics array are required",
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const semanticConfig = config.config as SemanticMetricsServerConfig;
|
|
815
|
+
|
|
816
|
+
const client = new SemanticMetricsClient(semanticConfig);
|
|
817
|
+
const result = await client.semanticQuery(body);
|
|
818
|
+
|
|
819
|
+
return {
|
|
820
|
+
success: true,
|
|
821
|
+
message: "Semantic query executed successfully",
|
|
822
|
+
data: {
|
|
823
|
+
datasourceId: result.datasourceId,
|
|
824
|
+
metrics: result.metrics,
|
|
825
|
+
dataPoints: result.dataPoints,
|
|
826
|
+
metadata: result.metadata,
|
|
827
|
+
},
|
|
828
|
+
};
|
|
829
|
+
} catch (error) {
|
|
830
|
+
console.error("Failed to query semantic metrics:", error);
|
|
831
|
+
return {
|
|
832
|
+
success: false,
|
|
833
|
+
message: "Failed to query semantic metrics",
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Test datasources endpoint without saving config
|
|
840
|
+
* Used for multi-step configuration flow
|
|
841
|
+
* POST /api/metrics-configs/test-datasources
|
|
842
|
+
*/
|
|
843
|
+
export async function testSemanticDataSources(
|
|
844
|
+
request: FastifyRequest,
|
|
845
|
+
reply: FastifyReply
|
|
846
|
+
): Promise<DataSourcesResponse> {
|
|
847
|
+
const body = request.body as {
|
|
848
|
+
serverUrl: string;
|
|
849
|
+
apiKey?: string;
|
|
850
|
+
username?: string;
|
|
851
|
+
password?: string;
|
|
852
|
+
headers?: Record<string, string>;
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
try {
|
|
856
|
+
if (!body.serverUrl) {
|
|
857
|
+
reply.code(400);
|
|
858
|
+
return {
|
|
859
|
+
success: false,
|
|
860
|
+
message: "serverUrl is required",
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const testConfig: SemanticMetricsServerConfig = {
|
|
865
|
+
type: "semantic",
|
|
866
|
+
serverUrl: body.serverUrl,
|
|
867
|
+
apiKey: body.apiKey,
|
|
868
|
+
username: body.username,
|
|
869
|
+
password: body.password,
|
|
870
|
+
headers: body.headers,
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
const client = new SemanticMetricsClient(testConfig);
|
|
874
|
+
|
|
875
|
+
const datasources = await client.getDataSources();
|
|
876
|
+
|
|
877
|
+
return {
|
|
878
|
+
success: true,
|
|
879
|
+
message: "Data sources retrieved successfully",
|
|
880
|
+
data: {
|
|
881
|
+
datasources,
|
|
882
|
+
},
|
|
883
|
+
};
|
|
884
|
+
} catch (error) {
|
|
885
|
+
console.error("Failed to test datasources:", error);
|
|
886
|
+
return {
|
|
887
|
+
success: false,
|
|
888
|
+
message: `Failed to retrieve data sources: ${error instanceof Error ? error.message : String(error)}`,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Test datasource metrics endpoint without saving config
|
|
895
|
+
* Used for multi-step configuration flow
|
|
896
|
+
* POST /api/metrics-configs/test-datasources/:datasourceId/metrics
|
|
897
|
+
*/
|
|
898
|
+
export async function testDatasourceMetrics(
|
|
899
|
+
request: FastifyRequest,
|
|
900
|
+
reply: FastifyReply
|
|
901
|
+
): Promise<DatasourceMetricsResponse> {
|
|
902
|
+
const { datasourceId } = request.params as { datasourceId: string };
|
|
903
|
+
const body = request.body as {
|
|
904
|
+
serverUrl: string;
|
|
905
|
+
apiKey?: string;
|
|
906
|
+
username?: string;
|
|
907
|
+
password?: string;
|
|
908
|
+
headers?: Record<string, string>;
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
try {
|
|
912
|
+
if (!body.serverUrl) {
|
|
913
|
+
reply.code(400);
|
|
914
|
+
return {
|
|
915
|
+
success: false,
|
|
916
|
+
message: "serverUrl is required",
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const testConfig: SemanticMetricsServerConfig = {
|
|
921
|
+
type: "semantic",
|
|
922
|
+
serverUrl: body.serverUrl,
|
|
923
|
+
apiKey: body.apiKey,
|
|
924
|
+
username: body.username,
|
|
925
|
+
password: body.password,
|
|
926
|
+
headers: body.headers,
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
const client = new SemanticMetricsClient(testConfig);
|
|
930
|
+
const metrics = await client.getDatasourceMetrics(datasourceId);
|
|
931
|
+
|
|
932
|
+
return {
|
|
933
|
+
success: true,
|
|
934
|
+
message: "Datasource metrics retrieved successfully",
|
|
935
|
+
data: metrics,
|
|
936
|
+
};
|
|
937
|
+
} catch (error) {
|
|
938
|
+
console.error("Failed to test datasource metrics:", error);
|
|
939
|
+
return {
|
|
940
|
+
success: false,
|
|
941
|
+
message: `Failed to retrieve datasource metrics: ${error instanceof Error ? error.message : String(error)}`,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Register metrics server config routes
|
|
948
|
+
*/
|
|
949
|
+
export function registerMetricsServerConfigRoutes(app: any): void {
|
|
950
|
+
// Get all configs
|
|
951
|
+
app.get("/api/metrics-configs", getMetricsServerConfigList);
|
|
952
|
+
|
|
953
|
+
// Get config by key
|
|
954
|
+
app.get("/api/metrics-configs/:key", getMetricsServerConfig);
|
|
955
|
+
|
|
956
|
+
// Create config
|
|
957
|
+
app.post("/api/metrics-configs", createMetricsServerConfig);
|
|
958
|
+
|
|
959
|
+
// Update config
|
|
960
|
+
app.put("/api/metrics-configs/:key", updateMetricsServerConfig);
|
|
961
|
+
|
|
962
|
+
// Delete config by key or id
|
|
963
|
+
app.delete("/api/metrics-configs/:keyOrId", deleteMetricsServerConfig);
|
|
964
|
+
|
|
965
|
+
// Test connection
|
|
966
|
+
app.post("/api/metrics-configs/:key/test", testMetricsServerConnection);
|
|
967
|
+
|
|
968
|
+
// List available metrics
|
|
969
|
+
app.get("/api/metrics-configs/:key/metrics", listAvailableMetrics);
|
|
970
|
+
|
|
971
|
+
// Query metric data
|
|
972
|
+
app.post("/api/metrics-configs/:key/query", queryMetricsData);
|
|
973
|
+
|
|
974
|
+
// Semantic-specific routes
|
|
975
|
+
// Get data sources for a semantic server
|
|
976
|
+
app.get("/api/metrics-configs/:key/datasources", getDataSources);
|
|
977
|
+
|
|
978
|
+
// Get metrics meta for a specific datasource
|
|
979
|
+
app.get("/api/metrics-configs/:key/datasources/:datasourceId/meta", getDatasourceMetrics);
|
|
980
|
+
|
|
981
|
+
// Query semantic metrics data
|
|
982
|
+
app.post("/api/metrics-configs/:key/semantic-query", querySemanticMetrics);
|
|
983
|
+
|
|
984
|
+
// Test datasources without saving config (for multi-step config flow)
|
|
985
|
+
app.post("/api/metrics-configs/test-datasources", testSemanticDataSources);
|
|
986
|
+
|
|
987
|
+
// Test datasource metrics meta without saving config (for multi-step config flow)
|
|
988
|
+
app.post("/api/metrics-configs/test-datasources/:datasourceId/meta", testDatasourceMetrics);
|
|
989
|
+
}
|