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,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knex/SQLite connection factory and PRAGMA configuration.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from DashboardStore to reduce initKnex line count
|
|
5
|
+
* and keep the main store file focused on business logic.
|
|
6
|
+
*/
|
|
7
|
+
import type { Knex } from 'knex';
|
|
8
|
+
export declare const SQLITE_PRAGMAS: string[];
|
|
9
|
+
/**
|
|
10
|
+
* Apply all SQLite PRAGMAs via db.raw() calls.
|
|
11
|
+
* This is the fallback if afterCreate didn't work.
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyPragmas(db: Knex): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Import knex and better-sqlite3 from the host app, then create
|
|
16
|
+
* a standalone Knex connection to SQLite.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createKnexConnection(dbFilePath: string): Promise<Knex>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knex/SQLite connection factory and PRAGMA configuration.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from DashboardStore to reduce initKnex line count
|
|
5
|
+
* and keep the main store file focused on business logic.
|
|
6
|
+
*/
|
|
7
|
+
import { log } from '../utils/logger.js';
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// PRAGMA constants
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
export const SQLITE_PRAGMAS = [
|
|
12
|
+
'journal_mode=WAL',
|
|
13
|
+
'foreign_keys=ON',
|
|
14
|
+
'synchronous=NORMAL',
|
|
15
|
+
'cache_size=-64000',
|
|
16
|
+
'mmap_size=268435456',
|
|
17
|
+
'temp_store=MEMORY',
|
|
18
|
+
];
|
|
19
|
+
const PRAGMA_STATEMENTS_FOR_POOL = [
|
|
20
|
+
'journal_mode = WAL',
|
|
21
|
+
'foreign_keys = ON',
|
|
22
|
+
'synchronous = NORMAL',
|
|
23
|
+
'cache_size = -64000',
|
|
24
|
+
'mmap_size = 268435456',
|
|
25
|
+
'temp_store = MEMORY',
|
|
26
|
+
];
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// PRAGMA application via db.raw()
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* Apply all SQLite PRAGMAs via db.raw() calls.
|
|
32
|
+
* This is the fallback if afterCreate didn't work.
|
|
33
|
+
*/
|
|
34
|
+
export async function applyPragmas(db) {
|
|
35
|
+
log.info('dashboard: setting PRAGMA...');
|
|
36
|
+
for (const pragma of SQLITE_PRAGMAS) {
|
|
37
|
+
await db.raw(`PRAGMA ${pragma}`);
|
|
38
|
+
}
|
|
39
|
+
log.info('dashboard: PRAGMA set');
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Knex connection creation
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Import knex and better-sqlite3 from the host app, then create
|
|
46
|
+
* a standalone Knex connection to SQLite.
|
|
47
|
+
*/
|
|
48
|
+
export async function createKnexConnection(dbFilePath) {
|
|
49
|
+
log.info('dashboard: loading knex...');
|
|
50
|
+
const { appImportWithPath } = await import('../utils/app_import.js');
|
|
51
|
+
const { module: knexModule, resolvedPath: knexPath } = await importWithError(appImportWithPath, 'knex', 'Install it with: npm install knex better-sqlite3');
|
|
52
|
+
const { resolvedPath: sqlite3Path } = await importWithError(appImportWithPath, 'better-sqlite3', 'Install it with: npm install better-sqlite3');
|
|
53
|
+
log.info(`dashboard: knex resolved from ${knexPath}`);
|
|
54
|
+
log.info(`dashboard: better-sqlite3 resolved from ${sqlite3Path}`);
|
|
55
|
+
const knexFactory = knexModule.default ?? knexModule;
|
|
56
|
+
log.info(`dashboard: opening SQLite database at ${dbFilePath}`);
|
|
57
|
+
return knexFactory({
|
|
58
|
+
client: 'better-sqlite3',
|
|
59
|
+
connection: { filename: dbFilePath },
|
|
60
|
+
useNullAsDefault: true,
|
|
61
|
+
pool: {
|
|
62
|
+
min: 1,
|
|
63
|
+
max: 1,
|
|
64
|
+
acquireTimeoutMillis: 10_000,
|
|
65
|
+
afterCreate: afterCreateHandler,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Internal helpers
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
async function importWithError(appImportWithPath, moduleName, installHint) {
|
|
73
|
+
try {
|
|
74
|
+
return await appImportWithPath(moduleName);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
throw new Error(`Could not load ${moduleName}: ${err?.message}. ${installHint}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function afterCreateHandler(conn, done) {
|
|
81
|
+
const raw = conn;
|
|
82
|
+
try {
|
|
83
|
+
for (const pragma of PRAGMA_STATEMENTS_FOR_POOL) {
|
|
84
|
+
raw.pragma(pragma);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Fallback: PRAGMAs will be set via db.raw() below
|
|
89
|
+
}
|
|
90
|
+
done(null, conn);
|
|
91
|
+
}
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Yield control back to the event loop so Node.js can process pending
|
|
3
|
-
* I/O (incoming HTTP requests, timers, etc.).
|
|
4
|
-
*
|
|
5
|
-
* `better-sqlite3` is fully synchronous — when Knex wraps it, each
|
|
6
|
-
* `await db.raw(...)` resolves via the microtask queue, never actually
|
|
7
|
-
* yielding to the I/O phase. Without explicit yields, 25+ sequential
|
|
8
|
-
* migration statements block the event loop for their entire duration.
|
|
9
|
-
*/
|
|
10
|
-
const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
|
|
1
|
+
import { yieldToEventLoop, migrateRequests, migrateQueries, migrateEvents, migrateEmails, migrateLogs, migrateTraces, migrateMetrics, migrateSavedFilters, } from './migrator_tables.js';
|
|
11
2
|
/**
|
|
12
3
|
* Auto-migrate all dashboard SQLite tables.
|
|
13
4
|
*
|
|
@@ -19,145 +10,21 @@ const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
|
|
|
19
10
|
* continue processing HTTP requests during migration.
|
|
20
11
|
*/
|
|
21
12
|
export async function autoMigrate(db) {
|
|
22
|
-
|
|
23
|
-
await db.raw(`
|
|
24
|
-
CREATE TABLE IF NOT EXISTS server_stats_requests (
|
|
25
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
26
|
-
method TEXT NOT NULL,
|
|
27
|
-
url TEXT NOT NULL,
|
|
28
|
-
status_code INTEGER NOT NULL,
|
|
29
|
-
duration REAL NOT NULL,
|
|
30
|
-
span_count INTEGER DEFAULT 0,
|
|
31
|
-
warning_count INTEGER DEFAULT 0,
|
|
32
|
-
http_request_id TEXT,
|
|
33
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
34
|
-
)
|
|
35
|
-
`);
|
|
36
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_created ON server_stats_requests(created_at)`);
|
|
37
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_url ON server_stats_requests(url)`);
|
|
38
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_duration ON server_stats_requests(duration)`);
|
|
39
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_status ON server_stats_requests(status_code)`);
|
|
40
|
-
try {
|
|
41
|
-
await db.raw('ALTER TABLE server_stats_requests ADD COLUMN http_request_id TEXT');
|
|
42
|
-
}
|
|
43
|
-
catch { }
|
|
44
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_http_req ON server_stats_requests(http_request_id)`);
|
|
13
|
+
await migrateRequests(db);
|
|
45
14
|
await yieldToEventLoop();
|
|
46
|
-
|
|
47
|
-
await db.raw(`
|
|
48
|
-
CREATE TABLE IF NOT EXISTS server_stats_queries (
|
|
49
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50
|
-
request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
|
|
51
|
-
sql_text TEXT NOT NULL,
|
|
52
|
-
sql_normalized TEXT NOT NULL,
|
|
53
|
-
bindings TEXT,
|
|
54
|
-
duration REAL NOT NULL,
|
|
55
|
-
method TEXT,
|
|
56
|
-
model TEXT,
|
|
57
|
-
connection TEXT,
|
|
58
|
-
in_transaction INTEGER DEFAULT 0,
|
|
59
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
60
|
-
)
|
|
61
|
-
`);
|
|
62
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_created ON server_stats_queries(created_at)`);
|
|
63
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_normalized ON server_stats_queries(sql_normalized)`);
|
|
64
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_request ON server_stats_queries(request_id)`);
|
|
65
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_duration ON server_stats_queries(duration)`);
|
|
15
|
+
await migrateQueries(db);
|
|
66
16
|
await yieldToEventLoop();
|
|
67
|
-
|
|
68
|
-
await db.raw(`
|
|
69
|
-
CREATE TABLE IF NOT EXISTS server_stats_events (
|
|
70
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
71
|
-
request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
|
|
72
|
-
event_name TEXT NOT NULL,
|
|
73
|
-
data TEXT,
|
|
74
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
75
|
-
)
|
|
76
|
-
`);
|
|
77
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_events_created ON server_stats_events(created_at)`);
|
|
78
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_events_name ON server_stats_events(event_name)`);
|
|
17
|
+
await migrateEvents(db);
|
|
79
18
|
await yieldToEventLoop();
|
|
80
|
-
|
|
81
|
-
await db.raw(`
|
|
82
|
-
CREATE TABLE IF NOT EXISTS server_stats_emails (
|
|
83
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
84
|
-
from_addr TEXT NOT NULL,
|
|
85
|
-
to_addr TEXT NOT NULL,
|
|
86
|
-
cc TEXT,
|
|
87
|
-
bcc TEXT,
|
|
88
|
-
subject TEXT NOT NULL,
|
|
89
|
-
html TEXT,
|
|
90
|
-
text_body TEXT,
|
|
91
|
-
mailer TEXT NOT NULL,
|
|
92
|
-
status TEXT NOT NULL,
|
|
93
|
-
message_id TEXT,
|
|
94
|
-
attachment_count INTEGER DEFAULT 0,
|
|
95
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
96
|
-
)
|
|
97
|
-
`);
|
|
98
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_emails_created ON server_stats_emails(created_at)`);
|
|
19
|
+
await migrateEmails(db);
|
|
99
20
|
await yieldToEventLoop();
|
|
100
|
-
|
|
101
|
-
await db.raw(`
|
|
102
|
-
CREATE TABLE IF NOT EXISTS server_stats_logs (
|
|
103
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
104
|
-
level TEXT NOT NULL,
|
|
105
|
-
message TEXT NOT NULL,
|
|
106
|
-
request_id TEXT,
|
|
107
|
-
data TEXT,
|
|
108
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
109
|
-
)
|
|
110
|
-
`);
|
|
111
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_created ON server_stats_logs(created_at)`);
|
|
112
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_level ON server_stats_logs(level)`);
|
|
113
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_request ON server_stats_logs(request_id)`);
|
|
21
|
+
await migrateLogs(db);
|
|
114
22
|
await yieldToEventLoop();
|
|
115
|
-
|
|
116
|
-
await db.raw(`
|
|
117
|
-
CREATE TABLE IF NOT EXISTS server_stats_traces (
|
|
118
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
119
|
-
request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
|
|
120
|
-
method TEXT NOT NULL,
|
|
121
|
-
url TEXT NOT NULL,
|
|
122
|
-
status_code INTEGER NOT NULL,
|
|
123
|
-
total_duration REAL NOT NULL,
|
|
124
|
-
span_count INTEGER DEFAULT 0,
|
|
125
|
-
spans TEXT NOT NULL,
|
|
126
|
-
warnings TEXT,
|
|
127
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
128
|
-
)
|
|
129
|
-
`);
|
|
130
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_traces_created ON server_stats_traces(created_at)`);
|
|
131
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_traces_request ON server_stats_traces(request_id)`);
|
|
23
|
+
await migrateTraces(db);
|
|
132
24
|
await yieldToEventLoop();
|
|
133
|
-
|
|
134
|
-
await db.raw(`
|
|
135
|
-
CREATE TABLE IF NOT EXISTS server_stats_metrics (
|
|
136
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
137
|
-
bucket TEXT NOT NULL,
|
|
138
|
-
request_count INTEGER DEFAULT 0,
|
|
139
|
-
avg_duration REAL DEFAULT 0,
|
|
140
|
-
p95_duration REAL DEFAULT 0,
|
|
141
|
-
error_count INTEGER DEFAULT 0,
|
|
142
|
-
query_count INTEGER DEFAULT 0,
|
|
143
|
-
avg_query_duration REAL DEFAULT 0,
|
|
144
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
145
|
-
)
|
|
146
|
-
`);
|
|
147
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_metrics_bucket ON server_stats_metrics(bucket)`);
|
|
148
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_metrics_created ON server_stats_metrics(created_at)`);
|
|
25
|
+
await migrateMetrics(db);
|
|
149
26
|
await yieldToEventLoop();
|
|
150
|
-
|
|
151
|
-
await db.raw(`
|
|
152
|
-
CREATE TABLE IF NOT EXISTS server_stats_saved_filters (
|
|
153
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
154
|
-
name TEXT NOT NULL,
|
|
155
|
-
section TEXT NOT NULL,
|
|
156
|
-
filter_config TEXT NOT NULL,
|
|
157
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
158
|
-
)
|
|
159
|
-
`);
|
|
160
|
-
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_filters_section ON server_stats_saved_filters(section)`);
|
|
27
|
+
await migrateSavedFilters(db);
|
|
161
28
|
}
|
|
162
29
|
/**
|
|
163
30
|
* Delete records older than `retentionDays` from all tables.
|
|
@@ -176,28 +43,15 @@ export async function runRetentionCleanup(db, retentionDays) {
|
|
|
176
43
|
const days = Math.max(1, Math.floor(retentionDays));
|
|
177
44
|
const cutoff = `datetime('now', '-${days} days')`;
|
|
178
45
|
try {
|
|
179
|
-
// Batch deletes to avoid blocking the event loop for large tables.
|
|
180
|
-
// Each batch deletes up to 1000 rows, yielding between batches.
|
|
181
|
-
const batchDelete = async (table) => {
|
|
182
|
-
let hasMore = true;
|
|
183
|
-
while (hasMore) {
|
|
184
|
-
await db.raw(`DELETE FROM ${table} WHERE rowid IN (SELECT rowid FROM ${table} WHERE created_at < ${cutoff} LIMIT 1000)`);
|
|
185
|
-
const remaining = await db.raw(`SELECT COUNT(*) as cnt FROM ${table} WHERE created_at < ${cutoff} LIMIT 1`);
|
|
186
|
-
const cnt = remaining?.[0]?.cnt ?? 0;
|
|
187
|
-
hasMore = cnt > 0;
|
|
188
|
-
if (hasMore)
|
|
189
|
-
await yieldToEventLoop();
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
46
|
// Cascade deletes queries, events, traces via FK ON DELETE CASCADE
|
|
193
|
-
await batchDelete('server_stats_requests');
|
|
47
|
+
await batchDelete(db, 'server_stats_requests', cutoff);
|
|
194
48
|
await yieldToEventLoop();
|
|
195
49
|
// Standalone tables
|
|
196
|
-
await batchDelete('server_stats_logs');
|
|
50
|
+
await batchDelete(db, 'server_stats_logs', cutoff);
|
|
197
51
|
await yieldToEventLoop();
|
|
198
|
-
await batchDelete('server_stats_emails');
|
|
52
|
+
await batchDelete(db, 'server_stats_emails', cutoff);
|
|
199
53
|
await yieldToEventLoop();
|
|
200
|
-
await batchDelete('server_stats_metrics');
|
|
54
|
+
await batchDelete(db, 'server_stats_metrics', cutoff);
|
|
201
55
|
await yieldToEventLoop();
|
|
202
56
|
// Reclaim space and update query planner statistics
|
|
203
57
|
await db.raw('PRAGMA optimize');
|
|
@@ -208,3 +62,20 @@ export async function runRetentionCleanup(db, retentionDays) {
|
|
|
208
62
|
log.warn(`dashboard: retention cleanup error — ${err?.message}`);
|
|
209
63
|
}
|
|
210
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Batch-delete old rows from a table, yielding between batches.
|
|
67
|
+
*
|
|
68
|
+
* Each batch deletes up to 1000 rows to avoid blocking the event loop
|
|
69
|
+
* for large tables.
|
|
70
|
+
*/
|
|
71
|
+
async function batchDelete(db, table, cutoff) {
|
|
72
|
+
let hasMore = true;
|
|
73
|
+
while (hasMore) {
|
|
74
|
+
await db.raw(`DELETE FROM ${table} WHERE rowid IN (SELECT rowid FROM ${table} WHERE created_at < ${cutoff} LIMIT 1000)`);
|
|
75
|
+
const remaining = await db.raw(`SELECT COUNT(*) as cnt FROM ${table} WHERE created_at < ${cutoff} LIMIT 1`);
|
|
76
|
+
const cnt = remaining?.[0]?.cnt ?? 0;
|
|
77
|
+
hasMore = cnt > 0;
|
|
78
|
+
if (hasMore)
|
|
79
|
+
await yieldToEventLoop();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Yield control back to the event loop so Node.js can process pending
|
|
4
|
+
* I/O (incoming HTTP requests, timers, etc.).
|
|
5
|
+
*
|
|
6
|
+
* `better-sqlite3` is fully synchronous — when Knex wraps it, each
|
|
7
|
+
* `await db.raw(...)` resolves via the microtask queue, never actually
|
|
8
|
+
* yielding to the I/O phase. Without explicit yields, 25+ sequential
|
|
9
|
+
* migration statements block the event loop for their entire duration.
|
|
10
|
+
*/
|
|
11
|
+
export declare const yieldToEventLoop: () => Promise<void>;
|
|
12
|
+
export declare function migrateRequests(db: Knex): Promise<void>;
|
|
13
|
+
export declare function migrateQueries(db: Knex): Promise<void>;
|
|
14
|
+
export declare function migrateEvents(db: Knex): Promise<void>;
|
|
15
|
+
export declare function migrateEmails(db: Knex): Promise<void>;
|
|
16
|
+
export declare function migrateLogs(db: Knex): Promise<void>;
|
|
17
|
+
export declare function migrateTraces(db: Knex): Promise<void>;
|
|
18
|
+
export declare function migrateMetrics(db: Knex): Promise<void>;
|
|
19
|
+
export declare function migrateSavedFilters(db: Knex): Promise<void>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yield control back to the event loop so Node.js can process pending
|
|
3
|
+
* I/O (incoming HTTP requests, timers, etc.).
|
|
4
|
+
*
|
|
5
|
+
* `better-sqlite3` is fully synchronous — when Knex wraps it, each
|
|
6
|
+
* `await db.raw(...)` resolves via the microtask queue, never actually
|
|
7
|
+
* yielding to the I/O phase. Without explicit yields, 25+ sequential
|
|
8
|
+
* migration statements block the event loop for their entire duration.
|
|
9
|
+
*/
|
|
10
|
+
export const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Per-table migration functions
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
export async function migrateRequests(db) {
|
|
15
|
+
await db.raw(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS server_stats_requests (
|
|
17
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
18
|
+
method TEXT NOT NULL,
|
|
19
|
+
url TEXT NOT NULL,
|
|
20
|
+
status_code INTEGER NOT NULL,
|
|
21
|
+
duration REAL NOT NULL,
|
|
22
|
+
span_count INTEGER DEFAULT 0,
|
|
23
|
+
warning_count INTEGER DEFAULT 0,
|
|
24
|
+
http_request_id TEXT,
|
|
25
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
26
|
+
)
|
|
27
|
+
`);
|
|
28
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_created ON server_stats_requests(created_at)`);
|
|
29
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_url ON server_stats_requests(url)`);
|
|
30
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_duration ON server_stats_requests(duration)`);
|
|
31
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_status ON server_stats_requests(status_code)`);
|
|
32
|
+
try {
|
|
33
|
+
await db.raw('ALTER TABLE server_stats_requests ADD COLUMN http_request_id TEXT');
|
|
34
|
+
}
|
|
35
|
+
catch { }
|
|
36
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_requests_http_req ON server_stats_requests(http_request_id)`);
|
|
37
|
+
}
|
|
38
|
+
export async function migrateQueries(db) {
|
|
39
|
+
await db.raw(`
|
|
40
|
+
CREATE TABLE IF NOT EXISTS server_stats_queries (
|
|
41
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
42
|
+
request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
|
|
43
|
+
sql_text TEXT NOT NULL,
|
|
44
|
+
sql_normalized TEXT NOT NULL,
|
|
45
|
+
bindings TEXT,
|
|
46
|
+
duration REAL NOT NULL,
|
|
47
|
+
method TEXT,
|
|
48
|
+
model TEXT,
|
|
49
|
+
connection TEXT,
|
|
50
|
+
in_transaction INTEGER DEFAULT 0,
|
|
51
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
52
|
+
)
|
|
53
|
+
`);
|
|
54
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_created ON server_stats_queries(created_at)`);
|
|
55
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_normalized ON server_stats_queries(sql_normalized)`);
|
|
56
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_request ON server_stats_queries(request_id)`);
|
|
57
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_queries_duration ON server_stats_queries(duration)`);
|
|
58
|
+
}
|
|
59
|
+
export async function migrateEvents(db) {
|
|
60
|
+
await db.raw(`
|
|
61
|
+
CREATE TABLE IF NOT EXISTS server_stats_events (
|
|
62
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
63
|
+
request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
|
|
64
|
+
event_name TEXT NOT NULL,
|
|
65
|
+
data TEXT,
|
|
66
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
67
|
+
)
|
|
68
|
+
`);
|
|
69
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_events_created ON server_stats_events(created_at)`);
|
|
70
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_events_name ON server_stats_events(event_name)`);
|
|
71
|
+
}
|
|
72
|
+
export async function migrateEmails(db) {
|
|
73
|
+
await db.raw(`
|
|
74
|
+
CREATE TABLE IF NOT EXISTS server_stats_emails (
|
|
75
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
76
|
+
from_addr TEXT NOT NULL,
|
|
77
|
+
to_addr TEXT NOT NULL,
|
|
78
|
+
cc TEXT,
|
|
79
|
+
bcc TEXT,
|
|
80
|
+
subject TEXT NOT NULL,
|
|
81
|
+
html TEXT,
|
|
82
|
+
text_body TEXT,
|
|
83
|
+
mailer TEXT NOT NULL,
|
|
84
|
+
status TEXT NOT NULL,
|
|
85
|
+
message_id TEXT,
|
|
86
|
+
attachment_count INTEGER DEFAULT 0,
|
|
87
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
88
|
+
)
|
|
89
|
+
`);
|
|
90
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_emails_created ON server_stats_emails(created_at)`);
|
|
91
|
+
}
|
|
92
|
+
export async function migrateLogs(db) {
|
|
93
|
+
await db.raw(`
|
|
94
|
+
CREATE TABLE IF NOT EXISTS server_stats_logs (
|
|
95
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
96
|
+
level TEXT NOT NULL,
|
|
97
|
+
message TEXT NOT NULL,
|
|
98
|
+
request_id TEXT,
|
|
99
|
+
data TEXT,
|
|
100
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
101
|
+
)
|
|
102
|
+
`);
|
|
103
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_created ON server_stats_logs(created_at)`);
|
|
104
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_level ON server_stats_logs(level)`);
|
|
105
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_logs_request ON server_stats_logs(request_id)`);
|
|
106
|
+
}
|
|
107
|
+
export async function migrateTraces(db) {
|
|
108
|
+
await db.raw(`
|
|
109
|
+
CREATE TABLE IF NOT EXISTS server_stats_traces (
|
|
110
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
111
|
+
request_id INTEGER REFERENCES server_stats_requests(id) ON DELETE CASCADE,
|
|
112
|
+
method TEXT NOT NULL,
|
|
113
|
+
url TEXT NOT NULL,
|
|
114
|
+
status_code INTEGER NOT NULL,
|
|
115
|
+
total_duration REAL NOT NULL,
|
|
116
|
+
span_count INTEGER DEFAULT 0,
|
|
117
|
+
spans TEXT NOT NULL,
|
|
118
|
+
warnings TEXT,
|
|
119
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
120
|
+
)
|
|
121
|
+
`);
|
|
122
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_traces_created ON server_stats_traces(created_at)`);
|
|
123
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_traces_request ON server_stats_traces(request_id)`);
|
|
124
|
+
}
|
|
125
|
+
export async function migrateMetrics(db) {
|
|
126
|
+
await db.raw(`
|
|
127
|
+
CREATE TABLE IF NOT EXISTS server_stats_metrics (
|
|
128
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
129
|
+
bucket TEXT NOT NULL,
|
|
130
|
+
request_count INTEGER DEFAULT 0,
|
|
131
|
+
avg_duration REAL DEFAULT 0,
|
|
132
|
+
p95_duration REAL DEFAULT 0,
|
|
133
|
+
error_count INTEGER DEFAULT 0,
|
|
134
|
+
query_count INTEGER DEFAULT 0,
|
|
135
|
+
avg_query_duration REAL DEFAULT 0,
|
|
136
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
137
|
+
)
|
|
138
|
+
`);
|
|
139
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_metrics_bucket ON server_stats_metrics(bucket)`);
|
|
140
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_metrics_created ON server_stats_metrics(created_at)`);
|
|
141
|
+
}
|
|
142
|
+
export async function migrateSavedFilters(db) {
|
|
143
|
+
await db.raw(`
|
|
144
|
+
CREATE TABLE IF NOT EXISTS server_stats_saved_filters (
|
|
145
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
146
|
+
name TEXT NOT NULL,
|
|
147
|
+
section TEXT NOT NULL,
|
|
148
|
+
filter_config TEXT NOT NULL,
|
|
149
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
150
|
+
)
|
|
151
|
+
`);
|
|
152
|
+
await db.raw(`CREATE INDEX IF NOT EXISTS idx_ss_filters_section ON server_stats_saved_filters(section)`);
|
|
153
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure data-mapping helpers for dashboard overview and chart queries.
|
|
3
|
+
*
|
|
4
|
+
* These functions transform raw database rows into the shapes
|
|
5
|
+
* expected by the dashboard API. No I/O — just mapping and math.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Build the overview metrics response from raw aggregated data.
|
|
9
|
+
* Pure function — no database access.
|
|
10
|
+
*/
|
|
11
|
+
interface OverviewInput {
|
|
12
|
+
total: number;
|
|
13
|
+
stats: Record<string, unknown> | null | undefined;
|
|
14
|
+
range: string;
|
|
15
|
+
slowestEndpoints: Record<string, unknown>[];
|
|
16
|
+
queryStats: Record<string, unknown> | null | undefined;
|
|
17
|
+
recentErrors: Record<string, unknown>[];
|
|
18
|
+
}
|
|
19
|
+
export declare function buildOverviewResult(input: OverviewInput): Record<string, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* Map raw top events rows to the widget shape.
|
|
22
|
+
*/
|
|
23
|
+
export declare function mapTopEvents(raw: Record<string, unknown>[] | null | undefined): {
|
|
24
|
+
eventName: string;
|
|
25
|
+
count: number;
|
|
26
|
+
}[];
|
|
27
|
+
/**
|
|
28
|
+
* Aggregate email status rows into sent/queued/failed counts.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mapEmailActivity(raw: Record<string, unknown>[] | null | undefined): {
|
|
31
|
+
sent: number;
|
|
32
|
+
queued: number;
|
|
33
|
+
failed: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Map log level rows to the breakdown shape.
|
|
37
|
+
*/
|
|
38
|
+
export declare function mapLogLevelBreakdown(raw: Record<string, unknown>[] | null | undefined): {
|
|
39
|
+
error: number;
|
|
40
|
+
warn: number;
|
|
41
|
+
info: number;
|
|
42
|
+
debug: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Map status distribution row to the widget shape.
|
|
46
|
+
*/
|
|
47
|
+
export declare function mapStatusDistribution(row: Record<string, unknown> | null | undefined): {
|
|
48
|
+
'2xx': number;
|
|
49
|
+
'3xx': number;
|
|
50
|
+
'4xx': number;
|
|
51
|
+
'5xx': number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Map slowest query rows to the widget shape.
|
|
55
|
+
*/
|
|
56
|
+
export declare function mapSlowestQueries(raw: Record<string, unknown>[] | null | undefined): {
|
|
57
|
+
sqlNormalized: string;
|
|
58
|
+
avgDuration: number;
|
|
59
|
+
count: number;
|
|
60
|
+
}[];
|
|
61
|
+
/**
|
|
62
|
+
* Aggregate per-minute metrics into larger buckets for 24h/7d ranges.
|
|
63
|
+
* For 1h/6h, rows are returned as-is.
|
|
64
|
+
*/
|
|
65
|
+
export declare function aggregateChartBuckets(rows: Record<string, unknown>[], range: string): Record<string, unknown>[];
|
|
66
|
+
export {};
|