fix: admin/monitoring: reused connections were displaying wrong info (moved logic to SendList because to be effective it must know what has actually been sent, and SendList is the only one)
fix: admin/monitoring: reused connections were displaying wrong info (moved logic to SendList because to be effective it must know what has actually been sent, and SendList is the only one)
Massimo Melina committed
Jan 8, 2024 at 00:30 UTC
a2091a946222db4d2c378ad4b248232d40b8f482
5 files changed
+38
-27
admin/src/MonitorPage.ts
+1
-1
@@ -149,7 +149,7 @@ function Connections() {
149
icon: row.archive ? FolderZip : row.op === 'upload' ? Upload : Download,
150
progress: row.opProgress ?? row.opOffset,
151
offset: row.opOffset,
152
- addTitle: row.op === 'cache' ? "Cache hit" : (row.opTotal && ("Total: " + formatBytes(row.opTotal))),
152
+ addTitle: row.opTotal && ("Total: " + formatBytes(row.opTotal)),
153
sx: { mr: 1 }
154
}),
155
row.archive ? h(Box, {}, value, h(Box, { fontSize: 'x-small', color: 'text.secondary' }, row.archive))
src/api.monitor.ts
+8
-21
@@ -2,14 +2,12 @@
2
3
import _ from 'lodash'
4
import { Connection, getConnections } from './connections'
5
-import { HTTP_NOT_MODIFIED, pendingPromise, shortenAgent, typedEntries, wait } from './misc'
5
+import { pendingPromise, shortenAgent, wait } from './misc'
6
import { ApiHandlers, SendListReadable } from './apiMiddleware'
7
import Koa from 'koa'
8
import { totalGot, totalInSpeed, totalOutSpeed, totalSent } from './throttler'
9
import { getCurrentUsername } from './auth'
10
11
-const sent = Symbol('sent')
12
-
11
export default {
12
13
async disconnect({ ip, port, wait }) {
@@ -28,38 +26,27 @@ export default {
26
27
get_connections({}, ctx) {
28
const list = new SendListReadable({
29
+ diff: true,
30
addAtStart: getConnections().map(c =>
32
- !ignore(c) && (c[sent] = serializeConnection(c))).filter(Boolean),
33
- onEnd() {
34
- for (const c of getConnections())
35
- delete c[sent]
36
- }
31
+ !ignore(c) && serializeConnection(c)).filter(Boolean),
32
})
33
type Change = Partial<Omit<Connection,'ip'>>
34
list.props({ you: ctx.ip })
35
return list.events(ctx, {
36
connection(conn: Connection) {
37
if (ignore(conn)) return
43
- list.add(conn[sent] = serializeConnection(conn))
38
+ list.add(serializeConnection(conn))
39
},
40
connectionClosed(conn: Connection) {
41
if (ignore(conn)) return
42
list.remove(getConnAddress(conn))
48
- delete conn[sent]
43
},
44
connectionUpdated(conn: Connection, change: Change) {
51
- if (ignore(conn) || ignore(change as any) || !conn[sent]) return
45
+ if (conn.socket.closed || ignore(conn) || ignore(change as any) || _.isEmpty(change)) return
46
if (change.ctx) {
47
Object.assign(change, fromCtx(change.ctx))
48
change.ctx = undefined
49
}
56
- // avoid sending non-changes
57
- const last = conn[sent]
58
- for (const [k, v] of typedEntries(change))
59
- if (v === last[k])
60
- delete change[k]
61
- if (_.isEmpty(change)) return
62
- Object.assign(last, change)
50
list.update(getConnAddress(conn), change)
51
},
52
})
@@ -88,10 +75,10 @@ export default {
75
...s.browsing ? { op: 'browsing', path: decodeURIComponent(s.browsing) }
76
: s.uploadPath ? { op: 'upload',path: decodeURIComponent(s.uploadPath) }
77
: {
91
- op: s.op === 'download' && ctx.status === HTTP_NOT_MODIFIED ? 'cache' : s.op,
92
- path: decodeURIComponent(ctx.path)
78
+ op: !s.considerAsGui && s.op || undefined,
79
+ path: decodeURIComponent(ctx.originalUrl)
80
},
94
- opProgress: _.round(s.opProgress, 3),
81
+ opProgress: _.isNumber(s.opProgress) ? _.round(s.opProgress, 3) : undefined,
82
opTotal: s.opTotal,
83
opOffset: s.opOffset,
84
}
src/apiMiddleware.ts
+26
-2
@@ -3,7 +3,7 @@
3
import Koa from 'koa'
4
import createSSE from './sse'
5
import { Readable } from 'stream'
6
-import { asyncGeneratorToReadable, LIST, onOff, removeStarting } from './misc'
6
+import { asyncGeneratorToReadable, LIST, onOff, removeStarting, wantArray } from './misc'
7
import events from './events'
8
import { HTTP_BAD_REQUEST, HTTP_FOOL, HTTP_NOT_FOUND } from './const'
9
import _ from 'lodash'
@@ -86,11 +86,35 @@ export class SendListReadable<T> extends Readable {
86
protected lastError: string | number | undefined
87
protected buffer: any[] = []
88
protected processBuffer: _.DebouncedFunc<any>
89
- constructor({ addAtStart, doAtStart, bufferTime, onEnd }:{ bufferTime?: number, addAtStart?: T[], doAtStart?: SendListFunc<T>, onEnd?: SendListFunc<T> }={}) {
89
+ protected sent: undefined | T[]
90
+ constructor({ addAtStart, doAtStart, bufferTime, onEnd, diff }:
91
+ { bufferTime?: number, addAtStart?: T[], doAtStart?: SendListFunc<T>, onEnd?: SendListFunc<T>, diff?: boolean }={}) {
92
super({ objectMode: true, read(){} })
93
if (!bufferTime)
94
bufferTime = 200
95
+ if (diff)
96
+ this.sent = []
97
this.processBuffer = _.debounce(() => {
98
+ const {sent} = this
99
+ if (sent)
100
+ this.buffer = this.buffer.filter(([cmd, a, b]) => {
101
+ if (cmd === LIST.add)
102
+ return sent.push(...wantArray(a))
103
+ if (cmd === LIST.remove)
104
+ return _.remove(sent, a)
105
+ if (cmd !== LIST.update)
106
+ return true
107
+ const found = _.find(sent, a) as any
108
+ if (!found) return
109
+ for (const k in b)
110
+ if (b[k] === found[k])
111
+ delete b[k]
112
+ else {
113
+ found[k] = b[k]
114
+ b[k] ??= null // go and delete it, remotely
115
+ }
116
+ return !_.isEmpty(b)
117
+ })
118
if (!this.buffer.length) return
119
this.push(this.buffer)
120
this.buffer = []
src/connections.ts
+1
@@ -61,6 +61,7 @@ export function updateConnectionForCtx(ctx: Context ) {
61
const conn = getConnection(ctx)
62
if (conn)
63
updateConnection(conn, { ctx })
64
+ return conn
65
}
66
67
export function updateConnection(conn: Connection, change: Partial<Connection>, changeState?: true | Partial<Context['state']>) {
src/upload.ts
+2
-3
@@ -12,7 +12,7 @@ import { Callback, dirTraversal, escapeHTML, loadFileAttr, storeFileAttr, try_ }
12
import { notifyClient } from './frontEndApis'
13
import { defineConfig } from './config'
14
import { getFreeDiskSync } from './util-os'
15
-import { socket2connection, updateConnection, updateConnectionForCtx } from './connections'
15
+import { updateConnection, updateConnectionForCtx } from './connections'
16
import { roundSpeed } from './throttler'
17
import { getCurrentUsername } from './auth'
18
import { setCommentFor } from './comments'
@@ -131,9 +131,8 @@ export function uploadWriter(base: VfsNode, path: string, ctx: Koa.Context) {
131
let lastGotTime = 0
132
const opTotal = reqSize + resume
133
Object.assign(ctx.state, { op: 'upload', opTotal, opOffset: resume / opTotal, opProgress: 0 })
134
- const conn = socket2connection(ctx.socket)
134
+ const conn = updateConnectionForCtx(ctx)
135
if (!conn) return
136
- updateConnectionForCtx(ctx)
136
const h = setInterval(() => {
137
const now = Date.now()
138
const got = ret.bytesWritten