@platformatic/watt-admin 0.5.0 → 0.6.0-alpha.10

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 (138) hide show
  1. package/README.md +163 -33
  2. package/cli.d.ts +3 -0
  3. package/cli.js +85 -91
  4. package/lib/start.d.ts +3 -0
  5. package/lib/start.js +87 -12
  6. package/package.json +53 -47
  7. package/renovate.json +17 -0
  8. package/watt.json +17 -9
  9. package/web/backend/global.d.ts +17 -0
  10. package/web/backend/platformatic.json +2 -3
  11. package/web/backend/plugins/metrics.ts +15 -0
  12. package/web/backend/plugins/websocket.ts +6 -0
  13. package/web/backend/routes/metrics.ts +46 -0
  14. package/web/backend/routes/proxy.ts +41 -0
  15. package/web/backend/routes/root.ts +204 -0
  16. package/web/backend/routes/ws.ts +45 -0
  17. package/web/backend/schemas/index.ts +226 -0
  18. package/web/backend/utils/bytes.ts +1 -0
  19. package/web/backend/utils/client.openapi.ts +29 -0
  20. package/web/backend/utils/constants.ts +4 -0
  21. package/web/backend/utils/metrics-helpers.ts +89 -0
  22. package/web/backend/utils/metrics.ts +202 -0
  23. package/web/backend/utils/rps.ts +3 -0
  24. package/web/backend/utils/runtimes.ts +21 -0
  25. package/web/backend/utils/states.ts +3 -0
  26. package/web/composer/platformatic.json +3 -3
  27. package/web/frontend/dist/index.html +3012 -6
  28. package/web/frontend/index.d.ts +4 -0
  29. package/web/frontend/index.html +0 -1
  30. package/web/frontend/playwright.config.ts +27 -0
  31. package/web/frontend/postcss.config.ts +14 -0
  32. package/web/frontend/watt.json +2 -2
  33. package/CLAUDE.md +0 -32
  34. package/web/backend/dist/plugins/metrics.js +0 -13
  35. package/web/backend/dist/plugins/metrics.js.map +0 -1
  36. package/web/backend/dist/plugins/websocket.js +0 -11
  37. package/web/backend/dist/plugins/websocket.js.map +0 -1
  38. package/web/backend/dist/routes/metrics.js +0 -45
  39. package/web/backend/dist/routes/metrics.js.map +0 -1
  40. package/web/backend/dist/routes/proxy.js +0 -34
  41. package/web/backend/dist/routes/proxy.js.map +0 -1
  42. package/web/backend/dist/routes/root.js +0 -174
  43. package/web/backend/dist/routes/root.js.map +0 -1
  44. package/web/backend/dist/routes/ws.js +0 -38
  45. package/web/backend/dist/routes/ws.js.map +0 -1
  46. package/web/backend/dist/schemas/index.js +0 -184
  47. package/web/backend/dist/schemas/index.js.map +0 -1
  48. package/web/backend/dist/utils/bytes.js +0 -6
  49. package/web/backend/dist/utils/bytes.js.map +0 -1
  50. package/web/backend/dist/utils/calc.js +0 -248
  51. package/web/backend/dist/utils/calc.js.map +0 -1
  52. package/web/backend/dist/utils/client.openapi.js +0 -31
  53. package/web/backend/dist/utils/client.openapi.js.map +0 -1
  54. package/web/backend/dist/utils/log.js +0 -29
  55. package/web/backend/dist/utils/log.js.map +0 -1
  56. package/web/backend/dist/utils/metrics-helpers.js +0 -72
  57. package/web/backend/dist/utils/metrics-helpers.js.map +0 -1
  58. package/web/backend/dist/utils/metrics.js +0 -183
  59. package/web/backend/dist/utils/metrics.js.map +0 -1
  60. package/web/backend/dist/utils/rps.js +0 -6
  61. package/web/backend/dist/utils/rps.js.map +0 -1
  62. package/web/backend/openapi.json +0 -1119
  63. package/web/backend/tsconfig.json +0 -21
  64. package/web/frontend/dist/assets/Collection.vue-3bb6N9VS.js +0 -2
  65. package/web/frontend/dist/assets/Collection.vue-3bb6N9VS.js.map +0 -1
  66. package/web/frontend/dist/assets/CollectionAuthentication.vue-BUiBAnw8.js +0 -2
  67. package/web/frontend/dist/assets/CollectionAuthentication.vue-BUiBAnw8.js.map +0 -1
  68. package/web/frontend/dist/assets/CollectionCookies.vue-VpQ7oUfB.js +0 -2
  69. package/web/frontend/dist/assets/CollectionCookies.vue-VpQ7oUfB.js.map +0 -1
  70. package/web/frontend/dist/assets/CollectionEnvironment.vue-B9aL6C1f.js +0 -2
  71. package/web/frontend/dist/assets/CollectionEnvironment.vue-B9aL6C1f.js.map +0 -1
  72. package/web/frontend/dist/assets/CollectionOverview.vue-Gs4h2k55.js +0 -2
  73. package/web/frontend/dist/assets/CollectionOverview.vue-Gs4h2k55.js.map +0 -1
  74. package/web/frontend/dist/assets/CollectionScripts.vue-DI73bgJP.js +0 -2
  75. package/web/frontend/dist/assets/CollectionScripts.vue-DI73bgJP.js.map +0 -1
  76. package/web/frontend/dist/assets/CollectionServers.vue-CTh_DkWz.js +0 -2
  77. package/web/frontend/dist/assets/CollectionServers.vue-CTh_DkWz.js.map +0 -1
  78. package/web/frontend/dist/assets/CollectionSettings.vue--aJQ9wMq.js +0 -2
  79. package/web/frontend/dist/assets/CollectionSettings.vue--aJQ9wMq.js.map +0 -1
  80. package/web/frontend/dist/assets/CollectionSync.vue-CwmTdwlV.js +0 -2
  81. package/web/frontend/dist/assets/CollectionSync.vue-CwmTdwlV.js.map +0 -1
  82. package/web/frontend/dist/assets/CommandActionInput.vue-TK77rD5U.js +0 -2
  83. package/web/frontend/dist/assets/CommandActionInput.vue-TK77rD5U.js.map +0 -1
  84. package/web/frontend/dist/assets/Cookies.vue-C3PhNsCO.js +0 -2
  85. package/web/frontend/dist/assets/Cookies.vue-C3PhNsCO.js.map +0 -1
  86. package/web/frontend/dist/assets/DataTableHeader.vue-DbIRXelw.js +0 -2
  87. package/web/frontend/dist/assets/DataTableHeader.vue-DbIRXelw.js.map +0 -1
  88. package/web/frontend/dist/assets/DeleteSidebarListElement.vue-B9hc23j9.js +0 -2
  89. package/web/frontend/dist/assets/DeleteSidebarListElement.vue-B9hc23j9.js.map +0 -1
  90. package/web/frontend/dist/assets/Draggable.vue-CgQ5Rr6l.js +0 -2
  91. package/web/frontend/dist/assets/Draggable.vue-CgQ5Rr6l.js.map +0 -1
  92. package/web/frontend/dist/assets/EditSidebarListElement.vue-fx233eKQ.js +0 -2
  93. package/web/frontend/dist/assets/EditSidebarListElement.vue-fx233eKQ.js.map +0 -1
  94. package/web/frontend/dist/assets/EmptyState.vue-CCWl6cFt.js +0 -23
  95. package/web/frontend/dist/assets/EmptyState.vue-CCWl6cFt.js.map +0 -1
  96. package/web/frontend/dist/assets/Environment.vue-BAgAaWzP.js +0 -2
  97. package/web/frontend/dist/assets/Environment.vue-BAgAaWzP.js.map +0 -1
  98. package/web/frontend/dist/assets/EnvironmentModal.vue-CoGQ0HVI.js +0 -2
  99. package/web/frontend/dist/assets/EnvironmentModal.vue-CoGQ0HVI.js.map +0 -1
  100. package/web/frontend/dist/assets/Form.vue-CPOtQXvw.js +0 -2
  101. package/web/frontend/dist/assets/Form.vue-CPOtQXvw.js.map +0 -1
  102. package/web/frontend/dist/assets/IconSelector.vue-s7y-7Ty-.js +0 -2
  103. package/web/frontend/dist/assets/IconSelector.vue-s7y-7Ty-.js.map +0 -1
  104. package/web/frontend/dist/assets/LibraryIcon.vue-CrkyDYXJ.js +0 -2
  105. package/web/frontend/dist/assets/LibraryIcon.vue-CrkyDYXJ.js.map +0 -1
  106. package/web/frontend/dist/assets/Request.vue-Cd99DAq4.js +0 -33
  107. package/web/frontend/dist/assets/Request.vue-Cd99DAq4.js.map +0 -1
  108. package/web/frontend/dist/assets/RequestRoot.vue-BVpdY64H.js +0 -20
  109. package/web/frontend/dist/assets/RequestRoot.vue-BVpdY64H.js.map +0 -1
  110. package/web/frontend/dist/assets/ScalarAsciiArt.vue-Bm66XRod.js +0 -3
  111. package/web/frontend/dist/assets/ScalarAsciiArt.vue-Bm66XRod.js.map +0 -1
  112. package/web/frontend/dist/assets/ScalarHotkey.vue-DGRaosjl.js +0 -2
  113. package/web/frontend/dist/assets/ScalarHotkey.vue-DGRaosjl.js.map +0 -1
  114. package/web/frontend/dist/assets/ScalarIconTrash.vue-BzkMDpLH.js +0 -2
  115. package/web/frontend/dist/assets/ScalarIconTrash.vue-BzkMDpLH.js.map +0 -1
  116. package/web/frontend/dist/assets/ScalarPopover.vue-DvIo_Yzb.js +0 -2
  117. package/web/frontend/dist/assets/ScalarPopover.vue-DvIo_Yzb.js.map +0 -1
  118. package/web/frontend/dist/assets/ScalarToggle.vue-C4n7WrpG.js +0 -2
  119. package/web/frontend/dist/assets/ScalarToggle.vue-C4n7WrpG.js.map +0 -1
  120. package/web/frontend/dist/assets/Settings.vue-vE6bOazq.js +0 -2
  121. package/web/frontend/dist/assets/Settings.vue-vE6bOazq.js.map +0 -1
  122. package/web/frontend/dist/assets/SidebarButton.vue-DAR-mVH7.js +0 -2
  123. package/web/frontend/dist/assets/SidebarButton.vue-DAR-mVH7.js.map +0 -1
  124. package/web/frontend/dist/assets/SidebarListElement.vue-CnWyRVB0.js +0 -2
  125. package/web/frontend/dist/assets/SidebarListElement.vue-CnWyRVB0.js.map +0 -1
  126. package/web/frontend/dist/assets/ViewLayout.vue-CEr_C1hG.js +0 -2
  127. package/web/frontend/dist/assets/ViewLayout.vue-CEr_C1hG.js.map +0 -1
  128. package/web/frontend/dist/assets/ViewLayoutContent.vue-t9k9noSS.js +0 -2
  129. package/web/frontend/dist/assets/ViewLayoutContent.vue-t9k9noSS.js.map +0 -1
  130. package/web/frontend/dist/assets/ViewLayoutSection.vue-BYrAaBmf.js +0 -2
  131. package/web/frontend/dist/assets/ViewLayoutSection.vue-BYrAaBmf.js.map +0 -1
  132. package/web/frontend/dist/assets/index-DN39RFTG.js +0 -2327
  133. package/web/frontend/dist/assets/index-DN39RFTG.js.map +0 -1
  134. package/web/frontend/dist/assets/index-DrvlsJch.css +0 -1
  135. package/web/frontend/dist/assets/mediaTypes-DEhrbNXe.js +0 -2
  136. package/web/frontend/dist/assets/mediaTypes-DEhrbNXe.js.map +0 -1
  137. package/web/frontend/postcss.config.cjs +0 -11
  138. package/web/frontend/tsconfig.json +0 -29
package/README.md CHANGED
@@ -1,70 +1,200 @@
1
- # watt-admin
1
+ # 🔌 Watt Admin
2
2
 
3
- ## CLI Tool
3
+ **Real-time monitoring and administration for your Platformatic applications**
4
4
 
5
- The project includes a command-line interface tool to check available Platformatic runtimes.
5
+ Watt Admin is an open-source developer monitoring tool that provides instant visibility into your Node.js services. Monitor performance, analyze logs, and troubleshoot issues—all from a single, intuitive dashboard.
6
+
7
+ ![Watt Admin Dashboard](https://blog.platformatic.dev/content/images/2025/01/CleanShot-2025-01-15-at-15.50.54@2x.png)
8
+
9
+ ## ✨ Features
10
+
11
+ ### 📊 **Performance Metrics**
12
+ - **Memory tracking**: RSS, heap usage, new/old space allocation with interactive charts
13
+ - **CPU monitoring**: Real-time CPU and event loop utilization
14
+ - **Latency analysis**: P90, P95, P99 percentiles at a glance
15
+ - **Request rates**: Monitor requests per second across all services
16
+
17
+ ### 📝 **Centralized Logging**
18
+ - View logs from all services in one place
19
+ - Filter by service or log level
20
+ - Toggle between formatted and raw JSON views
21
+ - Export logs for further analysis
22
+
23
+ ### 🎯 **Service Management**
24
+ - Monitor service status at a glance
25
+ - Drill down into individual service metrics
26
+ - Compare performance across services
27
+ - Restart services directly from the dashboard
28
+
29
+ ### 🔥 **Profiling & Recording** *(New!)*
30
+ - **CPU profiling**: Capture flame graphs to identify performance bottlenecks
31
+ - **Heap profiling**: Analyze memory allocation patterns
32
+ - **Offline analysis**: Generate self-contained HTML bundles for sharing and review
33
+ - **Recording mode**: Capture metrics and profiles over time for post-mortem analysis
34
+
35
+ ## 🚀 Quick Start
36
+
37
+ ### Using npx (Recommended)
38
+
39
+ Launch Watt Admin with a single command:
40
+
41
+ ```bash
42
+ npx wattpm admin
43
+ ```
44
+
45
+ The dashboard will automatically discover your running Platformatic runtimes and open at `http://localhost:4042`.
6
46
 
7
47
  ### Installation
8
48
 
9
- After installing the project dependencies, you can run the tool directly:
49
+ Install globally for convenient access:
10
50
 
11
51
  ```bash
12
- ./cli.js
52
+ npm install -g @platformatic/watt-admin
13
53
  ```
14
54
 
15
- To run the CLI on a custom port, you can just pass it as an argument with `./cli.js --port 4321`.
55
+ Then run:
16
56
 
17
- The tool is also available as a binary when installed globally or linked:
57
+ ```bash
58
+ watt-admin
59
+ ```
60
+
61
+ ## 💡 Usage
62
+
63
+ ### Basic Usage
64
+
65
+ When you run `watt-admin`, it automatically discovers all available Platformatic runtimes:
18
66
 
19
67
  ```bash
20
- npm link # Link the package locally
21
- watt-runtime # Run the command
68
+ $ watt-admin
69
+ Select a runtime: (Use arrow keys)
70
+ ❯ my-app (PID: 12345) (Started at 3/10/2025, 10:00:00 AM)
71
+ api-service (PID: 54321) (Started at 3/10/2025, 9:30:00 AM)
22
72
  ```
23
73
 
24
- ### Usage
74
+ If only one runtime is running, it will be selected automatically.
25
75
 
26
- The tool automatically discovers all available Platformatic runtimes. If multiple runtimes are found, it will prompt you to select one using an interactive menu.
76
+ ### Custom Port
27
77
 
78
+ Run Watt Admin on a different port:
79
+
80
+ ```bash
81
+ watt-admin --port 4321
28
82
  ```
29
- $ watt-runtime
30
- Select a runtime: (Use arrow keys)
31
- ❯ runtime-name (PID: 12345) (Started at 3/10/2025, 10:00:00 AM)
32
- another-runtime (PID: 54321) (Started at 3/10/2025, 9:30:00 AM)
83
+
84
+ ### Recording Mode
85
+
86
+ Capture metrics and profiling data for offline analysis:
87
+
88
+ ```bash
89
+ # Profile CPU usage
90
+ watt-admin --record --profile cpu
91
+
92
+ # Profile heap allocation
93
+ watt-admin --record --profile heap
33
94
  ```
34
95
 
35
- If only one runtime is available, it will be automatically selected.
96
+ When recording, press `Ctrl+C` to stop. Watt Admin will:
97
+ 1. Collect all metrics and profiling data
98
+ 2. Generate a self-contained HTML bundle
99
+ 3. Automatically open the bundle in your browser
100
+
101
+ The generated HTML file contains everything you need for offline analysis—perfect for sharing with your team or reviewing later.
102
+
103
+ ## 🏗️ Development
36
104
 
37
- ## Project setup
105
+ ### Prerequisites
38
106
 
107
+ - Node.js 18 or higher
108
+ - A running Platformatic runtime to monitor
109
+
110
+ ### Setup
111
+
112
+ ```bash
113
+ git clone https://github.com/platformatic/watt-admin.git
114
+ cd watt-admin
115
+ npm install
116
+ cp .env.sample .env
39
117
  ```
40
- npm install && cp .env.sample .env
118
+
119
+ ### Development Mode
120
+
121
+ Auto-reload both backend and frontend on changes:
122
+
123
+ ```bash
124
+ npm run dev
41
125
  ```
42
126
 
43
127
  ### Build
44
128
 
45
- ```
129
+ ```bash
46
130
  npm run build
47
131
  ```
48
132
 
49
- ### Start
133
+ ### Run Tests
50
134
 
51
- ```
52
- npm run start
135
+ ```bash
136
+ # Run all tests
137
+ npm test
138
+
139
+ # Run specific test suites
140
+ npm run test:cli # CLI tests
141
+ npm run test:backend # Backend tests
142
+ npm run test:frontend # Frontend tests
143
+ npm run test:e2e # End-to-end tests
53
144
  ```
54
145
 
55
- ### Dev Mode
56
-
57
- This will auto-reload both the backend and frontend when changes are made.
146
+ ### Project Structure
58
147
 
59
148
  ```
60
- npm run dev
149
+ watt-admin/
150
+ ├── cli.js # CLI entry point
151
+ ├── lib/ # Core CLI functionality
152
+ ├── web/
153
+ │ ├── backend/ # Fastify API server
154
+ │ ├── frontend/ # React dashboard
155
+ │ └── composer/ # Platformatic Gateway
156
+ ├── watt.json # Wattpm configuration
157
+ └── test/ # Test suites
61
158
  ```
62
159
 
63
- ### Navigate the app
160
+ ## 🎯 Use Cases
161
+
162
+ ### Development Workflow
163
+ Monitor your application while developing locally. Instantly see the impact of code changes on performance.
164
+
165
+ ### Debugging Performance Issues
166
+ Use CPU and heap profiling to identify bottlenecks and memory leaks before they reach production.
167
+
168
+ ### Team Collaboration
169
+ Generate offline HTML bundles to share performance data and profiling results with your team.
170
+
171
+ ### Learning and Optimization
172
+ Understand how your services behave under load and optimize based on real data.
173
+
174
+ ## 🔗 Related Tools
175
+
176
+ For production monitoring and observability, check out [Platformatic Console](https://platformatic.dev/console)—the intelligent command center for Platformatic Cloud deployments.
177
+
178
+ ## 📚 Documentation
179
+
180
+ - [Platformatic Documentation](https://docs.platformatic.dev)
181
+ - [Watt Admin Blog Post](https://blog.platformatic.dev/introducing-watt-admin)
182
+ - [Platformatic Wattpm](https://docs.platformatic.dev/docs/guides/wattpm)
183
+
184
+ ## 🤝 Contributing
185
+
186
+ Contributions are welcome! Please check out our [contributing guidelines](CONTRIBUTING.md) to get started.
187
+
188
+ ## 📄 License
189
+
190
+ Apache-2.0 License - see [LICENSE](LICENSE) for details.
191
+
192
+ ## 🙋 Support
193
+
194
+ - [GitHub Issues](https://github.com/platformatic/watt-admin/issues)
195
+ - [Discord Community](https://discord.gg/platformatic)
196
+ - [Twitter](https://twitter.com/platformatic)
197
+
198
+ ---
64
199
 
65
- 0. check you started `watt-admin`
66
- 1. you should now see an info log like `Platformatic is now listening at http://127.0.0.1:{PORT}` (open the local URL)
67
- 2. click on link above to navigate the frontend app
68
- 3. you can call the backend by prefixing the local URL with `/api`
69
- 4. call the `/api/runtimes` endpoint (you should receive the PID)
70
- 5. call the `/api/runtimes/{pid}/metrics` endpoint
200
+ Built with ❤️ by the [Platformatic](https://platformatic.dev) team
package/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Runtime } from '@platformatic/control'
2
+
3
+ export default function main (): Promise<Runtime | null>
package/cli.js CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  'use strict'
4
4
 
5
- const { RuntimeApiClient } = require('@platformatic/control')
6
- const { select } = require('@inquirer/prompts')
7
- const { start } = require('./lib/start')
5
+ import esmain from 'es-main'
6
+ import { RuntimeApiClient } from '@platformatic/control'
7
+ import { select } from '@inquirer/prompts'
8
+ import { start } from './lib/start.js'
8
9
 
9
10
  async function getLocationDetails (client, runtime) {
10
11
  try {
@@ -39,99 +40,95 @@ async function getLocationDetails (client, runtime) {
39
40
  }
40
41
  }
41
42
 
42
- async function main () {
43
+ let client
44
+ export default async function main () {
43
45
  try {
44
46
  // Get available runtimes
45
- const client = new RuntimeApiClient()
46
-
47
- try {
48
- const runtimes = await client.getRuntimes()
49
-
50
- if (runtimes.length === 0) {
51
- console.log('No runtimes available. Please start a Platformatic runtime first.')
52
- return null
53
- } else if (runtimes.length === 1) {
54
- // Only one runtime, no need to prompt
55
- const runtime = runtimes[0]
56
- const locationDetails = await getLocationDetails(client, runtime)
57
- // Display information about the runtime
58
- console.log(`Name: ${runtime.packageName || 'unnamed'}`)
59
- console.log(`PID: ${runtime.pid}`)
60
- console.log(`Working directory: ${locationDetails.cwd}`)
61
-
62
- if (locationDetails.configPath !== 'Unknown' &&
63
- locationDetails.configPath !== 'Unknown (config not available)') {
64
- console.log(`Configuration path: ${locationDetails.configPath}`)
65
- }
47
+ client = new RuntimeApiClient()
48
+
49
+ const runtimes = await client.getRuntimes()
50
+
51
+ if (runtimes.length === 0) {
52
+ console.log('No runtimes available. Please start a Platformatic runtime first.')
53
+ return null
54
+ } else if (runtimes.length === 1) {
55
+ // Only one runtime, no need to prompt
56
+ const runtime = runtimes[0]
57
+ const locationDetails = await getLocationDetails(client, runtime)
58
+ // Display information about the runtime
59
+ console.log(`Name: ${runtime.packageName || 'unnamed'}`)
60
+ console.log(`PID: ${runtime.pid}`)
61
+ console.log(`Working directory: ${locationDetails.cwd}`)
62
+
63
+ if (locationDetails.configPath !== 'Unknown' &&
64
+ locationDetails.configPath !== 'Unknown (config not available)') {
65
+ console.log(`Configuration path: ${locationDetails.configPath}`)
66
+ }
66
67
 
67
- // If we have any command line arguments, display them
68
- if (runtime.argv && runtime.argv.length > 0) {
69
- console.log(`Command: ${runtime.argv.join(' ')}`)
70
- }
68
+ // If we have any command line arguments, display them
69
+ if (runtime.argv && runtime.argv.length > 0) {
70
+ console.log(`Command: ${runtime.argv.join(' ')}`)
71
+ }
72
+
73
+ // If we have a start time, display it
74
+ if (runtime.startTime) {
75
+ console.log(`Started at: ${new Date(runtime.startTime).toLocaleString()}`)
76
+ }
77
+ return runtime
78
+ } else {
79
+ // Multiple runtimes, prompt user to select one
80
+
81
+ // Prepare the runtime choices with async map
82
+ const choicesPromises = runtimes.map(async (runtime) => {
83
+ // Create a concise description that helps identify this runtime
84
+ const description = []
71
85
 
72
- // If we have a start time, display it
86
+ // Show working directory as it's usually the most useful identifier
87
+ description.push(`Dir: ${runtime.cwd || 'Unknown'}`)
88
+
89
+ // Add the start time if available
73
90
  if (runtime.startTime) {
74
- console.log(`Started at: ${new Date(runtime.startTime).toLocaleString()}`)
75
- }
76
- return runtime
77
- } else {
78
- // Multiple runtimes, prompt user to select one
79
-
80
- // Prepare the runtime choices with async map
81
- const choicesPromises = runtimes.map(async runtime => {
82
- // Create a concise description that helps identify this runtime
83
- const description = []
84
-
85
- // Show working directory as it's usually the most useful identifier
86
- description.push(`Dir: ${runtime.cwd || 'Unknown'}`)
87
-
88
- // Add the start time if available
89
- if (runtime.startTime) {
90
- description.push(`Started: ${new Date(runtime.startTime).toLocaleString()}`)
91
- }
92
-
93
- return {
94
- name: `${runtime.packageName || 'unnamed'} (PID: ${runtime.pid})`,
95
- value: runtime,
96
- description: description.join(', ')
97
- }
98
- })
99
-
100
- // Wait for all async operations to complete
101
- const choices = await Promise.all(choicesPromises)
102
-
103
- // Prompt the user to select a runtime
104
- const selectedRuntime = await select({
105
- message: 'Select a runtime:',
106
- choices
107
- })
108
-
109
- const locationDetails = await getLocationDetails(client, selectedRuntime)
110
- // Display information about the selected runtime
111
- console.log('\nRuntime Details:')
112
- console.log(`Name: ${selectedRuntime.packageName || 'unnamed'}`)
113
- console.log(`PID: ${selectedRuntime.pid}`)
114
- console.log(`Working directory: ${locationDetails.cwd}`)
115
-
116
- if (locationDetails.configPath !== 'Unknown' &&
117
- locationDetails.configPath !== 'Unknown (config not available)') {
118
- console.log(`Configuration path: ${locationDetails.configPath}`)
91
+ description.push(`Started: ${new Date(runtime.startTime).toLocaleString()}`)
119
92
  }
120
93
 
121
- // If we have any command line arguments, display them
122
- if (selectedRuntime.argv && selectedRuntime.argv.length > 0) {
123
- console.log(`Command: ${selectedRuntime.argv.join(' ')}`)
94
+ return {
95
+ name: `${runtime.packageName || 'unnamed'} (PID: ${runtime.pid})`,
96
+ value: runtime,
97
+ description: description.join(', ')
124
98
  }
99
+ })
100
+
101
+ // Wait for all async operations to complete
102
+ const choices = await Promise.all(choicesPromises)
103
+
104
+ // Prompt the user to select a runtime
105
+ const selectedRuntime = await select({
106
+ message: 'Select a runtime:',
107
+ choices
108
+ })
109
+
110
+ const locationDetails = await getLocationDetails(client, selectedRuntime)
111
+ // Display information about the selected runtime
112
+ console.log('\nRuntime Details:')
113
+ console.log(`Name: ${selectedRuntime.packageName || 'unnamed'}`)
114
+ console.log(`PID: ${selectedRuntime.pid}`)
115
+ console.log(`Working directory: ${locationDetails.cwd}`)
116
+
117
+ if (locationDetails.configPath !== 'Unknown' &&
118
+ locationDetails.configPath !== 'Unknown (config not available)') {
119
+ console.log(`Configuration path: ${locationDetails.configPath}`)
120
+ }
125
121
 
126
- // If we have a start time, display it
127
- if (selectedRuntime.startTime) {
128
- console.log(`Started at: ${new Date(selectedRuntime.startTime).toLocaleString()}`)
129
- }
130
- return selectedRuntime
122
+ // If we have any command line arguments, display them
123
+ if (selectedRuntime.argv && selectedRuntime.argv.length > 0) {
124
+ console.log(`Command: ${selectedRuntime.argv.join(' ')}`)
125
+ }
126
+
127
+ // If we have a start time, display it
128
+ if (selectedRuntime.startTime) {
129
+ console.log(`Started at: ${new Date(selectedRuntime.startTime).toLocaleString()}`)
131
130
  }
132
- } finally {
133
- // Always clean up the client
134
- await client.close()
131
+ return selectedRuntime
135
132
  }
136
133
  } catch (error) {
137
134
  console.error('Error:', error.message)
@@ -140,19 +137,16 @@ async function main () {
140
137
  }
141
138
 
142
139
  // Execute the main function if this script is run directly
143
- if (require.main === module) {
140
+ if (esmain(import.meta)) {
144
141
  main().then((selectedRuntime) => {
145
142
  if (!selectedRuntime) {
146
143
  return
147
144
  }
148
145
  console.log('Starting Watt admin...')
149
146
  console.log('--------')
150
- return start(selectedRuntime.pid)
147
+ return start(client, selectedRuntime.pid)
151
148
  }).catch(error => {
152
149
  console.error('Fatal error:', error)
153
150
  process.exit(1)
154
151
  })
155
- } else {
156
- // Export for use as a module
157
- module.exports = main
158
152
  }
package/lib/start.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { RuntimeApiClient } from '@platformatic/control'
2
+
3
+ export declare function start (client: RuntimeApiClient, selectedRuntime: string): Promise<void>
package/lib/start.js CHANGED
@@ -1,20 +1,95 @@
1
1
  'use strict'
2
2
 
3
- const { buildRuntime, loadConfig } = require('@platformatic/runtime')
4
- const { join } = require('path')
5
- const { parseArgs } = require('util')
3
+ import { create } from '@platformatic/runtime'
4
+ import { join } from 'path'
5
+ import { parseArgs, promisify } from 'util'
6
+ import { request } from 'undici'
7
+ import { exec } from 'child_process'
8
+ import closeWithGrace from 'close-with-grace'
6
9
 
7
- async function start (selectedRuntime) {
10
+ const __dirname = import.meta.dirname
11
+
12
+ const execAsync = promisify(exec)
13
+
14
+ const msOneMinute = 1000 * 60
15
+ let recordTimeout
16
+ let entrypointUrl
17
+
18
+ export async function start (client, selectedRuntime) {
8
19
  process.env.SELECTED_RUNTIME = selectedRuntime
9
- const { values: { port } } = parseArgs({ options: { port: { type: 'string' } } })
20
+ const { values: { port, record, profile } } = parseArgs({
21
+ options: {
22
+ port: { type: 'string' },
23
+ profile: { type: 'string' },
24
+ record: { type: 'boolean' },
25
+ }
26
+ })
27
+
10
28
  process.env.PORT = port || 4042
11
29
 
30
+ const requestRecord = async (mode) => {
31
+ return await request(`${entrypointUrl}/api/record/${selectedRuntime}`, {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({ mode, profile: profile ?? 'cpu' })
35
+ })
36
+ }
37
+
38
+ const recordMetrics = async () => {
39
+ try {
40
+ const { statusCode, body } = await requestRecord('stop')
41
+ if (statusCode === 200) {
42
+ await body.dump()
43
+ const bundlePath = join(__dirname, '..', 'web', 'frontend', 'dist', 'index.html')
44
+ await execAsync(`${process.platform === 'win32' ? 'start' : 'open'} ${bundlePath}`)
45
+ } else {
46
+ console.error(`Failure triggering the stop command: ${await body.text()}`)
47
+ }
48
+ } catch (error) {
49
+ console.error('Error on record metrics', { error, entrypointUrl })
50
+ clearTimeout(recordTimeout)
51
+ }
52
+ }
53
+
12
54
  const configFile = join(__dirname, '..', 'watt.json')
13
- const { configManager, args } = await loadConfig({
14
- }, ['--production', '--config', configFile], { production: true, disableEnvLoad: true })
15
- configManager.args = args
16
- const server = await buildRuntime(configManager)
17
- await server.start()
18
- }
55
+ const server = await create(configFile, undefined, {
56
+ setupSignals: false
57
+ })
58
+ entrypointUrl = await server.start()
19
59
 
20
- module.exports.start = start
60
+ if (record) {
61
+ async function shutdown () {
62
+ // Always clean up the client
63
+ await client.close()
64
+
65
+ // Then stop the server
66
+ await server.close()
67
+ }
68
+
69
+ async function recordAndShutdown () {
70
+ console.log('SIGINT received, recording metrics and shutting down...')
71
+ clearTimeout(recordTimeout)
72
+ await recordMetrics(entrypointUrl)
73
+ await shutdown()
74
+ process.removeAllListeners('SIGINT')
75
+ }
76
+
77
+ process.once('SIGINT', () => {
78
+ process.on('SIGINT', () => {
79
+ // we ignore future signals to avoid double shutdown
80
+ })
81
+ recordAndShutdown()
82
+ })
83
+
84
+ const { statusCode, body } = await requestRecord('start')
85
+ if (statusCode === 200) {
86
+ await body.dump()
87
+
88
+ recordTimeout = setTimeout(async () => {
89
+ await recordMetrics(entrypointUrl)
90
+ }, msOneMinute * 10)
91
+ } else {
92
+ console.log(`Failure triggering the start command: ${await body.text()}`)
93
+ }
94
+ }
95
+ }