better code: split files
Massimo Melina committed
May 1, 2022 at 18:50 UTC
3fa0c4e5d1f003c14f5ee297f3f35be45693fccd
4 files changed
+154
-132
server/src/adminApis.ts
+7
-131
@@ -6,8 +6,10 @@ import { getStatus, getUrls, httpsPortCfg, portCfg } from './listen'
6
import { API_VERSION, BUILD_TIMESTAMP, COMPATIBLE_API_VERSION, FORBIDDEN, HFS_STARTED, IS_WINDOWS, VERSION } from './const'
7
import vfsApis from './api.vfs'
8
import accountsApis from './api.accounts'
9
-import { Connection, getConnections } from './connections'
10
-import { debounceAsync, isLocalHost, objSameKeys, onOff, pendingPromise, same } from './misc'
9
+import pluginsApis from './api.plugins'
10
+import monitorApis from './api.monitor'
11
+import { getConnections } from './connections'
12
+import { debounceAsync, isLocalHost, onOff } from './misc'
13
import _ from 'lodash'
14
import events from './events'
15
import { getFromAccount } from './perm'
@@ -18,23 +20,15 @@ import { writeFile } from 'fs/promises'
20
import { createReadStream } from 'fs'
21
import * as readline from 'readline'
22
import { loggers } from './log'
21
-import {
22
- mapPlugins,
23
- getAvailablePlugins,
24
- Plugin,
25
- AvailablePlugin,
26
- enablePlugins,
27
- pluginsConfig,
28
- getPluginConfigFields
29
-} from './plugins'
23
import { execFile } from 'child_process'
24
import { promisify } from 'util'
32
-import assert from 'assert'
25
26
export const adminApis: ApiHandlers = {
27
28
...vfsApis,
29
...accountsApis,
30
+ ...pluginsApis,
31
+ ...monitorApis,
32
33
async set_config({ values: v }) {
34
if (v) {
@@ -48,9 +42,7 @@ export const adminApis: ApiHandlers = {
42
return {}
43
},
44
51
- get_config(params) {
52
- return getWholeConfig(params)
53
- },
45
+ get_config: getWholeConfig,
46
47
async get_status() {
48
const st = getStatus()
@@ -77,48 +69,6 @@ export const adminApis: ApiHandlers = {
69
}
70
},
71
80
- async disconnect({ ip, port, wait }) {
81
- const match = _.matches({ ip, port })
82
- const c = getConnections().find(c => match(getConnAddress(c)))
83
- const waiter = pendingPromise<void>()
84
- c?.socket.end(waiter.resolve)
85
- if (wait)
86
- await waiter
87
- return { result: Boolean(c) }
88
- },
89
-
90
- get_connections({}, ctx) {
91
- const list = sendList( getConnections().map(c => serializeConnection(c)) )
92
- return list.events(ctx, {
93
- connection: conn => list.add(serializeConnection(conn)),
94
- connectionClosed(conn: Connection) {
95
- list.remove(serializeConnection(conn, true))
96
- },
97
- connectionUpdated(conn: Connection, change: Partial<Omit<Connection,'ip'>>) {
98
- if (change.ctx) {
99
- Object.assign(change, fromCtx(change.ctx))
100
- delete change.ctx
101
- }
102
- list.update(serializeConnection(conn, true), change)
103
- },
104
- })
105
-
106
- function serializeConnection(conn: Connection, minimal?:true) {
107
- const { socket, started, secure, got } = conn
108
- return Object.assign(getConnAddress(conn), !minimal && {
109
- v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
110
- got,
111
- started,
112
- secure: (secure || undefined) as boolean|undefined, // undefined will save some space once json-ed
113
- ...fromCtx(conn.ctx),
114
- })
115
- }
116
-
117
- function fromCtx(ctx?: Koa.Context) {
118
- return ctx && { path: ctx.fileSource && ctx.path } // only for downloading files
119
- }
120
- },
121
-
72
async save_pem({ cert, private_key, name='self' }) {
73
if (!cert || !private_key)
74
return new ApiError(400)
@@ -159,80 +109,6 @@ export const adminApis: ApiHandlers = {
109
}
110
}
111
},
162
-
163
- get_plugins({}, ctx) {
164
- const list = sendList([ ...mapPlugins(serialize), ...getAvailablePlugins() ])
165
- return list.events(ctx, {
166
- pluginInstalled: p => list.add(serialize(p)),
167
- 'pluginStarted pluginStopped': p => {
168
- const { id, ...rest } = serialize(p)
169
- list.update({ id }, rest)
170
- },
171
- pluginUninstalled: id => list.remove({ id }),
172
- })
173
-
174
- function serialize(p: Readonly<Plugin> | AvailablePlugin) {
175
- return Object.assign('getData' in p ? p.getData() : p, { started: null }, _.pick(p, ['id','started']))
176
- }
177
- },
178
-
179
- async set_plugin({ id, enabled, config }) {
180
- assert(id, 'id')
181
- if (enabled !== undefined)
182
- enablePlugins.set( arr =>
183
- arr.includes(id) === enabled ? arr
184
- : enabled ? [...arr, id]
185
- : arr.filter((x: string) => x !== id)
186
- )
187
- if (config) {
188
- const fields = getPluginConfigFields(id)
189
- config = _.pickBy(config, (v, k) =>
190
- v !== null && !same(v, fields?.[k]?.defaultValue))
191
- if (_.isEmpty(config))
192
- config = undefined
193
- pluginsConfig.set(v => ({ ...v, [id]: config }))
194
- }
195
- return {}
196
- },
197
-
198
- async get_plugin({ id }) {
199
- return {
200
- enabled: enablePlugins.get().includes(id),
201
- config: {
202
- ...objSameKeys(getPluginConfigFields(id) ||{}, v => v?.defaultValue),
203
- ...pluginsConfig.get()[id]
204
- }
205
- }
206
- },
207
-}
208
-
209
-// offer an api for a generic dynamic list
210
-function sendList<T>(addAtStart: T[]=[]) {
211
- const stream = new Readable({ objectMode: true, read(){} })
212
- const ret = {
213
- return: stream,
214
- add(rec: T) { stream.push({ add: rec }) },
215
- remove(key: Partial<T>) { stream.push({ remove: [ key ] }) },
216
- update(search: Partial<T>, change: Partial<T>) {
217
- stream.push({ update:[{ search, change }] })
218
- },
219
- events(ctx: Koa.Context, eventMap: Parameters<typeof onOff>[1]) {
220
- const off = onOff(events, eventMap)
221
- ctx.res.once('close', off)
222
- return stream
223
- }
224
- }
225
- for (const x of addAtStart)
226
- ret.add(x)
227
- stream.push('init')
228
- return ret
229
-}
230
-
231
-function getConnAddress(conn: Connection) {
232
- return {
233
- ip: conn.ctx?.ip || conn.socket.remoteAddress,
234
- port: conn.socket.remotePort,
235
- }
112
}
113
114
for (const k in adminApis) {
server/src/api.monitor.ts
new
+60
@@ -0,0 +1,60 @@
1
+import _ from 'lodash'
2
+import { Connection, getConnections } from './connections'
3
+import { pendingPromise } from './misc'
4
+import { ApiHandlers, sendList } from './apiMiddleware'
5
+import Koa from 'koa'
6
+
7
+const apis: ApiHandlers = {
8
+
9
+ async disconnect({ ip, port, wait }) {
10
+ const match = _.matches({ ip, port })
11
+ const c = getConnections().find(c => match(getConnAddress(c)))
12
+ const waiter = pendingPromise<void>()
13
+ c?.socket.end(waiter.resolve)
14
+ if (wait)
15
+ await waiter
16
+ return { result: Boolean(c) }
17
+ },
18
+
19
+ get_connections({}, ctx) {
20
+ const list = sendList( getConnections().map(c => serializeConnection(c)) )
21
+ return list.events(ctx, {
22
+ connection: conn => list.add(serializeConnection(conn)),
23
+ connectionClosed(conn: Connection) {
24
+ list.remove(serializeConnection(conn, true))
25
+ },
26
+ connectionUpdated(conn: Connection, change: Partial<Omit<Connection,'ip'>>) {
27
+ if (change.ctx) {
28
+ Object.assign(change, fromCtx(change.ctx))
29
+ delete change.ctx
30
+ }
31
+ list.update(serializeConnection(conn, true), change)
32
+ },
33
+ })
34
+
35
+ function serializeConnection(conn: Connection, minimal?:true) {
36
+ const { socket, started, secure, got } = conn
37
+ return Object.assign(getConnAddress(conn), !minimal && {
38
+ v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
39
+ got,
40
+ started,
41
+ secure: (secure || undefined) as boolean|undefined, // undefined will save some space once json-ed
42
+ ...fromCtx(conn.ctx),
43
+ })
44
+ }
45
+
46
+ function fromCtx(ctx?: Koa.Context) {
47
+ return ctx && { path: ctx.fileSource && ctx.path } // only for downloading files
48
+ }
49
+ },
50
+
51
+}
52
+
53
+export default apis
54
+
55
+function getConnAddress(conn: Connection) {
56
+ return {
57
+ ip: conn.ctx?.ip || conn.socket.remoteAddress,
58
+ port: conn.socket.remotePort,
59
+ }
60
+}
server/src/api.plugins.ts
new
+62
@@ -0,0 +1,62 @@
1
+import {
2
+ AvailablePlugin,
3
+ enablePlugins,
4
+ getAvailablePlugins,
5
+ getPluginConfigFields,
6
+ mapPlugins,
7
+ Plugin, pluginsConfig
8
+} from './plugins'
9
+import _ from 'lodash'
10
+import assert from 'assert'
11
+import { objSameKeys, same } from './misc'
12
+import { ApiHandlers, sendList } from './apiMiddleware'
13
+
14
+const apis: ApiHandlers = {
15
+ get_plugins({}, ctx) {
16
+ const list = sendList([ ...mapPlugins(serialize), ...getAvailablePlugins() ])
17
+ return list.events(ctx, {
18
+ pluginInstalled: p => list.add(serialize(p)),
19
+ 'pluginStarted pluginStopped': p => {
20
+ const { id, ...rest } = serialize(p)
21
+ list.update({ id }, rest)
22
+ },
23
+ pluginUninstalled: id => list.remove({ id }),
24
+ })
25
+
26
+ function serialize(p: Readonly<Plugin> | AvailablePlugin) {
27
+ return Object.assign('getData' in p ? p.getData() : p, { started: null }, _.pick(p, ['id','started']))
28
+ }
29
+ },
30
+
31
+ async set_plugin({ id, enabled, config }) {
32
+ assert(id, 'id')
33
+ if (enabled !== undefined)
34
+ enablePlugins.set( arr =>
35
+ arr.includes(id) === enabled ? arr
36
+ : enabled ? [...arr, id]
37
+ : arr.filter((x: string) => x !== id)
38
+ )
39
+ if (config) {
40
+ const fields = getPluginConfigFields(id)
41
+ config = _.pickBy(config, (v, k) =>
42
+ v !== null && !same(v, fields?.[k]?.defaultValue))
43
+ if (_.isEmpty(config))
44
+ config = undefined
45
+ pluginsConfig.set(v => ({ ...v, [id]: config }))
46
+ }
47
+ return {}
48
+ },
49
+
50
+ async get_plugin({ id }) {
51
+ return {
52
+ enabled: enablePlugins.get().includes(id),
53
+ config: {
54
+ ...objSameKeys(getPluginConfigFields(id) ||{}, v => v?.defaultValue),
55
+ ...pluginsConfig.get()[id]
56
+ }
57
+ }
58
+ },
59
+
60
+}
61
+
62
+export default apis
server/src/apiMiddleware.ts
+25
-1
@@ -4,7 +4,8 @@ import { IncomingMessage } from 'http'
4
import Koa from 'koa'
5
import createSSE from './sse'
6
import { Readable } from 'stream'
7
-import { asyncGeneratorToReadable } from './misc'
7
+import { asyncGeneratorToReadable, onOff } from './misc'
8
+import events from './events'
9
10
export class ApiError extends Error {
11
constructor(public status:number, message?:string | Error) {
@@ -63,3 +64,26 @@ async function getJsonFromReq(req: IncomingMessage): Promise<any> {
64
})
65
})
66
}
67
+
68
+// offer an api for a generic dynamic list
69
+export function sendList<T>(addAtStart: T[]=[]) {
70
+ const stream = new Readable({ objectMode: true, read(){} })
71
+ const ret = {
72
+ return: stream,
73
+ add(rec: T) { stream.push({ add: rec }) },
74
+ remove(key: Partial<T>) { stream.push({ remove: [ key ] }) },
75
+ update(search: Partial<T>, change: Partial<T>) {
76
+ stream.push({ update:[{ search, change }] })
77
+ },
78
+ events(ctx: Koa.Context, eventMap: Parameters<typeof onOff>[1]) {
79
+ const off = onOff(events, eventMap)
80
+ ctx.res.once('close', off)
81
+ return stream
82
+ }
83
+ }
84
+ for (const x of addAtStart)
85
+ ret.add(x)
86
+ stream.push('init')
87
+ return ret
88
+}
89
+