@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.
- package/README.md +163 -33
- package/cli.d.ts +3 -0
- package/cli.js +85 -91
- package/lib/start.d.ts +3 -0
- package/lib/start.js +87 -12
- package/package.json +53 -47
- package/renovate.json +17 -0
- package/watt.json +17 -9
- package/web/backend/global.d.ts +17 -0
- package/web/backend/platformatic.json +2 -3
- package/web/backend/plugins/metrics.ts +15 -0
- package/web/backend/plugins/websocket.ts +6 -0
- package/web/backend/routes/metrics.ts +46 -0
- package/web/backend/routes/proxy.ts +41 -0
- package/web/backend/routes/root.ts +204 -0
- package/web/backend/routes/ws.ts +45 -0
- package/web/backend/schemas/index.ts +226 -0
- package/web/backend/utils/bytes.ts +1 -0
- package/web/backend/utils/client.openapi.ts +29 -0
- package/web/backend/utils/constants.ts +4 -0
- package/web/backend/utils/metrics-helpers.ts +89 -0
- package/web/backend/utils/metrics.ts +202 -0
- package/web/backend/utils/rps.ts +3 -0
- package/web/backend/utils/runtimes.ts +21 -0
- package/web/backend/utils/states.ts +3 -0
- package/web/composer/platformatic.json +3 -3
- package/web/frontend/dist/index.html +3012 -6
- package/web/frontend/index.d.ts +4 -0
- package/web/frontend/index.html +0 -1
- package/web/frontend/playwright.config.ts +27 -0
- package/web/frontend/postcss.config.ts +14 -0
- package/web/frontend/watt.json +2 -2
- package/CLAUDE.md +0 -32
- package/web/backend/dist/plugins/metrics.js +0 -13
- package/web/backend/dist/plugins/metrics.js.map +0 -1
- package/web/backend/dist/plugins/websocket.js +0 -11
- package/web/backend/dist/plugins/websocket.js.map +0 -1
- package/web/backend/dist/routes/metrics.js +0 -45
- package/web/backend/dist/routes/metrics.js.map +0 -1
- package/web/backend/dist/routes/proxy.js +0 -34
- package/web/backend/dist/routes/proxy.js.map +0 -1
- package/web/backend/dist/routes/root.js +0 -174
- package/web/backend/dist/routes/root.js.map +0 -1
- package/web/backend/dist/routes/ws.js +0 -38
- package/web/backend/dist/routes/ws.js.map +0 -1
- package/web/backend/dist/schemas/index.js +0 -184
- package/web/backend/dist/schemas/index.js.map +0 -1
- package/web/backend/dist/utils/bytes.js +0 -6
- package/web/backend/dist/utils/bytes.js.map +0 -1
- package/web/backend/dist/utils/calc.js +0 -248
- package/web/backend/dist/utils/calc.js.map +0 -1
- package/web/backend/dist/utils/client.openapi.js +0 -31
- package/web/backend/dist/utils/client.openapi.js.map +0 -1
- package/web/backend/dist/utils/log.js +0 -29
- package/web/backend/dist/utils/log.js.map +0 -1
- package/web/backend/dist/utils/metrics-helpers.js +0 -72
- package/web/backend/dist/utils/metrics-helpers.js.map +0 -1
- package/web/backend/dist/utils/metrics.js +0 -183
- package/web/backend/dist/utils/metrics.js.map +0 -1
- package/web/backend/dist/utils/rps.js +0 -6
- package/web/backend/dist/utils/rps.js.map +0 -1
- package/web/backend/openapi.json +0 -1119
- package/web/backend/tsconfig.json +0 -21
- package/web/frontend/dist/assets/Collection.vue-3bb6N9VS.js +0 -2
- package/web/frontend/dist/assets/Collection.vue-3bb6N9VS.js.map +0 -1
- package/web/frontend/dist/assets/CollectionAuthentication.vue-BUiBAnw8.js +0 -2
- package/web/frontend/dist/assets/CollectionAuthentication.vue-BUiBAnw8.js.map +0 -1
- package/web/frontend/dist/assets/CollectionCookies.vue-VpQ7oUfB.js +0 -2
- package/web/frontend/dist/assets/CollectionCookies.vue-VpQ7oUfB.js.map +0 -1
- package/web/frontend/dist/assets/CollectionEnvironment.vue-B9aL6C1f.js +0 -2
- package/web/frontend/dist/assets/CollectionEnvironment.vue-B9aL6C1f.js.map +0 -1
- package/web/frontend/dist/assets/CollectionOverview.vue-Gs4h2k55.js +0 -2
- package/web/frontend/dist/assets/CollectionOverview.vue-Gs4h2k55.js.map +0 -1
- package/web/frontend/dist/assets/CollectionScripts.vue-DI73bgJP.js +0 -2
- package/web/frontend/dist/assets/CollectionScripts.vue-DI73bgJP.js.map +0 -1
- package/web/frontend/dist/assets/CollectionServers.vue-CTh_DkWz.js +0 -2
- package/web/frontend/dist/assets/CollectionServers.vue-CTh_DkWz.js.map +0 -1
- package/web/frontend/dist/assets/CollectionSettings.vue--aJQ9wMq.js +0 -2
- package/web/frontend/dist/assets/CollectionSettings.vue--aJQ9wMq.js.map +0 -1
- package/web/frontend/dist/assets/CollectionSync.vue-CwmTdwlV.js +0 -2
- package/web/frontend/dist/assets/CollectionSync.vue-CwmTdwlV.js.map +0 -1
- package/web/frontend/dist/assets/CommandActionInput.vue-TK77rD5U.js +0 -2
- package/web/frontend/dist/assets/CommandActionInput.vue-TK77rD5U.js.map +0 -1
- package/web/frontend/dist/assets/Cookies.vue-C3PhNsCO.js +0 -2
- package/web/frontend/dist/assets/Cookies.vue-C3PhNsCO.js.map +0 -1
- package/web/frontend/dist/assets/DataTableHeader.vue-DbIRXelw.js +0 -2
- package/web/frontend/dist/assets/DataTableHeader.vue-DbIRXelw.js.map +0 -1
- package/web/frontend/dist/assets/DeleteSidebarListElement.vue-B9hc23j9.js +0 -2
- package/web/frontend/dist/assets/DeleteSidebarListElement.vue-B9hc23j9.js.map +0 -1
- package/web/frontend/dist/assets/Draggable.vue-CgQ5Rr6l.js +0 -2
- package/web/frontend/dist/assets/Draggable.vue-CgQ5Rr6l.js.map +0 -1
- package/web/frontend/dist/assets/EditSidebarListElement.vue-fx233eKQ.js +0 -2
- package/web/frontend/dist/assets/EditSidebarListElement.vue-fx233eKQ.js.map +0 -1
- package/web/frontend/dist/assets/EmptyState.vue-CCWl6cFt.js +0 -23
- package/web/frontend/dist/assets/EmptyState.vue-CCWl6cFt.js.map +0 -1
- package/web/frontend/dist/assets/Environment.vue-BAgAaWzP.js +0 -2
- package/web/frontend/dist/assets/Environment.vue-BAgAaWzP.js.map +0 -1
- package/web/frontend/dist/assets/EnvironmentModal.vue-CoGQ0HVI.js +0 -2
- package/web/frontend/dist/assets/EnvironmentModal.vue-CoGQ0HVI.js.map +0 -1
- package/web/frontend/dist/assets/Form.vue-CPOtQXvw.js +0 -2
- package/web/frontend/dist/assets/Form.vue-CPOtQXvw.js.map +0 -1
- package/web/frontend/dist/assets/IconSelector.vue-s7y-7Ty-.js +0 -2
- package/web/frontend/dist/assets/IconSelector.vue-s7y-7Ty-.js.map +0 -1
- package/web/frontend/dist/assets/LibraryIcon.vue-CrkyDYXJ.js +0 -2
- package/web/frontend/dist/assets/LibraryIcon.vue-CrkyDYXJ.js.map +0 -1
- package/web/frontend/dist/assets/Request.vue-Cd99DAq4.js +0 -33
- package/web/frontend/dist/assets/Request.vue-Cd99DAq4.js.map +0 -1
- package/web/frontend/dist/assets/RequestRoot.vue-BVpdY64H.js +0 -20
- package/web/frontend/dist/assets/RequestRoot.vue-BVpdY64H.js.map +0 -1
- package/web/frontend/dist/assets/ScalarAsciiArt.vue-Bm66XRod.js +0 -3
- package/web/frontend/dist/assets/ScalarAsciiArt.vue-Bm66XRod.js.map +0 -1
- package/web/frontend/dist/assets/ScalarHotkey.vue-DGRaosjl.js +0 -2
- package/web/frontend/dist/assets/ScalarHotkey.vue-DGRaosjl.js.map +0 -1
- package/web/frontend/dist/assets/ScalarIconTrash.vue-BzkMDpLH.js +0 -2
- package/web/frontend/dist/assets/ScalarIconTrash.vue-BzkMDpLH.js.map +0 -1
- package/web/frontend/dist/assets/ScalarPopover.vue-DvIo_Yzb.js +0 -2
- package/web/frontend/dist/assets/ScalarPopover.vue-DvIo_Yzb.js.map +0 -1
- package/web/frontend/dist/assets/ScalarToggle.vue-C4n7WrpG.js +0 -2
- package/web/frontend/dist/assets/ScalarToggle.vue-C4n7WrpG.js.map +0 -1
- package/web/frontend/dist/assets/Settings.vue-vE6bOazq.js +0 -2
- package/web/frontend/dist/assets/Settings.vue-vE6bOazq.js.map +0 -1
- package/web/frontend/dist/assets/SidebarButton.vue-DAR-mVH7.js +0 -2
- package/web/frontend/dist/assets/SidebarButton.vue-DAR-mVH7.js.map +0 -1
- package/web/frontend/dist/assets/SidebarListElement.vue-CnWyRVB0.js +0 -2
- package/web/frontend/dist/assets/SidebarListElement.vue-CnWyRVB0.js.map +0 -1
- package/web/frontend/dist/assets/ViewLayout.vue-CEr_C1hG.js +0 -2
- package/web/frontend/dist/assets/ViewLayout.vue-CEr_C1hG.js.map +0 -1
- package/web/frontend/dist/assets/ViewLayoutContent.vue-t9k9noSS.js +0 -2
- package/web/frontend/dist/assets/ViewLayoutContent.vue-t9k9noSS.js.map +0 -1
- package/web/frontend/dist/assets/ViewLayoutSection.vue-BYrAaBmf.js +0 -2
- package/web/frontend/dist/assets/ViewLayoutSection.vue-BYrAaBmf.js.map +0 -1
- package/web/frontend/dist/assets/index-DN39RFTG.js +0 -2327
- package/web/frontend/dist/assets/index-DN39RFTG.js.map +0 -1
- package/web/frontend/dist/assets/index-DrvlsJch.css +0 -1
- package/web/frontend/dist/assets/mediaTypes-DEhrbNXe.js +0 -2
- package/web/frontend/dist/assets/mediaTypes-DEhrbNXe.js.map +0 -1
- package/web/frontend/postcss.config.cjs +0 -11
- package/web/frontend/tsconfig.json +0 -29
package/README.md
CHANGED
|
@@ -1,70 +1,200 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🔌 Watt Admin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Real-time monitoring and administration for your Platformatic applications**
|
|
4
4
|
|
|
5
|
-
|
|
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
|
+

|
|
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
|
-
|
|
49
|
+
Install globally for convenient access:
|
|
10
50
|
|
|
11
51
|
```bash
|
|
12
|
-
|
|
52
|
+
npm install -g @platformatic/watt-admin
|
|
13
53
|
```
|
|
14
54
|
|
|
15
|
-
|
|
55
|
+
Then run:
|
|
16
56
|
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
74
|
+
If only one runtime is running, it will be selected automatically.
|
|
25
75
|
|
|
26
|
-
|
|
76
|
+
### Custom Port
|
|
27
77
|
|
|
78
|
+
Run Watt Admin on a different port:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
watt-admin --port 4321
|
|
28
82
|
```
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
133
|
+
### Run Tests
|
|
50
134
|
|
|
51
|
-
```
|
|
52
|
-
|
|
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
|
-
###
|
|
56
|
-
|
|
57
|
-
This will auto-reload both the backend and frontend when changes are made.
|
|
146
|
+
### Project Structure
|
|
58
147
|
|
|
59
148
|
```
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/cli.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
43
|
+
let client
|
|
44
|
+
export default async function main () {
|
|
43
45
|
try {
|
|
44
46
|
// Get available runtimes
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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 (
|
|
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
package/lib/start.js
CHANGED
|
@@ -1,20 +1,95 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
await server.start()
|
|
18
|
-
}
|
|
55
|
+
const server = await create(configFile, undefined, {
|
|
56
|
+
setupSignals: false
|
|
57
|
+
})
|
|
58
|
+
entrypointUrl = await server.start()
|
|
19
59
|
|
|
20
|
-
|
|
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
|
+
}
|