@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/package.json CHANGED
@@ -1,71 +1,77 @@
1
1
  {
2
+ "type": "module",
2
3
  "name": "@platformatic/watt-admin",
3
- "version": "0.5.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": "./node_modules/.bin/plt-client ./web/backend/openapi.json --name backend --folder web/frontend/src/client --language ts --frontend --full --skip-config-update --props-optional",
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/cli.test.js test/start.test.js test/cli-integration.test.js",
13
- "test:backend": "cd web/backend && borp --concurrency 1",
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": "./node_modules/.bin/playwright install chromium",
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.1.0",
24
- "@inquirer/prompts": "^7.3.3",
25
- "@platformatic/composer": "^2.72.0",
26
- "@platformatic/control": "^2.72.0",
27
- "@platformatic/runtime": "^2.72.0",
28
- "@platformatic/service": "^2.72.0",
29
- "@platformatic/vite": "^2.72.0",
30
- "@scalar/api-reference-react": "^0.7.13",
31
- "fastify": "^5.0.0",
32
- "proxyquire": "^2.1.3",
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
- "wattpm": "^2.72.0"
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
- "@fastify/type-provider-json-schema-to-ts": "^5.0.0",
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/proxyquire": "1.3.31",
45
- "@types/react-dom": "^19.0.4",
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
- "@vitejs/plugin-react": "^4.3.3",
49
- "autoprefixer": "^10.4.20",
50
- "borp": "^0.19.0",
51
- "d3": "~7.9.0",
52
- "dayjs": "^1.11.13",
53
- "eslint": "^9.22.0",
54
- "fastify-tsconfig": "^2.0.0",
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": "./cli.js"
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.72.0.json",
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
- "autoload": {
11
- "path": "web",
12
- "mappings": {
13
- "backend": {
14
- "id": "backend",
15
- "useHttp": true
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.72.0.json",
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,6 @@
1
+ import websocket from '@fastify/websocket'
2
+ import type { FastifyInstance } from 'fastify'
3
+
4
+ export default async function (fastify: FastifyInstance) {
5
+ await fastify.register(websocket)
6
+ }
@@ -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
+ }