@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/package.json
CHANGED
|
@@ -1,71 +1,77 @@
|
|
|
1
1
|
{
|
|
2
|
+
"type": "module",
|
|
2
3
|
"name": "@platformatic/watt-admin",
|
|
3
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0-alpha.10",
|
|
4
5
|
"scripts": {
|
|
5
|
-
"prepublishOnly": "npm run build",
|
|
6
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
6
7
|
"dev": "wattpm dev",
|
|
7
8
|
"build": "wattpm build",
|
|
8
9
|
"start": "wattpm start",
|
|
10
|
+
"typecheck": "cd web/backend && tsc && cd ../frontend && tsc",
|
|
9
11
|
"client:openapi": "cd web/backend && node --experimental-strip-types utils/client.openapi.ts",
|
|
10
|
-
"client:generate": "
|
|
12
|
+
"client:generate": "massimo ./web/backend/openapi.json --name backend --folder web/frontend/src/client --language ts --frontend --full --skip-config-update --props-optional",
|
|
11
13
|
"test": "npm run test:cli && npm run test:e2e && npm run test:backend && npm run test:frontend",
|
|
12
|
-
"test:cli": "node --test test
|
|
13
|
-
"test:backend": "
|
|
14
|
+
"test:cli": "node --test --experimental-test-module-mocks --test-timeout 60000 test/*.test.ts",
|
|
15
|
+
"test:backend": "node --test --experimental-test-module-mocks --test-timeout 60000 --test-concurrency 1 web/backend/**/*.test.ts",
|
|
14
16
|
"test:frontend": "cd web/frontend && vitest run",
|
|
15
|
-
"pretest:e2e": "
|
|
17
|
+
"pretest:e2e": "playwright install chromium",
|
|
16
18
|
"test:e2e": "cd web/frontend && playwright test",
|
|
17
19
|
"test:e2e:ui": "npm run test:e2e -- --ui",
|
|
18
|
-
"clean": "rm -rf ./web/*/dist",
|
|
20
|
+
"clean": "rm -rf ./web/*/dist ./playwright-report ./test-results",
|
|
19
21
|
"lint": "eslint .",
|
|
20
22
|
"lint:fix": "eslint . --fix"
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|
|
23
|
-
"@fastify/websocket": "^11.
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
26
|
-
"@platformatic/control": "^
|
|
27
|
-
"@platformatic/
|
|
28
|
-
"@platformatic/
|
|
29
|
-
"@platformatic/
|
|
30
|
-
"@
|
|
31
|
-
"
|
|
32
|
-
"
|
|
25
|
+
"@fastify/websocket": "^11.2.0",
|
|
26
|
+
"@fastify/type-provider-json-schema-to-ts": "^5.0.0",
|
|
27
|
+
"@inquirer/prompts": "^8.0.0",
|
|
28
|
+
"@platformatic/control": "^3.15.0",
|
|
29
|
+
"@platformatic/gateway": "^3.15.0",
|
|
30
|
+
"@platformatic/runtime": "^3.15.0",
|
|
31
|
+
"@platformatic/service": "^3.15.0",
|
|
32
|
+
"@platformatic/ui-components": "^0.19.1",
|
|
33
|
+
"@platformatic/vite": "^3.11.0",
|
|
34
|
+
"@platformatic/wattpm-pprof-capture": "^3.15.0",
|
|
35
|
+
"@scalar/api-reference-react": "^0.8.0",
|
|
36
|
+
"@vitejs/plugin-react": "^5.0.4",
|
|
37
|
+
"amaro": "^1.1.4",
|
|
38
|
+
"autoprefixer": "^10.4.21",
|
|
39
|
+
"close-with-grace": "^2.3.0",
|
|
40
|
+
"d3": "^7.9.0",
|
|
41
|
+
"dayjs": "^1.11.18",
|
|
42
|
+
"es-main": "^1.4.0",
|
|
43
|
+
"fastify": "^5.6.1",
|
|
44
|
+
"pprof-format": "^2.2.1",
|
|
45
|
+
"react": "^19.2.0",
|
|
46
|
+
"react-dom": "^19.2.0",
|
|
47
|
+
"react-pprof": "^1.3.1",
|
|
48
|
+
"react-router-dom": "^7.9.3",
|
|
33
49
|
"react-use-websocket": "^4.13.0",
|
|
34
|
-
"split2": "4.2.0",
|
|
35
|
-
"
|
|
50
|
+
"split2": "^4.2.0",
|
|
51
|
+
"tailwindcss": "^3.4.18",
|
|
52
|
+
"undici": "^7.16.0",
|
|
53
|
+
"use-error-boundary": "^2.0.6",
|
|
54
|
+
"vite": "^7.1.9",
|
|
55
|
+
"vite-plugin-singlefile": "^2.3.0",
|
|
56
|
+
"wattpm": "^3.15.0",
|
|
57
|
+
"zustand": "^5.0.8"
|
|
36
58
|
},
|
|
37
59
|
"devDependencies": {
|
|
38
|
-
"@
|
|
39
|
-
"@inquirer/testing": "^2.1.45",
|
|
40
|
-
"@platformatic/client-cli": "^2.58.0",
|
|
41
|
-
"@platformatic/ui-components": "^0.15.2",
|
|
42
|
-
"@playwright/test": "^1.52.0",
|
|
60
|
+
"@playwright/test": "^1.56.0",
|
|
43
61
|
"@types/d3": "^7.4.3",
|
|
44
|
-
"@types/
|
|
45
|
-
"@types/react-dom": "^19.
|
|
46
|
-
"@types/split2": "4.2.3",
|
|
62
|
+
"@types/node": "^22",
|
|
63
|
+
"@types/react-dom": "^19.2.1",
|
|
64
|
+
"@types/split2": "^4.2.3",
|
|
47
65
|
"@types/ws": "^8.18.1",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"neostandard": "^0.12.1",
|
|
56
|
-
"playwright": "^1.52.0",
|
|
57
|
-
"react": "^19.0.0",
|
|
58
|
-
"react-dom": "^19.0.0",
|
|
59
|
-
"react-router-dom": "^7.0.0",
|
|
60
|
-
"source-map-support": "^0.5.21",
|
|
61
|
-
"tailwindcss": "^3.4.15",
|
|
62
|
-
"typescript": "^5.5.4",
|
|
63
|
-
"use-error-boundary": "^2.0.6",
|
|
64
|
-
"vite": "^5.4.11",
|
|
65
|
-
"vitest": "^3.0.8",
|
|
66
|
-
"zustand": "^5.0.2"
|
|
66
|
+
"eslint": "^9.37.0",
|
|
67
|
+
"fastify-tsconfig": "^3.0.0",
|
|
68
|
+
"massimo-cli": "^1.0.1",
|
|
69
|
+
"neostandard": "^0.12.2",
|
|
70
|
+
"playwright": "^1.56.0",
|
|
71
|
+
"typescript": "^5.9.3",
|
|
72
|
+
"vitest": "^3.2.4"
|
|
67
73
|
},
|
|
68
74
|
"bin": {
|
|
69
|
-
"watt-admin": "
|
|
75
|
+
"watt-admin": "cli.js"
|
|
70
76
|
}
|
|
71
77
|
}
|
package/renovate.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
+
"extends": [
|
|
4
|
+
"config:base"
|
|
5
|
+
],
|
|
6
|
+
"packageRules": [
|
|
7
|
+
{
|
|
8
|
+
"groupName": "Safe automerge",
|
|
9
|
+
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
|
10
|
+
"automerge": true
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"lockFileMaintenance": {
|
|
14
|
+
"enabled": true,
|
|
15
|
+
"automerge": true
|
|
16
|
+
}
|
|
17
|
+
}
|
package/watt.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://schemas.platformatic.dev/wattpm/
|
|
2
|
+
"$schema": "https://schemas.platformatic.dev/wattpm/3.11.0.json",
|
|
3
3
|
"server": {
|
|
4
4
|
"hostname": "127.0.0.1",
|
|
5
5
|
"port": "{PORT}"
|
|
@@ -7,13 +7,21 @@
|
|
|
7
7
|
"logger": {
|
|
8
8
|
"level": "info"
|
|
9
9
|
},
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
"entrypoint": "composer",
|
|
11
|
+
"applications": [
|
|
12
|
+
{
|
|
13
|
+
"id": "backend",
|
|
14
|
+
"path": "./web/backend",
|
|
15
|
+
"useHttp": true,
|
|
16
|
+
"nodeOptions": "--import=amaro/strip"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "composer",
|
|
20
|
+
"path": "./web/composer"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "frontend",
|
|
24
|
+
"path": "./web/frontend"
|
|
17
25
|
}
|
|
18
|
-
|
|
26
|
+
]
|
|
19
27
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2
|
+
import type { FastifyInstance } from 'fastify'
|
|
3
|
+
import type { PlatformaticApp, PlatformaticServiceConfig } from '@platformatic/service'
|
|
4
|
+
import type { Mode, Profile } from './schemas/index.ts'
|
|
5
|
+
import type { MappedMetrics } from './utils/metrics-helpers.ts'
|
|
6
|
+
|
|
7
|
+
declare module 'fastify' {
|
|
8
|
+
interface FastifyInstance {
|
|
9
|
+
platformatic: PlatformaticApp<PlatformaticServiceConfig>
|
|
10
|
+
metricsInterval: NodeJS.Timeout
|
|
11
|
+
loaded: { mode?: Mode, metrics: MappedMetrics, type?: Profile }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface FastifySchema {
|
|
15
|
+
hide?: boolean
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://schemas.platformatic.dev/@platformatic/service/
|
|
2
|
+
"$schema": "https://schemas.platformatic.dev/@platformatic/service/3.11.0.json",
|
|
3
3
|
"service": {
|
|
4
4
|
"openapi": true
|
|
5
5
|
},
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"encapsulate": false
|
|
12
12
|
},
|
|
13
13
|
"./routes"
|
|
14
|
-
]
|
|
15
|
-
"typescript": "{PLT_BACKEND_TYPESCRIPT}"
|
|
14
|
+
]
|
|
16
15
|
}
|
|
17
16
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify'
|
|
2
|
+
import { getMetrics } from '../utils/metrics.ts'
|
|
3
|
+
import { MS_WAITING } from '../utils/constants.ts'
|
|
4
|
+
|
|
5
|
+
export default async function (fastify: FastifyInstance) {
|
|
6
|
+
fastify.decorate('loaded', { metrics: {} })
|
|
7
|
+
|
|
8
|
+
fastify.decorate('metricsInterval', setInterval(() => getMetrics(fastify), MS_WAITING))
|
|
9
|
+
|
|
10
|
+
fastify.addHook('onClose', async () => {
|
|
11
|
+
// If the following log is not called, please run the project directly through the `wattpm` binary (ref. https://github.com/platformatic/platformatic/issues/3751)
|
|
12
|
+
fastify.log.info('Closing the backend...')
|
|
13
|
+
clearInterval(fastify.metricsInterval)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify'
|
|
2
|
+
import type { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
|
|
3
|
+
import { metricResponseSchema, pidParamSchema } from '../schemas/index.ts'
|
|
4
|
+
import type { MetricsResponse } from '../schemas/index.ts'
|
|
5
|
+
|
|
6
|
+
export default async function (fastify: FastifyInstance) {
|
|
7
|
+
const typedFastify = fastify.withTypeProvider<JsonSchemaToTsProvider>()
|
|
8
|
+
const emptyMetrics: MetricsResponse = { dataCpu: [], dataLatency: [], dataMem: [], dataReq: [], dataKafka: [], dataUndici: [], dataWebsocket: [], dataNodejs: [] }
|
|
9
|
+
|
|
10
|
+
typedFastify.get('/runtimes/:pid/metrics', {
|
|
11
|
+
schema: { params: pidParamSchema, response: { 200: metricResponseSchema } },
|
|
12
|
+
}, async ({ params: { pid } }) => fastify.loaded.metrics[pid]?.aggregated || emptyMetrics)
|
|
13
|
+
|
|
14
|
+
typedFastify.get('/runtimes/:pid/metrics/:serviceId', {
|
|
15
|
+
schema: {
|
|
16
|
+
params: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
pid: { type: 'number' },
|
|
20
|
+
serviceId: { type: 'string' }
|
|
21
|
+
},
|
|
22
|
+
required: ['pid', 'serviceId']
|
|
23
|
+
},
|
|
24
|
+
response: { 200: metricResponseSchema }
|
|
25
|
+
}
|
|
26
|
+
}, async ({ params: { pid, serviceId } }) => {
|
|
27
|
+
return fastify.loaded.metrics[pid]?.services[serviceId]?.all || emptyMetrics
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
typedFastify.get('/runtimes/:pid/metrics/:serviceId/:workerId', {
|
|
31
|
+
schema: {
|
|
32
|
+
params: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
pid: { type: 'number' },
|
|
36
|
+
serviceId: { type: 'string' },
|
|
37
|
+
workerId: { type: 'number' },
|
|
38
|
+
},
|
|
39
|
+
required: ['pid', 'serviceId', 'workerId']
|
|
40
|
+
},
|
|
41
|
+
response: { 200: metricResponseSchema }
|
|
42
|
+
}
|
|
43
|
+
}, async ({ params: { pid, serviceId, workerId } }) => {
|
|
44
|
+
return fastify.loaded.metrics[pid]?.services[serviceId]?.[workerId] || emptyMetrics
|
|
45
|
+
})
|
|
46
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
|
|
2
|
+
import { RuntimeApiClient } from '@platformatic/control'
|
|
3
|
+
import type { FastifyInstance } from 'fastify'
|
|
4
|
+
|
|
5
|
+
export default async function (fastify: FastifyInstance) {
|
|
6
|
+
const typedFastify = fastify.withTypeProvider<JsonSchemaToTsProvider>()
|
|
7
|
+
const api = new RuntimeApiClient()
|
|
8
|
+
|
|
9
|
+
typedFastify.removeContentTypeParser(['application/json', 'text/*'])
|
|
10
|
+
typedFastify.addContentTypeParser('*', { parseAs: 'buffer' }, async (_request: unknown, body: unknown) => body)
|
|
11
|
+
|
|
12
|
+
typedFastify.all('/proxy/:pid/services/:serviceId/*', {
|
|
13
|
+
schema: {
|
|
14
|
+
hide: true, // needed since the client generation fails to properly handle the '*' wildcard
|
|
15
|
+
}
|
|
16
|
+
}, async (request, reply) => {
|
|
17
|
+
const { pid, serviceId, '*': requestUrl } = request.params as { pid: number, serviceId: string, '*': string } // cast needed because we can't define a valid json schema with the '*' wildcard
|
|
18
|
+
|
|
19
|
+
delete request.headers.connection
|
|
20
|
+
delete request.headers['content-length']
|
|
21
|
+
delete request.headers['content-encoding']
|
|
22
|
+
delete request.headers['transfer-encoding']
|
|
23
|
+
|
|
24
|
+
const injectParams = {
|
|
25
|
+
method: request.method,
|
|
26
|
+
url: '/' + requestUrl,
|
|
27
|
+
headers: request.headers,
|
|
28
|
+
query: request.query,
|
|
29
|
+
body: request.body
|
|
30
|
+
} as Parameters<typeof api.injectRuntime>[2]
|
|
31
|
+
|
|
32
|
+
fastify.log.info({ pid, serviceId, injectParams }, 'runtime request proxy')
|
|
33
|
+
|
|
34
|
+
const res = await api.injectRuntime(pid, serviceId, injectParams)
|
|
35
|
+
|
|
36
|
+
delete res.headers['content-length']
|
|
37
|
+
delete res.headers['transfer-encoding']
|
|
38
|
+
|
|
39
|
+
return reply.code(res.statusCode).headers(res.headers).send(res.body)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify'
|
|
2
|
+
import type { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
|
|
3
|
+
import { RuntimeApiClient } from '@platformatic/control'
|
|
4
|
+
import { getPidToLoad, getSelectableRuntimes } from '../utils/runtimes.ts'
|
|
5
|
+
import { writeFile, readFile } from 'fs/promises'
|
|
6
|
+
import { checkRecordState } from '../utils/states.ts'
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
import { pidParamSchema, selectableRuntimeSchema, modeSchema, profileSchema } from '../schemas/index.ts'
|
|
9
|
+
|
|
10
|
+
const __dirname = import.meta.dirname
|
|
11
|
+
|
|
12
|
+
export default async function (fastify: FastifyInstance) {
|
|
13
|
+
const typedFastify = fastify.withTypeProvider<JsonSchemaToTsProvider>()
|
|
14
|
+
|
|
15
|
+
// FIXME: types have not been properly implemented in `@platformatic/control` and they should be updated as form the cast in the following line
|
|
16
|
+
const api = new RuntimeApiClient() as RuntimeApiClient & { startApplicationProfiling: (...args: unknown[]) => Promise<unknown>, stopApplicationProfiling: (...args: unknown[]) => Promise<string> }
|
|
17
|
+
|
|
18
|
+
typedFastify.get('/runtimes', {
|
|
19
|
+
schema: {
|
|
20
|
+
querystring: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
includeAdmin: {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
default: false,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
response: { 200: { type: 'array', items: selectableRuntimeSchema } }
|
|
30
|
+
}
|
|
31
|
+
}, async (request) => getSelectableRuntimes(await api.getRuntimes(), request.query.includeAdmin))
|
|
32
|
+
|
|
33
|
+
typedFastify.get('/runtimes/:pid/health', {
|
|
34
|
+
schema: {
|
|
35
|
+
params: pidParamSchema,
|
|
36
|
+
response: {
|
|
37
|
+
200: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
properties: {
|
|
41
|
+
status: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
enum: ['OK', 'KO'],
|
|
44
|
+
description: "Status can only be 'OK' or 'KO'"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
required: ['status']
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}, async ({ params: { pid } }) => {
|
|
52
|
+
const ok = { status: 'OK' as const }
|
|
53
|
+
const ko = { status: 'KO' as const }
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const result = await api.getMatchingRuntime({ pid: pid.toString() })
|
|
57
|
+
return (result.pid === pid) ? ok : ko
|
|
58
|
+
} catch {
|
|
59
|
+
return ko
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
typedFastify.get('/runtimes/:pid/services', {
|
|
64
|
+
schema: {
|
|
65
|
+
params: pidParamSchema,
|
|
66
|
+
response: {
|
|
67
|
+
200: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
additionalProperties: false,
|
|
70
|
+
required: ['entrypoint', 'production', 'applications'],
|
|
71
|
+
properties: {
|
|
72
|
+
entrypoint: {
|
|
73
|
+
type: 'string'
|
|
74
|
+
},
|
|
75
|
+
production: {
|
|
76
|
+
type: 'boolean'
|
|
77
|
+
},
|
|
78
|
+
applications: {
|
|
79
|
+
type: 'array',
|
|
80
|
+
items: {
|
|
81
|
+
anyOf: [
|
|
82
|
+
{
|
|
83
|
+
additionalProperties: false,
|
|
84
|
+
type: 'object',
|
|
85
|
+
required: ['id', 'type', 'status', 'version', 'localUrl', 'entrypoint', 'dependencies'],
|
|
86
|
+
properties: {
|
|
87
|
+
id: {
|
|
88
|
+
type: 'string'
|
|
89
|
+
},
|
|
90
|
+
type: {
|
|
91
|
+
type: 'string'
|
|
92
|
+
},
|
|
93
|
+
status: {
|
|
94
|
+
type: 'string'
|
|
95
|
+
},
|
|
96
|
+
version: {
|
|
97
|
+
type: 'string'
|
|
98
|
+
},
|
|
99
|
+
localUrl: {
|
|
100
|
+
type: 'string'
|
|
101
|
+
},
|
|
102
|
+
entrypoint: {
|
|
103
|
+
type: 'boolean'
|
|
104
|
+
},
|
|
105
|
+
workers: {
|
|
106
|
+
type: 'number'
|
|
107
|
+
},
|
|
108
|
+
url: {
|
|
109
|
+
type: 'string'
|
|
110
|
+
},
|
|
111
|
+
dependencies: {
|
|
112
|
+
type: 'array',
|
|
113
|
+
items: { type: 'string' }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
additionalProperties: false,
|
|
119
|
+
type: 'object',
|
|
120
|
+
required: ['id', 'status'],
|
|
121
|
+
properties: {
|
|
122
|
+
id: {
|
|
123
|
+
type: 'string'
|
|
124
|
+
},
|
|
125
|
+
status: {
|
|
126
|
+
type: 'string'
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}, async (request) => api.getRuntimeApplications(request.params.pid))
|
|
138
|
+
|
|
139
|
+
typedFastify.get('/runtimes/:pid/openapi/:serviceId', {
|
|
140
|
+
schema: {
|
|
141
|
+
params: { type: 'object', properties: { pid: { type: 'number' }, serviceId: { type: 'string' } }, required: ['pid', 'serviceId'] }
|
|
142
|
+
}
|
|
143
|
+
}, async ({ params: { pid, serviceId } }) => api.getRuntimeOpenapi(pid, serviceId))
|
|
144
|
+
|
|
145
|
+
typedFastify.post('/runtimes/:pid/restart', {
|
|
146
|
+
schema: { params: pidParamSchema, body: { type: 'object' } }
|
|
147
|
+
}, async (request) => {
|
|
148
|
+
try {
|
|
149
|
+
await api.restartRuntime(request.params.pid)
|
|
150
|
+
} catch (err) {
|
|
151
|
+
fastify.log.warn({ err }, 'Issue restarting the runtime')
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
typedFastify.post('/record/:pid', {
|
|
156
|
+
schema: {
|
|
157
|
+
params: pidParamSchema,
|
|
158
|
+
body: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
additionalProperties: false,
|
|
161
|
+
properties: { mode: modeSchema, profile: profileSchema },
|
|
162
|
+
required: ['mode', 'profile']
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}, async ({ body: { mode, profile: type }, params: { pid } }) => {
|
|
166
|
+
const from = fastify.loaded.mode
|
|
167
|
+
const to = mode
|
|
168
|
+
if (!checkRecordState({ from, to })) {
|
|
169
|
+
return fastify.log.error({ from, to }, 'Invalid record state machine transition')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const { applications } = await api.getRuntimeApplications(pid)
|
|
173
|
+
fastify.loaded.mode = mode
|
|
174
|
+
if (mode === 'start') {
|
|
175
|
+
for (const { id } of applications) {
|
|
176
|
+
await api.startApplicationProfiling(pid, id, { type })
|
|
177
|
+
}
|
|
178
|
+
fastify.loaded.type = type
|
|
179
|
+
fastify.loaded.metrics = {}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (mode === 'stop') {
|
|
183
|
+
try {
|
|
184
|
+
const runtimes = getSelectableRuntimes(await api.getRuntimes(), false)
|
|
185
|
+
const services = await api.getRuntimeApplications(getPidToLoad(runtimes))
|
|
186
|
+
|
|
187
|
+
const profile: Record<string, Uint8Array> = {}
|
|
188
|
+
for (const { id } of applications) {
|
|
189
|
+
const profileData = Buffer.from(await api.stopApplicationProfiling(pid, id, { type }))
|
|
190
|
+
await writeFile(join(__dirname, '..', '..', 'frontend', 'dist', `${fastify.loaded.type}-profile-${id}.pb`), profileData)
|
|
191
|
+
profile[id] = new Uint8Array(profileData)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const loadedJson = JSON.stringify({ runtimes, services, metrics: fastify.loaded.metrics[getPidToLoad(runtimes)], profile, type })
|
|
195
|
+
|
|
196
|
+
const scriptToAppend = ` <script>window.LOADED_JSON=${loadedJson}</script>\n</body>`
|
|
197
|
+
const bundlePath = join(__dirname, '..', '..', 'frontend', 'dist', 'index.html')
|
|
198
|
+
await writeFile(bundlePath, (await readFile(bundlePath, 'utf8')).replace('</body>', scriptToAppend), 'utf8')
|
|
199
|
+
} catch (err) {
|
|
200
|
+
fastify.log.error({ err }, 'Unable to save the loaded JSON')
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { WebSocket } from 'ws'
|
|
2
|
+
import split2 from 'split2'
|
|
3
|
+
import type { FastifyInstance } from 'fastify'
|
|
4
|
+
import type { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
|
|
5
|
+
import { RuntimeApiClient } from '@platformatic/control'
|
|
6
|
+
import { pidParamSchema } from '../schemas/index.ts'
|
|
7
|
+
import type { PidParam } from '../schemas/index.ts'
|
|
8
|
+
import { pipeline } from 'node:stream/promises'
|
|
9
|
+
|
|
10
|
+
export default async function (fastify: FastifyInstance) {
|
|
11
|
+
const typedFastify = fastify.withTypeProvider<JsonSchemaToTsProvider>()
|
|
12
|
+
const api = new RuntimeApiClient()
|
|
13
|
+
|
|
14
|
+
const wsSendAsync = (socket: WebSocket, data: string): Promise<void> => new Promise((resolve, reject) => setTimeout(() => socket.send(data, (err) => (err)
|
|
15
|
+
? reject(err)
|
|
16
|
+
: resolve()
|
|
17
|
+
), 100)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
typedFastify.get<{ Params: PidParam }>('/runtimes/:pid/logs/ws', {
|
|
21
|
+
schema: { params: pidParamSchema },
|
|
22
|
+
websocket: true
|
|
23
|
+
}, async (socket, { params: { pid } }) => {
|
|
24
|
+
try {
|
|
25
|
+
const clientStream = api.getRuntimeLiveLogsStream(pid)
|
|
26
|
+
|
|
27
|
+
socket.on('close', () => {
|
|
28
|
+
clientStream.destroy()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
await pipeline(
|
|
32
|
+
clientStream,
|
|
33
|
+
split2(),
|
|
34
|
+
async function * (source: AsyncIterable<string>) {
|
|
35
|
+
for await (const line of source) {
|
|
36
|
+
await wsSendAsync(socket, line)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
} catch (error) {
|
|
41
|
+
fastify.log.error({ error }, 'fatal error on runtime logs ws')
|
|
42
|
+
socket.close()
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|