@platformatic/watt-admin 0.6.0-alpha.2 → 0.6.0-alpha.3
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/cli.d.ts +3 -0
- package/lib/start.d.ts +3 -0
- package/lib/start.js +6 -1
- package/package.json +2 -1
- package/renovate.json +17 -0
- package/watt.json +16 -8
- package/web/backend/global.d.ts +17 -0
- 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/frontend/dist/index.html +470 -466
- package/web/frontend/index.d.ts +4 -0
- package/web/frontend/playwright.config.ts +27 -0
- package/web/frontend/postcss.config.ts +14 -0
- package/CLAUDE.md +0 -32
- package/tsconfig.json +0 -22
- package/web/backend/tsconfig.json +0 -24
- package/web/frontend/tsconfig.json +0 -30
package/cli.d.ts
ADDED
package/lib/start.d.ts
ADDED
package/lib/start.js
CHANGED
|
@@ -52,7 +52,9 @@ export async function start (client, selectedRuntime) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const configFile = join(__dirname, '..', 'watt.json')
|
|
55
|
-
const server = await create(configFile
|
|
55
|
+
const server = await create(configFile, undefined, {
|
|
56
|
+
setupSignals: false
|
|
57
|
+
})
|
|
56
58
|
entrypointUrl = await server.start()
|
|
57
59
|
|
|
58
60
|
if (record) {
|
|
@@ -64,6 +66,9 @@ export async function start (client, selectedRuntime) {
|
|
|
64
66
|
|
|
65
67
|
// Always clean up the client
|
|
66
68
|
await client.close()
|
|
69
|
+
|
|
70
|
+
// Then stop the server
|
|
71
|
+
await server.close()
|
|
67
72
|
})
|
|
68
73
|
|
|
69
74
|
const { statusCode, body } = await requestRecord('start')
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@platformatic/watt-admin",
|
|
4
|
-
"version": "0.6.0-alpha.
|
|
4
|
+
"version": "0.6.0-alpha.3",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepublishOnly": "npm run clean && npm run build",
|
|
7
7
|
"dev": "wattpm dev",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@platformatic/wattpm-pprof-capture": "^3.13.0",
|
|
34
34
|
"@scalar/api-reference-react": "^0.7.55",
|
|
35
35
|
"@vitejs/plugin-react": "^5.0.4",
|
|
36
|
+
"amaro": "^1.1.4",
|
|
36
37
|
"autoprefixer": "^10.4.21",
|
|
37
38
|
"close-with-grace": "^2.3.0",
|
|
38
39
|
"d3": "^7.9.0",
|
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
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|