adonisjs-server-stats 1.5.4 → 1.6.2
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 +351 -171
- package/dist/configure.d.ts.map +1 -1
- package/dist/core/api-client.d.ts +73 -0
- package/dist/core/api-client.d.ts.map +1 -0
- package/dist/core/config-utils.d.ts +109 -0
- package/dist/core/config-utils.d.ts.map +1 -0
- package/dist/core/constants.d.ts +11 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/dashboard-api.d.ts +65 -0
- package/dist/core/dashboard-api.d.ts.map +1 -0
- package/dist/core/dashboard-data-controller.d.ts +157 -0
- package/dist/core/dashboard-data-controller.d.ts.map +1 -0
- package/dist/core/debug-data-controller.d.ts +89 -0
- package/dist/core/debug-data-controller.d.ts.map +1 -0
- package/dist/core/feature-detect.d.ts +67 -0
- package/dist/core/feature-detect.d.ts.map +1 -0
- package/dist/core/formatters.d.ts +189 -0
- package/dist/core/formatters.d.ts.map +1 -0
- package/dist/core/history-buffer.d.ts +23 -0
- package/dist/core/history-buffer.d.ts.map +1 -0
- package/dist/core/icons.d.ts +36 -0
- package/dist/core/icons.d.ts.map +1 -0
- package/dist/core/index.d.ts +39 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +1961 -0
- package/dist/core/internals-utils.d.ts +71 -0
- package/dist/core/internals-utils.d.ts.map +1 -0
- package/dist/core/job-utils.d.ts +45 -0
- package/dist/core/job-utils.d.ts.map +1 -0
- package/dist/core/log-utils.d.ts +34 -0
- package/dist/core/log-utils.d.ts.map +1 -0
- package/dist/core/metrics.d.ts +41 -0
- package/dist/core/metrics.d.ts.map +1 -0
- package/dist/core/pagination.d.ts +128 -0
- package/dist/core/pagination.d.ts.map +1 -0
- package/dist/core/query-utils.d.ts +35 -0
- package/dist/core/query-utils.d.ts.map +1 -0
- package/dist/core/resizable-columns.d.ts +18 -0
- package/dist/core/resizable-columns.d.ts.map +1 -0
- package/dist/core/routes.d.ts +12 -0
- package/dist/core/routes.d.ts.map +1 -0
- package/dist/core/server-stats-controller.d.ts +106 -0
- package/dist/core/server-stats-controller.d.ts.map +1 -0
- package/dist/core/sparkline.d.ts +80 -0
- package/dist/core/sparkline.d.ts.map +1 -0
- package/dist/core/theme.d.ts +42 -0
- package/dist/core/theme.d.ts.map +1 -0
- package/dist/core/trace-utils.d.ts +62 -0
- package/dist/core/trace-utils.d.ts.map +1 -0
- package/dist/core/transmit-adapter.d.ts +59 -0
- package/dist/core/transmit-adapter.d.ts.map +1 -0
- package/dist/core/types.d.ts +619 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/react/CacheSection-DGxMDlWK.js +146 -0
- package/dist/react/CacheTab-CnVW5PLs.js +123 -0
- package/dist/react/ConfigContent-CnsEI4j3.js +397 -0
- package/dist/react/ConfigSection-DPcrfqXY.js +11 -0
- package/dist/react/ConfigTab-BSWq_o2p.js +15 -0
- package/dist/react/CustomPaneTab-xjkYwTvH.js +104 -0
- package/dist/react/DataTable-YyShr5B-.js +55 -0
- package/dist/react/EmailsSection-CSyTg1aX.js +262 -0
- package/dist/react/EmailsTab-Dh2YSa_f.js +131 -0
- package/dist/react/EventsSection-C1pbJDfW.js +86 -0
- package/dist/react/EventsTab-eCh02cdd.js +63 -0
- package/dist/react/FilterBar-DQRXpWrb.js +50 -0
- package/dist/react/InternalsContent-DBzsI0CG.js +346 -0
- package/dist/react/InternalsSection-t7ihcWO-.js +32 -0
- package/dist/react/InternalsTab-Oij0A2fN.js +30 -0
- package/dist/react/JobsSection-CLAin5vU.js +187 -0
- package/dist/react/JobsTab-Dl5nrj2z.js +141 -0
- package/dist/react/LogsSection-C1p81fXO.js +212 -0
- package/dist/react/LogsTab-D-kR7PjX.js +88 -0
- package/dist/react/OverviewSection-nm3xdACz.js +539 -0
- package/dist/react/Pagination-BkmzUDY8.js +64 -0
- package/dist/react/QueriesSection-DB12HMfQ.js +461 -0
- package/dist/react/QueriesTab-fyBB1u_Y.js +90 -0
- package/dist/react/RequestsSection-DTqB81ac.js +209 -0
- package/dist/react/RoutesSection-DJWa4NPV.js +74 -0
- package/dist/react/RoutesTab-D3l8TOpu.js +74 -0
- package/dist/react/TimelineSection-C4d-jRX1.js +158 -0
- package/dist/react/TimelineTab-C5TFaSmQ.js +193 -0
- package/dist/react/WaterfallChart-Cj73WdfM.js +100 -0
- package/dist/react/core/api-client.d.ts +73 -0
- package/dist/react/core/api-client.d.ts.map +1 -0
- package/dist/react/core/config-utils.d.ts +109 -0
- package/dist/react/core/config-utils.d.ts.map +1 -0
- package/dist/react/core/constants.d.ts +11 -0
- package/dist/react/core/constants.d.ts.map +1 -0
- package/dist/react/core/dashboard-api.d.ts +65 -0
- package/dist/react/core/dashboard-api.d.ts.map +1 -0
- package/dist/react/core/dashboard-data-controller.d.ts +157 -0
- package/dist/react/core/dashboard-data-controller.d.ts.map +1 -0
- package/dist/react/core/debug-data-controller.d.ts +89 -0
- package/dist/react/core/debug-data-controller.d.ts.map +1 -0
- package/dist/react/core/feature-detect.d.ts +67 -0
- package/dist/react/core/feature-detect.d.ts.map +1 -0
- package/dist/react/core/formatters.d.ts +189 -0
- package/dist/react/core/formatters.d.ts.map +1 -0
- package/dist/react/core/history-buffer.d.ts +23 -0
- package/dist/react/core/history-buffer.d.ts.map +1 -0
- package/dist/react/core/icons.d.ts +36 -0
- package/dist/react/core/icons.d.ts.map +1 -0
- package/dist/react/core/index.d.ts +39 -0
- package/dist/react/core/index.d.ts.map +1 -0
- package/dist/react/core/internals-utils.d.ts +71 -0
- package/dist/react/core/internals-utils.d.ts.map +1 -0
- package/dist/react/core/job-utils.d.ts +45 -0
- package/dist/react/core/job-utils.d.ts.map +1 -0
- package/dist/react/core/log-utils.d.ts +34 -0
- package/dist/react/core/log-utils.d.ts.map +1 -0
- package/dist/react/core/metrics.d.ts +41 -0
- package/dist/react/core/metrics.d.ts.map +1 -0
- package/dist/react/core/pagination.d.ts +128 -0
- package/dist/react/core/pagination.d.ts.map +1 -0
- package/dist/react/core/query-utils.d.ts +35 -0
- package/dist/react/core/query-utils.d.ts.map +1 -0
- package/dist/react/core/resizable-columns.d.ts +18 -0
- package/dist/react/core/resizable-columns.d.ts.map +1 -0
- package/dist/react/core/routes.d.ts +12 -0
- package/dist/react/core/routes.d.ts.map +1 -0
- package/dist/react/core/server-stats-controller.d.ts +106 -0
- package/dist/react/core/server-stats-controller.d.ts.map +1 -0
- package/dist/react/core/sparkline.d.ts +80 -0
- package/dist/react/core/sparkline.d.ts.map +1 -0
- package/dist/react/core/theme.d.ts +42 -0
- package/dist/react/core/theme.d.ts.map +1 -0
- package/dist/react/core/trace-utils.d.ts +62 -0
- package/dist/react/core/trace-utils.d.ts.map +1 -0
- package/dist/react/core/transmit-adapter.d.ts +59 -0
- package/dist/react/core/transmit-adapter.d.ts.map +1 -0
- package/dist/react/core/types.d.ts +619 -0
- package/dist/react/core/types.d.ts.map +1 -0
- package/dist/react/index-UdTfSvtO.js +1074 -0
- package/dist/react/index.js +18 -0
- package/dist/react/react/components/Dashboard/DashboardPage.d.ts +17 -0
- package/dist/react/react/components/Dashboard/DashboardPage.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/CacheSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/CacheSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/ConfigSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/ConfigSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/EmailsSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/EmailsSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/EventsSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/EventsSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/InternalsSection.d.ts +14 -0
- package/dist/react/react/components/Dashboard/sections/InternalsSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/JobsSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/JobsSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/LogsSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/LogsSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/OverviewSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/OverviewSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/QueriesSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/QueriesSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/RequestsSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/RequestsSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/RoutesSection.d.ts +7 -0
- package/dist/react/react/components/Dashboard/sections/RoutesSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/sections/TimelineSection.d.ts +9 -0
- package/dist/react/react/components/Dashboard/sections/TimelineSection.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/shared/DataTable.d.ts +27 -0
- package/dist/react/react/components/Dashboard/shared/DataTable.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/shared/FilterBar.d.ts +17 -0
- package/dist/react/react/components/Dashboard/shared/FilterBar.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/shared/Pagination.d.ts +13 -0
- package/dist/react/react/components/Dashboard/shared/Pagination.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/shared/TimeRangeSelector.d.ts +12 -0
- package/dist/react/react/components/Dashboard/shared/TimeRangeSelector.d.ts.map +1 -0
- package/dist/react/react/components/Dashboard/shared/WaterfallChart.d.ts +16 -0
- package/dist/react/react/components/Dashboard/shared/WaterfallChart.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/DebugPanel.d.ts +16 -0
- package/dist/react/react/components/DebugPanel/DebugPanel.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/CacheTab.d.ts +8 -0
- package/dist/react/react/components/DebugPanel/tabs/CacheTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/ConfigTab.d.ts +8 -0
- package/dist/react/react/components/DebugPanel/tabs/ConfigTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/CustomPaneTab.d.ts +14 -0
- package/dist/react/react/components/DebugPanel/tabs/CustomPaneTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/EmailsTab.d.ts +7 -0
- package/dist/react/react/components/DebugPanel/tabs/EmailsTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/EventsTab.d.ts +7 -0
- package/dist/react/react/components/DebugPanel/tabs/EventsTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/InternalsTab.d.ts +12 -0
- package/dist/react/react/components/DebugPanel/tabs/InternalsTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/JobsTab.d.ts +9 -0
- package/dist/react/react/components/DebugPanel/tabs/JobsTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/LogsTab.d.ts +7 -0
- package/dist/react/react/components/DebugPanel/tabs/LogsTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/QueriesTab.d.ts +7 -0
- package/dist/react/react/components/DebugPanel/tabs/QueriesTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/RoutesTab.d.ts +8 -0
- package/dist/react/react/components/DebugPanel/tabs/RoutesTab.d.ts.map +1 -0
- package/dist/react/react/components/DebugPanel/tabs/TimelineTab.d.ts +7 -0
- package/dist/react/react/components/DebugPanel/tabs/TimelineTab.d.ts.map +1 -0
- package/dist/react/react/components/StatsBar/MetricCard.d.ts +21 -0
- package/dist/react/react/components/StatsBar/MetricCard.d.ts.map +1 -0
- package/dist/react/react/components/StatsBar/Sparkline.d.ts +15 -0
- package/dist/react/react/components/StatsBar/Sparkline.d.ts.map +1 -0
- package/dist/react/react/components/StatsBar/StatsBar.d.ts +22 -0
- package/dist/react/react/components/StatsBar/StatsBar.d.ts.map +1 -0
- package/dist/react/react/components/shared/Badge.d.ts +32 -0
- package/dist/react/react/components/shared/Badge.d.ts.map +1 -0
- package/dist/react/react/components/shared/ConfigContent.d.ts +13 -0
- package/dist/react/react/components/shared/ConfigContent.d.ts.map +1 -0
- package/dist/react/react/components/shared/InternalsContent.d.ts +10 -0
- package/dist/react/react/components/shared/InternalsContent.d.ts.map +1 -0
- package/dist/react/react/components/shared/JsonViewer.d.ts +12 -0
- package/dist/react/react/components/shared/JsonViewer.d.ts.map +1 -0
- package/dist/react/react/components/shared/ThemeToggle.d.ts +13 -0
- package/dist/react/react/components/shared/ThemeToggle.d.ts.map +1 -0
- package/dist/react/react/components/shared/Tooltip.d.ts +16 -0
- package/dist/react/react/components/shared/Tooltip.d.ts.map +1 -0
- package/dist/react/react/hooks/useApiClient.d.ts +10 -0
- package/dist/react/react/hooks/useApiClient.d.ts.map +1 -0
- package/dist/react/react/hooks/useDashboardApiBase.d.ts +19 -0
- package/dist/react/react/hooks/useDashboardApiBase.d.ts.map +1 -0
- package/dist/react/react/hooks/useDashboardData.d.ts +27 -0
- package/dist/react/react/hooks/useDashboardData.d.ts.map +1 -0
- package/dist/react/react/hooks/useDebugData.d.ts +17 -0
- package/dist/react/react/hooks/useDebugData.d.ts.map +1 -0
- package/dist/react/react/hooks/useFeatures.d.ts +13 -0
- package/dist/react/react/hooks/useFeatures.d.ts.map +1 -0
- package/dist/react/react/hooks/useResizableTable.d.ts +13 -0
- package/dist/react/react/hooks/useResizableTable.d.ts.map +1 -0
- package/dist/react/react/hooks/useServerStats.d.ts +21 -0
- package/dist/react/react/hooks/useServerStats.d.ts.map +1 -0
- package/dist/react/react/hooks/useTheme.d.ts +12 -0
- package/dist/react/react/hooks/useTheme.d.ts.map +1 -0
- package/dist/react/react/index.d.ts +14 -0
- package/dist/react/react/index.d.ts.map +1 -0
- package/dist/react/style.css +1 -0
- package/dist/react/useApiClient-BVtNCmnL.js +9 -0
- package/dist/react/useDashboardApiBase-Bi36pJ2L.js +14 -0
- package/dist/react/useResizableTable-CNJmACdt.js +13 -0
- package/dist/src/collectors/app_collector.d.ts.map +1 -1
- package/dist/src/collectors/app_collector.js +30 -2
- package/dist/src/collectors/auto_detect.d.ts +31 -0
- package/dist/src/collectors/auto_detect.d.ts.map +1 -0
- package/dist/src/collectors/auto_detect.js +120 -0
- package/dist/src/collectors/collector.d.ts +17 -0
- package/dist/src/collectors/collector.d.ts.map +1 -1
- package/dist/src/collectors/db_pool_collector.d.ts.map +1 -1
- package/dist/src/collectors/db_pool_collector.js +35 -1
- package/dist/src/collectors/http_collector.d.ts +4 -3
- package/dist/src/collectors/http_collector.d.ts.map +1 -1
- package/dist/src/collectors/http_collector.js +28 -11
- package/dist/src/collectors/index.d.ts +2 -0
- package/dist/src/collectors/index.d.ts.map +1 -1
- package/dist/src/collectors/index.js +1 -0
- package/dist/src/collectors/log_collector.d.ts +11 -4
- package/dist/src/collectors/log_collector.d.ts.map +1 -1
- package/dist/src/collectors/log_collector.js +51 -5
- package/dist/src/collectors/process_collector.d.ts.map +1 -1
- package/dist/src/collectors/process_collector.js +4 -0
- package/dist/src/collectors/queue_collector.d.ts.map +1 -1
- package/dist/src/collectors/queue_collector.js +55 -1
- package/dist/src/collectors/redis_collector.d.ts.map +1 -1
- package/dist/src/collectors/redis_collector.js +42 -3
- package/dist/src/collectors/system_collector.d.ts.map +1 -1
- package/dist/src/collectors/system_collector.js +4 -0
- package/dist/src/controller/api_controller.d.ts +101 -0
- package/dist/src/controller/api_controller.d.ts.map +1 -0
- package/dist/src/controller/api_controller.js +131 -0
- package/dist/src/controller/debug_controller.d.ts +19 -10
- package/dist/src/controller/debug_controller.d.ts.map +1 -1
- package/dist/src/controller/debug_controller.js +118 -101
- package/dist/src/core/theme.d.ts +42 -0
- package/dist/src/core/theme.d.ts.map +1 -0
- package/dist/src/core/theme.js +115 -0
- package/dist/src/dashboard/chart_aggregator.d.ts.map +1 -1
- package/dist/src/dashboard/chart_aggregator.js +3 -2
- package/dist/src/dashboard/dashboard_controller.d.ts +10 -14
- package/dist/src/dashboard/dashboard_controller.d.ts.map +1 -1
- package/dist/src/dashboard/dashboard_controller.js +132 -250
- package/dist/src/dashboard/dashboard_store.d.ts +62 -19
- package/dist/src/dashboard/dashboard_store.d.ts.map +1 -1
- package/dist/src/dashboard/dashboard_store.js +242 -53
- package/dist/src/dashboard/integrations/cache_inspector.d.ts +19 -1
- package/dist/src/dashboard/integrations/cache_inspector.d.ts.map +1 -1
- package/dist/src/dashboard/integrations/config_inspector.d.ts +1 -1
- package/dist/src/dashboard/integrations/config_inspector.d.ts.map +1 -1
- package/dist/src/dashboard/integrations/config_inspector.js +3 -2
- package/dist/src/dashboard/integrations/queue_inspector.d.ts +55 -10
- package/dist/src/dashboard/integrations/queue_inspector.d.ts.map +1 -1
- package/dist/src/dashboard/integrations/queue_inspector.js +70 -24
- package/dist/src/dashboard/migrator.d.ts +5 -0
- package/dist/src/dashboard/migrator.d.ts.map +1 -1
- package/dist/src/dashboard/migrator.js +44 -9
- package/dist/src/dashboard/models/stats_event.d.ts +1 -1
- package/dist/src/dashboard/models/stats_event.d.ts.map +1 -1
- package/dist/src/dashboard/models/stats_log.d.ts +1 -1
- package/dist/src/dashboard/models/stats_log.d.ts.map +1 -1
- package/dist/src/dashboard/models/stats_query.d.ts +1 -1
- package/dist/src/dashboard/models/stats_query.d.ts.map +1 -1
- package/dist/src/dashboard/models/stats_saved_filter.d.ts +1 -1
- package/dist/src/dashboard/models/stats_saved_filter.d.ts.map +1 -1
- package/dist/src/dashboard/models/stats_trace.d.ts +2 -1
- package/dist/src/dashboard/models/stats_trace.d.ts.map +1 -1
- package/dist/src/data/data_access.d.ts +105 -0
- package/dist/src/data/data_access.d.ts.map +1 -0
- package/dist/src/data/data_access.js +310 -0
- package/dist/src/data/index.d.ts +3 -0
- package/dist/src/data/index.d.ts.map +1 -0
- package/dist/src/data/index.js +1 -0
- package/dist/src/debug/debug_store.d.ts +20 -1
- package/dist/src/debug/debug_store.d.ts.map +1 -1
- package/dist/src/debug/debug_store.js +43 -15
- package/dist/src/debug/email_collector.d.ts +6 -2
- package/dist/src/debug/email_collector.d.ts.map +1 -1
- package/dist/src/debug/email_collector.js +3 -0
- package/dist/src/debug/event_collector.d.ts +6 -2
- package/dist/src/debug/event_collector.d.ts.map +1 -1
- package/dist/src/debug/event_collector.js +12 -8
- package/dist/src/debug/query_collector.d.ts +6 -2
- package/dist/src/debug/query_collector.d.ts.map +1 -1
- package/dist/src/debug/query_collector.js +3 -0
- package/dist/src/debug/ring_buffer.d.ts +1 -0
- package/dist/src/debug/ring_buffer.d.ts.map +1 -1
- package/dist/src/debug/ring_buffer.js +5 -2
- package/dist/src/debug/route_inspector.d.ts +2 -2
- package/dist/src/debug/route_inspector.d.ts.map +1 -1
- package/dist/src/debug/route_inspector.js +4 -3
- package/dist/src/debug/trace_collector.d.ts +7 -3
- package/dist/src/debug/trace_collector.d.ts.map +1 -1
- package/dist/src/debug/trace_collector.js +7 -5
- package/dist/src/debug/types.d.ts +107 -2
- package/dist/src/debug/types.d.ts.map +1 -1
- package/dist/src/debug/types.js +1 -1
- package/dist/src/define_config.d.ts +49 -5
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +361 -4
- package/dist/src/edge/bootstrap.d.ts +17 -0
- package/dist/src/edge/bootstrap.d.ts.map +1 -0
- package/dist/src/edge/bootstrap.js +29 -0
- package/dist/src/edge/client/dashboard.js +2 -3619
- package/dist/src/edge/client/debug-panel-deferred.js +1 -0
- package/dist/src/edge/client/debug-panel.js +1 -2140
- package/dist/src/edge/client/stats-bar.js +1 -801
- package/dist/src/edge/client-vue/dashboard.js +5 -0
- package/dist/src/edge/client-vue/debug-panel-deferred.js +4 -0
- package/dist/src/edge/client-vue/debug-panel.js +4 -0
- package/dist/src/edge/client-vue/stats-bar.js +4 -0
- package/dist/src/edge/plugin.d.ts +35 -2
- package/dist/src/edge/plugin.d.ts.map +1 -1
- package/dist/src/edge/plugin.js +30 -66
- package/dist/src/edge/types.d.ts +46 -0
- package/dist/src/edge/types.d.ts.map +1 -0
- package/dist/src/edge/types.js +4 -0
- package/dist/src/edge/views/dashboard.edge +1 -358
- package/dist/src/edge/views/debug-panel.edge +2 -154
- package/dist/src/edge/views/stats-bar.edge +15 -48
- package/dist/src/engine/stats_engine.d.ts +18 -0
- package/dist/src/engine/stats_engine.d.ts.map +1 -1
- package/dist/src/engine/stats_engine.js +45 -2
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/log_stream/log_stream_provider.d.ts.map +1 -1
- package/dist/src/log_stream/log_stream_provider.js +21 -4
- package/dist/src/log_stream/log_stream_service.d.ts +8 -1
- package/dist/src/log_stream/log_stream_service.d.ts.map +1 -1
- package/dist/src/log_stream/log_stream_service.js +27 -3
- package/dist/src/middleware/request_tracking_middleware.d.ts +1 -1
- package/dist/src/middleware/request_tracking_middleware.d.ts.map +1 -1
- package/dist/src/middleware/request_tracking_middleware.js +21 -6
- package/dist/src/prometheus/prometheus_collector.d.ts +2 -1
- package/dist/src/prometheus/prometheus_collector.d.ts.map +1 -1
- package/dist/src/provider/server_stats_provider.d.ts +100 -0
- package/dist/src/provider/server_stats_provider.d.ts.map +1 -1
- package/dist/src/provider/server_stats_provider.js +460 -104
- package/dist/src/routes/access_middleware.d.ts +2 -1
- package/dist/src/routes/access_middleware.d.ts.map +1 -1
- package/dist/src/routes/access_middleware.js +7 -1
- package/dist/src/routes/index.d.ts +4 -0
- package/dist/src/routes/index.d.ts.map +1 -0
- package/dist/src/routes/index.js +1 -0
- package/dist/src/routes/register_routes.d.ts +103 -0
- package/dist/src/routes/register_routes.d.ts.map +1 -0
- package/dist/src/routes/register_routes.js +356 -0
- package/dist/src/routes/router_types.d.ts +29 -0
- package/dist/src/routes/router_types.d.ts.map +1 -0
- package/dist/src/routes/router_types.js +1 -0
- package/dist/src/stubs/config.stub +12 -32
- package/dist/src/styles/components.css +1048 -0
- package/dist/src/{edge/client → styles}/dashboard.css +299 -736
- package/dist/src/{edge/client → styles}/debug-panel.css +117 -633
- package/dist/src/{edge/client → styles}/stats-bar.css +28 -10
- package/dist/src/styles/tokens.css +153 -0
- package/dist/src/styles/utilities.css +75 -0
- package/dist/src/types.d.ts +119 -16
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/app_import.d.ts +23 -0
- package/dist/src/utils/app_import.d.ts.map +1 -0
- package/dist/src/utils/app_import.js +44 -0
- package/dist/src/utils/json_helpers.d.ts +2 -2
- package/dist/src/utils/json_helpers.d.ts.map +1 -1
- package/dist/src/utils/logger.d.ts +17 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +27 -0
- package/dist/src/utils/mail_helpers.d.ts +1 -1
- package/dist/src/utils/mail_helpers.d.ts.map +1 -1
- package/dist/src/utils/mail_helpers.js +1 -1
- package/dist/vue/CacheSection-C788Yfai.js +149 -0
- package/dist/vue/CacheTab-BPisYYiQ.js +104 -0
- package/dist/vue/ConfigSection-CRzYxqW2.js +576 -0
- package/dist/vue/ConfigTab-C8cafGUj.js +361 -0
- package/dist/vue/CustomPaneTab-BJxT5Dp7.js +172 -0
- package/dist/vue/EmailsSection-C8JFMtW7.js +206 -0
- package/dist/vue/EmailsTab-DhFhoNmU.js +157 -0
- package/dist/vue/EventsSection-C4wXUgxG.js +107 -0
- package/dist/vue/EventsTab-DQ4Nd6AK.js +97 -0
- package/dist/vue/FilterBar.vue_vue_type_script_setup_true_lang-ClJ37hhT.js +62 -0
- package/dist/vue/InternalsSection-BJUXE-5F.js +468 -0
- package/dist/vue/InternalsTab-DEMjqtlw.js +471 -0
- package/dist/vue/JobsSection-CsKWTjgN.js +187 -0
- package/dist/vue/JobsTab-BCvhOARO.js +117 -0
- package/dist/vue/JsonViewer.vue_vue_type_script_setup_true_lang-Vsqar1zx.js +67 -0
- package/dist/vue/LogsSection-BFVjSZ24.js +227 -0
- package/dist/vue/LogsTab-DpEQ7euu.js +122 -0
- package/dist/vue/OverviewSection-CbMdAido.js +849 -0
- package/dist/vue/PaginationControls.vue_vue_type_script_setup_true_lang-CuN7g_8Z.js +50 -0
- package/dist/vue/QueriesSection-BPiv7u3r.js +429 -0
- package/dist/vue/QueriesTab-C8_7oprC.js +107 -0
- package/dist/vue/RequestsSection-LtImH4rD.js +243 -0
- package/dist/vue/RoutesSection-CrxOxmzx.js +106 -0
- package/dist/vue/RoutesTab-Dz0MkZuF.js +80 -0
- package/dist/vue/TimelineSection-DLxMW2J_.js +186 -0
- package/dist/vue/TimelineTab-Db6lKKsD.js +250 -0
- package/dist/vue/WaterfallChart.vue_vue_type_script_setup_true_lang-tZ13cNj1.js +118 -0
- package/dist/vue/components/Dashboard/DashboardPage.vue.d.ts +18 -0
- package/dist/vue/components/Dashboard/DashboardPage.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/CacheSection.vue.d.ts +5 -0
- package/dist/vue/components/Dashboard/sections/CacheSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/ConfigSection.vue.d.ts +3 -0
- package/dist/vue/components/Dashboard/sections/ConfigSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/EmailsSection.vue.d.ts +5 -0
- package/dist/vue/components/Dashboard/sections/EmailsSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/EventsSection.vue.d.ts +5 -0
- package/dist/vue/components/Dashboard/sections/EventsSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/InternalsSection.vue.d.ts +3 -0
- package/dist/vue/components/Dashboard/sections/InternalsSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/JobsSection.vue.d.ts +5 -0
- package/dist/vue/components/Dashboard/sections/JobsSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/LogsSection.vue.d.ts +3 -0
- package/dist/vue/components/Dashboard/sections/LogsSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/OverviewSection.vue.d.ts +5 -0
- package/dist/vue/components/Dashboard/sections/OverviewSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/QueriesSection.vue.d.ts +45 -0
- package/dist/vue/components/Dashboard/sections/QueriesSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/RequestsSection.vue.d.ts +5 -0
- package/dist/vue/components/Dashboard/sections/RequestsSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/RoutesSection.vue.d.ts +5 -0
- package/dist/vue/components/Dashboard/sections/RoutesSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/sections/TimelineSection.vue.d.ts +11 -0
- package/dist/vue/components/Dashboard/sections/TimelineSection.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/shared/FilterBar.vue.d.ts +29 -0
- package/dist/vue/components/Dashboard/shared/FilterBar.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/shared/PaginationControls.vue.d.ts +12 -0
- package/dist/vue/components/Dashboard/shared/PaginationControls.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/shared/TimeRangeSelector.vue.d.ts +11 -0
- package/dist/vue/components/Dashboard/shared/TimeRangeSelector.vue.d.ts.map +1 -0
- package/dist/vue/components/Dashboard/shared/WaterfallChart.vue.d.ts +10 -0
- package/dist/vue/components/Dashboard/shared/WaterfallChart.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/DebugPanel.vue.d.ts +21 -0
- package/dist/vue/components/DebugPanel/DebugPanel.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/CacheTab.vue.d.ts +12 -0
- package/dist/vue/components/DebugPanel/tabs/CacheTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/ConfigTab.vue.d.ts +11 -0
- package/dist/vue/components/DebugPanel/tabs/ConfigTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/CustomPaneTab.vue.d.ts +11 -0
- package/dist/vue/components/DebugPanel/tabs/CustomPaneTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/EmailsTab.vue.d.ts +12 -0
- package/dist/vue/components/DebugPanel/tabs/EmailsTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/EventsTab.vue.d.ts +12 -0
- package/dist/vue/components/DebugPanel/tabs/EventsTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/InternalsTab.vue.d.ts +11 -0
- package/dist/vue/components/DebugPanel/tabs/InternalsTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/JobsTab.vue.d.ts +13 -0
- package/dist/vue/components/DebugPanel/tabs/JobsTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/LogsTab.vue.d.ts +15 -0
- package/dist/vue/components/DebugPanel/tabs/LogsTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/QueriesTab.vue.d.ts +12 -0
- package/dist/vue/components/DebugPanel/tabs/QueriesTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/RoutesTab.vue.d.ts +12 -0
- package/dist/vue/components/DebugPanel/tabs/RoutesTab.vue.d.ts.map +1 -0
- package/dist/vue/components/DebugPanel/tabs/TimelineTab.vue.d.ts +15 -0
- package/dist/vue/components/DebugPanel/tabs/TimelineTab.vue.d.ts.map +1 -0
- package/dist/vue/components/StatsBar/MetricCard.vue.d.ts +19 -0
- package/dist/vue/components/StatsBar/MetricCard.vue.d.ts.map +1 -0
- package/dist/vue/components/StatsBar/Sparkline.vue.d.ts +17 -0
- package/dist/vue/components/StatsBar/Sparkline.vue.d.ts.map +1 -0
- package/dist/vue/components/StatsBar/StatsBar.vue.d.ts +22 -0
- package/dist/vue/components/StatsBar/StatsBar.vue.d.ts.map +1 -0
- package/dist/vue/components/shared/JsonViewer.vue.d.ts +13 -0
- package/dist/vue/components/shared/JsonViewer.vue.d.ts.map +1 -0
- package/dist/vue/components/shared/ThemeToggle.vue.d.ts +8 -0
- package/dist/vue/components/shared/ThemeToggle.vue.d.ts.map +1 -0
- package/dist/vue/composables/useApiClient.d.ts +9 -0
- package/dist/vue/composables/useApiClient.d.ts.map +1 -0
- package/dist/vue/composables/useDashboardData.d.ts +53 -0
- package/dist/vue/composables/useDashboardData.d.ts.map +1 -0
- package/dist/vue/composables/useDebugData.d.ts +25 -0
- package/dist/vue/composables/useDebugData.d.ts.map +1 -0
- package/dist/vue/composables/useFeatures.d.ts +80 -0
- package/dist/vue/composables/useFeatures.d.ts.map +1 -0
- package/dist/vue/composables/useResizableTable.d.ts +16 -0
- package/dist/vue/composables/useResizableTable.d.ts.map +1 -0
- package/dist/vue/composables/useServerStats.d.ts +104 -0
- package/dist/vue/composables/useServerStats.d.ts.map +1 -0
- package/dist/vue/composables/useTheme.d.ts +6 -0
- package/dist/vue/composables/useTheme.d.ts.map +1 -0
- package/dist/vue/index-qCQpBftQ.js +1233 -0
- package/dist/vue/index.d.ts +10 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +11 -0
- package/dist/vue/style.css +1 -0
- package/dist/vue/useApiClient-BQQ9CF-q.js +10 -0
- package/dist/vue/useResizableTable-BoivAevK.js +17 -0
- package/package.json +72 -10
- package/dist/src/dashboard/dashboard_routes.d.ts +0 -16
- package/dist/src/dashboard/dashboard_routes.d.ts.map +0 -1
- package/dist/src/dashboard/dashboard_routes.js +0 -77
- package/dist/src/routes/debug_routes.d.ts +0 -14
- package/dist/src/routes/debug_routes.d.ts.map +0 -1
- package/dist/src/routes/debug_routes.js +0 -42
- package/dist/src/routes/stats_routes.d.ts +0 -14
- package/dist/src/routes/stats_routes.d.ts.map +0 -1
- package/dist/src/routes/stats_routes.js +0 -27
|
@@ -1,3619 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Client-side SPA for the server-stats full-page dashboard.
|
|
3
|
-
*
|
|
4
|
-
* Config is read from:
|
|
5
|
-
* - data-path on #ss-dash — dashboard base path (e.g. "/__stats")
|
|
6
|
-
* - data-tracing on #ss-dash — "1" if tracing enabled
|
|
7
|
-
* - <script id="ss-dash-config"> — JSON config object
|
|
8
|
-
*
|
|
9
|
-
* Hash-based routing: #overview, #requests, #queries, etc.
|
|
10
|
-
* Deep link support: #queries?id=42, #logs?requestId=abc123
|
|
11
|
-
*/
|
|
12
|
-
;(function () {
|
|
13
|
-
var root = document.getElementById('ss-dash')
|
|
14
|
-
if (!root) return
|
|
15
|
-
|
|
16
|
-
var BASE = (root.dataset.path || '/__stats').replace(/\/+$/, '')
|
|
17
|
-
var API = BASE + '/api'
|
|
18
|
-
var tracingEnabled = root.dataset.tracing === '1'
|
|
19
|
-
|
|
20
|
-
// Config from JSON script tag
|
|
21
|
-
var dashConfig = {}
|
|
22
|
-
try {
|
|
23
|
-
var cfgEl = document.getElementById('ss-dash-config')
|
|
24
|
-
if (cfgEl) dashConfig = JSON.parse(cfgEl.textContent || '{}')
|
|
25
|
-
} catch (e) {
|
|
26
|
-
/* ignore */
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
var customPanes = dashConfig.customPanes || []
|
|
30
|
-
|
|
31
|
-
// ── Helpers ───────────────────────────────────────────────────
|
|
32
|
-
var esc = function (s) {
|
|
33
|
-
if (typeof s !== 'string') s = '' + s
|
|
34
|
-
return s
|
|
35
|
-
.replace(/&/g, '&')
|
|
36
|
-
.replace(/</g, '<')
|
|
37
|
-
.replace(/>/g, '>')
|
|
38
|
-
.replace(/"/g, '"')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
var timeAgo = function (ts) {
|
|
42
|
-
if (!ts) return '-'
|
|
43
|
-
var d = typeof ts === 'string' ? new Date(ts).getTime() : ts
|
|
44
|
-
var diff = Math.floor((Date.now() - d) / 1000)
|
|
45
|
-
if (diff < 0) return 'just now'
|
|
46
|
-
if (diff < 60) return diff + 's ago'
|
|
47
|
-
if (diff < 3600) return Math.floor(diff / 60) + 'm ago'
|
|
48
|
-
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'
|
|
49
|
-
return Math.floor(diff / 86400) + 'd ago'
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
var formatTime = function (ts) {
|
|
53
|
-
var d = typeof ts === 'string' ? new Date(ts) : new Date(ts)
|
|
54
|
-
if (isNaN(d.getTime())) return '-'
|
|
55
|
-
return (
|
|
56
|
-
d.toLocaleTimeString('en-US', {
|
|
57
|
-
hour12: false,
|
|
58
|
-
hour: '2-digit',
|
|
59
|
-
minute: '2-digit',
|
|
60
|
-
second: '2-digit',
|
|
61
|
-
}) +
|
|
62
|
-
'.' +
|
|
63
|
-
String(d.getMilliseconds()).padStart(3, '0')
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
var methodClass = function (m) {
|
|
68
|
-
return 'ss-dash-method ss-dash-method-' + (typeof m === 'string' ? m.toLowerCase() : '')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
var durationClass = function (ms) {
|
|
72
|
-
if (ms > 500) return 'ss-dash-very-slow'
|
|
73
|
-
if (ms > 100) return 'ss-dash-slow'
|
|
74
|
-
return ''
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
var statusClass = function (code) {
|
|
78
|
-
if (code >= 500) return 'ss-dash-status-5xx'
|
|
79
|
-
if (code >= 400) return 'ss-dash-status-4xx'
|
|
80
|
-
if (code >= 300) return 'ss-dash-status-3xx'
|
|
81
|
-
return 'ss-dash-status-2xx'
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
var compactPreview = function (val, maxLen) {
|
|
85
|
-
if (val === null) return 'null'
|
|
86
|
-
if (typeof val === 'string')
|
|
87
|
-
return '"' + (val.length > 40 ? val.slice(0, 40) + '...' : val) + '"'
|
|
88
|
-
if (typeof val === 'number' || typeof val === 'boolean') return String(val)
|
|
89
|
-
if (Array.isArray(val)) {
|
|
90
|
-
if (val.length === 0) return '[]'
|
|
91
|
-
var items = val.slice(0, 3).map(function (v) {
|
|
92
|
-
return compactPreview(v, 30)
|
|
93
|
-
})
|
|
94
|
-
var s = '[' + items.join(', ') + (val.length > 3 ? ', ...' + val.length + ' items' : '') + ']'
|
|
95
|
-
return s.length > maxLen ? '[' + val.length + ' items]' : s
|
|
96
|
-
}
|
|
97
|
-
if (typeof val === 'object') {
|
|
98
|
-
var keys = Object.keys(val)
|
|
99
|
-
if (keys.length === 0) return '{}'
|
|
100
|
-
var pairs = []
|
|
101
|
-
for (var i = 0; i < Math.min(keys.length, 4); i++) {
|
|
102
|
-
pairs.push(keys[i] + ': ' + compactPreview(val[keys[i]], 30))
|
|
103
|
-
}
|
|
104
|
-
var s2 =
|
|
105
|
-
'{ ' + pairs.join(', ') + (keys.length > 4 ? ', ...+' + (keys.length - 4) : '') + ' }'
|
|
106
|
-
return s2.length > maxLen
|
|
107
|
-
? '{ ' + keys.slice(0, 6).join(', ') + (keys.length > 6 ? ', ...' : '') + ' }'
|
|
108
|
-
: s2
|
|
109
|
-
}
|
|
110
|
-
return String(val)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
var eventPreview = function (data) {
|
|
114
|
-
if (!data) return '-'
|
|
115
|
-
try {
|
|
116
|
-
var parsed = typeof data === 'string' ? JSON.parse(data) : data
|
|
117
|
-
return compactPreview(parsed, 100)
|
|
118
|
-
} catch (e) {
|
|
119
|
-
return data.length > 100 ? data.slice(0, 100) + '...' : data
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
var shortReqId = function (id) {
|
|
124
|
-
return id ? id.slice(0, 8) : ''
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
var TRUNC = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap'
|
|
128
|
-
|
|
129
|
-
var getTimestamp = function (obj) {
|
|
130
|
-
return obj.createdAt || obj.created_at || obj.timestamp || 0
|
|
131
|
-
}
|
|
132
|
-
var getStatus = function (obj) {
|
|
133
|
-
return obj.status_code || obj.statusCode
|
|
134
|
-
}
|
|
135
|
-
var renderStatusBadge = function (code) {
|
|
136
|
-
return '<span class="ss-dash-status ' + statusClass(code) + '">' + code + '</span>'
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
var formatCell = function (value, col) {
|
|
140
|
-
if (value === null || value === undefined) return '<span style="color:var(--ss-dim)">-</span>'
|
|
141
|
-
var fmt = col.format || 'text'
|
|
142
|
-
switch (fmt) {
|
|
143
|
-
case 'time':
|
|
144
|
-
return typeof value === 'number' ? formatTime(value) : esc(value)
|
|
145
|
-
case 'timeAgo':
|
|
146
|
-
return (
|
|
147
|
-
'<span class="ss-dash-event-time">' +
|
|
148
|
-
(typeof value === 'number' ? timeAgo(value) : esc(value)) +
|
|
149
|
-
'</span>'
|
|
150
|
-
)
|
|
151
|
-
case 'duration': {
|
|
152
|
-
var ms = typeof value === 'number' ? value : parseFloat(value)
|
|
153
|
-
if (isNaN(ms)) return esc(value)
|
|
154
|
-
return (
|
|
155
|
-
'<span class="ss-dash-duration ' + durationClass(ms) + '">' + ms.toFixed(2) + 'ms</span>'
|
|
156
|
-
)
|
|
157
|
-
}
|
|
158
|
-
case 'method':
|
|
159
|
-
return '<span class="' + methodClass(value) + '">' + esc(value) + '</span>'
|
|
160
|
-
case 'json': {
|
|
161
|
-
if (typeof value === 'string') {
|
|
162
|
-
try {
|
|
163
|
-
value = JSON.parse(value)
|
|
164
|
-
} catch (e) {
|
|
165
|
-
/* use as-is */
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
var preview = typeof value === 'object' ? compactPreview(value, 100) : String(value)
|
|
169
|
-
return (
|
|
170
|
-
'<span class="ss-dash-data-preview" style="cursor:default">' + esc(preview) + '</span>'
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
case 'badge': {
|
|
174
|
-
var sv = String(value).toLowerCase()
|
|
175
|
-
var colorMap = col.badgeColorMap || {}
|
|
176
|
-
var color = colorMap[sv] || 'muted'
|
|
177
|
-
return (
|
|
178
|
-
'<span class="ss-dash-badge ss-dash-badge-' + esc(color) + '">' + esc(value) + '</span>'
|
|
179
|
-
)
|
|
180
|
-
}
|
|
181
|
-
default:
|
|
182
|
-
return esc(value)
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// ── State ─────────────────────────────────────────────────────
|
|
187
|
-
var activeSection = 'overview'
|
|
188
|
-
var sidebarCollapsed = localStorage.getItem('ss-dash-sidebar') === 'collapsed'
|
|
189
|
-
var refreshTimer = null
|
|
190
|
-
var transmitSub = null
|
|
191
|
-
var isLive = false
|
|
192
|
-
|
|
193
|
-
// Per-section pagination state
|
|
194
|
-
var pageState = {}
|
|
195
|
-
var PER_PAGE = 50
|
|
196
|
-
|
|
197
|
-
var getPage = function (section) {
|
|
198
|
-
if (!pageState[section]) pageState[section] = { page: 1, total: 0 }
|
|
199
|
-
return pageState[section]
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// ── Fetch helper ──────────────────────────────────────────────
|
|
203
|
-
var fetchJSON = function (url) {
|
|
204
|
-
return fetch(url, { credentials: 'same-origin' }).then(function (r) {
|
|
205
|
-
if (!r.ok) throw new Error(r.status)
|
|
206
|
-
return r.json()
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
var fetchSection = function (section, urlSuffix, renderFn) {
|
|
211
|
-
fetchJSON(API + urlSuffix)
|
|
212
|
-
.then(renderFn)
|
|
213
|
-
.catch(function () {
|
|
214
|
-
setInner(
|
|
215
|
-
'ss-dash-' + section + '-body',
|
|
216
|
-
'<div class="ss-dash-empty">Failed to load ' + section + '</div>'
|
|
217
|
-
)
|
|
218
|
-
})
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// ── Theme ─────────────────────────────────────────────────────
|
|
222
|
-
var themeOverride = localStorage.getItem('ss-dash-theme')
|
|
223
|
-
var themeBtn = document.getElementById('ss-dash-theme-btn')
|
|
224
|
-
|
|
225
|
-
var applyTheme = function () {
|
|
226
|
-
if (themeOverride) {
|
|
227
|
-
root.setAttribute('data-theme', themeOverride)
|
|
228
|
-
} else {
|
|
229
|
-
root.removeAttribute('data-theme')
|
|
230
|
-
}
|
|
231
|
-
if (themeBtn) {
|
|
232
|
-
var isDark =
|
|
233
|
-
themeOverride === 'dark' ||
|
|
234
|
-
(!themeOverride && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
235
|
-
themeBtn.textContent = isDark ? '\u2600' : '\u263D'
|
|
236
|
-
themeBtn.title = isDark ? 'Switch to light theme' : 'Switch to dark theme'
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (themeBtn) {
|
|
241
|
-
themeBtn.addEventListener('click', function () {
|
|
242
|
-
var isDark =
|
|
243
|
-
themeOverride === 'dark' ||
|
|
244
|
-
(!themeOverride && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
245
|
-
themeOverride = isDark ? 'light' : 'dark'
|
|
246
|
-
localStorage.setItem('ss-dash-theme', themeOverride)
|
|
247
|
-
applyTheme()
|
|
248
|
-
})
|
|
249
|
-
}
|
|
250
|
-
applyTheme()
|
|
251
|
-
|
|
252
|
-
// ── Sidebar ───────────────────────────────────────────────────
|
|
253
|
-
var sidebar = document.getElementById('ss-dash-sidebar')
|
|
254
|
-
var sidebarToggle = document.getElementById('ss-dash-sidebar-toggle')
|
|
255
|
-
var navItems = root.querySelectorAll('[data-ss-section]')
|
|
256
|
-
|
|
257
|
-
var applySidebar = function () {
|
|
258
|
-
if (sidebar) sidebar.classList.toggle('ss-dash-collapsed', sidebarCollapsed)
|
|
259
|
-
if (sidebarToggle)
|
|
260
|
-
sidebarToggle.innerHTML = sidebarCollapsed
|
|
261
|
-
? '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 18l6-6-6-6"/></svg>'
|
|
262
|
-
: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M15 18l-6-6 6-6"/></svg>'
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (sidebarToggle) {
|
|
266
|
-
sidebarToggle.addEventListener('click', function () {
|
|
267
|
-
sidebarCollapsed = !sidebarCollapsed
|
|
268
|
-
localStorage.setItem('ss-dash-sidebar', sidebarCollapsed ? 'collapsed' : 'expanded')
|
|
269
|
-
applySidebar()
|
|
270
|
-
})
|
|
271
|
-
}
|
|
272
|
-
applySidebar()
|
|
273
|
-
|
|
274
|
-
// ── Section switching ─────────────────────────────────────────
|
|
275
|
-
var switchSection = function (name) {
|
|
276
|
-
if (name === activeSection) return
|
|
277
|
-
|
|
278
|
-
navItems.forEach(function (item) {
|
|
279
|
-
item.classList.toggle('ss-dash-active', item.getAttribute('data-ss-section') === name)
|
|
280
|
-
})
|
|
281
|
-
root.querySelectorAll('.ss-dash-pane').forEach(function (p) {
|
|
282
|
-
p.classList.toggle('ss-dash-active', p.id === 'ss-dash-pane-' + name)
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
activeSection = name
|
|
286
|
-
location.hash = name
|
|
287
|
-
loadSection(name)
|
|
288
|
-
startRefresh()
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
navItems.forEach(function (item) {
|
|
292
|
-
item.addEventListener('click', function () {
|
|
293
|
-
switchSection(item.getAttribute('data-ss-section'))
|
|
294
|
-
})
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
// ── Data loading ──────────────────────────────────────────────
|
|
298
|
-
var sectionLoaded = {}
|
|
299
|
-
|
|
300
|
-
var loadSection = function (name) {
|
|
301
|
-
switch (name) {
|
|
302
|
-
case 'overview':
|
|
303
|
-
fetchOverview()
|
|
304
|
-
break
|
|
305
|
-
case 'requests':
|
|
306
|
-
fetchRequests()
|
|
307
|
-
break
|
|
308
|
-
case 'queries':
|
|
309
|
-
fetchQueries()
|
|
310
|
-
break
|
|
311
|
-
case 'events':
|
|
312
|
-
fetchEvents()
|
|
313
|
-
break
|
|
314
|
-
case 'routes':
|
|
315
|
-
if (!sectionLoaded.routes) fetchRoutes()
|
|
316
|
-
break
|
|
317
|
-
case 'logs':
|
|
318
|
-
fetchLogs()
|
|
319
|
-
break
|
|
320
|
-
case 'emails':
|
|
321
|
-
fetchEmails()
|
|
322
|
-
break
|
|
323
|
-
case 'timeline':
|
|
324
|
-
fetchTraces()
|
|
325
|
-
break
|
|
326
|
-
case 'cache':
|
|
327
|
-
fetchCache()
|
|
328
|
-
break
|
|
329
|
-
case 'jobs':
|
|
330
|
-
fetchJobs()
|
|
331
|
-
break
|
|
332
|
-
case 'config':
|
|
333
|
-
if (!sectionLoaded.config) fetchConfig()
|
|
334
|
-
break
|
|
335
|
-
default: {
|
|
336
|
-
var cp = customPanes.find(function (p) {
|
|
337
|
-
return p.id === name
|
|
338
|
-
})
|
|
339
|
-
if (cp) fetchCustomPane(cp)
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// ── Overview ──────────────────────────────────────────────────
|
|
345
|
-
var overviewRange = '1h'
|
|
346
|
-
|
|
347
|
-
var fetchOverview = function () {
|
|
348
|
-
Promise.all([
|
|
349
|
-
fetchJSON(API + '/overview?range=' + overviewRange),
|
|
350
|
-
fetchJSON(API + '/overview/chart?range=' + overviewRange),
|
|
351
|
-
])
|
|
352
|
-
.then(function (results) {
|
|
353
|
-
renderOverview(results[0], results[1])
|
|
354
|
-
})
|
|
355
|
-
.catch(function () {
|
|
356
|
-
var el = document.getElementById('ss-dash-overview-content')
|
|
357
|
-
if (el) el.innerHTML = '<div class="ss-dash-empty">Failed to load overview</div>'
|
|
358
|
-
})
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Cache overview data so live updates (which only contain core metrics) don't wipe widgets
|
|
362
|
-
var lastChartData = []
|
|
363
|
-
var lastSparklines = {}
|
|
364
|
-
var lastSlowest = []
|
|
365
|
-
var lastQueryStats = {}
|
|
366
|
-
var lastRecentErrors = []
|
|
367
|
-
var lastTopEvents = []
|
|
368
|
-
var lastEmailActivity = {}
|
|
369
|
-
var lastLogLevelBreakdown = {}
|
|
370
|
-
var lastCacheStats = null
|
|
371
|
-
var lastJobQueueStatus = null
|
|
372
|
-
var lastStatusDistribution = {}
|
|
373
|
-
var lastSlowQueries = []
|
|
374
|
-
|
|
375
|
-
var renderOverview = function (data, chartData) {
|
|
376
|
-
var el = document.getElementById('ss-dash-overview-content')
|
|
377
|
-
if (!el) return
|
|
378
|
-
|
|
379
|
-
// Preserve data from previous full fetch when live partial data arrives
|
|
380
|
-
if (data.sparklines) lastSparklines = data.sparklines
|
|
381
|
-
var sparklines = data.sparklines || lastSparklines
|
|
382
|
-
if (chartData && chartData.buckets && chartData.buckets.length > 0)
|
|
383
|
-
lastChartData = chartData.buckets
|
|
384
|
-
var chart = (chartData && chartData.buckets) || lastChartData
|
|
385
|
-
|
|
386
|
-
if (data.slowestEndpoints) lastSlowest = data.slowestEndpoints
|
|
387
|
-
if (data.queryStats && data.queryStats.total !== undefined) lastQueryStats = data.queryStats
|
|
388
|
-
if (data.recentErrors) lastRecentErrors = data.recentErrors
|
|
389
|
-
if (data.topEvents && data.topEvents.length > 0) lastTopEvents = data.topEvents
|
|
390
|
-
if (data.emailActivity && (data.emailActivity.sent || data.emailActivity.queued || data.emailActivity.failed)) lastEmailActivity = data.emailActivity
|
|
391
|
-
if (data.logLevelBreakdown && (data.logLevelBreakdown.error || data.logLevelBreakdown.warn || data.logLevelBreakdown.info || data.logLevelBreakdown.debug)) lastLogLevelBreakdown = data.logLevelBreakdown
|
|
392
|
-
if (data.cacheStats) lastCacheStats = data.cacheStats
|
|
393
|
-
if (data.jobQueueStatus) lastJobQueueStatus = data.jobQueueStatus
|
|
394
|
-
if (data.statusDistribution) lastStatusDistribution = data.statusDistribution
|
|
395
|
-
if (data.slowestQueries) lastSlowQueries = data.slowestQueries
|
|
396
|
-
|
|
397
|
-
var slowest = data.slowestEndpoints || lastSlowest
|
|
398
|
-
var queryStats = data.queryStats || lastQueryStats
|
|
399
|
-
var recentErrors = data.recentErrors || lastRecentErrors
|
|
400
|
-
var topEvents = data.topEvents || lastTopEvents
|
|
401
|
-
var emailActivity = data.emailActivity || lastEmailActivity
|
|
402
|
-
var logLevelBreakdown = data.logLevelBreakdown || lastLogLevelBreakdown
|
|
403
|
-
var cacheStats = data.cacheStats || lastCacheStats
|
|
404
|
-
var jobQueueStatus = data.jobQueueStatus || lastJobQueueStatus
|
|
405
|
-
var statusDistribution = data.statusDistribution || lastStatusDistribution
|
|
406
|
-
var slowQueries = data.slowestQueries || lastSlowQueries
|
|
407
|
-
|
|
408
|
-
var avgVal = data.avgResponseTime || 0
|
|
409
|
-
var p95Val = data.p95ResponseTime || 0
|
|
410
|
-
var rpmVal = data.requestsPerMinute || 0
|
|
411
|
-
var errVal = data.errorRate || 0
|
|
412
|
-
var hasData = (data.totalRequests || 0) > 0
|
|
413
|
-
|
|
414
|
-
var avgClass = avgVal > 500 ? 'ss-dash-red' : avgVal > 200 ? 'ss-dash-amber' : 'ss-dash-accent'
|
|
415
|
-
var p95Class = p95Val > 500 ? 'ss-dash-red' : p95Val > 200 ? 'ss-dash-amber' : 'ss-dash-accent'
|
|
416
|
-
var errClass = errVal > 5 ? 'ss-dash-red' : errVal > 1 ? 'ss-dash-amber' : 'ss-dash-accent'
|
|
417
|
-
|
|
418
|
-
var fmtMs = function (v) {
|
|
419
|
-
return hasData ? v.toFixed(1) + 'ms' : '-'
|
|
420
|
-
}
|
|
421
|
-
var fmtNum = function (v) {
|
|
422
|
-
return hasData ? String(Math.round(v * 10) / 10) : '-'
|
|
423
|
-
}
|
|
424
|
-
var fmtPct = function (v) {
|
|
425
|
-
return hasData ? v.toFixed(1) + '%' : '-'
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
var html = '<div class="ss-dash-overview">'
|
|
429
|
-
|
|
430
|
-
// Top cards
|
|
431
|
-
html += '<div class="ss-dash-cards">'
|
|
432
|
-
html += renderCard(
|
|
433
|
-
'Avg Response Time',
|
|
434
|
-
fmtMs(avgVal),
|
|
435
|
-
hasData ? avgClass : 'ss-dash-dim',
|
|
436
|
-
sparklines.avgResponseTime
|
|
437
|
-
)
|
|
438
|
-
html += renderCard(
|
|
439
|
-
'P95 Response Time',
|
|
440
|
-
fmtMs(p95Val),
|
|
441
|
-
hasData ? p95Class : 'ss-dash-dim',
|
|
442
|
-
sparklines.p95ResponseTime
|
|
443
|
-
)
|
|
444
|
-
html += renderCard(
|
|
445
|
-
'Requests / min',
|
|
446
|
-
fmtNum(rpmVal),
|
|
447
|
-
hasData ? 'ss-dash-accent' : 'ss-dash-dim',
|
|
448
|
-
sparklines.requestsPerMinute
|
|
449
|
-
)
|
|
450
|
-
html += renderCard(
|
|
451
|
-
'Error Rate',
|
|
452
|
-
fmtPct(errVal),
|
|
453
|
-
hasData ? errClass : 'ss-dash-dim',
|
|
454
|
-
sparklines.errorRate
|
|
455
|
-
)
|
|
456
|
-
html += '</div>'
|
|
457
|
-
|
|
458
|
-
// Chart
|
|
459
|
-
html += '<div class="ss-dash-chart-container">'
|
|
460
|
-
html += '<div class="ss-dash-chart-header">'
|
|
461
|
-
html += '<span class="ss-dash-chart-title">Request Volume</span>'
|
|
462
|
-
html += '<div class="ss-dash-btn-group">'
|
|
463
|
-
;['5m', '15m', '30m', '1h', '6h', '24h', '7d'].forEach(function (r) {
|
|
464
|
-
html +=
|
|
465
|
-
'<button class="ss-dash-btn' +
|
|
466
|
-
(r === overviewRange ? ' ss-dash-active' : '') +
|
|
467
|
-
'" data-range="' +
|
|
468
|
-
r +
|
|
469
|
-
'">' +
|
|
470
|
-
r +
|
|
471
|
-
'</button>'
|
|
472
|
-
})
|
|
473
|
-
html += '</div></div>'
|
|
474
|
-
html += '<div class="ss-dash-chart" id="ss-dash-chart-area"></div>'
|
|
475
|
-
html += '</div>'
|
|
476
|
-
|
|
477
|
-
// Secondary cards
|
|
478
|
-
html += '<div class="ss-dash-secondary-cards">'
|
|
479
|
-
|
|
480
|
-
// Slowest endpoints
|
|
481
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
482
|
-
html +=
|
|
483
|
-
'<div class="ss-dash-secondary-card-title"><a href="#requests" class="ss-dash-widget-link">Slowest Endpoints</a></div>'
|
|
484
|
-
if (slowest.length > 0) {
|
|
485
|
-
html += '<ul class="ss-dash-secondary-list">'
|
|
486
|
-
slowest.forEach(function (ep) {
|
|
487
|
-
var epUrl = ep.url || ep.pattern || '-'
|
|
488
|
-
html +=
|
|
489
|
-
'<li><a href="#requests?url=' +
|
|
490
|
-
encodeURIComponent(epUrl) +
|
|
491
|
-
'" class="ss-dash-widget-row-link"><span title="' +
|
|
492
|
-
esc(epUrl) +
|
|
493
|
-
'">' +
|
|
494
|
-
esc(epUrl) +
|
|
495
|
-
'</span><span class="ss-dash-secondary-list-value ss-dash-duration ' +
|
|
496
|
-
durationClass(ep.avgDuration || 0) +
|
|
497
|
-
'">' +
|
|
498
|
-
(ep.avgDuration || 0).toFixed(1) +
|
|
499
|
-
'ms</span></a></li>'
|
|
500
|
-
})
|
|
501
|
-
html += '</ul>'
|
|
502
|
-
} else {
|
|
503
|
-
html += '<div class="ss-dash-empty" style="min-height:60px">No data yet</div>'
|
|
504
|
-
}
|
|
505
|
-
html += '</div>'
|
|
506
|
-
|
|
507
|
-
// Query stats
|
|
508
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
509
|
-
html +=
|
|
510
|
-
'<div class="ss-dash-secondary-card-title"><a href="#queries" class="ss-dash-widget-link">Query Stats</a></div>'
|
|
511
|
-
html += '<ul class="ss-dash-secondary-list">'
|
|
512
|
-
html +=
|
|
513
|
-
'<li><span>Total Queries</span><span class="ss-dash-secondary-list-value">' +
|
|
514
|
-
(queryStats.total || 0) +
|
|
515
|
-
'</span></li>'
|
|
516
|
-
html +=
|
|
517
|
-
'<li><span>Avg Duration</span><span class="ss-dash-secondary-list-value">' +
|
|
518
|
-
(queryStats.avgDuration || 0).toFixed(1) +
|
|
519
|
-
'ms</span></li>'
|
|
520
|
-
html +=
|
|
521
|
-
'<li><span>Queries / Request</span><span class="ss-dash-secondary-list-value">' +
|
|
522
|
-
(queryStats.perRequest || 0).toFixed(1) +
|
|
523
|
-
'</span></li>'
|
|
524
|
-
html += '</ul>'
|
|
525
|
-
html += '</div>'
|
|
526
|
-
|
|
527
|
-
// Recent errors
|
|
528
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
529
|
-
html +=
|
|
530
|
-
'<div class="ss-dash-secondary-card-title"><a href="#logs?level=error" class="ss-dash-widget-link">Recent Errors</a></div>'
|
|
531
|
-
if (recentErrors.length > 0) {
|
|
532
|
-
html += '<ul class="ss-dash-secondary-list">'
|
|
533
|
-
recentErrors.forEach(function (err) {
|
|
534
|
-
html +=
|
|
535
|
-
'<li><a href="#logs?id=' +
|
|
536
|
-
encodeURIComponent(err.id || '') +
|
|
537
|
-
'" class="ss-dash-widget-row-link"><span style="color:var(--ss-red-fg)" title="' +
|
|
538
|
-
esc(err.message || '') +
|
|
539
|
-
'">' +
|
|
540
|
-
esc(err.message || '') +
|
|
541
|
-
'</span><span class="ss-dash-secondary-list-value">' +
|
|
542
|
-
timeAgo(getTimestamp(err)) +
|
|
543
|
-
'</span></a></li>'
|
|
544
|
-
})
|
|
545
|
-
html += '</ul>'
|
|
546
|
-
} else {
|
|
547
|
-
html += '<div class="ss-dash-empty" style="min-height:60px">No recent errors</div>'
|
|
548
|
-
}
|
|
549
|
-
html += '</div>'
|
|
550
|
-
|
|
551
|
-
// Top Events
|
|
552
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
553
|
-
html +=
|
|
554
|
-
'<div class="ss-dash-secondary-card-title"><a href="#events" class="ss-dash-widget-link">Top Events</a></div>'
|
|
555
|
-
if (topEvents.length > 0) {
|
|
556
|
-
html += '<ul class="ss-dash-secondary-list">'
|
|
557
|
-
topEvents.slice(0, 5).forEach(function (ev) {
|
|
558
|
-
html +=
|
|
559
|
-
'<li><a href="#events?event_name=' +
|
|
560
|
-
encodeURIComponent(ev.name || ev.eventName || ev.event_name || ev.event || '') +
|
|
561
|
-
'" class="ss-dash-widget-row-link"><span title="' +
|
|
562
|
-
esc(ev.name || ev.eventName || ev.event_name || ev.event || '') +
|
|
563
|
-
'">' +
|
|
564
|
-
esc(ev.name || ev.eventName || ev.event_name || ev.event || '') +
|
|
565
|
-
'</span><span class="ss-dash-secondary-list-value">' +
|
|
566
|
-
(ev.count || 0) +
|
|
567
|
-
'</span></a></li>'
|
|
568
|
-
})
|
|
569
|
-
html += '</ul>'
|
|
570
|
-
} else {
|
|
571
|
-
html += '<div class="ss-dash-empty" style="min-height:60px">No events yet</div>'
|
|
572
|
-
}
|
|
573
|
-
html += '</div>'
|
|
574
|
-
|
|
575
|
-
// Email Activity
|
|
576
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
577
|
-
html +=
|
|
578
|
-
'<div class="ss-dash-secondary-card-title"><a href="#emails" class="ss-dash-widget-link">Email Activity</a></div>'
|
|
579
|
-
html += '<ul class="ss-dash-secondary-list">'
|
|
580
|
-
html +=
|
|
581
|
-
'<li><a href="#emails?status=sent" class="ss-dash-widget-row-link"><span>Sent</span><span class="ss-dash-secondary-list-value">' +
|
|
582
|
-
(emailActivity.sent || 0) +
|
|
583
|
-
'</span></a></li>'
|
|
584
|
-
html +=
|
|
585
|
-
'<li><a href="#emails?status=queued" class="ss-dash-widget-row-link"><span>Queued</span><span class="ss-dash-secondary-list-value">' +
|
|
586
|
-
(emailActivity.queued || 0) +
|
|
587
|
-
'</span></a></li>'
|
|
588
|
-
html +=
|
|
589
|
-
'<li><a href="#emails?status=failed" class="ss-dash-widget-row-link"><span>Failed</span><span class="ss-dash-secondary-list-value">' +
|
|
590
|
-
(emailActivity.failed || 0) +
|
|
591
|
-
'</span></a></li>'
|
|
592
|
-
html += '</ul>'
|
|
593
|
-
html += '</div>'
|
|
594
|
-
|
|
595
|
-
// Log Level Breakdown
|
|
596
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
597
|
-
html +=
|
|
598
|
-
'<div class="ss-dash-secondary-card-title"><a href="#logs" class="ss-dash-widget-link">Log Levels</a></div>'
|
|
599
|
-
html += '<ul class="ss-dash-secondary-list">'
|
|
600
|
-
html +=
|
|
601
|
-
'<li><a href="#logs?level=error" class="ss-dash-widget-row-link"><span style="color:var(--ss-red-fg)">Error</span><span class="ss-dash-secondary-list-value">' +
|
|
602
|
-
(logLevelBreakdown.error || 0) +
|
|
603
|
-
'</span></a></li>'
|
|
604
|
-
html +=
|
|
605
|
-
'<li><a href="#logs?level=warn" class="ss-dash-widget-row-link"><span style="color:var(--ss-amber-fg)">Warn</span><span class="ss-dash-secondary-list-value">' +
|
|
606
|
-
(logLevelBreakdown.warn || 0) +
|
|
607
|
-
'</span></a></li>'
|
|
608
|
-
html +=
|
|
609
|
-
'<li><a href="#logs?level=info" class="ss-dash-widget-row-link"><span style="color:var(--ss-green-fg)">Info</span><span class="ss-dash-secondary-list-value">' +
|
|
610
|
-
(logLevelBreakdown.info || 0) +
|
|
611
|
-
'</span></a></li>'
|
|
612
|
-
html +=
|
|
613
|
-
'<li><a href="#logs?level=debug" class="ss-dash-widget-row-link"><span style="color:var(--ss-muted)">Debug</span><span class="ss-dash-secondary-list-value">' +
|
|
614
|
-
(logLevelBreakdown.debug || 0) +
|
|
615
|
-
'</span></a></li>'
|
|
616
|
-
html += '</ul>'
|
|
617
|
-
html += '</div>'
|
|
618
|
-
|
|
619
|
-
// Cache Stats
|
|
620
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
621
|
-
html +=
|
|
622
|
-
'<div class="ss-dash-secondary-card-title"><a href="#cache" class="ss-dash-widget-link">Cache</a></div>'
|
|
623
|
-
if (cacheStats) {
|
|
624
|
-
html +=
|
|
625
|
-
'<div class="ss-dash-widget-stat"><span class="ss-dash-widget-stat-label">Connected</span><span class="ss-dash-widget-stat-value" style="color:var(--ss-green-fg)">\u2713</span></div>'
|
|
626
|
-
html +=
|
|
627
|
-
'<div class="ss-dash-widget-stat"><span class="ss-dash-widget-stat-label">Total Keys</span><span class="ss-dash-widget-stat-value">' +
|
|
628
|
-
(cacheStats.totalKeys || 0) +
|
|
629
|
-
'</span></div>'
|
|
630
|
-
html +=
|
|
631
|
-
'<div class="ss-dash-widget-stat"><span class="ss-dash-widget-stat-label">Hit Rate</span><span class="ss-dash-widget-stat-value">' +
|
|
632
|
-
(cacheStats.hitRate || 0).toFixed(1) +
|
|
633
|
-
'%</span></div>'
|
|
634
|
-
html +=
|
|
635
|
-
'<div class="ss-dash-widget-stat"><span class="ss-dash-widget-stat-label">Memory</span><span class="ss-dash-widget-stat-value">' +
|
|
636
|
-
esc(cacheStats.memory || '-') +
|
|
637
|
-
'</span></div>'
|
|
638
|
-
} else {
|
|
639
|
-
html += '<div class="ss-dash-empty" style="min-height:60px">Not available</div>'
|
|
640
|
-
}
|
|
641
|
-
html += '</div>'
|
|
642
|
-
|
|
643
|
-
// Job Queue
|
|
644
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
645
|
-
html +=
|
|
646
|
-
'<div class="ss-dash-secondary-card-title"><a href="#jobs" class="ss-dash-widget-link">Job Queue</a></div>'
|
|
647
|
-
if (jobQueueStatus) {
|
|
648
|
-
html += '<ul class="ss-dash-secondary-list">'
|
|
649
|
-
html +=
|
|
650
|
-
'<li><a href="#jobs?status=active" class="ss-dash-widget-row-link"><span>Active</span><span class="ss-dash-secondary-list-value">' +
|
|
651
|
-
(jobQueueStatus.active || 0) +
|
|
652
|
-
'</span></a></li>'
|
|
653
|
-
html +=
|
|
654
|
-
'<li><a href="#jobs?status=waiting" class="ss-dash-widget-row-link"><span>Waiting</span><span class="ss-dash-secondary-list-value">' +
|
|
655
|
-
(jobQueueStatus.waiting || 0) +
|
|
656
|
-
'</span></a></li>'
|
|
657
|
-
html +=
|
|
658
|
-
'<li><a href="#jobs?status=failed" class="ss-dash-widget-row-link"><span>Failed</span><span class="ss-dash-secondary-list-value">' +
|
|
659
|
-
(jobQueueStatus.failed || 0) +
|
|
660
|
-
'</span></a></li>'
|
|
661
|
-
html +=
|
|
662
|
-
'<li><a href="#jobs?status=completed" class="ss-dash-widget-row-link"><span>Completed</span><span class="ss-dash-secondary-list-value">' +
|
|
663
|
-
(jobQueueStatus.completed || 0) +
|
|
664
|
-
'</span></a></li>'
|
|
665
|
-
html += '</ul>'
|
|
666
|
-
} else {
|
|
667
|
-
html += '<div class="ss-dash-empty" style="min-height:60px">Not available</div>'
|
|
668
|
-
}
|
|
669
|
-
html += '</div>'
|
|
670
|
-
|
|
671
|
-
// Response Status
|
|
672
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
673
|
-
html +=
|
|
674
|
-
'<div class="ss-dash-secondary-card-title"><a href="#requests" class="ss-dash-widget-link">Response Status</a></div>'
|
|
675
|
-
html += '<ul class="ss-dash-secondary-list">'
|
|
676
|
-
html +=
|
|
677
|
-
'<li><a href="#requests?status=2xx" class="ss-dash-widget-row-link"><span style="color:var(--ss-green-fg)">2xx</span><span class="ss-dash-secondary-list-value">' +
|
|
678
|
-
(statusDistribution['2xx'] || 0) +
|
|
679
|
-
'</span></a></li>'
|
|
680
|
-
html +=
|
|
681
|
-
'<li><a href="#requests?status=3xx" class="ss-dash-widget-row-link"><span style="color:var(--ss-blue-fg)">3xx</span><span class="ss-dash-secondary-list-value">' +
|
|
682
|
-
(statusDistribution['3xx'] || 0) +
|
|
683
|
-
'</span></a></li>'
|
|
684
|
-
html +=
|
|
685
|
-
'<li><a href="#requests?status=4xx" class="ss-dash-widget-row-link"><span style="color:var(--ss-amber-fg)">4xx</span><span class="ss-dash-secondary-list-value">' +
|
|
686
|
-
(statusDistribution['4xx'] || 0) +
|
|
687
|
-
'</span></a></li>'
|
|
688
|
-
html +=
|
|
689
|
-
'<li><a href="#requests?status=5xx" class="ss-dash-widget-row-link"><span style="color:var(--ss-red-fg)">5xx</span><span class="ss-dash-secondary-list-value">' +
|
|
690
|
-
(statusDistribution['5xx'] || 0) +
|
|
691
|
-
'</span></a></li>'
|
|
692
|
-
html += '</ul>'
|
|
693
|
-
html += '</div>'
|
|
694
|
-
|
|
695
|
-
// Slowest Queries
|
|
696
|
-
html += '<div class="ss-dash-secondary-card">'
|
|
697
|
-
html +=
|
|
698
|
-
'<div class="ss-dash-secondary-card-title"><a href="#queries" class="ss-dash-widget-link">Slowest Queries</a></div>'
|
|
699
|
-
if (slowQueries.length > 0) {
|
|
700
|
-
html += '<ul class="ss-dash-secondary-list">'
|
|
701
|
-
slowQueries.slice(0, 5).forEach(function (q) {
|
|
702
|
-
var sql = q.sqlNormalized || q.normalizedSql || q.sql_normalized || q.sql || '-'
|
|
703
|
-
html +=
|
|
704
|
-
'<li><a href="#queries" class="ss-dash-widget-row-link"><span title="' +
|
|
705
|
-
esc(sql) +
|
|
706
|
-
'">' +
|
|
707
|
-
esc(sql) +
|
|
708
|
-
'</span><span class="ss-dash-secondary-list-value ss-dash-duration ' +
|
|
709
|
-
durationClass(q.avgDuration || 0) +
|
|
710
|
-
'">' +
|
|
711
|
-
(q.avgDuration || 0).toFixed(1) +
|
|
712
|
-
'ms</span></a></li>'
|
|
713
|
-
})
|
|
714
|
-
html += '</ul>'
|
|
715
|
-
} else {
|
|
716
|
-
html += '<div class="ss-dash-empty" style="min-height:60px">No queries yet</div>'
|
|
717
|
-
}
|
|
718
|
-
html += '</div>'
|
|
719
|
-
|
|
720
|
-
html += '</div></div>'
|
|
721
|
-
el.innerHTML = html
|
|
722
|
-
|
|
723
|
-
// Bind range buttons
|
|
724
|
-
el.querySelectorAll('[data-range]').forEach(function (btn) {
|
|
725
|
-
btn.addEventListener('click', function () {
|
|
726
|
-
overviewRange = btn.getAttribute('data-range')
|
|
727
|
-
fetchOverview()
|
|
728
|
-
})
|
|
729
|
-
})
|
|
730
|
-
|
|
731
|
-
// Render SVG chart
|
|
732
|
-
if (chart.length > 0) renderBarChart(chart)
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
var renderCard = function (title, value, colorClass, sparkline) {
|
|
736
|
-
var html = '<div class="ss-dash-card">'
|
|
737
|
-
html += '<div class="ss-dash-card-title">' + esc(title) + '</div>'
|
|
738
|
-
html += '<div class="ss-dash-card-value ' + colorClass + '">' + esc(value) + '</div>'
|
|
739
|
-
if (sparkline && sparkline.length > 1) {
|
|
740
|
-
html += '<div class="ss-dash-sparkline">' + renderSparklineSVG(sparkline) + '</div>'
|
|
741
|
-
}
|
|
742
|
-
html += '</div>'
|
|
743
|
-
return html
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
var renderSparklineSVG = function (points) {
|
|
747
|
-
var w = 200,
|
|
748
|
-
h = 30
|
|
749
|
-
var max = Math.max.apply(null, points) || 1
|
|
750
|
-
var step = w / (points.length - 1)
|
|
751
|
-
|
|
752
|
-
var coords = points.map(function (v, i) {
|
|
753
|
-
return (i * step).toFixed(1) + ',' + (h - ((v / max) * h * 0.9 + h * 0.05)).toFixed(1)
|
|
754
|
-
})
|
|
755
|
-
var pathD = 'M' + coords.join(' L')
|
|
756
|
-
var areaD = pathD + ' L' + w + ',' + h + ' L0,' + h + ' Z'
|
|
757
|
-
|
|
758
|
-
return (
|
|
759
|
-
'<svg viewBox="0 0 ' +
|
|
760
|
-
w +
|
|
761
|
-
' ' +
|
|
762
|
-
h +
|
|
763
|
-
'" preserveAspectRatio="none">' +
|
|
764
|
-
'<path class="ss-dash-sparkline-area" d="' +
|
|
765
|
-
areaD +
|
|
766
|
-
'"/>' +
|
|
767
|
-
'<path class="ss-dash-sparkline-line" d="' +
|
|
768
|
-
pathD +
|
|
769
|
-
'"/>' +
|
|
770
|
-
'</svg>'
|
|
771
|
-
)
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
var renderBarChart = function (data) {
|
|
775
|
-
var container = document.getElementById('ss-dash-chart-area')
|
|
776
|
-
if (!container) return
|
|
777
|
-
|
|
778
|
-
if (!data || data.length === 0) {
|
|
779
|
-
container.innerHTML =
|
|
780
|
-
'<div class="ss-dash-empty" style="height:100%;display:flex;align-items:center;justify-content:center">No chart data for this range</div>'
|
|
781
|
-
return
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
var w = container.clientWidth || 600
|
|
785
|
-
var h = container.clientHeight || 200
|
|
786
|
-
var pad = { top: 12, right: 12, bottom: 28, left: 38 }
|
|
787
|
-
var cw = w - pad.left - pad.right
|
|
788
|
-
var ch = h - pad.top - pad.bottom
|
|
789
|
-
|
|
790
|
-
var maxCount = 0
|
|
791
|
-
data.forEach(function (d) {
|
|
792
|
-
var total = (d.requestCount || 0) + (d.request_count || 0)
|
|
793
|
-
if (total > maxCount) maxCount = total
|
|
794
|
-
})
|
|
795
|
-
// Add 10% headroom so bars don't touch the top
|
|
796
|
-
var yMax = maxCount > 0 ? Math.ceil(maxCount * 1.1) : 1
|
|
797
|
-
|
|
798
|
-
// Choose nice Y-axis tick values
|
|
799
|
-
var yTicks = niceYTicks(yMax, 4)
|
|
800
|
-
var yTop = yTicks[yTicks.length - 1] || yMax
|
|
801
|
-
|
|
802
|
-
var toY = function (val) {
|
|
803
|
-
return pad.top + ch - (val / yTop) * ch
|
|
804
|
-
}
|
|
805
|
-
var toX = function (i) {
|
|
806
|
-
return pad.left + (i / (data.length - 1 || 1)) * cw
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Build point arrays for the area chart
|
|
810
|
-
var totalPoints = []
|
|
811
|
-
var errorPoints = []
|
|
812
|
-
data.forEach(function (d, i) {
|
|
813
|
-
var total = (d.requestCount || 0) + (d.request_count || 0)
|
|
814
|
-
var errors = (d.errorCount || 0) + (d.error_count || 0)
|
|
815
|
-
totalPoints.push({ x: toX(i), y: toY(total), val: total })
|
|
816
|
-
errorPoints.push({ x: toX(i), y: toY(errors), val: errors })
|
|
817
|
-
})
|
|
818
|
-
|
|
819
|
-
// Smooth curve helper (monotone cubic spline)
|
|
820
|
-
var smoothPath = function (points) {
|
|
821
|
-
if (points.length < 2) return ''
|
|
822
|
-
if (points.length === 2)
|
|
823
|
-
return (
|
|
824
|
-
'M' +
|
|
825
|
-
points[0].x.toFixed(1) +
|
|
826
|
-
',' +
|
|
827
|
-
points[0].y.toFixed(1) +
|
|
828
|
-
'L' +
|
|
829
|
-
points[1].x.toFixed(1) +
|
|
830
|
-
',' +
|
|
831
|
-
points[1].y.toFixed(1)
|
|
832
|
-
)
|
|
833
|
-
|
|
834
|
-
var d = 'M' + points[0].x.toFixed(1) + ',' + points[0].y.toFixed(1)
|
|
835
|
-
for (var pi = 1; pi < points.length; pi++) {
|
|
836
|
-
var p0 = points[pi - 1]
|
|
837
|
-
var p1 = points[pi]
|
|
838
|
-
var cpx = (p0.x + p1.x) / 2
|
|
839
|
-
d +=
|
|
840
|
-
' C' +
|
|
841
|
-
cpx.toFixed(1) +
|
|
842
|
-
',' +
|
|
843
|
-
p0.y.toFixed(1) +
|
|
844
|
-
' ' +
|
|
845
|
-
cpx.toFixed(1) +
|
|
846
|
-
',' +
|
|
847
|
-
p1.y.toFixed(1) +
|
|
848
|
-
' ' +
|
|
849
|
-
p1.x.toFixed(1) +
|
|
850
|
-
',' +
|
|
851
|
-
p1.y.toFixed(1)
|
|
852
|
-
}
|
|
853
|
-
return d
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
var baseline = pad.top + ch
|
|
857
|
-
|
|
858
|
-
var svg = '<svg viewBox="0 0 ' + w + ' ' + h + '" class="ss-dash-chart-svg">'
|
|
859
|
-
|
|
860
|
-
// Defs: gradients
|
|
861
|
-
svg += '<defs>'
|
|
862
|
-
svg += '<linearGradient id="ss-cg-total" x1="0" y1="0" x2="0" y2="1">'
|
|
863
|
-
svg += '<stop offset="0%" stop-color="var(--ss-accent)" stop-opacity="0.3"/>'
|
|
864
|
-
svg += '<stop offset="100%" stop-color="var(--ss-accent)" stop-opacity="0.02"/>'
|
|
865
|
-
svg += '</linearGradient>'
|
|
866
|
-
svg += '<linearGradient id="ss-cg-error" x1="0" y1="0" x2="0" y2="1">'
|
|
867
|
-
svg += '<stop offset="0%" stop-color="var(--ss-red-fg)" stop-opacity="0.35"/>'
|
|
868
|
-
svg += '<stop offset="100%" stop-color="var(--ss-red-fg)" stop-opacity="0.02"/>'
|
|
869
|
-
svg += '</linearGradient>'
|
|
870
|
-
svg += '</defs>'
|
|
871
|
-
|
|
872
|
-
// Horizontal grid lines
|
|
873
|
-
yTicks.forEach(function (val) {
|
|
874
|
-
var yy = toY(val)
|
|
875
|
-
svg +=
|
|
876
|
-
'<line x1="' +
|
|
877
|
-
pad.left +
|
|
878
|
-
'" y1="' +
|
|
879
|
-
yy.toFixed(1) +
|
|
880
|
-
'" x2="' +
|
|
881
|
-
(w - pad.right) +
|
|
882
|
-
'" y2="' +
|
|
883
|
-
yy.toFixed(1) +
|
|
884
|
-
'" stroke="var(--ss-border-faint)" stroke-width="0.5" stroke-dasharray="3,3"/>'
|
|
885
|
-
svg +=
|
|
886
|
-
'<text x="' +
|
|
887
|
-
(pad.left - 6) +
|
|
888
|
-
'" y="' +
|
|
889
|
-
yy.toFixed(1) +
|
|
890
|
-
'" text-anchor="end" fill="var(--ss-dim)" font-size="9" dominant-baseline="middle">' +
|
|
891
|
-
val +
|
|
892
|
-
'</text>'
|
|
893
|
-
})
|
|
894
|
-
|
|
895
|
-
// Total requests: filled area + line
|
|
896
|
-
var totalPath = smoothPath(totalPoints)
|
|
897
|
-
if (totalPath) {
|
|
898
|
-
var last = totalPoints[totalPoints.length - 1]
|
|
899
|
-
var first = totalPoints[0]
|
|
900
|
-
svg +=
|
|
901
|
-
'<path d="' +
|
|
902
|
-
totalPath +
|
|
903
|
-
' L' +
|
|
904
|
-
last.x.toFixed(1) +
|
|
905
|
-
',' +
|
|
906
|
-
baseline +
|
|
907
|
-
' L' +
|
|
908
|
-
first.x.toFixed(1) +
|
|
909
|
-
',' +
|
|
910
|
-
baseline +
|
|
911
|
-
' Z" fill="url(#ss-cg-total)"/>'
|
|
912
|
-
svg +=
|
|
913
|
-
'<path d="' +
|
|
914
|
-
totalPath +
|
|
915
|
-
'" fill="none" stroke="var(--ss-accent)" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"/>'
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
// Error requests: filled area + line (if any errors)
|
|
919
|
-
var hasErrors = errorPoints.some(function (p) {
|
|
920
|
-
return p.val > 0
|
|
921
|
-
})
|
|
922
|
-
if (hasErrors) {
|
|
923
|
-
var errorPath = smoothPath(errorPoints)
|
|
924
|
-
if (errorPath) {
|
|
925
|
-
var eLast = errorPoints[errorPoints.length - 1]
|
|
926
|
-
var eFirst = errorPoints[0]
|
|
927
|
-
svg +=
|
|
928
|
-
'<path d="' +
|
|
929
|
-
errorPath +
|
|
930
|
-
' L' +
|
|
931
|
-
eLast.x.toFixed(1) +
|
|
932
|
-
',' +
|
|
933
|
-
baseline +
|
|
934
|
-
' L' +
|
|
935
|
-
eFirst.x.toFixed(1) +
|
|
936
|
-
',' +
|
|
937
|
-
baseline +
|
|
938
|
-
' Z" fill="url(#ss-cg-error)"/>'
|
|
939
|
-
svg +=
|
|
940
|
-
'<path d="' +
|
|
941
|
-
errorPath +
|
|
942
|
-
'" fill="none" stroke="var(--ss-red-fg)" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="4,2"/>'
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// Interactive dots and hover zones
|
|
947
|
-
data.forEach(function (d, i) {
|
|
948
|
-
var total = (d.requestCount || 0) + (d.request_count || 0)
|
|
949
|
-
var errors = (d.errorCount || 0) + (d.error_count || 0)
|
|
950
|
-
var success = total - errors
|
|
951
|
-
var cx = totalPoints[i].x
|
|
952
|
-
var cy = totalPoints[i].y
|
|
953
|
-
|
|
954
|
-
// Invisible wide hover target
|
|
955
|
-
var sliceW = cw / (data.length || 1)
|
|
956
|
-
svg +=
|
|
957
|
-
'<rect x="' +
|
|
958
|
-
(cx - sliceW / 2).toFixed(1) +
|
|
959
|
-
'" y="' +
|
|
960
|
-
pad.top +
|
|
961
|
-
'" width="' +
|
|
962
|
-
sliceW.toFixed(1) +
|
|
963
|
-
'" height="' +
|
|
964
|
-
ch +
|
|
965
|
-
'" fill="transparent" class="ss-dash-chart-hover-zone" data-idx="' +
|
|
966
|
-
i +
|
|
967
|
-
'"/>'
|
|
968
|
-
|
|
969
|
-
// Visible dot (only for non-zero)
|
|
970
|
-
if (total > 0) {
|
|
971
|
-
svg +=
|
|
972
|
-
'<circle cx="' +
|
|
973
|
-
cx.toFixed(1) +
|
|
974
|
-
'" cy="' +
|
|
975
|
-
cy.toFixed(1) +
|
|
976
|
-
'" r="2.5" fill="var(--ss-accent)" stroke="var(--ss-surface)" stroke-width="1" class="ss-dash-chart-dot" data-idx="' +
|
|
977
|
-
i +
|
|
978
|
-
'"/>'
|
|
979
|
-
}
|
|
980
|
-
if (errors > 0) {
|
|
981
|
-
var ey = errorPoints[i].y
|
|
982
|
-
svg +=
|
|
983
|
-
'<circle cx="' +
|
|
984
|
-
cx.toFixed(1) +
|
|
985
|
-
'" cy="' +
|
|
986
|
-
ey.toFixed(1) +
|
|
987
|
-
'" r="2" fill="var(--ss-red-fg)" stroke="var(--ss-surface)" stroke-width="1" class="ss-dash-chart-dot ss-dash-chart-dot-err" data-idx="' +
|
|
988
|
-
i +
|
|
989
|
-
'"/>'
|
|
990
|
-
}
|
|
991
|
-
})
|
|
992
|
-
|
|
993
|
-
// X axis labels
|
|
994
|
-
var maxLabels = Math.min(10, data.length)
|
|
995
|
-
var labelInterval = Math.max(1, Math.ceil(data.length / maxLabels))
|
|
996
|
-
data.forEach(function (d, i) {
|
|
997
|
-
if (i % labelInterval === 0 || i === data.length - 1) {
|
|
998
|
-
var x = toX(i)
|
|
999
|
-
var label = ''
|
|
1000
|
-
if (d.bucket) {
|
|
1001
|
-
var bd = new Date(d.bucket)
|
|
1002
|
-
label = bd.toLocaleTimeString('en-US', {
|
|
1003
|
-
hour12: false,
|
|
1004
|
-
hour: '2-digit',
|
|
1005
|
-
minute: '2-digit',
|
|
1006
|
-
})
|
|
1007
|
-
}
|
|
1008
|
-
svg +=
|
|
1009
|
-
'<text x="' +
|
|
1010
|
-
x.toFixed(1) +
|
|
1011
|
-
'" y="' +
|
|
1012
|
-
(h - 6) +
|
|
1013
|
-
'" text-anchor="middle" fill="var(--ss-dim)" font-size="9">' +
|
|
1014
|
-
esc(label) +
|
|
1015
|
-
'</text>'
|
|
1016
|
-
}
|
|
1017
|
-
})
|
|
1018
|
-
|
|
1019
|
-
svg += '</svg>'
|
|
1020
|
-
|
|
1021
|
-
// Tooltip element
|
|
1022
|
-
svg += '<div class="ss-dash-chart-tooltip" id="ss-dash-chart-tip" style="display:none"></div>'
|
|
1023
|
-
|
|
1024
|
-
container.innerHTML = svg
|
|
1025
|
-
|
|
1026
|
-
// Hover interactivity
|
|
1027
|
-
var tip = document.getElementById('ss-dash-chart-tip')
|
|
1028
|
-
var svgEl = container.querySelector('svg')
|
|
1029
|
-
if (svgEl && tip) {
|
|
1030
|
-
var dots = container.querySelectorAll('.ss-dash-chart-dot')
|
|
1031
|
-
var zones = container.querySelectorAll('.ss-dash-chart-hover-zone')
|
|
1032
|
-
|
|
1033
|
-
var showTip = function (idx, x) {
|
|
1034
|
-
var d = data[idx]
|
|
1035
|
-
if (!d) return
|
|
1036
|
-
var total = (d.requestCount || 0) + (d.request_count || 0)
|
|
1037
|
-
var errors = (d.errorCount || 0) + (d.error_count || 0)
|
|
1038
|
-
var success = total - errors
|
|
1039
|
-
var time = ''
|
|
1040
|
-
if (d.bucket) {
|
|
1041
|
-
var bd = new Date(d.bucket)
|
|
1042
|
-
time = bd.toLocaleTimeString('en-US', {
|
|
1043
|
-
hour12: false,
|
|
1044
|
-
hour: '2-digit',
|
|
1045
|
-
minute: '2-digit',
|
|
1046
|
-
})
|
|
1047
|
-
}
|
|
1048
|
-
tip.innerHTML =
|
|
1049
|
-
'<div style="font-weight:600;margin-bottom:2px;color:var(--ss-text)">' +
|
|
1050
|
-
esc(time) +
|
|
1051
|
-
'</div>' +
|
|
1052
|
-
'<div style="color:var(--ss-accent)">' +
|
|
1053
|
-
total +
|
|
1054
|
-
' requests</div>' +
|
|
1055
|
-
(errors > 0 ? '<div style="color:var(--ss-red-fg)">' + errors + ' errors</div>' : '')
|
|
1056
|
-
tip.style.display = 'block'
|
|
1057
|
-
|
|
1058
|
-
// Position tooltip
|
|
1059
|
-
var tipW = tip.offsetWidth || 100
|
|
1060
|
-
var left = x - tipW / 2
|
|
1061
|
-
if (left < 0) left = 4
|
|
1062
|
-
if (left + tipW > w) left = w - tipW - 4
|
|
1063
|
-
tip.style.left = left + 'px'
|
|
1064
|
-
tip.style.top = pad.top - 4 + 'px'
|
|
1065
|
-
|
|
1066
|
-
// Highlight dots for this index
|
|
1067
|
-
dots.forEach(function (dot) {
|
|
1068
|
-
var isActive = dot.getAttribute('data-idx') === String(idx)
|
|
1069
|
-
dot.setAttribute(
|
|
1070
|
-
'r',
|
|
1071
|
-
isActive
|
|
1072
|
-
? dot.classList.contains('ss-dash-chart-dot-err')
|
|
1073
|
-
? '3.5'
|
|
1074
|
-
: '4'
|
|
1075
|
-
: dot.classList.contains('ss-dash-chart-dot-err')
|
|
1076
|
-
? '2'
|
|
1077
|
-
: '2.5'
|
|
1078
|
-
)
|
|
1079
|
-
dot.style.opacity = isActive ? '1' : '0.5'
|
|
1080
|
-
})
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
var hideTip = function () {
|
|
1084
|
-
tip.style.display = 'none'
|
|
1085
|
-
dots.forEach(function (dot) {
|
|
1086
|
-
dot.setAttribute('r', dot.classList.contains('ss-dash-chart-dot-err') ? '2' : '2.5')
|
|
1087
|
-
dot.style.opacity = '1'
|
|
1088
|
-
})
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
zones.forEach(function (zone) {
|
|
1092
|
-
zone.addEventListener('mouseenter', function () {
|
|
1093
|
-
var idx = parseInt(zone.getAttribute('data-idx'), 10)
|
|
1094
|
-
var rect = container.getBoundingClientRect()
|
|
1095
|
-
var px = totalPoints[idx] ? totalPoints[idx].x : 0
|
|
1096
|
-
showTip(idx, px)
|
|
1097
|
-
})
|
|
1098
|
-
zone.addEventListener('mouseleave', hideTip)
|
|
1099
|
-
})
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// Legend
|
|
1103
|
-
var legend = document.getElementById('ss-dash-chart-legend')
|
|
1104
|
-
if (!legend) {
|
|
1105
|
-
legend = document.createElement('div')
|
|
1106
|
-
legend.id = 'ss-dash-chart-legend'
|
|
1107
|
-
legend.className = 'ss-dash-chart-legend'
|
|
1108
|
-
container.parentNode.insertBefore(legend, container.nextSibling)
|
|
1109
|
-
}
|
|
1110
|
-
legend.innerHTML =
|
|
1111
|
-
'<span class="ss-dash-chart-legend-item"><span class="ss-dash-legend-dot" style="background:var(--ss-accent)"></span>Requests</span>' +
|
|
1112
|
-
(hasErrors
|
|
1113
|
-
? '<span class="ss-dash-chart-legend-item"><span class="ss-dash-legend-dot" style="background:var(--ss-red-fg)"></span>Errors</span>'
|
|
1114
|
-
: '')
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
// Compute nice Y-axis tick values
|
|
1118
|
-
var niceYTicks = function (max, count) {
|
|
1119
|
-
if (max <= 0) return [0]
|
|
1120
|
-
var raw = max / count
|
|
1121
|
-
var mag = Math.pow(10, Math.floor(Math.log10(raw)))
|
|
1122
|
-
var nice = raw / mag
|
|
1123
|
-
var step
|
|
1124
|
-
if (nice <= 1) step = mag
|
|
1125
|
-
else if (nice <= 2) step = 2 * mag
|
|
1126
|
-
else if (nice <= 5) step = 5 * mag
|
|
1127
|
-
else step = 10 * mag
|
|
1128
|
-
|
|
1129
|
-
var ticks = []
|
|
1130
|
-
for (var v = step; v <= max + step * 0.5; v += step) {
|
|
1131
|
-
ticks.push(Math.round(v))
|
|
1132
|
-
}
|
|
1133
|
-
return ticks
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
// ── Requests ──────────────────────────────────────────────────
|
|
1137
|
-
var requestUrlFilter = ''
|
|
1138
|
-
var requestStatusFilter = ''
|
|
1139
|
-
|
|
1140
|
-
var fetchRequests = function () {
|
|
1141
|
-
var ps = getPage('requests')
|
|
1142
|
-
var suffix = '/requests?page=' + ps.page + '&limit=' + PER_PAGE
|
|
1143
|
-
if (requestUrlFilter) suffix += '&url=' + encodeURIComponent(requestUrlFilter)
|
|
1144
|
-
if (requestStatusFilter) suffix += '&status=' + encodeURIComponent(requestStatusFilter)
|
|
1145
|
-
requestUrlFilter = ''
|
|
1146
|
-
requestStatusFilter = ''
|
|
1147
|
-
fetchSection('requests', suffix, renderRequests)
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
var renderRequests = function (data) {
|
|
1151
|
-
var items = data.data || data.requests || []
|
|
1152
|
-
var ps = getPage('requests')
|
|
1153
|
-
ps.total = data.meta ? data.meta.total : data.total || items.length
|
|
1154
|
-
|
|
1155
|
-
setInner('ss-dash-requests-summary', ps.total + ' requests')
|
|
1156
|
-
|
|
1157
|
-
if (items.length === 0) {
|
|
1158
|
-
setInner('ss-dash-requests-body', '<div class="ss-dash-empty">No requests recorded yet</div>')
|
|
1159
|
-
renderPagination('requests', ps)
|
|
1160
|
-
return
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
var html =
|
|
1164
|
-
'<table class="ss-dash-table" style="table-layout:fixed"><thead><tr>' +
|
|
1165
|
-
'<th style="width:50px">#</th>' +
|
|
1166
|
-
'<th style="width:60px">Method</th>' +
|
|
1167
|
-
'<th>URL</th>' +
|
|
1168
|
-
'<th style="width:55px">Status</th>' +
|
|
1169
|
-
'<th style="width:80px">Duration</th>' +
|
|
1170
|
-
'<th style="width:50px">Spans</th>' +
|
|
1171
|
-
'<th style="width:30px" title="Warnings">⚠</th>' +
|
|
1172
|
-
'<th style="width:60px">Time</th>' +
|
|
1173
|
-
'</tr></thead><tbody>'
|
|
1174
|
-
|
|
1175
|
-
items.forEach(function (r) {
|
|
1176
|
-
html +=
|
|
1177
|
-
'<tr class="ss-dash-clickable" data-request-id="' +
|
|
1178
|
-
r.id +
|
|
1179
|
-
'">' +
|
|
1180
|
-
'<td style="color:var(--ss-dim);' +
|
|
1181
|
-
TRUNC +
|
|
1182
|
-
'">' +
|
|
1183
|
-
r.id +
|
|
1184
|
-
'</td>' +
|
|
1185
|
-
'<td><span class="' +
|
|
1186
|
-
methodClass(r.method) +
|
|
1187
|
-
'">' +
|
|
1188
|
-
esc(r.method) +
|
|
1189
|
-
'</span></td>' +
|
|
1190
|
-
'<td style="color:var(--ss-text);' +
|
|
1191
|
-
TRUNC +
|
|
1192
|
-
'" title="' +
|
|
1193
|
-
esc(r.url) +
|
|
1194
|
-
'">' +
|
|
1195
|
-
esc(r.url) +
|
|
1196
|
-
'</td>' +
|
|
1197
|
-
'<td>' +
|
|
1198
|
-
renderStatusBadge(getStatus(r)) +
|
|
1199
|
-
'</td>' +
|
|
1200
|
-
'<td class="ss-dash-duration ' +
|
|
1201
|
-
durationClass(r.duration) +
|
|
1202
|
-
'">' +
|
|
1203
|
-
(r.duration || 0).toFixed(1) +
|
|
1204
|
-
'ms</td>' +
|
|
1205
|
-
'<td style="color:var(--ss-muted);text-align:center">' +
|
|
1206
|
-
(r.span_count || r.spanCount || 0) +
|
|
1207
|
-
'</td>' +
|
|
1208
|
-
'<td style="text-align:center">' +
|
|
1209
|
-
((r.warning_count || r.warningCount || 0) > 0
|
|
1210
|
-
? '<span style="color:var(--ss-amber-fg)">' +
|
|
1211
|
-
(r.warning_count || r.warningCount) +
|
|
1212
|
-
'</span>'
|
|
1213
|
-
: '<span style="color:var(--ss-dim)">-</span>') +
|
|
1214
|
-
'</td>' +
|
|
1215
|
-
'<td class="ss-dash-event-time" style="white-space:nowrap">' +
|
|
1216
|
-
timeAgo(getTimestamp(r)) +
|
|
1217
|
-
'</td>' +
|
|
1218
|
-
'</tr>'
|
|
1219
|
-
})
|
|
1220
|
-
html += '</tbody></table>'
|
|
1221
|
-
|
|
1222
|
-
setInner('ss-dash-requests-body', html)
|
|
1223
|
-
updateBadge('requests', ps.total)
|
|
1224
|
-
renderPagination('requests', ps)
|
|
1225
|
-
|
|
1226
|
-
// Click to expand trace
|
|
1227
|
-
var body = document.getElementById('ss-dash-requests-body')
|
|
1228
|
-
if (body) {
|
|
1229
|
-
body.querySelectorAll('[data-request-id]').forEach(function (row) {
|
|
1230
|
-
row.addEventListener('click', function () {
|
|
1231
|
-
var id = row.getAttribute('data-request-id')
|
|
1232
|
-
fetchJSON(API + '/requests/' + id)
|
|
1233
|
-
.then(function (trace) {
|
|
1234
|
-
showRequestDetail(trace)
|
|
1235
|
-
})
|
|
1236
|
-
.catch(function () {
|
|
1237
|
-
/* ignore */
|
|
1238
|
-
})
|
|
1239
|
-
})
|
|
1240
|
-
})
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
var showRequestDetail = function (trace) {
|
|
1245
|
-
var listEl = document.getElementById('ss-dash-requests-list')
|
|
1246
|
-
var detailEl = document.getElementById('ss-dash-requests-detail')
|
|
1247
|
-
var titleEl = document.getElementById('ss-dash-requests-detail-title')
|
|
1248
|
-
var waterfallEl = document.getElementById('ss-dash-requests-waterfall')
|
|
1249
|
-
if (!listEl || !detailEl) return
|
|
1250
|
-
|
|
1251
|
-
listEl.style.display = 'none'
|
|
1252
|
-
detailEl.style.display = 'flex'
|
|
1253
|
-
detailEl.classList.add('ss-dash-active')
|
|
1254
|
-
|
|
1255
|
-
if (titleEl) {
|
|
1256
|
-
titleEl.innerHTML =
|
|
1257
|
-
'<span class="' +
|
|
1258
|
-
methodClass(trace.method) +
|
|
1259
|
-
'">' +
|
|
1260
|
-
esc(trace.method) +
|
|
1261
|
-
'</span> ' +
|
|
1262
|
-
esc(trace.url) +
|
|
1263
|
-
' ' +
|
|
1264
|
-
renderStatusBadge(getStatus(trace)) +
|
|
1265
|
-
'<span class="ss-dash-tl-meta">' +
|
|
1266
|
-
(trace.total_duration || trace.totalDuration || trace.duration || 0).toFixed(1) +
|
|
1267
|
-
'ms</span>'
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
if (waterfallEl) renderWaterfall(waterfallEl, trace)
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
// Back button for requests detail
|
|
1274
|
-
var reqBackBtn = document.getElementById('ss-dash-requests-back')
|
|
1275
|
-
if (reqBackBtn) {
|
|
1276
|
-
reqBackBtn.addEventListener('click', function () {
|
|
1277
|
-
var listEl = document.getElementById('ss-dash-requests-list')
|
|
1278
|
-
var detailEl = document.getElementById('ss-dash-requests-detail')
|
|
1279
|
-
if (listEl) listEl.style.display = ''
|
|
1280
|
-
if (detailEl) {
|
|
1281
|
-
detailEl.style.display = 'none'
|
|
1282
|
-
detailEl.classList.remove('ss-dash-active')
|
|
1283
|
-
}
|
|
1284
|
-
})
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
// ── Queries ───────────────────────────────────────────────────
|
|
1288
|
-
var queryGrouped = false
|
|
1289
|
-
|
|
1290
|
-
var fetchQueries = function () {
|
|
1291
|
-
if (queryGrouped) {
|
|
1292
|
-
fetchJSON(API + '/queries/grouped')
|
|
1293
|
-
.then(function (data) {
|
|
1294
|
-
renderQueriesGrouped(data)
|
|
1295
|
-
})
|
|
1296
|
-
.catch(function () {
|
|
1297
|
-
setInner(
|
|
1298
|
-
'ss-dash-queries-body',
|
|
1299
|
-
'<div class="ss-dash-empty">Failed to load queries</div>'
|
|
1300
|
-
)
|
|
1301
|
-
})
|
|
1302
|
-
} else {
|
|
1303
|
-
var ps = getPage('queries')
|
|
1304
|
-
fetchJSON(API + '/queries?page=' + ps.page + '&limit=' + PER_PAGE)
|
|
1305
|
-
.then(function (data) {
|
|
1306
|
-
renderQueries(data)
|
|
1307
|
-
})
|
|
1308
|
-
.catch(function () {
|
|
1309
|
-
setInner(
|
|
1310
|
-
'ss-dash-queries-body',
|
|
1311
|
-
'<div class="ss-dash-empty">Failed to load queries</div>'
|
|
1312
|
-
)
|
|
1313
|
-
})
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
var renderQueries = function (data) {
|
|
1318
|
-
var items = data.data || data.queries || []
|
|
1319
|
-
var summary = data.summary || data.meta || {}
|
|
1320
|
-
var ps = getPage('queries')
|
|
1321
|
-
ps.total = summary.total || (data.meta ? data.meta.total : items.length)
|
|
1322
|
-
|
|
1323
|
-
setInner(
|
|
1324
|
-
'ss-dash-queries-summary',
|
|
1325
|
-
(ps.total || items.length) +
|
|
1326
|
-
' queries' +
|
|
1327
|
-
(summary.slow > 0 ? ', ' + summary.slow + ' slow' : '') +
|
|
1328
|
-
(summary.duplicates > 0 ? ', ' + summary.duplicates + ' dup' : '') +
|
|
1329
|
-
', avg ' +
|
|
1330
|
-
(summary.avgDuration || 0).toFixed(1) +
|
|
1331
|
-
'ms'
|
|
1332
|
-
)
|
|
1333
|
-
|
|
1334
|
-
updateBadge('queries', ps.total)
|
|
1335
|
-
|
|
1336
|
-
if (items.length === 0) {
|
|
1337
|
-
setInner('ss-dash-queries-body', '<div class="ss-dash-empty">No queries recorded yet</div>')
|
|
1338
|
-
renderPagination('queries', ps)
|
|
1339
|
-
return
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
var html =
|
|
1343
|
-
'<table class="ss-dash-table" style="table-layout:fixed"><thead><tr>' +
|
|
1344
|
-
'<th style="width:50px">#</th>' +
|
|
1345
|
-
'<th>SQL</th>' +
|
|
1346
|
-
'<th style="width:75px">Duration</th>' +
|
|
1347
|
-
'<th style="width:60px">Method</th>' +
|
|
1348
|
-
'<th style="width:100px">Model</th>' +
|
|
1349
|
-
'<th style="width:80px">Connection</th>' +
|
|
1350
|
-
'<th style="width:50px">Time</th>' +
|
|
1351
|
-
'<th style="width:70px">EXPLAIN</th>' +
|
|
1352
|
-
'</tr></thead><tbody>'
|
|
1353
|
-
|
|
1354
|
-
items.forEach(function (q) {
|
|
1355
|
-
var dur = q.duration || 0
|
|
1356
|
-
var sqlMethod = q.method || q.sql_method || ''
|
|
1357
|
-
var modelName = q.model || '-'
|
|
1358
|
-
html +=
|
|
1359
|
-
'<tr>' +
|
|
1360
|
-
'<td style="color:var(--ss-dim)">' +
|
|
1361
|
-
q.id +
|
|
1362
|
-
'</td>' +
|
|
1363
|
-
'<td><span class="ss-dash-sql" title="Click to expand" onclick="this.classList.toggle(\'ss-dash-expanded\')">' +
|
|
1364
|
-
esc(q.sql || q.sql_text || '') +
|
|
1365
|
-
'</span></td>' +
|
|
1366
|
-
'<td class="ss-dash-duration ' +
|
|
1367
|
-
durationClass(dur) +
|
|
1368
|
-
'">' +
|
|
1369
|
-
dur.toFixed(2) +
|
|
1370
|
-
'ms</td>' +
|
|
1371
|
-
'<td><span class="' +
|
|
1372
|
-
methodClass(sqlMethod) +
|
|
1373
|
-
'">' +
|
|
1374
|
-
esc(sqlMethod) +
|
|
1375
|
-
'</span></td>' +
|
|
1376
|
-
'<td style="color:var(--ss-muted);' +
|
|
1377
|
-
TRUNC +
|
|
1378
|
-
'" title="' +
|
|
1379
|
-
esc(modelName) +
|
|
1380
|
-
'">' +
|
|
1381
|
-
esc(modelName) +
|
|
1382
|
-
'</td>' +
|
|
1383
|
-
'<td style="color:var(--ss-dim);' +
|
|
1384
|
-
TRUNC +
|
|
1385
|
-
'">' +
|
|
1386
|
-
esc(q.connection || '-') +
|
|
1387
|
-
'</td>' +
|
|
1388
|
-
'<td class="ss-dash-event-time" style="white-space:nowrap">' +
|
|
1389
|
-
timeAgo(getTimestamp(q)) +
|
|
1390
|
-
'</td>' +
|
|
1391
|
-
'<td>' +
|
|
1392
|
-
((sqlMethod || '').toLowerCase() === 'select'
|
|
1393
|
-
? '<button class="ss-dash-explain-btn" data-query-id="' + q.id + '">EXPLAIN</button>'
|
|
1394
|
-
: '') +
|
|
1395
|
-
'</td>' +
|
|
1396
|
-
'</tr>'
|
|
1397
|
-
})
|
|
1398
|
-
|
|
1399
|
-
html += '</tbody></table>'
|
|
1400
|
-
setInner('ss-dash-queries-body', html)
|
|
1401
|
-
renderPagination('queries', ps)
|
|
1402
|
-
bindExplainButtons()
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
var renderQueriesGrouped = function (data) {
|
|
1406
|
-
var groups = data.groups || data.data || []
|
|
1407
|
-
|
|
1408
|
-
setInner('ss-dash-queries-summary', groups.length + ' query patterns')
|
|
1409
|
-
updateBadge('queries', groups.length)
|
|
1410
|
-
|
|
1411
|
-
if (groups.length === 0) {
|
|
1412
|
-
setInner('ss-dash-queries-body', '<div class="ss-dash-empty">No queries recorded yet</div>')
|
|
1413
|
-
return
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
var html =
|
|
1417
|
-
'<table class="ss-dash-table" style="table-layout:fixed"><thead><tr>' +
|
|
1418
|
-
'<th>Pattern</th>' +
|
|
1419
|
-
'<th style="width:55px">Count</th>' +
|
|
1420
|
-
'<th style="width:70px">Avg</th>' +
|
|
1421
|
-
'<th style="width:70px">Min</th>' +
|
|
1422
|
-
'<th style="width:70px">Max</th>' +
|
|
1423
|
-
'<th style="width:70px">Total</th>' +
|
|
1424
|
-
'<th style="width:55px">% Time</th>' +
|
|
1425
|
-
'</tr></thead><tbody>'
|
|
1426
|
-
|
|
1427
|
-
groups.forEach(function (g) {
|
|
1428
|
-
var isDup = (g.count || 0) >= 3
|
|
1429
|
-
html +=
|
|
1430
|
-
'<tr>' +
|
|
1431
|
-
'<td style="' +
|
|
1432
|
-
TRUNC +
|
|
1433
|
-
'"><span class="ss-dash-sql" onclick="this.classList.toggle(\'ss-dash-expanded\')">' +
|
|
1434
|
-
esc(g.pattern || g.sql_normalized || '') +
|
|
1435
|
-
'</span>' +
|
|
1436
|
-
(isDup ? ' <span class="ss-dash-dup">DUP</span>' : '') +
|
|
1437
|
-
'</td>' +
|
|
1438
|
-
'<td style="color:var(--ss-muted);text-align:center">' +
|
|
1439
|
-
(g.count || 0) +
|
|
1440
|
-
'</td>' +
|
|
1441
|
-
'<td class="ss-dash-duration ' +
|
|
1442
|
-
durationClass(g.avg_duration || 0) +
|
|
1443
|
-
'">' +
|
|
1444
|
-
(g.avg_duration || 0).toFixed(2) +
|
|
1445
|
-
'ms</td>' +
|
|
1446
|
-
'<td class="ss-dash-duration">' +
|
|
1447
|
-
(g.min_duration || 0).toFixed(2) +
|
|
1448
|
-
'ms</td>' +
|
|
1449
|
-
'<td class="ss-dash-duration ' +
|
|
1450
|
-
durationClass(g.max_duration || 0) +
|
|
1451
|
-
'">' +
|
|
1452
|
-
(g.max_duration || 0).toFixed(2) +
|
|
1453
|
-
'ms</td>' +
|
|
1454
|
-
'<td class="ss-dash-duration">' +
|
|
1455
|
-
(g.total_duration || 0).toFixed(1) +
|
|
1456
|
-
'ms</td>' +
|
|
1457
|
-
'<td style="color:var(--ss-muted);text-align:center">' +
|
|
1458
|
-
(g.pct_time || 0).toFixed(1) +
|
|
1459
|
-
'%</td>' +
|
|
1460
|
-
'</tr>'
|
|
1461
|
-
})
|
|
1462
|
-
|
|
1463
|
-
html += '</tbody></table>'
|
|
1464
|
-
setInner('ss-dash-queries-body', html)
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
// ── EXPLAIN: render a PostgreSQL JSON plan as a tree ──────────
|
|
1468
|
-
var renderPlanNode = function (node, depth) {
|
|
1469
|
-
if (!node) return ''
|
|
1470
|
-
depth = depth || 0
|
|
1471
|
-
var indent = depth * 20
|
|
1472
|
-
var html = '<div class="ss-dash-explain-node" style="margin-left:' + indent + 'px">'
|
|
1473
|
-
var nodeType = node['Node Type'] || 'Unknown'
|
|
1474
|
-
var relation = node['Relation Name']
|
|
1475
|
-
? ' on <strong>' + esc(node['Relation Name']) + '</strong>'
|
|
1476
|
-
: ''
|
|
1477
|
-
var alias =
|
|
1478
|
-
node['Alias'] && node['Alias'] !== node['Relation Name']
|
|
1479
|
-
? ' (' + esc(node['Alias']) + ')'
|
|
1480
|
-
: ''
|
|
1481
|
-
var idx = node['Index Name'] ? ' using <em>' + esc(node['Index Name']) + '</em>' : ''
|
|
1482
|
-
|
|
1483
|
-
html +=
|
|
1484
|
-
'<div class="ss-dash-explain-node-header">' +
|
|
1485
|
-
'<span class="ss-dash-explain-node-type">' +
|
|
1486
|
-
esc(nodeType) +
|
|
1487
|
-
'</span>' +
|
|
1488
|
-
relation +
|
|
1489
|
-
alias +
|
|
1490
|
-
idx +
|
|
1491
|
-
'</div>'
|
|
1492
|
-
|
|
1493
|
-
// Key metrics row
|
|
1494
|
-
var metrics = []
|
|
1495
|
-
if (node['Startup Cost'] != null)
|
|
1496
|
-
metrics.push('cost=' + node['Startup Cost'] + '..' + node['Total Cost'])
|
|
1497
|
-
if (node['Plan Rows'] != null) metrics.push('rows=' + node['Plan Rows'])
|
|
1498
|
-
if (node['Plan Width'] != null) metrics.push('width=' + node['Plan Width'])
|
|
1499
|
-
if (node['Filter']) metrics.push('filter: ' + esc(node['Filter']))
|
|
1500
|
-
if (node['Index Cond']) metrics.push('cond: ' + esc(node['Index Cond']))
|
|
1501
|
-
if (node['Hash Cond']) metrics.push('hash: ' + esc(node['Hash Cond']))
|
|
1502
|
-
if (node['Join Type']) metrics.push('join: ' + esc(node['Join Type']))
|
|
1503
|
-
if (node['Sort Key'])
|
|
1504
|
-
metrics.push(
|
|
1505
|
-
'sort: ' +
|
|
1506
|
-
esc(Array.isArray(node['Sort Key']) ? node['Sort Key'].join(', ') : node['Sort Key'])
|
|
1507
|
-
)
|
|
1508
|
-
|
|
1509
|
-
if (metrics.length > 0) {
|
|
1510
|
-
html += '<div class="ss-dash-explain-metrics">' + metrics.join(' · ') + '</div>'
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
// Recurse into child plans
|
|
1514
|
-
var plans = node['Plans'] || []
|
|
1515
|
-
for (var i = 0; i < plans.length; i++) {
|
|
1516
|
-
html += renderPlanNode(plans[i], depth + 1)
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
html += '</div>'
|
|
1520
|
-
return html
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
var renderExplainPlan = function (plan) {
|
|
1524
|
-
if (!plan || !Array.isArray(plan) || plan.length === 0) {
|
|
1525
|
-
return '<div class="ss-dash-explain-result">No plan data returned</div>'
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
// JSON format: array of objects with a "Plan" key
|
|
1529
|
-
var topPlan = plan[0]
|
|
1530
|
-
if (topPlan && topPlan['Plan']) {
|
|
1531
|
-
return '<div class="ss-dash-explain-result">' + renderPlanNode(topPlan['Plan'], 0) + '</div>'
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
// Fallback: plain rows table (for non-JSON EXPLAIN output)
|
|
1535
|
-
if (typeof topPlan === 'object') {
|
|
1536
|
-
var cols = Object.keys(topPlan)
|
|
1537
|
-
var tbl = '<table><thead><tr>'
|
|
1538
|
-
cols.forEach(function (c) {
|
|
1539
|
-
tbl += '<th>' + esc(c) + '</th>'
|
|
1540
|
-
})
|
|
1541
|
-
tbl += '</tr></thead><tbody>'
|
|
1542
|
-
plan.forEach(function (r) {
|
|
1543
|
-
tbl += '<tr>'
|
|
1544
|
-
cols.forEach(function (c) {
|
|
1545
|
-
tbl += '<td>' + esc(r[c] != null ? String(r[c]) : '-') + '</td>'
|
|
1546
|
-
})
|
|
1547
|
-
tbl += '</tr>'
|
|
1548
|
-
})
|
|
1549
|
-
tbl += '</tbody></table>'
|
|
1550
|
-
return '<div class="ss-dash-explain-result">' + tbl + '</div>'
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
return '<div class="ss-dash-explain-result">No plan data returned</div>'
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
// EXPLAIN buttons
|
|
1557
|
-
var bindExplainButtons = function () {
|
|
1558
|
-
var body = document.getElementById('ss-dash-queries-body')
|
|
1559
|
-
if (!body) return
|
|
1560
|
-
body.querySelectorAll('.ss-dash-explain-btn').forEach(function (btn) {
|
|
1561
|
-
btn.addEventListener('click', function (e) {
|
|
1562
|
-
e.stopPropagation()
|
|
1563
|
-
var id = btn.getAttribute('data-query-id')
|
|
1564
|
-
var row = btn.closest('tr')
|
|
1565
|
-
if (!row) return
|
|
1566
|
-
|
|
1567
|
-
// Toggle: if already shown, remove it
|
|
1568
|
-
var existing = row.nextElementSibling
|
|
1569
|
-
if (existing && existing.classList.contains('ss-dash-explain-row')) {
|
|
1570
|
-
existing.remove()
|
|
1571
|
-
btn.classList.remove('ss-dash-explain-btn-active')
|
|
1572
|
-
return
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
btn.textContent = '...'
|
|
1576
|
-
btn.disabled = true
|
|
1577
|
-
fetchJSON(API + '/queries/' + id + '/explain')
|
|
1578
|
-
.then(function (data) {
|
|
1579
|
-
// Remove any existing explain row
|
|
1580
|
-
var prev = row.nextElementSibling
|
|
1581
|
-
if (prev && prev.classList.contains('ss-dash-explain-row')) prev.remove()
|
|
1582
|
-
|
|
1583
|
-
var tr = document.createElement('tr')
|
|
1584
|
-
tr.className = 'ss-dash-explain-row'
|
|
1585
|
-
var td = document.createElement('td')
|
|
1586
|
-
td.colSpan = 8
|
|
1587
|
-
td.className = 'ss-dash-explain'
|
|
1588
|
-
|
|
1589
|
-
if (data.error) {
|
|
1590
|
-
td.innerHTML =
|
|
1591
|
-
'<div class="ss-dash-explain-result ss-dash-explain-error">' +
|
|
1592
|
-
'<strong>Error:</strong> ' +
|
|
1593
|
-
esc(data.error) +
|
|
1594
|
-
(data.message ? '<br>' + esc(data.message) : '') +
|
|
1595
|
-
'</div>'
|
|
1596
|
-
} else {
|
|
1597
|
-
td.innerHTML = renderExplainPlan(data.plan || data.rows || [])
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
tr.appendChild(td)
|
|
1601
|
-
row.parentNode.insertBefore(tr, row.nextSibling)
|
|
1602
|
-
btn.textContent = 'EXPLAIN'
|
|
1603
|
-
btn.disabled = false
|
|
1604
|
-
btn.classList.add('ss-dash-explain-btn-active')
|
|
1605
|
-
})
|
|
1606
|
-
.catch(function (err) {
|
|
1607
|
-
btn.textContent = 'EXPLAIN'
|
|
1608
|
-
btn.disabled = false
|
|
1609
|
-
})
|
|
1610
|
-
})
|
|
1611
|
-
})
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
// Grouped toggle
|
|
1615
|
-
var queryGroupBtn = document.getElementById('ss-dash-queries-group-btn')
|
|
1616
|
-
if (queryGroupBtn) {
|
|
1617
|
-
queryGroupBtn.addEventListener('click', function () {
|
|
1618
|
-
queryGrouped = !queryGrouped
|
|
1619
|
-
queryGroupBtn.classList.toggle('ss-dash-active', queryGrouped)
|
|
1620
|
-
queryGroupBtn.textContent = queryGrouped ? 'List View' : 'Grouped'
|
|
1621
|
-
fetchQueries()
|
|
1622
|
-
})
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
// ── Events ────────────────────────────────────────────────────
|
|
1626
|
-
var eventNameFilter = ''
|
|
1627
|
-
|
|
1628
|
-
var fetchEvents = function () {
|
|
1629
|
-
var ps = getPage('events')
|
|
1630
|
-
var url = API + '/events?page=' + ps.page + '&limit=' + PER_PAGE
|
|
1631
|
-
if (eventNameFilter) url += '&event_name=' + encodeURIComponent(eventNameFilter)
|
|
1632
|
-
eventNameFilter = ''
|
|
1633
|
-
fetchJSON(url)
|
|
1634
|
-
.then(function (data) {
|
|
1635
|
-
renderEvents(data)
|
|
1636
|
-
})
|
|
1637
|
-
.catch(function () {
|
|
1638
|
-
setInner('ss-dash-events-body', '<div class="ss-dash-empty">Failed to load events</div>')
|
|
1639
|
-
})
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
var renderEvents = function (data) {
|
|
1643
|
-
var items = data.data || data.events || []
|
|
1644
|
-
var ps = getPage('events')
|
|
1645
|
-
ps.total = data.meta ? data.meta.total : data.total || items.length
|
|
1646
|
-
|
|
1647
|
-
setInner('ss-dash-events-summary', ps.total + ' events')
|
|
1648
|
-
|
|
1649
|
-
if (items.length === 0) {
|
|
1650
|
-
setInner('ss-dash-events-body', '<div class="ss-dash-empty">No events recorded yet</div>')
|
|
1651
|
-
renderPagination('events', ps)
|
|
1652
|
-
return
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
var html =
|
|
1656
|
-
'<table class="ss-dash-table" style="table-layout:fixed"><thead><tr>' +
|
|
1657
|
-
'<th style="width:50px">#</th>' +
|
|
1658
|
-
'<th style="width:200px">Event</th>' +
|
|
1659
|
-
'<th>Data</th>' +
|
|
1660
|
-
'<th style="width:80px">Time</th>' +
|
|
1661
|
-
'</tr></thead><tbody>'
|
|
1662
|
-
|
|
1663
|
-
items.forEach(function (ev, idx) {
|
|
1664
|
-
var hasData = ev.data && ev.data !== '-'
|
|
1665
|
-
var preview = hasData ? eventPreview(ev.data) : '-'
|
|
1666
|
-
var evName = ev.event_name || ev.eventName || ev.event || ''
|
|
1667
|
-
html +=
|
|
1668
|
-
'<tr>' +
|
|
1669
|
-
'<td style="color:var(--ss-dim)">' +
|
|
1670
|
-
ev.id +
|
|
1671
|
-
'</td>' +
|
|
1672
|
-
'<td class="ss-dash-event-name" style="' +
|
|
1673
|
-
TRUNC +
|
|
1674
|
-
'" title="' +
|
|
1675
|
-
esc(evName) +
|
|
1676
|
-
'">' +
|
|
1677
|
-
esc(evName) +
|
|
1678
|
-
'</td>' +
|
|
1679
|
-
'<td class="ss-dash-event-data" style="' +
|
|
1680
|
-
TRUNC +
|
|
1681
|
-
'">' +
|
|
1682
|
-
(hasData
|
|
1683
|
-
? '<span class="ss-dash-data-preview" data-ev-idx="' +
|
|
1684
|
-
idx +
|
|
1685
|
-
'">' +
|
|
1686
|
-
esc(preview) +
|
|
1687
|
-
'</span>' +
|
|
1688
|
-
'<pre class="ss-dash-data-full" id="ss-dash-evdata-' +
|
|
1689
|
-
idx +
|
|
1690
|
-
'" style="display:none">' +
|
|
1691
|
-
esc(typeof ev.data === 'string' ? ev.data : JSON.stringify(ev.data, null, 2)) +
|
|
1692
|
-
'</pre>'
|
|
1693
|
-
: '<span style="color:var(--ss-dim)">-</span>') +
|
|
1694
|
-
'</td>' +
|
|
1695
|
-
'<td class="ss-dash-event-time">' +
|
|
1696
|
-
timeAgo(getTimestamp(ev)) +
|
|
1697
|
-
'</td>' +
|
|
1698
|
-
'</tr>'
|
|
1699
|
-
})
|
|
1700
|
-
|
|
1701
|
-
html += '</tbody></table>'
|
|
1702
|
-
setInner('ss-dash-events-body', html)
|
|
1703
|
-
renderPagination('events', ps)
|
|
1704
|
-
bindDataExpand('ss-dash-events-body')
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
// ── Routes ────────────────────────────────────────────────────
|
|
1708
|
-
var fetchRoutes = function () {
|
|
1709
|
-
fetchJSON(API + '/routes')
|
|
1710
|
-
.then(function (data) {
|
|
1711
|
-
sectionLoaded.routes = true
|
|
1712
|
-
renderRoutes(data)
|
|
1713
|
-
})
|
|
1714
|
-
.catch(function () {
|
|
1715
|
-
setInner('ss-dash-routes-body', '<div class="ss-dash-empty">Failed to load routes</div>')
|
|
1716
|
-
})
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
var renderRoutes = function (data) {
|
|
1720
|
-
var items = data.routes || data.data || []
|
|
1721
|
-
setInner('ss-dash-routes-summary', items.length + ' routes')
|
|
1722
|
-
|
|
1723
|
-
if (items.length === 0) {
|
|
1724
|
-
setInner('ss-dash-routes-body', '<div class="ss-dash-empty">No routes available</div>')
|
|
1725
|
-
return
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
var html =
|
|
1729
|
-
'<table class="ss-dash-table" style="table-layout:fixed"><thead><tr>' +
|
|
1730
|
-
'<th style="width:70px">Method</th>' +
|
|
1731
|
-
'<th style="width:25%">Pattern</th>' +
|
|
1732
|
-
'<th style="width:18%">Name</th>' +
|
|
1733
|
-
'<th style="width:32%">Handler</th>' +
|
|
1734
|
-
'<th style="width:120px">Middleware</th>' +
|
|
1735
|
-
'</tr></thead><tbody>'
|
|
1736
|
-
|
|
1737
|
-
items.forEach(function (r) {
|
|
1738
|
-
html +=
|
|
1739
|
-
'<tr>' +
|
|
1740
|
-
'<td><span class="' +
|
|
1741
|
-
methodClass(r.method) +
|
|
1742
|
-
'">' +
|
|
1743
|
-
esc(r.method) +
|
|
1744
|
-
'</span></td>' +
|
|
1745
|
-
'<td style="color:var(--ss-text);' +
|
|
1746
|
-
TRUNC +
|
|
1747
|
-
'" title="' +
|
|
1748
|
-
esc(r.pattern) +
|
|
1749
|
-
'">' +
|
|
1750
|
-
esc(r.pattern) +
|
|
1751
|
-
'</td>' +
|
|
1752
|
-
'<td style="color:var(--ss-muted);' +
|
|
1753
|
-
TRUNC +
|
|
1754
|
-
'" title="' +
|
|
1755
|
-
esc(r.name || '-') +
|
|
1756
|
-
'">' +
|
|
1757
|
-
esc(r.name || '-') +
|
|
1758
|
-
'</td>' +
|
|
1759
|
-
'<td style="color:var(--ss-sql-color);' +
|
|
1760
|
-
TRUNC +
|
|
1761
|
-
'" title="' +
|
|
1762
|
-
esc(r.handler) +
|
|
1763
|
-
'">' +
|
|
1764
|
-
esc(r.handler) +
|
|
1765
|
-
'</td>' +
|
|
1766
|
-
'<td style="color:var(--ss-dim);font-size:10px;' +
|
|
1767
|
-
TRUNC +
|
|
1768
|
-
'" title="' +
|
|
1769
|
-
(r.middleware && r.middleware.length ? esc(r.middleware.join(', ')) : '-') +
|
|
1770
|
-
'">' +
|
|
1771
|
-
(r.middleware && r.middleware.length ? esc(r.middleware.join(', ')) : '-') +
|
|
1772
|
-
'</td>' +
|
|
1773
|
-
'</tr>'
|
|
1774
|
-
})
|
|
1775
|
-
|
|
1776
|
-
html += '</tbody></table>'
|
|
1777
|
-
setInner('ss-dash-routes-body', html)
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
// ── Logs ──────────────────────────────────────────────────────
|
|
1781
|
-
var logLevelFilter = 'all'
|
|
1782
|
-
var logReqIdFilter = ''
|
|
1783
|
-
var logDeepLevelFilter = ''
|
|
1784
|
-
var logStructuredFilters = []
|
|
1785
|
-
var logSavedFilters = []
|
|
1786
|
-
|
|
1787
|
-
var fetchLogs = function () {
|
|
1788
|
-
var ps = getPage('logs')
|
|
1789
|
-
var params = 'page=' + ps.page + '&limit=' + PER_PAGE
|
|
1790
|
-
if (logDeepLevelFilter) {
|
|
1791
|
-
logLevelFilter = logDeepLevelFilter
|
|
1792
|
-
logDeepLevelFilter = ''
|
|
1793
|
-
}
|
|
1794
|
-
if (logLevelFilter !== 'all') params += '&level=' + logLevelFilter
|
|
1795
|
-
if (logReqIdFilter) params += '&request_id=' + encodeURIComponent(logReqIdFilter)
|
|
1796
|
-
logStructuredFilters.forEach(function (f) {
|
|
1797
|
-
params +=
|
|
1798
|
-
'&filter_field=' +
|
|
1799
|
-
encodeURIComponent(f.field) +
|
|
1800
|
-
'&filter_op=' +
|
|
1801
|
-
encodeURIComponent(f.op) +
|
|
1802
|
-
'&filter_value=' +
|
|
1803
|
-
encodeURIComponent(f.value)
|
|
1804
|
-
})
|
|
1805
|
-
|
|
1806
|
-
fetchJSON(API + '/logs?' + params)
|
|
1807
|
-
.then(function (data) {
|
|
1808
|
-
renderLogs(data)
|
|
1809
|
-
})
|
|
1810
|
-
.catch(function () {
|
|
1811
|
-
setInner('ss-dash-logs-body', '<div class="ss-dash-empty">Failed to load logs</div>')
|
|
1812
|
-
})
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
var renderLogs = function (data) {
|
|
1816
|
-
var items = data.data || data.logs || data.entries || []
|
|
1817
|
-
var ps = getPage('logs')
|
|
1818
|
-
ps.total = data.meta ? data.meta.total : data.total || items.length
|
|
1819
|
-
|
|
1820
|
-
if (items.length === 0) {
|
|
1821
|
-
var hint = ''
|
|
1822
|
-
if (logReqIdFilter) hint = ' matching request ' + logReqIdFilter
|
|
1823
|
-
else if (logLevelFilter !== 'all') hint = ' for ' + logLevelFilter
|
|
1824
|
-
setInner('ss-dash-logs-body', '<div class="ss-dash-empty">No log entries' + hint + '</div>')
|
|
1825
|
-
renderPagination('logs', ps)
|
|
1826
|
-
return
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
var html = ''
|
|
1830
|
-
items.forEach(function (e) {
|
|
1831
|
-
var level = (e.level || e.levelName || e.level_name || 'info').toLowerCase()
|
|
1832
|
-
var msg = e.message || e.msg || ''
|
|
1833
|
-
var ts = e.createdAt || e.created_at || e.time || e.timestamp || 0
|
|
1834
|
-
var reqId = e.request_id || e['x-request-id'] || ''
|
|
1835
|
-
|
|
1836
|
-
html +=
|
|
1837
|
-
'<div class="ss-dash-log-entry">' +
|
|
1838
|
-
'<span class="ss-dash-log-level ss-dash-log-level-' +
|
|
1839
|
-
esc(level) +
|
|
1840
|
-
'">' +
|
|
1841
|
-
esc(level.toUpperCase()) +
|
|
1842
|
-
'</span>' +
|
|
1843
|
-
'<span class="ss-dash-log-time">' +
|
|
1844
|
-
(ts ? formatTime(ts) : '-') +
|
|
1845
|
-
'</span>' +
|
|
1846
|
-
(reqId
|
|
1847
|
-
? '<span class="ss-dash-log-reqid" data-reqid="' +
|
|
1848
|
-
esc(reqId) +
|
|
1849
|
-
'" title="' +
|
|
1850
|
-
esc(reqId) +
|
|
1851
|
-
'">' +
|
|
1852
|
-
esc(shortReqId(reqId)) +
|
|
1853
|
-
'</span>'
|
|
1854
|
-
: '') +
|
|
1855
|
-
'<span class="ss-dash-log-msg">' +
|
|
1856
|
-
esc(msg) +
|
|
1857
|
-
'</span>' +
|
|
1858
|
-
'</div>'
|
|
1859
|
-
})
|
|
1860
|
-
|
|
1861
|
-
setInner('ss-dash-logs-body', html)
|
|
1862
|
-
updateBadge('logs', ps.total)
|
|
1863
|
-
renderPagination('logs', ps)
|
|
1864
|
-
|
|
1865
|
-
// Click request ID to filter
|
|
1866
|
-
var logBody = document.getElementById('ss-dash-logs-body')
|
|
1867
|
-
if (logBody) {
|
|
1868
|
-
logBody.querySelectorAll('.ss-dash-log-reqid').forEach(function (el) {
|
|
1869
|
-
el.addEventListener('click', function () {
|
|
1870
|
-
logReqIdFilter = el.getAttribute('data-reqid') || ''
|
|
1871
|
-
var input = document.getElementById('ss-dash-log-reqid-input')
|
|
1872
|
-
if (input) input.value = logReqIdFilter
|
|
1873
|
-
var clearBtn = document.getElementById('ss-dash-log-reqid-clear')
|
|
1874
|
-
if (clearBtn) clearBtn.style.display = logReqIdFilter ? '' : 'none'
|
|
1875
|
-
getPage('logs').page = 1
|
|
1876
|
-
fetchLogs()
|
|
1877
|
-
})
|
|
1878
|
-
})
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
// Log level filters
|
|
1883
|
-
root.querySelectorAll('[data-ss-log-level]').forEach(function (btn) {
|
|
1884
|
-
btn.addEventListener('click', function () {
|
|
1885
|
-
root.querySelectorAll('[data-ss-log-level]').forEach(function (b) {
|
|
1886
|
-
b.classList.remove('ss-dash-active')
|
|
1887
|
-
})
|
|
1888
|
-
btn.classList.add('ss-dash-active')
|
|
1889
|
-
logLevelFilter = btn.getAttribute('data-ss-log-level')
|
|
1890
|
-
getPage('logs').page = 1
|
|
1891
|
-
fetchLogs()
|
|
1892
|
-
})
|
|
1893
|
-
})
|
|
1894
|
-
|
|
1895
|
-
// Log request ID filter
|
|
1896
|
-
var logReqIdInput = document.getElementById('ss-dash-log-reqid-input')
|
|
1897
|
-
var logReqIdClear = document.getElementById('ss-dash-log-reqid-clear')
|
|
1898
|
-
if (logReqIdInput) {
|
|
1899
|
-
logReqIdInput.addEventListener('input', function () {
|
|
1900
|
-
logReqIdFilter = logReqIdInput.value.trim()
|
|
1901
|
-
if (logReqIdClear) logReqIdClear.style.display = logReqIdFilter ? '' : 'none'
|
|
1902
|
-
getPage('logs').page = 1
|
|
1903
|
-
fetchLogs()
|
|
1904
|
-
})
|
|
1905
|
-
}
|
|
1906
|
-
if (logReqIdClear) {
|
|
1907
|
-
logReqIdClear.addEventListener('click', function () {
|
|
1908
|
-
logReqIdFilter = ''
|
|
1909
|
-
if (logReqIdInput) logReqIdInput.value = ''
|
|
1910
|
-
logReqIdClear.style.display = 'none'
|
|
1911
|
-
getPage('logs').page = 1
|
|
1912
|
-
fetchLogs()
|
|
1913
|
-
})
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
// Structured search: add filter
|
|
1917
|
-
var logAddFilterBtn = document.getElementById('ss-dash-log-add-filter')
|
|
1918
|
-
if (logAddFilterBtn) {
|
|
1919
|
-
logAddFilterBtn.addEventListener('click', function () {
|
|
1920
|
-
var fieldEl = document.getElementById('ss-dash-log-filter-field')
|
|
1921
|
-
var opEl = document.getElementById('ss-dash-log-filter-op')
|
|
1922
|
-
var valEl = document.getElementById('ss-dash-log-filter-value')
|
|
1923
|
-
if (!fieldEl || !opEl || !valEl) return
|
|
1924
|
-
var field = fieldEl.value
|
|
1925
|
-
var op = opEl.value
|
|
1926
|
-
var val = valEl.value.trim()
|
|
1927
|
-
if (!field || !val) return
|
|
1928
|
-
logStructuredFilters.push({ field: field, op: op, value: val })
|
|
1929
|
-
valEl.value = ''
|
|
1930
|
-
renderFilterChips()
|
|
1931
|
-
getPage('logs').page = 1
|
|
1932
|
-
fetchLogs()
|
|
1933
|
-
})
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
var renderFilterChips = function () {
|
|
1937
|
-
var container = document.getElementById('ss-dash-log-filter-chips')
|
|
1938
|
-
if (!container) return
|
|
1939
|
-
var html = ''
|
|
1940
|
-
logStructuredFilters.forEach(function (f, i) {
|
|
1941
|
-
html +=
|
|
1942
|
-
'<span class="ss-dash-filter-chip">' +
|
|
1943
|
-
esc(f.field) +
|
|
1944
|
-
' ' +
|
|
1945
|
-
esc(f.op) +
|
|
1946
|
-
' ' +
|
|
1947
|
-
esc(f.value) +
|
|
1948
|
-
' <button class="ss-dash-filter-chip-remove" data-chip-idx="' +
|
|
1949
|
-
i +
|
|
1950
|
-
'">×</button>' +
|
|
1951
|
-
'</span>'
|
|
1952
|
-
})
|
|
1953
|
-
container.innerHTML = html
|
|
1954
|
-
container.querySelectorAll('.ss-dash-filter-chip-remove').forEach(function (btn) {
|
|
1955
|
-
btn.addEventListener('click', function () {
|
|
1956
|
-
var idx = parseInt(btn.getAttribute('data-chip-idx'), 10)
|
|
1957
|
-
logStructuredFilters.splice(idx, 1)
|
|
1958
|
-
renderFilterChips()
|
|
1959
|
-
getPage('logs').page = 1
|
|
1960
|
-
fetchLogs()
|
|
1961
|
-
})
|
|
1962
|
-
})
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
// Saved filters
|
|
1966
|
-
var fetchSavedFilters = function () {
|
|
1967
|
-
fetchJSON(API + '/filters?section=logs')
|
|
1968
|
-
.then(function (data) {
|
|
1969
|
-
logSavedFilters = data.filters || data.data || []
|
|
1970
|
-
renderSavedFilters()
|
|
1971
|
-
})
|
|
1972
|
-
.catch(function () {
|
|
1973
|
-
/* ignore */
|
|
1974
|
-
})
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
var renderSavedFilters = function () {
|
|
1978
|
-
var sel = document.getElementById('ss-dash-log-saved-select')
|
|
1979
|
-
if (!sel) return
|
|
1980
|
-
var html = '<option value="">Saved Filters...</option>'
|
|
1981
|
-
logSavedFilters.forEach(function (f) {
|
|
1982
|
-
html += '<option value="' + f.id + '">' + esc(f.name) + '</option>'
|
|
1983
|
-
})
|
|
1984
|
-
sel.innerHTML = html
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
var savedFilterSelect = document.getElementById('ss-dash-log-saved-select')
|
|
1988
|
-
if (savedFilterSelect) {
|
|
1989
|
-
savedFilterSelect.addEventListener('change', function () {
|
|
1990
|
-
var id = savedFilterSelect.value
|
|
1991
|
-
if (!id) return
|
|
1992
|
-
var filter = logSavedFilters.find(function (f) {
|
|
1993
|
-
return String(f.id) === id
|
|
1994
|
-
})
|
|
1995
|
-
if (filter && filter.filter_config) {
|
|
1996
|
-
try {
|
|
1997
|
-
var cfg =
|
|
1998
|
-
typeof filter.filter_config === 'string'
|
|
1999
|
-
? JSON.parse(filter.filter_config)
|
|
2000
|
-
: filter.filter_config
|
|
2001
|
-
if (cfg.level) logLevelFilter = cfg.level
|
|
2002
|
-
if (cfg.filters) logStructuredFilters = cfg.filters
|
|
2003
|
-
renderFilterChips()
|
|
2004
|
-
getPage('logs').page = 1
|
|
2005
|
-
fetchLogs()
|
|
2006
|
-
} catch (e) {
|
|
2007
|
-
/* ignore */
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
savedFilterSelect.value = ''
|
|
2011
|
-
})
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
var saveFilterBtn = document.getElementById('ss-dash-log-save-filter')
|
|
2015
|
-
if (saveFilterBtn) {
|
|
2016
|
-
saveFilterBtn.addEventListener('click', function () {
|
|
2017
|
-
var name = prompt('Filter preset name:')
|
|
2018
|
-
if (!name) return
|
|
2019
|
-
var config = {
|
|
2020
|
-
level: logLevelFilter,
|
|
2021
|
-
requestId: logReqIdFilter,
|
|
2022
|
-
filters: logStructuredFilters,
|
|
2023
|
-
}
|
|
2024
|
-
fetch(API + '/filters', {
|
|
2025
|
-
method: 'POST',
|
|
2026
|
-
credentials: 'same-origin',
|
|
2027
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2028
|
-
body: JSON.stringify({ name: name, section: 'logs', filter_config: config }),
|
|
2029
|
-
})
|
|
2030
|
-
.then(function () {
|
|
2031
|
-
fetchSavedFilters()
|
|
2032
|
-
})
|
|
2033
|
-
.catch(function () {
|
|
2034
|
-
/* ignore */
|
|
2035
|
-
})
|
|
2036
|
-
})
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
var deleteFilterBtn = document.getElementById('ss-dash-log-delete-filter')
|
|
2040
|
-
if (deleteFilterBtn) {
|
|
2041
|
-
deleteFilterBtn.addEventListener('click', function () {
|
|
2042
|
-
var sel = document.getElementById('ss-dash-log-saved-select')
|
|
2043
|
-
if (!sel || !sel.value) return
|
|
2044
|
-
fetch(API + '/filters/' + sel.value, { method: 'DELETE', credentials: 'same-origin' })
|
|
2045
|
-
.then(function () {
|
|
2046
|
-
fetchSavedFilters()
|
|
2047
|
-
})
|
|
2048
|
-
.catch(function () {
|
|
2049
|
-
/* ignore */
|
|
2050
|
-
})
|
|
2051
|
-
})
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
fetchSavedFilters()
|
|
2055
|
-
|
|
2056
|
-
// ── Emails ────────────────────────────────────────────────────
|
|
2057
|
-
var emailStatusFilter = ''
|
|
2058
|
-
|
|
2059
|
-
var fetchEmails = function () {
|
|
2060
|
-
var ps = getPage('emails')
|
|
2061
|
-
var url = API + '/emails?page=' + ps.page + '&limit=' + PER_PAGE
|
|
2062
|
-
if (emailStatusFilter) url += '&status=' + encodeURIComponent(emailStatusFilter)
|
|
2063
|
-
emailStatusFilter = ''
|
|
2064
|
-
fetchJSON(url)
|
|
2065
|
-
.then(function (data) {
|
|
2066
|
-
renderEmails(data)
|
|
2067
|
-
})
|
|
2068
|
-
.catch(function () {
|
|
2069
|
-
setInner('ss-dash-emails-body', '<div class="ss-dash-empty">Failed to load emails</div>')
|
|
2070
|
-
})
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
var renderEmails = function (data) {
|
|
2074
|
-
var items = data.data || data.emails || []
|
|
2075
|
-
var ps = getPage('emails')
|
|
2076
|
-
ps.total = data.meta ? data.meta.total : data.total || items.length
|
|
2077
|
-
|
|
2078
|
-
setInner('ss-dash-emails-summary', ps.total + ' emails')
|
|
2079
|
-
|
|
2080
|
-
if (items.length === 0) {
|
|
2081
|
-
setInner('ss-dash-emails-body', '<div class="ss-dash-empty">No emails captured yet</div>')
|
|
2082
|
-
renderPagination('emails', ps)
|
|
2083
|
-
return
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
var html =
|
|
2087
|
-
'<table class="ss-dash-table" style="table-layout:fixed"><thead><tr>' +
|
|
2088
|
-
'<th style="width:40px">#</th>' +
|
|
2089
|
-
'<th style="width:160px">From</th>' +
|
|
2090
|
-
'<th style="width:160px">To</th>' +
|
|
2091
|
-
'<th>Subject</th>' +
|
|
2092
|
-
'<th style="width:70px">Status</th>' +
|
|
2093
|
-
'<th style="width:70px">Mailer</th>' +
|
|
2094
|
-
'<th style="width:30px" title="Attachments">ATT</th>' +
|
|
2095
|
-
'<th style="width:60px">Time</th>' +
|
|
2096
|
-
'</tr></thead><tbody>'
|
|
2097
|
-
|
|
2098
|
-
items.forEach(function (e) {
|
|
2099
|
-
var fromAddr = e.from_addr || e.from || ''
|
|
2100
|
-
var toAddr = e.to_addr || e.to || ''
|
|
2101
|
-
html +=
|
|
2102
|
-
'<tr class="ss-dash-email-row" data-email-id="' +
|
|
2103
|
-
e.id +
|
|
2104
|
-
'">' +
|
|
2105
|
-
'<td style="color:var(--ss-dim)">' +
|
|
2106
|
-
e.id +
|
|
2107
|
-
'</td>' +
|
|
2108
|
-
'<td style="color:var(--ss-text-secondary);' +
|
|
2109
|
-
TRUNC +
|
|
2110
|
-
'" title="' +
|
|
2111
|
-
esc(fromAddr) +
|
|
2112
|
-
'">' +
|
|
2113
|
-
esc(fromAddr) +
|
|
2114
|
-
'</td>' +
|
|
2115
|
-
'<td style="color:var(--ss-text-secondary);' +
|
|
2116
|
-
TRUNC +
|
|
2117
|
-
'" title="' +
|
|
2118
|
-
esc(toAddr) +
|
|
2119
|
-
'">' +
|
|
2120
|
-
esc(toAddr) +
|
|
2121
|
-
'</td>' +
|
|
2122
|
-
'<td style="color:var(--ss-sql-color);' +
|
|
2123
|
-
TRUNC +
|
|
2124
|
-
'" title="' +
|
|
2125
|
-
esc(e.subject || '') +
|
|
2126
|
-
'">' +
|
|
2127
|
-
esc(e.subject || '') +
|
|
2128
|
-
'</td>' +
|
|
2129
|
-
'<td><span class="ss-dash-badge ss-dash-email-status-' +
|
|
2130
|
-
esc(e.status || '') +
|
|
2131
|
-
'">' +
|
|
2132
|
-
esc(e.status || '') +
|
|
2133
|
-
'</span></td>' +
|
|
2134
|
-
'<td style="color:var(--ss-muted);' +
|
|
2135
|
-
TRUNC +
|
|
2136
|
-
'">' +
|
|
2137
|
-
esc(e.mailer || '') +
|
|
2138
|
-
'</td>' +
|
|
2139
|
-
'<td style="color:var(--ss-dim);text-align:center">' +
|
|
2140
|
-
((e.attachment_count || e.attachmentCount || 0) > 0
|
|
2141
|
-
? e.attachment_count || e.attachmentCount
|
|
2142
|
-
: '-') +
|
|
2143
|
-
'</td>' +
|
|
2144
|
-
'<td class="ss-dash-event-time" style="white-space:nowrap">' +
|
|
2145
|
-
timeAgo(getTimestamp(e)) +
|
|
2146
|
-
'</td>' +
|
|
2147
|
-
'</tr>'
|
|
2148
|
-
})
|
|
2149
|
-
|
|
2150
|
-
html += '</tbody></table>'
|
|
2151
|
-
setInner('ss-dash-emails-body', html)
|
|
2152
|
-
renderPagination('emails', ps)
|
|
2153
|
-
|
|
2154
|
-
var body = document.getElementById('ss-dash-emails-body')
|
|
2155
|
-
if (body) {
|
|
2156
|
-
body.querySelectorAll('.ss-dash-email-row').forEach(function (row) {
|
|
2157
|
-
row.addEventListener('click', function () {
|
|
2158
|
-
var id = row.getAttribute('data-email-id')
|
|
2159
|
-
showEmailPreview(id, items)
|
|
2160
|
-
})
|
|
2161
|
-
})
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
var showEmailPreview = function (id, emails) {
|
|
2166
|
-
var previewEl = document.getElementById('ss-dash-email-preview')
|
|
2167
|
-
var metaEl = document.getElementById('ss-dash-email-preview-meta')
|
|
2168
|
-
var iframeEl = document.getElementById('ss-dash-email-iframe')
|
|
2169
|
-
if (!previewEl || !iframeEl) return
|
|
2170
|
-
|
|
2171
|
-
var email = emails.find(function (e) {
|
|
2172
|
-
return String(e.id) === String(id)
|
|
2173
|
-
})
|
|
2174
|
-
if (metaEl && email) {
|
|
2175
|
-
metaEl.innerHTML =
|
|
2176
|
-
'<strong>Subject:</strong> ' +
|
|
2177
|
-
esc(email.subject || '') +
|
|
2178
|
-
' | <strong>From:</strong> ' +
|
|
2179
|
-
esc(email.from_addr || email.from || '') +
|
|
2180
|
-
' | <strong>To:</strong> ' +
|
|
2181
|
-
esc(email.to_addr || email.to || '') +
|
|
2182
|
-
(email.cc ? ' | <strong>CC:</strong> ' + esc(email.cc) : '') +
|
|
2183
|
-
' | <strong>Status:</strong> <span class="ss-dash-badge ss-dash-email-status-' +
|
|
2184
|
-
esc(email.status || '') +
|
|
2185
|
-
'">' +
|
|
2186
|
-
esc(email.status || '') +
|
|
2187
|
-
'</span>' +
|
|
2188
|
-
' | <strong>Mailer:</strong> ' +
|
|
2189
|
-
esc(email.mailer || '')
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
iframeEl.src = API + '/emails/' + id + '/preview'
|
|
2193
|
-
previewEl.style.display = 'flex'
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
var emailPreviewClose = document.getElementById('ss-dash-email-preview-close')
|
|
2197
|
-
if (emailPreviewClose) {
|
|
2198
|
-
emailPreviewClose.addEventListener('click', function () {
|
|
2199
|
-
var previewEl = document.getElementById('ss-dash-email-preview')
|
|
2200
|
-
var iframeEl = document.getElementById('ss-dash-email-iframe')
|
|
2201
|
-
if (previewEl) previewEl.style.display = 'none'
|
|
2202
|
-
if (iframeEl) iframeEl.src = 'about:blank'
|
|
2203
|
-
})
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
// ── Timeline / Traces ─────────────────────────────────────────
|
|
2207
|
-
var fetchTraces = function () {
|
|
2208
|
-
if (!tracingEnabled) {
|
|
2209
|
-
setInner(
|
|
2210
|
-
'ss-dash-timeline-body',
|
|
2211
|
-
'<div class="ss-dash-empty">Tracing is not enabled. Set tracing: true in config.</div>'
|
|
2212
|
-
)
|
|
2213
|
-
return
|
|
2214
|
-
}
|
|
2215
|
-
var ps = getPage('timeline')
|
|
2216
|
-
fetchJSON(API + '/traces?page=' + ps.page + '&limit=' + PER_PAGE)
|
|
2217
|
-
.then(function (data) {
|
|
2218
|
-
renderTraces(data)
|
|
2219
|
-
})
|
|
2220
|
-
.catch(function () {
|
|
2221
|
-
setInner('ss-dash-timeline-body', '<div class="ss-dash-empty">Failed to load traces</div>')
|
|
2222
|
-
})
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
var renderTraces = function (data) {
|
|
2226
|
-
var items = data.data || data.traces || []
|
|
2227
|
-
var ps = getPage('timeline')
|
|
2228
|
-
ps.total = data.meta ? data.meta.total : data.total || items.length
|
|
2229
|
-
|
|
2230
|
-
setInner('ss-dash-timeline-summary', ps.total + ' requests')
|
|
2231
|
-
|
|
2232
|
-
if (items.length === 0) {
|
|
2233
|
-
setInner('ss-dash-timeline-body', '<div class="ss-dash-empty">No requests traced yet</div>')
|
|
2234
|
-
renderPagination('timeline', ps)
|
|
2235
|
-
return
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
var html =
|
|
2239
|
-
'<table class="ss-dash-table" style="table-layout:fixed"><thead><tr>' +
|
|
2240
|
-
'<th style="width:50px">#</th>' +
|
|
2241
|
-
'<th style="width:60px">Method</th>' +
|
|
2242
|
-
'<th>URL</th>' +
|
|
2243
|
-
'<th style="width:55px">Status</th>' +
|
|
2244
|
-
'<th style="width:80px">Duration</th>' +
|
|
2245
|
-
'<th style="width:50px">Spans</th>' +
|
|
2246
|
-
'<th style="width:60px">Time</th>' +
|
|
2247
|
-
'</tr></thead><tbody>'
|
|
2248
|
-
|
|
2249
|
-
items.forEach(function (t) {
|
|
2250
|
-
html +=
|
|
2251
|
-
'<tr class="ss-dash-clickable" data-trace-id="' +
|
|
2252
|
-
t.id +
|
|
2253
|
-
'">' +
|
|
2254
|
-
'<td style="color:var(--ss-dim)">' +
|
|
2255
|
-
t.id +
|
|
2256
|
-
'</td>' +
|
|
2257
|
-
'<td><span class="' +
|
|
2258
|
-
methodClass(t.method) +
|
|
2259
|
-
'">' +
|
|
2260
|
-
esc(t.method) +
|
|
2261
|
-
'</span></td>' +
|
|
2262
|
-
'<td style="' +
|
|
2263
|
-
TRUNC +
|
|
2264
|
-
';color:var(--ss-text)" title="' +
|
|
2265
|
-
esc(t.url) +
|
|
2266
|
-
'">' +
|
|
2267
|
-
esc(t.url) +
|
|
2268
|
-
'</td>' +
|
|
2269
|
-
'<td>' +
|
|
2270
|
-
renderStatusBadge(getStatus(t)) +
|
|
2271
|
-
'</td>' +
|
|
2272
|
-
'<td class="ss-dash-duration ' +
|
|
2273
|
-
durationClass(t.total_duration || t.totalDuration) +
|
|
2274
|
-
'">' +
|
|
2275
|
-
(t.total_duration || t.totalDuration || 0).toFixed(1) +
|
|
2276
|
-
'ms</td>' +
|
|
2277
|
-
'<td style="color:var(--ss-muted);text-align:center">' +
|
|
2278
|
-
(t.span_count || t.spanCount || 0) +
|
|
2279
|
-
'</td>' +
|
|
2280
|
-
'<td class="ss-dash-event-time" style="white-space:nowrap">' +
|
|
2281
|
-
timeAgo(getTimestamp(t)) +
|
|
2282
|
-
'</td>' +
|
|
2283
|
-
'</tr>'
|
|
2284
|
-
})
|
|
2285
|
-
|
|
2286
|
-
html += '</tbody></table>'
|
|
2287
|
-
setInner('ss-dash-timeline-body', html)
|
|
2288
|
-
renderPagination('timeline', ps)
|
|
2289
|
-
|
|
2290
|
-
var body = document.getElementById('ss-dash-timeline-body')
|
|
2291
|
-
if (body) {
|
|
2292
|
-
body.querySelectorAll('[data-trace-id]').forEach(function (row) {
|
|
2293
|
-
row.addEventListener('click', function () {
|
|
2294
|
-
var id = row.getAttribute('data-trace-id')
|
|
2295
|
-
fetchJSON(API + '/traces/' + id)
|
|
2296
|
-
.then(function (trace) {
|
|
2297
|
-
showTraceDetail(trace)
|
|
2298
|
-
})
|
|
2299
|
-
.catch(function () {
|
|
2300
|
-
/* ignore */
|
|
2301
|
-
})
|
|
2302
|
-
})
|
|
2303
|
-
})
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
var showTraceDetail = function (trace) {
|
|
2308
|
-
var listEl = document.getElementById('ss-dash-timeline-list')
|
|
2309
|
-
var detailEl = document.getElementById('ss-dash-timeline-detail')
|
|
2310
|
-
var titleEl = document.getElementById('ss-dash-timeline-detail-title')
|
|
2311
|
-
var waterfallEl = document.getElementById('ss-dash-timeline-waterfall')
|
|
2312
|
-
if (!listEl || !detailEl) return
|
|
2313
|
-
|
|
2314
|
-
listEl.style.display = 'none'
|
|
2315
|
-
detailEl.style.display = 'flex'
|
|
2316
|
-
detailEl.classList.add('ss-dash-active')
|
|
2317
|
-
|
|
2318
|
-
if (titleEl) {
|
|
2319
|
-
titleEl.innerHTML =
|
|
2320
|
-
'<span class="' +
|
|
2321
|
-
methodClass(trace.method) +
|
|
2322
|
-
'">' +
|
|
2323
|
-
esc(trace.method) +
|
|
2324
|
-
'</span> ' +
|
|
2325
|
-
esc(trace.url) +
|
|
2326
|
-
' ' +
|
|
2327
|
-
renderStatusBadge(getStatus(trace)) +
|
|
2328
|
-
'<span class="ss-dash-tl-meta">' +
|
|
2329
|
-
(trace.total_duration || trace.totalDuration || 0).toFixed(1) +
|
|
2330
|
-
'ms · ' +
|
|
2331
|
-
(trace.span_count || trace.spanCount || 0) +
|
|
2332
|
-
' spans</span>'
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
|
-
if (waterfallEl) renderWaterfall(waterfallEl, trace)
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
var timelineBackBtn = document.getElementById('ss-dash-timeline-back')
|
|
2339
|
-
if (timelineBackBtn) {
|
|
2340
|
-
timelineBackBtn.addEventListener('click', function () {
|
|
2341
|
-
var listEl = document.getElementById('ss-dash-timeline-list')
|
|
2342
|
-
var detailEl = document.getElementById('ss-dash-timeline-detail')
|
|
2343
|
-
if (listEl) listEl.style.display = ''
|
|
2344
|
-
if (detailEl) {
|
|
2345
|
-
detailEl.style.display = 'none'
|
|
2346
|
-
detailEl.classList.remove('ss-dash-active')
|
|
2347
|
-
}
|
|
2348
|
-
})
|
|
2349
|
-
}
|
|
2350
|
-
|
|
2351
|
-
// ── Waterfall renderer (shared) ───────────────────────────────
|
|
2352
|
-
var renderWaterfall = function (container, trace) {
|
|
2353
|
-
var spans = trace.spans || []
|
|
2354
|
-
if (typeof spans === 'string') {
|
|
2355
|
-
try {
|
|
2356
|
-
spans = JSON.parse(spans)
|
|
2357
|
-
} catch (e) {
|
|
2358
|
-
spans = []
|
|
2359
|
-
}
|
|
2360
|
-
}
|
|
2361
|
-
var total = trace.total_duration || trace.totalDuration || trace.duration || 1
|
|
2362
|
-
|
|
2363
|
-
var html =
|
|
2364
|
-
'<div class="ss-dash-tl-legend">' +
|
|
2365
|
-
'<div class="ss-dash-tl-legend-item"><span class="ss-dash-tl-legend-dot" style="background:#6d28d9"></span>DB</div>' +
|
|
2366
|
-
'<div class="ss-dash-tl-legend-item"><span class="ss-dash-tl-legend-dot" style="background:#1e3a5f"></span>Request</div>' +
|
|
2367
|
-
'<div class="ss-dash-tl-legend-item"><span class="ss-dash-tl-legend-dot" style="background:#059669"></span>Mail</div>' +
|
|
2368
|
-
'<div class="ss-dash-tl-legend-item"><span class="ss-dash-tl-legend-dot" style="background:#b45309"></span>Event</div>' +
|
|
2369
|
-
'<div class="ss-dash-tl-legend-item"><span class="ss-dash-tl-legend-dot" style="background:#0e7490"></span>View</div>' +
|
|
2370
|
-
'<div class="ss-dash-tl-legend-item"><span class="ss-dash-tl-legend-dot" style="background:var(--ss-dim)"></span>Custom</div>' +
|
|
2371
|
-
'</div>'
|
|
2372
|
-
|
|
2373
|
-
if (spans.length === 0) {
|
|
2374
|
-
html += '<div class="ss-dash-empty">No spans captured for this request</div>'
|
|
2375
|
-
} else {
|
|
2376
|
-
var depthMap = {}
|
|
2377
|
-
for (var i = 0; i < spans.length; i++) {
|
|
2378
|
-
var s = spans[i]
|
|
2379
|
-
depthMap[s.id] = s.parentId ? (depthMap[s.parentId] || 0) + 1 : 0
|
|
2380
|
-
}
|
|
2381
|
-
var sorted = spans.slice().sort(function (a, b) {
|
|
2382
|
-
return a.startOffset - b.startOffset
|
|
2383
|
-
})
|
|
2384
|
-
|
|
2385
|
-
for (var j = 0; j < sorted.length; j++) {
|
|
2386
|
-
var sp = sorted[j]
|
|
2387
|
-
var depth = depthMap[sp.id] || 0
|
|
2388
|
-
var leftPct = ((sp.startOffset / total) * 100).toFixed(2)
|
|
2389
|
-
var widthPct = Math.max((sp.duration / total) * 100, 0.5).toFixed(2)
|
|
2390
|
-
var indent = depth * 16
|
|
2391
|
-
var catLabel = sp.category === 'db' ? 'DB' : sp.category
|
|
2392
|
-
var metaStr = sp.metadata
|
|
2393
|
-
? Object.entries(sp.metadata)
|
|
2394
|
-
.filter(function (e) {
|
|
2395
|
-
return e[1] != null
|
|
2396
|
-
})
|
|
2397
|
-
.map(function (e) {
|
|
2398
|
-
return e[0] + '=' + e[1]
|
|
2399
|
-
})
|
|
2400
|
-
.join(', ')
|
|
2401
|
-
: ''
|
|
2402
|
-
var tooltip =
|
|
2403
|
-
sp.label + ' (' + sp.duration.toFixed(2) + 'ms)' + (metaStr ? '\n' + metaStr : '')
|
|
2404
|
-
|
|
2405
|
-
var badgeCat =
|
|
2406
|
-
sp.category === 'db'
|
|
2407
|
-
? 'purple'
|
|
2408
|
-
: sp.category === 'mail'
|
|
2409
|
-
? 'green'
|
|
2410
|
-
: sp.category === 'event'
|
|
2411
|
-
? 'amber'
|
|
2412
|
-
: sp.category === 'view'
|
|
2413
|
-
? 'blue'
|
|
2414
|
-
: 'muted'
|
|
2415
|
-
|
|
2416
|
-
html +=
|
|
2417
|
-
'<div class="ss-dash-tl-row">' +
|
|
2418
|
-
'<div class="ss-dash-tl-label" style="padding-left:' +
|
|
2419
|
-
(8 + indent) +
|
|
2420
|
-
'px" title="' +
|
|
2421
|
-
esc(tooltip) +
|
|
2422
|
-
'">' +
|
|
2423
|
-
'<span class="ss-dash-badge ss-dash-badge-' +
|
|
2424
|
-
badgeCat +
|
|
2425
|
-
'" style="font-size:9px;margin-right:4px">' +
|
|
2426
|
-
esc(catLabel) +
|
|
2427
|
-
'</span>' +
|
|
2428
|
-
esc(sp.label.length > 50 ? sp.label.slice(0, 50) + '...' : sp.label) +
|
|
2429
|
-
'</div>' +
|
|
2430
|
-
'<div class="ss-dash-tl-track">' +
|
|
2431
|
-
'<div class="ss-dash-tl-bar ss-dash-tl-bar-' +
|
|
2432
|
-
esc(sp.category) +
|
|
2433
|
-
'" style="left:' +
|
|
2434
|
-
leftPct +
|
|
2435
|
-
'%;width:' +
|
|
2436
|
-
widthPct +
|
|
2437
|
-
'%" title="' +
|
|
2438
|
-
esc(tooltip) +
|
|
2439
|
-
'"></div>' +
|
|
2440
|
-
'</div>' +
|
|
2441
|
-
'<span class="ss-dash-tl-dur">' +
|
|
2442
|
-
sp.duration.toFixed(2) +
|
|
2443
|
-
'ms</span>' +
|
|
2444
|
-
'</div>'
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
// Warnings
|
|
2449
|
-
var warnings = trace.warnings || []
|
|
2450
|
-
if (typeof warnings === 'string') {
|
|
2451
|
-
try {
|
|
2452
|
-
warnings = JSON.parse(warnings)
|
|
2453
|
-
} catch (e) {
|
|
2454
|
-
warnings = []
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
if (warnings.length > 0) {
|
|
2458
|
-
html +=
|
|
2459
|
-
'<div class="ss-dash-tl-warnings">' +
|
|
2460
|
-
'<div class="ss-dash-tl-warnings-title">Warnings (' +
|
|
2461
|
-
warnings.length +
|
|
2462
|
-
')</div>'
|
|
2463
|
-
warnings.forEach(function (w) {
|
|
2464
|
-
html += '<div class="ss-dash-tl-warning">' + esc(w) + '</div>'
|
|
2465
|
-
})
|
|
2466
|
-
html += '</div>'
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
|
-
container.innerHTML = html
|
|
2470
|
-
}
|
|
2471
|
-
|
|
2472
|
-
// ── Cache ─────────────────────────────────────────────────────
|
|
2473
|
-
var fetchCache = function () {
|
|
2474
|
-
fetchJSON(API + '/cache')
|
|
2475
|
-
.then(function (data) {
|
|
2476
|
-
renderCache(data)
|
|
2477
|
-
})
|
|
2478
|
-
.catch(function () {
|
|
2479
|
-
setInner('ss-dash-cache-body', '<div class="ss-dash-empty">Cache not available</div>')
|
|
2480
|
-
})
|
|
2481
|
-
}
|
|
2482
|
-
|
|
2483
|
-
var renderCache = function (data) {
|
|
2484
|
-
var stats = data.stats || {}
|
|
2485
|
-
var keys = data.keys || data.data || []
|
|
2486
|
-
|
|
2487
|
-
var statsHtml =
|
|
2488
|
-
'<div class="ss-dash-cache-stats">' +
|
|
2489
|
-
'<div class="ss-dash-cache-stat"><span class="ss-dash-cache-stat-label">Hit Rate:</span><span class="ss-dash-cache-stat-value">' +
|
|
2490
|
-
(stats.hitRate || 0).toFixed(1) +
|
|
2491
|
-
'%</span></div>' +
|
|
2492
|
-
'<div class="ss-dash-cache-stat"><span class="ss-dash-cache-stat-label">Hits:</span><span class="ss-dash-cache-stat-value">' +
|
|
2493
|
-
(stats.hits || 0) +
|
|
2494
|
-
'</span></div>' +
|
|
2495
|
-
'<div class="ss-dash-cache-stat"><span class="ss-dash-cache-stat-label">Misses:</span><span class="ss-dash-cache-stat-value">' +
|
|
2496
|
-
(stats.misses || 0) +
|
|
2497
|
-
'</span></div>' +
|
|
2498
|
-
'<div class="ss-dash-cache-stat"><span class="ss-dash-cache-stat-label">Keys:</span><span class="ss-dash-cache-stat-value">' +
|
|
2499
|
-
(stats.keyCount || keys.length || 0) +
|
|
2500
|
-
'</span></div>' +
|
|
2501
|
-
'</div>'
|
|
2502
|
-
|
|
2503
|
-
setInner('ss-dash-cache-stats-area', statsHtml)
|
|
2504
|
-
|
|
2505
|
-
if (keys.length === 0) {
|
|
2506
|
-
setInner('ss-dash-cache-body', '<div class="ss-dash-empty">No cache keys found</div>')
|
|
2507
|
-
return
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2510
|
-
var html =
|
|
2511
|
-
'<table class="ss-dash-table" style="table-layout:fixed"><thead><tr>' +
|
|
2512
|
-
'<th>Key</th>' +
|
|
2513
|
-
'<th style="width:80px">Type</th>' +
|
|
2514
|
-
'<th style="width:80px">TTL</th>' +
|
|
2515
|
-
'<th style="width:80px">Size</th>' +
|
|
2516
|
-
'</tr></thead><tbody>'
|
|
2517
|
-
|
|
2518
|
-
keys.forEach(function (k) {
|
|
2519
|
-
html +=
|
|
2520
|
-
'<tr class="ss-dash-clickable" data-cache-key="' +
|
|
2521
|
-
esc(k.key || '') +
|
|
2522
|
-
'">' +
|
|
2523
|
-
'<td style="color:var(--ss-sql-color);' +
|
|
2524
|
-
TRUNC +
|
|
2525
|
-
'" title="' +
|
|
2526
|
-
esc(k.key || '') +
|
|
2527
|
-
'">' +
|
|
2528
|
-
esc(k.key || '') +
|
|
2529
|
-
'</td>' +
|
|
2530
|
-
'<td style="color:var(--ss-muted)">' +
|
|
2531
|
-
esc(k.type || '-') +
|
|
2532
|
-
'</td>' +
|
|
2533
|
-
'<td style="color:var(--ss-muted)">' +
|
|
2534
|
-
(k.ttl != null ? k.ttl + 's' : '-') +
|
|
2535
|
-
'</td>' +
|
|
2536
|
-
'<td style="color:var(--ss-dim)">' +
|
|
2537
|
-
(k.size != null ? k.size + 'B' : '-') +
|
|
2538
|
-
'</td>' +
|
|
2539
|
-
'</tr>'
|
|
2540
|
-
})
|
|
2541
|
-
|
|
2542
|
-
html += '</tbody></table>'
|
|
2543
|
-
setInner('ss-dash-cache-body', html)
|
|
2544
|
-
|
|
2545
|
-
var body = document.getElementById('ss-dash-cache-body')
|
|
2546
|
-
if (body) {
|
|
2547
|
-
body.querySelectorAll('[data-cache-key]').forEach(function (row) {
|
|
2548
|
-
row.addEventListener('click', function () {
|
|
2549
|
-
var key = row.getAttribute('data-cache-key')
|
|
2550
|
-
fetchJSON(API + '/cache/' + encodeURIComponent(key))
|
|
2551
|
-
.then(function (data) {
|
|
2552
|
-
setInner(
|
|
2553
|
-
'ss-dash-cache-detail',
|
|
2554
|
-
'<div class="ss-dash-cache-detail"><strong>Key:</strong> ' +
|
|
2555
|
-
esc(key) +
|
|
2556
|
-
'<pre class="ss-dash-data-full" style="display:block">' +
|
|
2557
|
-
esc(JSON.stringify(data.value || data, null, 2)) +
|
|
2558
|
-
'</pre></div>'
|
|
2559
|
-
)
|
|
2560
|
-
})
|
|
2561
|
-
.catch(function () {
|
|
2562
|
-
/* ignore */
|
|
2563
|
-
})
|
|
2564
|
-
})
|
|
2565
|
-
})
|
|
2566
|
-
}
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
|
-
// ── Jobs ──────────────────────────────────────────────────────
|
|
2570
|
-
var jobStatusFilter = ''
|
|
2571
|
-
|
|
2572
|
-
var fetchJobs = function () {
|
|
2573
|
-
var ps = getPage('jobs')
|
|
2574
|
-
var params = 'page=' + ps.page + '&limit=' + PER_PAGE
|
|
2575
|
-
if (jobStatusFilter) params += '&status=' + jobStatusFilter
|
|
2576
|
-
|
|
2577
|
-
fetchJSON(API + '/jobs?' + params)
|
|
2578
|
-
.then(function (data) {
|
|
2579
|
-
renderJobs(data)
|
|
2580
|
-
})
|
|
2581
|
-
.catch(function () {
|
|
2582
|
-
setInner('ss-dash-jobs-body', '<div class="ss-dash-empty">Jobs/Queue not available</div>')
|
|
2583
|
-
})
|
|
2584
|
-
}
|
|
2585
|
-
|
|
2586
|
-
var renderJobs = function (data) {
|
|
2587
|
-
var items = data.data || data.jobs || []
|
|
2588
|
-
var stats = data.stats || {}
|
|
2589
|
-
var ps = getPage('jobs')
|
|
2590
|
-
ps.total = data.meta ? data.meta.total : data.total || items.length
|
|
2591
|
-
|
|
2592
|
-
var statsHtml =
|
|
2593
|
-
'<div class="ss-dash-job-stats">' +
|
|
2594
|
-
'<div class="ss-dash-job-stat"><span class="ss-dash-job-stat-label">Active:</span><span class="ss-dash-job-stat-value">' +
|
|
2595
|
-
(stats.active || 0) +
|
|
2596
|
-
'</span></div>' +
|
|
2597
|
-
'<div class="ss-dash-job-stat"><span class="ss-dash-job-stat-label">Waiting:</span><span class="ss-dash-job-stat-value">' +
|
|
2598
|
-
(stats.waiting || 0) +
|
|
2599
|
-
'</span></div>' +
|
|
2600
|
-
'<div class="ss-dash-job-stat"><span class="ss-dash-job-stat-label">Delayed:</span><span class="ss-dash-job-stat-value">' +
|
|
2601
|
-
(stats.delayed || 0) +
|
|
2602
|
-
'</span></div>' +
|
|
2603
|
-
'<div class="ss-dash-job-stat"><span class="ss-dash-job-stat-label">Completed:</span><span class="ss-dash-job-stat-value">' +
|
|
2604
|
-
(stats.completed || 0) +
|
|
2605
|
-
'</span></div>' +
|
|
2606
|
-
'<div class="ss-dash-job-stat"><span class="ss-dash-job-stat-label">Failed:</span><span class="ss-dash-job-stat-value" style="color:var(--ss-red-fg)">' +
|
|
2607
|
-
(stats.failed || 0) +
|
|
2608
|
-
'</span></div>' +
|
|
2609
|
-
'</div>'
|
|
2610
|
-
setInner('ss-dash-jobs-stats-area', statsHtml)
|
|
2611
|
-
|
|
2612
|
-
if (items.length === 0) {
|
|
2613
|
-
setInner('ss-dash-jobs-body', '<div class="ss-dash-empty">No jobs found</div>')
|
|
2614
|
-
renderPagination('jobs', ps)
|
|
2615
|
-
return
|
|
2616
|
-
}
|
|
2617
|
-
|
|
2618
|
-
var html =
|
|
2619
|
-
'<table class="ss-dash-table" style="table-layout:fixed"><thead><tr>' +
|
|
2620
|
-
'<th style="width:50px">ID</th>' +
|
|
2621
|
-
'<th style="width:160px">Name</th>' +
|
|
2622
|
-
'<th style="width:80px">Status</th>' +
|
|
2623
|
-
'<th>Payload</th>' +
|
|
2624
|
-
'<th style="width:55px">Tries</th>' +
|
|
2625
|
-
'<th style="width:75px">Duration</th>' +
|
|
2626
|
-
'<th style="width:60px">Time</th>' +
|
|
2627
|
-
'<th style="width:50px"></th>' +
|
|
2628
|
-
'</tr></thead><tbody>'
|
|
2629
|
-
|
|
2630
|
-
items.forEach(function (j) {
|
|
2631
|
-
var statusBadge =
|
|
2632
|
-
j.status === 'failed'
|
|
2633
|
-
? 'red'
|
|
2634
|
-
: j.status === 'completed'
|
|
2635
|
-
? 'green'
|
|
2636
|
-
: j.status === 'active'
|
|
2637
|
-
? 'blue'
|
|
2638
|
-
: 'amber'
|
|
2639
|
-
html +=
|
|
2640
|
-
'<tr class="ss-dash-clickable" data-job-id="' +
|
|
2641
|
-
j.id +
|
|
2642
|
-
'">' +
|
|
2643
|
-
'<td style="color:var(--ss-dim)">' +
|
|
2644
|
-
j.id +
|
|
2645
|
-
'</td>' +
|
|
2646
|
-
'<td style="color:var(--ss-sql-color);' +
|
|
2647
|
-
TRUNC +
|
|
2648
|
-
'" title="' +
|
|
2649
|
-
esc(j.name || '') +
|
|
2650
|
-
'">' +
|
|
2651
|
-
esc(j.name || '') +
|
|
2652
|
-
'</td>' +
|
|
2653
|
-
'<td><span class="ss-dash-badge ss-dash-badge-' +
|
|
2654
|
-
statusBadge +
|
|
2655
|
-
'">' +
|
|
2656
|
-
esc(j.status || '') +
|
|
2657
|
-
'</span></td>' +
|
|
2658
|
-
'<td style="color:var(--ss-muted);font-size:10px;' +
|
|
2659
|
-
TRUNC +
|
|
2660
|
-
'">' +
|
|
2661
|
-
esc(j.payload ? compactPreview(j.payload, 60) : '-') +
|
|
2662
|
-
'</td>' +
|
|
2663
|
-
'<td style="color:var(--ss-muted);text-align:center">' +
|
|
2664
|
-
(j.attempts || j.attemptsMade || 0) +
|
|
2665
|
-
'</td>' +
|
|
2666
|
-
'<td class="ss-dash-duration">' +
|
|
2667
|
-
(j.duration != null ? j.duration.toFixed(0) + 'ms' : '-') +
|
|
2668
|
-
'</td>' +
|
|
2669
|
-
'<td class="ss-dash-event-time" style="white-space:nowrap">' +
|
|
2670
|
-
timeAgo(j.timestamp || j.processedOn || j.created_at) +
|
|
2671
|
-
'</td>' +
|
|
2672
|
-
'<td>' +
|
|
2673
|
-
(j.status === 'failed'
|
|
2674
|
-
? '<button class="ss-dash-retry-btn" data-retry-id="' + j.id + '">Retry</button>'
|
|
2675
|
-
: '') +
|
|
2676
|
-
'</td>' +
|
|
2677
|
-
'</tr>'
|
|
2678
|
-
})
|
|
2679
|
-
|
|
2680
|
-
html += '</tbody></table>'
|
|
2681
|
-
setInner('ss-dash-jobs-body', html)
|
|
2682
|
-
renderPagination('jobs', ps)
|
|
2683
|
-
|
|
2684
|
-
// Retry buttons
|
|
2685
|
-
var body = document.getElementById('ss-dash-jobs-body')
|
|
2686
|
-
if (body) {
|
|
2687
|
-
body.querySelectorAll('.ss-dash-retry-btn').forEach(function (btn) {
|
|
2688
|
-
btn.addEventListener('click', function (e) {
|
|
2689
|
-
e.stopPropagation()
|
|
2690
|
-
var id = btn.getAttribute('data-retry-id')
|
|
2691
|
-
btn.textContent = '...'
|
|
2692
|
-
btn.disabled = true
|
|
2693
|
-
fetch(API + '/jobs/' + id + '/retry', { method: 'POST', credentials: 'same-origin' })
|
|
2694
|
-
.then(function () {
|
|
2695
|
-
btn.textContent = 'OK'
|
|
2696
|
-
setTimeout(fetchJobs, 1000)
|
|
2697
|
-
})
|
|
2698
|
-
.catch(function () {
|
|
2699
|
-
btn.textContent = 'Retry'
|
|
2700
|
-
btn.disabled = false
|
|
2701
|
-
})
|
|
2702
|
-
})
|
|
2703
|
-
})
|
|
2704
|
-
}
|
|
2705
|
-
}
|
|
2706
|
-
|
|
2707
|
-
// Job status filter buttons
|
|
2708
|
-
root.querySelectorAll('[data-ss-job-status]').forEach(function (btn) {
|
|
2709
|
-
btn.addEventListener('click', function () {
|
|
2710
|
-
root.querySelectorAll('[data-ss-job-status]').forEach(function (b) {
|
|
2711
|
-
b.classList.remove('ss-dash-active')
|
|
2712
|
-
})
|
|
2713
|
-
btn.classList.add('ss-dash-active')
|
|
2714
|
-
jobStatusFilter = btn.getAttribute('data-ss-job-status')
|
|
2715
|
-
getPage('jobs').page = 1
|
|
2716
|
-
fetchJobs()
|
|
2717
|
-
})
|
|
2718
|
-
})
|
|
2719
|
-
|
|
2720
|
-
// ── Config ────────────────────────────────────────────────────
|
|
2721
|
-
// ── Config: state ─────────────────────────────────────────────
|
|
2722
|
-
var configRawData = null
|
|
2723
|
-
var configActiveTab = 'config'
|
|
2724
|
-
var configSearchTerm = ''
|
|
2725
|
-
|
|
2726
|
-
/** Check if a value is a redacted marker object. */
|
|
2727
|
-
var isRedactedObj = function (val) {
|
|
2728
|
-
return val && typeof val === 'object' && val.__redacted === true
|
|
2729
|
-
}
|
|
2730
|
-
|
|
2731
|
-
/** Render a redacted value with reveal/copy buttons. */
|
|
2732
|
-
var renderRedacted = function (val, prefix) {
|
|
2733
|
-
var cls = prefix + '-config-redacted'
|
|
2734
|
-
var realVal = esc(val.value || '')
|
|
2735
|
-
return (
|
|
2736
|
-
'<span class="' +
|
|
2737
|
-
cls +
|
|
2738
|
-
' ' +
|
|
2739
|
-
prefix +
|
|
2740
|
-
'-redacted-wrap" data-redacted-value="' +
|
|
2741
|
-
realVal +
|
|
2742
|
-
'">' +
|
|
2743
|
-
'<span class="' +
|
|
2744
|
-
prefix +
|
|
2745
|
-
'-redacted-display">' +
|
|
2746
|
-
esc(val.display) +
|
|
2747
|
-
'</span>' +
|
|
2748
|
-
'<span class="' +
|
|
2749
|
-
prefix +
|
|
2750
|
-
'-redacted-real" style="display:none">' +
|
|
2751
|
-
realVal +
|
|
2752
|
-
'</span>' +
|
|
2753
|
-
'<button type="button" class="' +
|
|
2754
|
-
prefix +
|
|
2755
|
-
'-redacted-reveal" title="Reveal value">' +
|
|
2756
|
-
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>' +
|
|
2757
|
-
'</button>' +
|
|
2758
|
-
'<button type="button" class="' +
|
|
2759
|
-
prefix +
|
|
2760
|
-
'-redacted-copy" title="Copy value">' +
|
|
2761
|
-
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>' +
|
|
2762
|
-
'</button>' +
|
|
2763
|
-
'</span>'
|
|
2764
|
-
)
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
/** Bind reveal/copy click handlers inside a container. */
|
|
2768
|
-
var bindRedactedButtons = function (container, prefix) {
|
|
2769
|
-
container.querySelectorAll('.' + prefix + '-redacted-reveal').forEach(function (btn) {
|
|
2770
|
-
btn.addEventListener('click', function (e) {
|
|
2771
|
-
e.stopPropagation()
|
|
2772
|
-
var wrap = btn.closest('.' + prefix + '-redacted-wrap')
|
|
2773
|
-
if (!wrap) return
|
|
2774
|
-
var display = wrap.querySelector('.' + prefix + '-redacted-display')
|
|
2775
|
-
var real = wrap.querySelector('.' + prefix + '-redacted-real')
|
|
2776
|
-
if (!display || !real) return
|
|
2777
|
-
var isHidden = real.style.display === 'none'
|
|
2778
|
-
display.style.display = isHidden ? 'none' : ''
|
|
2779
|
-
real.style.display = isHidden ? '' : 'none'
|
|
2780
|
-
// Toggle icon between eye and eye-off
|
|
2781
|
-
btn.innerHTML = isHidden
|
|
2782
|
-
? '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>'
|
|
2783
|
-
: '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>'
|
|
2784
|
-
btn.title = isHidden ? 'Hide value' : 'Reveal value'
|
|
2785
|
-
})
|
|
2786
|
-
})
|
|
2787
|
-
|
|
2788
|
-
container.querySelectorAll('.' + prefix + '-redacted-copy').forEach(function (btn) {
|
|
2789
|
-
btn.addEventListener('click', function (e) {
|
|
2790
|
-
e.stopPropagation()
|
|
2791
|
-
var wrap = btn.closest('.' + prefix + '-redacted-wrap')
|
|
2792
|
-
if (!wrap) return
|
|
2793
|
-
var val = wrap.getAttribute('data-redacted-value')
|
|
2794
|
-
if (!val) return
|
|
2795
|
-
navigator.clipboard.writeText(val).then(function () {
|
|
2796
|
-
btn.innerHTML = '\u2713'
|
|
2797
|
-
btn.classList.add(prefix + '-copy-row-ok')
|
|
2798
|
-
setTimeout(function () {
|
|
2799
|
-
btn.innerHTML =
|
|
2800
|
-
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>'
|
|
2801
|
-
btn.classList.remove(prefix + '-copy-row-ok')
|
|
2802
|
-
}, 1200)
|
|
2803
|
-
})
|
|
2804
|
-
})
|
|
2805
|
-
})
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
|
-
var fetchConfig = function () {
|
|
2809
|
-
fetchJSON(API + '/config')
|
|
2810
|
-
.then(function (data) {
|
|
2811
|
-
sectionLoaded.config = true
|
|
2812
|
-
configRawData = data
|
|
2813
|
-
renderConfig()
|
|
2814
|
-
})
|
|
2815
|
-
.catch(function () {
|
|
2816
|
-
setInner('ss-dash-config-body', '<div class="ss-dash-empty">Config not available</div>')
|
|
2817
|
-
})
|
|
2818
|
-
}
|
|
2819
|
-
|
|
2820
|
-
var renderConfig = function () {
|
|
2821
|
-
var body = document.getElementById('ss-dash-config-body')
|
|
2822
|
-
if (!body || !configRawData) return
|
|
2823
|
-
|
|
2824
|
-
var source =
|
|
2825
|
-
configActiveTab === 'env' ? configRawData.env || {} : configRawData.config || configRawData
|
|
2826
|
-
|
|
2827
|
-
// Flatten to dot-notation paths for search
|
|
2828
|
-
var flat = flattenConfig(source, '')
|
|
2829
|
-
var filtered = flat
|
|
2830
|
-
var term = configSearchTerm.toLowerCase()
|
|
2831
|
-
if (term) {
|
|
2832
|
-
filtered = flat.filter(function (item) {
|
|
2833
|
-
var valStr = isRedactedObj(item.value) ? item.value.display : String(item.value)
|
|
2834
|
-
return (
|
|
2835
|
-
item.path.toLowerCase().indexOf(term) !== -1 || valStr.toLowerCase().indexOf(term) !== -1
|
|
2836
|
-
)
|
|
2837
|
-
})
|
|
2838
|
-
}
|
|
2839
|
-
|
|
2840
|
-
var html = ''
|
|
2841
|
-
|
|
2842
|
-
if (configActiveTab === 'env') {
|
|
2843
|
-
// Env vars: simple table
|
|
2844
|
-
html +=
|
|
2845
|
-
'<div class="ss-dash-config-table-wrap"><table class="ss-dash-table ss-dash-config-env-table"><thead><tr>' +
|
|
2846
|
-
'<th>Variable</th><th>Value</th><th style="width:36px"></th>' +
|
|
2847
|
-
'</tr></thead><tbody>'
|
|
2848
|
-
filtered.forEach(function (item) {
|
|
2849
|
-
var redacted = isRedactedObj(item.value)
|
|
2850
|
-
var displayVal = redacted ? item.value.display : String(item.value)
|
|
2851
|
-
var copyVal = esc(item.path + '=' + displayVal)
|
|
2852
|
-
html +=
|
|
2853
|
-
'<tr>' +
|
|
2854
|
-
'<td class="ss-dash-env-key"><span class="ss-dash-config-key">' +
|
|
2855
|
-
highlightMatch(esc(item.path), term) +
|
|
2856
|
-
'</span></td>' +
|
|
2857
|
-
'<td class="ss-dash-env-val">' +
|
|
2858
|
-
(redacted
|
|
2859
|
-
? renderRedacted(item.value, 'ss-dash')
|
|
2860
|
-
: '<span class="ss-dash-config-val">' +
|
|
2861
|
-
highlightMatch(esc(displayVal), term) +
|
|
2862
|
-
'</span>') +
|
|
2863
|
-
'</td>' +
|
|
2864
|
-
'<td>' +
|
|
2865
|
-
(redacted
|
|
2866
|
-
? ''
|
|
2867
|
-
: '<button class="ss-dash-copy-row-btn" data-copy-val="' +
|
|
2868
|
-
copyVal +
|
|
2869
|
-
'" title="Copy">\u2398</button>') +
|
|
2870
|
-
'</td>' +
|
|
2871
|
-
'</tr>'
|
|
2872
|
-
})
|
|
2873
|
-
html += '</tbody></table></div>'
|
|
2874
|
-
} else {
|
|
2875
|
-
// App config: grouped by top-level section
|
|
2876
|
-
if (term) {
|
|
2877
|
-
// Search mode: flat list of matching paths
|
|
2878
|
-
html +=
|
|
2879
|
-
'<div class="ss-dash-config-table-wrap"><table class="ss-dash-table"><thead><tr>' +
|
|
2880
|
-
'<th>Path</th><th>Value</th><th style="width:36px"></th>' +
|
|
2881
|
-
'</tr></thead><tbody>'
|
|
2882
|
-
filtered.forEach(function (item) {
|
|
2883
|
-
var redacted = isRedactedObj(item.value)
|
|
2884
|
-
var displayVal = redacted ? item.value.display : String(item.value)
|
|
2885
|
-
var copyVal = esc(item.path + ': ' + displayVal)
|
|
2886
|
-
html +=
|
|
2887
|
-
'<tr>' +
|
|
2888
|
-
'<td><span class="ss-dash-config-key" style="white-space:nowrap">' +
|
|
2889
|
-
highlightMatch(esc(item.path), term) +
|
|
2890
|
-
'</span></td>' +
|
|
2891
|
-
'<td>' +
|
|
2892
|
-
(redacted
|
|
2893
|
-
? renderRedacted(item.value, 'ss-dash')
|
|
2894
|
-
: '<span class="ss-dash-config-val" style="word-break:break-all">' +
|
|
2895
|
-
highlightMatch(esc(displayVal), term) +
|
|
2896
|
-
'</span>') +
|
|
2897
|
-
'</td>' +
|
|
2898
|
-
'<td>' +
|
|
2899
|
-
(redacted
|
|
2900
|
-
? ''
|
|
2901
|
-
: '<button class="ss-dash-copy-row-btn" data-copy-val="' +
|
|
2902
|
-
copyVal +
|
|
2903
|
-
'" title="Copy">\u2398</button>') +
|
|
2904
|
-
'</td>' +
|
|
2905
|
-
'</tr>'
|
|
2906
|
-
})
|
|
2907
|
-
html += '</tbody></table></div>'
|
|
2908
|
-
html +=
|
|
2909
|
-
'<div style="padding:4px 16px;font-size:10px;color:var(--ss-muted)">' +
|
|
2910
|
-
filtered.length +
|
|
2911
|
-
' of ' +
|
|
2912
|
-
flat.length +
|
|
2913
|
-
' entries</div>'
|
|
2914
|
-
} else {
|
|
2915
|
-
// Browse mode: collapsible sections by top-level key
|
|
2916
|
-
var topKeys = Object.keys(source)
|
|
2917
|
-
html += '<div class="ss-dash-config-sections">'
|
|
2918
|
-
topKeys.forEach(function (sectionKey) {
|
|
2919
|
-
var sectionVal = source[sectionKey]
|
|
2920
|
-
var childCount = countLeaves(sectionVal)
|
|
2921
|
-
var isObj =
|
|
2922
|
-
typeof sectionVal === 'object' && sectionVal !== null && !sectionVal.__redacted
|
|
2923
|
-
|
|
2924
|
-
html += '<div class="ss-dash-config-section">'
|
|
2925
|
-
if (isObj) {
|
|
2926
|
-
html +=
|
|
2927
|
-
'<div class="ss-dash-config-section-header" data-config-section="' +
|
|
2928
|
-
esc(sectionKey) +
|
|
2929
|
-
'">' +
|
|
2930
|
-
'<span class="ss-dash-config-toggle">\u25B6</span>' +
|
|
2931
|
-
'<span class="ss-dash-config-key">' +
|
|
2932
|
-
esc(sectionKey) +
|
|
2933
|
-
'</span>' +
|
|
2934
|
-
'<span class="ss-dash-config-count">' +
|
|
2935
|
-
childCount +
|
|
2936
|
-
' entries</span>' +
|
|
2937
|
-
'</div>'
|
|
2938
|
-
html += '<div class="ss-dash-config-section-body" style="display:none">'
|
|
2939
|
-
html += renderConfigTable(sectionVal, sectionKey)
|
|
2940
|
-
html += '</div>'
|
|
2941
|
-
} else {
|
|
2942
|
-
html +=
|
|
2943
|
-
'<div class="ss-dash-config-section-header ss-dash-config-leaf">' +
|
|
2944
|
-
'<span class="ss-dash-config-key">' +
|
|
2945
|
-
esc(sectionKey) +
|
|
2946
|
-
'</span>' +
|
|
2947
|
-
'<span class="ss-dash-config-val" style="margin-left:8px">' +
|
|
2948
|
-
esc(String(sectionVal)) +
|
|
2949
|
-
'</span>' +
|
|
2950
|
-
'</div>'
|
|
2951
|
-
}
|
|
2952
|
-
html += '</div>'
|
|
2953
|
-
})
|
|
2954
|
-
html += '</div>'
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
|
|
2958
|
-
body.innerHTML = html
|
|
2959
|
-
|
|
2960
|
-
// Bind section toggles
|
|
2961
|
-
body.querySelectorAll('[data-config-section]').forEach(function (header) {
|
|
2962
|
-
header.addEventListener('click', function () {
|
|
2963
|
-
var sectionBody = header.nextElementSibling
|
|
2964
|
-
if (!sectionBody) return
|
|
2965
|
-
var isHidden = sectionBody.style.display === 'none'
|
|
2966
|
-
sectionBody.style.display = isHidden ? '' : 'none'
|
|
2967
|
-
var toggle = header.querySelector('.ss-dash-config-toggle')
|
|
2968
|
-
if (toggle) toggle.textContent = isHidden ? '\u25BC' : '\u25B6'
|
|
2969
|
-
})
|
|
2970
|
-
})
|
|
2971
|
-
|
|
2972
|
-
// Bind row copy buttons
|
|
2973
|
-
body.querySelectorAll('.ss-dash-copy-row-btn').forEach(function (btn) {
|
|
2974
|
-
btn.addEventListener('click', function (e) {
|
|
2975
|
-
e.stopPropagation()
|
|
2976
|
-
var val = btn.getAttribute('data-copy-val')
|
|
2977
|
-
if (!val) return
|
|
2978
|
-
navigator.clipboard.writeText(val).then(function () {
|
|
2979
|
-
btn.textContent = '\u2713'
|
|
2980
|
-
btn.classList.add('ss-dash-copy-row-ok')
|
|
2981
|
-
setTimeout(function () {
|
|
2982
|
-
btn.textContent = '\u2398'
|
|
2983
|
-
btn.classList.remove('ss-dash-copy-row-ok')
|
|
2984
|
-
}, 1200)
|
|
2985
|
-
})
|
|
2986
|
-
})
|
|
2987
|
-
})
|
|
2988
|
-
|
|
2989
|
-
// Bind redacted reveal/copy buttons
|
|
2990
|
-
bindRedactedButtons(body, 'ss-dash')
|
|
2991
|
-
}
|
|
2992
|
-
|
|
2993
|
-
/** Render a nested object as a flat key-value table. */
|
|
2994
|
-
var renderConfigTable = function (obj, prefix) {
|
|
2995
|
-
var flat = flattenConfig(obj, prefix)
|
|
2996
|
-
var html =
|
|
2997
|
-
'<table class="ss-dash-table ss-dash-config-inner-table"><thead><tr>' +
|
|
2998
|
-
'<th style="width:35%">Key</th><th>Value</th><th style="width:36px"></th>' +
|
|
2999
|
-
'</tr></thead><tbody>'
|
|
3000
|
-
flat.forEach(function (item) {
|
|
3001
|
-
// Show relative path (strip the section prefix)
|
|
3002
|
-
var relPath =
|
|
3003
|
-
item.path.indexOf(prefix + '.') === 0 ? item.path.slice(prefix.length + 1) : item.path
|
|
3004
|
-
var redacted = isRedactedObj(item.value)
|
|
3005
|
-
var displayVal = redacted ? item.value.display : String(item.value)
|
|
3006
|
-
var copyVal = esc(item.path + ': ' + displayVal)
|
|
3007
|
-
var valStr = redacted
|
|
3008
|
-
? item.value.display
|
|
3009
|
-
: typeof item.value === 'object' && item.value !== null
|
|
3010
|
-
? JSON.stringify(item.value)
|
|
3011
|
-
: String(item.value)
|
|
3012
|
-
html +=
|
|
3013
|
-
'<tr>' +
|
|
3014
|
-
'<td title="' +
|
|
3015
|
-
esc(relPath) +
|
|
3016
|
-
'"><span class="ss-dash-config-key">' +
|
|
3017
|
-
esc(relPath) +
|
|
3018
|
-
'</span></td>' +
|
|
3019
|
-
'<td title="' +
|
|
3020
|
-
esc(valStr) +
|
|
3021
|
-
'">' +
|
|
3022
|
-
(redacted
|
|
3023
|
-
? renderRedacted(item.value, 'ss-dash')
|
|
3024
|
-
: '<span class="ss-dash-config-val">' + formatConfigValue(item.value) + '</span>') +
|
|
3025
|
-
'</td>' +
|
|
3026
|
-
'<td>' +
|
|
3027
|
-
(redacted
|
|
3028
|
-
? ''
|
|
3029
|
-
: '<button class="ss-dash-copy-row-btn" data-copy-val="' +
|
|
3030
|
-
copyVal +
|
|
3031
|
-
'" title="Copy">\u2398</button>') +
|
|
3032
|
-
'</td>' +
|
|
3033
|
-
'</tr>'
|
|
3034
|
-
})
|
|
3035
|
-
html += '</tbody></table>'
|
|
3036
|
-
return html
|
|
3037
|
-
}
|
|
3038
|
-
|
|
3039
|
-
/** Flatten a nested object into [{path, value}] dot-notation entries. */
|
|
3040
|
-
var flattenConfig = function (obj, prefix) {
|
|
3041
|
-
var results = []
|
|
3042
|
-
if (typeof obj !== 'object' || obj === null) {
|
|
3043
|
-
results.push({ path: prefix, value: obj })
|
|
3044
|
-
return results
|
|
3045
|
-
}
|
|
3046
|
-
var keys = Object.keys(obj)
|
|
3047
|
-
keys.forEach(function (key) {
|
|
3048
|
-
var fullPath = prefix ? prefix + '.' + key : key
|
|
3049
|
-
var val = obj[key]
|
|
3050
|
-
if (typeof val === 'object' && val !== null && !Array.isArray(val) && !val.__redacted) {
|
|
3051
|
-
results = results.concat(flattenConfig(val, fullPath))
|
|
3052
|
-
} else {
|
|
3053
|
-
results.push({ path: fullPath, value: val })
|
|
3054
|
-
}
|
|
3055
|
-
})
|
|
3056
|
-
return results
|
|
3057
|
-
}
|
|
3058
|
-
|
|
3059
|
-
/** Count leaf values in a nested object. */
|
|
3060
|
-
var countLeaves = function (obj) {
|
|
3061
|
-
if (typeof obj !== 'object' || obj === null || obj.__redacted) return 1
|
|
3062
|
-
var count = 0
|
|
3063
|
-
Object.keys(obj).forEach(function (k) {
|
|
3064
|
-
count += countLeaves(obj[k])
|
|
3065
|
-
})
|
|
3066
|
-
return count
|
|
3067
|
-
}
|
|
3068
|
-
|
|
3069
|
-
/** Format a config value with type-aware coloring. */
|
|
3070
|
-
var formatConfigValue = function (val) {
|
|
3071
|
-
if (val === null || val === undefined) return '<span style="color:var(--ss-dim)">null</span>'
|
|
3072
|
-
if (val === true) return '<span style="color:var(--ss-green-fg)">true</span>'
|
|
3073
|
-
if (val === false) return '<span style="color:var(--ss-red-fg)">false</span>'
|
|
3074
|
-
if (typeof val === 'number') return '<span style="color:var(--ss-amber-fg)">' + val + '</span>'
|
|
3075
|
-
if (Array.isArray(val)) {
|
|
3076
|
-
var items = val.map(function (item) {
|
|
3077
|
-
if (item === null || item === undefined) return 'null'
|
|
3078
|
-
if (typeof item === 'object') {
|
|
3079
|
-
try {
|
|
3080
|
-
return JSON.stringify(item)
|
|
3081
|
-
} catch (e) {
|
|
3082
|
-
return String(item)
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
return String(item)
|
|
3086
|
-
})
|
|
3087
|
-
return '<span style="color:var(--ss-purple-fg)">[' + esc(items.join(', ')) + ']</span>'
|
|
3088
|
-
}
|
|
3089
|
-
if (typeof val === 'object') {
|
|
3090
|
-
try {
|
|
3091
|
-
return '<span style="color:var(--ss-dim)">' + esc(JSON.stringify(val, null, 2)) + '</span>'
|
|
3092
|
-
} catch (e) {
|
|
3093
|
-
/* fall through */
|
|
3094
|
-
}
|
|
3095
|
-
}
|
|
3096
|
-
return esc(String(val))
|
|
3097
|
-
}
|
|
3098
|
-
|
|
3099
|
-
/** Highlight matching substring in text. */
|
|
3100
|
-
var highlightMatch = function (text, term) {
|
|
3101
|
-
if (!term) return text
|
|
3102
|
-
var idx = text.toLowerCase().indexOf(term.toLowerCase())
|
|
3103
|
-
if (idx === -1) return text
|
|
3104
|
-
return (
|
|
3105
|
-
text.slice(0, idx) +
|
|
3106
|
-
'<mark class="ss-dash-config-match">' +
|
|
3107
|
-
text.slice(idx, idx + term.length) +
|
|
3108
|
-
'</mark>' +
|
|
3109
|
-
text.slice(idx + term.length)
|
|
3110
|
-
)
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
// ── Config: tab switching ───────────────────────────────────────
|
|
3114
|
-
document.querySelectorAll('[data-config-tab]').forEach(function (btn) {
|
|
3115
|
-
btn.addEventListener('click', function () {
|
|
3116
|
-
configActiveTab = btn.getAttribute('data-config-tab')
|
|
3117
|
-
document.querySelectorAll('[data-config-tab]').forEach(function (b) {
|
|
3118
|
-
b.classList.remove('ss-dash-active')
|
|
3119
|
-
})
|
|
3120
|
-
btn.classList.add('ss-dash-active')
|
|
3121
|
-
renderConfig()
|
|
3122
|
-
})
|
|
3123
|
-
})
|
|
3124
|
-
|
|
3125
|
-
// ── Config: search ──────────────────────────────────────────────
|
|
3126
|
-
var configSearchEl = document.getElementById('ss-dash-config-search')
|
|
3127
|
-
if (configSearchEl) {
|
|
3128
|
-
var configSearchTimer = null
|
|
3129
|
-
configSearchEl.addEventListener('input', function () {
|
|
3130
|
-
clearTimeout(configSearchTimer)
|
|
3131
|
-
configSearchTimer = setTimeout(function () {
|
|
3132
|
-
configSearchTerm = configSearchEl.value.trim()
|
|
3133
|
-
renderConfig()
|
|
3134
|
-
}, 200)
|
|
3135
|
-
})
|
|
3136
|
-
}
|
|
3137
|
-
|
|
3138
|
-
// ── Config: expand/collapse all ─────────────────────────────────
|
|
3139
|
-
var expandAllBtn = document.getElementById('ss-dash-config-expand-all')
|
|
3140
|
-
var collapseAllBtn = document.getElementById('ss-dash-config-collapse-all')
|
|
3141
|
-
if (expandAllBtn) {
|
|
3142
|
-
expandAllBtn.addEventListener('click', function () {
|
|
3143
|
-
var body = document.getElementById('ss-dash-config-body')
|
|
3144
|
-
if (!body) return
|
|
3145
|
-
body.querySelectorAll('.ss-dash-config-section-body').forEach(function (el) {
|
|
3146
|
-
el.style.display = ''
|
|
3147
|
-
})
|
|
3148
|
-
body.querySelectorAll('.ss-dash-config-toggle').forEach(function (el) {
|
|
3149
|
-
el.textContent = '\u25BC'
|
|
3150
|
-
})
|
|
3151
|
-
})
|
|
3152
|
-
}
|
|
3153
|
-
if (collapseAllBtn) {
|
|
3154
|
-
collapseAllBtn.addEventListener('click', function () {
|
|
3155
|
-
var body = document.getElementById('ss-dash-config-body')
|
|
3156
|
-
if (!body) return
|
|
3157
|
-
body.querySelectorAll('.ss-dash-config-section-body').forEach(function (el) {
|
|
3158
|
-
el.style.display = 'none'
|
|
3159
|
-
})
|
|
3160
|
-
body.querySelectorAll('.ss-dash-config-toggle').forEach(function (el) {
|
|
3161
|
-
el.textContent = '\u25B6'
|
|
3162
|
-
})
|
|
3163
|
-
})
|
|
3164
|
-
}
|
|
3165
|
-
|
|
3166
|
-
// ── Config: copy button ─────────────────────────────────────────
|
|
3167
|
-
var configCopyBtn = document.getElementById('ss-dash-config-copy')
|
|
3168
|
-
if (configCopyBtn) {
|
|
3169
|
-
configCopyBtn.addEventListener('click', function () {
|
|
3170
|
-
if (!configRawData) return
|
|
3171
|
-
var source =
|
|
3172
|
-
configActiveTab === 'env' ? configRawData.env || {} : configRawData.config || configRawData
|
|
3173
|
-
navigator.clipboard.writeText(JSON.stringify(source, null, 2)).then(function () {
|
|
3174
|
-
configCopyBtn.textContent = 'Copied!'
|
|
3175
|
-
setTimeout(function () {
|
|
3176
|
-
configCopyBtn.textContent = 'Copy JSON'
|
|
3177
|
-
}, 1500)
|
|
3178
|
-
})
|
|
3179
|
-
})
|
|
3180
|
-
}
|
|
3181
|
-
|
|
3182
|
-
// ── Custom Panes ──────────────────────────────────────────────
|
|
3183
|
-
var customPaneState = {}
|
|
3184
|
-
customPanes.forEach(function (cp) {
|
|
3185
|
-
customPaneState[cp.id] = { data: [], fetched: false, filter: '' }
|
|
3186
|
-
})
|
|
3187
|
-
|
|
3188
|
-
var getNestedValue = function (obj, path) {
|
|
3189
|
-
var parts = path.split('.')
|
|
3190
|
-
var cur = obj
|
|
3191
|
-
for (var i = 0; i < parts.length; i++) {
|
|
3192
|
-
if (cur == null) return undefined
|
|
3193
|
-
cur = cur[parts[i]]
|
|
3194
|
-
}
|
|
3195
|
-
return cur
|
|
3196
|
-
}
|
|
3197
|
-
|
|
3198
|
-
var fetchCustomPane = function (pane) {
|
|
3199
|
-
fetchJSON(pane.endpoint)
|
|
3200
|
-
.then(function (data) {
|
|
3201
|
-
var key = pane.dataKey || pane.id
|
|
3202
|
-
var rows = getNestedValue(data, key) || (Array.isArray(data) ? data : [])
|
|
3203
|
-
customPaneState[pane.id].data = rows
|
|
3204
|
-
customPaneState[pane.id].fetched = true
|
|
3205
|
-
renderCustomPane(pane)
|
|
3206
|
-
})
|
|
3207
|
-
.catch(function () {
|
|
3208
|
-
setInner(
|
|
3209
|
-
'ss-dash-' + pane.id + '-body',
|
|
3210
|
-
'<div class="ss-dash-empty">Failed to load ' + esc(pane.label) + '</div>'
|
|
3211
|
-
)
|
|
3212
|
-
})
|
|
3213
|
-
}
|
|
3214
|
-
|
|
3215
|
-
var renderCustomPane = function (pane) {
|
|
3216
|
-
var state = customPaneState[pane.id]
|
|
3217
|
-
if (!state) return
|
|
3218
|
-
var bodyEl = document.getElementById('ss-dash-' + pane.id + '-body')
|
|
3219
|
-
var summaryEl = document.getElementById('ss-dash-' + pane.id + '-summary')
|
|
3220
|
-
if (!bodyEl) return
|
|
3221
|
-
|
|
3222
|
-
var filter = state.filter.toLowerCase()
|
|
3223
|
-
var rows = state.data
|
|
3224
|
-
|
|
3225
|
-
if (summaryEl) summaryEl.textContent = rows.length + ' ' + pane.label.toLowerCase()
|
|
3226
|
-
|
|
3227
|
-
if (filter) {
|
|
3228
|
-
var searchCols = pane.columns.filter(function (c) {
|
|
3229
|
-
return c.searchable
|
|
3230
|
-
})
|
|
3231
|
-
if (searchCols.length > 0) {
|
|
3232
|
-
rows = rows.filter(function (row) {
|
|
3233
|
-
return searchCols.some(function (c) {
|
|
3234
|
-
var v = row[c.key]
|
|
3235
|
-
return v != null && String(v).toLowerCase().indexOf(filter) !== -1
|
|
3236
|
-
})
|
|
3237
|
-
})
|
|
3238
|
-
}
|
|
3239
|
-
}
|
|
3240
|
-
|
|
3241
|
-
if (rows.length === 0) {
|
|
3242
|
-
bodyEl.innerHTML =
|
|
3243
|
-
'<div class="ss-dash-empty">' +
|
|
3244
|
-
(filter
|
|
3245
|
-
? 'No matching ' + esc(pane.label.toLowerCase())
|
|
3246
|
-
: 'No ' + esc(pane.label.toLowerCase()) + ' recorded yet') +
|
|
3247
|
-
'</div>'
|
|
3248
|
-
return
|
|
3249
|
-
}
|
|
3250
|
-
|
|
3251
|
-
var html = '<table class="ss-dash-table"><thead><tr>'
|
|
3252
|
-
pane.columns.forEach(function (col) {
|
|
3253
|
-
html +=
|
|
3254
|
-
'<th' +
|
|
3255
|
-
(col.width ? ' style="width:' + col.width + '"' : '') +
|
|
3256
|
-
'>' +
|
|
3257
|
-
esc(col.label) +
|
|
3258
|
-
'</th>'
|
|
3259
|
-
})
|
|
3260
|
-
html += '</tr></thead><tbody>'
|
|
3261
|
-
|
|
3262
|
-
rows.forEach(function (row) {
|
|
3263
|
-
html += '<tr>'
|
|
3264
|
-
pane.columns.forEach(function (col) {
|
|
3265
|
-
var val = row[col.key]
|
|
3266
|
-
html += '<td>' + formatCell(val, col) + '</td>'
|
|
3267
|
-
})
|
|
3268
|
-
html += '</tr>'
|
|
3269
|
-
})
|
|
3270
|
-
|
|
3271
|
-
html += '</tbody></table>'
|
|
3272
|
-
bodyEl.innerHTML = html
|
|
3273
|
-
}
|
|
3274
|
-
|
|
3275
|
-
// Bind search/clear for custom panes
|
|
3276
|
-
customPanes.forEach(function (cp) {
|
|
3277
|
-
var searchInput = document.getElementById('ss-dash-search-' + cp.id)
|
|
3278
|
-
var clearBtn = document.getElementById('ss-dash-' + cp.id + '-clear')
|
|
3279
|
-
if (searchInput) {
|
|
3280
|
-
searchInput.addEventListener('input', function () {
|
|
3281
|
-
customPaneState[cp.id].filter = searchInput.value
|
|
3282
|
-
renderCustomPane(cp)
|
|
3283
|
-
})
|
|
3284
|
-
}
|
|
3285
|
-
if (clearBtn) {
|
|
3286
|
-
clearBtn.addEventListener('click', function () {
|
|
3287
|
-
customPaneState[cp.id].data = []
|
|
3288
|
-
customPaneState[cp.id].fetched = false
|
|
3289
|
-
if (searchInput) searchInput.value = ''
|
|
3290
|
-
customPaneState[cp.id].filter = ''
|
|
3291
|
-
renderCustomPane(cp)
|
|
3292
|
-
})
|
|
3293
|
-
}
|
|
3294
|
-
})
|
|
3295
|
-
|
|
3296
|
-
// ── Pagination ────────────────────────────────────────────────
|
|
3297
|
-
var renderPagination = function (section, ps) {
|
|
3298
|
-
var el = document.getElementById('ss-dash-pagination-' + section)
|
|
3299
|
-
if (!el) return
|
|
3300
|
-
|
|
3301
|
-
var totalPages = Math.ceil(ps.total / PER_PAGE) || 1
|
|
3302
|
-
if (totalPages <= 1) {
|
|
3303
|
-
el.innerHTML = ''
|
|
3304
|
-
return
|
|
3305
|
-
}
|
|
3306
|
-
|
|
3307
|
-
var html =
|
|
3308
|
-
'<button class="ss-dash-page-btn" data-page="prev" ' +
|
|
3309
|
-
(ps.page <= 1 ? 'disabled' : '') +
|
|
3310
|
-
'>« Prev</button>'
|
|
3311
|
-
html += '<span class="ss-dash-page-info">Page ' + ps.page + ' of ' + totalPages + '</span>'
|
|
3312
|
-
html +=
|
|
3313
|
-
'<button class="ss-dash-page-btn" data-page="next" ' +
|
|
3314
|
-
(ps.page >= totalPages ? 'disabled' : '') +
|
|
3315
|
-
'>Next »</button>'
|
|
3316
|
-
|
|
3317
|
-
el.innerHTML = html
|
|
3318
|
-
el.querySelectorAll('.ss-dash-page-btn').forEach(function (btn) {
|
|
3319
|
-
btn.addEventListener('click', function () {
|
|
3320
|
-
var dir = btn.getAttribute('data-page')
|
|
3321
|
-
if (dir === 'prev' && ps.page > 1) ps.page--
|
|
3322
|
-
else if (dir === 'next' && ps.page < totalPages) ps.page++
|
|
3323
|
-
loadSection(section)
|
|
3324
|
-
})
|
|
3325
|
-
})
|
|
3326
|
-
}
|
|
3327
|
-
|
|
3328
|
-
// ── Badge updates ─────────────────────────────────────────────
|
|
3329
|
-
var updateBadge = function (section, count) {
|
|
3330
|
-
var badge = root.querySelector('[data-ss-section="' + section + '"] .ss-dash-nav-badge')
|
|
3331
|
-
if (badge && count != null) badge.textContent = count
|
|
3332
|
-
}
|
|
3333
|
-
|
|
3334
|
-
// ── DOM helpers ───────────────────────────────────────────────
|
|
3335
|
-
var setInner = function (id, html) {
|
|
3336
|
-
var el = document.getElementById(id)
|
|
3337
|
-
if (el) el.innerHTML = html
|
|
3338
|
-
}
|
|
3339
|
-
|
|
3340
|
-
var bindDataExpand = function (containerId) {
|
|
3341
|
-
var container = document.getElementById(containerId)
|
|
3342
|
-
if (!container) return
|
|
3343
|
-
container.querySelectorAll('.ss-dash-data-preview').forEach(function (el) {
|
|
3344
|
-
el.addEventListener('click', function () {
|
|
3345
|
-
var idx = el.getAttribute('data-ev-idx')
|
|
3346
|
-
var pre = document.getElementById('ss-dash-evdata-' + idx)
|
|
3347
|
-
if (pre) {
|
|
3348
|
-
var open = pre.style.display !== 'none'
|
|
3349
|
-
pre.style.display = open ? 'none' : 'block'
|
|
3350
|
-
el.style.display = open ? '' : 'none'
|
|
3351
|
-
}
|
|
3352
|
-
})
|
|
3353
|
-
})
|
|
3354
|
-
container.querySelectorAll('.ss-dash-data-full').forEach(function (el) {
|
|
3355
|
-
el.addEventListener('click', function () {
|
|
3356
|
-
el.style.display = 'none'
|
|
3357
|
-
var idx = el.id.replace('ss-dash-evdata-', '')
|
|
3358
|
-
var preview = container.querySelector('[data-ev-idx="' + idx + '"]')
|
|
3359
|
-
if (preview) preview.style.display = ''
|
|
3360
|
-
})
|
|
3361
|
-
})
|
|
3362
|
-
}
|
|
3363
|
-
|
|
3364
|
-
// ── Auto-refresh ──────────────────────────────────────────────
|
|
3365
|
-
var startRefresh = function () {
|
|
3366
|
-
stopRefresh()
|
|
3367
|
-
if (isLive) return // Transmit handles live updates
|
|
3368
|
-
refreshTimer = setInterval(
|
|
3369
|
-
function () {
|
|
3370
|
-
loadSection(activeSection)
|
|
3371
|
-
},
|
|
3372
|
-
activeSection === 'overview' ? 5000 : 3000
|
|
3373
|
-
)
|
|
3374
|
-
}
|
|
3375
|
-
|
|
3376
|
-
var stopRefresh = function () {
|
|
3377
|
-
if (refreshTimer) {
|
|
3378
|
-
clearInterval(refreshTimer)
|
|
3379
|
-
refreshTimer = null
|
|
3380
|
-
}
|
|
3381
|
-
}
|
|
3382
|
-
|
|
3383
|
-
// ── Transmit real-time ────────────────────────────────────────
|
|
3384
|
-
var ssLog = function (msg, data) {
|
|
3385
|
-
var prefix = '%c[server-stats]%c '
|
|
3386
|
-
if (data !== undefined) {
|
|
3387
|
-
console.log(prefix + msg, 'color:#34d399;font-weight:bold', 'color:inherit', data)
|
|
3388
|
-
} else {
|
|
3389
|
-
console.log(prefix + msg, 'color:#34d399;font-weight:bold', 'color:inherit')
|
|
3390
|
-
}
|
|
3391
|
-
}
|
|
3392
|
-
var ssWarn = function (msg, data) {
|
|
3393
|
-
var prefix = '[server-stats] '
|
|
3394
|
-
if (data !== undefined) {
|
|
3395
|
-
console.warn(prefix + msg, data)
|
|
3396
|
-
} else {
|
|
3397
|
-
console.warn(prefix + msg)
|
|
3398
|
-
}
|
|
3399
|
-
}
|
|
3400
|
-
|
|
3401
|
-
var setConnectionStatus = function (status) {
|
|
3402
|
-
var dot = document.getElementById('ss-dash-live-dot')
|
|
3403
|
-
var label = document.getElementById('ss-dash-live-label')
|
|
3404
|
-
if (status === 'live') {
|
|
3405
|
-
isLive = true
|
|
3406
|
-
if (dot) dot.classList.add('ss-dash-connected')
|
|
3407
|
-
if (label) {
|
|
3408
|
-
label.textContent = 'Live'
|
|
3409
|
-
label.classList.add('ss-dash-connected')
|
|
3410
|
-
}
|
|
3411
|
-
stopRefresh()
|
|
3412
|
-
} else {
|
|
3413
|
-
isLive = false
|
|
3414
|
-
if (dot) dot.classList.remove('ss-dash-connected')
|
|
3415
|
-
if (label) {
|
|
3416
|
-
label.textContent = 'Polling'
|
|
3417
|
-
label.classList.remove('ss-dash-connected')
|
|
3418
|
-
}
|
|
3419
|
-
startRefresh()
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
|
|
3423
|
-
var initTransmit = function () {
|
|
3424
|
-
ssLog('Initializing real-time connection...')
|
|
3425
|
-
|
|
3426
|
-
if (typeof Transmit === 'undefined' && typeof window.Transmit === 'undefined') {
|
|
3427
|
-
ssWarn(
|
|
3428
|
-
'Transmit client not found. The @adonisjs/transmit-client package may not be installed. Falling back to polling.'
|
|
3429
|
-
)
|
|
3430
|
-
startRefresh()
|
|
3431
|
-
return
|
|
3432
|
-
}
|
|
3433
|
-
|
|
3434
|
-
ssLog('Transmit client found, creating subscription...')
|
|
3435
|
-
|
|
3436
|
-
try {
|
|
3437
|
-
var TransmitClass = typeof Transmit !== 'undefined' ? Transmit : window.Transmit
|
|
3438
|
-
ssLog('TransmitClass type: ' + typeof TransmitClass)
|
|
3439
|
-
|
|
3440
|
-
// onSubscription and onReconnectFailed are constructor options, NOT subscription methods
|
|
3441
|
-
var transmit = new TransmitClass({
|
|
3442
|
-
baseUrl: window.location.origin,
|
|
3443
|
-
onSubscription: function (channel) {
|
|
3444
|
-
ssLog('Subscription active on channel: ' + channel + ' — switched to live mode')
|
|
3445
|
-
setConnectionStatus('live')
|
|
3446
|
-
},
|
|
3447
|
-
onReconnectAttempt: function (attempt) {
|
|
3448
|
-
ssLog('Reconnect attempt #' + attempt)
|
|
3449
|
-
},
|
|
3450
|
-
onReconnectFailed: function () {
|
|
3451
|
-
ssWarn('Transmit reconnection failed — falling back to polling')
|
|
3452
|
-
setConnectionStatus('polling')
|
|
3453
|
-
},
|
|
3454
|
-
onSubscribeFailed: function (channel) {
|
|
3455
|
-
ssWarn('Subscribe failed for channel: ' + channel + ' — falling back to polling')
|
|
3456
|
-
setConnectionStatus('polling')
|
|
3457
|
-
},
|
|
3458
|
-
})
|
|
3459
|
-
|
|
3460
|
-
transmitSub = transmit.subscription('server-stats/dashboard')
|
|
3461
|
-
ssLog('Subscription instance created')
|
|
3462
|
-
|
|
3463
|
-
// Start polling while we wait for subscription to connect
|
|
3464
|
-
startRefresh()
|
|
3465
|
-
|
|
3466
|
-
transmitSub.onMessage(function (message) {
|
|
3467
|
-
try {
|
|
3468
|
-
var event = typeof message === 'string' ? JSON.parse(message) : message
|
|
3469
|
-
var kind = event.type || (event.avgResponseTime !== undefined ? 'overview' : 'unknown')
|
|
3470
|
-
ssLog('Live event received: ' + kind)
|
|
3471
|
-
handleLiveEvent(event)
|
|
3472
|
-
} catch (e) {
|
|
3473
|
-
/* ignore */
|
|
3474
|
-
}
|
|
3475
|
-
})
|
|
3476
|
-
|
|
3477
|
-
ssLog('Calling transmitSub.create()...')
|
|
3478
|
-
var createResult = transmitSub.create()
|
|
3479
|
-
if (createResult && typeof createResult.then === 'function') {
|
|
3480
|
-
createResult
|
|
3481
|
-
.then(function () {
|
|
3482
|
-
ssLog('transmitSub.create() resolved — subscription is active')
|
|
3483
|
-
})
|
|
3484
|
-
.catch(function (err) {
|
|
3485
|
-
ssWarn('transmitSub.create() rejected:', err && err.message ? err.message : err)
|
|
3486
|
-
setConnectionStatus('polling')
|
|
3487
|
-
})
|
|
3488
|
-
}
|
|
3489
|
-
} catch (e) {
|
|
3490
|
-
ssWarn('Transmit init error:', e && e.message ? e.message : e)
|
|
3491
|
-
startRefresh()
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
|
|
3495
|
-
var handleLiveEvent = function (event) {
|
|
3496
|
-
// Detect overview data broadcast (has avgResponseTime but no type)
|
|
3497
|
-
if (event && typeof event.avgResponseTime === 'number') {
|
|
3498
|
-
if (activeSection === 'overview') {
|
|
3499
|
-
renderOverview(event, null)
|
|
3500
|
-
}
|
|
3501
|
-
return
|
|
3502
|
-
}
|
|
3503
|
-
|
|
3504
|
-
// Typed events for specific sections
|
|
3505
|
-
var type = event.type
|
|
3506
|
-
var sectionMap = {
|
|
3507
|
-
request: 'requests',
|
|
3508
|
-
query: 'queries',
|
|
3509
|
-
event: 'events',
|
|
3510
|
-
log: 'logs',
|
|
3511
|
-
email: 'emails',
|
|
3512
|
-
trace: 'timeline',
|
|
3513
|
-
}
|
|
3514
|
-
var section = sectionMap[type]
|
|
3515
|
-
|
|
3516
|
-
if (activeSection === 'overview') {
|
|
3517
|
-
fetchOverview()
|
|
3518
|
-
return
|
|
3519
|
-
}
|
|
3520
|
-
if (section && section === activeSection) {
|
|
3521
|
-
loadSection(activeSection)
|
|
3522
|
-
}
|
|
3523
|
-
}
|
|
3524
|
-
|
|
3525
|
-
// ── Hash-based routing ────────────────────────────────────────
|
|
3526
|
-
var parseHash = function () {
|
|
3527
|
-
var hash = location.hash.replace('#', '')
|
|
3528
|
-
var parts = hash.split('?')
|
|
3529
|
-
var section = parts[0] || 'overview'
|
|
3530
|
-
var params = {}
|
|
3531
|
-
if (parts[1]) {
|
|
3532
|
-
parts[1].split('&').forEach(function (p) {
|
|
3533
|
-
var kv = p.split('=')
|
|
3534
|
-
params[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1] || '')
|
|
3535
|
-
})
|
|
3536
|
-
}
|
|
3537
|
-
return { section: section, params: params }
|
|
3538
|
-
}
|
|
3539
|
-
|
|
3540
|
-
var applyRouteParams = function (route) {
|
|
3541
|
-
var section = route.section
|
|
3542
|
-
if (route.params.requestId && section === 'logs') {
|
|
3543
|
-
logReqIdFilter = route.params.requestId
|
|
3544
|
-
var input = document.getElementById('ss-dash-log-reqid-input')
|
|
3545
|
-
if (input) input.value = logReqIdFilter
|
|
3546
|
-
}
|
|
3547
|
-
if (route.params.level && section === 'logs') {
|
|
3548
|
-
logDeepLevelFilter = route.params.level
|
|
3549
|
-
}
|
|
3550
|
-
if (route.params.url && section === 'requests') {
|
|
3551
|
-
requestUrlFilter = route.params.url
|
|
3552
|
-
}
|
|
3553
|
-
if (route.params.status && section === 'requests') {
|
|
3554
|
-
requestStatusFilter = route.params.status
|
|
3555
|
-
}
|
|
3556
|
-
if (route.params.status && section === 'emails') {
|
|
3557
|
-
emailStatusFilter = route.params.status
|
|
3558
|
-
}
|
|
3559
|
-
if (route.params.event_name && section === 'events') {
|
|
3560
|
-
eventNameFilter = route.params.event_name
|
|
3561
|
-
}
|
|
3562
|
-
if (route.params.status && section === 'jobs') {
|
|
3563
|
-
jobStatusFilter = route.params.status
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3566
|
-
|
|
3567
|
-
var initRoute = function () {
|
|
3568
|
-
var route = parseHash()
|
|
3569
|
-
var section = route.section
|
|
3570
|
-
|
|
3571
|
-
// Validate section exists
|
|
3572
|
-
var valid = [
|
|
3573
|
-
'overview',
|
|
3574
|
-
'requests',
|
|
3575
|
-
'queries',
|
|
3576
|
-
'events',
|
|
3577
|
-
'routes',
|
|
3578
|
-
'logs',
|
|
3579
|
-
'emails',
|
|
3580
|
-
'timeline',
|
|
3581
|
-
'cache',
|
|
3582
|
-
'jobs',
|
|
3583
|
-
'config',
|
|
3584
|
-
]
|
|
3585
|
-
customPanes.forEach(function (cp) {
|
|
3586
|
-
valid.push(cp.id)
|
|
3587
|
-
})
|
|
3588
|
-
if (valid.indexOf(section) === -1) section = 'overview'
|
|
3589
|
-
|
|
3590
|
-
// Apply deep link params
|
|
3591
|
-
applyRouteParams({ section: section, params: route.params })
|
|
3592
|
-
|
|
3593
|
-
// Switch to section
|
|
3594
|
-
activeSection = section
|
|
3595
|
-
navItems.forEach(function (item) {
|
|
3596
|
-
item.classList.toggle('ss-dash-active', item.getAttribute('data-ss-section') === section)
|
|
3597
|
-
})
|
|
3598
|
-
root.querySelectorAll('.ss-dash-pane').forEach(function (p) {
|
|
3599
|
-
p.classList.toggle('ss-dash-active', p.id === 'ss-dash-pane-' + section)
|
|
3600
|
-
})
|
|
3601
|
-
|
|
3602
|
-
loadSection(section)
|
|
3603
|
-
}
|
|
3604
|
-
|
|
3605
|
-
window.addEventListener('hashchange', function () {
|
|
3606
|
-
var route = parseHash()
|
|
3607
|
-
if (route.section !== activeSection) {
|
|
3608
|
-
applyRouteParams(route)
|
|
3609
|
-
switchSection(route.section)
|
|
3610
|
-
} else if (Object.keys(route.params).length > 0) {
|
|
3611
|
-
applyRouteParams(route)
|
|
3612
|
-
loadSection(activeSection)
|
|
3613
|
-
}
|
|
3614
|
-
})
|
|
3615
|
-
|
|
3616
|
-
// ── Init ──────────────────────────────────────────────────────
|
|
3617
|
-
initRoute()
|
|
3618
|
-
initTransmit()
|
|
3619
|
-
})()
|
|
1
|
+
(function(){"use strict";var ye,A,et,le,tt,st,nt,rt,Re,De,Oe,ge={},be=[],gs=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,ue=Array.isArray;function Z(e,s){for(var n in s)e[n]=s[n];return e}function Fe(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function we(e,s,n){var r,o,a,l={};for(a in s)a=="key"?r=s[a]:a=="ref"?o=s[a]:l[a]=s[a];if(arguments.length>2&&(l.children=arguments.length>3?ye.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(a in e.defaultProps)l[a]===void 0&&(l[a]=e.defaultProps[a]);return Ne(e,l,r,o,null)}function Ne(e,s,n,r,o){var a={type:e,props:s,key:n,ref:r,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++et,__i:-1,__u:0};return o==null&&A.vnode!=null&&A.vnode(a),a}function D(e){return e.children}function ee(e,s){this.props=e,this.context=s}function de(e,s){if(s==null)return e.__?de(e.__,e.__i+1):null;for(var n;s<e.__k.length;s++)if((n=e.__k[s])!=null&&n.__e!=null)return n.__e;return typeof e.type=="function"?de(e):null}function bs(e){if(e.__P&&e.__d){var s=e.__v,n=s.__e,r=[],o=[],a=Z({},s);a.__v=s.__v+1,A.vnode&&A.vnode(a),je(e.__P,a,s,e.__n,e.__P.namespaceURI,32&s.__u?[n]:null,r,n??de(s),!!(32&s.__u),o),a.__v=s.__v,a.__.__k[a.__i]=a,ht(r,a,o),s.__e=s.__=null,a.__e!=n&&at(a)}}function at(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(s){if(s!=null&&s.__e!=null)return e.__e=e.__c.base=s.__e}),at(e)}function lt(e){(!e.__d&&(e.__d=!0)&&le.push(e)&&!xe.__r++||tt!=A.debounceRendering)&&((tt=A.debounceRendering)||st)(xe)}function xe(){for(var e,s=1;le.length;)le.length>s&&le.sort(nt),e=le.shift(),s=le.length,bs(e);xe.__r=0}function it(e,s,n,r,o,a,l,i,d,c,p){var h,y,f,_,g,S,k,w=r&&r.__k||be,u=s.length;for(d=ws(n,s,w,d,u),h=0;h<u;h++)(f=n.__k[h])!=null&&(y=f.__i!=-1&&w[f.__i]||ge,f.__i=h,S=je(e,f,y,o,a,l,i,d,c,p),_=f.__e,f.ref&&y.ref!=f.ref&&(y.ref&&Be(y.ref,null,f),p.push(f.ref,f.__c||_,f)),g==null&&_!=null&&(g=_),(k=!!(4&f.__u))||y.__k===f.__k?d=ot(f,d,e,k):typeof f.type=="function"&&S!==void 0?d=S:_&&(d=_.nextSibling),f.__u&=-7);return n.__e=g,d}function ws(e,s,n,r,o){var a,l,i,d,c,p=n.length,h=p,y=0;for(e.__k=new Array(o),a=0;a<o;a++)(l=s[a])!=null&&typeof l!="boolean"&&typeof l!="function"?(typeof l=="string"||typeof l=="number"||typeof l=="bigint"||l.constructor==String?l=e.__k[a]=Ne(null,l,null,null,null):ue(l)?l=e.__k[a]=Ne(D,{children:l},null,null,null):l.constructor===void 0&&l.__b>0?l=e.__k[a]=Ne(l.type,l.props,l.key,l.ref?l.ref:null,l.__v):e.__k[a]=l,d=a+y,l.__=e,l.__b=e.__b+1,i=null,(c=l.__i=Ns(l,n,d,h))!=-1&&(h--,(i=n[c])&&(i.__u|=2)),i==null||i.__v==null?(c==-1&&(o>p?y--:o<p&&y++),typeof l.type!="function"&&(l.__u|=4)):c!=d&&(c==d-1?y--:c==d+1?y++:(c>d?y--:y++,l.__u|=4))):e.__k[a]=null;if(h)for(a=0;a<p;a++)(i=n[a])!=null&&(2&i.__u)==0&&(i.__e==r&&(r=de(i)),pt(i,i));return r}function ot(e,s,n,r){var o,a;if(typeof e.type=="function"){for(o=e.__k,a=0;o&&a<o.length;a++)o[a]&&(o[a].__=e,s=ot(o[a],s,n,r));return s}e.__e!=s&&(r&&(s&&e.type&&!s.parentNode&&(s=de(e)),n.insertBefore(e.__e,s||null)),s=e.__e);do s=s&&s.nextSibling;while(s!=null&&s.nodeType==8);return s}function ke(e,s){return s=s||[],e==null||typeof e=="boolean"||(ue(e)?e.some(function(n){ke(n,s)}):s.push(e)),s}function Ns(e,s,n,r){var o,a,l,i=e.key,d=e.type,c=s[n],p=c!=null&&(2&c.__u)==0;if(c===null&&i==null||p&&i==c.key&&d==c.type)return n;if(r>(p?1:0)){for(o=n-1,a=n+1;o>=0||a<s.length;)if((c=s[l=o>=0?o--:a++])!=null&&(2&c.__u)==0&&i==c.key&&d==c.type)return l}return-1}function ct(e,s,n){s[0]=="-"?e.setProperty(s,n??""):e[s]=n==null?"":typeof n!="number"||gs.test(s)?n:n+"px"}function Se(e,s,n,r,o){var a,l;e:if(s=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof r=="string"&&(e.style.cssText=r=""),r)for(s in r)n&&s in n||ct(e.style,s,"");if(n)for(s in n)r&&n[s]==r[s]||ct(e.style,s,n[s])}else if(s[0]=="o"&&s[1]=="n")a=s!=(s=s.replace(rt,"$1")),l=s.toLowerCase(),s=l in e||s=="onFocusOut"||s=="onFocusIn"?l.slice(2):s.slice(2),e.l||(e.l={}),e.l[s+a]=n,n?r?n.u=r.u:(n.u=Re,e.addEventListener(s,a?Oe:De,a)):e.removeEventListener(s,a?Oe:De,a);else{if(o=="http://www.w3.org/2000/svg")s=s.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(s!="width"&&s!="height"&&s!="href"&&s!="list"&&s!="form"&&s!="tabIndex"&&s!="download"&&s!="rowSpan"&&s!="colSpan"&&s!="role"&&s!="popover"&&s in e)try{e[s]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&s[4]!="-"?e.removeAttribute(s):e.setAttribute(s,s=="popover"&&n==1?"":n))}}function dt(e){return function(s){if(this.l){var n=this.l[s.type+e];if(s.t==null)s.t=Re++;else if(s.t<n.u)return;return n(A.event?A.event(s):s)}}}function je(e,s,n,r,o,a,l,i,d,c){var p,h,y,f,_,g,S,k,w,u,N,b,L,M,q,O=s.type;if(s.constructor!==void 0)return null;128&n.__u&&(d=!!(32&n.__u),a=[i=s.__e=n.__e]),(p=A.__b)&&p(s);e:if(typeof O=="function")try{if(k=s.props,w="prototype"in O&&O.prototype.render,u=(p=O.contextType)&&r[p.__c],N=p?u?u.props.value:p.__:r,n.__c?S=(h=s.__c=n.__c).__=h.__E:(w?s.__c=h=new O(k,N):(s.__c=h=new ee(k,N),h.constructor=O,h.render=ks),u&&u.sub(h),h.state||(h.state={}),h.__n=r,y=h.__d=!0,h.__h=[],h._sb=[]),w&&h.__s==null&&(h.__s=h.state),w&&O.getDerivedStateFromProps!=null&&(h.__s==h.state&&(h.__s=Z({},h.__s)),Z(h.__s,O.getDerivedStateFromProps(k,h.__s))),f=h.props,_=h.state,h.__v=s,y)w&&O.getDerivedStateFromProps==null&&h.componentWillMount!=null&&h.componentWillMount(),w&&h.componentDidMount!=null&&h.__h.push(h.componentDidMount);else{if(w&&O.getDerivedStateFromProps==null&&k!==f&&h.componentWillReceiveProps!=null&&h.componentWillReceiveProps(k,N),s.__v==n.__v||!h.__e&&h.shouldComponentUpdate!=null&&h.shouldComponentUpdate(k,h.__s,N)===!1){s.__v!=n.__v&&(h.props=k,h.state=h.__s,h.__d=!1),s.__e=n.__e,s.__k=n.__k,s.__k.some(function(U){U&&(U.__=s)}),be.push.apply(h.__h,h._sb),h._sb=[],h.__h.length&&l.push(h);break e}h.componentWillUpdate!=null&&h.componentWillUpdate(k,h.__s,N),w&&h.componentDidUpdate!=null&&h.__h.push(function(){h.componentDidUpdate(f,_,g)})}if(h.context=N,h.props=k,h.__P=e,h.__e=!1,b=A.__r,L=0,w)h.state=h.__s,h.__d=!1,b&&b(s),p=h.render(h.props,h.state,h.context),be.push.apply(h.__h,h._sb),h._sb=[];else do h.__d=!1,b&&b(s),p=h.render(h.props,h.state,h.context),h.state=h.__s;while(h.__d&&++L<25);h.state=h.__s,h.getChildContext!=null&&(r=Z(Z({},r),h.getChildContext())),w&&!y&&h.getSnapshotBeforeUpdate!=null&&(g=h.getSnapshotBeforeUpdate(f,_)),M=p!=null&&p.type===D&&p.key==null?ut(p.props.children):p,i=it(e,ue(M)?M:[M],s,n,r,o,a,l,i,d,c),h.base=s.__e,s.__u&=-161,h.__h.length&&l.push(h),S&&(h.__E=h.__=null)}catch(U){if(s.__v=null,d||a!=null)if(U.then){for(s.__u|=d?160:128;i&&i.nodeType==8&&i.nextSibling;)i=i.nextSibling;a[a.indexOf(i)]=null,s.__e=i}else{for(q=a.length;q--;)Fe(a[q]);Ie(s)}else s.__e=n.__e,s.__k=n.__k,U.then||Ie(s);A.__e(U,s,n)}else a==null&&s.__v==n.__v?(s.__k=n.__k,s.__e=n.__e):i=s.__e=xs(n.__e,s,n,r,o,a,l,d,c);return(p=A.diffed)&&p(s),128&s.__u?void 0:i}function Ie(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(Ie))}function ht(e,s,n){for(var r=0;r<n.length;r++)Be(n[r],n[++r],n[++r]);A.__c&&A.__c(s,e),e.some(function(o){try{e=o.__h,o.__h=[],e.some(function(a){a.call(o)})}catch(a){A.__e(a,o.__v)}})}function ut(e){return typeof e!="object"||e==null||e.__b>0?e:ue(e)?e.map(ut):Z({},e)}function xs(e,s,n,r,o,a,l,i,d){var c,p,h,y,f,_,g,S=n.props||ge,k=s.props,w=s.type;if(w=="svg"?o="http://www.w3.org/2000/svg":w=="math"?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),a!=null){for(c=0;c<a.length;c++)if((f=a[c])&&"setAttribute"in f==!!w&&(w?f.localName==w:f.nodeType==3)){e=f,a[c]=null;break}}if(e==null){if(w==null)return document.createTextNode(k);e=document.createElementNS(o,w,k.is&&k),i&&(A.__m&&A.__m(s,a),i=!1),a=null}if(w==null)S===k||i&&e.data==k||(e.data=k);else{if(a=a&&ye.call(e.childNodes),!i&&a!=null)for(S={},c=0;c<e.attributes.length;c++)S[(f=e.attributes[c]).name]=f.value;for(c in S)f=S[c],c=="dangerouslySetInnerHTML"?h=f:c=="children"||c in k||c=="value"&&"defaultValue"in k||c=="checked"&&"defaultChecked"in k||Se(e,c,null,f,o);for(c in k)f=k[c],c=="children"?y=f:c=="dangerouslySetInnerHTML"?p=f:c=="value"?_=f:c=="checked"?g=f:i&&typeof f!="function"||S[c]===f||Se(e,c,f,S[c],o);if(p)i||h&&(p.__html==h.__html||p.__html==e.innerHTML)||(e.innerHTML=p.__html),s.__k=[];else if(h&&(e.innerHTML=""),it(s.type=="template"?e.content:e,ue(y)?y:[y],s,n,r,w=="foreignObject"?"http://www.w3.org/1999/xhtml":o,a,l,a?a[0]:n.__k&&de(n,0),i,d),a!=null)for(c=a.length;c--;)Fe(a[c]);i||(c="value",w=="progress"&&_==null?e.removeAttribute("value"):_!=null&&(_!==e[c]||w=="progress"&&!_||w=="option"&&_!=S[c])&&Se(e,c,_,S[c],o),c="checked",g!=null&&g!=e[c]&&Se(e,c,g,S[c],o))}return e}function Be(e,s,n){try{if(typeof e=="function"){var r=typeof e.__u=="function";r&&e.__u(),r&&s==null||(e.__u=e(s))}else e.current=s}catch(o){A.__e(o,n)}}function pt(e,s,n){var r,o;if(A.unmount&&A.unmount(e),(r=e.ref)&&(r.current&&r.current!=e.__e||Be(r,null,s)),(r=e.__c)!=null){if(r.componentWillUnmount)try{r.componentWillUnmount()}catch(a){A.__e(a,s)}r.base=r.__P=null}if(r=e.__k)for(o=0;o<r.length;o++)r[o]&&pt(r[o],s,n||typeof e.type!="function");n||Fe(e.__e),e.__c=e.__=e.__e=void 0}function ks(e,s,n){return this.constructor(e,n)}function Ss(e,s,n){var r,o,a,l;s==document&&(s=document.documentElement),A.__&&A.__(e,s),o=(r=!1)?null:s.__k,a=[],l=[],je(s,e=s.__k=we(D,null,[e]),o||ge,ge,s.namespaceURI,o?null:s.firstChild?ye.call(s.childNodes):null,a,o?o.__e:s.firstChild,r,l),ht(a,e,l)}ye=be.slice,A={__e:function(e,s,n,r){for(var o,a,l;s=s.__;)if((o=s.__c)&&!o.__)try{if((a=o.constructor)&&a.getDerivedStateFromError!=null&&(o.setState(a.getDerivedStateFromError(e)),l=o.__d),o.componentDidCatch!=null&&(o.componentDidCatch(e,r||{}),l=o.__d),l)return o.__E=o}catch(i){e=i}throw e}},et=0,ee.prototype.setState=function(e,s){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=Z({},this.state),typeof e=="function"&&(e=e(Z({},n),this.props)),e&&Z(n,e),e!=null&&this.__v&&(s&&this._sb.push(s),lt(this))},ee.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),lt(this))},ee.prototype.render=D,le=[],st=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,nt=function(e,s){return e.__v.__b-s.__v.__b},xe.__r=0,rt=/(PointerCapture)$|Capture$/i,Re=0,De=dt(!1),Oe=dt(!0);var $s=0;function t(e,s,n,r,o,a){s||(s={});var l,i,d=s;if("ref"in d)for(i in d={},s)i=="ref"?l=s[i]:d[i]=s[i];var c={type:e,props:d,key:n,ref:l,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--$s,__i:-1,__u:0,__source:o,__self:a};if(typeof e=="function"&&(l=e.defaultProps))for(i in l)d[i]===void 0&&(d[i]=l[i]);return A.vnode&&A.vnode(c),c}var pe,I,Ue,ft,fe=0,mt=[],B=A,_t=B.__b,vt=B.__r,yt=B.diffed,gt=B.__c,bt=B.unmount,wt=B.__;function ze(e,s){B.__h&&B.__h(I,e,fe||s),fe=0;var n=I.__H||(I.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function x(e){return fe=1,Cs(kt,e)}function Cs(e,s,n){var r=ze(pe++,2);if(r.t=e,!r.__c&&(r.__=[n?n(s):kt(void 0,s),function(i){var d=r.__N?r.__N[0]:r.__[0],c=r.t(d,i);d!==c&&(r.__N=[c,r.__[1]],r.__c.setState({}))}],r.__c=I,!I.__f)){var o=function(i,d,c){if(!r.__c.__H)return!0;var p=r.__c.__H.__.filter(function(y){return y.__c});if(p.every(function(y){return!y.__N}))return!a||a.call(this,i,d,c);var h=r.__c.props!==i;return p.some(function(y){if(y.__N){var f=y.__[0];y.__=y.__N,y.__N=void 0,f!==y.__[0]&&(h=!0)}}),a&&a.call(this,i,d,c)||h};I.__f=!0;var a=I.shouldComponentUpdate,l=I.componentWillUpdate;I.componentWillUpdate=function(i,d,c){if(this.__e){var p=a;a=void 0,o(i,d,c),a=p}l&&l.call(this,i,d,c)},I.shouldComponentUpdate=o}return r.__N||r.__}function H(e,s){var n=ze(pe++,3);!B.__s&&xt(n.__H,s)&&(n.__=e,n.u=s,I.__H.__h.push(n))}function V(e){return fe=5,z(function(){return{current:e}},[])}function z(e,s){var n=ze(pe++,7);return xt(n.__H,s)&&(n.__=e(),n.__H=s,n.__h=e),n.__}function P(e,s){return fe=8,z(function(){return e},s)}function Ts(){for(var e;e=mt.shift();){var s=e.__H;if(e.__P&&s)try{s.__h.some($e),s.__h.some(He),s.__h=[]}catch(n){s.__h=[],B.__e(n,e.__v)}}}B.__b=function(e){I=null,_t&&_t(e)},B.__=function(e,s){e&&s.__k&&s.__k.__m&&(e.__m=s.__k.__m),wt&&wt(e,s)},B.__r=function(e){vt&&vt(e),pe=0;var s=(I=e.__c).__H;s&&(Ue===I?(s.__h=[],I.__h=[],s.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(s.__h.some($e),s.__h.some(He),s.__h=[],pe=0)),Ue=I},B.diffed=function(e){yt&&yt(e);var s=e.__c;s&&s.__H&&(s.__H.__h.length&&(mt.push(s)!==1&&ft===B.requestAnimationFrame||((ft=B.requestAnimationFrame)||Ls)(Ts)),s.__H.__.some(function(n){n.u&&(n.__H=n.u),n.u=void 0})),Ue=I=null},B.__c=function(e,s){s.some(function(n){try{n.__h.some($e),n.__h=n.__h.filter(function(r){return!r.__||He(r)})}catch(r){s.some(function(o){o.__h&&(o.__h=[])}),s=[],B.__e(r,n.__v)}}),gt&>(e,s)},B.unmount=function(e){bt&&bt(e);var s,n=e.__c;n&&n.__H&&(n.__H.__.some(function(r){try{$e(r)}catch(o){s=o}}),n.__H=void 0,s&&B.__e(s,n.__v))};var Nt=typeof requestAnimationFrame=="function";function Ls(e){var s,n=function(){clearTimeout(r),Nt&&cancelAnimationFrame(s),setTimeout(e)},r=setTimeout(n,35);Nt&&(s=requestAnimationFrame(n))}function $e(e){var s=I,n=e.__c;typeof n=="function"&&(e.__c=void 0,n()),I=s}function He(e){var s=I;e.__c=e.__(),I=s}function xt(e,s){return!e||e.length!==s.length||s.some(function(n,r){return n!==e[r]})}function kt(e,s){return typeof s=="function"?s(e):s}function Es(e,s){for(var n in s)e[n]=s[n];return e}function St(e,s){for(var n in e)if(n!=="__source"&&!(n in s))return!0;for(var r in s)if(r!=="__source"&&e[r]!==s[r])return!0;return!1}function $t(e,s){this.props=e,this.context=s}($t.prototype=new ee).isPureReactComponent=!0,$t.prototype.shouldComponentUpdate=function(e,s){return St(this.props,e)||St(this.state,s)};var Ct=A.__b;A.__b=function(e){e.type&&e.type.__f&&e.ref&&(e.props.ref=e.ref,e.ref=null),Ct&&Ct(e)};var As=A.__e;A.__e=function(e,s,n,r){if(e.then){for(var o,a=s;a=a.__;)if((o=a.__c)&&o.__c)return s.__e==null&&(s.__e=n.__e,s.__k=n.__k),o.__c(e,s)}As(e,s,n,r)};var Tt=A.unmount;function Lt(e,s,n){return e&&(e.__c&&e.__c.__H&&(e.__c.__H.__.forEach(function(r){typeof r.__c=="function"&&r.__c()}),e.__c.__H=null),(e=Es({},e)).__c!=null&&(e.__c.__P===n&&(e.__c.__P=s),e.__c.__e=!0,e.__c=null),e.__k=e.__k&&e.__k.map(function(r){return Lt(r,s,n)})),e}function Et(e,s,n){return e&&n&&(e.__v=null,e.__k=e.__k&&e.__k.map(function(r){return Et(r,s,n)}),e.__c&&e.__c.__P===s&&(e.__e&&n.appendChild(e.__e),e.__c.__e=!0,e.__c.__P=n)),e}function Ce(){this.__u=0,this.o=null,this.__b=null}function At(e){if(!e.__)return null;var s=e.__.__c;return s&&s.__a&&s.__a(e)}function Q(e){var s,n,r,o=null;function a(l){if(s||(s=e()).then(function(i){i&&(o=i.default||i),r=!0},function(i){n=i,r=!0}),n)throw n;if(!r)throw s;return o?we(o,l):null}return a.displayName="Lazy",a.__f=!0,a}function Te(){this.i=null,this.l=null}A.unmount=function(e){var s=e.__c;s&&(s.__z=!0),s&&s.__R&&s.__R(),s&&32&e.__u&&(e.type=null),Tt&&Tt(e)},(Ce.prototype=new ee).__c=function(e,s){var n=s.__c,r=this;r.o==null&&(r.o=[]),r.o.push(n);var o=At(r.__v),a=!1,l=function(){a||r.__z||(a=!0,n.__R=null,o?o(d):d())};n.__R=l;var i=n.__P;n.__P=null;var d=function(){if(!--r.__u){if(r.state.__a){var c=r.state.__a;r.__v.__k[0]=Et(c,c.__c.__P,c.__c.__O)}var p;for(r.setState({__a:r.__b=null});p=r.o.pop();)p.__P=i,p.forceUpdate()}};r.__u++||32&s.__u||r.setState({__a:r.__b=r.__v.__k[0]}),e.then(l,l)},Ce.prototype.componentWillUnmount=function(){this.o=[]},Ce.prototype.render=function(e,s){if(this.__b){if(this.__v.__k){var n=document.createElement("div"),r=this.__v.__k[0].__c;this.__v.__k[0]=Lt(this.__b,n,r.__O=r.__P)}this.__b=null}var o=s.__a&&we(D,null,e.fallback);return o&&(o.__u&=-33),[we(D,null,s.__a?null:e.children),o]};var Pt=function(e,s,n){if(++n[1]===n[0]&&e.l.delete(s),e.props.revealOrder&&(e.props.revealOrder[0]!=="t"||!e.l.size))for(n=e.i;n;){for(;n.length>3;)n.pop()();if(n[1]<n[0])break;e.i=n=n[2]}};(Te.prototype=new ee).__a=function(e){var s=this,n=At(s.__v),r=s.l.get(e);return r[0]++,function(o){var a=function(){s.props.revealOrder?(r.push(o),Pt(s,e,r)):o()};n?n(a):a()}},Te.prototype.render=function(e){this.i=null,this.l=new Map;var s=ke(e.children);e.revealOrder&&e.revealOrder[0]==="b"&&s.reverse();for(var n=s.length;n--;)this.l.set(s[n],this.i=[1,0,this.i]);return e.children},Te.prototype.componentDidUpdate=Te.prototype.componentDidMount=function(){var e=this;this.l.forEach(function(s,n){Pt(e,n,s)})};var Ps=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.element")||60103,Ms=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,qs=/^on(Ani|Tra|Tou|BeforeInp|Compo)/,Rs=/[A-Z0-9]/g,Ds=typeof document<"u",Os=function(e){return(typeof Symbol<"u"&&typeof Symbol()=="symbol"?/fil|che|rad/:/fil|che|ra/).test(e)};ee.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(e){Object.defineProperty(ee.prototype,e,{configurable:!0,get:function(){return this["UNSAFE_"+e]},set:function(s){Object.defineProperty(this,e,{configurable:!0,writable:!0,value:s})}})});var Mt=A.event;function Fs(){}function js(){return this.cancelBubble}function Is(){return this.defaultPrevented}A.event=function(e){return Mt&&(e=Mt(e)),e.persist=Fs,e.isPropagationStopped=js,e.isDefaultPrevented=Is,e.nativeEvent=e};var Bs={enumerable:!1,configurable:!0,get:function(){return this.class}},qt=A.vnode;A.vnode=function(e){typeof e.type=="string"&&(function(s){var n=s.props,r=s.type,o={},a=r.indexOf("-")===-1;for(var l in n){var i=n[l];if(!(l==="value"&&"defaultValue"in n&&i==null||Ds&&l==="children"&&r==="noscript"||l==="class"||l==="className")){var d=l.toLowerCase();l==="defaultValue"&&"value"in n&&n.value==null?l="value":l==="download"&&i===!0?i="":d==="translate"&&i==="no"?i=!1:d[0]==="o"&&d[1]==="n"?d==="ondoubleclick"?l="ondblclick":d!=="onchange"||r!=="input"&&r!=="textarea"||Os(n.type)?d==="onfocus"?l="onfocusin":d==="onblur"?l="onfocusout":qs.test(l)&&(l=d):d=l="oninput":a&&Ms.test(l)?l=l.replace(Rs,"-$&").toLowerCase():i===null&&(i=void 0),d==="oninput"&&o[l=d]&&(l="oninputCapture"),o[l]=i}}r=="select"&&o.multiple&&Array.isArray(o.value)&&(o.value=ke(n.children).forEach(function(c){c.props.selected=o.value.indexOf(c.props.value)!=-1})),r=="select"&&o.defaultValue!=null&&(o.value=ke(n.children).forEach(function(c){c.props.selected=o.multiple?o.defaultValue.indexOf(c.props.value)!=-1:o.defaultValue==c.props.value})),n.class&&!n.className?(o.class=n.class,Object.defineProperty(o,"className",Bs)):n.className&&(o.class=o.className=n.className),s.props=o})(e),e.$$typeof=Ps,qt&&qt(e)};var Rt=A.__r;A.__r=function(e){Rt&&Rt(e),e.__c};var Dt=A.diffed;A.diffed=function(e){Dt&&Dt(e);var s=e.props,n=e.__e;n!=null&&e.type==="textarea"&&"value"in s&&s.value!==n.value&&(n.value=s.value==null?"":s.value)};var Us={Fragment:D};const F={queries:{viewBox:"0 0 24 24",elements:['<ellipse cx="12" cy="5" rx="9" ry="3"/>','<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>','<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>']},events:{viewBox:"0 0 24 24",elements:['<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>']},emails:{viewBox:"0 0 24 24",elements:['<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>','<polyline points="22,6 12,13 2,6"/>']},routes:{viewBox:"0 0 24 24",elements:['<circle cx="12" cy="12" r="10"/>','<line x1="2" y1="12" x2="22" y2="12"/>','<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>']},logs:{viewBox:"0 0 24 24",elements:['<line x1="8" y1="6" x2="21" y2="6"/>','<line x1="8" y1="12" x2="21" y2="12"/>','<line x1="8" y1="18" x2="21" y2="18"/>','<line x1="3" y1="6" x2="3.01" y2="6"/>','<line x1="3" y1="12" x2="3.01" y2="12"/>','<line x1="3" y1="18" x2="3.01" y2="18"/>']},timeline:{viewBox:"0 0 24 24",elements:['<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>']},cache:{viewBox:"0 0 24 24",elements:['<rect x="2" y="2" width="20" height="8" rx="2" ry="2"/>','<rect x="2" y="14" width="20" height="8" rx="2" ry="2"/>','<line x1="6" y1="6" x2="6.01" y2="6"/>','<line x1="6" y1="18" x2="6.01" y2="18"/>']},jobs:{viewBox:"0 0 24 24",elements:['<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/>','<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/>']},config:{viewBox:"0 0 24 24",elements:['<circle cx="12" cy="12" r="3"/>','<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>']},internals:{viewBox:"0 0 24 24",elements:['<rect x="4" y="4" width="16" height="16" rx="2"/>','<rect x="9" y="9" width="6" height="6"/>','<line x1="9" y1="1" x2="9" y2="4"/>','<line x1="15" y1="1" x2="15" y2="4"/>','<line x1="9" y1="20" x2="9" y2="23"/>','<line x1="15" y1="20" x2="15" y2="23"/>','<line x1="20" y1="9" x2="23" y2="9"/>','<line x1="20" y1="14" x2="23" y2="14"/>','<line x1="1" y1="9" x2="4" y2="9"/>','<line x1="1" y1="14" x2="4" y2="14"/>']},overview:{viewBox:"0 0 24 24",elements:['<rect x="3" y="3" width="7" height="7"/>','<rect x="14" y="3" width="7" height="7"/>','<rect x="14" y="14" width="7" height="7"/>','<rect x="3" y="14" width="7" height="7"/>']},requests:{viewBox:"0 0 24 24",elements:['<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>']},"dashboard-timeline":{viewBox:"0 0 24 24",elements:['<circle cx="12" cy="12" r="10"/>','<polyline points="12 6 12 12 16 14"/>']},"custom-pane":{viewBox:"0 0 24 24",elements:['<rect x="3" y="3" width="18" height="18" rx="2"/>','<path d="M9 3v18"/>']},wrench:{viewBox:"0 0 24 24",elements:['<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>']},"external-link":{viewBox:"0 0 24 24",elements:['<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>','<polyline points="15 3 21 3 21 9"/>','<line x1="10" y1="14" x2="21" y2="3"/>']},sun:{viewBox:"0 0 24 24",elements:['<circle cx="12" cy="12" r="5"/>','<line x1="12" y1="1" x2="12" y2="3"/>','<line x1="12" y1="21" x2="12" y2="23"/>','<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>','<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>','<line x1="1" y1="12" x2="3" y2="12"/>','<line x1="21" y1="12" x2="23" y2="12"/>','<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>','<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>']},moon:{viewBox:"0 0 24 24",elements:['<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>']},search:{viewBox:"0 0 24 24",elements:['<circle cx="11" cy="11" r="8"/>','<line x1="21" y1="21" x2="16.65" y2="16.65"/>']},eye:{viewBox:"0 0 24 24",elements:['<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>','<circle cx="12" cy="12" r="3"/>']},"eye-off":{viewBox:"0 0 24 24",elements:['<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/>','<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/>','<line x1="1" y1="1" x2="23" y2="23"/>','<path d="M14.12 14.12a3 3 0 1 1-4.24-4.24"/>']},"chevron-right":{viewBox:"0 0 24 24",elements:['<path d="M9 18l6-6-6-6"/>']},"chevron-left":{viewBox:"0 0 24 24",elements:['<path d="M15 18l-6-6 6-6"/>']},"open-external":{viewBox:"0 0 16 16",elements:['<path d="M6 3H3v10h10v-3M9 1h6v6M7 9L15 1"/>']}};async function zs(){if(typeof window<"u"&&window.Transmit&&typeof window.Transmit=="function")return window.Transmit;try{const e=await import("@adonisjs/transmit-client");return e.Transmit??e.default??null}catch{return null}}function Hs(e){let s=null,n=null,r=!1;return{subscribe:async()=>{try{const l=await zs();if(!l)throw new Error("Transmit client not available (neither window.Transmit nor @adonisjs/transmit-client)");if(r)return;s=new l({baseUrl:e.baseUrl||window.location.origin,...e.authToken?{beforeSubscribe(i){return{headers:{Authorization:`Bearer ${e.authToken}`}}},beforeUnsubscribe(i){return{headers:{Authorization:`Bearer ${e.authToken}`}}}}:{}}),n=s.subscription(e.channelName),n.onMessage(i=>{r||e.onMessage(i)}),await n.create()}catch(l){e.onError&&e.onError(l)}},unsubscribe:async()=>{r=!0;try{n&&(await n.delete(),n=null),s&&(s=null)}catch{}}}}function Vs(e){let s=!1;const n=Hs({baseUrl:e.baseUrl,channelName:e.channelName,authToken:e.authToken,onMessage:e.onMessage,onError:r=>{s=!0,e.onError?.(r),e.onDisconnect?.()}});return n.subscribe().then(()=>{s||e.onConnect?.()}).catch(r=>{e.onError?.(r),e.onDisconnect?.()}),{unsubscribe:()=>{n.unsubscribe().catch(()=>{})}}}class Ve extends Error{status;constructor(s=403){super(`Unauthorized (HTTP ${s})`),this.name="UnauthorizedError",this.status=s}}class Ws extends Error{status;body;constructor(s,n){super(`API error (HTTP ${s})`),this.name="ApiError",this.status=s,this.body=n}}class We{baseUrl;authToken;constructor(s){this.baseUrl=s.baseUrl.replace(/\/+$/,""),this.authToken=s.authToken}async fetch(s,n){const o={...{Accept:"application/json",...this.authToken?{Authorization:`Bearer ${this.authToken}`}:{}},...n?.headers},a=await globalThis.fetch(`${this.baseUrl}${s}`,{...n,headers:o,credentials:this.authToken?"omit":"include"});if(a.status===401||a.status===403)throw new Ve(a.status);if(!a.ok){const l=await a.text().catch(()=>"");throw new Ws(a.status,l)}return a.json()}async get(s,n){const r=n?`${s}?${n}`:s;return this.fetch(r)}async post(s,n){const r={method:"POST",...n!==void 0?{body:JSON.stringify(n),headers:{"Content-Type":"application/json"}}:{}};return this.fetch(s,r)}async delete(s){return this.fetch(s,{method:"DELETE"})}}const Ks=5e3,Ot=1e4,Ft=100,Js=500,Qs={overview:"/overview",requests:"/requests",queries:"/queries",events:"/events",routes:"/routes",logs:"/logs",emails:"/emails",timeline:"/traces",cache:"/cache",jobs:"/jobs",config:"/config"};function Gs(e){return Qs[e]||`/${e}`}class Ys{constructor(s,n){this.client=s,this.basePath=n}async fetchSection(s,n){const r=Gs(s),o=n?`${this.basePath}${r}?${n}`:`${this.basePath}${r}`;return this.client.fetch(o)}async fetchChart(s){return this.client.fetch(`${this.basePath}/overview/chart?range=${s}`)}async fetchGroupedQueries(){return this.client.fetch(`${this.basePath}/queries/grouped`)}async explainQuery(s){return this.client.fetch(`${this.basePath}/queries/${s}/explain`)}async retryJob(s){return this.client.fetch(`${this.basePath}/jobs/${s}/retry`,{method:"POST"})}async fetchCacheKey(s){return this.client.fetch(`${this.basePath}/cache/${encodeURIComponent(s)}`)}async deleteCacheKey(s){return this.client.fetch(`${this.basePath}/cache/${encodeURIComponent(s)}`,{method:"DELETE"})}async fetchEmailPreview(s){return this.client.fetch(`${this.basePath}/emails/${s}/preview`)}}function Xs(e){const s=new URLSearchParams;if(e.page!==null&&e.page!==void 0&&s.set("page",String(e.page)),e.perPage!==null&&e.perPage!==void 0&&s.set("perPage",String(e.perPage)),e.search&&s.set("search",e.search),e.sort&&s.set("sort",e.sort),e.sortDir&&s.set("direction",e.sortDir),e.timeRange&&s.set("range",e.timeRange),e.filters)for(const[n,r]of Object.entries(e.filters))r&&s.set(n,r);return s.toString()}function Zs(e,s,n=2){if(s<=1)return[1];const r=[],o=Math.max(2,e-n),a=Math.min(s-1,e+n);r.push(1),o>2&&r.push("...");for(let l=o;l<=a;l++)r.push(l);return a<s-1&&r.push("..."),s>1&&r.push(s),r}class en{client;api;callbacks;endpoint;perPage;section;page=1;search;sort;sortDir;filters;timeRange;timer=null;fetchId=0;explicitFetchPending=!1;hasFetched=!1;stopped=!1;constructor(s){this.client=new We({baseUrl:s.baseUrl,authToken:s.authToken}),this.api=new Ys(this.client,s.endpoint),this.endpoint=s.endpoint,this.section=s.section,this.perPage=s.perPage,this.callbacks=s.callbacks}start(){this.stopped=!1,this.fetch(!1),this.startRefreshTimer()}stop(){this.stopped=!0,this.stopRefreshTimer()}async fetch(s=!0){if(s&&this.explicitFetchPending)return;const n=++this.fetchId,r=this.section;if(!r)return;const o=this.filters,a=this.sort?this.sort.replace(/[A-Z]/g,i=>"_"+i.toLowerCase()):void 0,l=Xs({page:this.page,perPage:this.perPage,search:this.search,sort:a,sortDir:this.sort?this.sortDir:void 0,filters:o&&Object.keys(o).length>0?o:void 0,timeRange:r.startsWith("overview")?this.timeRange:void 0});s||(this.callbacks.onLoading(!0),this.explicitFetchPending=!0);try{const i=await this.api.fetchSection(r,l||void 0);if(n!==this.fetchId||this.stopped)return;if(i&&typeof i=="object"&&i.data!==void 0&&i.meta!==void 0){const d=i;this.callbacks.onData(d.data),this.callbacks.onPagination(d.meta)}else this.callbacks.onData(i),this.callbacks.onPagination(null);this.callbacks.onError(null),this.callbacks.onLoading(!1),this.hasFetched=!0}catch(i){if(n!==this.fetchId||this.stopped)return;if(i instanceof Ve){this.callbacks.onError(i),this.callbacks.onLoading(!1),this.stopRefreshTimer(),this.callbacks.onUnauthorized();return}s||(this.callbacks.onError(i instanceof Error?i:new Error(String(i))),this.callbacks.onLoading(!1))}finally{s||(this.explicitFetchPending=!1)}}setSection(s){this.section!==s&&(this.section=s,this.page=1,this.search=void 0,this.sort=void 0,this.sortDir=void 0,this.filters=void 0,this.hasFetched=!1,this.callbacks.onData(null),this.callbacks.onPagination(null),this.callbacks.onLoading(!0),this.callbacks.onError(null),this.fetch(!1),this.startRefreshTimer())}setPage(s){this.page=s,this.fetch(!1)}setSearch(s){this.search=s||void 0,this.page=1,this.fetch(!1)}setFilter(s,n){this.filters||(this.filters={}),this.filters[s]=String(n),this.page=1,this.fetch(!1)}setSort(s,n){this.sort===s&&!n?this.sortDir=this.sortDir==="asc"?"desc":"asc":(this.sort=s,this.sortDir=n||"desc"),this.fetch(!1)}setTimeRange(s){this.timeRange=s,this.fetch(!1)}async mutate(s,n="post",r){const o=`${this.endpoint}/${s}`;try{const a=n==="post"?await this.client.post(o,r):await this.client.delete(o);return await this.fetch(!0),a}catch(a){throw a instanceof Error?a:new Error(String(a))}}configure(s){s.page!==void 0&&(this.page=s.page),s.perPage!==void 0&&(this.perPage=s.perPage),this.search=s.search,this.sort=s.sort,this.sortDir=s.sortDir,this.filters=s.filters,this.timeRange=s.timeRange}hasData(){return this.hasFetched}handleRefreshSignal(){this.hasFetched&&this.fetch(!0)}getApi(){return this.api}getClient(){return this.client}startRefreshTimer(){this.stopRefreshTimer();const s=this.section==="overview"?Ks:Ot;this.timer=setInterval(()=>this.fetch(!0),s)}stopRefreshTimer(){this.timer&&(clearInterval(this.timer),this.timer=null)}}function J(e,s={}){const{baseUrl:n="",dashboardEndpoint:r="/__stats/api",authToken:o,page:a=1,perPage:l=50,search:i,sort:d,sortDir:c,filters:p,timeRange:h,refreshKey:y}=s,[f,_]=x(null),[g,S]=x(null),[k,w]=x(!0),[u,N]=x(null),b=V(null),L=V(e),M=V(!1);b.current||(b.current=new en({baseUrl:n,endpoint:r,authToken:o,section:e,perPage:l,callbacks:{onData:E=>_(E),onPagination:E=>S(E),onLoading:E=>w(E),onError:E=>N(E),onUnauthorized:()=>{}}})),H(()=>{const E=b.current,$=L.current!==e;return L.current=e,E.configure({page:a,perPage:l,search:i,sort:d,sortDir:c,filters:p,timeRange:h}),$||!M.current?($?E.setSection(e):E.start(),M.current=!0):E.fetch(!0),()=>{E.stop()}},[e,a,l,i,d,c,p,h,y]);const q=P(()=>{b.current?.fetch(!0)},[]),O=P(async(E,$="post",R)=>b.current.mutate(E,$,R),[]),U=P(()=>b.current.getApi(),[]);return{data:f,meta:g,isLoading:k,error:u,refresh:q,mutate:O,getApi:U}}const jt="/admin/api/debug",It={tracing:!1,process:!1,system:!1,http:!1,db:!1,redis:!1,queues:!1,cache:!1,app:!1,log:!1,emails:!1,dashboard:!1,customPanes:[]};function tn(e){return{tracing:e.features?.tracing??!1,process:e.features?.process??!1,system:e.features?.system??!1,http:e.features?.http??!1,db:e.features?.db??!1,redis:e.features?.redis??!1,queues:e.features?.queues??!1,cache:e.features?.cache??!1,app:e.features?.app??!1,log:e.features?.log??!1,emails:e.features?.emails??!1,dashboard:e.features?.dashboard??!1,customPanes:e.customPanes??[]}}async function sn(e,s=jt){const n=`${s.replace(/\/+$/,"")}/config`;return e.fetch(n)}async function nn(e){const{baseUrl:s="",debugEndpoint:n=jt,authToken:r}=e,o=new We({baseUrl:s,authToken:r});try{const a=await sn(o,n);return tn(a)}catch{return It}}function rn(e={}){const{baseUrl:s="",debugEndpoint:n="/admin/api/debug",authToken:r}=e,[o,a]=x(It),[l,i]=x(!0),[d,c]=x(null),p=V(!1);return H(()=>{if(p.current)return;p.current=!0;let h=!1;return(async()=>{try{const f=await nn({baseUrl:s,debugEndpoint:n,authToken:r});h||(a(f),i(!1))}catch(f){h||(c(f instanceof Error?f:new Error(String(f))),i(!1))}})(),()=>{h=!0}},[s,n,r]),{features:o,isLoading:l,error:d}}const Le="ss-dash-theme",Ke="ss-theme-change";function Ee(){if(typeof window>"u")return"light";const e=localStorage.getItem(Le);return e==="dark"||e==="light"?e:window.matchMedia?.("(prefers-color-scheme: dark)").matches?"dark":"light"}function an(e){typeof window>"u"||(localStorage.setItem(Le,e),window.dispatchEvent(new CustomEvent(Ke,{detail:e})))}function ln(){const s=Ee()==="dark"?"light":"dark";return an(s),s}function Bt(e){if(typeof window>"u")return()=>{};const s=a=>{const l=a.detail;(l==="dark"||l==="light")&&e(l)},n=a=>{if(a.key===Le){const l=a.newValue;e(l==="dark"||l==="light"?l:Ee())}},r=window.matchMedia("(prefers-color-scheme: dark)"),o=a=>{localStorage.getItem(Le)||e(a.matches?"dark":"light")};return window.addEventListener(Ke,s),window.addEventListener("storage",n),r.addEventListener("change",o),()=>{window.removeEventListener(Ke,s),window.removeEventListener("storage",n),r.removeEventListener("change",o)}}function on(){const[e,s]=x(()=>Ee());H(()=>Bt(o=>{s(o)}),[]);const n=P(()=>{const r=ln();return s(r),r},[]);return{theme:e,toggleTheme:n}}function cn({theme:e,onToggle:s,className:n="",classPrefix:r="ss-dash"}){const o=e==="dark";return t("button",{type:"button",className:`${r==="ss-dbg"?"ss-dbg-theme-toggle":"ss-dash-theme-btn"} ${n}`,onClick:s,title:o?"Switch to light theme":"Switch to dark theme","aria-label":o?"Switch to light theme":"Switch to dark theme",children:o?t("svg",{width:"16",height:"16",viewBox:F.sun.viewBox,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",dangerouslySetInnerHTML:{__html:F.sun.elements.join("")}}):t("svg",{width:"16",height:"16",viewBox:F.moon.viewBox,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",dangerouslySetInnerHTML:{__html:F.moon.elements.join("")}})})}const dn=["overview","requests","queries","events","routes","logs","emails","timeline","cache","jobs","config","internals"],hn=Q(()=>Promise.resolve().then(()=>In)),un=Q(()=>Promise.resolve().then(()=>Kn)),pn=Q(()=>Promise.resolve().then(()=>Gn)),fn=Q(()=>Promise.resolve().then(()=>Yn)),mn=Q(()=>Promise.resolve().then(()=>Xn)),_n=Q(()=>Promise.resolve().then(()=>ar)),vn=Q(()=>Promise.resolve().then(()=>lr)),yn=Q(()=>Promise.resolve().then(()=>ir)),gn=Q(()=>Promise.resolve().then(()=>or)),bn=Q(()=>Promise.resolve().then(()=>pr)),wn=Q(()=>Promise.resolve().then(()=>wr)),Nn=Q(()=>Promise.resolve().then(()=>qr));function Ut(e){return e==="timeline"?"dashboard-timeline":e}function xn(e){const{baseUrl:s="",dashboardEndpoint:n="/__stats/api",debugEndpoint:r,authToken:o,backUrl:a="/",channelName:l="server-stats/dashboard"}=e,{features:i}=rn({baseUrl:s,debugEndpoint:r,authToken:o}),{theme:d,toggleTheme:c}=on(),[p,h]=x("overview"),[y,f]=x(()=>typeof window>"u"?!1:localStorage.getItem("ss-dash-sidebar")==="collapsed"),[_,g]=x(!1),[S,k]=x(0),w=V(0);H(()=>{if(!l)return;const $=Vs({baseUrl:s,channelName:l,authToken:o,onMessage:()=>{w.current+=1,k(w.current)},onConnect:()=>g(!0),onDisconnect:()=>g(!1),onError:()=>g(!1)});return()=>$.unsubscribe()},[s,l,o]);const u=i.customPanes||[],N=P($=>{const R=$.replace("#","").split("?")[0];return R&&[...dn,...u.map(m=>m.id)].includes(R)?R:"overview"},[u]);H(()=>{if(typeof window>"u")return;const $=N(window.location.hash);($!=="overview"||window.location.hash)&&h($)},[N]),H(()=>{const $=()=>{const R=N(window.location.hash);R!==p&&h(R)};return window.addEventListener("hashchange",$),()=>window.removeEventListener("hashchange",$)},[p,N]),H(()=>{typeof window>"u"||(window.location.hash=p)},[p]);const b=P(()=>{f($=>{const R=!$;return localStorage.setItem("ss-dash-sidebar",R?"collapsed":"expanded"),R})},[]),L=z(()=>[{id:"overview",label:"Overview",visible:!0},{id:"requests",label:"Requests",visible:!0},{id:"queries",label:"Queries",visible:!0},{id:"events",label:"Events",visible:!0},{id:"routes",label:"Routes",visible:!0},{id:"logs",label:"Logs",visible:!0},{id:"emails",label:"Emails",visible:!0},{id:"timeline",label:"Timeline",visible:i.tracing},{id:"cache",label:"Cache",visible:i.cache},{id:"jobs",label:"Jobs",visible:i.queues},{id:"config",label:"Config",visible:!0},{id:"internals",label:"Internals",visible:!0}],[i]),M=z(()=>L.filter($=>$.visible),[L]),q=z(()=>({baseUrl:s,dashboardEndpoint:n,authToken:o,refreshKey:S}),[s,n,o,S]),{data:O}=J("overview",q),U=z(()=>{if(!O)return{};const $={};if(O.totalRequests>0&&($.requests={count:O.totalRequests}),O.queryStats?.total>0&&($.queries={count:O.queryStats.total}),O.logLevelBreakdown){const R=O.logLevelBreakdown,ne=R.error+R.warn+R.info+R.debug;ne>0&&($.logs={count:ne})}return $},[O]),E=P(()=>{const $={overview:t(hn,{options:q}),requests:t(un,{options:q}),queries:t(pn,{options:q}),events:t(fn,{options:q}),routes:t(mn,{options:q}),logs:t(_n,{options:q}),emails:t(vn,{options:q}),timeline:t(yn,{options:q}),cache:t(gn,{options:q}),jobs:t(bn,{options:q}),config:t(wn,{options:q}),internals:t(Nn,{options:q,debugEndpoint:r})};return t("div",{className:"ss-dash-pane ss-dash-active",id:`ss-dash-pane-${p}`,children:t("div",{className:"ss-dash-pane-inner",children:t(Ce,{fallback:t("div",{className:"ss-dash-empty",children:"Loading..."}),children:$[p]||t("div",{className:"ss-dash-empty",children:"Unknown section"})})})})},[p,q]);return t("div",{className:"ss-dash","data-theme":d,id:"ss-dash",children:[t("div",{className:"ss-dash-header",children:[t("div",{className:"ss-dash-header-left",children:[t("span",{className:"ss-dash-logo",children:"Server Stats"}),t("span",{className:"ss-dash-logo-sub",children:"Dashboard"})]}),t("div",{className:"ss-dash-header-center",children:[t("span",{className:`ss-dash-live-dot ${_?"ss-dash-connected":""}`,id:"ss-dash-live-dot"}),t("span",{className:`ss-dash-live-label ${_?"ss-dash-connected":""}`,id:"ss-dash-live-label",children:_?"Live":"Polling"})]}),t("div",{className:"ss-dash-header-right",children:[t(cn,{theme:d,onToggle:c}),a&&t("a",{href:a,className:"ss-dash-back-link",title:"Back to app",children:"← App"})]})]}),t("div",{className:"ss-dash-body",children:[t("div",{className:`ss-dash-sidebar ${y?"ss-dash-collapsed":""}`,id:"ss-dash-sidebar",children:[t("nav",{className:"ss-dash-nav",children:[M.map($=>{const R=U[$.id];return t("button",{type:"button",className:`ss-dash-nav-item ${p===$.id?"ss-dash-active":""}`,"data-ss-section":$.id,onClick:()=>{$.id!==p&&h($.id)},title:y?$.label:void 0,children:[t("span",{className:"ss-dash-nav-icon",children:t("svg",{width:"20",height:"20",viewBox:(F[Ut($.id)]||F.config).viewBox,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",dangerouslySetInnerHTML:{__html:(F[Ut($.id)]||F.config).elements.join("")}})}),t("span",{className:"ss-dash-nav-label",children:$.label}),R&&R.count>0&&t("span",{className:`ss-dash-nav-badge${R.variant?" "+R.variant:""}`,children:R.count})]},$.id)}),u.length>0&&t("div",{className:"ss-dash-nav-sep"}),u.map($=>t("button",{type:"button",className:`ss-dash-nav-item ${p===$.id?"ss-dash-active":""}`,onClick:()=>h($.id),title:y?$.label:void 0,children:[t("span",{className:"ss-dash-nav-icon",children:t("svg",{width:"20",height:"20",viewBox:F["custom-pane"].viewBox,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",dangerouslySetInnerHTML:{__html:F["custom-pane"].elements.join("")}})}),t("span",{className:"ss-dash-nav-label",children:$.label})]},$.id))]}),t("button",{type:"button",className:"ss-dash-sidebar-toggle",id:"ss-dash-sidebar-toggle",onClick:b,title:y?"Expand sidebar":"Collapse sidebar",children:y?t("svg",{width:"16",height:"16",viewBox:F["chevron-right"].viewBox,fill:"none",stroke:"currentColor",strokeWidth:"1.5",dangerouslySetInnerHTML:{__html:F["chevron-right"].elements.join("")}}):t("svg",{width:"16",height:"16",viewBox:F["chevron-left"].viewBox,fill:"none",stroke:"currentColor",strokeWidth:"1.5",dangerouslySetInnerHTML:{__html:F["chevron-left"].elements.join("")}})})]}),t("div",{className:"ss-dash-main",children:E()})]})]})}function kn(e){const s=document.getElementById(e);return s?JSON.parse(s.textContent||"{}"):{}}function Sn(){function e(s){document.documentElement.setAttribute("data-theme",s)}e(Ee()),Bt(e)}const he=kn("ss-dash-config");Sn();const zt=document.getElementById("ss-dash");zt&&Ss(t(xn,{baseUrl:he.baseUrl,dashboardEndpoint:he.dashboardEndpoint,debugEndpoint:he.debugEndpoint,authToken:he.authToken,backUrl:he.backUrl,channelName:he.channelName}),zt);function $n(e){if(!e&&e!==0)return"-";const s=Math.floor(e),n=Math.floor(s/86400),r=Math.floor(s%86400/3600),o=Math.floor(s%3600/60);return n>0?`${n}d ${r}h`:r>0?`${r}h ${o}m`:o>0?`${o}m ${s%60}s`:`${s}s`}function re(e){return e>=1e3?`${(e/1e3).toFixed(2)}s`:e>=1?`${e.toFixed(0)}ms`:`${e.toFixed(2)}ms`}function ae(e){if(!e)return"-";const s=typeof e=="string"?new Date(e):new Date(e);return Number.isNaN(s.getTime())?"-":s.toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"})+"."+String(s.getMilliseconds()).padStart(3,"0")}function X(e){if(!e)return"-";const s=typeof e=="string"?new Date(e).getTime():e,n=Math.floor((Date.now()-s)/1e3);return n<0?"just now":n<60?`${n}s ago`:n<3600?`${Math.floor(n/60)}m ago`:n<86400?`${Math.floor(n/3600)}h ago`:`${Math.floor(n/86400)}d ago`}function Y(e){return e>Js?"very-slow":e>Ft?"slow":"normal"}function Je(e,s=100){if(e===null)return"null";if(e===void 0)return"-";if(typeof e=="string")return'"'+(e.length>40?e.slice(0,40)+"...":e)+'"';if(typeof e=="number"||typeof e=="boolean")return String(e);if(Array.isArray(e)){if(e.length===0)return"[]";const r="["+e.slice(0,3).map(o=>Je(o,30)).join(", ")+(e.length>3?", ..."+e.length+" items":"")+"]";return r.length>s?"["+e.length+" items]":r}if(typeof e=="object"){const n=Object.keys(e);if(n.length===0)return"{}";const r=[];for(let a=0;a<Math.min(n.length,4);a++)r.push(n[a]+": "+Je(e[n[a]],30));const o="{ "+r.join(", ")+(n.length>4?", ...+"+(n.length-4):"")+" }";return o.length>s?"{ "+n.slice(0,6).join(", ")+(n.length>6?", ...":"")+" }":o}return String(e)}function Cn(e){return e<0?"no expiry":e<60?`${e}s`:e<3600?`${Math.floor(e/60)}m`:e<86400?`${Math.floor(e/3600)}h`:`${Math.floor(e/86400)}d`}function Tn(e){return e<1024?`${e}B`:e<1024*1024?`${(e/1024).toFixed(1)}KB`:`${(e/(1024*1024)).toFixed(1)}MB`}const ie={color:"#34d399",fillOpacityTop:.25,fillOpacityBottom:.02,strokeWidth:1.5,width:120,height:32,padding:2};function Ln(e){return{...ie,...e}}function En(e,s=ie.width,n=ie.height,r=ie.padding){if(e.length<2)return null;const o=s-r*2,a=n-r*2,l=Math.min(...e),d=Math.max(...e)-l||1;return e.map((p,h)=>{const y=r+h/(e.length-1)*o,f=r+a-(p-l)/d*a;return`${y.toFixed(1)},${f.toFixed(1)}`}).join(" ")}function An(e,s=ie.width,n=ie.height,r=ie.padding){if(e.length<2)return null;const o=s-r*2,a=n-r*2,l=Math.min(...e),d=Math.max(...e)-l||1,c=e.map((f,_)=>{const g=r+_/(e.length-1)*o,S=r+a-(f-l)/d*a;return`${g.toFixed(1)},${S.toFixed(1)}`}),p=(r+o).toFixed(1),h=(r+a).toFixed(1),y=r.toFixed(1);return`M${c[0]} `+c.slice(1).map(f=>`L${f}`).join(" ")+` L${p},${h} L${y},${h} Z`}let Pn=0;function Mn(){return`ss-grad-${Pn++}`}function qn(e){if(e.length===0)return null;const s=Math.min(...e),n=Math.max(...e),r=e.reduce((o,a)=>o+a,0)/e.length;return{min:s,max:n,avg:r}}function Rn(e,s){const n=Ln(s),r=En(e,n.width,n.height,n.padding),o=An(e,n.width,n.height,n.padding);return!r||!o?null:{points:r,areaPath:o,gradientId:Mn(),options:n,stats:qn(e)}}function Ae({data:e,color:s="#34d399",width:n=120,height:r=32,className:o=""}){const a=z(()=>Rn(e,{width:n,height:r}),[e,n,r]),l=z(()=>"ss-grad-"+Math.random().toString(36).slice(2,8),[]),i={"--ss-accent":s};if(!a)return t("div",{className:`ss-dash-sparkline ${o}`,style:i,children:t("svg",{width:n,height:r,viewBox:`0 0 ${n} ${r}`,style:{display:"block"},children:t("text",{x:n/2,y:r/2+3,textAnchor:"middle",fill:"#737373",fontSize:"9",children:["collecting","…"]})})});const d=s||"var(--ss-accent)";return t("div",{className:`ss-dash-sparkline ${o}`,style:i,children:t("svg",{width:n,height:r,viewBox:`0 0 ${n} ${r}`,style:{display:"block"},children:[t("defs",{children:t("linearGradient",{id:l,x1:"0",y1:"0",x2:"0",y2:"1",children:[t("stop",{offset:"0%",stopColor:d,stopOpacity:"0.25"}),t("stop",{offset:"100%",stopColor:d,stopOpacity:"0.02"})]})}),t("path",{d:a.areaPath,fill:`url(#${l})`}),t("path",{className:"ss-dash-sparkline-line",d:"M"+a.points.replace(/ /g," L"),fill:"none",stroke:d,strokeWidth:"1.5",strokeLinejoin:"round",strokeLinecap:"round"})]})})}const Dn=[{value:"5m",label:"5m"},{value:"15m",label:"15m"},{value:"30m",label:"30m"},{value:"1h",label:"1h"},{value:"6h",label:"6h"},{value:"24h",label:"24h"},{value:"7d",label:"7d"}];function On({value:e,onChange:s,className:n=""}){return t("div",{className:`ss-dash-btn-group ${n}`,children:Dn.map(r=>t("button",{type:"button",className:`ss-dash-btn ${e===r.value?"ss-dash-active":""}`,onClick:()=>s(r.value),children:r.label},r.value))})}function Fn(e,s){if(e<=0)return[0];const n=e/s,r=Math.pow(10,Math.floor(Math.log10(n))),o=n/r;let a;o<=1?a=r:o<=2?a=2*r:o<=5?a=5*r:a=10*r;const l=[];for(let i=a;i<=e+a*.5;i+=a)l.push(Math.round(i));return l.length===0&&l.push(Math.ceil(e)),l}function Ht(e){try{return new Date(e).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit"})}catch{return""}}function Vt(e){if(e.length===0)return"";if(e.length===1)return`M${e[0].x},${e[0].y}`;let s=`M${e[0].x.toFixed(1)},${e[0].y.toFixed(1)}`;for(let n=0;n<e.length-1;n++){const r=e[n],o=e[n+1],a=(r.x+o.x)/2;s+=` C${a.toFixed(1)},${r.y.toFixed(1)} ${a.toFixed(1)},${o.y.toFixed(1)} ${o.x.toFixed(1)},${o.y.toFixed(1)}`}return s}function Wt(e){const s=Y(e);return s==="very-slow"?"ss-dash-very-slow":s==="slow"?"ss-dash-slow":""}function jn({chartPoints:e}){const s={top:12,right:12,bottom:28,left:38},n=220,r=V(null),[o,a]=x(0);H(()=>{const C=r.current;if(!C)return;a(C.clientWidth);const T=new ResizeObserver(j=>{for(const G of j)a(G.contentRect.width)});return T.observe(C),()=>T.disconnect()},[]);const l=o||600,i=l-s.left-s.right,d=n-s.top-s.bottom,c=s.top+d,p=e.map(C=>{const T=C;return(C.requestCount??0)+(Number(T.request_count)||0)||C.total||0}),h=e.map(C=>{const T=C;return(C.errorCount??0)+(Number(T.error_count)||0)}),y=Math.max(...p,1),f=Math.ceil(y*1.1),_=h.some(C=>C>0),g=Fn(f,4),S=g.length>0?g[g.length-1]:f,k=C=>s.left+C/Math.max(e.length-1,1)*i,w=C=>s.top+d-C/(S||1)*d,u=e.map((C,T)=>({x:k(T),y:w(p[T])})),N=e.map((C,T)=>({x:k(T),y:w(h[T])})),b=Vt(u),L=_?Vt(N):"",M=u.length>1?`${b} L${u[u.length-1].x.toFixed(1)},${c} L${u[0].x.toFixed(1)},${c} Z`:"",q=_&&N.length>1?`${L} L${N[N.length-1].x.toFixed(1)},${c} L${N[0].x.toFixed(1)},${c} Z`:"",O=Math.min(10,e.length),U=Math.max(1,Math.ceil(e.length/O)),[E,$]=x({visible:!1,x:0,idx:-1}),R=P(C=>{const T=u[C].x;$({visible:!0,x:T,idx:C})},[u]),ne=P(()=>{$({visible:!1,x:0,idx:-1})},[]),m=120,v=E.visible?Math.max(m/2,Math.min(E.x,l-m/2)):0;return t("div",{ref:r,style:{position:"relative"},children:[t("svg",{viewBox:`0 0 ${l} ${n}`,className:"ss-dash-chart-svg",children:[t("defs",{children:[t("linearGradient",{id:"ss-cg-total",x1:"0",y1:"0",x2:"0",y2:"1",children:[t("stop",{offset:"0%",stopColor:"var(--ss-accent)",stopOpacity:.3}),t("stop",{offset:"100%",stopColor:"var(--ss-accent)",stopOpacity:.02})]}),t("linearGradient",{id:"ss-cg-error",x1:"0",y1:"0",x2:"0",y2:"1",children:[t("stop",{offset:"0%",stopColor:"var(--ss-red-fg)",stopOpacity:.35}),t("stop",{offset:"100%",stopColor:"var(--ss-red-fg)",stopOpacity:.02})]})]}),g.map(C=>{const T=w(C);return t("g",{children:[t("line",{x1:s.left,y1:T,x2:l-s.right,y2:T,stroke:"var(--ss-border-faint)",strokeWidth:.5,strokeDasharray:"3,3"}),t("text",{x:s.left-6,y:T,textAnchor:"end",fill:"var(--ss-dim)",fontSize:9,dominantBaseline:"middle",children:C})]},`ytick-${C}`)}),M&&t("path",{d:M,fill:"url(#ss-cg-total)"}),b&&t("path",{d:b,fill:"none",stroke:"var(--ss-accent)",strokeWidth:1.5,strokeLinejoin:"round",strokeLinecap:"round"}),q&&t("path",{d:q,fill:"url(#ss-cg-error)"}),L&&t("path",{d:L,fill:"none",stroke:"var(--ss-red-fg)",strokeWidth:1.5,strokeLinejoin:"round",strokeLinecap:"round",strokeDasharray:"4,2"}),e.map((C,T)=>{const j=p[T],G=h[T],K=u[T].x,ce=u[T].y,_s=i/(e.length||1),vs=E.visible&&E.idx===T,ys=E.visible&&E.idx!==T;return t("g",{children:[t("rect",{x:K-_s/2,y:s.top,width:_s,height:d,fill:"transparent",className:"ss-dash-chart-hover-zone","data-idx":T,onMouseEnter:()=>R(T),onMouseLeave:ne}),j>0&&t("circle",{cx:K,cy:ce,r:vs?4:2.5,fill:"var(--ss-accent)",stroke:"var(--ss-surface)",strokeWidth:1,className:"ss-dash-chart-dot","data-idx":T,opacity:ys?.3:1}),G>0&&t("circle",{cx:K,cy:N[T].y,r:vs?3.5:2,fill:"var(--ss-red-fg)",stroke:"var(--ss-surface)",strokeWidth:1,className:"ss-dash-chart-dot ss-dash-chart-dot-err","data-idx":T,opacity:ys?.3:1})]},T)}),e.map((C,T)=>{if(T%U!==0&&T!==e.length-1)return null;const j=Ht(C.bucket);return j?t("text",{x:k(T),y:n-6,textAnchor:"middle",fill:"var(--ss-dim)",fontSize:9,children:j},`xlabel-${T}`):null})]}),E.visible&&E.idx>=0&&t("div",{className:"ss-dash-chart-tooltip",style:{left:v,top:s.top-4,transform:"translate(-50%, -100%)"},children:[t("div",{children:Ht(e[E.idx].bucket)}),t("div",{children:["Requests: ",p[E.idx]]}),h[E.idx]>0&&t("div",{style:{color:"var(--ss-red-fg)"},children:["Errors: ",h[E.idx]]})]})]})}function Kt({options:e={}}){const[s,n]=x("1h"),{data:r,isLoading:o}=J("overview",e),{data:a}=J("overview/chart",{...e,timeRange:s});if(o&&!r)return t("div",{className:"ss-dash-empty",children:"Loading overview..."});const l=r||{avgResponseTime:0,p95ResponseTime:0,requestsPerMinute:0,errorRate:0,totalRequests:0,slowestEndpoints:[],queryStats:{total:0,avgDuration:0,perRequest:0},recentErrors:[],topEvents:[],emailActivity:{sent:0,queued:0,failed:0},logLevelBreakdown:{error:0,warn:0,info:0,debug:0},cacheStats:null,jobQueueStatus:null,statusDistribution:{"2xx":0,"3xx":0,"4xx":0,"5xx":0},slowestQueries:[]},i=l,d=l.avgResponseTime||Number(i.avg_response_time)||0,c=l.p95ResponseTime||Number(i.p95_response_time)||0,p=l.requestsPerMinute||Number(i.requests_per_minute)||0,h=l.errorRate||Number(i.error_rate)||0,f=(l.totalRequests||Number(i.total_requests)||0)>0,_=a?.buckets||[],g=l.sparklines?.avgResponseTime??_.map(u=>u.avgDuration??0),S=l.sparklines?.p95ResponseTime??_.map(u=>u.p95Duration??0),k=l.sparklines?.requestsPerMinute??_.map(u=>u.requestCount??0),w=l.sparklines?.errorRate??_.map(u=>u.errorCount??0);return t("div",{className:"ss-dash-overview",children:[t("div",{className:"ss-dash-cards",children:[t("div",{className:"ss-dash-card",children:[t("div",{className:"ss-dash-card-title",children:"Avg Response Time"}),t("div",{className:`ss-dash-card-value ${f?d>500?"ss-dash-red":d>200?"ss-dash-amber":"ss-dash-accent":"ss-dash-dim"}`,children:f?re(d):"-"}),t("div",{className:"ss-dash-sparkline",children:t(Ae,{data:g,color:"#34d399",width:160,height:40})})]}),t("div",{className:"ss-dash-card",children:[t("div",{className:"ss-dash-card-title",children:"P95 Response Time"}),t("div",{className:`ss-dash-card-value ${f?c>500?"ss-dash-red":c>200?"ss-dash-amber":"ss-dash-accent":"ss-dash-dim"}`,children:f?re(c):"-"}),t("div",{className:"ss-dash-sparkline",children:t(Ae,{data:S,color:"#60a5fa",width:160,height:40})})]}),t("div",{className:"ss-dash-card",children:[t("div",{className:"ss-dash-card-title",children:"Requests / min"}),t("div",{className:`ss-dash-card-value ${f?"ss-dash-accent":"ss-dash-dim"}`,children:f?p.toFixed(1):"-"}),t("div",{className:"ss-dash-sparkline",children:t(Ae,{data:k,color:"#34d399",width:160,height:40})})]}),t("div",{className:"ss-dash-card",children:[t("div",{className:"ss-dash-card-title",children:"Error Rate"}),t("div",{className:`ss-dash-card-value ${f?h>5?"ss-dash-red":h>1?"ss-dash-amber":"ss-dash-accent":"ss-dash-dim"}`,children:f?`${h.toFixed(1)}%`:"-"}),t("div",{className:"ss-dash-sparkline",children:t(Ae,{data:w,color:"#f87171",width:160,height:40})})]})]}),t("div",{className:"ss-dash-chart-container",children:[t("div",{className:"ss-dash-chart-header",children:[t("span",{className:"ss-dash-chart-title",children:"Request Volume"}),t(On,{value:s,onChange:n})]}),t("div",{className:"ss-dash-chart",id:"ss-dash-chart-area",children:_.length===0?t("div",{className:"ss-dash-empty",style:{minHeight:"120px"},children:"No data for this range"}):t(jn,{chartPoints:_})}),t("div",{className:"ss-dash-chart-legend",id:"ss-dash-chart-legend",children:[t("span",{className:"ss-dash-chart-legend-item",children:[t("span",{className:"ss-dash-legend-dot",style:{background:"var(--ss-accent)"}}),"Requests"]}),_.some(u=>(u.errorCount??0)>0)&&t("span",{className:"ss-dash-chart-legend-item",children:[t("span",{className:"ss-dash-legend-dot",style:{background:"var(--ss-red-fg)"}}),"Errors"]})]})]}),t("div",{className:"ss-dash-secondary-cards",children:[t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#requests",className:"ss-dash-widget-link",children:"Slowest Endpoints"})}),l.slowestEndpoints.length===0?t("div",{className:"ss-dash-empty",style:{minHeight:"60px"},children:"No data yet"}):t("ul",{className:"ss-dash-secondary-list",children:l.slowestEndpoints.slice(0,5).map((u,N)=>{const b=u.url||u.pattern||"-";return t("li",{children:t("a",{href:`#requests?url=${encodeURIComponent(b)}`,className:"ss-dash-widget-row-link",children:[t("span",{title:b,children:b}),t("span",{className:`ss-dash-secondary-list-value ss-dash-duration ${Wt(u.avgDuration)}`,children:re(u.avgDuration)})]})},N)})})]}),t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#queries",className:"ss-dash-widget-link",children:"Query Stats"})}),t("ul",{className:"ss-dash-secondary-list",children:[t("li",{children:[t("span",{children:"Total Queries"}),t("span",{className:"ss-dash-secondary-list-value",children:l.queryStats.total})]}),t("li",{children:[t("span",{children:"Avg Duration"}),t("span",{className:"ss-dash-secondary-list-value",children:re(l.queryStats.avgDuration)})]}),t("li",{children:[t("span",{children:"Queries / Request"}),t("span",{className:"ss-dash-secondary-list-value",children:l.queryStats.perRequest.toFixed(1)})]})]})]}),t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#logs?level=error",className:"ss-dash-widget-link",children:"Recent Errors"})}),l.recentErrors.length===0?t("div",{className:"ss-dash-empty",style:{minHeight:"60px"},children:"No recent errors"}):t("ul",{className:"ss-dash-secondary-list",children:l.recentErrors.map((u,N)=>t("li",{children:t("a",{href:`#logs?id=${u.id??""}`,className:"ss-dash-widget-row-link",children:[t("span",{style:{color:"var(--ss-red-fg)"},title:u.message,children:u.message}),u.timestamp&&t("span",{className:"ss-dash-secondary-list-value",style:{color:"var(--ss-dim)"},title:ae(u.timestamp),children:X(u.timestamp)})]})},N))})]}),t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#events",className:"ss-dash-widget-link",children:"Top Events"})}),l.topEvents.length===0?t("div",{className:"ss-dash-empty",style:{minHeight:"60px"},children:"No events yet"}):t("ul",{className:"ss-dash-secondary-list",children:l.topEvents.slice(0,5).map((u,N)=>{const b=u.name||u.eventName||u.event_name||u.event||"";return t("li",{children:t("a",{href:`#events?event_name=${encodeURIComponent(b)}`,className:"ss-dash-widget-row-link",children:[t("span",{children:b}),t("span",{className:"ss-dash-secondary-list-value",children:u.count})]})},N)})})]}),t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#emails",className:"ss-dash-widget-link",children:"Email Activity"})}),t("ul",{className:"ss-dash-secondary-list",children:[t("li",{children:t("a",{href:"#emails?status=sent",className:"ss-dash-widget-row-link",children:[t("span",{children:"Sent"}),t("span",{className:"ss-dash-secondary-list-value",children:l.emailActivity.sent})]})}),t("li",{children:t("a",{href:"#emails?status=queued",className:"ss-dash-widget-row-link",children:[t("span",{children:"Queued"}),t("span",{className:"ss-dash-secondary-list-value",children:l.emailActivity.queued})]})}),t("li",{children:t("a",{href:"#emails?status=failed",className:"ss-dash-widget-row-link",children:[t("span",{children:"Failed"}),t("span",{className:"ss-dash-secondary-list-value",style:l.emailActivity.failed>0?{color:"var(--ss-red-fg)"}:void 0,children:l.emailActivity.failed})]})})]})]}),t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#logs",className:"ss-dash-widget-link",children:"Log Levels"})}),t("ul",{className:"ss-dash-secondary-list",children:[t("li",{children:t("a",{href:"#logs?level=error",className:"ss-dash-widget-row-link",children:[t("span",{style:{color:"var(--ss-red-fg)"},children:"Error"}),t("span",{className:"ss-dash-secondary-list-value",children:l.logLevelBreakdown.error})]})}),t("li",{children:t("a",{href:"#logs?level=warn",className:"ss-dash-widget-row-link",children:[t("span",{style:{color:"var(--ss-amber-fg)"},children:"Warn"}),t("span",{className:"ss-dash-secondary-list-value",children:l.logLevelBreakdown.warn})]})}),t("li",{children:t("a",{href:"#logs?level=info",className:"ss-dash-widget-row-link",children:[t("span",{style:{color:"var(--ss-green-fg)"},children:"Info"}),t("span",{className:"ss-dash-secondary-list-value",children:l.logLevelBreakdown.info})]})}),t("li",{children:t("a",{href:"#logs?level=debug",className:"ss-dash-widget-row-link",children:[t("span",{style:{color:"var(--ss-dim)"},children:"Debug"}),t("span",{className:"ss-dash-secondary-list-value",children:l.logLevelBreakdown.debug})]})})]})]}),l.cacheStats&&l.cacheStats.available&&t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#cache",className:"ss-dash-widget-link",children:"Cache"})}),t("ul",{className:"ss-dash-secondary-list",children:[t("li",{children:t("a",{href:"#cache",className:"ss-dash-widget-row-link",children:[t("span",{children:"Keys"}),t("span",{className:"ss-dash-secondary-list-value",children:l.cacheStats.totalKeys})]})}),t("li",{children:t("a",{href:"#cache",className:"ss-dash-widget-row-link",children:[t("span",{children:"Hit Rate"}),t("span",{className:"ss-dash-secondary-list-value",children:[l.cacheStats.hitRate.toFixed(1),"%"]})]})}),t("li",{children:t("a",{href:"#cache",className:"ss-dash-widget-row-link",children:[t("span",{children:"Memory"}),t("span",{className:"ss-dash-secondary-list-value",children:l.cacheStats.memoryUsedHuman})]})})]})]}),l.jobQueueStatus&&l.jobQueueStatus.available&&t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#jobs",className:"ss-dash-widget-link",children:"Job Queue"})}),t("ul",{className:"ss-dash-secondary-list",children:[t("li",{children:t("a",{href:"#jobs?status=active",className:"ss-dash-widget-row-link",children:[t("span",{children:"Active"}),t("span",{className:"ss-dash-secondary-list-value",children:l.jobQueueStatus.active})]})}),t("li",{children:t("a",{href:"#jobs?status=waiting",className:"ss-dash-widget-row-link",children:[t("span",{children:"Waiting"}),t("span",{className:"ss-dash-secondary-list-value",children:l.jobQueueStatus.waiting})]})}),t("li",{children:t("a",{href:"#jobs?status=failed",className:"ss-dash-widget-row-link",children:[t("span",{children:"Failed"}),t("span",{className:"ss-dash-secondary-list-value",style:l.jobQueueStatus.failed>0?{color:"var(--ss-red-fg)"}:void 0,children:l.jobQueueStatus.failed})]})}),t("li",{children:t("a",{href:"#jobs?status=completed",className:"ss-dash-widget-row-link",children:[t("span",{children:"Completed"}),t("span",{className:"ss-dash-secondary-list-value",children:l.jobQueueStatus.completed})]})})]})]}),t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#requests",className:"ss-dash-widget-link",children:"Response Status"})}),t("ul",{className:"ss-dash-secondary-list",children:[t("li",{children:t("a",{href:"#requests?status=2xx",className:"ss-dash-widget-row-link",children:[t("span",{style:{color:"var(--ss-green-fg)"},children:"2xx"}),t("span",{className:"ss-dash-secondary-list-value",children:l.statusDistribution["2xx"]})]})}),t("li",{children:t("a",{href:"#requests?status=3xx",className:"ss-dash-widget-row-link",children:[t("span",{style:{color:"var(--ss-blue-fg)"},children:"3xx"}),t("span",{className:"ss-dash-secondary-list-value",children:l.statusDistribution["3xx"]})]})}),t("li",{children:t("a",{href:"#requests?status=4xx",className:"ss-dash-widget-row-link",children:[t("span",{style:{color:"var(--ss-amber-fg)"},children:"4xx"}),t("span",{className:"ss-dash-secondary-list-value",children:l.statusDistribution["4xx"]})]})}),t("li",{children:t("a",{href:"#requests?status=5xx",className:"ss-dash-widget-row-link",children:[t("span",{style:{color:"var(--ss-red-fg)"},children:"5xx"}),t("span",{className:"ss-dash-secondary-list-value",children:l.statusDistribution["5xx"]})]})})]})]}),t("div",{className:"ss-dash-secondary-card",children:[t("div",{className:"ss-dash-secondary-card-title",children:t("a",{href:"#queries",className:"ss-dash-widget-link",children:"Slowest Queries"})}),l.slowestQueries.length===0?t("div",{className:"ss-dash-empty",style:{minHeight:"60px"},children:"No query data yet"}):t("ul",{className:"ss-dash-secondary-list",children:l.slowestQueries.slice(0,5).map((u,N)=>{const b=u.sqlNormalized||u.normalizedSql||u.sql_normalized||u.sql||"-";return t("li",{children:t("a",{href:`#queries?pattern=${encodeURIComponent(b)}`,className:"ss-dash-widget-row-link",children:[t("span",{title:b,children:b}),t("span",{className:`ss-dash-secondary-list-value ss-dash-duration ${Wt(u.avgDuration)}`,children:re(u.avgDuration)})]})},N)})})]})]})]})}const In=Object.freeze(Object.defineProperty({__proto__:null,OverviewSection:Kt,default:Kt},Symbol.toStringTag,{value:"Module"}));function Bn(e){if(!e)return[];if(typeof e=="string")try{return JSON.parse(e)}catch{return[]}return Array.isArray(e)?e:[]}function Un(e){if(!e)return[];if(typeof e=="string")try{return JSON.parse(e)}catch{return[]}return Array.isArray(e)?e:[]}function Qe(e,s,n,r=0){return e[s]||e[n]||r}function Jt(e){return{method:e.method||"",url:e.url||"",statusCode:Qe(e,"status_code","statusCode"),totalDuration:Qe(e,"total_duration","totalDuration")||e.duration||0,spanCount:Qe(e,"span_count","spanCount"),spans:Bn(e.spans),warnings:Un(e.warnings)}}function Ge(e="",s){const n=V(null);return P(()=>(n.current||(n.current=new We({baseUrl:e,authToken:s})),n.current),[e,s])}function zn({color:e="muted",children:s,className:n="",classPrefix:r="ss-dash"}){return t("span",{className:`${r}-badge ${r}-badge-${e} ${n}`,children:s})}function me({method:e,className:s="",classPrefix:n="ss-dash"}){return t("span",{className:`${n}-method ${n}-method-${e.toLowerCase()} ${s}`,children:e})}function Pe({code:e,className:s="",classPrefix:n="ss-dash"}){let r=`${n}-status-2xx`;return e>=500?r=`${n}-status-5xx`:e>=400?r=`${n}-status-4xx`:e>=300&&(r=`${n}-status-3xx`),t("span",{className:`${n}-status ${r} ${s}`,children:e})}const Hn="ss-col-resize",Qt="ss-resizing";function Gt(e){const s=Array.from(e.querySelectorAll("thead th"));if(s.length===0)return()=>{};const n=[];let r=!1;function o(){if(!r){r=!0;for(const a of s)a.style.width=a.offsetWidth+"px";e.style.tableLayout="fixed"}}for(const a of s){let l=function(d){d.preventDefault(),d.stopPropagation(),o();const c=d.clientX,p=a.offsetWidth;i.classList.add(Qt),i.setPointerCapture(d.pointerId);function h(f){const _=f.clientX-c,g=Math.max(30,p+_);a.style.width=g+"px"}function y(){i.classList.remove(Qt),i.removeEventListener("pointermove",h),i.removeEventListener("pointerup",y)}i.addEventListener("pointermove",h),i.addEventListener("pointerup",y)};if(!a.textContent?.trim())continue;const i=document.createElement("div");i.className=Hn,a.appendChild(i),i.addEventListener("pointerdown",l),n.push(()=>{i.removeEventListener("pointerdown",l),i.remove()})}return()=>{for(const a of n)a()}}function Vn(e=[]){const s=V(null),n=V(null);return H(()=>(s.current&&(n.current?.(),n.current=Gt(s.current)),()=>{n.current?.(),n.current=null}),e),P(o=>{n.current?.(),n.current=null,s.current=o,o&&(n.current=Gt(o))},[])}function te({columns:e,data:s,keyField:n="id",sort:r,sortDir:o,onSort:a,onRowClick:l,rowClassName:i,emptyMessage:d="No data",className:c="",renderAfterRow:p}){const h=P(f=>{a&&a(f)},[a]),y=Vn([s,e]);return s.length===0?t("div",{className:"ss-dash-empty",children:d}):t("table",{ref:y,className:`ss-dash-table ${c}`,children:[t("colgroup",{children:e.map(f=>t("col",{style:f.width?{width:f.width}:void 0},f.key))}),t("thead",{children:t("tr",{children:e.map(f=>t("th",{onClick:f.sortable?()=>h(f.key):void 0,className:f.sortable?"ss-dash-sortable":"",children:[f.label,r===f.key&&t("span",{className:"ss-dash-sort-arrow",children:o==="asc"?" ▲":" ▼"})]},f.key))})}),t("tbody",{children:s.map((f,_)=>{const g=i?typeof i=="function"?i(f):i:"",S=l?"ss-dash-clickable":"";return t(Us.Fragment,{children:[t("tr",{onClick:l?()=>l(f):void 0,className:`${S} ${g}`.trim(),children:e.map(k=>t("td",{children:k.render?k.render(f[k.key],f):f[k.key]??"-"},k.key))}),p?p(f,_):null]},f[n]??_)})})]})}function se({search:e,onSearchChange:s,placeholder:n="Search...",summary:r,children:o,className:a=""}){const l=P(()=>{s("")},[s]);return t("div",{className:`ss-dash-filter-bar ${a}`,children:[r!=null&&t("span",{className:"ss-dash-summary",children:r}),t("div",{className:"ss-dash-search-wrapper",children:[t("svg",{className:"ss-dash-search-icon",width:"14",height:"14",viewBox:F.search.viewBox,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",dangerouslySetInnerHTML:{__html:F.search.elements.join("")}}),t("input",{type:"text",className:"ss-dash-search",placeholder:n,value:e,onChange:i=>s(i.target.value)}),e&&t("button",{type:"button",className:"ss-dash-search-clear",onClick:l,children:"×"})]}),o&&t("div",{className:"ss-dash-filter-controls",children:o})]})}function oe({page:e,lastPage:s,total:n,onPageChange:r,className:o=""}){const a=z(()=>Zs(e,s),[e,s]),l=P(()=>{e>1&&r(e-1)},[e,r]),i=P(()=>{e<s&&r(e+1)},[e,s,r]);return s<=1?null:t("div",{className:`ss-dash-pagination ${o}`,children:[t("span",{className:"ss-dash-page-info",children:["Page ",e," of ",s," (",n," total)"]}),t("div",{className:"ss-dash-pagination-controls",children:[t("button",{type:"button",className:"ss-dash-page-btn",onClick:l,disabled:e<=1,children:"« Prev"}),a.map((d,c)=>d==="..."?t("span",{className:"ss-dash-page-ellipsis",children:"..."},`ellipsis-${c}`):t("button",{type:"button",className:`ss-dash-page-btn ${d===e?"ss-dash-active":""}`,onClick:()=>r(d),children:d},d)),t("button",{type:"button",className:"ss-dash-page-btn",onClick:i,disabled:e>=s,children:"Next »"})]})]})}const Yt={request:"#1e3a5f",middleware:"rgba(30, 58, 95, 0.7)",db:"#6d28d9",view:"#0e7490",mail:"#059669",event:"#b45309",custom:"var(--ss-dim)"},Wn={request:"Request",middleware:"Middleware",db:"DB",mail:"Mail",event:"Event",view:"View",custom:"Custom"};function Xt({spans:e,totalDuration:s,className:n="",warnings:r}){const o=e||[],a=z(()=>[...o].sort((i,d)=>i.startOffset-d.startOffset),[o]),l=z(()=>{const i={};for(const d of a)i[d.id]=d.parentId?(i[d.parentId]||0)+1:0;return i},[a]);return o.length===0?t("div",{className:"ss-dash-empty",children:"No spans recorded"}):t("div",{className:`ss-dash-tl-waterfall ${n}`,children:[t("div",{className:"ss-dash-tl-legend",children:Object.entries(Wn).map(([i,d])=>t("div",{className:"ss-dash-tl-legend-item",children:[t("span",{className:"ss-dash-tl-legend-dot",style:{background:Yt[i]||Yt.custom}}),t("span",{children:d})]},i))}),a.map(i=>{const d=s>0?i.startOffset/s*100:0,c=s>0?Math.max(i.duration/s*100,.5):1,p=l[i.id]||0,h=i.label.length>50?i.label.slice(0,50)+"...":i.label,y=i.category==="db"?"DB":i.category,f=i.category==="db"?"purple":i.category==="mail"?"green":i.category==="event"?"amber":i.category==="view"?"blue":"muted",_=i.metadata?Object.entries(i.metadata).filter(([,S])=>S!=null).map(([S,k])=>`${S}=${k}`).join(", "):"",g=_?`${i.label} (${i.duration.toFixed(2)}ms)
|
|
2
|
+
${_}`:`${i.label} (${i.duration.toFixed(2)}ms)`;return t("div",{className:"ss-dash-tl-row",children:[t("div",{className:"ss-dash-tl-label",title:g,style:{paddingLeft:8+p*16+"px"},children:[t("span",{className:`ss-dash-badge ss-dash-badge-${f}`,style:{fontSize:"9px",marginRight:"4px"},children:y}),h]}),t("div",{className:"ss-dash-tl-track",children:t("div",{className:`ss-dash-tl-bar ss-dash-tl-bar-${i.category||"custom"}`,style:{left:`${d}%`,width:`${c}%`},title:g})}),t("span",{className:"ss-dash-tl-dur",children:[i.duration.toFixed(2),"ms"]})]},i.id)}),r&&r.length>0&&t("div",{className:"ss-dash-tl-warnings",children:[t("div",{className:"ss-dash-tl-warnings-title",children:["Warnings (",r.length,")"]}),r.map((i,d)=>t("div",{className:"ss-dash-tl-warning",children:i},d))]})]})}function Zt({options:e={}}){const[s,n]=x(1),[r,o]=x(""),[a,l]=x("createdAt"),[i,d]=x("desc"),[c,p]=x(null),[h,y]=x(!1);H(()=>n(1),[r]);const{data:f,meta:_,isLoading:g,error:S}=J("requests",{...e,page:s,search:r,sort:a,sortDir:i}),k=Ge(e.baseUrl||"",e.authToken),w=P(b=>{const L=b.id;y(!0);const M=e.dashboardEndpoint||"/__stats/api";k().fetch(`${M}/requests/${L}`).then(q=>{p(q),y(!1)}).catch(()=>{y(!1)})},[k,e.dashboardEndpoint]),u=P(b=>{a===b?d(L=>L==="asc"?"desc":"asc"):(l(b),d("desc"))},[a]),N=f||[];if(c){const b=Jt(c);return t("div",{children:[t("div",{className:"ss-dash-tl-detail-header",children:[t("button",{type:"button",className:"ss-dash-btn",onClick:()=>p(null),children:"← Back to Requests"}),t(me,{method:b.method}),t("span",{style:{color:"var(--ss-text)"},children:b.url}),t(Pe,{code:b.statusCode}),t("span",{className:"ss-dash-tl-meta",children:[b.totalDuration.toFixed(1),"ms · ",b.spanCount," spans"]})]}),t(Xt,{spans:b.spans,totalDuration:b.totalDuration,warnings:b.warnings})]})}return h?t("div",{children:[t("div",{className:"ss-dash-tl-detail-header",children:t("button",{type:"button",className:"ss-dash-btn",onClick:()=>y(!1),children:"← Back to Requests"})}),t("div",{className:"ss-dash-empty",children:"Loading request detail..."})]}):t("div",{children:[t(se,{search:r,onSearchChange:o,placeholder:"Filter requests...",summary:`${_?.total??0} requests`}),S&&t("div",{className:"ss-dash-empty",children:"Failed to load requests"}),g&&!f?t("div",{className:"ss-dash-empty",children:"Loading requests..."}):t(D,{children:[t("div",{className:"ss-dash-table-wrap",children:t(te,{columns:[{key:"id",label:"#",width:"40px",render:b=>t("span",{style:{color:"var(--ss-dim)"},children:b})},{key:"method",label:"Method",width:"70px",sortable:!0,render:b=>t(me,{method:b})},{key:"url",label:"URL",sortable:!0,render:b=>t("span",{style:{color:"var(--ss-text)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},title:b,children:b})},{key:"statusCode",label:"Status",width:"60px",sortable:!0,render:(b,L)=>{const M=L.status_code||L.statusCode||L.statusCode;return t(Pe,{code:M})}},{key:"duration",label:"Duration",width:"80px",sortable:!0,render:(b,L)=>{const M=L.total_duration||L.totalDuration||L.duration||0;return t("span",{className:`ss-dash-duration ${Y(M)==="very-slow"?"ss-dash-very-slow":Y(M)==="slow"?"ss-dash-slow":""}`,children:[M.toFixed(1),"ms"]})}},{key:"spanCount",label:"Spans",width:"50px",render:(b,L)=>{const M=L.span_count||L.spanCount||0;return t("span",{style:{color:"var(--ss-muted)",textAlign:"center"},children:M})}},{key:"warningCount",label:"⚠",width:"40px",render:(b,L)=>{const M=L.warning_count||L.warningCount||0;return M>0?t("span",{style:{color:"var(--ss-amber-fg)",textAlign:"center",display:"block"},children:M}):t("span",{style:{color:"var(--ss-dim)",textAlign:"center",display:"block"},children:"-"})}},{key:"createdAt",label:"Time",width:"80px",sortable:!0,render:(b,L)=>{const M=L.createdAt||L.created_at||L.timestamp||"";return t("span",{className:"ss-dash-event-time",title:ae(M),children:X(M)})}}],data:N,sort:a,sortDir:i,onSort:u,onRowClick:w,emptyMessage:"No requests recorded yet"})}),_&&t(oe,{page:_.page,lastPage:_.lastPage,total:_.total,onPageChange:n})]})]})}const Kn=Object.freeze(Object.defineProperty({__proto__:null,RequestsSection:Zt,default:Zt},Symbol.toStringTag,{value:"Module"}));function es({node:e,depth:s=0}){if(!e)return null;const n=s*20,r=e["Node Type"]||"Unknown",o=e["Relation Name"]?t(D,{children:[" on ",t("strong",{children:e["Relation Name"]})]}):null,a=e.Alias&&e.Alias!==e["Relation Name"]?` (${e.Alias})`:"",l=e["Index Name"]?t(D,{children:[" using ",t("em",{children:e["Index Name"]})]}):null,i=[];if(e["Startup Cost"]!==null&&e["Startup Cost"]!==void 0&&i.push(`cost=${e["Startup Cost"]}..${e["Total Cost"]}`),e["Plan Rows"]!==null&&e["Plan Rows"]!==void 0&&i.push(`rows=${e["Plan Rows"]}`),e["Plan Width"]!==null&&e["Plan Width"]!==void 0&&i.push(`width=${e["Plan Width"]}`),e.Filter&&i.push(`filter: ${e.Filter}`),e["Index Cond"]&&i.push(`cond: ${e["Index Cond"]}`),e["Hash Cond"]&&i.push(`hash: ${e["Hash Cond"]}`),e["Join Type"]&&i.push(`join: ${e["Join Type"]}`),e["Sort Key"]){const c=Array.isArray(e["Sort Key"])?e["Sort Key"].join(", "):e["Sort Key"];i.push(`sort: ${c}`)}const d=e.Plans||[];return t("div",{className:"ss-dash-explain-node",style:{marginLeft:`${n}px`},children:[t("div",{className:"ss-dash-explain-node-header",children:[t("span",{className:"ss-dash-explain-node-type",children:r}),o,a,l]}),i.length>0&&t("div",{className:"ss-dash-explain-metrics",children:i.join(" · ")}),d.map((c,p)=>t(es,{node:c,depth:s+1},p))]})}function Jn({plan:e}){if(!e||!Array.isArray(e)||e.length===0)return t("div",{className:"ss-dash-explain-result",children:"No plan data returned"});const s=e[0];if(s&&s.Plan)return t("div",{className:"ss-dash-explain-result",children:t(es,{node:s.Plan,depth:0})});if(typeof s=="object"&&s!==null){const n=Object.keys(s);return t("div",{className:"ss-dash-explain-result",children:t("table",{children:[t("thead",{children:t("tr",{children:n.map(r=>t("th",{children:r},r))})}),t("tbody",{children:e.map((r,o)=>{const a=r;return t("tr",{children:n.map(l=>t("td",{children:a[l]!==null&&a[l]!==void 0?String(a[l]):"-"},l))},o)})})]})})}return t("div",{className:"ss-dash-explain-result",children:"No plan data returned"})}const Qn=8;function ts({options:e={}}){const[s,n]=x(1),[r,o]=x(""),[a,l]=x("createdAt"),[i,d]=x("desc"),[c,p]=x("list"),[h,y]=x(null),[f,_]=x(null),[g,S]=x(null),k=P(m=>{m!==c&&(p(m),n(1),l(m==="list"?"createdAt":"count"),d("desc"),y(null),_(null),S(null))},[c]);H(()=>n(1),[r]);const w=c==="grouped"?"queries/grouped":"queries",{data:u,meta:N,isLoading:b,mutate:L}=J(w,{...e,page:s,search:r,sort:a,sortDir:i}),M=P(m=>{a===m?d(v=>v==="asc"?"desc":"asc"):(l(m),d("desc"))},[a]),q=P(async m=>{if(f&&f.queryId===m){_(null);return}S(m);try{const v=await L(`queries/${m}/explain`);v&&v.error?_({queryId:m,plan:[],error:v.error,message:v.message}):_({queryId:m,plan:v?.plan||v?.rows||[]})}catch(v){console.warn("[ss] Query explain failed:",v),_({queryId:m,plan:[],error:v instanceof Error?v.message:String(v)})}finally{S(null)}},[L,f]),O=P(m=>{const v=m.id;return!f||f.queryId!==v?null:t("tr",{className:"ss-dash-explain-row",children:t("td",{colSpan:Qn,className:"ss-dash-explain",children:t("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"start"},children:[t("div",{style:{flex:1},children:f.error?t("div",{className:"ss-dash-explain-result ss-dash-explain-error",children:[t("strong",{children:"Error:"})," ",f.error,f.message&&t(D,{children:[t("br",{}),f.message]})]}):t(Jn,{plan:f.plan})}),t("button",{type:"button",className:"ss-dash-explain-btn",onClick:()=>_(null),style:{marginLeft:"8px",flexShrink:0},children:"Close"})]})})})},[f]),U=c==="grouped"?u?.groups||[]:u||[],E=z(()=>c!=="grouped"?U:U.map(m=>{const v={...m};return(v.sqlNormalized===null||v.sqlNormalized===void 0)&&(m.sql_normalized||m.pattern)&&(v.sqlNormalized=m.sql_normalized||m.pattern||""),(v.count===null||v.count===void 0)&&m.total_count!==null&&m.total_count!==void 0&&(v.count=m.total_count),(v.avgDuration===null||v.avgDuration===void 0)&&m.avg_duration!==null&&m.avg_duration!==void 0&&(v.avgDuration=m.avg_duration),(v.maxDuration===null||v.maxDuration===void 0)&&m.max_duration!==null&&m.max_duration!==void 0&&(v.maxDuration=m.max_duration),(v.minDuration===null||v.minDuration===void 0)&&m.min_duration!==null&&m.min_duration!==void 0&&(v.minDuration=m.min_duration),(v.totalDuration===null||v.totalDuration===void 0)&&m.total_duration!==null&&m.total_duration!==void 0&&(v.totalDuration=m.total_duration),(v.percentOfTotal===null||v.percentOfTotal===void 0)&&m.pct_time!==null&&m.pct_time!==void 0&&(v.percentOfTotal=m.pct_time),v}),[U,c]),$=z(()=>{const m=N?.total??E.length;let v=0,C=0,T=0,j=0;for(const K of E){const ce=K.duration||0;T+=ce,j++,ce>Ft&&v++}const G=new Map;for(const K of E){const ce=K.sqlNormalized||K.sql||K.sql_text||"";G.set(ce,(G.get(ce)||0)+1)}for(const K of G.values())K>1&&(C+=K);return{total:m,slow:v,duplicates:C,avgDuration:j>0?T/j:0}},[E,N]),R=z(()=>{const m=new Map;for(const v of E){const C=v.sqlNormalized||v.sql||v.sql_text||"";m.set(C,(m.get(C)||0)+1)}return m},[E]),ne=z(()=>{if(c==="grouped")return`${E.length} query patterns`;const m=[`${$.total} queries`];return $.slow>0&&m.push(`${$.slow} slow`),$.duplicates>0&&m.push(`${$.duplicates} dup`),m.push(`avg ${($.avgDuration||0).toFixed(1)}ms`),m.join(", ")},[c,E.length,$]);return t("div",{children:[t(se,{search:r,onSearchChange:o,placeholder:"Filter queries...",summary:ne,children:t("div",{className:"ss-dash-btn-group",children:[t("button",{type:"button",className:`ss-dash-btn ${c==="list"?"ss-dash-active":""}`,onClick:()=>k("list"),children:"List"}),t("button",{type:"button",className:`ss-dash-btn ${c==="grouped"?"ss-dash-active":""}`,onClick:()=>k("grouped"),children:"Grouped"})]})}),b&&!u?t("div",{className:"ss-dash-empty",children:"Loading queries..."}):c==="grouped"?t(D,{children:t("div",{className:"ss-dash-table-wrap",children:t(te,{columns:[{key:"sqlNormalized",label:"Pattern",render:(m,v)=>{const C=m||"",T=(v.count||0)>=3;return t(D,{children:[t("span",{className:`ss-dash-sql ${h===C?"ss-dash-expanded":""}`,title:"Click to expand",onClick:j=>{j.stopPropagation(),y(h===C?null:C)},role:"button",tabIndex:0,onKeyDown:j=>j.key==="Enter"&&y(h===C?null:C),children:C}),T&&t(D,{children:[" ",t("span",{className:"ss-dash-dup",children:"DUP"})]})]})}},{key:"count",label:"Count",width:"60px",sortable:!0,render:m=>t("span",{style:{color:"var(--ss-muted)",textAlign:"center",display:"block"},children:m||0})},{key:"avgDuration",label:"Avg",width:"70px",sortable:!0,render:m=>{const v=m||0;return t("span",{className:`ss-dash-duration ${Y(v)==="very-slow"?"ss-dash-very-slow":Y(v)==="slow"?"ss-dash-slow":""}`,children:v.toFixed(2)+"ms"})}},{key:"minDuration",label:"Min",width:"70px",render:m=>t("span",{className:"ss-dash-duration",children:(m||0).toFixed(2)+"ms"})},{key:"maxDuration",label:"Max",width:"70px",render:m=>{const v=m||0;return t("span",{className:`ss-dash-duration ${Y(v)==="very-slow"?"ss-dash-very-slow":Y(v)==="slow"?"ss-dash-slow":""}`,children:v.toFixed(2)+"ms"})}},{key:"totalDuration",label:"Total",width:"70px",sortable:!0,render:m=>t("span",{className:"ss-dash-duration",children:(m||0).toFixed(1)+"ms"})},{key:"percentOfTotal",label:"% Time",width:"60px",render:m=>t("span",{style:{color:"var(--ss-muted)",textAlign:"center",display:"block"},children:(m||0).toFixed(1)+"%"})}],data:E,keyField:"sqlNormalized",sort:a,sortDir:i,onSort:M,emptyMessage:"No queries recorded"})})}):t(D,{children:[t("div",{className:"ss-dash-table-wrap",children:t(te,{columns:[{key:"id",label:"#",width:"40px",render:m=>t("span",{style:{color:"var(--ss-dim)"},children:m})},{key:"sql",label:"SQL",render:(m,v)=>{const C=v.sql||v.sql_text||"";return t("div",{children:[t("span",{className:`ss-dash-sql ${h===v.id?"ss-dash-expanded":""}`,title:"Click to expand",onClick:T=>{T.stopPropagation(),y(h===v.id?null:v.id)},role:"button",tabIndex:0,onKeyDown:T=>T.key==="Enter"&&y(h===v.id?null:v.id),children:C}),(R.get((v.sqlNormalized||v.sql||v.sql_text)??"")??0)>1&&t("span",{className:"ss-dash-dup",children:["×",R.get((v.sqlNormalized||v.sql||v.sql_text)??"")]})]})}},{key:"duration",label:"Duration",width:"70px",sortable:!0,render:m=>{const v=m||0;return t("span",{className:`ss-dash-duration ${Y(v)==="very-slow"?"ss-dash-very-slow":Y(v)==="slow"?"ss-dash-slow":""}`,children:v.toFixed(2)+"ms"})}},{key:"method",label:"Method",width:"60px",render:(m,v)=>{const C=v.method||v.sql_method||"";return t("span",{className:`ss-dash-method ss-dash-method-${C.toLowerCase()}`,children:C})}},{key:"model",label:"Model",width:"90px",render:m=>t("span",{style:{color:"var(--ss-muted)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},title:m,children:m||"-"})},{key:"connection",label:"Connection",width:"80px",render:m=>t("span",{style:{color:"var(--ss-dim)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:m||"-"})},{key:"createdAt",label:"Time",width:"90px",sortable:!0,render:(m,v)=>{const C=m||v.created_at||v.timestamp||"";return t("span",{className:"ss-dash-event-time",title:ae(C),children:X(C)})}},{key:"id",label:"",width:"70px",render:(m,v)=>{if((v.method||v.sql_method||"")!=="select")return null;const T=f?.queryId===v.id&&!f?.error;return t("button",{type:"button",className:`ss-dash-explain-btn${T?" ss-dash-explain-btn-active":""}`,onClick:j=>{j.stopPropagation(),q(v.id)},disabled:g===v.id,children:g===v.id?"...":"EXPLAIN"})}}],data:E,sort:a,sortDir:i,onSort:M,emptyMessage:"No queries recorded",renderAfterRow:O})}),N&&t(oe,{page:N.page,lastPage:N.lastPage,total:N.total,onPageChange:n})]})]})}const Gn=Object.freeze(Object.defineProperty({__proto__:null,QueriesSection:ts,default:ts},Symbol.toStringTag,{value:"Module"}));function Ye({data:e,maxPreviewLength:s=100,className:n="",classPrefix:r="ss-dash"}){const[o,a]=x(!1),l=z(()=>{if(typeof e=="string")try{return JSON.parse(e)}catch{return e}return e},[e]),i=z(()=>typeof l=="object"&&l!==null?Je(l,s):String(l??"-"),[l,s]),d=z(()=>typeof l=="object"&&l!==null?JSON.stringify(l,null,2):String(l),[l]),c=P(()=>{a(h=>!h)},[]),p=P(async()=>{try{await navigator.clipboard.writeText(d)}catch{}},[d]);return!e&&e!==0&&e!==!1?t("span",{className:`ss-dim ${r}-c-dim`,children:"-"}):t("div",{className:`${r}-data-cell ${n}`,children:[t("span",{className:`${r}-data-preview`,onClick:c,role:"button",tabIndex:0,onKeyDown:h=>h.key==="Enter"&&c(),children:i}),o&&t("div",{className:`${r}-data-full`,onClick:c,children:[t("button",{className:`${r}-copy-btn`,onClick:h=>{h.stopPropagation(),p()},title:"Copy to clipboard",type:"button",children:"Copy"}),t("pre",{children:d})]})]})}function ss({options:e={}}){const[s,n]=x(1),[r,o]=x(""),{data:a,meta:l,isLoading:i}=J("events",{...e,page:s,search:r}),d=a||[];return H(()=>n(1),[r]),t("div",{children:[t(se,{search:r,onSearchChange:o,placeholder:"Filter events...",summary:`${l?.total??0} events`}),i&&!a?t("div",{className:"ss-dash-empty",children:"Loading events..."}):t(D,{children:[t("div",{className:"ss-dash-table-wrap",children:t(te,{columns:[{key:"id",label:"#",width:"40px",render:c=>t("span",{style:{color:"var(--ss-dim)"},children:c})},{key:"eventName",label:"Event",render:(c,p)=>{const h=p.event_name||p.eventName||p.event||"";return t("span",{className:"ss-dash-event-name",title:h,style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",display:"block"},children:h})}},{key:"data",label:"Data",render:c=>t(Ye,{data:c,maxPreviewLength:80,className:"ss-dash-event-data"})},{key:"createdAt",label:"Time",width:"80px",render:(c,p)=>{const h=p.createdAt||p.created_at||p.timestamp;return t("span",{className:"ss-dash-event-time",title:ae(h),children:X(h)})}}],data:d,emptyMessage:"No events recorded yet"})}),l&&t(oe,{page:l.page,lastPage:l.lastPage,total:l.total,onPageChange:n})]})]})}const Yn=Object.freeze(Object.defineProperty({__proto__:null,EventsSection:ss,default:ss},Symbol.toStringTag,{value:"Module"}));function ns({options:e={}}){const[s,n]=x(""),{data:r,isLoading:o,error:a}=J("routes",{...e,search:s}),l=r,i=Array.isArray(l)?l:l&&Array.isArray(l.routes)?l.routes:l&&Array.isArray(l.data)?l.data:[],d={overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"};return t("div",{children:[t(se,{search:s,onSearchChange:n,placeholder:"Filter routes...",summary:`${i.length} routes`}),a?t("div",{className:"ss-dash-empty",children:"Failed to load routes"}):o&&!r?t("div",{className:"ss-dash-empty",children:"Loading routes..."}):t(D,{children:t("div",{className:"ss-dash-table-wrap",children:t(te,{columns:[{key:"method",label:"Method",width:"70px",render:c=>t(me,{method:c})},{key:"pattern",label:"Pattern",render:c=>t("span",{style:{color:"var(--ss-text)",...d},title:c,children:c})},{key:"name",label:"Name",width:"120px",render:c=>t("span",{style:{color:"var(--ss-muted)",...d},title:c||"-",children:c||"-"})},{key:"handler",label:"Handler",render:c=>t("span",{style:{color:"var(--ss-sql-color)",...d},title:c,children:c})},{key:"middleware",label:"Middleware",render:c=>{const p=c?.length?c.join(", "):"-";return t("span",{style:{color:"var(--ss-dim)",fontSize:"10px",...d},title:p,children:p})}}],data:i,keyField:"pattern",emptyMessage:"No routes available"})})})]})}const Xn=Object.freeze(Object.defineProperty({__proto__:null,RoutesSection:ns,default:ns},Symbol.toStringTag,{value:"Module"})),Zn=["all","error","warn","info","debug"];function er(e){return(e.levelName||e.level_name||(typeof e.level=="string"?e.level:"")||"info").toLowerCase()}function tr(e){return e.msg||e.message||JSON.stringify(e)}function sr(e){return e.createdAt||e.created_at||e.time||e.timestamp||0}function nr(e){const s=e.data||{};return e.requestId||e.request_id||e["x-request-id"]||s.requestId||s.request_id||s["x-request-id"]||""}function rr(e,s="ss-dbg-log-level"){switch(e){case"error":case"fatal":return`${s}-error`;case"warn":return`${s}-warn`;case"info":return`${s}-info`;case"debug":return`${s}-debug`;case"trace":return`${s}-trace`;default:return`${s}-info`}}function rs({options:e={}}){const[s,n]=x(1),[r,o]=x(""),[a,l]=x("all"),[i,d]=x(""),[c,p]=x(""),[h,y]=x([]),[f,_]=x("level"),[g,S]=x("equals"),[k,w]=x(""),u={};a!=="all"&&(u.level=a),i&&(u.request_id=i),h.forEach((m,v)=>{u[`filter_field_${v}`]=m.field,u[`filter_op_${v}`]=m.operator,u[`filter_value_${v}`]=m.value});const{data:N,meta:b,isLoading:L}=J("logs",{...e,page:s,search:r,filters:u}),M=N||[],q=P(m=>{d(m),p(m),n(1)},[]),O=P(()=>{const m=c.trim();d(m),n(1)},[c]),U=P(()=>{d(""),p(""),n(1)},[]),E=P(()=>{l("all"),n(1)},[]),$=P(()=>{const m=k.trim();m&&(y(v=>[...v,{field:f,operator:g,value:m}]),w(""))},[f,g,k]),R=P(m=>{y(v=>v.filter((C,T)=>T!==m))},[]),ne=a!=="all"||i!==""||h.length>0;return t("div",{children:[t(se,{search:r,onSearchChange:o,placeholder:"Search logs...",summary:`${b?.total??0} logs`,children:t("div",{className:"ss-dash-log-filters",children:[Zn.map(m=>t("button",{type:"button",className:`ss-dash-log-filter ${a===m?"ss-dash-active":""}`,onClick:()=>{l(m),n(1)},children:m},m)),t("input",{type:"text",className:"ss-dash-filter-input ss-dash-reqid-input",placeholder:"Filter by request ID...",value:c,onChange:m=>p(m.target.value),onKeyDown:m=>m.key==="Enter"&&O()}),(c||i)&&t("button",{type:"button",className:"ss-dash-btn ss-dash-reqid-clear",onClick:()=>{U()},children:"Clear"})]})}),t("div",{className:"ss-dash-structured-search",children:[t("select",{className:"ss-dash-filter-select",value:f,onChange:m=>_(m.target.value),children:[t("option",{value:"level",children:"level"}),t("option",{value:"message",children:"message"}),t("option",{value:"request_id",children:"request_id"}),t("option",{value:"userId",children:"userId"}),t("option",{value:"email",children:"email"}),t("option",{value:"path",children:"path"})]}),t("select",{className:"ss-dash-filter-select",value:g,onChange:m=>S(m.target.value),children:[t("option",{value:"equals",children:"equals"}),t("option",{value:"contains",children:"contains"}),t("option",{value:"starts_with",children:"starts with"})]}),t("input",{className:"ss-dash-filter-input",placeholder:"Value...",value:k,onChange:m=>w(m.target.value),onKeyDown:m=>m.key==="Enter"&&$()}),t("button",{type:"button",className:"ss-dash-btn",onClick:$,children:"Add"})]}),ne&&t("div",{className:"ss-dash-filter-chips",children:[a!=="all"&&t("span",{className:"ss-dash-filter-chip",children:["level: ",a,t("button",{type:"button",className:"ss-dash-filter-chip-remove",onClick:E,children:"×"})]}),i&&t("span",{className:"ss-dash-filter-chip",children:["requestId: ",i.slice(0,8),"...",t("button",{type:"button",className:"ss-dash-filter-chip-remove",onClick:U,children:"×"})]}),h.map((m,v)=>t("span",{className:"ss-dash-filter-chip",children:[m.field," ",m.operator,' "',m.value,'"',t("button",{type:"button",className:"ss-dash-filter-chip-remove",onClick:()=>R(v),children:"×"})]},v))]}),L&&!N?t("div",{className:"ss-dash-empty",children:"Loading logs..."}):M.length===0?t("div",{className:"ss-dash-empty",children:["No log entries",i?` matching request ${i}`:a!=="all"?` for ${a}`:""]}):t("div",{className:"ss-dash-log-entries",children:M.map((m,v)=>{const C=er(m),T=tr(m),j=nr(m),G=sr(m);return t("div",{className:"ss-dash-log-entry",children:[t("span",{className:`ss-dash-log-level ${rr(C,"ss-dash-log-level")}`,children:C.toUpperCase()}),t("span",{className:"ss-dash-log-time",title:G?ae(G):"",children:G?X(G):"-"}),j?t("span",{className:"ss-dash-log-reqid",title:j,onClick:()=>q(j),role:"button",tabIndex:0,onKeyDown:K=>K.key==="Enter"&&q(j),children:j.slice(0,8)}):t("span",{className:"ss-dash-log-reqid-empty",children:"--"}),t("span",{className:"ss-dash-log-msg",children:T})]},m.id||v)})}),b&&t(oe,{page:b.page,lastPage:b.lastPage,total:b.total,onPageChange:n})]})}const ar=Object.freeze(Object.defineProperty({__proto__:null,LogsSection:rs,default:rs},Symbol.toStringTag,{value:"Module"}));function as({options:e={}}){const[s,n]=x(1),[r,o]=x(""),[a,l]=x(null),[i,d]=x(null),{data:c,meta:p,isLoading:h}=J("emails",{...e,page:s,search:r}),y=c||[];H(()=>n(1),[r]);const f=P(async _=>{if(_.html){l(_.id),d(_.html);return}try{const{baseUrl:g="",dashboardEndpoint:S="/__stats/api",authToken:k}=e,w=`${g}${S}/emails/${_.id}/preview`,u={Accept:"text/html"};k&&(u.Authorization=`Bearer ${k}`);const b=await(await fetch(w,{headers:u,credentials:"same-origin"})).text();l(_.id),d(b)}catch{}},[e]);if(a&&i){const _=y.find(g=>g.id===a);return t("div",{className:"ss-dash-email-preview",id:"ss-dash-email-preview",children:[t("div",{className:"ss-dash-email-preview-header",children:[t("div",{className:"ss-dash-email-preview-meta",id:"ss-dash-email-preview-meta",children:_&&t(D,{children:[t("strong",{children:"Subject:"})," ",_.subject," | ",t("strong",{children:"From:"})," ",_.from_addr||_.from," | ",t("strong",{children:"To:"})," ",_.to_addr||_.to,(_.cc||_.cc_addr)&&t(D,{children:[" | ",t("strong",{children:"CC:"})," ",_.cc||_.cc_addr]})," | ",t("strong",{children:"Status:"})," ",t("span",{className:`ss-dash-badge ss-dash-email-status-${_.status}`,children:_.status}),_.mailer&&t(D,{children:[" | ",t("strong",{children:"Mailer:"})," ",_.mailer]})]})}),t("button",{type:"button",className:"ss-dash-btn",id:"ss-dash-email-preview-close",onClick:()=>{l(null),d(null)},children:"Close"})]}),t("iframe",{className:"ss-dash-email-iframe",id:"ss-dash-email-iframe",srcDoc:i,title:"Email preview",sandbox:""})]})}return t("div",{children:[t(se,{search:r,onSearchChange:o,placeholder:"Filter emails...",summary:`${p?.total??0} emails`}),h&&!c?t("div",{className:"ss-dash-empty",children:"Loading emails..."}):t(D,{children:[t("div",{className:"ss-dash-table-wrap",children:t(te,{columns:[{key:"id",label:"#",width:"40px",render:_=>t("span",{style:{color:"var(--ss-dim)"},children:_})},{key:"from",label:"From",width:"150px",render:(_,g)=>{const S=g.from_addr||g.from||"";return t("span",{title:S,style:{color:"var(--ss-text-secondary)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",display:"block"},children:S})}},{key:"to",label:"To",width:"150px",render:(_,g)=>{const S=g.to_addr||g.to||"";return t("span",{title:S,style:{color:"var(--ss-text-secondary)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",display:"block"},children:S})}},{key:"subject",label:"Subject",render:_=>{const g=_||"";return t("span",{title:g,style:{color:"var(--ss-sql-color)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",display:"block"},children:g})}},{key:"status",label:"Status",width:"80px",render:_=>{const g=_||"";return t("span",{className:`ss-dash-badge ss-dash-email-status-${g}`,children:g})}},{key:"attachmentCount",label:"ATT",width:"40px",render:(_,g)=>{const S=g.attachment_count||g.attachmentCount||0;return S>0?t("span",{style:{color:"var(--ss-dim)",textAlign:"center",display:"block"},children:S}):t("span",{style:{color:"var(--ss-dim)",textAlign:"center",display:"block"},children:"-"})}},{key:"mailer",label:"Mailer",width:"70px",render:_=>{const g=_||"";return t("span",{title:g,style:{color:"var(--ss-muted)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",display:"block"},children:g})}},{key:"createdAt",label:"Time",width:"80px",render:(_,g)=>{const S=g.createdAt||g.created_at||g.timestamp;return t("span",{className:"ss-dash-event-time",style:{whiteSpace:"nowrap"},title:ae(S),children:X(S)})}}],data:y,onRowClick:f,rowClassName:"ss-dash-email-row",emptyMessage:"No emails captured yet"})}),p&&t(oe,{page:p.page,lastPage:p.lastPage,total:p.total,onPageChange:n})]})]})}const lr=Object.freeze(Object.defineProperty({__proto__:null,EmailsSection:as,default:as},Symbol.toStringTag,{value:"Module"}));function ls({options:e={},tracingEnabled:s=!0}){const[n,r]=x(1),[o,a]=x(""),[l,i]=x(null),[d,c]=x(null),[p,h]=x(!1),{data:y,meta:f,isLoading:_,error:g}=J("traces",{...e,page:n,search:o}),S=y||[],k=Ge(e.baseUrl||"",e.authToken);H(()=>{if(!l){c(null);return}let u=!1;h(!0);const N=e.dashboardEndpoint||"/__stats/api";return k().fetch(`${N}/traces/${l}`).then(b=>{u||(c(b),h(!1))}).catch(()=>{u||h(!1)}),()=>{u=!0}},[l,k,e.dashboardEndpoint]);const w=P(()=>i(null),[]);if(!s)return t("div",{className:"ss-dash-empty",children:"Tracing is not enabled. Enable tracing in your server-stats config to use the timeline."});if(l&&d){const u=Jt(d);return t("div",{children:[t("div",{className:"ss-dash-tl-detail-header",children:[t("button",{type:"button",className:"ss-dash-btn",onClick:w,children:"← Back"}),t(me,{method:u.method}),t("span",{style:{color:"var(--ss-text)"},children:u.url}),t(Pe,{code:u.statusCode}),t("span",{className:"ss-dash-tl-meta",children:[u.totalDuration.toFixed(1),"ms · ",u.spanCount," spans"]})]}),t(Xt,{spans:u.spans,totalDuration:u.totalDuration,warnings:u.warnings})]})}return l&&p?t("div",{children:[t("div",{className:"ss-dash-tl-detail-header",children:t("button",{type:"button",className:"ss-dash-btn",onClick:w,children:"← Back"})}),t("div",{className:"ss-dash-empty",children:"Loading trace detail..."})]}):t("div",{children:[t(se,{search:o,onSearchChange:a,placeholder:"Filter traces...",summary:`${f?.total??0} traces`}),g&&t("div",{className:"ss-dash-empty",children:"Failed to load traces"}),_&&!y?t("div",{className:"ss-dash-empty",children:"Loading traces..."}):t(D,{children:[t("div",{className:"ss-dash-table-wrap",children:t(te,{columns:[{key:"id",label:"#",width:"40px",render:u=>t("span",{style:{color:"var(--ss-dim)"},children:u})},{key:"method",label:"Method",width:"70px",render:u=>t(me,{method:u})},{key:"url",label:"URL",render:u=>t("span",{style:{color:"var(--ss-text)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},title:u,children:u})},{key:"statusCode",label:"Status",width:"60px",render:u=>t(Pe,{code:u})},{key:"totalDuration",label:"Duration",width:"80px",render:u=>t("span",{className:`ss-dash-duration ${Y(u)==="very-slow"?"ss-dash-very-slow":Y(u)==="slow"?"ss-dash-slow":""}`,children:[u.toFixed(1),"ms"]})},{key:"spanCount",label:"Spans",width:"50px",render:u=>t("span",{style:{color:"var(--ss-muted)",textAlign:"center"},children:u})},{key:"createdAt",label:"Time",width:"80px",render:u=>t("span",{className:"ss-dash-event-time",title:ae(u),children:X(u)})}],data:S,onRowClick:u=>i(u.id),emptyMessage:"No traces recorded"})}),f&&t(oe,{page:f.page,lastPage:f.lastPage,total:f.total,onPageChange:r})]})]})}const ir=Object.freeze(Object.defineProperty({__proto__:null,TimelineSection:ls,default:ls},Symbol.toStringTag,{value:"Module"}));function is({options:e={}}){const[s,n]=x(""),[r,o]=x(null),[a,l]=x(null),[i,d]=x(!1),[c,p]=x(null),{data:h,isLoading:y,mutate:f,getApi:_}=J("cache",{...e,search:s}),g=h,S=P(async w=>{if(confirm(`Delete cache key "${w}"?`))try{await f(`cache/${encodeURIComponent(w)}`,"delete"),r===w&&(o(null),l(null),p(null))}catch{}},[f,r]),k=P(async w=>{if(r===w){o(null),l(null),p(null);return}o(w),l(null),p(null),d(!0);try{const N=await _().fetchCacheKey(w);l(N.value!==void 0?N.value:N.data!==void 0?N.data:N),p(null)}catch{l(null),p("Failed to fetch key value")}finally{d(!1)}},[r,_]);return t("div",{children:[g?.available&&g?.stats&&t("div",{className:"ss-dash-cache-stats",children:[t("div",{className:"ss-dash-cache-stat",children:[t("span",{className:"ss-dash-cache-stat-label",children:"Hit Rate:"}),t("span",{className:"ss-dash-cache-stat-value",children:[(g.stats.hitRate??0).toFixed(1),"%"]})]}),t("div",{className:"ss-dash-cache-stat",children:[t("span",{className:"ss-dash-cache-stat-label",children:"Hits:"}),t("span",{className:"ss-dash-cache-stat-value",children:g.stats.hits??0})]}),t("div",{className:"ss-dash-cache-stat",children:[t("span",{className:"ss-dash-cache-stat-label",children:"Misses:"}),t("span",{className:"ss-dash-cache-stat-value",children:g.stats.misses??0})]}),t("div",{className:"ss-dash-cache-stat",children:[t("span",{className:"ss-dash-cache-stat-label",children:"Keys:"}),t("span",{className:"ss-dash-cache-stat-value",children:g.stats.totalKeys||g.stats.keyCount||g.keys?.length||0})]})]}),t(se,{search:s,onSearchChange:n,placeholder:"Filter cache keys...",summary:`${(g?.keys||g?.data||[]).length} keys`}),y&&!h?t("div",{className:"ss-dash-empty",children:"Loading cache..."}):!g||!g.available?t("div",{className:"ss-dash-empty",children:"Cache inspector not available"}):t("div",{className:"ss-dash-table-wrap",children:t(te,{columns:[{key:"key",label:"Key",render:w=>t("span",{title:w,style:{color:"var(--ss-sql-color)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",display:"block"},children:w})},{key:"type",label:"Type",width:"70px",render:w=>t("span",{style:{color:"var(--ss-muted)"},children:w})},{key:"size",label:"Size",width:"60px",render:w=>w!=null&&w>0?Tn(w):"-"},{key:"ttl",label:"TTL",width:"70px",render:w=>w>0?Cn(w):"-"},{key:"_actions",label:"",width:"60px",render:(w,u)=>t("button",{type:"button",className:"ss-dash-retry-btn",onClick:N=>{N.stopPropagation(),S(u.key)},children:"Delete"})}],data:g.keys||g.data||[],keyField:"key",onRowClick:w=>k(w.key),emptyMessage:"No cache keys found"})}),r&&t("div",{className:"ss-dash-cache-detail",children:[t("h4",{children:["Key: ",r]}),i?t("div",{className:"ss-dash-empty",children:"Loading value..."}):c?t("div",{className:"ss-dash-empty",style:{color:"var(--ss-red-fg)"},children:c}):t(Ye,{data:a})]})]})}const or=Object.freeze(Object.defineProperty({__proto__:null,CacheSection:is,default:is},Symbol.toStringTag,{value:"Module"})),cr=["all","active","waiting","delayed","completed","failed"];function dr(e){switch(e){case"active":return"blue";case"waiting":return"amber";case"delayed":return"purple";case"completed":return"green";case"failed":return"red";default:return"muted"}}function hr(e){if(!e)return[];if(Array.isArray(e))return e;const s=e;return s.jobs||s.data||[]}function ur(e){if(!e||Array.isArray(e))return null;const s=e;return s.stats||s.overview||null}function os({options:e={}}){const[s,n]=x(1),[r,o]=x(""),[a,l]=x("all"),[i,d]=x({}),c={};a!=="all"&&(c.status=a);const{data:p,meta:h,isLoading:y,error:f,refresh:_,mutate:g}=J("jobs",{...e,page:s,search:r,filters:c}),S=hr(p),k=ur(p),w=P(async u=>{d(N=>({...N,[u]:"pending"}));try{await g(`jobs/${u}/retry`),d(N=>({...N,[u]:"success"})),setTimeout(()=>{d(N=>{const b={...N};return delete b[u],b}),_()},1e3)}catch{d(N=>{const b={...N};return delete b[u],b})}},[g,_]);return t("div",{children:[k&&t("div",{className:"ss-dash-job-stats",children:[t("div",{className:"ss-dash-job-stat",children:[t("span",{className:"ss-dash-job-stat-label",children:"Active:"}),t("span",{className:"ss-dash-job-stat-value",children:k.active??0})]}),t("div",{className:"ss-dash-job-stat",children:[t("span",{className:"ss-dash-job-stat-label",children:"Waiting:"}),t("span",{className:"ss-dash-job-stat-value",children:k.waiting??0})]}),t("div",{className:"ss-dash-job-stat",children:[t("span",{className:"ss-dash-job-stat-label",children:"Delayed:"}),t("span",{className:"ss-dash-job-stat-value",children:k.delayed??0})]}),t("div",{className:"ss-dash-job-stat",children:[t("span",{className:"ss-dash-job-stat-label",children:"Completed:"}),t("span",{className:"ss-dash-job-stat-value",children:k.completed??0})]}),t("div",{className:"ss-dash-job-stat",children:[t("span",{className:"ss-dash-job-stat-label",children:"Failed:"}),t("span",{className:"ss-dash-job-stat-value",style:{color:"var(--ss-red-fg)"},children:k.failed??0})]})]}),t(se,{search:r,onSearchChange:o,placeholder:"Filter jobs...",summary:`${h?.total??S.length} jobs`,children:t("div",{className:"ss-dash-btn-group",children:cr.map(u=>t("button",{type:"button",className:`ss-dash-btn ${a===u?"ss-dash-active":""}`,onClick:()=>{l(u),n(1)},children:u.charAt(0).toUpperCase()+u.slice(1)},u))})}),y&&!p?t("div",{className:"ss-dash-empty",children:"Loading jobs..."}):f?t("div",{className:"ss-dash-empty",children:"Jobs/Queue not available"}):t(D,{children:[t("div",{className:"ss-dash-table-wrap",children:t(te,{columns:[{key:"id",label:"ID",width:"40px",render:u=>t("span",{style:{color:"var(--ss-dim)"},children:u})},{key:"name",label:"Name",render:u=>t("span",{style:{color:"var(--ss-text)"},title:u,children:u})},{key:"status",label:"Status",width:"90px",render:u=>t(zn,{color:dr(u),children:u})},{key:"payload",label:"Payload",render:(u,N)=>t(Ye,{data:u||N?.data,maxPreviewLength:60})},{key:"attempts",label:"Tries",width:"50px",render:(u,N)=>t("span",{style:{color:"var(--ss-muted)",textAlign:"center",display:"block"},children:u||N.attemptsMade||0})},{key:"duration",label:"Duration",width:"75px",render:u=>t("span",{className:"ss-dash-duration",children:u!==null?re(u):"-"})},{key:"timestamp",label:"Time",width:"70px",render:(u,N)=>{const b=u||N?.createdAt||N?.processedAt||N?.created_at;return t("span",{className:"ss-dash-event-time",style:{whiteSpace:"nowrap"},title:ae(b),children:X(b)})}},{key:"_actions",label:"",width:"50px",render:(u,N)=>{const b=N.id,L=i[b];return N.status!=="failed"?null:t("button",{type:"button",className:"ss-dash-retry-btn",disabled:L==="pending"||L==="success",onClick:M=>{M.stopPropagation(),w(b)},children:L==="pending"?"...":L==="success"?"OK":"Retry"})}}],data:S,emptyMessage:"No jobs found"})}),h&&t(oe,{page:h.page,lastPage:h.lastPage,total:h.total,onPageChange:n})]})]})}const pr=Object.freeze(Object.defineProperty({__proto__:null,JobsSection:os,default:os},Symbol.toStringTag,{value:"Module"}));function W(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&e.__redacted===!0}function Xe(e,s=""){if(typeof e!="object"||e===null||e===void 0)return[{path:s,value:e}];if(Array.isArray(e)||W(e))return[{path:s,value:e}];const n=[];for(const r of Object.keys(e)){const o=s?`${s}.${r}`:r,a=e[r];typeof a=="object"&&a!==null&&!Array.isArray(a)&&!W(a)?n.push(...Xe(a,o)):n.push({path:o,value:a})}return n}function Ze(e){return e==null?{text:"null",color:"var(--ss-dim)"}:typeof e=="boolean"?{text:String(e),color:e?"var(--ss-green-fg)":"var(--ss-red-fg)"}:typeof e=="number"?{text:String(e),color:"var(--ss-amber-fg)"}:Array.isArray(e)?{text:`[${e.map(n=>n==null?"null":typeof n=="object"?JSON.stringify(n):String(n)).join(", ")}]`,color:"var(--ss-purple-fg)"}:typeof e=="object"?{text:JSON.stringify(e),color:"var(--ss-dim)"}:{text:String(e)}}function cs(e){if(e==null||typeof e!="object"||Array.isArray(e)||W(e))return 1;let s=0;for(const n of Object.keys(e))s+=cs(e[n]);return s}function fr(e){if(e==null||typeof e!="object"||Array.isArray(e)||W(e))return[];const s=[];for(const n of Object.keys(e)){const r=e[n];r!==null&&typeof r=="object"&&!Array.isArray(r)&&!W(r)&&s.push(n)}return s}function Me(e,s,n){s&&navigator.clipboard.writeText(e).then(()=>{const r=s.textContent;s.textContent="✓",s.classList.add(`${n}-copy-row-ok`),setTimeout(()=>{s.textContent=r,s.classList.remove(`${n}-copy-row-ok`)},1200)}).catch(()=>{})}function qe({redacted:e,p:s}){const[n,r]=x(!1);return t("span",{className:`${s}-config-redacted`,style:{display:"inline-flex",alignItems:"center",gap:"4px"},children:[t("span",{children:n?e.value:e.display}),t("button",{type:"button",className:`${s}-btn`,title:n?"Hide":"Reveal",style:{padding:"0 4px",fontSize:"0.85em",lineHeight:1,minWidth:"auto"},onClick:o=>{o.stopPropagation(),r(a=>!a)},children:n?t("svg",{width:"14",height:"14",viewBox:F["eye-off"].viewBox,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",dangerouslySetInnerHTML:{__html:F["eye-off"].elements.join("")}}):t("svg",{width:"14",height:"14",viewBox:F.eye.viewBox,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",dangerouslySetInnerHTML:{__html:F.eye.elements.join("")}})})]})}function mr({env:e,search:s,p:n}){const r=V(new Map),o=s.toLowerCase(),a=Object.entries(e).filter(([l,i])=>{if(!o)return!0;const d=W(i)?i.display:i==null?"":String(i);return l.toLowerCase().includes(o)||d.toLowerCase().includes(o)});return t("div",{className:`${n}-config-table-wrap`,children:t("table",{className:`${n}-table ${n}-config-env-table`,children:[t("thead",{children:t("tr",{children:[t("th",{children:"Variable"}),t("th",{children:"Value"}),t("th",{style:{width:36}})]})}),t("tbody",{children:[a.map(([l,i])=>{const d=W(i),c=d?i.display:i==null?"null":String(i),p=`${l}=${c}`;return t("tr",{children:[t("td",{className:`${n}-env-key`,children:t("span",{className:`${n}-config-key`,children:l})}),t("td",{className:`${n}-env-val`,children:d?t(qe,{redacted:i,p:n}):t("span",{className:`${n}-config-val`,children:c})}),t("td",{children:!d&&t("button",{type:"button",className:`${n}-copy-row-btn`,title:"Copy",ref:h=>{r.current.set(l,h)},onClick:h=>{h.stopPropagation(),Me(p,r.current.get(l)??null,n)},children:"⎘"})})]},l)}),a.length===0&&t("tr",{children:t("td",{colSpan:3,style:{textAlign:"center",color:"var(--ss-dim)"},children:"No matching variables"})})]})]})})}function _r({source:e,search:s,p:n}){const r=V(new Map),o=s.toLowerCase(),a=Xe(e,""),l=a.filter(i=>{const d=W(i.value)?i.value.display:i.value===null||i.value===void 0?"":String(i.value);return i.path.toLowerCase().includes(o)||d.toLowerCase().includes(o)});return t("div",{className:`${n}-config-table-wrap`,children:[t("table",{className:`${n}-table`,children:[t("thead",{children:t("tr",{children:[t("th",{children:"Path"}),t("th",{children:"Value"}),t("th",{style:{width:36}})]})}),t("tbody",{children:[l.map(i=>{const d=W(i.value),c=d?null:Ze(i.value),p=d?i.value.display:c.text,h=`${i.path}: ${p}`;return t("tr",{children:[t("td",{children:t("span",{className:`${n}-config-key`,style:{whiteSpace:"nowrap"},children:i.path})}),t("td",{children:d?t(qe,{redacted:i.value,p:n}):t("span",{className:`${n}-config-val`,style:{wordBreak:"break-all",color:c.color},children:c.text})}),t("td",{children:!d&&t("button",{type:"button",className:`${n}-copy-row-btn`,title:"Copy",ref:y=>{r.current.set(i.path,y)},onClick:y=>{y.stopPropagation(),Me(h,r.current.get(i.path)??null,n)},children:"⎘"})})]},i.path)}),l.length===0&&t("tr",{children:t("td",{colSpan:3,style:{textAlign:"center",color:"var(--ss-dim)"},children:"No matching entries"})})]})]}),t("div",{style:{padding:"4px 16px",fontSize:"10px",color:"var(--ss-muted)"},children:[l.length," of ",a.length," entries"]})]})}function vr({obj:e,prefix:s,p:n}){const r=V(new Map),o=Xe(e,s);return t("table",{className:`${n}-table ${n}-config-inner-table`,children:[t("thead",{children:t("tr",{children:[t("th",{style:{width:"35%"},children:"Key"}),t("th",{children:"Value"}),t("th",{style:{width:36}})]})}),t("tbody",{children:o.map(a=>{const l=a.path.indexOf(s+".")===0?a.path.slice(s.length+1):a.path,i=W(a.value),d=i?null:Ze(a.value),c=i?a.value.display:d.text,p=`${a.path}: ${c}`;return t("tr",{children:[t("td",{title:l,children:t("span",{className:`${n}-config-key`,children:l})}),t("td",{title:c,children:i?t(qe,{redacted:a.value,p:n}):t("span",{className:`${n}-config-val`,style:{color:d.color},children:d.text})}),t("td",{children:!i&&t("button",{type:"button",className:`${n}-copy-row-btn`,title:"Copy",ref:h=>{r.current.set(a.path,h)},onClick:h=>{h.stopPropagation(),Me(p,r.current.get(a.path)??null,n)},children:"⎘"})})]},a.path)})})]})}function yr({value:e,p:s}){if(e==null)return t("span",{className:`${s}-config-val`,style:{color:"var(--ss-dim)"},children:"null"});if(W(e))return t(qe,{redacted:e,p:s});if(typeof e=="boolean")return t("span",{className:`${s}-config-val`,style:{color:e?"var(--ss-green-fg)":"var(--ss-red-fg)"},children:String(e)});if(typeof e=="number")return t("span",{className:`${s}-config-val`,style:{color:"var(--ss-amber-fg)"},children:String(e)});if(Array.isArray(e)){const n=e.map(r=>r==null?"null":typeof r=="object"?JSON.stringify(r):String(r));return t("span",{className:`${s}-config-val`,style:{color:"var(--ss-purple-fg)"},children:["[",n.join(", "),"]"]})}return typeof e=="object"?t("span",{className:`${s}-config-val`,style:{color:"var(--ss-dim)"},children:JSON.stringify(e)}):t("span",{className:`${s}-config-val`,children:String(e)})}function gr({obj:e,expandedPaths:s,onToggle:n,p:r}){if(e==null||typeof e!="object"||Array.isArray(e)||W(e))return null;const o=Object.keys(e),a=V(new Map);return t("div",{className:`${r}-config-sections`,children:o.map(l=>{const i=e[l],d=i!==null&&typeof i=="object"&&!Array.isArray(i)&&!W(i),c=s.has(l),p=W(i);return t("div",{className:`${r}-config-section`,children:[t("div",{className:`${r}-config-section-header${d?"":` ${r}-config-leaf`}`,onClick:d?()=>n(l):void 0,style:{cursor:d?"pointer":"default"},children:[d?t("span",{className:`${r}-config-toggle`,children:c?"▼":"▶"}):t("span",{className:`${r}-config-toggle`,style:{visibility:"hidden"},children:"•"}),t("span",{className:`${r}-config-key`,children:l}),d?t("span",{className:`${r}-config-count`,children:[cs(i)," entries"]}):t(D,{children:[t("span",{className:`${r}-config-val`,style:{marginLeft:"8px"},children:t(yr,{value:i,p:r})}),!p&&t("button",{type:"button",className:`${r}-copy-row-btn`,style:{marginLeft:"4px"},title:"Copy",ref:h=>{a.current.set(l,h)},onClick:h=>{h.stopPropagation();const y=Ze(i);Me(`${l}: ${y.text}`,a.current.get(l)??null,r)},children:"⎘"})]})]}),d&&c&&t("div",{className:`${r}-config-section-body`,children:t(vr,{obj:i,prefix:l,p:r})})]},l)})})}function br({data:e,isLoading:s,classPrefix:n}){const r=n,[o,a]=x(""),[l,i]=x(""),[d,c]=x("app"),[p,h]=x(new Set),[y,f]=x("Copy JSON");H(()=>{const u=setTimeout(()=>i(o),200);return()=>clearTimeout(u)},[o]);const _=e,g=P(u=>{h(N=>{const b=new Set(N);return b.has(u)?b.delete(u):b.add(u),b})},[]),S=P(()=>{if(!_)return;const u=d==="app"?_.app:_.env;if(!u)return;const N=fr(u);h(new Set(N))},[_,d]),k=P(()=>{h(new Set)},[]),w=P(async()=>{if(_)try{const u=d==="app"?_.app:_.env;await navigator.clipboard.writeText(JSON.stringify(u,null,2)),f("Copied!"),setTimeout(()=>f("Copy JSON"),1500)}catch{}},[_,d]);return t("div",{children:[t("div",{className:`${r}-config-toolbar`,style:{display:"flex",alignItems:"center",gap:"8px",padding:"8px 12px"},children:[t("button",{type:"button",className:`${r}-config-tab${d==="app"?` ${r}-active`:""}`,onClick:()=>c("app"),children:"App Config"}),t("button",{type:"button",className:`${r}-config-tab${d==="env"?` ${r}-active`:""}`,onClick:()=>c("env"),children:"Env"}),t("div",{style:{position:"relative",flex:1},children:[t("input",{type:"text",className:`${r}-search`,placeholder:"Search keys and values...",value:o,onChange:u=>a(u.target.value),style:{width:"100%"}}),o&&t("button",{type:"button",onClick:()=>a(""),style:{position:"absolute",right:"6px",top:"50%",transform:"translateY(-50%)",background:"none",border:"none",cursor:"pointer",fontSize:"14px",color:"var(--ss-dim)",padding:"0 2px",lineHeight:1},children:"×"})]}),d==="app"&&!l&&t(D,{children:[t("button",{type:"button",className:`${r}-btn`,onClick:S,children:"Expand All"}),t("button",{type:"button",className:`${r}-btn`,onClick:k,children:"Collapse All"})]}),t("button",{type:"button",className:`${r}-btn`,onClick:w,children:y})]}),s&&!e?t("div",{className:`${r}-empty`,children:"Loading config..."}):_?d==="env"?t(mr,{env:_.env??{},search:l,p:r}):l?t(_r,{source:_.app??{},search:l,p:r}):t("div",{className:`${r}-config-table-wrap`,children:t(gr,{obj:_.app,expandedPaths:p,onToggle:g,p:r})}):t("div",{className:`${r}-empty`,children:"Config not available"})]})}function ds({options:e={}}){const{data:s,isLoading:n}=J("config",e);return t(br,{data:s,isLoading:n,classPrefix:"ss-dash"})}const wr=Object.freeze(Object.defineProperty({__proto__:null,ConfigSection:ds,default:ds},Symbol.toStringTag,{value:"Module"})),Nr=["password","secret","token","key","credential","auth"];function hs(e){const s=e.toLowerCase();return Nr.some(n=>s.includes(n))}function us(e){if(e==null)return"-";if(typeof e=="string"||typeof e=="number"||typeof e=="boolean")return String(e);if(Array.isArray(e))return e.join(", ")||"-";try{return JSON.stringify(e)}catch{return String(e)}}const xr={collectionInterval:"Stats Collection",dashboardBroadcast:"Dashboard Broadcast",debugBroadcast:"Debug Broadcast",persistFlush:"Persist Flush",retentionCleanup:"Retention Cleanup"};function kr(e){return xr[e]||e}const Sr={prometheus:"Prometheus",pinoHook:"Pino Log Hook",edgePlugin:"Edge Plugin",cacheInspector:"Cache Inspector",queueInspector:"Queue Inspector"};function $r(e){return Sr[e]||e}const Cr=["healthy","active","connected","available","ready"],Tr=["errored","unavailable"];function Lr(e){return Cr.includes(e)?"ok":Tr.includes(e)?"err":""}function _e({status:e,prefix:s}){const n=Lr(e);let r=`${s}-dot`;return n==="ok"?r+=` ${s}-dot-ok`:n==="err"&&(r+=` ${s}-dot-err`),t("span",{className:r})}const ps=()=>t("svg",{xmlns:"http://www.w3.org/2000/svg",width:"12",height:"12",viewBox:F.eye.viewBox,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",dangerouslySetInnerHTML:{__html:F.eye.elements.join("")}}),fs=()=>t("svg",{xmlns:"http://www.w3.org/2000/svg",width:"12",height:"12",viewBox:F["eye-off"].viewBox,fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",dangerouslySetInnerHTML:{__html:F["eye-off"].elements.join("")}});function Er({value:e}){const[s,n]=x(!1);return t("span",{children:[s?e:"••••••••"," ",t("button",{type:"button",onClick:()=>n(r=>!r),style:{background:"none",border:"1px solid var(--ss-border)",borderRadius:3,padding:"0 4px",fontSize:"10px",color:"var(--ss-dim)",cursor:"pointer",verticalAlign:"middle"},children:s?t(fs,{}):t(ps,{})})]})}function Ar({current:e,max:s,prefix:n}){const r=s>0?Math.min(100,Math.round(e/s*100)):0,o=r>=100;return t("div",{className:`${n}-bar`,children:[t("div",{className:`${n}-bar-track`,children:t("div",{className:`${n}-bar-fill${o?` ${n}-bar-fill-warn`:""}`,style:{width:`${r}%`}})}),t("span",{className:`${n}-bar-pct${o?` ${n}-bar-pct-warn`:""}`,children:[r,"%"]})]})}function Pr({config:e,prefix:s}){const n=Object.entries(e);return n.length===0?t("span",{className:`${s}-c-dim`,children:"-"}):t("span",{className:`${s}-c-muted`,children:n.map(([r,o],a)=>t("span",{children:[a>0&&", ",t("span",{className:`${s}-c-dim`,children:r}),"=",hs(r)&&typeof o=="string"?t(Er,{value:o}):t("span",{children:us(o)})]},r))})}function ve({label:e,value:s,prefix:n}){return t("div",{className:`${n}-info-card`,children:[t("span",{className:`${n}-info-card-label`,children:e}),t("span",{className:`${n}-info-card-value`,children:s})]})}function Mr({data:e,tableClassName:s,classPrefix:n}){const r=n||"ss-dash",[o,a]=x(new Set),l=P(d=>{a(c=>{const p=new Set(c);return p.has(d)?p.delete(d):p.add(d),p})},[]),i=P((d,c)=>{if(c==null)return t("span",{className:`${r}-c-dim`,children:"null"});if(typeof c=="boolean")return t("span",{className:c?`${r}-c-green`:`${r}-c-red`,children:String(c)});if(Array.isArray(c))return t("span",{children:c.join(", ")||"-"});const p=us(c);if(hs(d)){const h=o.has(d);return t("span",{children:[h?p:"••••••••"," ",t("button",{type:"button",onClick:()=>l(d),style:{background:"none",border:"1px solid var(--ss-border)",borderRadius:3,padding:"0 4px",fontSize:"10px",color:"var(--ss-dim)",cursor:"pointer",verticalAlign:"middle"},children:h?t(fs,{}):t(ps,{})})]})}return t("span",{children:p})},[o,l,r]);return t("div",{children:[t("h3",{className:`${r}-internals-title`,children:"Package Info"}),t("div",{className:`${r}-info-cards`,children:[t(ve,{label:"Version",value:e.package.version||"-",prefix:r}),t(ve,{label:"Node.js",value:e.package.nodeVersion||"-",prefix:r}),t(ve,{label:"AdonisJS",value:e.package.adonisVersion||"-",prefix:r}),t(ve,{label:"Uptime",value:$n(e.package.uptime),prefix:r}),t(ve,{label:"Renderer",value:e.devToolbar?.renderer||"preact",prefix:r})]}),e.collectors.length>0&&t(D,{children:[t("h3",{className:`${r}-internals-title`,children:"Collectors"}),t("table",{className:s,children:[t("thead",{children:t("tr",{children:[t("th",{children:"Collector"}),t("th",{children:"Status"}),t("th",{children:"Last Error"}),t("th",{children:"Config"})]})}),t("tbody",{children:e.collectors.map(d=>t("tr",{children:[t("td",{children:[t("code",{children:d.name}),d.label&&d.label!==d.name&&t("span",{className:`${r}-c-dim`,children:[" ",d.label]})]}),t("td",{children:[t(_e,{status:d.status,prefix:r}),d.status]}),t("td",{className:d.lastError?`${r}-c-red`:`${r}-c-dim`,children:d.lastError?t(D,{children:[d.lastError,d.lastErrorAt&&t("span",{className:`${r}-c-dim`,style:{fontSize:"10px"},children:X(d.lastErrorAt)})]}):"-"}),t("td",{children:t(Pr,{config:d.config,prefix:r})})]},d.name))})]})]}),t("h3",{className:`${r}-internals-title`,children:"Buffers"}),t("table",{className:s,children:[t("thead",{children:t("tr",{children:[t("th",{children:"Buffer"}),t("th",{children:"Usage"}),t("th",{children:"Fill %"})]})}),t("tbody",{children:Object.entries(e.buffers).map(([d,c])=>t("tr",{children:[t("td",{style:{textTransform:"capitalize"},children:d}),t("td",{children:[c.current.toLocaleString()," / ",c.max.toLocaleString()]}),t("td",{children:t(Ar,{current:c.current,max:c.max,prefix:r})})]},d))})]}),t("h3",{className:`${r}-internals-title`,children:"Timers"}),t("table",{className:s,children:[t("thead",{children:t("tr",{children:[t("th",{children:"Timer"}),t("th",{children:"Status"}),t("th",{children:"Interval"})]})}),t("tbody",{children:Object.entries(e.timers).map(([d,c])=>t("tr",{children:[t("td",{children:kr(d)}),t("td",{children:[t(_e,{status:c.active?"active":"inactive",prefix:r}),t("span",{className:c.active?`${r}-c-green`:`${r}-c-dim`,children:c.active?"active":"inactive"})]}),t("td",{children:c.active?c.intervalMs?re(c.intervalMs):c.debounceMs?`${re(c.debounceMs)} (debounce)`:"-":t("span",{className:`${r}-c-dim`,children:"—"})})]},d))})]}),t("h3",{className:`${r}-internals-title`,children:"Integrations"}),t("table",{className:s,children:[t("thead",{children:t("tr",{children:[t("th",{children:"Integration"}),t("th",{children:"Status"}),t("th",{children:"Details"})]})}),t("tbody",{children:[t("tr",{children:[t("td",{children:"Transmit (SSE)"}),t("td",{children:[t(_e,{status:e.transmit.available?"connected":"inactive",prefix:r}),e.transmit.available?"connected":"unavailable"]}),t("td",{style:{fontSize:"11px"},children:e.transmit.channels.length>0?`Channels: ${e.transmit.channels.join(", ")}`:"-"})]}),Object.entries(e.integrations).map(([d,c])=>{const p=c.active??c.available??!1,h=c.active?"active":c.available?"available":"unavailable";let y=c.mode?`Mode: ${c.mode}`:"-";return d==="edgePlugin"&&c.active?y="@serverStats() tag registered":d==="cacheInspector"&&c.available?y="Redis dependency detected":d==="queueInspector"&&c.available&&(y="Queue dependency detected"),t("tr",{children:[t("td",{children:$r(d)}),t("td",{children:[t(_e,{status:p?"active":"inactive",prefix:r}),h]}),t("td",{className:`${r}-c-dim`,style:{fontSize:"11px"},children:y})]},d)})]})]}),e.storage&&t(D,{children:[t("h3",{className:`${r}-internals-title`,children:"Storage (SQLite)"}),t("table",{className:s,children:[t("thead",{children:t("tr",{children:[t("th",{style:{width:"200px"},children:"Metric"}),t("th",{children:"Value"})]})}),t("tbody",{children:[t("tr",{children:[t("td",{children:"Status"}),t("td",{children:[t(_e,{status:e.storage.ready?"ready":"inactive",prefix:r}),e.storage.ready?"ready":"not ready"]})]}),t("tr",{children:[t("td",{children:"DB Path"}),t("td",{children:t("code",{children:e.storage.dbPath})})]}),t("tr",{children:[t("td",{children:"File Size"}),t("td",{children:[e.storage.fileSizeMb.toFixed(1)," MB"]})]}),t("tr",{children:[t("td",{children:"WAL Size"}),t("td",{children:[e.storage.walSizeMb.toFixed(1)," MB"]})]}),t("tr",{children:[t("td",{children:"Retention"}),t("td",{children:[e.storage.retentionDays," days"]})]}),t("tr",{children:[t("td",{children:"Last Cleanup"}),t("td",{children:e.storage.lastCleanupAt?X(e.storage.lastCleanupAt):"-"})]})]})]}),e.storage.tables.length>0&&t("table",{className:s,style:{marginTop:8},children:[t("thead",{children:t("tr",{children:[t("th",{children:"Table"}),t("th",{children:"Rows"})]})}),t("tbody",{children:e.storage.tables.map(d=>t("tr",{children:[t("td",{children:t("code",{children:d.name})}),t("td",{children:d.rowCount.toLocaleString()})]},d.name))})]})]}),t("h3",{className:`${r}-internals-title`,children:"Resolved Config"}),t("table",{className:s,children:[t("thead",{children:t("tr",{children:[t("th",{style:{width:"200px"},children:"Setting"}),t("th",{children:"Value"})]})}),t("tbody",{children:[t("tr",{children:[t("td",{children:"intervalMs"}),t("td",{children:e.config.intervalMs})]}),t("tr",{children:[t("td",{children:"transport"}),t("td",{children:e.config.transport})]}),t("tr",{children:[t("td",{children:"channelName"}),t("td",{children:e.config.channelName})]}),t("tr",{children:[t("td",{children:"endpoint"}),t("td",{children:e.config.endpoint===!1?"false":e.config.endpoint})]}),t("tr",{children:[t("td",{children:"skipInTest"}),t("td",{children:i("skipInTest",e.config.skipInTest)})]}),t("tr",{children:[t("td",{children:"onStats callback"}),t("td",{children:e.config.hasOnStatsCallback?"defined":"not defined"})]}),t("tr",{children:[t("td",{children:"shouldShow callback"}),t("td",{children:e.config.hasShouldShowCallback?"defined":"not defined"})]})]})]}),t("h4",{className:`${r}-internals-title`,children:"DevToolbar"}),t("table",{className:s,children:[t("thead",{children:t("tr",{children:[t("th",{style:{width:"200px"},children:"Setting"}),t("th",{children:"Value"})]})}),t("tbody",{children:Object.entries(e.devToolbar).map(([d,c])=>t("tr",{children:[t("td",{children:d==="customPaneCount"?"customPanes":d}),t("td",{children:d==="customPaneCount"?`${c} registered`:i(d,c)})]},d))})]})]})}function ms({options:e={},debugEndpoint:s="/admin/api/debug"}){const{baseUrl:n="",authToken:r}=e,[o,a]=x(null),[l,i]=x(!0),[d,c]=x(null),p=V(null),h=Ge(n,r),y=P(async()=>{try{const _=await h().get(`${s}/diagnostics`);a(_),c(null),i(!1)}catch(f){if(f instanceof Ve){c(f),i(!1),p.current&&(clearInterval(p.current),p.current=null);return}c(f instanceof Error?f:new Error(String(f))),i(!1)}},[s,h]);return H(()=>(i(!0),c(null),y(),p.current=setInterval(y,Ot),()=>{p.current&&(clearInterval(p.current),p.current=null)}),[y]),l&&!o?t("div",{className:"ss-dash-empty",children:"Loading diagnostics..."}):d?t("div",{className:"ss-dash-empty",children:["Error: ",d.message]}):o?t(Mr,{data:o,tableClassName:"ss-dash-table",classPrefix:"ss-dash"}):t("div",{className:"ss-dash-empty",children:"Diagnostics not available"})}const qr=Object.freeze(Object.defineProperty({__proto__:null,InternalsSection:ms,default:ms},Symbol.toStringTag,{value:"Module"}))})();
|