adonisjs-server-stats 1.10.0 → 1.10.3
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/README.md +23 -14
- package/dist/core/config-utils.d.ts +8 -0
- package/dist/core/constants.d.ts +4 -0
- package/dist/core/dashboard-data-controller.d.ts +16 -0
- package/dist/core/dashboard-data-helpers.d.ts +12 -0
- package/dist/core/debug-data-controller.d.ts +4 -0
- package/dist/core/define-config-helpers.d.ts +25 -0
- package/dist/core/feature-detect-helpers.d.ts +36 -0
- package/dist/core/formatters-helpers.d.ts +23 -0
- package/dist/core/index.js +594 -509
- package/dist/core/log-utils-helpers.d.ts +13 -0
- package/dist/core/metrics.d.ts +3 -28
- package/dist/core/pagination.d.ts +0 -9
- package/dist/core/server-stats-controller.d.ts +6 -0
- package/dist/core/transmit-helpers.d.ts +7 -0
- package/dist/core/types-dashboard.d.ts +178 -0
- package/dist/core/types-diagnostics.d.ts +85 -0
- package/dist/core/types.d.ts +10 -442
- package/dist/react/{CacheSection-UCMptWyn.js → CacheSection-baMZotSn.js} +2 -2
- package/dist/react/CacheTab-2cw_rMzj.js +117 -0
- package/dist/react/{ConfigSection-DfFd-WRq.js → ConfigSection-DGgqjAal.js} +1 -1
- package/dist/react/{ConfigTab-Bdg8YMer.js → ConfigTab-H3OnYqmK.js} +1 -1
- package/dist/react/CustomPaneTab-B6r7ha0u.js +98 -0
- package/dist/react/{EmailsSection-CM7stSyh.js → EmailsSection-C-UZISG-.js} +2 -2
- package/dist/react/EmailsTab-DbK4Eobn.js +139 -0
- package/dist/react/{EventsSection-ByQ-9blq.js → EventsSection-C7RQW_LY.js} +2 -2
- package/dist/react/EventsTab-CfVr7AiM.js +57 -0
- package/dist/react/{FilterBar-DQRXpWrb.js → FilterBar-CQ7bD669.js} +15 -15
- package/dist/react/{JobsSection-DF3qEv9O.js → JobsSection-CQHNK_Ls.js} +2 -2
- package/dist/react/{JobsTab-BbrBWIOb.js → JobsTab-znzf6jzk.js} +54 -42
- package/dist/react/{LogsSection-DcFTZY7b.js → LogsSection-Dmm3rE2B.js} +9 -3
- package/dist/react/LogsTab-D8unMV5P.js +108 -0
- package/dist/react/{OverviewSection-C4T1ur51.js → OverviewSection-ABP9ueBo.js} +1 -1
- package/dist/react/{QueriesSection-PswteoF9.js → QueriesSection-CnmSkznA.js} +2 -2
- package/dist/react/{QueriesTab-osLUWd4L.js → QueriesTab-BQzcxEiW.js} +37 -40
- package/dist/react/{RelatedLogs-DFDOyUMr.js → RelatedLogs-3A8RuGKH.js} +15 -3
- package/dist/react/{RequestsSection-Nag30rEA.js → RequestsSection-kW79_M7k.js} +3 -3
- package/dist/react/{RoutesSection-BUSkM6PY.js → RoutesSection-BRhxrtjZ.js} +2 -2
- package/dist/react/RoutesTab-CpYH5lUw.js +68 -0
- package/dist/react/{TimelineTab-Covg5weo.js → TimelineTab-DjLR35Ce.js} +47 -53
- package/dist/react/index-CsImORX6.js +1121 -0
- package/dist/react/index.js +1 -1
- package/dist/react/react/components/{Dashboard/shared → shared}/FilterBar.d.ts +4 -3
- package/dist/react/react/hooks/useDashboardData.d.ts +4 -8
- package/dist/react/style.css +1 -1
- package/dist/src/collectors/app_collector.d.ts +0 -8
- package/dist/src/collectors/app_collector.js +45 -52
- package/dist/src/collectors/auto_detect.d.ts +0 -23
- package/dist/src/collectors/auto_detect.js +33 -55
- package/dist/src/collectors/db_pool_collector.d.ts +14 -16
- package/dist/src/collectors/db_pool_collector.js +72 -57
- package/dist/src/collectors/log_collector.d.ts +0 -47
- package/dist/src/collectors/log_collector.js +36 -65
- package/dist/src/collectors/queue_collector.d.ts +0 -20
- package/dist/src/collectors/queue_collector.js +60 -76
- package/dist/src/collectors/redis_collector.d.ts +10 -10
- package/dist/src/collectors/redis_collector.js +69 -66
- package/dist/src/config/deprecation_migration.d.ts +7 -0
- package/dist/src/config/deprecation_migration.js +201 -0
- package/dist/src/controller/debug_controller.d.ts +1 -1
- package/dist/src/controller/debug_controller.js +87 -81
- package/dist/src/dashboard/cache_handlers.d.ts +14 -0
- package/dist/src/dashboard/cache_handlers.js +52 -0
- package/dist/src/dashboard/chart_aggregator.d.ts +0 -7
- package/dist/src/dashboard/chart_aggregator.js +68 -50
- package/dist/src/dashboard/coalesce_cache.d.ts +25 -0
- package/dist/src/dashboard/coalesce_cache.js +47 -0
- package/dist/src/dashboard/dashboard_controller.d.ts +11 -37
- package/dist/src/dashboard/dashboard_controller.js +51 -544
- package/dist/src/dashboard/dashboard_page_assets.d.ts +17 -0
- package/dist/src/dashboard/dashboard_page_assets.js +51 -0
- package/dist/src/dashboard/dashboard_store.d.ts +19 -218
- package/dist/src/dashboard/dashboard_store.js +115 -1116
- package/dist/src/dashboard/dashboard_types.d.ts +83 -0
- package/dist/src/dashboard/dashboard_types.js +4 -0
- package/dist/src/dashboard/detail_queries.d.ts +19 -0
- package/dist/src/dashboard/detail_queries.js +98 -0
- package/dist/src/dashboard/email_event_builder.d.ts +8 -0
- package/dist/src/dashboard/email_event_builder.js +65 -0
- package/dist/src/dashboard/explain_query.d.ts +8 -0
- package/dist/src/dashboard/explain_query.js +22 -0
- package/dist/src/dashboard/filter_handlers.d.ts +23 -0
- package/dist/src/dashboard/filter_handlers.js +56 -0
- package/dist/src/dashboard/filtered_queries.d.ts +15 -0
- package/dist/src/dashboard/filtered_queries.js +155 -0
- package/dist/src/dashboard/flush_manager.d.ts +25 -0
- package/dist/src/dashboard/flush_manager.js +107 -0
- package/dist/src/dashboard/format_helpers.d.ts +126 -0
- package/dist/src/dashboard/format_helpers.js +140 -0
- package/dist/src/dashboard/inspector_manager.d.ts +36 -0
- package/dist/src/dashboard/inspector_manager.js +102 -0
- package/dist/src/dashboard/integrations/config_inspector.js +11 -13
- package/dist/src/dashboard/integrations/queue_inspector.d.ts +3 -3
- package/dist/src/dashboard/integrations/queue_inspector.js +13 -10
- package/dist/src/dashboard/jobs_handlers.d.ts +14 -0
- package/dist/src/dashboard/jobs_handlers.js +61 -0
- package/dist/src/dashboard/knex_factory.d.ts +18 -0
- package/dist/src/dashboard/knex_factory.js +91 -0
- package/dist/src/dashboard/migrator.js +30 -159
- package/dist/src/dashboard/migrator_tables.d.ts +19 -0
- package/dist/src/dashboard/migrator_tables.js +153 -0
- package/dist/src/dashboard/overview_queries.d.ts +66 -0
- package/dist/src/dashboard/overview_queries.js +155 -0
- package/dist/src/dashboard/overview_query_runners.d.ts +25 -0
- package/dist/src/dashboard/overview_query_runners.js +84 -0
- package/dist/src/dashboard/overview_store_queries.d.ts +40 -0
- package/dist/src/dashboard/overview_store_queries.js +69 -0
- package/dist/src/dashboard/paginate_helper.d.ts +12 -0
- package/dist/src/dashboard/paginate_helper.js +33 -0
- package/dist/src/dashboard/query_explain_handler.d.ts +10 -0
- package/dist/src/dashboard/query_explain_handler.js +80 -0
- package/dist/src/dashboard/read_queries.d.ts +32 -0
- package/dist/src/dashboard/read_queries.js +107 -0
- package/dist/src/dashboard/saved_filter_queries.d.ts +10 -0
- package/dist/src/dashboard/saved_filter_queries.js +24 -0
- package/dist/src/dashboard/storage_stats.d.ts +41 -0
- package/dist/src/dashboard/storage_stats.js +81 -0
- package/dist/src/dashboard/write_queue.d.ts +106 -0
- package/dist/src/dashboard/write_queue.js +225 -0
- package/dist/src/data/data_access.d.ts +2 -39
- package/dist/src/data/data_access.js +17 -193
- package/dist/src/data/data_access_helpers.d.ts +130 -0
- package/dist/src/data/data_access_helpers.js +212 -0
- package/dist/src/debug/debug_store.js +37 -32
- package/dist/src/debug/email_collector.d.ts +1 -10
- package/dist/src/debug/email_collector.js +78 -81
- package/dist/src/debug/event_collector.d.ts +0 -9
- package/dist/src/debug/event_collector.js +79 -62
- package/dist/src/debug/query_collector.js +23 -19
- package/dist/src/debug/route_inspector.d.ts +1 -5
- package/dist/src/debug/route_inspector.js +50 -51
- package/dist/src/debug/trace_collector.d.ts +9 -1
- package/dist/src/debug/trace_collector.js +21 -15
- package/dist/src/debug/types.d.ts +1 -1
- package/dist/src/define_config.d.ts +0 -65
- package/dist/src/define_config.js +93 -333
- package/dist/src/edge/client/dashboard.js +2 -2
- package/dist/src/edge/client/debug-panel-deferred.js +1 -1
- package/dist/src/edge/client/stats-bar.js +1 -1
- package/dist/src/edge/client-vue/dashboard.js +5 -5
- package/dist/src/edge/client-vue/debug-panel-deferred.js +3 -3
- package/dist/src/edge/client-vue/stats-bar.js +3 -3
- package/dist/src/edge/plugin.d.ts +0 -16
- package/dist/src/edge/plugin.js +57 -64
- package/dist/src/engine/request_metrics.d.ts +1 -0
- package/dist/src/engine/request_metrics.js +32 -42
- package/dist/src/middleware/request_tracking_middleware.d.ts +2 -8
- package/dist/src/middleware/request_tracking_middleware.js +65 -93
- package/dist/src/provider/auth_middleware_detector.d.ts +16 -0
- package/dist/src/provider/auth_middleware_detector.js +97 -0
- package/dist/src/provider/boot_helpers.d.ts +20 -0
- package/dist/src/provider/boot_helpers.js +91 -0
- package/dist/src/provider/boot_initializer.d.ts +28 -0
- package/dist/src/provider/boot_initializer.js +35 -0
- package/dist/src/provider/dashboard_init.d.ts +30 -0
- package/dist/src/provider/dashboard_init.js +138 -0
- package/dist/src/provider/dashboard_setup.d.ts +25 -0
- package/dist/src/provider/dashboard_setup.js +78 -0
- package/dist/src/provider/diagnostics.d.ts +134 -0
- package/dist/src/provider/diagnostics.js +127 -0
- package/dist/src/provider/email_bridge.d.ts +43 -0
- package/dist/src/provider/email_bridge.js +80 -0
- package/dist/src/provider/email_helpers.d.ts +13 -0
- package/dist/src/provider/email_helpers.js +68 -0
- package/dist/src/provider/pino_hook.d.ts +17 -0
- package/dist/src/provider/pino_hook.js +35 -0
- package/dist/src/provider/provider_helpers_extra.d.ts +47 -0
- package/dist/src/provider/provider_helpers_extra.js +177 -0
- package/dist/src/provider/server_stats_provider.d.ts +39 -85
- package/dist/src/provider/server_stats_provider.js +132 -951
- package/dist/src/provider/shutdown_helpers.d.ts +43 -0
- package/dist/src/provider/shutdown_helpers.js +70 -0
- package/dist/src/provider/toolbar_setup.d.ts +57 -0
- package/dist/src/provider/toolbar_setup.js +141 -0
- package/dist/src/routes/dashboard_routes.d.ts +14 -0
- package/dist/src/routes/dashboard_routes.js +197 -0
- package/dist/src/routes/debug_routes.d.ts +14 -0
- package/dist/src/routes/debug_routes.js +101 -0
- package/dist/src/routes/register_routes.d.ts +0 -78
- package/dist/src/routes/register_routes.js +22 -352
- package/dist/src/routes/stats_routes.d.ts +5 -0
- package/dist/src/routes/stats_routes.js +14 -0
- package/dist/src/styles/components.css +96 -0
- package/dist/src/styles/dashboard.css +8 -90
- package/dist/src/styles/debug-panel.css +1 -31
- package/dist/src/types.d.ts +305 -14
- package/dist/vue/{CacheSection-oFAJL3mo.js → CacheSection-ITqvpfH5.js} +1 -1
- package/dist/vue/{ConfigSection-BhfJ4KqL.js → ConfigSection-DTn3GslE.js} +1 -1
- package/dist/vue/{EmailsSection-BcNyhyHs.js → EmailsSection-DtLJ4XoS.js} +1 -1
- package/dist/vue/{EventsSection-r60Q5Lmu.js → EventsSection-BOYYz0Ty.js} +1 -1
- package/dist/vue/{JobsSection-BHL-hkQw.js → JobsSection-BazTxcJL.js} +1 -1
- package/dist/vue/{LogsSection-DRMGzJmg.js → LogsSection-D55PjTKX.js} +9 -3
- package/dist/vue/{LogsTab-Bg3o0Mm6.js → LogsTab-47zEK7jL.js} +4 -1
- package/dist/vue/{OverviewSection-CXh6Ja1B.js → OverviewSection-1uBKo-Tu.js} +1 -1
- package/dist/vue/{QueriesSection-IodIsCJ-.js → QueriesSection-rpoZ4ogd.js} +1 -1
- package/dist/vue/{RequestsSection-BPuMdmMc.js → RequestsSection-x7LvT0MC.js} +1 -1
- package/dist/vue/{RoutesSection-NKo3Rbq3.js → RoutesSection-CCD0zZqQ.js} +1 -1
- package/dist/vue/composables/useDashboardData.d.ts +12 -23
- package/dist/vue/index-C8MxnS7Q.js +1232 -0
- package/dist/vue/index.js +1 -1
- package/dist/vue/style.css +1 -1
- package/package.json +1 -1
- package/dist/react/CacheTab-CA8LB1J5.js +0 -123
- package/dist/react/CustomPaneTab-Bxtv_8Rw.js +0 -104
- package/dist/react/EmailsTab-BDhEiomM.js +0 -153
- package/dist/react/EventsTab-CMfY98Rl.js +0 -63
- package/dist/react/LogsTab-CicucmVk.js +0 -103
- package/dist/react/RoutesTab-DgVzd2PZ.js +0 -74
- package/dist/react/index-Cflz9Ebj.js +0 -1069
- package/dist/vue/index-Dtgysd26.js +0 -1229
|
@@ -1,126 +1,62 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { safeParseJson, safeParseJsonArray } from '../utils/json_helpers.js';
|
|
5
1
|
import { log } from '../utils/logger.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
2
|
+
import { clamp } from '../utils/math_helpers.js';
|
|
3
|
+
import { handleCacheStats, handleCacheKey, handleCacheKeyDelete } from './cache_handlers.js';
|
|
4
|
+
import { DashboardPageAssets } from './dashboard_page_assets.js';
|
|
5
|
+
import { handleSavedFilters, handleCreateSavedFilter, handleDeleteSavedFilter, } from './filter_handlers.js';
|
|
6
|
+
import { paginatedResponse, emptyPage, emptyOverview, formatRequest, formatQuery, formatTrace, formatLog, mapChartBucket, buildSparklines, formatGroupedQuery, } from './format_helpers.js';
|
|
7
|
+
import { InspectorManager } from './inspector_manager.js';
|
|
9
8
|
import { ConfigInspector } from './integrations/config_inspector.js';
|
|
10
|
-
import {
|
|
9
|
+
import { handleJobs, handleJobDetail, handleJobRetry } from './jobs_handlers.js';
|
|
10
|
+
import { handleQueryExplain } from './query_explain_handler.js';
|
|
11
11
|
const warnedDbReads = new Set();
|
|
12
|
-
const SRC_DIR = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const EDGE_DIR = join(SRC_DIR, '..', 'edge');
|
|
14
|
-
const STYLES_DIR = join(SRC_DIR, '..', 'styles');
|
|
15
|
-
function paginatedResponse(data, total, page, perPage) {
|
|
16
|
-
return {
|
|
17
|
-
data,
|
|
18
|
-
meta: { total, page, perPage, lastPage: Math.max(1, Math.ceil(total / perPage)) },
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
function emptyPage(page, perPage) {
|
|
22
|
-
return { data: [], meta: { total: 0, page, perPage, lastPage: 1 } };
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Controller for the full-page dashboard.
|
|
26
|
-
*
|
|
27
|
-
* Serves the dashboard HTML page and dashboard-specific JSON API
|
|
28
|
-
* endpoints (overview, requests, grouped queries, query explain,
|
|
29
|
-
* cache, jobs, config, saved filters).
|
|
30
|
-
*
|
|
31
|
-
* Data resource endpoints (queries, events, emails, traces, routes,
|
|
32
|
-
* logs) are handled by the unified ApiController — see
|
|
33
|
-
* `src/controller/api_controller.ts`.
|
|
34
|
-
*/
|
|
35
12
|
export default class DashboardController {
|
|
36
13
|
dashboardStore;
|
|
37
14
|
app;
|
|
38
|
-
cacheInspector = null;
|
|
39
|
-
queueInspector = null;
|
|
40
15
|
configInspector;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
cachedCss = null;
|
|
44
|
-
cachedJs = null;
|
|
45
|
-
cachedTransmitClient = null;
|
|
16
|
+
inspectors;
|
|
17
|
+
pageAssets;
|
|
46
18
|
constructor(dashboardStore, app) {
|
|
47
19
|
this.dashboardStore = dashboardStore;
|
|
48
20
|
this.app = app;
|
|
49
21
|
this.configInspector = new ConfigInspector(app);
|
|
22
|
+
this.inspectors = new InspectorManager(app);
|
|
23
|
+
this.pageAssets = new DashboardPageAssets();
|
|
50
24
|
}
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Page
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
25
|
async page(ctx) {
|
|
55
|
-
if (!this.checkAccess(ctx))
|
|
26
|
+
if (!this.checkAccess(ctx))
|
|
56
27
|
return ctx.response.forbidden({ error: 'Access denied' });
|
|
57
|
-
}
|
|
58
28
|
const config = this.app.config.get('server_stats');
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const components = readFileSync(join(STYLES_DIR, 'components.css'), 'utf-8');
|
|
63
|
-
const utilities = readFileSync(join(STYLES_DIR, 'utilities.css'), 'utf-8');
|
|
64
|
-
const dashboard = readFileSync(join(STYLES_DIR, 'dashboard.css'), 'utf-8');
|
|
65
|
-
this.cachedCss = tokens + '\n' + components + '\n' + utilities + '\n' + dashboard;
|
|
66
|
-
}
|
|
67
|
-
if (!this.cachedJs) {
|
|
68
|
-
const renderer = toolbarConfig.renderer || 'preact';
|
|
69
|
-
const clientDir = renderer === 'vue' ? 'client-vue' : 'client';
|
|
70
|
-
this.cachedJs = readFileSync(join(EDGE_DIR, clientDir, 'dashboard.js'), 'utf-8');
|
|
71
|
-
}
|
|
72
|
-
if (this.cachedTransmitClient === null) {
|
|
73
|
-
this.cachedTransmitClient = loadTransmitClient(this.app.makePath('package.json'));
|
|
74
|
-
if (this.cachedTransmitClient) {
|
|
75
|
-
log.info('Transmit client loaded for dashboard');
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
log.info('Dashboard will use polling. Install @adonisjs/transmit-client for real-time updates.');
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
const dashPath = this.getDashboardPath();
|
|
29
|
+
const tc = config?.devToolbar ?? {};
|
|
30
|
+
const renderer = tc.renderer || 'preact';
|
|
31
|
+
const dp = this.getDashboardPath();
|
|
82
32
|
return ctx.view.render('ss::dashboard', {
|
|
83
|
-
css: this.
|
|
84
|
-
js: this.
|
|
85
|
-
transmitClient: this.
|
|
33
|
+
css: this.pageAssets.getCss(),
|
|
34
|
+
js: this.pageAssets.getJs(renderer),
|
|
35
|
+
transmitClient: this.pageAssets.getTransmitClient(this.app.makePath('package.json')),
|
|
86
36
|
dashConfig: {
|
|
87
37
|
baseUrl: '',
|
|
88
|
-
dashboardEndpoint:
|
|
89
|
-
debugEndpoint:
|
|
38
|
+
dashboardEndpoint: dp + '/api',
|
|
39
|
+
debugEndpoint: tc.debugEndpoint || '/admin/api/debug',
|
|
90
40
|
channelName: 'server-stats/dashboard',
|
|
91
41
|
backUrl: '/',
|
|
92
42
|
},
|
|
93
43
|
});
|
|
94
44
|
}
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
// Overview
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
45
|
async overview({ request, response }) {
|
|
99
46
|
return this.withDb(response, 'overview', emptyOverview(), async () => {
|
|
100
47
|
const range = request.qs().range || '1h';
|
|
101
|
-
// Sequential awaits — with a single-connection SQLite pool, Promise.all
|
|
102
|
-
// creates concurrent pendingAcquires that thrash tarn's scheduler.
|
|
103
48
|
const overview = await this.dashboardStore.getOverviewMetrics(range);
|
|
104
49
|
if (!overview)
|
|
105
50
|
return emptyOverview();
|
|
106
51
|
const widgets = await this.dashboardStore.getOverviewWidgets(range);
|
|
107
52
|
const sparklineData = await this.dashboardStore.getSparklineData(range);
|
|
108
|
-
// Cache and queue inspectors use Redis/BullMQ (not SQLite), so
|
|
109
|
-
// Promise.all is fine here — different connection pools.
|
|
110
53
|
const [cacheStats, jobQueueStatus] = await Promise.all([
|
|
111
|
-
this.fetchCacheOverview(),
|
|
112
|
-
this.fetchQueueOverview(),
|
|
54
|
+
this.inspectors.fetchCacheOverview(),
|
|
55
|
+
this.inspectors.fetchQueueOverview(),
|
|
113
56
|
]);
|
|
114
57
|
return {
|
|
115
58
|
...overview,
|
|
116
|
-
sparklines:
|
|
117
|
-
avgResponseTime: sparklineData.map((m) => m.avg_duration),
|
|
118
|
-
p95ResponseTime: sparklineData.map((m) => m.p95_duration),
|
|
119
|
-
requestsPerMinute: sparklineData.map((m) => m.request_count),
|
|
120
|
-
errorRate: sparklineData.map((m) => m.request_count > 0
|
|
121
|
-
? round((m.error_count / m.request_count) * 100)
|
|
122
|
-
: 0),
|
|
123
|
-
},
|
|
59
|
+
sparklines: buildSparklines(sparklineData),
|
|
124
60
|
...widgets,
|
|
125
61
|
cacheStats,
|
|
126
62
|
jobQueueStatus,
|
|
@@ -131,22 +67,9 @@ export default class DashboardController {
|
|
|
131
67
|
const range = request.qs().range || '1h';
|
|
132
68
|
return this.withDb(response, 'overviewChart', { range, buckets: [] }, async () => {
|
|
133
69
|
const buckets = await this.dashboardStore.getChartData(range);
|
|
134
|
-
return {
|
|
135
|
-
range,
|
|
136
|
-
buckets: buckets.map((b) => ({
|
|
137
|
-
bucket: b.bucket,
|
|
138
|
-
requestCount: b.request_count,
|
|
139
|
-
avgDuration: b.avg_duration,
|
|
140
|
-
p95Duration: b.p95_duration,
|
|
141
|
-
errorCount: b.error_count,
|
|
142
|
-
queryCount: b.query_count,
|
|
143
|
-
})),
|
|
144
|
-
};
|
|
70
|
+
return { range, buckets: buckets.map(mapChartBucket) };
|
|
145
71
|
});
|
|
146
72
|
}
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
// Requests
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
73
|
async requests({ request, response }) {
|
|
151
74
|
const qs = request.qs();
|
|
152
75
|
const page = Math.max(1, Number(qs.page) || 1);
|
|
@@ -162,9 +85,8 @@ export default class DashboardController {
|
|
|
162
85
|
});
|
|
163
86
|
}
|
|
164
87
|
async requestDetail({ params, response }) {
|
|
165
|
-
if (!this.dashboardStore.isReady())
|
|
88
|
+
if (!this.dashboardStore.isReady())
|
|
166
89
|
return response.notFound({ error: 'Not found' });
|
|
167
|
-
}
|
|
168
90
|
try {
|
|
169
91
|
const detail = await this.dashboardStore.getRequestDetail(Number(params.id));
|
|
170
92
|
if (!detail)
|
|
@@ -184,301 +106,49 @@ export default class DashboardController {
|
|
|
184
106
|
return this.withDb(response, 'queriesGrouped', { groups: [] }, async () => {
|
|
185
107
|
const qs = request.qs();
|
|
186
108
|
const limit = clamp(Number(qs.limit) || 50, 1, 200);
|
|
187
|
-
const
|
|
188
|
-
const search = qs.search || undefined;
|
|
189
|
-
const groups = await this.dashboardStore.getQueriesGrouped(limit, sort, search);
|
|
109
|
+
const groups = await this.dashboardStore.getQueriesGrouped(limit, qs.sort || 'total_duration', qs.search || undefined);
|
|
190
110
|
const totalTime = groups.reduce((sum, g) => sum + (g.total_duration || 0), 0);
|
|
191
111
|
return {
|
|
192
|
-
groups: groups.map((g) => (
|
|
193
|
-
sqlNormalized: g.sql_normalized,
|
|
194
|
-
count: g.count,
|
|
195
|
-
avgDuration: round(g.avg_duration),
|
|
196
|
-
minDuration: round(g.min_duration),
|
|
197
|
-
maxDuration: round(g.max_duration),
|
|
198
|
-
totalDuration: round(g.total_duration),
|
|
199
|
-
percentOfTotal: totalTime > 0 ? round((g.total_duration / totalTime) * 100) : 0,
|
|
200
|
-
})),
|
|
112
|
+
groups: groups.map((g) => formatGroupedQuery(g, totalTime)),
|
|
201
113
|
};
|
|
202
114
|
});
|
|
203
115
|
}
|
|
204
|
-
async queryExplain(
|
|
205
|
-
|
|
206
|
-
return response.notFound({ error: 'Not found' });
|
|
207
|
-
}
|
|
208
|
-
try {
|
|
209
|
-
const db = this.dashboardStore.getDb();
|
|
210
|
-
if (!db)
|
|
211
|
-
return response.notFound({ error: 'Not found' });
|
|
212
|
-
const id = Number(params.id);
|
|
213
|
-
const query = await db('server_stats_queries')
|
|
214
|
-
.where('id', id)
|
|
215
|
-
.first();
|
|
216
|
-
if (!query)
|
|
217
|
-
return response.notFound({ error: 'Query not found' });
|
|
218
|
-
const sqlTrimmed = query.sql_text.trim().toUpperCase();
|
|
219
|
-
if (!sqlTrimmed.startsWith('SELECT')) {
|
|
220
|
-
return response.badRequest({
|
|
221
|
-
error: 'EXPLAIN is only supported for SELECT queries',
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
let appDb;
|
|
225
|
-
try {
|
|
226
|
-
const lucid = await this.app.container.make('lucid.db');
|
|
227
|
-
appDb = lucid
|
|
228
|
-
.connection()
|
|
229
|
-
.getWriteClient();
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
return response.serviceUnavailable({
|
|
233
|
-
error: 'App database connection not available',
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
let bindings = [];
|
|
237
|
-
if (query.bindings) {
|
|
238
|
-
try {
|
|
239
|
-
bindings = JSON.parse(query.bindings);
|
|
240
|
-
}
|
|
241
|
-
catch {
|
|
242
|
-
// If bindings can't be parsed, run without them
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
const explainResult = await appDb.raw(`EXPLAIN (FORMAT JSON) ${query.sql_text}`, bindings);
|
|
246
|
-
let plan = [];
|
|
247
|
-
const rawRows = explainResult?.rows ??
|
|
248
|
-
(Array.isArray(explainResult) ? explainResult : []);
|
|
249
|
-
if (rawRows.length > 0 && rawRows[0]['QUERY PLAN']) {
|
|
250
|
-
plan = rawRows[0]['QUERY PLAN'];
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
plan = rawRows;
|
|
254
|
-
}
|
|
255
|
-
return response.json({ queryId: id, sql: query.sql_text, plan });
|
|
256
|
-
}
|
|
257
|
-
catch (error) {
|
|
258
|
-
return response.internalServerError({
|
|
259
|
-
error: 'EXPLAIN failed',
|
|
260
|
-
message: error?.message ?? 'Unknown error',
|
|
261
|
-
});
|
|
262
|
-
}
|
|
116
|
+
async queryExplain(ctx) {
|
|
117
|
+
return handleQueryExplain(this.dashboardStore, this.app, ctx);
|
|
263
118
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
// ---------------------------------------------------------------------------
|
|
267
|
-
async cacheStats({ request, response }) {
|
|
268
|
-
const inspector = await this.getInspector('cache');
|
|
269
|
-
if (!inspector) {
|
|
270
|
-
return response.json({ available: false, stats: null, keys: [] });
|
|
271
|
-
}
|
|
272
|
-
const qs = request.qs();
|
|
273
|
-
const searchTerm = qs.search || qs.pattern || '';
|
|
274
|
-
const pattern = searchTerm ? `*${searchTerm}*` : '*';
|
|
275
|
-
const cursor = qs.cursor || '0';
|
|
276
|
-
const count = clamp(Number(qs.count) || 100, 1, 500);
|
|
277
|
-
try {
|
|
278
|
-
const [stats, keyList] = await Promise.all([
|
|
279
|
-
inspector.getStats(),
|
|
280
|
-
inspector.listKeys(pattern, cursor, count),
|
|
281
|
-
]);
|
|
282
|
-
return response.json({
|
|
283
|
-
available: true,
|
|
284
|
-
stats,
|
|
285
|
-
keys: keyList.keys,
|
|
286
|
-
cursor: keyList.cursor,
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
catch {
|
|
290
|
-
return response.json({ available: false, stats: null, keys: [] });
|
|
291
|
-
}
|
|
119
|
+
async cacheStats(ctx) {
|
|
120
|
+
return handleCacheStats(this.inspectors, ctx);
|
|
292
121
|
}
|
|
293
|
-
async cacheKey(
|
|
294
|
-
|
|
295
|
-
if (!inspector) {
|
|
296
|
-
return response.notFound({ error: 'Cache not available' });
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
const key = decodeURIComponent(params.key);
|
|
300
|
-
const detail = await inspector.getKey(key);
|
|
301
|
-
if (!detail)
|
|
302
|
-
return response.notFound({ error: 'Key not found' });
|
|
303
|
-
return response.json(detail);
|
|
304
|
-
}
|
|
305
|
-
catch {
|
|
306
|
-
return response.notFound({ error: 'Key not found' });
|
|
307
|
-
}
|
|
122
|
+
async cacheKey(ctx) {
|
|
123
|
+
return handleCacheKey(this.inspectors, ctx);
|
|
308
124
|
}
|
|
309
|
-
async cacheKeyDelete(
|
|
310
|
-
|
|
311
|
-
if (!inspector) {
|
|
312
|
-
return response.notFound({ error: 'Cache not available' });
|
|
313
|
-
}
|
|
314
|
-
try {
|
|
315
|
-
const key = decodeURIComponent(params.key);
|
|
316
|
-
const deleted = await inspector.deleteKey(key);
|
|
317
|
-
if (!deleted)
|
|
318
|
-
return response.notFound({ error: 'Key not found' });
|
|
319
|
-
return response.json({ deleted: true });
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
return response.internalServerError({ error: 'Failed to delete cache key' });
|
|
323
|
-
}
|
|
125
|
+
async cacheKeyDelete(ctx) {
|
|
126
|
+
return handleCacheKeyDelete(this.inspectors, ctx);
|
|
324
127
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// ---------------------------------------------------------------------------
|
|
328
|
-
async jobs({ request, response }) {
|
|
329
|
-
const inspector = await this.getInspector('queue');
|
|
330
|
-
if (!inspector) {
|
|
331
|
-
return response.json({
|
|
332
|
-
available: false,
|
|
333
|
-
overview: null,
|
|
334
|
-
stats: null,
|
|
335
|
-
jobs: [],
|
|
336
|
-
total: 0,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
const qs = request.qs();
|
|
340
|
-
const status = qs.status || 'all';
|
|
341
|
-
const searchTerm = qs.search || '';
|
|
342
|
-
const page = Math.max(1, Number(qs.page) || 1);
|
|
343
|
-
const perPage = clamp(Number(qs.perPage) || Number(qs.limit) || 25, 1, 100);
|
|
344
|
-
try {
|
|
345
|
-
const [overview, jobList] = await Promise.all([
|
|
346
|
-
inspector.getOverview(),
|
|
347
|
-
inspector.listJobs(status, page, perPage),
|
|
348
|
-
]);
|
|
349
|
-
// Filter jobs by search term (name match) if provided
|
|
350
|
-
let filteredJobs = jobList.jobs;
|
|
351
|
-
let filteredTotal = jobList.total;
|
|
352
|
-
if (searchTerm) {
|
|
353
|
-
const term = searchTerm.toLowerCase();
|
|
354
|
-
filteredJobs = jobList.jobs.filter((j) => j.name?.toLowerCase().includes(term) || j.id?.toString().toLowerCase().includes(term));
|
|
355
|
-
filteredTotal = filteredJobs.length;
|
|
356
|
-
}
|
|
357
|
-
return response.json({
|
|
358
|
-
available: true,
|
|
359
|
-
overview,
|
|
360
|
-
stats: overview,
|
|
361
|
-
jobs: filteredJobs,
|
|
362
|
-
total: filteredTotal,
|
|
363
|
-
page,
|
|
364
|
-
perPage,
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
catch {
|
|
368
|
-
return response.json({
|
|
369
|
-
available: false,
|
|
370
|
-
overview: null,
|
|
371
|
-
stats: null,
|
|
372
|
-
jobs: [],
|
|
373
|
-
total: 0,
|
|
374
|
-
});
|
|
375
|
-
}
|
|
128
|
+
async jobs(ctx) {
|
|
129
|
+
return handleJobs(this.inspectors, ctx);
|
|
376
130
|
}
|
|
377
|
-
async jobDetail(
|
|
378
|
-
|
|
379
|
-
if (!inspector) {
|
|
380
|
-
return response.notFound({ error: 'Queue not available' });
|
|
381
|
-
}
|
|
382
|
-
try {
|
|
383
|
-
const detail = await inspector.getJob(String(params.id));
|
|
384
|
-
if (!detail)
|
|
385
|
-
return response.notFound({ error: 'Job not found' });
|
|
386
|
-
return response.json(detail);
|
|
387
|
-
}
|
|
388
|
-
catch {
|
|
389
|
-
return response.notFound({ error: 'Job not found' });
|
|
390
|
-
}
|
|
131
|
+
async jobDetail(ctx) {
|
|
132
|
+
return handleJobDetail(this.inspectors, ctx);
|
|
391
133
|
}
|
|
392
|
-
async jobRetry(
|
|
393
|
-
|
|
394
|
-
if (!inspector) {
|
|
395
|
-
return response.notFound({ error: 'Queue not available' });
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
398
|
-
const success = await inspector.retryJob(String(params.id));
|
|
399
|
-
if (!success) {
|
|
400
|
-
return response.badRequest({
|
|
401
|
-
error: 'Job could not be retried (not in failed state)',
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
return response.json({ success: true });
|
|
405
|
-
}
|
|
406
|
-
catch {
|
|
407
|
-
return response.internalServerError({ error: 'Retry failed' });
|
|
408
|
-
}
|
|
134
|
+
async jobRetry(ctx) {
|
|
135
|
+
return handleJobRetry(this.inspectors, ctx);
|
|
409
136
|
}
|
|
410
|
-
// ---------------------------------------------------------------------------
|
|
411
|
-
// Config
|
|
412
|
-
// ---------------------------------------------------------------------------
|
|
413
137
|
async config({ response }) {
|
|
414
|
-
const configData = this.configInspector.getConfig();
|
|
415
|
-
const envData = this.configInspector.getEnvVars();
|
|
416
138
|
return response.json({
|
|
417
|
-
app:
|
|
418
|
-
env:
|
|
139
|
+
app: this.configInspector.getConfig().config,
|
|
140
|
+
env: this.configInspector.getEnvVars().env,
|
|
419
141
|
});
|
|
420
142
|
}
|
|
421
|
-
// ---------------------------------------------------------------------------
|
|
422
|
-
// Saved Filters
|
|
423
|
-
// ---------------------------------------------------------------------------
|
|
424
143
|
async savedFilters({ response }) {
|
|
425
|
-
return this.withDb(response, 'savedFilters', { filters: [] },
|
|
426
|
-
const filters = await this.dashboardStore.getSavedFilters();
|
|
427
|
-
return {
|
|
428
|
-
filters: filters.map((f) => ({
|
|
429
|
-
id: f.id,
|
|
430
|
-
name: f.name,
|
|
431
|
-
section: f.section,
|
|
432
|
-
filterConfig: safeParseJson(f.filter_config),
|
|
433
|
-
createdAt: f.created_at,
|
|
434
|
-
})),
|
|
435
|
-
};
|
|
436
|
-
});
|
|
144
|
+
return this.withDb(response, 'savedFilters', { filters: [] }, () => handleSavedFilters(this.dashboardStore));
|
|
437
145
|
}
|
|
438
|
-
async createSavedFilter(
|
|
439
|
-
|
|
440
|
-
return response.serviceUnavailable({ error: 'Database not available' });
|
|
441
|
-
}
|
|
442
|
-
try {
|
|
443
|
-
const body = request.body();
|
|
444
|
-
const { name, section, filterConfig } = body;
|
|
445
|
-
if (!name || !section || !filterConfig) {
|
|
446
|
-
return response.badRequest({
|
|
447
|
-
error: 'Missing required fields: name, section, filterConfig',
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
const configObj = typeof filterConfig === 'string' ? safeParseJson(filterConfig) : filterConfig;
|
|
451
|
-
const result = await this.dashboardStore.createSavedFilter(name, section, configObj);
|
|
452
|
-
if (!result) {
|
|
453
|
-
return response.serviceUnavailable({ error: 'Database not available' });
|
|
454
|
-
}
|
|
455
|
-
return response.json(result);
|
|
456
|
-
}
|
|
457
|
-
catch {
|
|
458
|
-
return response.internalServerError({ error: 'Failed to create filter' });
|
|
459
|
-
}
|
|
146
|
+
async createSavedFilter(ctx) {
|
|
147
|
+
return handleCreateSavedFilter(this.dashboardStore, ctx);
|
|
460
148
|
}
|
|
461
|
-
async deleteSavedFilter(
|
|
462
|
-
|
|
463
|
-
return response.serviceUnavailable({ error: 'Database not available' });
|
|
464
|
-
}
|
|
465
|
-
try {
|
|
466
|
-
const deleted = await this.dashboardStore.deleteSavedFilter(Number(params.id));
|
|
467
|
-
if (!deleted)
|
|
468
|
-
return response.notFound({ error: 'Filter not found' });
|
|
469
|
-
return response.json({ success: true });
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
472
|
-
return response.internalServerError({ error: 'Failed to delete filter' });
|
|
473
|
-
}
|
|
149
|
+
async deleteSavedFilter(ctx) {
|
|
150
|
+
return handleDeleteSavedFilter(this.dashboardStore, ctx);
|
|
474
151
|
}
|
|
475
|
-
// ---------------------------------------------------------------------------
|
|
476
|
-
// Private helpers
|
|
477
|
-
// ---------------------------------------------------------------------------
|
|
478
|
-
/**
|
|
479
|
-
* Wraps a store call with null-guard + try/catch boilerplate.
|
|
480
|
-
* Returns emptyValue if the store is not ready or the fn throws.
|
|
481
|
-
*/
|
|
482
152
|
async withDb(response, label, emptyValue, fn) {
|
|
483
153
|
if (!this.dashboardStore.isReady())
|
|
484
154
|
return response.json(emptyValue);
|
|
@@ -505,170 +175,7 @@ export default class DashboardController {
|
|
|
505
175
|
}
|
|
506
176
|
}
|
|
507
177
|
getDashboardPath() {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
async getInspector(type) {
|
|
512
|
-
if (type === 'cache') {
|
|
513
|
-
if (this.cacheAvailable === false)
|
|
514
|
-
return null;
|
|
515
|
-
if (this.cacheInspector)
|
|
516
|
-
return this.cacheInspector;
|
|
517
|
-
try {
|
|
518
|
-
const available = await CacheInspector.isAvailable(this.app);
|
|
519
|
-
this.cacheAvailable = available;
|
|
520
|
-
if (!available) {
|
|
521
|
-
log.info('dashboard: Redis not detected — Cache panel disabled');
|
|
522
|
-
return null;
|
|
523
|
-
}
|
|
524
|
-
const redis = await this.app.container.make('redis');
|
|
525
|
-
this.cacheInspector = new CacheInspector(redis);
|
|
526
|
-
return this.cacheInspector;
|
|
527
|
-
}
|
|
528
|
-
catch (err) {
|
|
529
|
-
this.cacheAvailable = false;
|
|
530
|
-
log.warn('dashboard: CacheInspector init failed — ' + err?.message);
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
if (this.queueAvailable === false)
|
|
536
|
-
return null;
|
|
537
|
-
if (this.queueInspector)
|
|
538
|
-
return this.queueInspector;
|
|
539
|
-
try {
|
|
540
|
-
const available = await QueueInspector.isAvailable(this.app);
|
|
541
|
-
this.queueAvailable = available;
|
|
542
|
-
if (!available) {
|
|
543
|
-
log.info('dashboard: Queue not detected — Jobs panel disabled');
|
|
544
|
-
return null;
|
|
545
|
-
}
|
|
546
|
-
const queue = await this.app.container.make('rlanz/queue');
|
|
547
|
-
this.queueInspector = new QueueInspector(queue);
|
|
548
|
-
return this.queueInspector;
|
|
549
|
-
}
|
|
550
|
-
catch (err) {
|
|
551
|
-
this.queueAvailable = false;
|
|
552
|
-
log.warn('dashboard: QueueInspector init failed — ' + err?.message);
|
|
553
|
-
return null;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
/** Fetch cache overview stats for the overview page. */
|
|
558
|
-
async fetchCacheOverview() {
|
|
559
|
-
try {
|
|
560
|
-
const inspector = await this.getInspector('cache');
|
|
561
|
-
if (!inspector)
|
|
562
|
-
return null;
|
|
563
|
-
const stats = await inspector.getStats();
|
|
564
|
-
return {
|
|
565
|
-
available: true,
|
|
566
|
-
totalKeys: stats.totalKeys,
|
|
567
|
-
hitRate: stats.hitRate,
|
|
568
|
-
memoryUsedHuman: stats.memoryUsedHuman,
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
catch {
|
|
572
|
-
return null;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
/** Fetch queue overview stats for the overview page. */
|
|
576
|
-
async fetchQueueOverview() {
|
|
577
|
-
try {
|
|
578
|
-
const inspector = await this.getInspector('queue');
|
|
579
|
-
if (!inspector)
|
|
580
|
-
return null;
|
|
581
|
-
const overview = await inspector.getOverview();
|
|
582
|
-
return {
|
|
583
|
-
available: true,
|
|
584
|
-
active: overview.active,
|
|
585
|
-
waiting: overview.waiting,
|
|
586
|
-
failed: overview.failed,
|
|
587
|
-
completed: overview.completed,
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
catch {
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
178
|
+
return (this.app.config.get('server_stats')?.devToolbar?.dashboardPath ??
|
|
179
|
+
'/__stats');
|
|
593
180
|
}
|
|
594
181
|
}
|
|
595
|
-
// ---------------------------------------------------------------------------
|
|
596
|
-
// Formatting helpers (snake_case DB rows → camelCase API response)
|
|
597
|
-
// ---------------------------------------------------------------------------
|
|
598
|
-
function formatRequest(r) {
|
|
599
|
-
return {
|
|
600
|
-
id: r.id,
|
|
601
|
-
method: r.method,
|
|
602
|
-
url: r.url,
|
|
603
|
-
statusCode: r.status_code,
|
|
604
|
-
duration: r.duration,
|
|
605
|
-
spanCount: r.span_count,
|
|
606
|
-
warningCount: r.warning_count,
|
|
607
|
-
createdAt: r.created_at,
|
|
608
|
-
...(r.http_request_id ? { httpRequestId: r.http_request_id } : {}),
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
function formatQuery(q) {
|
|
612
|
-
return {
|
|
613
|
-
id: q.id,
|
|
614
|
-
requestId: q.request_id,
|
|
615
|
-
sql: q.sql_text,
|
|
616
|
-
sqlNormalized: q.sql_normalized,
|
|
617
|
-
bindings: safeParseJson(q.bindings),
|
|
618
|
-
duration: q.duration,
|
|
619
|
-
method: q.method,
|
|
620
|
-
model: q.model,
|
|
621
|
-
connection: q.connection,
|
|
622
|
-
inTransaction: !!q.in_transaction,
|
|
623
|
-
createdAt: q.created_at,
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
function formatTrace(t) {
|
|
627
|
-
return {
|
|
628
|
-
id: t.id,
|
|
629
|
-
requestId: t.request_id,
|
|
630
|
-
method: t.method,
|
|
631
|
-
url: t.url,
|
|
632
|
-
statusCode: t.status_code,
|
|
633
|
-
totalDuration: t.total_duration,
|
|
634
|
-
spanCount: t.span_count,
|
|
635
|
-
spans: safeParseJson(t.spans) ?? [],
|
|
636
|
-
warnings: safeParseJsonArray(t.warnings),
|
|
637
|
-
createdAt: t.created_at,
|
|
638
|
-
...(t.http_request_id ? { httpRequestId: t.http_request_id } : {}),
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
function formatLog(l) {
|
|
642
|
-
return {
|
|
643
|
-
id: l.id,
|
|
644
|
-
level: l.level,
|
|
645
|
-
message: l.message,
|
|
646
|
-
requestId: l.request_id,
|
|
647
|
-
data: l.data,
|
|
648
|
-
createdAt: l.created_at,
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
function emptyOverview() {
|
|
652
|
-
return {
|
|
653
|
-
avgResponseTime: 0,
|
|
654
|
-
p95ResponseTime: 0,
|
|
655
|
-
requestsPerMinute: 0,
|
|
656
|
-
errorRate: 0,
|
|
657
|
-
sparklines: {
|
|
658
|
-
avgResponseTime: [],
|
|
659
|
-
p95ResponseTime: [],
|
|
660
|
-
requestsPerMinute: [],
|
|
661
|
-
errorRate: [],
|
|
662
|
-
},
|
|
663
|
-
slowestEndpoints: [],
|
|
664
|
-
queryStats: { total: 0, avgDuration: 0, perRequest: 0 },
|
|
665
|
-
recentErrors: [],
|
|
666
|
-
topEvents: [],
|
|
667
|
-
emailActivity: { sent: 0, queued: 0, failed: 0 },
|
|
668
|
-
logLevelBreakdown: { error: 0, warn: 0, info: 0, debug: 0 },
|
|
669
|
-
statusDistribution: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0 },
|
|
670
|
-
slowestQueries: [],
|
|
671
|
-
cacheStats: null,
|
|
672
|
-
jobQueueStatus: null,
|
|
673
|
-
};
|
|
674
|
-
}
|