hfs 3.1.4 → 3.2.0-alpha1.1
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/admin/assets/af-RjyQ-neT.js +1 -0
- package/admin/assets/am-PptHpglb.js +1 -0
- package/admin/assets/ar-Du3syDxa.js +1 -0
- package/admin/assets/ar-dz-CiziRh1x.js +1 -0
- package/admin/assets/ar-iq-hCKg0CRP.js +1 -0
- package/admin/assets/ar-kw-COYePt-p.js +1 -0
- package/admin/assets/ar-ly-CY5yETqb.js +1 -0
- package/admin/assets/ar-ma-DczXi97r.js +1 -0
- package/admin/assets/ar-sa-Bu7sTKzl.js +1 -0
- package/admin/assets/ar-tn-BrG0r6qZ.js +1 -0
- package/admin/assets/az-DYRCrdk0.js +1 -0
- package/admin/assets/be-DObSs5HX.js +1 -0
- package/admin/assets/bg-BDBkMe50.js +1 -0
- package/admin/assets/bi-CApXFwc6.js +1 -0
- package/admin/assets/bm-CjxCV8Lr.js +1 -0
- package/admin/assets/bn-DzWZbDi3.js +1 -0
- package/admin/assets/bn-bd-ijdPRJbb.js +1 -0
- package/admin/assets/bo-BDDfj5XH.js +1 -0
- package/admin/assets/br-DRHEjait.js +1 -0
- package/admin/assets/bs-B0Zv2Agm.js +1 -0
- package/admin/assets/ca-DuZAUqU_.js +1 -0
- package/admin/assets/cs-DVkylosR.js +1 -0
- package/admin/assets/cv-8Is1Fns9.js +1 -0
- package/admin/assets/cy-Bk30Or_y.js +1 -0
- package/admin/assets/da-C41DIdZK.js +1 -0
- package/admin/assets/de--qvLJgIE.js +1 -0
- package/admin/assets/de-at-D5vCBlsw.js +1 -0
- package/admin/assets/de-ch-Oh2cO23c.js +1 -0
- package/admin/assets/dv-4ZO2KNbw.js +1 -0
- package/admin/assets/el-CobOUqyu.js +1 -0
- package/admin/assets/en-C9afClpS.js +1 -0
- package/admin/assets/en-au-C_QMghEl.js +1 -0
- package/admin/assets/en-ca-C80KILwc.js +1 -0
- package/admin/assets/en-gb-BwQ0asAI.js +1 -0
- package/admin/assets/en-ie-ConCcQkq.js +1 -0
- package/admin/assets/en-il-CAOzD4DD.js +1 -0
- package/admin/assets/en-in-87gardaO.js +1 -0
- package/admin/assets/en-nz-uMTjgkDP.js +1 -0
- package/admin/assets/en-sg-BlbiLv4L.js +1 -0
- package/admin/assets/en-tt-BXXmBax2.js +1 -0
- package/admin/assets/eo-BBL7o-0W.js +1 -0
- package/admin/assets/es-DuNej-79.js +1 -0
- package/admin/assets/es-do-bZWKFwTE.js +1 -0
- package/admin/assets/es-mx-C2Lfb86F.js +1 -0
- package/admin/assets/es-pr-wU3Du8m-.js +1 -0
- package/admin/assets/es-us-qcU-zRiw.js +1 -0
- package/admin/assets/et-B-4PjN_n.js +1 -0
- package/admin/assets/eu-D0pNDZ9i.js +1 -0
- package/admin/assets/fa-R_zmYBGc.js +1 -0
- package/admin/assets/fi-CWYS_0Hg.js +1 -0
- package/admin/assets/fo-aJYpcnWS.js +1 -0
- package/admin/assets/fr-BHFrWfwB.js +1 -0
- package/admin/assets/fr-ca-ihwmO47n.js +1 -0
- package/admin/assets/fr-ch-Cb8XbRfE.js +1 -0
- package/admin/assets/fy-DT9BUc6t.js +1 -0
- package/admin/assets/ga-B8p4naXJ.js +1 -0
- package/admin/assets/gd-Dzrl6rPc.js +1 -0
- package/admin/assets/gl-bN2tXl9d.js +1 -0
- package/admin/assets/gom-latn-DUK8PgIJ.js +1 -0
- package/admin/assets/gu-CxAIP8fw.js +1 -0
- package/admin/assets/he-Bm9XJVAj.js +1 -0
- package/admin/assets/hi-CHik9Qxx.js +1 -0
- package/admin/assets/hr-Dn57_dqN.js +1 -0
- package/admin/assets/ht-BRS-FrJq.js +1 -0
- package/admin/assets/hu-hVaGcZ-U.js +1 -0
- package/admin/assets/hy-am-heLxXhpz.js +1 -0
- package/admin/assets/id-B6AkO4h8.js +1 -0
- package/admin/assets/{index-DTxjaflW.js → index-Buyl8rI8.js} +1 -1
- package/admin/assets/index-CFWd-FDo.css +1 -0
- package/admin/assets/index-WXxQwyJV.js +889 -0
- package/admin/assets/is-BUADPoni.js +1 -0
- package/admin/assets/it-Ce3gssOP.js +1 -0
- package/admin/assets/it-ch-CJzj2czr.js +1 -0
- package/admin/assets/ja-DykMIu-9.js +1 -0
- package/admin/assets/jv-Cum6DLwa.js +1 -0
- package/admin/assets/ka-foCPVFxw.js +1 -0
- package/admin/assets/kk-CMNu1ysC.js +1 -0
- package/admin/assets/km-BfMisGaD.js +1 -0
- package/admin/assets/kn-R_9S5KJz.js +1 -0
- package/admin/assets/ko-DYRND-mM.js +1 -0
- package/admin/assets/ku-CNZ_weGy.js +1 -0
- package/admin/assets/ky-BhTAWL4I.js +1 -0
- package/admin/assets/lb-CxWhQxwF.js +1 -0
- package/admin/assets/lo-DBL_XLkE.js +1 -0
- package/admin/assets/lt-BakZVm4p.js +1 -0
- package/admin/assets/lv-CCn-slXG.js +1 -0
- package/admin/assets/me-xPEA4qjE.js +1 -0
- package/admin/assets/mi-6cCs2Mzx.js +1 -0
- package/admin/assets/mk-CwWbHJPS.js +1 -0
- package/admin/assets/ml-Dp7Ab1SV.js +1 -0
- package/admin/assets/mn-ClotGWAe.js +1 -0
- package/admin/assets/mr-GiM-paTF.js +1 -0
- package/admin/assets/ms-BiZ1_eVR.js +1 -0
- package/admin/assets/ms-my-oevI0fOH.js +1 -0
- package/admin/assets/mt-BaIHeOl1.js +1 -0
- package/admin/assets/my-Cg5Fc_1j.js +1 -0
- package/admin/assets/nb-ZPODjT6R.js +1 -0
- package/admin/assets/ne-C0ti-ZX2.js +1 -0
- package/admin/assets/nl-DP2PkkGx.js +1 -0
- package/admin/assets/nl-be-VPC0QyaW.js +1 -0
- package/admin/assets/nn-NLidKyJd.js +1 -0
- package/admin/assets/oc-lnc-Ddj9_PnD.js +1 -0
- package/admin/assets/pa-in-DSAWffhb.js +1 -0
- package/admin/assets/pl-Yojex5aS.js +1 -0
- package/admin/assets/pt-BQMs2la2.js +1 -0
- package/admin/assets/pt-br-DVeeNZu8.js +1 -0
- package/admin/assets/rn-CKkTrK3f.js +1 -0
- package/admin/assets/ro-BbgGULSZ.js +1 -0
- package/admin/assets/ru-mAZX3DTa.js +1 -0
- package/admin/assets/rw-CYErOxKd.js +1 -0
- package/admin/assets/sd-kfPTqkSy.js +1 -0
- package/admin/assets/se-D0bN3rDS.js +1 -0
- package/admin/assets/{sha512-D936QW8l.js → sha512-99nhg44S.js} +1 -1
- package/admin/assets/si-COjb_4Hy.js +1 -0
- package/admin/assets/sk-Co95XNOo.js +1 -0
- package/admin/assets/sl-DUXa5wTF.js +1 -0
- package/admin/assets/sq-B-KU0nWK.js +1 -0
- package/admin/assets/sr-Cb_b-zPG.js +1 -0
- package/admin/assets/sr-cyrl-csuTDIB6.js +1 -0
- package/admin/assets/ss-BDnE-cG6.js +1 -0
- package/admin/assets/sv-CzWdOHcE.js +1 -0
- package/admin/assets/sv-fi-IK8oi5nB.js +1 -0
- package/admin/assets/sw-fPHo5hof.js +1 -0
- package/admin/assets/ta-TteN0nyU.js +1 -0
- package/admin/assets/te-2gsQb1fF.js +1 -0
- package/admin/assets/tet-ClTpDYJv.js +1 -0
- package/admin/assets/tg-Bel7Uc6z.js +1 -0
- package/admin/assets/th-pDQttM9V.js +1 -0
- package/admin/assets/tk-CxoKYZH6.js +1 -0
- package/admin/assets/tl-ph-D9htRcOQ.js +1 -0
- package/admin/assets/tlh-7UqvDBxU.js +1 -0
- package/admin/assets/tr-NjJrq1iC.js +1 -0
- package/admin/assets/tzl-CzGaXn8M.js +1 -0
- package/admin/assets/tzm-BiuscZFr.js +1 -0
- package/admin/assets/tzm-latn-IeWkCgWf.js +1 -0
- package/admin/assets/ug-cn-5LK64x5v.js +1 -0
- package/admin/assets/uk-CzacUaQe.js +1 -0
- package/admin/assets/ur-BRV7z4Gu.js +1 -0
- package/admin/assets/uz-C4G77QOI.js +1 -0
- package/admin/assets/uz-latn-CQFrzZ6F.js +1 -0
- package/admin/assets/vi-DEFXdlJi.js +1 -0
- package/admin/assets/x-pseudo-sWj5RKxR.js +1 -0
- package/admin/assets/yo-CBDjk96e.js +1 -0
- package/admin/assets/zh-CQtX_fkD.js +1 -0
- package/admin/assets/zh-cn-D6dF0jKM.js +1 -0
- package/admin/assets/zh-hk-DY-Jaqdg.js +1 -0
- package/admin/assets/zh-tw-BdmVby0R.js +1 -0
- package/admin/index.html +2 -2
- package/frontend/assets/index-legacy-BtoTkZho.js +9 -0
- package/frontend/assets/{index-legacy-CQ3_LTGh.js → index-legacy-CQovmh_0.js} +1 -1
- package/frontend/assets/{sha512-legacy-DLqdsV8R.js → sha512-legacy-CXU3efCO.js} +1 -1
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +170 -81
- package/package.json +9 -9
- package/plugins/antibrute/plugin.js +150 -19
- package/plugins/list-uploader/public/main.js +1 -1
- package/src/acme.js +11 -7
- package/src/api.accounts.js +3 -3
- package/src/api.auth.js +3 -1
- package/src/api.get_file_list.js +16 -12
- package/src/api.monitor.js +47 -43
- package/src/api.net.js +4 -3
- package/src/api.vfs.js +1 -1
- package/src/commands.js +54 -1
- package/src/config.js +9 -5
- package/src/consoleLog.js +39 -1
- package/src/const.js +5 -1
- package/src/cross.js +22 -1
- package/src/errorPages.js +20 -10
- package/src/events.js +1 -0
- package/src/fileAttr.js +73 -15
- package/src/frontEndApis.js +3 -1
- package/src/index.js +2 -1
- package/src/langs/hfs-lang-ar.json +2 -1
- package/src/langs/hfs-lang-bg.json +2 -1
- package/src/langs/hfs-lang-de.json +2 -1
- package/src/langs/hfs-lang-el.json +2 -1
- package/src/langs/hfs-lang-es.json +2 -1
- package/src/langs/hfs-lang-fi.json +2 -1
- package/src/langs/hfs-lang-fr.json +2 -1
- package/src/langs/hfs-lang-hu.json +2 -1
- package/src/langs/hfs-lang-it.json +2 -1
- package/src/langs/hfs-lang-ja.json +2 -1
- package/src/langs/hfs-lang-ko.json +2 -1
- package/src/langs/hfs-lang-lt.json +2 -1
- package/src/langs/hfs-lang-ms.json +2 -0
- package/src/langs/hfs-lang-nl.json +2 -1
- package/src/langs/hfs-lang-pt-br.json +2 -1
- package/src/langs/hfs-lang-ro.json +2 -1
- package/src/langs/hfs-lang-ru.json +2 -1
- package/src/langs/hfs-lang-sr-latn.json +2 -1
- package/src/langs/hfs-lang-sr.json +2 -1
- package/src/langs/hfs-lang-th.json +2 -1
- package/src/langs/hfs-lang-tr.json +2 -1
- package/src/langs/hfs-lang-uk.json +2 -1
- package/src/langs/hfs-lang-vi.json +2 -1
- package/src/langs/hfs-lang-zh-tw.json +2 -1
- package/src/langs/hfs-lang-zh.json +2 -1
- package/src/listen.js +2 -1
- package/src/log.js +27 -2
- package/src/middlewares.js +8 -2
- package/src/misc.js +14 -0
- package/src/nat.js +57 -20
- package/src/outboundProxy.js +1 -1
- package/src/perm.js +5 -1
- package/src/plugins.js +34 -5
- package/src/serveGuiAndSharedFiles.js +1 -0
- package/src/serveGuiFiles.js +1 -0
- package/src/update.js +9 -15
- package/src/urlList.js +32 -0
- package/src/util-files.js +15 -5
- package/src/util-http.js +2 -0
- package/src/vfs.js +1 -1
- package/src/walkDir.js +7 -1
- package/src/webdav.js +4 -1
- package/src/zip.js +2 -1
- package/admin/assets/index-B66w-a0v.css +0 -1
- package/admin/assets/index-Df6vYR7s.js +0 -822
- package/frontend/assets/index-legacy-D6nPw49_.js +0 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
exports.version = 3.
|
|
1
|
+
exports.version = 3.2
|
|
2
2
|
exports.description = "Introduce increasing delays between login attempts."
|
|
3
3
|
exports.apiRequired = 9.6 // addBlock
|
|
4
4
|
|
|
@@ -8,42 +8,173 @@ exports.config = {
|
|
|
8
8
|
blockAfter: { type: 'number', xs: 6, min: 1, max: 9999, defaultValue: 100, label: "Block IP after", unit: "attempts", helperText: "localhost excluded" },
|
|
9
9
|
blockForHours: { type: 'number', xs: 6, min: 0, defaultValue: 24, label: "Block for", unit: "hours" },
|
|
10
10
|
exclude: { type: 'string', defaultValue: '', label: "Exclude IPs", helperText: "Net mask syntax" },
|
|
11
|
+
maxQueuePerIp: { type: 'number', min: 1, max: 9999, defaultValue: 32, label: "Max queued per IP" },
|
|
12
|
+
maxQueuePerAccount: { type: 'number', min: 1, max: 9999, defaultValue: 16, label: "Max queued per account" },
|
|
13
|
+
maxQueueGlobal: { type: 'number', min: 1, max: 999999, defaultValue: 512, label: "Max queued globally" },
|
|
11
14
|
}
|
|
12
15
|
exports.configDialog = {
|
|
13
16
|
maxWidth: 'xs',
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
const byIp = {}
|
|
20
|
+
const byAccount = {}
|
|
21
|
+
const laneByIp = {}
|
|
22
|
+
const laneByAccount = {}
|
|
23
|
+
const UNKNOWN_ACCOUNT = 'unknown\t'
|
|
17
24
|
|
|
18
25
|
exports.init = api => {
|
|
19
26
|
const { isLocalHost, HOUR, netMatches } = api.misc
|
|
27
|
+
const { makeQ } = api.require('./makeQ')
|
|
28
|
+
const gateQ = makeQ(1)
|
|
29
|
+
let waitingGlobal = 0
|
|
30
|
+
const QUEUE_FULL = Symbol('queue_full')
|
|
20
31
|
api.events.multi({
|
|
21
|
-
async attemptingLogin({ ctx }) {
|
|
32
|
+
async attemptingLogin({ ctx, username }) {
|
|
22
33
|
const { ip } = ctx
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
const account = getAccountKey(username)
|
|
35
|
+
const ipRec = getRecord(byIp, ip)
|
|
36
|
+
const accountRec = getRecord(byAccount, account)
|
|
37
|
+
let admitted = false
|
|
38
|
+
try {
|
|
39
|
+
await runGate(() => {
|
|
40
|
+
if (ipRec.waiting >= api.getConfig('maxQueuePerIp')
|
|
41
|
+
|| accountRec.waiting >= api.getConfig('maxQueuePerAccount')
|
|
42
|
+
|| waitingGlobal >= api.getConfig('maxQueueGlobal'))
|
|
43
|
+
throw QUEUE_FULL
|
|
44
|
+
// reserve all buckets atomically so we never exceed limits due to parallel requests
|
|
45
|
+
ipRec.waiting++
|
|
46
|
+
accountRec.waiting++
|
|
47
|
+
waitingGlobal++
|
|
48
|
+
admitted = true
|
|
49
|
+
})
|
|
50
|
+
// serialize waits per ip and per account so parallel bursts can't consume the same penalty window
|
|
51
|
+
await runInLane(getLane(laneByIp, ip), () =>
|
|
52
|
+
runInLane(getLane(laneByAccount, account), async () => {
|
|
53
|
+
const now = Date.now()
|
|
54
|
+
const wait = Math.max(0, ipRec.next - now, accountRec.next - now)
|
|
55
|
+
if (wait <= 0) return
|
|
56
|
+
api.log('delaying', ip, 'for', Math.round(wait / 1000))
|
|
57
|
+
ctx.set('x-anti-brute-force', wait)
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, wait))
|
|
59
|
+
}))
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
if (e === QUEUE_FULL) {
|
|
63
|
+
ctx.status = 429
|
|
64
|
+
return api.events.stop
|
|
65
|
+
}
|
|
66
|
+
throw e
|
|
32
67
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
68
|
+
finally {
|
|
69
|
+
if (admitted) {
|
|
70
|
+
ipRec.waiting--
|
|
71
|
+
accountRec.waiting--
|
|
72
|
+
waitingGlobal--
|
|
73
|
+
}
|
|
74
|
+
armCleanup(byIp, ip, ipRec)
|
|
75
|
+
armCleanup(byAccount, account, accountRec)
|
|
76
|
+
dropLaneIfIdle(laneByIp, ip)
|
|
77
|
+
dropLaneIfIdle(laneByAccount, account)
|
|
38
78
|
}
|
|
39
|
-
|
|
79
|
+
},
|
|
80
|
+
failedLogin({ ctx, username }) {
|
|
81
|
+
const { ip } = ctx
|
|
82
|
+
const account = getAccountKey(username)
|
|
83
|
+
const now = Date.now()
|
|
84
|
+
const ipRec = getRecord(byIp, ip)
|
|
85
|
+
const accountRec = getRecord(byAccount, account)
|
|
86
|
+
const ipAttempts = increasePenalty(ipRec, now)
|
|
87
|
+
increasePenalty(accountRec, now)
|
|
88
|
+
if (ipAttempts > api.getConfig('blockAfter') && !isLocalHost(ctx) && !isExcluded(ip)) {
|
|
89
|
+
const hours = api.getConfig('blockForHours')
|
|
90
|
+
api.addBlock({ ip, comment: "From antibrute plugin", expire: hours ? new Date(now + hours * HOUR) : undefined })
|
|
91
|
+
}
|
|
92
|
+
armCleanup(byIp, ip, ipRec)
|
|
93
|
+
armCleanup(byAccount, account, accountRec)
|
|
94
|
+
dropLaneIfIdle(laneByIp, ip)
|
|
95
|
+
dropLaneIfIdle(laneByAccount, account)
|
|
40
96
|
},
|
|
41
97
|
login(ctx) {
|
|
42
|
-
if (ctx.state.account)
|
|
43
|
-
|
|
98
|
+
if (ctx.state.account) {
|
|
99
|
+
const { ip } = ctx
|
|
100
|
+
const account = getAccountKey(ctx.state.account.username)
|
|
101
|
+
resetRecord(byIp, ip)
|
|
102
|
+
resetRecord(byAccount, account)
|
|
103
|
+
dropLaneIfIdle(laneByIp, ip)
|
|
104
|
+
dropLaneIfIdle(laneByAccount, account)
|
|
105
|
+
}
|
|
44
106
|
}
|
|
45
107
|
})
|
|
46
108
|
|
|
109
|
+
function getRecord(container, key) {
|
|
110
|
+
return container[key] ||= { failures: 0, next: 0, waiting: 0 }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function increasePenalty(rec, now) {
|
|
114
|
+
const attempts = ++rec.failures
|
|
115
|
+
const max = api.getConfig('max') * 1000
|
|
116
|
+
const delay = Math.min(max, attempts * api.getConfig('increment') * 1000)
|
|
117
|
+
rec.next = Math.max(now, rec.next) + delay
|
|
118
|
+
return attempts
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function armCleanup(records, key, rec) {
|
|
122
|
+
clearTimeout(rec.timer)
|
|
123
|
+
rec.timer = setTimeout(() => {
|
|
124
|
+
// keep records while there are in-flight admissions, otherwise later releases may touch deleted state
|
|
125
|
+
if (rec.waiting)
|
|
126
|
+
return armCleanup(records, key, rec)
|
|
127
|
+
delete records[key]
|
|
128
|
+
}, 24 * HOUR) // no memory leak
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function runGate(job) {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
gateQ.add(async () => {
|
|
134
|
+
try { resolve(await job()) }
|
|
135
|
+
catch (e) { reject(e) }
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getLane(container, key) {
|
|
141
|
+
return container[key] ||= makeQ(1)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function runInLane(q, job) {
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
q.add(async () => {
|
|
147
|
+
try { resolve(await job()) }
|
|
148
|
+
catch (e) { reject(e) }
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function dropLaneIfIdle(container, key) {
|
|
154
|
+
const q = container[key]
|
|
155
|
+
if (q?.isWorking() || q?.queueSize()) return
|
|
156
|
+
delete container[key]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function resetRecord(container, key) {
|
|
160
|
+
const rec = container[key]
|
|
161
|
+
if (!rec) return
|
|
162
|
+
if (rec.waiting) {
|
|
163
|
+
// successful login must clear penalties without dropping admission counters still needed by concurrent requests
|
|
164
|
+
rec.failures = 0
|
|
165
|
+
rec.next = 0
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
delete container[key]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getAccountKey(username) {
|
|
172
|
+
// fold unknown usernames together to avoid unbounded memory growth from random names
|
|
173
|
+
if (!username || !api.getAccount(String(username)))
|
|
174
|
+
return UNKNOWN_ACCOUNT
|
|
175
|
+
return String(username).toLowerCase()
|
|
176
|
+
}
|
|
177
|
+
|
|
47
178
|
function isExcluded(ip) {
|
|
48
179
|
const mask = api.getConfig('exclude')
|
|
49
180
|
if (!mask) return false
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
HFS.h(Uploader, entry))
|
|
7
7
|
|
|
8
8
|
function Uploader({ uri }) {
|
|
9
|
-
const { data } = HFS.useBatch(getDetails, uri)
|
|
9
|
+
const { data } = HFS.useBatch(getDetails, uri, { depend: HFS.state.isAdmin })
|
|
10
10
|
const text = React.useMemo(() => {
|
|
11
11
|
if (!data || data === true) return ''
|
|
12
12
|
const { upload: x } = data
|
package/src/acme.js
CHANGED
|
@@ -32,17 +32,21 @@ const acmeMiddleware = (ctx, next) => {
|
|
|
32
32
|
return next();
|
|
33
33
|
};
|
|
34
34
|
exports.acmeMiddleware = acmeMiddleware;
|
|
35
|
-
const TEMP_MAP =
|
|
35
|
+
const TEMP_MAP = (0, nat_1.upnpMappingParam)(80, 80, 'hfs temporary', 5000); // from my tests (zyxel VMG8825), lower values won't make a working mapping
|
|
36
|
+
// remove temporary port mapping, if any is left from previous execution
|
|
36
37
|
(0, misc_1.repeat)(misc_1.MINUTE, async (stop) => {
|
|
37
|
-
await nat_1.
|
|
38
|
-
|
|
38
|
+
if (!await nat_1.upnpEnabled.getWhenReady())
|
|
39
|
+
return stop();
|
|
40
|
+
const client = (0, nat_1.getUpnpClient)();
|
|
41
|
+
await client.getGateway(); // without this, the next call will break upnp support
|
|
42
|
+
const res = await client.getMappings();
|
|
39
43
|
const leftover = res.find(x => x.description === TEMP_MAP.description); // in case the process is interrupted
|
|
40
44
|
if (!leftover)
|
|
41
45
|
return void stop(); // we are good
|
|
42
46
|
if (acmeOngoing)
|
|
43
47
|
return; // it doesn't count, as we are in the middle of something. Retry later
|
|
44
48
|
stop();
|
|
45
|
-
return
|
|
49
|
+
return client.removeMapping(TEMP_MAP);
|
|
46
50
|
});
|
|
47
51
|
async function generateSSLCert(domain, email, altNames) {
|
|
48
52
|
// will answer the challenge through our koa app (if on port 80) or must we spawn a dedicated server?
|
|
@@ -63,7 +67,7 @@ async function generateSSLCert(domain, email, altNames) {
|
|
|
63
67
|
let check = await (0, selfCheck_1.selfCheck)(checkUrl); // some check services may not consider the domain, but we already verified that
|
|
64
68
|
if (check?.success === false && nat.upnp && !nat.mapped80) {
|
|
65
69
|
console.debug("Setting temporary port forward");
|
|
66
|
-
tempMap = await (0, misc_1.haveTimeout)(10_000, nat_1.
|
|
70
|
+
tempMap = await (0, misc_1.haveTimeout)(10_000, (0, nat_1.getUpnpClient)().createMapping(TEMP_MAP)).catch(() => { });
|
|
67
71
|
check = await (0, selfCheck_1.selfCheck)(checkUrl); // repeat test
|
|
68
72
|
}
|
|
69
73
|
//if (!check) throw new ApiError(HTTP_FAILED_DEPENDENCY, "couldn't test port 80")
|
|
@@ -88,9 +92,9 @@ async function generateSSLCert(domain, email, altNames) {
|
|
|
88
92
|
return { key, cert };
|
|
89
93
|
}
|
|
90
94
|
finally {
|
|
91
|
-
if (tempMap) {
|
|
95
|
+
if (tempMap && nat_1.upnpEnabled.get()) {
|
|
92
96
|
console.debug("Removing temporary port forward");
|
|
93
|
-
nat_1.
|
|
97
|
+
(0, nat_1.getUpnpClient)().removeMapping(TEMP_MAP).catch(() => { }); // clean after ourselves
|
|
94
98
|
}
|
|
95
99
|
acmeOngoing = false;
|
|
96
100
|
if (tempSrv)
|
package/src/api.accounts.js
CHANGED
|
@@ -16,9 +16,9 @@ function prepareAccount(ac) {
|
|
|
16
16
|
...lodash_1.default.omit(ac, ['password', 'hashed_password', 'srp']),
|
|
17
17
|
username: ac.username, // omit won't copy it because it's a hidden prop
|
|
18
18
|
hasPassword: (0, perm_1.accountHasPassword)(ac),
|
|
19
|
-
isGroup: !
|
|
19
|
+
isGroup: !(0, perm_1.accountHasLoginMethod)(ac),
|
|
20
20
|
adminActualAccess: (0, perm_1.accountCanLoginAdmin)(ac),
|
|
21
|
-
canLogin: (0, perm_1.
|
|
21
|
+
canLogin: (0, perm_1.accountHasLoginMethod)(ac) ? (0, perm_1.accountCanLogin)(ac) : undefined,
|
|
22
22
|
canChangePassword: (0, perm_1.accountCanChangePassword)(ac),
|
|
23
23
|
invalidated: auth_1.invalidateSessionBefore.get(ac.username),
|
|
24
24
|
directMembers: Object.values(perm_1.accounts.get()).filter(a => a.belongs?.includes(ac.username)).map(x => x.username),
|
|
@@ -33,7 +33,7 @@ function prepareAccount(ac) {
|
|
|
33
33
|
})
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
const ALLOWED_KEYS = ['admin', 'allow_net', 'belongs', 'days_to_live', 'disable_password_change',
|
|
36
|
+
const ALLOWED_KEYS = ['admin', 'allow_net', 'auto_login_net', 'belongs', 'days_to_live', 'disable_password_change',
|
|
37
37
|
'disabled', 'expire', 'ignore_limits', 'notes', 'password', 'redirect', 'require_password_change', 'username'];
|
|
38
38
|
exports.default = {
|
|
39
39
|
get_usernames() {
|
package/src/api.auth.js
CHANGED
|
@@ -18,10 +18,12 @@ const ongoingLogins = {}; // store data that doesn't fit session object
|
|
|
18
18
|
const keepSessionAlive = (0, config_1.defineConfig)('keep_session_alive', true);
|
|
19
19
|
const refresh_session = async ({}, ctx) => {
|
|
20
20
|
const username = (0, auth_1.getCurrentUsername)(ctx);
|
|
21
|
+
const isAdmin = (0, adminApis_1.ctxAdminAccess)(ctx) || undefined;
|
|
21
22
|
return !ctx.session ? new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR) : {
|
|
22
23
|
username,
|
|
23
24
|
expandedUsername: Array.from((0, perm_1.expandUsername)(username)),
|
|
24
|
-
|
|
25
|
+
isAdmin,
|
|
26
|
+
adminUrl: isAdmin && ctx.state.revProxyPath + const_1.ADMIN_URI,
|
|
25
27
|
canChangePassword: (0, perm_1.accountCanChangePassword)(ctx.state.account),
|
|
26
28
|
requireChangePassword: ctx.state.account?.require_password_change,
|
|
27
29
|
exp: username && keepSessionAlive.get() ? new Date(Date.now() + middlewares_1.sessionDuration.compiled()) : undefined,
|
package/src/api.get_file_list.js
CHANGED
|
@@ -44,7 +44,7 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
|
|
|
44
44
|
limit = Number(limit);
|
|
45
45
|
const { filterName, filterComment, fileMask, depth } = paramsToFilter(rest);
|
|
46
46
|
const walker = (0, vfs_1.walkNode)(node, { ctx: admin ? undefined : ctx, onlyFolders, onlyFiles, depth });
|
|
47
|
-
const onDirEntryHandlers = (0, plugins_1.mapPlugins)(plug => plug.onDirEntry);
|
|
47
|
+
const onDirEntryHandlers = (0, plugins_1.mapPlugins)((plug, id) => plug.onDirEntry && { id, cb: plug.onDirEntry });
|
|
48
48
|
const can_upload = admin || (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
|
|
49
49
|
const can_delete = admin || (0, vfs_1.hasPermission)(node, 'can_delete', ctx);
|
|
50
50
|
const fakeChild = await (0, vfs_1.applyParentToChild)({ source: 'dummy-file', original: undefined }, node); // used to check permission; simple but can produce false results; 'original' to simulate a non-vfs node
|
|
@@ -74,7 +74,7 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
|
|
|
74
74
|
async function* produceEntries() {
|
|
75
75
|
for await (const sub of walker) {
|
|
76
76
|
let name = (0, vfs_1.getNodeName)(sub);
|
|
77
|
-
name = (0, path_1.basename)(name) || name; // on
|
|
77
|
+
name = (0, path_1.basename)(name) || name; // on Windows, basename('C:') === ''
|
|
78
78
|
if (filterName && !filterName(name) || fileMask && !(0, vfs_1.nodeIsFolder)(sub) && !fileMask(name)
|
|
79
79
|
|| filterComment && !filterComment(await (0, comments_1.getCommentFor)(sub.source) || ''))
|
|
80
80
|
continue;
|
|
@@ -83,15 +83,15 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
|
|
|
83
83
|
continue;
|
|
84
84
|
const cbParams = { entry, ctx, listUri: uri, node: sub };
|
|
85
85
|
try {
|
|
86
|
-
const res = await Promise.all(onDirEntryHandlers.map(cb => cb(cbParams)));
|
|
86
|
+
const res = await Promise.all(onDirEntryHandlers.map(({ id, cb }) => Promise.resolve().then(() => cb(cbParams)).catch(error => { throw { id, error }; })));
|
|
87
87
|
if (res.some(x => x === false))
|
|
88
88
|
continue;
|
|
89
|
-
if ((await events_1.default.emitAsync('dirEntry', cbParams))?.isDefaultPrevented())
|
|
90
|
-
continue;
|
|
91
89
|
}
|
|
92
90
|
catch (e) {
|
|
93
|
-
console.warn(
|
|
91
|
+
console.warn(`Plugin ${e?.id || '?'} is causing problems on onDirEntry:`, e?.error ?? e);
|
|
94
92
|
}
|
|
93
|
+
if ((await events_1.default.emitAsync('dirEntry', cbParams))?.isDefaultPrevented())
|
|
94
|
+
continue;
|
|
95
95
|
if (offset) {
|
|
96
96
|
--offset;
|
|
97
97
|
continue;
|
|
@@ -111,10 +111,14 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
|
|
|
111
111
|
const name = (0, vfs_1.getNodeName)(node);
|
|
112
112
|
const isFolder = (0, vfs_1.nodeIsFolder)(node);
|
|
113
113
|
try {
|
|
114
|
-
const st =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
const [web, comment, st] = await Promise.all([
|
|
115
|
+
(0, vfs_1.hasDefaultFile)(node, ctx).then(x => x ? true : undefined),
|
|
116
|
+
node.comment ?? (0, comments_1.getCommentFor)(source),
|
|
117
|
+
(0, vfs_1.nodeStats)(node).catch(e => {
|
|
118
|
+
if (!isFolder || !node.children?.length) // folders with virtual children, keep them
|
|
119
|
+
throw e;
|
|
120
|
+
})
|
|
121
|
+
]);
|
|
118
122
|
// permissions of entries are sent as a difference with permissions of parent
|
|
119
123
|
const pl = node.can_list === misc_1.WHO_NO_ONE ? 'l'
|
|
120
124
|
: !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L'
|
|
@@ -136,9 +140,9 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
|
|
|
136
140
|
url,
|
|
137
141
|
target: node.target,
|
|
138
142
|
order: node.order,
|
|
139
|
-
comment
|
|
143
|
+
comment,
|
|
140
144
|
icon: getNodeIcon(node),
|
|
141
|
-
web
|
|
145
|
+
web,
|
|
142
146
|
};
|
|
143
147
|
}
|
|
144
148
|
catch {
|
package/src/api.monitor.js
CHANGED
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.serializeConnection = serializeConnection;
|
|
8
|
+
exports.inferOperation = inferOperation;
|
|
7
9
|
const lodash_1 = __importDefault(require("lodash"));
|
|
8
10
|
const connections_1 = require("./connections");
|
|
9
11
|
const misc_1 = require("./misc");
|
|
@@ -11,6 +13,7 @@ const throttler_1 = require("./throttler");
|
|
|
11
13
|
const auth_1 = require("./auth");
|
|
12
14
|
const SendList_1 = require("./SendList");
|
|
13
15
|
const persistence_1 = require("./persistence");
|
|
16
|
+
const cross_1 = require("./cross");
|
|
14
17
|
exports.default = {
|
|
15
18
|
async disconnect({ ip, port, allButLocalhost }) {
|
|
16
19
|
(0, misc_1.apiAssertTypes)({
|
|
@@ -28,25 +31,21 @@ exports.default = {
|
|
|
28
31
|
get_connections({}, ctx) {
|
|
29
32
|
const list = new SendList_1.SendListReadable({
|
|
30
33
|
diff: true,
|
|
31
|
-
addAtStart: (0, connections_1.getConnections)().map(
|
|
34
|
+
addAtStart: (0, connections_1.getConnections)().map(serializeConnection),
|
|
32
35
|
});
|
|
33
36
|
list.props({ you: ctx.ip });
|
|
34
37
|
return list.events(ctx, {
|
|
35
38
|
connection(conn) {
|
|
36
|
-
if (ignore(conn))
|
|
37
|
-
return;
|
|
38
39
|
list.add(serializeConnection(conn));
|
|
39
40
|
},
|
|
40
41
|
connectionClosed(conn) {
|
|
41
|
-
if (ignore(conn))
|
|
42
|
-
return;
|
|
43
42
|
list.remove(getConnAddress(conn));
|
|
44
43
|
},
|
|
45
44
|
connectionNewIp(conn, oldIp, newIp) {
|
|
46
45
|
list.update(getConnAddress(conn, oldIp), { ip: newIp });
|
|
47
46
|
},
|
|
48
47
|
connectionUpdated(conn, change) {
|
|
49
|
-
if (conn.socket.closed ||
|
|
48
|
+
if (conn.socket.closed || lodash_1.default.isEmpty(change))
|
|
50
49
|
return;
|
|
51
50
|
if (change.ctx) {
|
|
52
51
|
Object.assign(change, fromCtx(change.ctx));
|
|
@@ -55,48 +54,16 @@ exports.default = {
|
|
|
55
54
|
list.update(getConnAddress(conn), change);
|
|
56
55
|
},
|
|
57
56
|
});
|
|
58
|
-
function serializeConnection(conn) {
|
|
59
|
-
const { socket, started, secure } = conn;
|
|
60
|
-
return {
|
|
61
|
-
...getConnAddress(conn),
|
|
62
|
-
v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
|
|
63
|
-
got: socket.bytesRead,
|
|
64
|
-
sent: socket.bytesWritten,
|
|
65
|
-
country: conn.country,
|
|
66
|
-
started,
|
|
67
|
-
secure: (secure || undefined), // undefined will save some space once json-ed
|
|
68
|
-
...fromCtx(conn.ctx),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function fromCtx(ctx) {
|
|
72
|
-
if (!ctx)
|
|
73
|
-
return;
|
|
74
|
-
const s = ctx.state; // short alias
|
|
75
|
-
return {
|
|
76
|
-
user: (0, auth_1.getCurrentUsername)(ctx),
|
|
77
|
-
agent: (0, misc_1.shortenAgent)(ctx.get('user-agent')),
|
|
78
|
-
archive: s.archive,
|
|
79
|
-
...s.browsing ? { op: 'browsing', path: (0, misc_1.safeDecodeURIComponent)(s.browsing) }
|
|
80
|
-
: s.uploadPath ? { op: 'upload', path: (0, misc_1.safeDecodeURIComponent)(s.uploadPath) }
|
|
81
|
-
: {
|
|
82
|
-
op: !s.considerAsGui && (ctx.state.archive || ctx.state.vfsNode) ? 'download' : undefined,
|
|
83
|
-
path: (0, misc_1.safeDecodeURIComponent)(ctx.originalUrl),
|
|
84
|
-
},
|
|
85
|
-
opProgress: lodash_1.default.isNumber(s.opProgress) ? lodash_1.default.round(s.opProgress, 3) : undefined,
|
|
86
|
-
opTotal: s.opTotal,
|
|
87
|
-
opOffset: s.opOffset,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
57
|
},
|
|
91
58
|
async *get_connection_stats() {
|
|
92
59
|
while (1) {
|
|
93
|
-
const
|
|
60
|
+
const connections = (0, connections_1.getConnections)();
|
|
94
61
|
yield {
|
|
95
62
|
outSpeedKb: throttler_1.totalOutSpeedKb,
|
|
96
63
|
inSpeedKb: throttler_1.totalInSpeedKb,
|
|
97
64
|
sent_got: [throttler_1.totalSent.get(), throttler_1.totalGot.get(), totalGotSentResetTime.get()],
|
|
98
|
-
connections:
|
|
99
|
-
ips:
|
|
65
|
+
connections: connections.length,
|
|
66
|
+
ips: (0, cross_1.countUniqueBy)(connections, conn => conn.ip),
|
|
100
67
|
};
|
|
101
68
|
await (0, misc_1.wait)(1000);
|
|
102
69
|
}
|
|
@@ -108,8 +75,45 @@ exports.default = {
|
|
|
108
75
|
void persistence_1.storedMap.del(x);
|
|
109
76
|
},
|
|
110
77
|
};
|
|
111
|
-
function
|
|
112
|
-
|
|
78
|
+
function serializeConnection(conn) {
|
|
79
|
+
const { socket, started, secure } = conn;
|
|
80
|
+
return {
|
|
81
|
+
...getConnAddress(conn),
|
|
82
|
+
v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
|
|
83
|
+
// connection fields are request-scoped once transfer tracking starts; socket counters cover earlier snapshots
|
|
84
|
+
got: conn.got || socket.bytesRead,
|
|
85
|
+
sent: conn.sent || socket.bytesWritten,
|
|
86
|
+
outSpeedKb: conn.outSpeedKb,
|
|
87
|
+
inSpeedKb: conn.inSpeedKb,
|
|
88
|
+
country: conn.country,
|
|
89
|
+
started,
|
|
90
|
+
secure: (secure || undefined), // undefined will save some space once json-ed
|
|
91
|
+
...fromCtx(conn.ctx),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function fromCtx(ctx) {
|
|
95
|
+
if (!ctx)
|
|
96
|
+
return;
|
|
97
|
+
return {
|
|
98
|
+
user: (0, auth_1.getCurrentUsername)(ctx),
|
|
99
|
+
agent: (0, misc_1.shortenAgent)(ctx.get('user-agent')),
|
|
100
|
+
...inferOperation(ctx)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function inferOperation(ctx) {
|
|
104
|
+
const s = ctx.state; // short alias
|
|
105
|
+
return {
|
|
106
|
+
archive: s.archive,
|
|
107
|
+
...s.browsing ? { op: 'browsing', path: (0, misc_1.safeDecodeURIComponent)(s.browsing) }
|
|
108
|
+
: s.uploadPath ? { op: 'upload', path: (0, misc_1.safeDecodeURIComponent)(s.uploadPath) }
|
|
109
|
+
: {
|
|
110
|
+
op: !s.considerAsGui && (ctx.state.archive || ctx.state.vfsNode) ? 'download' : undefined,
|
|
111
|
+
path: (0, misc_1.safeDecodeURIComponent)(ctx.originalUrl),
|
|
112
|
+
},
|
|
113
|
+
opProgress: lodash_1.default.isNumber(s.opProgress) ? lodash_1.default.round(s.opProgress, 3) : undefined,
|
|
114
|
+
opTotal: s.opTotal,
|
|
115
|
+
opOffset: s.opOffset,
|
|
116
|
+
};
|
|
113
117
|
}
|
|
114
118
|
function getConnAddress(conn, overrideIp) {
|
|
115
119
|
return {
|
package/src/api.net.js
CHANGED
|
@@ -52,16 +52,17 @@ exports.default = {
|
|
|
52
52
|
return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "no internal port");
|
|
53
53
|
if (externalPort)
|
|
54
54
|
try {
|
|
55
|
-
await nat_1.
|
|
55
|
+
await (0, nat_1.getUpnpClient)().removeMapping({ public: { host: '', port: externalPort } });
|
|
56
56
|
}
|
|
57
57
|
catch (e) {
|
|
58
58
|
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, "removeMapping failed: " + String(e));
|
|
59
59
|
}
|
|
60
|
-
if (external)
|
|
61
|
-
await nat_1.
|
|
60
|
+
if (external)
|
|
61
|
+
await (0, nat_1.getUpnpClient)().createMapping((0, nat_1.upnpMappingParam)(internal || internalPort, external))
|
|
62
62
|
.catch(res => {
|
|
63
63
|
throw new apiMiddleware_1.ApiError(res.errorCode || const_1.HTTP_SERVER_ERROR, res.errorCode === 718 ? "Port not available" : res.errorDescription || res.message || "unknown error");
|
|
64
64
|
});
|
|
65
|
+
nat_1.mappedPort.set(external || 0); // remember only successful HFS mappings
|
|
65
66
|
return {};
|
|
66
67
|
},
|
|
67
68
|
async self_check({ url }) {
|
package/src/api.vfs.js
CHANGED
|
@@ -225,7 +225,7 @@ exports.default = {
|
|
|
225
225
|
for (const k of ['*', 'Directory']) {
|
|
226
226
|
await (0, util_os_1.reg)('add', WINDOWS_REG_KEY.replace('*', k), '/ve', '/f', '/d', 'Add to HFS (new)');
|
|
227
227
|
await (0, util_os_1.reg)('add', WINDOWS_REG_KEY.replace('*', k), '/v', 'icon', '/f', '/d', const_1.IS_BINARY ? process.execPath : const_1.APP_PATH + '\\hfs.ico');
|
|
228
|
-
await (0, util_os_1.reg)('add', WINDOWS_REG_KEY.replace('*', k) + '\\command', '/ve', '/f', '/d', `powershell -WindowStyle Hidden -Command "
|
|
228
|
+
await (0, util_os_1.reg)('add', WINDOWS_REG_KEY.replace('*', k) + '\\command', '/ve', '/f', '/d', `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -Command "
|
|
229
229
|
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12;
|
|
230
230
|
$wsh = New-Object -ComObject Wscript.Shell;
|
|
231
231
|
$j = @{parent=@'\n${parent}\n'@; source=@'\n%1\n'@} | ConvertTo-Json -Compress
|
package/src/commands.js
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
38
|
};
|
|
@@ -17,7 +50,8 @@ const plugins_1 = require("./plugins");
|
|
|
17
50
|
const fileAttr_1 = require("./fileAttr");
|
|
18
51
|
const github_1 = require("./github");
|
|
19
52
|
const cross_1 = require("./cross");
|
|
20
|
-
const api_monitor_1 =
|
|
53
|
+
const api_monitor_1 = __importStar(require("./api.monitor"));
|
|
54
|
+
const connections_1 = require("./connections");
|
|
21
55
|
const argv_1 = require("./argv");
|
|
22
56
|
const listen_2 = require("./listen");
|
|
23
57
|
let debugEnabled = argv_1.argv.debug || process.env.HFS_DEBUG || const_1.DEV;
|
|
@@ -191,11 +225,30 @@ const commands = {
|
|
|
191
225
|
params: '',
|
|
192
226
|
cb: fileAttr_1.purgeFileAttr,
|
|
193
227
|
},
|
|
228
|
+
transfers: {
|
|
229
|
+
params: '',
|
|
230
|
+
cb() {
|
|
231
|
+
const transfers = (0, connections_1.getConnections)().map(api_monitor_1.serializeConnection).filter(x => x.op === 'upload' || x.op === 'download');
|
|
232
|
+
if (!transfers.length)
|
|
233
|
+
return console.log("No ongoing uploads/downloads");
|
|
234
|
+
console.table(transfers.map(x => ({
|
|
235
|
+
type: x.op,
|
|
236
|
+
progress: (0, cross_1.with_)(x.opProgress ?? x.opOffset, v => v == null ? '' : (0, cross_1.formatPerc)(v)),
|
|
237
|
+
transferred: (0, cross_1.formatBytes)(Math.max(x.sent || 0, x.got || 0)),
|
|
238
|
+
total: x.opTotal == null ? '' : (0, cross_1.formatBytes)(x.opTotal),
|
|
239
|
+
speed: (0, cross_1.formatSpeed)(Math.max(x.outSpeedKb || 0, x.inSpeedKb || 0) * 1000),
|
|
240
|
+
user: x.user,
|
|
241
|
+
path: x.path,
|
|
242
|
+
})));
|
|
243
|
+
}
|
|
244
|
+
},
|
|
194
245
|
status: {
|
|
195
246
|
params: '',
|
|
196
247
|
async cb() {
|
|
197
248
|
const ports = await (0, listen_2.getServerStatus)(false);
|
|
198
249
|
console.log(lodash_1.default.map(ports, (x, k) => `${k.toUpperCase()} ${x.configuredPort < 0 ? "disabled" : x.listening ? `on port ${x.port}` : (x.error || "not working")}`).join(" – "));
|
|
250
|
+
const operations = lodash_1.default.countBy((0, connections_1.getConnections)(), x => x.ctx && (0, api_monitor_1.inferOperation)(x.ctx).op);
|
|
251
|
+
console.log(`Active downloads ↑ ${operations.download || 0} – uploads ↓ ${operations.upload || 0}`);
|
|
199
252
|
const conn = (await api_monitor_1.default.get_connection_stats().next()).value;
|
|
200
253
|
if (conn) {
|
|
201
254
|
const { sent_got: sg } = conn;
|