windmill-components 1.665.1 → 1.677.0

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.
Files changed (252) hide show
  1. package/dist/sharedUtils/assets/tokens/colorTokensConfig.d.ts +2 -0
  2. package/dist/sharedUtils/base.d.ts +1 -0
  3. package/dist/sharedUtils/cloud.d.ts +1 -0
  4. package/dist/sharedUtils/common.d.ts +111 -0
  5. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/count.d.ts +5 -0
  6. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/delete.d.ts +5 -0
  7. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/insert.d.ts +5 -0
  8. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/select.d.ts +13 -0
  9. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/update.d.ts +11 -0
  10. package/dist/sharedUtils/components/apps/components/display/dbtable/utils.d.ts +95 -0
  11. package/dist/sharedUtils/components/apps/editor/appPolicy.d.ts +6 -0
  12. package/dist/sharedUtils/components/apps/editor/appUtilsCore.d.ts +7 -0
  13. package/dist/sharedUtils/components/apps/editor/appUtilsS3.d.ts +33 -0
  14. package/dist/sharedUtils/components/apps/editor/commonAppUtils.d.ts +10 -0
  15. package/dist/sharedUtils/components/apps/editor/component/components.d.ts +5371 -0
  16. package/dist/sharedUtils/components/apps/editor/component/default-codes.d.ts +3 -0
  17. package/dist/sharedUtils/components/apps/editor/component/index.d.ts +3 -0
  18. package/dist/sharedUtils/components/apps/editor/component/sets.d.ts +7 -0
  19. package/dist/sharedUtils/components/apps/editor/componentsPanel/componentDefaultProps.d.ts +3 -0
  20. package/dist/sharedUtils/components/apps/gridUtils.d.ts +14 -0
  21. package/dist/sharedUtils/components/apps/inputType.d.ts +178 -0
  22. package/dist/sharedUtils/components/apps/rx.d.ts +29 -0
  23. package/dist/sharedUtils/components/apps/sharedTypes.d.ts +21 -0
  24. package/dist/sharedUtils/components/apps/types.d.ts +274 -0
  25. package/dist/sharedUtils/components/assets/lib.d.ts +25 -0
  26. package/dist/sharedUtils/components/common/alert/model.d.ts +2 -0
  27. package/dist/sharedUtils/components/common/badge/model.d.ts +8 -0
  28. package/dist/sharedUtils/components/common/button/model.d.ts +45 -0
  29. package/dist/sharedUtils/components/common/fileInput/model.d.ts +1 -0
  30. package/dist/sharedUtils/components/common/index.d.ts +24 -0
  31. package/dist/sharedUtils/components/common/skeleton/model.d.ts +21 -0
  32. package/dist/sharedUtils/components/dbTypes.d.ts +14 -0
  33. package/dist/sharedUtils/components/diff_drawer.d.ts +26 -0
  34. package/dist/sharedUtils/components/ducklake.d.ts +1 -0
  35. package/dist/sharedUtils/components/flows/scheduleUtils.d.ts +7 -0
  36. package/dist/sharedUtils/components/icons/index.d.ts +101 -0
  37. package/dist/sharedUtils/components/random_positive_adjetive.d.ts +1 -0
  38. package/dist/sharedUtils/components/raw_apps/rawAppPolicy.d.ts +10 -0
  39. package/dist/sharedUtils/components/raw_apps/utils.d.ts +15 -0
  40. package/dist/sharedUtils/components/triggers/email/utils.d.ts +4 -0
  41. package/dist/sharedUtils/components/triggers/gcp/utils.d.ts +2 -0
  42. package/dist/sharedUtils/components/triggers/http/utils.d.ts +11 -0
  43. package/dist/sharedUtils/components/triggers/kafka/utils.d.ts +2 -0
  44. package/dist/sharedUtils/components/triggers/mqtt/utils.d.ts +2 -0
  45. package/dist/sharedUtils/components/triggers/nats/utils.d.ts +2 -0
  46. package/dist/sharedUtils/components/triggers/postgres/utils.d.ts +8 -0
  47. package/dist/sharedUtils/components/triggers/sqs/utils.d.ts +2 -0
  48. package/dist/sharedUtils/components/triggers/triggers.svelte.d.ts +32 -0
  49. package/dist/sharedUtils/components/triggers/utils.d.ts +80 -0
  50. package/dist/sharedUtils/components/triggers/websocket/utils.d.ts +2 -0
  51. package/dist/sharedUtils/components/triggers.d.ts +20 -0
  52. package/dist/sharedUtils/gen/core/ApiError.d.ts +10 -0
  53. package/dist/sharedUtils/gen/core/ApiRequestOptions.d.ts +13 -0
  54. package/dist/sharedUtils/gen/core/ApiResult.d.ts +7 -0
  55. package/dist/sharedUtils/gen/core/CancelablePromise.d.ts +26 -0
  56. package/dist/sharedUtils/gen/core/OpenAPI.d.ts +27 -0
  57. package/dist/sharedUtils/gen/core/request.d.ts +29 -0
  58. package/dist/sharedUtils/gen/index.d.ts +6 -0
  59. package/dist/sharedUtils/gen/schemas.gen.d.ts +7036 -0
  60. package/dist/sharedUtils/gen/services.gen.d.ts +6047 -0
  61. package/dist/sharedUtils/gen/types.gen.d.ts +21881 -0
  62. package/dist/sharedUtils/history.svelte.d.ts +9 -0
  63. package/dist/sharedUtils/hub.d.ts +49 -0
  64. package/dist/sharedUtils/jsr.json +6 -0
  65. package/dist/sharedUtils/lib.d.ts +5 -0
  66. package/dist/sharedUtils/lib.es.js +1588 -0
  67. package/dist/sharedUtils/package.json +12 -0
  68. package/dist/sharedUtils/schema.d.ts +3 -0
  69. package/dist/sharedUtils/stores.d.ts +97 -0
  70. package/dist/sharedUtils/svelte5Utils.svelte.d.ts +80 -0
  71. package/dist/sharedUtils/toast.d.ts +8 -0
  72. package/dist/sharedUtils/utils.d.ts +265 -0
  73. package/package/components/AddUser.svelte +67 -34
  74. package/package/components/AppConnectInner.svelte +9 -1
  75. package/package/components/ArgInfo.svelte +9 -1
  76. package/package/components/ArgInput.svelte +21 -1
  77. package/package/components/CompareWorkspaces.svelte +11 -2
  78. package/package/components/DedicatedWorkersSelector.svelte +262 -247
  79. package/package/components/DefaultTagsInner.svelte +40 -2
  80. package/package/components/DeployWorkspace.svelte +13 -0
  81. package/package/components/DiffEditor.svelte +44 -1
  82. package/package/components/EditableSchemaForm.svelte +5 -2
  83. package/package/components/EditableSchemaForm.svelte.d.ts +1 -0
  84. package/package/components/Editor.svelte +5 -1
  85. package/package/components/EditorBar.svelte +12 -3
  86. package/package/components/FilterSearchbar.svelte +26 -2
  87. package/package/components/FlowBuilder.svelte +6 -3
  88. package/package/components/FlowGraphDiffViewer.svelte +16 -17
  89. package/package/components/FlowGraphViewer.svelte +20 -6
  90. package/package/components/FlowGraphViewer.svelte.d.ts +2 -0
  91. package/package/components/FlowGraphViewerStep.svelte +14 -32
  92. package/package/components/FlowMetadata.svelte +4 -1
  93. package/package/components/FlowPreviewContent.svelte +2 -0
  94. package/package/components/FlowStatusWaitingForEvents.svelte +25 -4
  95. package/package/components/HighlightCode.svelte +3 -0
  96. package/package/components/InstanceSetting.svelte +9 -25
  97. package/package/components/InstanceSettings.svelte +16 -0
  98. package/package/components/LabelsInput.svelte +149 -0
  99. package/package/components/LabelsInput.svelte.d.ts +8 -0
  100. package/package/components/Login.svelte +6 -1
  101. package/package/components/ObjectStoreConfigSettings.svelte +273 -1
  102. package/package/components/OktaSetting.svelte +6 -5
  103. package/package/components/Password.svelte +74 -20
  104. package/package/components/Password.svelte.d.ts +1 -0
  105. package/package/components/PasswordArgInput.svelte +2 -2
  106. package/package/components/PasswordArgInput.svelte.d.ts +1 -0
  107. package/package/components/Path.svelte +8 -10
  108. package/package/components/PathNameAutocomplete.svelte +308 -0
  109. package/package/components/PathNameAutocomplete.svelte.d.ts +27 -0
  110. package/package/components/PowerShellCommonParams.svelte +84 -0
  111. package/package/components/PowerShellCommonParams.svelte.d.ts +6 -0
  112. package/package/components/Range.svelte +8 -3
  113. package/package/components/ResourceEditor.svelte +6 -2
  114. package/package/components/RunForm.svelte +71 -7
  115. package/package/components/RunForm.svelte.d.ts +2 -1
  116. package/package/components/ScriptBuilder.svelte +7 -3
  117. package/package/components/ScriptEditor.svelte +221 -187
  118. package/package/components/ScriptEditor.svelte.d.ts +1 -1
  119. package/package/components/ScriptSchema.svelte +1 -1
  120. package/package/components/StringTypeNarrowing.svelte +1 -1
  121. package/package/components/StringTypeNarrowing.svelte.d.ts +1 -1
  122. package/package/components/SummaryPathDisplay.svelte +32 -10
  123. package/package/components/SummaryPathDisplay.svelte.d.ts +2 -1
  124. package/package/components/VariableEditor.svelte +9 -2
  125. package/package/components/WorkerGroup.svelte +47 -2
  126. package/package/components/apps/editor/DeploymentHistory.svelte +112 -13
  127. package/package/components/apps/editor/inlineScriptsPanel/InlineScriptRunnableByPath.svelte +135 -35
  128. package/package/components/apps/editor/inlineScriptsPanel/InlineScriptRunnableByPath.svelte.d.ts +3 -1
  129. package/package/components/apps/editor/settingsPanel/mainInput/RunnableSelector.svelte +11 -35
  130. package/package/components/apps/editor/settingsPanel/mainInput/runnableSelectorUtils.d.ts +10 -0
  131. package/package/components/apps/editor/settingsPanel/mainInput/runnableSelectorUtils.js +14 -0
  132. package/package/components/apps/editor/settingsPanel/mainInput/runnableSelectorUtils.test.d.ts +1 -0
  133. package/package/components/apps/editor/settingsPanel/mainInput/runnableSelectorUtils.test.js +34 -0
  134. package/package/components/assets/AssetButtons.svelte +21 -25
  135. package/package/components/assets/AssetsUsageDrawer.svelte +7 -9
  136. package/package/components/common/fileUpload/FileUpload.svelte +6 -2
  137. package/package/components/common/languageIcons/LanguageIcon.svelte +3 -0
  138. package/package/components/common/table/AppRow.svelte +18 -0
  139. package/package/components/common/table/FlowRow.svelte +18 -0
  140. package/package/components/common/table/ScriptRow.svelte +18 -0
  141. package/package/components/copilot/chat/AIChatManager.svelte.js +3 -3
  142. package/package/components/copilot/chat/flow/openFlow.json +1 -1
  143. package/package/components/copilot/chat/flow/openFlowZod.js +3 -3
  144. package/package/components/custom_ui.d.ts +2 -0
  145. package/package/components/details/DetailPageHeader.svelte +2 -2
  146. package/package/components/details/DetailPageHeader.svelte.d.ts +2 -1
  147. package/package/components/flows/agentToolUtils.d.ts +5 -0
  148. package/package/components/flows/agentToolUtils.js +49 -0
  149. package/package/components/flows/agentToolUtils.test.d.ts +1 -0
  150. package/package/components/flows/agentToolUtils.test.js +55 -0
  151. package/package/components/flows/content/FlowInput.svelte +2 -0
  152. package/package/components/flows/content/FlowInputsQuick.svelte +1 -1
  153. package/package/components/flows/content/FlowLoop.svelte +5 -12
  154. package/package/components/flows/content/FlowModuleScript.svelte +5 -3
  155. package/package/components/flows/content/FlowPathViewer.svelte +2 -2
  156. package/package/components/flows/content/FlowPathViewer.svelte.d.ts +1 -0
  157. package/package/components/flows/content/FlowSettings.svelte +2 -0
  158. package/package/components/flows/content/FlowWhileLoop.svelte +5 -12
  159. package/package/components/flows/flowInfers.js +8 -3
  160. package/package/components/flows/map/FlowModuleSchemaMap.svelte +49 -9
  161. package/package/components/flows/pickers/PickHubScriptQuick.svelte +5 -3
  162. package/package/components/flows/pickers/PickHubScriptQuick.svelte.d.ts +1 -1
  163. package/package/components/flows/scheduleUtils.js +2 -1
  164. package/package/components/graph/FlowGraphV2.svelte +13 -1
  165. package/package/components/graph/WacDiagram.svelte +96 -0
  166. package/package/components/graph/WacDiagram.svelte.d.ts +7 -0
  167. package/package/components/graph/noteEditor.svelte.d.ts +1 -1
  168. package/package/components/graph/noteEditor.svelte.js +12 -1
  169. package/package/components/graph/noteUtils.svelte.d.ts +1 -1
  170. package/package/components/graph/noteUtils.svelte.js +9 -2
  171. package/package/components/graph/renderers/edges/WacEdge.svelte +41 -0
  172. package/package/components/graph/renderers/edges/WacEdge.svelte.d.ts +4 -0
  173. package/package/components/graph/renderers/nodes/WacControlNode.svelte +51 -0
  174. package/package/components/graph/renderers/nodes/WacControlNode.svelte.d.ts +9 -0
  175. package/package/components/graph/renderers/nodes/WacStepNode.svelte +35 -0
  176. package/package/components/graph/renderers/nodes/WacStepNode.svelte.d.ts +9 -0
  177. package/package/components/graph/wacDagLayout.d.ts +10 -0
  178. package/package/components/graph/wacDagLayout.js +120 -0
  179. package/package/components/graph/wacToFlow.js +1 -1
  180. package/package/components/home/ItemsList.svelte +28 -4
  181. package/package/components/icons/RIcon.svelte +32 -0
  182. package/package/components/icons/RIcon.svelte.d.ts +7 -0
  183. package/package/components/instanceSettings/DbHealth.svelte +723 -0
  184. package/package/components/instanceSettings/DbHealth.svelte.d.ts +3 -0
  185. package/package/components/instanceSettings/SecretBackendConfig.svelte +343 -304
  186. package/package/components/instanceSettings/SmtpSettings.svelte +8 -0
  187. package/package/components/instanceSettings.js +14 -5
  188. package/package/components/mcp/McpScopeSelector.svelte +82 -16
  189. package/package/components/moveRenameManager.d.ts +1 -0
  190. package/package/components/moveRenameManager.js +7 -4
  191. package/package/components/raw_apps/RawAppInlineScriptRunnable.svelte +14 -1
  192. package/package/components/raw_apps/rawAppPolicy.js +3 -2
  193. package/package/components/raw_apps/utils.test.d.ts +1 -0
  194. package/package/components/raw_apps/utils.test.js +38 -0
  195. package/package/components/resources/resourcesFilter.d.ts +15 -2
  196. package/package/components/resources/resourcesFilter.js +11 -2
  197. package/package/components/runs/JobDetailFieldConfig.js +5 -3
  198. package/package/components/runs/JobDetailHeader.svelte +5 -2
  199. package/package/components/runs/JobRunsPreview.svelte +1 -0
  200. package/package/components/runs/RunBadges.svelte +7 -4
  201. package/package/components/runs/RunRow.svelte +7 -7
  202. package/package/components/schedules/schedulesFilter.d.ts +15 -2
  203. package/package/components/schedules/schedulesFilter.js +11 -2
  204. package/package/components/schema/EditableSchemaWrapper.svelte +6 -8
  205. package/package/components/schema/PropertyEditor.svelte +22 -1
  206. package/package/components/schema/PropertyEditor.svelte.d.ts +1 -0
  207. package/package/components/schema/editable_schema_wrapper.d.ts +1 -0
  208. package/package/components/secretArgUtils.d.ts +7 -0
  209. package/package/components/secretArgUtils.js +45 -0
  210. package/package/components/settings/WorkspaceUserSettings.svelte +359 -286
  211. package/package/components/sidebar/OperatorMenu.svelte +215 -197
  212. package/package/components/triggers/CaptureWrapper.svelte +1 -1
  213. package/package/components/triggers/TriggerFilters.svelte +17 -5
  214. package/package/components/triggers/TriggerFilters.svelte.d.ts +2 -1
  215. package/package/components/triggers/kafka/KafkaCapture.svelte +6 -2
  216. package/package/components/triggers/kafka/KafkaCapture.svelte.d.ts +1 -1
  217. package/package/components/triggers/kafka/KafkaTriggerEditorInner.svelte +5 -1
  218. package/package/components/triggers/kafka/utils.js +1 -0
  219. package/package/components/triggers/schedules/ScheduleEditorInner.svelte +6 -0
  220. package/package/components/triggers/websocket/WebsocketTriggerEditorInner.svelte +87 -1
  221. package/package/components/triggers/websocket/utils.js +2 -0
  222. package/package/components/variables/variablesFilter.d.ts +15 -2
  223. package/package/components/variables/variablesFilter.js +11 -2
  224. package/package/components/worker_group.js +1 -0
  225. package/package/components/workspaceSettings/DucklakeSettings.svelte +33 -41
  226. package/package/consts.d.ts +1 -0
  227. package/package/consts.js +1 -0
  228. package/package/editorLangUtils.d.ts +1 -1
  229. package/package/editorLangUtils.js +2 -0
  230. package/package/gen/core/OpenAPI.js +1 -1
  231. package/package/gen/schemas.gen.d.ts +94 -294
  232. package/package/gen/schemas.gen.js +94 -294
  233. package/package/gen/services.gen.d.ts +5 -121
  234. package/package/gen/services.gen.js +5 -237
  235. package/package/gen/types.gen.d.ts +91 -715
  236. package/package/hubPaths.json +6 -3
  237. package/package/infer.d.ts +55 -0
  238. package/package/infer.js +131 -0
  239. package/package/infer.svelte.js +2 -0
  240. package/package/mcpEndpointTools.js +213 -22
  241. package/package/script_helpers.d.ts +3 -0
  242. package/package/script_helpers.js +26 -0
  243. package/package/scripts.d.ts +2 -1
  244. package/package/scripts.js +15 -3
  245. package/package/stores.d.ts +2 -0
  246. package/package/system_prompts/prompts.d.ts +6 -5
  247. package/package/system_prompts/prompts.js +188 -29
  248. package/package/user.js +5 -1
  249. package/package/utils.js +21 -0
  250. package/package/utils_deployable.d.ts +7 -0
  251. package/package/utils_workspace_deploy.js +36 -8
  252. package/package.json +6 -5
@@ -0,0 +1,723 @@
1
+ <script lang="ts">import { Button, Tab, Tabs } from '../common';
2
+ import { sendUserToast } from '../../toast';
3
+ import { Loader2, RefreshCw, ChevronDown, ChevronRight, ArrowDown } from 'lucide-svelte';
4
+ let loading = $state(false);
5
+ let jobsLoading = $state(false);
6
+ let data = $state(null);
7
+ let jobsData = $state(null);
8
+ let error = $state(null);
9
+ let jobsError = $state(null);
10
+ let scanLimit = $state(10000);
11
+ let activeTab = $state('overview');
12
+ let slowSort = $state('total');
13
+ let slowSortLoading = $state(false);
14
+ let expandedQueries = $state({});
15
+ async function resetSlowStats() {
16
+ if (!confirm('Reset pg_stat_statements? This clears cumulative stats for ALL queries on this postgres instance.'))
17
+ return;
18
+ try {
19
+ const response = await fetch(`/api/db_health/slow_queries/reset`, {
20
+ method: 'POST',
21
+ credentials: 'include'
22
+ });
23
+ if (!response.ok) {
24
+ const text = await response.text();
25
+ throw new Error(text || `HTTP ${response.status}`);
26
+ }
27
+ // refetch with current sort
28
+ const refetch = await fetch(`/api/db_health/slow_queries?sort=${slowSort}`, {
29
+ credentials: 'include'
30
+ });
31
+ if (refetch.ok && data) {
32
+ data.slow_queries = await refetch.json();
33
+ expandedQueries = {};
34
+ }
35
+ sendUserToast('pg_stat_statements reset successfully', false);
36
+ }
37
+ catch (e) {
38
+ sendUserToast('Failed to reset stats: ' + e.message, true);
39
+ }
40
+ }
41
+ async function setSlowSort(sort) {
42
+ if (sort === slowSort || slowSortLoading)
43
+ return;
44
+ slowSort = sort;
45
+ slowSortLoading = true;
46
+ try {
47
+ const response = await fetch(`/api/db_health/slow_queries?sort=${sort}`, {
48
+ credentials: 'include'
49
+ });
50
+ if (!response.ok) {
51
+ const text = await response.text();
52
+ throw new Error(text || `HTTP ${response.status}`);
53
+ }
54
+ const slowQueries = await response.json();
55
+ if (data) {
56
+ data.slow_queries = slowQueries;
57
+ expandedQueries = {};
58
+ }
59
+ }
60
+ catch (e) {
61
+ sendUserToast('Failed to re-sort slow queries: ' + e.message, true);
62
+ }
63
+ finally {
64
+ slowSortLoading = false;
65
+ }
66
+ }
67
+ const scanLimitOptions = [
68
+ { label: '10,000', value: 10000 },
69
+ { label: '50,000', value: 50000 },
70
+ { label: '100,000', value: 100000 },
71
+ { label: '500,000', value: 500000 }
72
+ ];
73
+ let expandedSections = $state({
74
+ database_size: true,
75
+ job_retention: true,
76
+ large_results: true,
77
+ connection_pool: true,
78
+ table_maintenance: true,
79
+ slow_queries: true,
80
+ datatables: true
81
+ });
82
+ function toggleSection(key) {
83
+ expandedSections[key] = !expandedSections[key];
84
+ }
85
+ async function runFastDiagnostics() {
86
+ loading = true;
87
+ error = null;
88
+ try {
89
+ const response = await fetch(`/api/db_health`, { credentials: 'include' });
90
+ if (!response.ok) {
91
+ const text = await response.text();
92
+ throw new Error(text || `HTTP ${response.status}`);
93
+ }
94
+ data = await response.json();
95
+ // main endpoint always returns slow_queries sorted by total
96
+ slowSort = 'total';
97
+ expandedQueries = {};
98
+ }
99
+ catch (e) {
100
+ error = e.message;
101
+ sendUserToast('Failed to run diagnostics: ' + e.message, true);
102
+ }
103
+ finally {
104
+ loading = false;
105
+ }
106
+ }
107
+ async function runJobsDiagnostics() {
108
+ jobsLoading = true;
109
+ jobsError = null;
110
+ try {
111
+ const response = await fetch(`/api/db_health/jobs?scan_limit=${scanLimit}`, {
112
+ credentials: 'include'
113
+ });
114
+ if (!response.ok) {
115
+ const text = await response.text();
116
+ throw new Error(text || `HTTP ${response.status}`);
117
+ }
118
+ jobsData = await response.json();
119
+ }
120
+ catch (e) {
121
+ jobsError = e.message;
122
+ sendUserToast('Failed to run job diagnostics: ' + e.message, true);
123
+ }
124
+ finally {
125
+ jobsLoading = false;
126
+ }
127
+ }
128
+ $effect(() => {
129
+ if (activeTab === 'overview') {
130
+ runFastDiagnostics();
131
+ }
132
+ else if (activeTab === 'jobs') {
133
+ runJobsDiagnostics();
134
+ }
135
+ });
136
+ function statusColor(status) {
137
+ switch (status) {
138
+ case 'green':
139
+ return 'text-green-600 dark:text-green-400';
140
+ case 'yellow':
141
+ return 'text-yellow-600 dark:text-yellow-400';
142
+ case 'red':
143
+ return 'text-red-600 dark:text-red-400';
144
+ default:
145
+ return 'text-tertiary';
146
+ }
147
+ }
148
+ function statusBadge(status) {
149
+ switch (status) {
150
+ case 'green':
151
+ return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200';
152
+ case 'yellow':
153
+ return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200';
154
+ case 'red':
155
+ return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200';
156
+ default:
157
+ return 'bg-surface-secondary text-secondary';
158
+ }
159
+ }
160
+ function formatBytes(bytes) {
161
+ if (bytes === 0)
162
+ return '0 B';
163
+ const k = 1024;
164
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
165
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
166
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
167
+ }
168
+ function formatNumber(n) {
169
+ return n.toLocaleString();
170
+ }
171
+ function formatMs(ms) {
172
+ if (ms < 1)
173
+ return '<1 ms';
174
+ if (ms < 1000)
175
+ return ms.toFixed(1) + ' ms';
176
+ return (ms / 1000).toFixed(2) + ' s';
177
+ }
178
+ function formatDate(d) {
179
+ if (!d)
180
+ return 'Never';
181
+ return new Date(d).toLocaleString();
182
+ }
183
+ </script>
184
+
185
+ <div class="flex flex-col gap-4">
186
+ <Tabs bind:selected={activeTab}>
187
+ <Tab value="overview" label="Overview" />
188
+ <Tab value="jobs" label="Jobs" />
189
+ </Tabs>
190
+
191
+ {#if activeTab === 'overview'}
192
+ <div class="flex items-center justify-end gap-2">
193
+ <Button
194
+ variant="subtle"
195
+ onclick={runFastDiagnostics}
196
+ disabled={loading}
197
+ startIcon={{ icon: loading ? Loader2 : RefreshCw }}
198
+ size="xs"
199
+ >
200
+ {loading ? 'Refreshing...' : 'Refresh'}
201
+ </Button>
202
+ </div>
203
+
204
+ {#if error}
205
+ <div
206
+ class="rounded border border-red-300 bg-red-50 p-3 text-sm text-red-700 dark:border-red-700 dark:bg-red-950 dark:text-red-300"
207
+ >
208
+ {error}
209
+ </div>
210
+ {/if}
211
+
212
+ {#if data}
213
+ <!-- Database Size -->
214
+ <section class="border-surface-secondary rounded-md border">
215
+ <button
216
+ class="flex w-full items-center justify-between p-3 text-left hover:bg-surface-secondary/50"
217
+ onclick={() => toggleSection('database_size')}
218
+ >
219
+ <h3 class="text-primary text-sm font-semibold">Database Size</h3>
220
+ <div class="flex items-center gap-2">
221
+ <span class="text-tertiary text-xs">{data.database_size.total_size_pretty}</span>
222
+ {#if expandedSections.database_size}
223
+ <ChevronDown size={16} />
224
+ {:else}
225
+ <ChevronRight size={16} />
226
+ {/if}
227
+ </div>
228
+ </button>
229
+ {#if expandedSections.database_size}
230
+ <div class="border-surface-secondary border-t p-3">
231
+ <p class="text-secondary mb-2 text-xs">
232
+ Total database size: <strong>{data.database_size.total_size_pretty}</strong>
233
+ </p>
234
+ <div class="overflow-x-auto">
235
+ <table class="w-full text-left text-xs">
236
+ <thead>
237
+ <tr class="text-tertiary border-surface-secondary border-b">
238
+ <th class="pb-1 pr-4">Table</th>
239
+ <th class="pb-1 pr-4 text-right">Size</th>
240
+ </tr>
241
+ </thead>
242
+ <tbody>
243
+ {#each data.database_size.top_tables as t}
244
+ <tr class="border-surface-secondary border-b last:border-0">
245
+ <td class="text-primary py-1 pr-4 font-mono">{t.table_name}</td>
246
+ <td class="text-secondary py-1 pr-4 text-right">{t.total_size_pretty}</td>
247
+ </tr>
248
+ {/each}
249
+ </tbody>
250
+ </table>
251
+ </div>
252
+ </div>
253
+ {/if}
254
+ </section>
255
+
256
+ <!-- Connection Pool -->
257
+ <section class="border-surface-secondary rounded-md border">
258
+ <button
259
+ class="flex w-full items-center justify-between p-3 text-left hover:bg-surface-secondary/50"
260
+ onclick={() => toggleSection('connection_pool')}
261
+ >
262
+ <h3 class="text-primary text-sm font-semibold">Database Connections</h3>
263
+ <div class="flex items-center gap-2">
264
+ <span
265
+ class="rounded px-1.5 py-0.5 text-xs font-medium {statusBadge(
266
+ data.connection_pool.status
267
+ )}"
268
+ >
269
+ {data.connection_pool.status}
270
+ </span>
271
+ {#if expandedSections.connection_pool}
272
+ <ChevronDown size={16} />
273
+ {:else}
274
+ <ChevronRight size={16} />
275
+ {/if}
276
+ </div>
277
+ </button>
278
+ {#if expandedSections.connection_pool}
279
+ <div class="border-surface-secondary flex flex-col gap-1 border-t p-3 text-xs">
280
+ <p class="text-secondary">
281
+ Total connections: <strong>{data.connection_pool.pg_total_connections}</strong> / Max:
282
+ <strong>{data.connection_pool.pg_max_connections}</strong>
283
+ </p>
284
+ <p class="text-secondary">
285
+ Active: <strong>{data.connection_pool.pg_active_connections}</strong>
286
+ / Idle: <strong>{data.connection_pool.pg_idle_connections}</strong>
287
+ </p>
288
+ <p class="{statusColor(data.connection_pool.status)} mt-1 font-medium">
289
+ {data.connection_pool.message}
290
+ </p>
291
+ </div>
292
+ {/if}
293
+ </section>
294
+
295
+ <!-- Table Maintenance -->
296
+ <section class="border-surface-secondary rounded-md border">
297
+ <button
298
+ class="flex w-full items-center justify-between p-3 text-left hover:bg-surface-secondary/50"
299
+ onclick={() => toggleSection('table_maintenance')}
300
+ >
301
+ <h3 class="text-primary text-sm font-semibold">Table Maintenance (Vacuum/Bloat)</h3>
302
+ <div class="flex items-center gap-2">
303
+ {#if expandedSections.table_maintenance}
304
+ <ChevronDown size={16} />
305
+ {:else}
306
+ <ChevronRight size={16} />
307
+ {/if}
308
+ </div>
309
+ </button>
310
+ {#if expandedSections.table_maintenance}
311
+ <div class="border-surface-secondary border-t p-3">
312
+ <div class="overflow-x-auto">
313
+ <table class="w-full text-left text-xs">
314
+ <thead>
315
+ <tr class="text-tertiary border-surface-secondary border-b">
316
+ <th class="pb-1 pr-4">Table</th>
317
+ <th class="pb-1 pr-4 text-right">Live Tuples</th>
318
+ <th class="pb-1 pr-4 text-right">Dead Tuples</th>
319
+ <th class="pb-1 pr-4 text-right">Dead %</th>
320
+ <th class="pb-1 pr-4 text-right">Last Vacuum</th>
321
+ <th class="pb-1 pr-4 text-right">Last Analyze</th>
322
+ <th class="pb-1 pr-4">Status</th>
323
+ </tr>
324
+ </thead>
325
+ <tbody>
326
+ {#each data.table_maintenance as t}
327
+ <tr class="border-surface-secondary border-b last:border-0">
328
+ <td class="text-primary py-1 pr-4 font-mono">{t.table_name}</td>
329
+ <td class="text-secondary py-1 pr-4 text-right"
330
+ >{formatNumber(t.live_tuples)}</td
331
+ >
332
+ <td class="text-secondary py-1 pr-4 text-right"
333
+ >{formatNumber(t.dead_tuples)}</td
334
+ >
335
+ <td class="text-secondary py-1 pr-4 text-right"
336
+ >{(t.dead_ratio * 100).toFixed(1)}%</td
337
+ >
338
+ <td class="text-secondary py-1 pr-4 text-right"
339
+ >{formatDate(t.last_autovacuum)}</td
340
+ >
341
+ <td class="text-secondary py-1 pr-4 text-right"
342
+ >{formatDate(t.last_autoanalyze)}</td
343
+ >
344
+ <td class="py-1 pr-4">
345
+ <span
346
+ class="rounded px-1.5 py-0.5 text-xs font-medium {statusBadge(t.status)}"
347
+ >
348
+ {t.status}
349
+ </span>
350
+ </td>
351
+ </tr>
352
+ {/each}
353
+ </tbody>
354
+ </table>
355
+ </div>
356
+ </div>
357
+ {/if}
358
+ </section>
359
+
360
+ <!-- Slow Queries -->
361
+ <section class="border-surface-secondary rounded-md border">
362
+ <button
363
+ class="flex w-full items-center justify-between p-3 text-left hover:bg-surface-secondary/50"
364
+ onclick={() => toggleSection('slow_queries')}
365
+ >
366
+ <h3 class="text-primary text-sm font-semibold">Slow Queries</h3>
367
+ <div class="flex items-center gap-2">
368
+ {#if expandedSections.slow_queries}
369
+ <ChevronDown size={16} />
370
+ {:else}
371
+ <ChevronRight size={16} />
372
+ {/if}
373
+ </div>
374
+ </button>
375
+ {#if expandedSections.slow_queries}
376
+ <div class="border-surface-secondary border-t p-3">
377
+ {#if data.slow_queries == null}
378
+ <p class="text-tertiary text-xs">Slow query data not available.</p>
379
+ {:else if data.slow_queries.message}
380
+ <div class="flex flex-col gap-2 text-xs">
381
+ <p class="text-secondary">{data.slow_queries.message}</p>
382
+ <div class="bg-surface-secondary rounded p-2">
383
+ <p class="text-secondary mb-1 font-semibold">
384
+ Setup (requires superuser + a postgres restart):
385
+ </p>
386
+ <ol class="text-tertiary ml-4 list-decimal space-y-1">
387
+ <li>
388
+ On the postgres server, enable the preload library:
389
+ <pre
390
+ class="text-primary mt-0.5 overflow-x-auto whitespace-pre-wrap break-all font-mono"
391
+ >ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements';</pre
392
+ >
393
+ </li>
394
+ <li>
395
+ Restart postgres so the library is actually loaded (e.g. <code
396
+ class="text-primary font-mono">sudo systemctl restart postgresql</code
397
+ >, or use your managed DB console for RDS/Cloud SQL).
398
+ </li>
399
+ <li>
400
+ On this database, create the extension:
401
+ <pre
402
+ class="text-primary mt-0.5 overflow-x-auto whitespace-pre-wrap break-all font-mono"
403
+ >CREATE EXTENSION pg_stat_statements;</pre
404
+ >
405
+ </li>
406
+ <li>Click Refresh on this page.</li>
407
+ </ol>
408
+ </div>
409
+ </div>
410
+ {:else}
411
+ <div class="flex items-start justify-between gap-6 pb-3">
412
+ <div class="text-tertiary flex flex-col gap-0.5 text-xs">
413
+ <div class="flex items-center gap-2">
414
+ <span
415
+ >Top 50 queries from pg_stat_statements, sorted server-side. Click a column to
416
+ re-sort. Click a row to show the full query.</span
417
+ >
418
+ {#if slowSortLoading}
419
+ <Loader2 size={12} class="animate-spin shrink-0" />
420
+ {/if}
421
+ </div>
422
+ {#if data.slow_queries.stats_reset}
423
+ <span>Stats since: {formatDate(data.slow_queries.stats_reset)}</span>
424
+ {/if}
425
+ </div>
426
+ <div class="shrink-0">
427
+ <Button variant="subtle" size="xs" onclick={resetSlowStats}>Reset stats</Button>
428
+ </div>
429
+ </div>
430
+ {#if data.slow_queries.queries.length === 0}
431
+ <p class="text-tertiary text-xs">No slow queries found.</p>
432
+ {:else}
433
+ <div class="overflow-x-auto">
434
+ <table class="w-full text-left text-xs">
435
+ <thead>
436
+ <tr class="text-tertiary border-surface-secondary border-b">
437
+ <th class="pb-1 pr-4">Query</th>
438
+ <th class="pb-1 pr-4 text-right">
439
+ <button
440
+ class="hover:text-primary inline-flex items-center gap-0.5"
441
+ onclick={() => setSlowSort('calls')}
442
+ disabled={slowSortLoading}
443
+ >
444
+ Calls
445
+ {#if slowSort === 'calls'}
446
+ <ArrowDown size={10} />
447
+ {/if}
448
+ </button>
449
+ </th>
450
+ <th class="pb-1 pr-4 text-right">
451
+ <button
452
+ class="hover:text-primary inline-flex items-center gap-0.5"
453
+ onclick={() => setSlowSort('total')}
454
+ disabled={slowSortLoading}
455
+ >
456
+ Total Time
457
+ {#if slowSort === 'total'}
458
+ <ArrowDown size={10} />
459
+ {/if}
460
+ </button>
461
+ </th>
462
+ <th class="pb-1 pr-4 text-right">
463
+ <button
464
+ class="hover:text-primary inline-flex items-center gap-0.5"
465
+ onclick={() => setSlowSort('mean')}
466
+ disabled={slowSortLoading}
467
+ >
468
+ Mean Time
469
+ {#if slowSort === 'mean'}
470
+ <ArrowDown size={10} />
471
+ {/if}
472
+ </button>
473
+ </th>
474
+ </tr>
475
+ </thead>
476
+ <tbody>
477
+ {#each data.slow_queries.queries as q, i}
478
+ <tr
479
+ class="border-surface-secondary hover:bg-surface-secondary/40 cursor-pointer border-b last:border-0"
480
+ onclick={() => (expandedQueries[i] = !expandedQueries[i])}
481
+ >
482
+ <td class="text-primary py-1 pr-4 font-mono">
483
+ {#if expandedQueries[i]}
484
+ <pre class="whitespace-pre-wrap break-all">{q.query}</pre>
485
+ {:else}
486
+ <div class="max-w-md truncate">{q.query}</div>
487
+ {/if}
488
+ </td>
489
+ <td class="text-secondary py-1 pr-4 text-right align-top"
490
+ >{formatNumber(q.calls)}</td
491
+ >
492
+ <td class="text-secondary py-1 pr-4 text-right align-top"
493
+ >{formatMs(q.total_exec_time_ms)}</td
494
+ >
495
+ <td class="text-secondary py-1 pr-4 text-right align-top"
496
+ >{formatMs(q.mean_exec_time_ms)}</td
497
+ >
498
+ </tr>
499
+ {/each}
500
+ </tbody>
501
+ </table>
502
+ </div>
503
+ {/if}
504
+ {/if}
505
+ </div>
506
+ {/if}
507
+ </section>
508
+
509
+ <!-- Datatables -->
510
+ <section class="border-surface-secondary rounded-md border">
511
+ <button
512
+ class="flex w-full items-center justify-between p-3 text-left hover:bg-surface-secondary/50"
513
+ onclick={() => toggleSection('datatables')}
514
+ >
515
+ <h3 class="text-primary text-sm font-semibold">Datatables (Instance Storage)</h3>
516
+ <div class="flex items-center gap-2">
517
+ {#if expandedSections.datatables}
518
+ <ChevronDown size={16} />
519
+ {:else}
520
+ <ChevronRight size={16} />
521
+ {/if}
522
+ </div>
523
+ </button>
524
+ {#if expandedSections.datatables}
525
+ <div class="border-surface-secondary border-t p-3">
526
+ {#if data.datatables.length === 0}
527
+ <p class="text-tertiary text-xs">No instance-stored datatables found.</p>
528
+ {:else}
529
+ <div class="overflow-x-auto">
530
+ <table class="w-full text-left text-xs">
531
+ <thead>
532
+ <tr class="text-tertiary border-surface-secondary border-b">
533
+ <th class="pb-1 pr-4">Workspace</th>
534
+ <th class="pb-1 pr-4">Name</th>
535
+ <th class="pb-1 pr-4">Table</th>
536
+ <th class="pb-1 pr-4 text-right">Size</th>
537
+ <th class="pb-1 pr-4 text-right">Est. Rows</th>
538
+ </tr>
539
+ </thead>
540
+ <tbody>
541
+ {#each data.datatables as dt}
542
+ <tr class="border-surface-secondary border-b last:border-0">
543
+ <td class="text-secondary py-1 pr-4">{dt.workspace_id}</td>
544
+ <td class="text-primary py-1 pr-4">{dt.name}</td>
545
+ <td class="text-secondary py-1 pr-4 font-mono">{dt.table_name}</td>
546
+ <td class="text-secondary py-1 pr-4 text-right">{dt.size_pretty}</td>
547
+ <td class="text-secondary py-1 pr-4 text-right"
548
+ >{formatNumber(Math.round(dt.estimated_rows))}</td
549
+ >
550
+ </tr>
551
+ {/each}
552
+ </tbody>
553
+ </table>
554
+ </div>
555
+ {/if}
556
+ </div>
557
+ {/if}
558
+ </section>
559
+ {/if}
560
+ {/if}
561
+
562
+ {#if activeTab === 'jobs'}
563
+ <div class="flex items-center justify-between gap-2">
564
+ <label class="text-tertiary flex items-center gap-1 whitespace-nowrap text-xs">
565
+ Scan last
566
+ <select
567
+ class="border-surface-secondary text-secondary rounded border bg-transparent px-1 py-0.5 text-xs"
568
+ bind:value={scanLimit}
569
+ >
570
+ {#each scanLimitOptions as opt}
571
+ <option value={opt.value}>{opt.label}</option>
572
+ {/each}
573
+ </select>
574
+ jobs
575
+ </label>
576
+ <Button
577
+ variant="subtle"
578
+ onclick={runJobsDiagnostics}
579
+ disabled={jobsLoading}
580
+ startIcon={{ icon: jobsLoading ? Loader2 : RefreshCw }}
581
+ size="xs"
582
+ >
583
+ {jobsLoading ? 'Scanning...' : 'Refresh'}
584
+ </Button>
585
+ </div>
586
+
587
+ {#if jobsError}
588
+ <div
589
+ class="rounded border border-red-300 bg-red-50 p-3 text-sm text-red-700 dark:border-red-700 dark:bg-red-950 dark:text-red-300"
590
+ >
591
+ {jobsError}
592
+ </div>
593
+ {/if}
594
+ <!-- Job Retention -->
595
+ <section class="border-surface-secondary rounded-md border">
596
+ <button
597
+ class="flex w-full items-center justify-between p-3 text-left hover:bg-surface-secondary/50"
598
+ onclick={() => toggleSection('job_retention')}
599
+ >
600
+ <h3 class="text-primary text-sm font-semibold">Job Retention</h3>
601
+ <div class="flex items-center gap-2">
602
+ {#if jobsData}
603
+ <span
604
+ class="rounded px-1.5 py-0.5 text-xs font-medium {statusBadge(
605
+ jobsData.job_retention.status
606
+ )}"
607
+ >
608
+ {jobsData.job_retention.status}
609
+ </span>
610
+ {:else if jobsLoading}
611
+ <Loader2 size={14} class="text-tertiary animate-spin" />
612
+ {/if}
613
+ {#if expandedSections.job_retention}
614
+ <ChevronDown size={16} />
615
+ {:else}
616
+ <ChevronRight size={16} />
617
+ {/if}
618
+ </div>
619
+ </button>
620
+ {#if expandedSections.job_retention}
621
+ <div class="border-surface-secondary border-t p-3 text-xs">
622
+ {#if jobsData}
623
+ <div class="flex flex-col gap-1">
624
+ <p class="text-secondary">
625
+ Total completed jobs: <strong
626
+ >{formatNumber(jobsData.job_retention.total_completed_jobs)}</strong
627
+ >
628
+ </p>
629
+ <p class="text-secondary">
630
+ Oldest job: <strong>{formatDate(jobsData.job_retention.oldest_completed_at)}</strong
631
+ >
632
+ </p>
633
+ <p class="text-secondary">
634
+ Retention period: <strong>
635
+ {jobsData.job_retention.retention_period_secs
636
+ ? formatNumber(jobsData.job_retention.retention_period_secs) + 's'
637
+ : 'Not configured'}
638
+ </strong>
639
+ </p>
640
+ <p class="{statusColor(jobsData.job_retention.status)} mt-1 font-medium">
641
+ {jobsData.job_retention.message}
642
+ </p>
643
+ </div>
644
+ {:else}
645
+ <p class="text-tertiary">Click Refresh to load.</p>
646
+ {/if}
647
+ </div>
648
+ {/if}
649
+ </section>
650
+
651
+ <!-- Large Results -->
652
+ <section class="border-surface-secondary rounded-md border">
653
+ <button
654
+ class="flex w-full items-center justify-between p-3 text-left hover:bg-surface-secondary/50"
655
+ onclick={() => toggleSection('large_results')}
656
+ >
657
+ <h3 class="text-primary text-sm font-semibold"
658
+ >Large Job Results (last {scanLimit.toLocaleString()} jobs)</h3
659
+ >
660
+ <div class="flex items-center gap-2">
661
+ {#if jobsData && jobsData.large_results.avg_result_size_bytes != null}
662
+ <span class="text-tertiary text-xs"
663
+ >avg: {formatBytes(jobsData.large_results.avg_result_size_bytes)}</span
664
+ >
665
+ {:else if jobsLoading}
666
+ <Loader2 size={14} class="text-tertiary animate-spin" />
667
+ {/if}
668
+ {#if expandedSections.large_results}
669
+ <ChevronDown size={16} />
670
+ {:else}
671
+ <ChevronRight size={16} />
672
+ {/if}
673
+ </div>
674
+ </button>
675
+ {#if expandedSections.large_results}
676
+ <div class="border-surface-secondary border-t p-3">
677
+ {#if !jobsData}
678
+ <p class="text-tertiary text-xs">Click Refresh to load.</p>
679
+ {:else if jobsData.large_results.top_large_results.length === 0}
680
+ <p class="text-tertiary text-xs"
681
+ >No job results larger than 1 KB found in the scanned jobs.</p
682
+ >
683
+ {:else}
684
+ <div class="overflow-x-auto">
685
+ <table class="w-full text-left text-xs">
686
+ <thead>
687
+ <tr class="text-tertiary border-surface-secondary border-b">
688
+ <th class="pb-1 pr-4">Job ID</th>
689
+ <th class="pb-1 pr-4">Workspace</th>
690
+ <th class="pb-1 pr-4">Script</th>
691
+ <th class="pb-1 pr-4 text-right">Result Size</th>
692
+ <th class="pb-1 pr-4 text-right">Completed</th>
693
+ </tr>
694
+ </thead>
695
+ <tbody>
696
+ {#each jobsData.large_results.top_large_results as r}
697
+ <tr class="border-surface-secondary border-b last:border-0">
698
+ <td class="text-primary py-1 pr-4 font-mono"
699
+ ><a
700
+ href="/run/{r.id}?workspace={r.workspace_id}"
701
+ class="text-blue-600 hover:underline dark:text-blue-400"
702
+ >{r.id.substring(0, 8)}...</a
703
+ ></td
704
+ >
705
+ <td class="text-secondary py-1 pr-4">{r.workspace_id}</td>
706
+ <td class="text-secondary py-1 pr-4 font-mono">{r.runnable_path ?? '-'}</td>
707
+ <td class="text-secondary py-1 pr-4 text-right"
708
+ >{formatBytes(r.result_size_bytes)}</td
709
+ >
710
+ <td class="text-secondary py-1 pr-4 text-right"
711
+ >{formatDate(r.completed_at)}</td
712
+ >
713
+ </tr>
714
+ {/each}
715
+ </tbody>
716
+ </table>
717
+ </div>
718
+ {/if}
719
+ </div>
720
+ {/if}
721
+ </section>
722
+ {/if}
723
+ </div>