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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the dashboard store and its extracted modules.
|
|
3
|
+
*/
|
|
4
|
+
import type { Knex } from 'knex';
|
|
5
|
+
export interface EventEmitter {
|
|
6
|
+
on(event: string, handler: (...args: unknown[]) => void): void;
|
|
7
|
+
off(event: string, handler: (...args: unknown[]) => void): void;
|
|
8
|
+
}
|
|
9
|
+
export interface RequestInput {
|
|
10
|
+
method: string;
|
|
11
|
+
url: string;
|
|
12
|
+
statusCode: number;
|
|
13
|
+
duration: number;
|
|
14
|
+
spanCount?: number;
|
|
15
|
+
warningCount?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface PersistRequestInput extends RequestInput {
|
|
18
|
+
queries: import('../debug/types.js').QueryRecord[];
|
|
19
|
+
trace: import('../debug/types.js').TraceRecord | null;
|
|
20
|
+
httpRequestId?: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface RequestFilters {
|
|
23
|
+
method?: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
status?: number;
|
|
26
|
+
statusMin?: number;
|
|
27
|
+
statusMax?: number;
|
|
28
|
+
durationMin?: number;
|
|
29
|
+
durationMax?: number;
|
|
30
|
+
search?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface QueryFilters {
|
|
33
|
+
method?: string;
|
|
34
|
+
model?: string;
|
|
35
|
+
connection?: string;
|
|
36
|
+
durationMin?: number;
|
|
37
|
+
durationMax?: number;
|
|
38
|
+
requestId?: number;
|
|
39
|
+
search?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface EventFilters {
|
|
42
|
+
eventName?: string;
|
|
43
|
+
search?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface EmailFilters {
|
|
46
|
+
search?: string;
|
|
47
|
+
from?: string;
|
|
48
|
+
to?: string;
|
|
49
|
+
subject?: string;
|
|
50
|
+
mailer?: string;
|
|
51
|
+
status?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface LogFilters {
|
|
54
|
+
level?: string;
|
|
55
|
+
requestId?: string;
|
|
56
|
+
search?: string;
|
|
57
|
+
structured?: {
|
|
58
|
+
field: string;
|
|
59
|
+
operator: 'equals' | 'contains' | 'startsWith';
|
|
60
|
+
value: string;
|
|
61
|
+
}[];
|
|
62
|
+
}
|
|
63
|
+
export interface TraceFilters {
|
|
64
|
+
method?: string;
|
|
65
|
+
url?: string;
|
|
66
|
+
statusMin?: number;
|
|
67
|
+
statusMax?: number;
|
|
68
|
+
search?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface PaginatedResult<T> {
|
|
71
|
+
data: T[];
|
|
72
|
+
total: number;
|
|
73
|
+
page: number;
|
|
74
|
+
perPage: number;
|
|
75
|
+
lastPage: number;
|
|
76
|
+
}
|
|
77
|
+
export interface PaginateOptions {
|
|
78
|
+
table: string;
|
|
79
|
+
page: number;
|
|
80
|
+
perPage: number;
|
|
81
|
+
applyFilters?: (query: Knex.QueryBuilder) => void;
|
|
82
|
+
filterKey?: string;
|
|
83
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detail queries for individual request and trace records.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from DashboardStore to reduce file length.
|
|
5
|
+
*/
|
|
6
|
+
import type { Knex } from 'knex';
|
|
7
|
+
/**
|
|
8
|
+
* Query logs within a time window around a given timestamp.
|
|
9
|
+
* Used as a fallback when no precise request_id correlation exists.
|
|
10
|
+
*/
|
|
11
|
+
export declare function queryLogsByTimeWindow(db: Knex, createdAt: string, duration: number): Promise<Record<string, unknown>[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Fetch a single trace with full span data and correlated logs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fetchTraceDetail(db: Knex, id: number): Promise<Record<string, unknown> | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Fetch a single request with associated queries, events, trace, and logs.
|
|
18
|
+
*/
|
|
19
|
+
export declare function fetchRequestDetail(db: Knex, id: number): Promise<Record<string, unknown> | null>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detail queries for individual request and trace records.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from DashboardStore to reduce file length.
|
|
5
|
+
*/
|
|
6
|
+
import { safeParseJson, safeParseJsonArray } from '../utils/json_helpers.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Log time-window fallback
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
/**
|
|
11
|
+
* Query logs within a time window around a given timestamp.
|
|
12
|
+
* Used as a fallback when no precise request_id correlation exists.
|
|
13
|
+
*/
|
|
14
|
+
export async function queryLogsByTimeWindow(db, createdAt, duration) {
|
|
15
|
+
const windowSec = Math.ceil(duration / 1000) + 2;
|
|
16
|
+
return db('server_stats_logs')
|
|
17
|
+
.where('created_at', '>=', db.raw(`datetime(?, '-${windowSec} seconds')`, [createdAt]))
|
|
18
|
+
.where('created_at', '<=', db.raw(`datetime(?, '+${windowSec} seconds')`, [createdAt]))
|
|
19
|
+
.orderBy('created_at', 'asc')
|
|
20
|
+
.limit(100);
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Trace detail
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* Fetch a single trace with full span data and correlated logs.
|
|
27
|
+
*/
|
|
28
|
+
export async function fetchTraceDetail(db, id) {
|
|
29
|
+
const row = await db('server_stats_traces').where('id', id).first();
|
|
30
|
+
if (!row)
|
|
31
|
+
return null;
|
|
32
|
+
let logs = [];
|
|
33
|
+
let httpRequestId = null;
|
|
34
|
+
if (row.request_id) {
|
|
35
|
+
const linkedRequest = await db('server_stats_requests')
|
|
36
|
+
.where('id', row.request_id)
|
|
37
|
+
.select('http_request_id', 'created_at')
|
|
38
|
+
.first();
|
|
39
|
+
if (linkedRequest?.http_request_id) {
|
|
40
|
+
httpRequestId = linkedRequest.http_request_id;
|
|
41
|
+
logs = await db('server_stats_logs')
|
|
42
|
+
.where('request_id', linkedRequest.http_request_id)
|
|
43
|
+
.orderBy('created_at', 'asc');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (logs.length === 0 && row.created_at) {
|
|
47
|
+
logs = await queryLogsByTimeWindow(db, row.created_at, row.total_duration || 0);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
...row,
|
|
51
|
+
spans: safeParseJson(row.spans) ?? [],
|
|
52
|
+
warnings: safeParseJsonArray(row.warnings),
|
|
53
|
+
logs,
|
|
54
|
+
http_request_id: httpRequestId,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Request detail
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Fetch a single request with associated queries, events, trace, and logs.
|
|
62
|
+
*/
|
|
63
|
+
export async function fetchRequestDetail(db, id) {
|
|
64
|
+
return db.transaction(async (trx) => {
|
|
65
|
+
const request = await trx('server_stats_requests').where('id', id).first();
|
|
66
|
+
if (!request)
|
|
67
|
+
return null;
|
|
68
|
+
const queries = await trx('server_stats_queries')
|
|
69
|
+
.where('request_id', id)
|
|
70
|
+
.orderBy('created_at', 'asc');
|
|
71
|
+
const events = await trx('server_stats_events')
|
|
72
|
+
.where('request_id', id)
|
|
73
|
+
.orderBy('created_at', 'asc');
|
|
74
|
+
const trace = await trx('server_stats_traces').where('request_id', id).first();
|
|
75
|
+
let logs = [];
|
|
76
|
+
if (request.http_request_id) {
|
|
77
|
+
logs = await trx('server_stats_logs')
|
|
78
|
+
.where('request_id', request.http_request_id)
|
|
79
|
+
.orderBy('created_at', 'asc');
|
|
80
|
+
}
|
|
81
|
+
if (logs.length === 0 && request.created_at) {
|
|
82
|
+
logs = await queryLogsByTimeWindow(trx, request.created_at, request.duration || 0);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
...request,
|
|
86
|
+
queries,
|
|
87
|
+
events,
|
|
88
|
+
logs,
|
|
89
|
+
trace: trace
|
|
90
|
+
? {
|
|
91
|
+
...trace,
|
|
92
|
+
spans: safeParseJson(trace.spans) ?? [],
|
|
93
|
+
warnings: safeParseJsonArray(trace.warnings),
|
|
94
|
+
}
|
|
95
|
+
: null,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure function to build an EmailRecord from an AdonisJS mail event.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from DashboardStore.wireEventListeners to reduce
|
|
5
|
+
* cyclomatic complexity (the original had 28 due to many || chains).
|
|
6
|
+
*/
|
|
7
|
+
import type { EmailRecord } from '../debug/types.js';
|
|
8
|
+
export declare function buildEmailRecordFromEvent(data: unknown, status: EmailRecord['status']): EmailRecord;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure function to build an EmailRecord from an AdonisJS mail event.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from DashboardStore.wireEventListeners to reduce
|
|
5
|
+
* cyclomatic complexity (the original had 28 due to many || chains).
|
|
6
|
+
*/
|
|
7
|
+
import { extractAddresses } from '../utils/mail_helpers.js';
|
|
8
|
+
/**
|
|
9
|
+
* Build an EmailRecord from raw event data emitted by AdonisJS mail events.
|
|
10
|
+
*
|
|
11
|
+
* Handles two shapes:
|
|
12
|
+
* 1. Standard: `{ message: { from, to, ... }, mailerName, response }`
|
|
13
|
+
* 2. Flat: `{ from, to, subject, ... }` (data IS the message)
|
|
14
|
+
*/
|
|
15
|
+
function extractAddressFields(msg) {
|
|
16
|
+
return {
|
|
17
|
+
from: extractAddresses(msg?.from) || 'unknown',
|
|
18
|
+
to: extractAddresses(msg?.to) || 'unknown',
|
|
19
|
+
cc: extractAddresses(msg?.cc) || null,
|
|
20
|
+
bcc: extractAddresses(msg?.bcc) || null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function extractContentFields(msg) {
|
|
24
|
+
return {
|
|
25
|
+
subject: extractSubject(msg),
|
|
26
|
+
html: extractString(msg?.html),
|
|
27
|
+
text: extractString(msg?.text),
|
|
28
|
+
attachmentCount: countAttachments(msg),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function buildEmailRecordFromEvent(data, status) {
|
|
32
|
+
const d = (data ?? {});
|
|
33
|
+
const msg = extractMessage(d);
|
|
34
|
+
return {
|
|
35
|
+
...extractAddressFields(msg),
|
|
36
|
+
...extractContentFields(msg),
|
|
37
|
+
mailer: extractMailer(d),
|
|
38
|
+
status,
|
|
39
|
+
messageId: extractMessageId(d),
|
|
40
|
+
timestamp: Date.now(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function extractMessage(d) {
|
|
44
|
+
return (d.message || d);
|
|
45
|
+
}
|
|
46
|
+
function extractSubject(msg) {
|
|
47
|
+
return msg?.subject || '(no subject)';
|
|
48
|
+
}
|
|
49
|
+
function extractString(value) {
|
|
50
|
+
return value || null;
|
|
51
|
+
}
|
|
52
|
+
function extractMailer(d) {
|
|
53
|
+
return d.mailerName || d.mailer || 'unknown';
|
|
54
|
+
}
|
|
55
|
+
function extractMessageId(d) {
|
|
56
|
+
const fromResponse = d.response?.messageId;
|
|
57
|
+
if (fromResponse)
|
|
58
|
+
return fromResponse;
|
|
59
|
+
return d.messageId || null;
|
|
60
|
+
}
|
|
61
|
+
function countAttachments(msg) {
|
|
62
|
+
if (!Array.isArray(msg?.attachments))
|
|
63
|
+
return 0;
|
|
64
|
+
return msg.attachments.length;
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXPLAIN query execution helper for the DashboardStore.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from DashboardStore to reduce file length.
|
|
5
|
+
*/
|
|
6
|
+
import { CoalesceCache } from './coalesce_cache.js';
|
|
7
|
+
import type { Knex } from 'knex';
|
|
8
|
+
export declare function executeExplain(db: Knex, cache: CoalesceCache, queryId: number, appDb: unknown): Promise<Record<string, unknown> | null>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXPLAIN query execution helper for the DashboardStore.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from DashboardStore to reduce file length.
|
|
5
|
+
*/
|
|
6
|
+
export function executeExplain(db, cache, queryId, appDb) {
|
|
7
|
+
return cache.coalesce('explain:' + queryId, async () => {
|
|
8
|
+
const row = await db('server_stats_queries').where('id', queryId).first();
|
|
9
|
+
if (!row)
|
|
10
|
+
return { error: 'Query not found' };
|
|
11
|
+
const sql = row.sql_text.trim();
|
|
12
|
+
if (!sql.toLowerCase().startsWith('select'))
|
|
13
|
+
return { error: 'EXPLAIN is only supported for SELECT queries' };
|
|
14
|
+
try {
|
|
15
|
+
const r = await appDb.rawQuery(`EXPLAIN ${sql}`);
|
|
16
|
+
return { plan: r.rows || r };
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
return { error: err.message || 'EXPLAIN failed' };
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DashboardStore } from './dashboard_store.js';
|
|
2
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
|
+
/**
|
|
4
|
+
* Handle GET /saved-filters — list all saved filters.
|
|
5
|
+
*/
|
|
6
|
+
export declare function handleSavedFilters(dashboardStore: DashboardStore): Promise<{
|
|
7
|
+
filters: {
|
|
8
|
+
id: unknown;
|
|
9
|
+
name: unknown;
|
|
10
|
+
section: unknown;
|
|
11
|
+
filterConfig: unknown;
|
|
12
|
+
createdAt: unknown;
|
|
13
|
+
}[];
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Handle POST /saved-filters — create a new saved filter.
|
|
17
|
+
* Returns the response payload or null if creation fails.
|
|
18
|
+
*/
|
|
19
|
+
export declare function handleCreateSavedFilter(dashboardStore: DashboardStore, { request, response }: HttpContext): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Handle DELETE /saved-filters/:id — delete a saved filter.
|
|
22
|
+
*/
|
|
23
|
+
export declare function handleDeleteSavedFilter(dashboardStore: DashboardStore, { params, response }: HttpContext): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { safeParseJson } from '../utils/json_helpers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handle GET /saved-filters — list all saved filters.
|
|
4
|
+
*/
|
|
5
|
+
export async function handleSavedFilters(dashboardStore) {
|
|
6
|
+
const filters = await dashboardStore.getSavedFilters();
|
|
7
|
+
return {
|
|
8
|
+
filters: filters.map((f) => ({
|
|
9
|
+
id: f.id,
|
|
10
|
+
name: f.name,
|
|
11
|
+
section: f.section,
|
|
12
|
+
filterConfig: safeParseJson(f.filter_config),
|
|
13
|
+
createdAt: f.created_at,
|
|
14
|
+
})),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Handle POST /saved-filters — create a new saved filter.
|
|
19
|
+
* Returns the response payload or null if creation fails.
|
|
20
|
+
*/
|
|
21
|
+
export async function handleCreateSavedFilter(dashboardStore, { request, response }) {
|
|
22
|
+
if (!dashboardStore.isReady()) {
|
|
23
|
+
return response.serviceUnavailable({ error: 'Database not available' });
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const { name, section, filterConfig } = request.body();
|
|
27
|
+
if (!name || !section || !filterConfig) {
|
|
28
|
+
return response.badRequest({
|
|
29
|
+
error: 'Missing required fields: name, section, filterConfig',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const result = await dashboardStore.createSavedFilter(name, section, typeof filterConfig === 'string' ? safeParseJson(filterConfig) : filterConfig);
|
|
33
|
+
return result
|
|
34
|
+
? response.json(result)
|
|
35
|
+
: response.serviceUnavailable({ error: 'Database not available' });
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return response.internalServerError({ error: 'Failed to create filter' });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Handle DELETE /saved-filters/:id — delete a saved filter.
|
|
43
|
+
*/
|
|
44
|
+
export async function handleDeleteSavedFilter(dashboardStore, { params, response }) {
|
|
45
|
+
if (!dashboardStore.isReady()) {
|
|
46
|
+
return response.serviceUnavailable({ error: 'Database not available' });
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return (await dashboardStore.deleteSavedFilter(Number(params.id)))
|
|
50
|
+
? response.json({ success: true })
|
|
51
|
+
: response.notFound({ error: 'Filter not found' });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return response.internalServerError({ error: 'Failed to delete filter' });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter application functions for paginated dashboard queries.
|
|
3
|
+
*
|
|
4
|
+
* Each `apply*Filters` function takes a Knex QueryBuilder and an
|
|
5
|
+
* optional filter object, adding WHERE clauses as needed.
|
|
6
|
+
* Extracting these from DashboardStore reduces per-function complexity.
|
|
7
|
+
*/
|
|
8
|
+
import type { RequestFilters, QueryFilters, EventFilters, EmailFilters, LogFilters, TraceFilters } from './dashboard_types.js';
|
|
9
|
+
import type { Knex } from 'knex';
|
|
10
|
+
export declare function applyRequestFilters(query: Knex.QueryBuilder, filters: RequestFilters | undefined): void;
|
|
11
|
+
export declare function applyQueryFilters(query: Knex.QueryBuilder, filters: QueryFilters | undefined): void;
|
|
12
|
+
export declare function applyEventFilters(query: Knex.QueryBuilder, filters: EventFilters | undefined): void;
|
|
13
|
+
export declare function applyEmailFilters(query: Knex.QueryBuilder, filters: EmailFilters | undefined, excludeBody: boolean): void;
|
|
14
|
+
export declare function applyLogFilters(query: Knex.QueryBuilder, filters: LogFilters | undefined): void;
|
|
15
|
+
export declare function applyTraceFilters(query: Knex.QueryBuilder, filters: TraceFilters | undefined): void;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter application functions for paginated dashboard queries.
|
|
3
|
+
*
|
|
4
|
+
* Each `apply*Filters` function takes a Knex QueryBuilder and an
|
|
5
|
+
* optional filter object, adding WHERE clauses as needed.
|
|
6
|
+
* Extracting these from DashboardStore reduces per-function complexity.
|
|
7
|
+
*/
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Request filters
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
export function applyRequestFilters(query, filters) {
|
|
12
|
+
if (!filters)
|
|
13
|
+
return;
|
|
14
|
+
if (filters.method)
|
|
15
|
+
query.where('method', filters.method);
|
|
16
|
+
if (filters.url)
|
|
17
|
+
query.where('url', 'like', `%${filters.url}%`);
|
|
18
|
+
if (filters.status)
|
|
19
|
+
query.where('status_code', filters.status);
|
|
20
|
+
if (filters.statusMin)
|
|
21
|
+
query.where('status_code', '>=', filters.statusMin);
|
|
22
|
+
if (filters.statusMax)
|
|
23
|
+
query.where('status_code', '<=', filters.statusMax);
|
|
24
|
+
if (filters.durationMin)
|
|
25
|
+
query.where('duration', '>=', filters.durationMin);
|
|
26
|
+
if (filters.durationMax)
|
|
27
|
+
query.where('duration', '<=', filters.durationMax);
|
|
28
|
+
if (filters.search) {
|
|
29
|
+
const term = `%${filters.search}%`;
|
|
30
|
+
query.where((qb) => {
|
|
31
|
+
qb.where('url', 'like', term).orWhere('method', 'like', term);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Query filters
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
export function applyQueryFilters(query, filters) {
|
|
39
|
+
if (!filters)
|
|
40
|
+
return;
|
|
41
|
+
if (filters.method)
|
|
42
|
+
query.where('method', filters.method);
|
|
43
|
+
if (filters.model)
|
|
44
|
+
query.where('model', filters.model);
|
|
45
|
+
if (filters.connection)
|
|
46
|
+
query.where('connection', filters.connection);
|
|
47
|
+
if (filters.durationMin)
|
|
48
|
+
query.where('duration', '>=', filters.durationMin);
|
|
49
|
+
if (filters.durationMax)
|
|
50
|
+
query.where('duration', '<=', filters.durationMax);
|
|
51
|
+
if (filters.requestId)
|
|
52
|
+
query.where('request_id', filters.requestId);
|
|
53
|
+
if (filters.search) {
|
|
54
|
+
const term = `%${filters.search}%`;
|
|
55
|
+
query.where((qb) => {
|
|
56
|
+
qb.where('sql_text', 'like', term)
|
|
57
|
+
.orWhere('model', 'like', term)
|
|
58
|
+
.orWhere('connection', 'like', term);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Event filters
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
export function applyEventFilters(query, filters) {
|
|
66
|
+
if (!filters)
|
|
67
|
+
return;
|
|
68
|
+
if (filters.eventName)
|
|
69
|
+
query.where('event_name', 'like', `%${filters.eventName}%`);
|
|
70
|
+
if (filters.search)
|
|
71
|
+
query.where('event_name', 'like', `%${filters.search}%`);
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Email filters
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
export function applyEmailFilters(query, filters, excludeBody) {
|
|
77
|
+
if (filters) {
|
|
78
|
+
if (filters.search) {
|
|
79
|
+
const term = `%${filters.search}%`;
|
|
80
|
+
query.where((sub) => {
|
|
81
|
+
sub
|
|
82
|
+
.where('from_addr', 'like', term)
|
|
83
|
+
.orWhere('to_addr', 'like', term)
|
|
84
|
+
.orWhere('subject', 'like', term);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (filters.from)
|
|
88
|
+
query.where('from_addr', 'like', `%${filters.from}%`);
|
|
89
|
+
if (filters.to)
|
|
90
|
+
query.where('to_addr', 'like', `%${filters.to}%`);
|
|
91
|
+
if (filters.subject)
|
|
92
|
+
query.where('subject', 'like', `%${filters.subject}%`);
|
|
93
|
+
if (filters.mailer)
|
|
94
|
+
query.where('mailer', filters.mailer);
|
|
95
|
+
if (filters.status)
|
|
96
|
+
query.where('status', filters.status);
|
|
97
|
+
}
|
|
98
|
+
if (excludeBody) {
|
|
99
|
+
query.select('id', 'from_addr', 'to_addr', 'cc', 'bcc', 'subject', 'mailer', 'status', 'message_id', 'attachment_count', 'created_at');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Log filters
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
/** Operator-to-SQL pattern lookup for structured log filters. */
|
|
106
|
+
const STRUCTURED_OPERATORS = {
|
|
107
|
+
equals: (value) => value,
|
|
108
|
+
contains: (value) => `%${value}%`,
|
|
109
|
+
startsWith: (value) => `${value}%`,
|
|
110
|
+
};
|
|
111
|
+
/** Apply a single structured filter to a query. */
|
|
112
|
+
function applyStructuredFilter(query, sf) {
|
|
113
|
+
const patternFn = STRUCTURED_OPERATORS[sf.operator];
|
|
114
|
+
if (!patternFn)
|
|
115
|
+
return;
|
|
116
|
+
const jsonPath = `$.${sf.field}`;
|
|
117
|
+
const op = sf.operator === 'equals' ? '=' : 'LIKE';
|
|
118
|
+
query.whereRaw(`json_extract(data, ?) ${op} ?`, [jsonPath, patternFn(sf.value)]);
|
|
119
|
+
}
|
|
120
|
+
export function applyLogFilters(query, filters) {
|
|
121
|
+
if (!filters)
|
|
122
|
+
return;
|
|
123
|
+
if (filters.level)
|
|
124
|
+
query.where('level', filters.level);
|
|
125
|
+
if (filters.requestId)
|
|
126
|
+
query.where('request_id', filters.requestId);
|
|
127
|
+
if (filters.search)
|
|
128
|
+
query.where('message', 'like', `%${filters.search}%`);
|
|
129
|
+
if (filters.structured && filters.structured.length > 0) {
|
|
130
|
+
for (const sf of filters.structured) {
|
|
131
|
+
applyStructuredFilter(query, sf);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Trace filters
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
export function applyTraceFilters(query, filters) {
|
|
139
|
+
if (!filters)
|
|
140
|
+
return;
|
|
141
|
+
if (filters.method)
|
|
142
|
+
query.where('method', filters.method);
|
|
143
|
+
if (filters.url)
|
|
144
|
+
query.where('url', 'like', `%${filters.url}%`);
|
|
145
|
+
if (filters.statusMin)
|
|
146
|
+
query.where('status_code', '>=', filters.statusMin);
|
|
147
|
+
if (filters.statusMax)
|
|
148
|
+
query.where('status_code', '<=', filters.statusMax);
|
|
149
|
+
if (filters.search) {
|
|
150
|
+
const term = `%${filters.search}%`;
|
|
151
|
+
query.where((qb) => {
|
|
152
|
+
qb.where('url', 'like', term).orWhere('method', 'like', term);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { EventRecord, EmailRecord } from '../debug/types.js';
|
|
2
|
+
import type { PersistRequestInput } from './dashboard_types.js';
|
|
3
|
+
import type { Knex } from 'knex';
|
|
4
|
+
export declare class FlushManager {
|
|
5
|
+
writeQueue: PersistRequestInput[];
|
|
6
|
+
pendingEvents: {
|
|
7
|
+
requestIndex: number;
|
|
8
|
+
events: EventRecord[];
|
|
9
|
+
}[];
|
|
10
|
+
pendingLogs: Record<string, unknown>[];
|
|
11
|
+
pendingEmails: EmailRecord[];
|
|
12
|
+
private flushTimer;
|
|
13
|
+
private flushing;
|
|
14
|
+
private db;
|
|
15
|
+
constructor(getDb: () => Knex | null);
|
|
16
|
+
persistRequest(input: PersistRequestInput, dashboardPath: string): void;
|
|
17
|
+
queueEvents(requestIndex: number, events: EventRecord[]): void;
|
|
18
|
+
recordLog(entry: Record<string, unknown>): void;
|
|
19
|
+
recordEmail(record: EmailRecord): void;
|
|
20
|
+
stop(): Promise<void>;
|
|
21
|
+
private backpressure;
|
|
22
|
+
private scheduleFlush;
|
|
23
|
+
flush(): Promise<void>;
|
|
24
|
+
private takeSnapshot;
|
|
25
|
+
}
|