hfs 0.26.8 → 0.27.2

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 (163) hide show
  1. package/README.md +15 -2
  2. package/admin/assets/index-509bb1d6.js +415 -0
  3. package/admin/assets/index-60a380a7.css +1 -0
  4. package/admin/assets/sha512-738f0943.js +8 -0
  5. package/admin/index.html +3 -1
  6. package/admin/{public/logo.svg → logo.svg} +0 -0
  7. package/frontend/assets/index-6e178dfd.css +1 -0
  8. package/frontend/assets/index-aea7654e.js +85 -0
  9. package/frontend/assets/sha512-bf915587.js +8 -0
  10. package/frontend/{public/fontello.css → fontello.css} +0 -0
  11. package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
  12. package/frontend/index.html +4 -2
  13. package/package.json +2 -6
  14. package/plugins/vhosting/plugin.js +23 -20
  15. package/src/QuickZipStream.js +285 -0
  16. package/src/ThrottledStream.js +93 -0
  17. package/src/adminApis.js +169 -0
  18. package/src/api.accounts.js +59 -0
  19. package/src/api.auth.js +128 -0
  20. package/src/api.file_list.js +110 -0
  21. package/src/api.helpers.js +32 -0
  22. package/src/api.monitor.js +104 -0
  23. package/src/api.plugins.js +128 -0
  24. package/src/api.vfs.js +167 -0
  25. package/src/apiMiddleware.js +123 -0
  26. package/src/block.js +34 -0
  27. package/src/commands.js +125 -0
  28. package/src/config.js +168 -0
  29. package/src/connections.js +57 -0
  30. package/src/const.js +94 -0
  31. package/src/crypt.js +21 -0
  32. package/src/debounceAsync.js +49 -0
  33. package/src/events.js +9 -0
  34. package/src/frontEndApis.js +38 -0
  35. package/src/github.js +104 -0
  36. package/src/index.js +57 -0
  37. package/src/listen.js +235 -0
  38. package/src/log.js +137 -0
  39. package/src/middlewares.js +195 -0
  40. package/src/misc.js +160 -0
  41. package/src/pbkdf2.js +74 -0
  42. package/src/perm.js +183 -0
  43. package/src/plugins.js +343 -0
  44. package/src/serveFile.js +105 -0
  45. package/src/serveGuiFiles.js +113 -0
  46. package/src/sse.js +30 -0
  47. package/src/throttler.js +91 -0
  48. package/src/update.js +70 -0
  49. package/src/util-files.js +163 -0
  50. package/src/util-generators.js +31 -0
  51. package/src/util-http.js +32 -0
  52. package/src/vfs.js +232 -0
  53. package/src/watchLoad.js +73 -0
  54. package/src/zip.js +73 -0
  55. package/admin/.DS_Store +0 -0
  56. package/admin/.eslintrc +0 -8
  57. package/admin/.gitignore +0 -23
  58. package/admin/package.json +0 -67
  59. package/admin/src/AccountForm.ts +0 -92
  60. package/admin/src/AccountsPage.ts +0 -143
  61. package/admin/src/App.ts +0 -83
  62. package/admin/src/ArrayField.ts +0 -84
  63. package/admin/src/ConfigPage.ts +0 -279
  64. package/admin/src/FileField.ts +0 -52
  65. package/admin/src/FileForm.ts +0 -148
  66. package/admin/src/FilePicker.ts +0 -166
  67. package/admin/src/HomePage.ts +0 -96
  68. package/admin/src/InstalledPlugins.ts +0 -158
  69. package/admin/src/LoginRequired.ts +0 -75
  70. package/admin/src/LogoutPage.ts +0 -27
  71. package/admin/src/LogsPage.ts +0 -75
  72. package/admin/src/MainMenu.ts +0 -74
  73. package/admin/src/MenuButton.ts +0 -38
  74. package/admin/src/MonitorPage.ts +0 -200
  75. package/admin/src/OnlinePlugins.ts +0 -101
  76. package/admin/src/PermField.ts +0 -80
  77. package/admin/src/PluginsPage.ts +0 -27
  78. package/admin/src/VfsMenuBar.ts +0 -58
  79. package/admin/src/VfsPage.ts +0 -124
  80. package/admin/src/VfsTree.ts +0 -95
  81. package/admin/src/addFiles.ts +0 -59
  82. package/admin/src/api.ts +0 -246
  83. package/admin/src/dialog.ts +0 -203
  84. package/admin/src/index.css +0 -21
  85. package/admin/src/index.ts +0 -10
  86. package/admin/src/md.ts +0 -31
  87. package/admin/src/misc.ts +0 -141
  88. package/admin/src/react-app-env.d.ts +0 -1
  89. package/admin/src/reportWebVitals.ts +0 -15
  90. package/admin/src/setupTests.ts +0 -5
  91. package/admin/src/state.ts +0 -40
  92. package/admin/src/theme.ts +0 -37
  93. package/admin/tsconfig.json +0 -26
  94. package/admin/vite.config.ts +0 -32
  95. package/frontend/.DS_Store +0 -0
  96. package/frontend/.eslintrc +0 -8
  97. package/frontend/.gitignore +0 -23
  98. package/frontend/package.json +0 -51
  99. package/frontend/src/App.ts +0 -25
  100. package/frontend/src/Breadcrumbs.ts +0 -43
  101. package/frontend/src/BrowseFiles.ts +0 -141
  102. package/frontend/src/Head.ts +0 -45
  103. package/frontend/src/UserPanel.ts +0 -52
  104. package/frontend/src/api.ts +0 -78
  105. package/frontend/src/components.ts +0 -54
  106. package/frontend/src/dialog.css +0 -76
  107. package/frontend/src/dialog.ts +0 -105
  108. package/frontend/src/icons.ts +0 -46
  109. package/frontend/src/index.scss +0 -307
  110. package/frontend/src/index.ts +0 -10
  111. package/frontend/src/login.ts +0 -50
  112. package/frontend/src/menu.ts +0 -188
  113. package/frontend/src/misc.ts +0 -54
  114. package/frontend/src/options.ts +0 -52
  115. package/frontend/src/react-app-env.d.ts +0 -1
  116. package/frontend/src/reportWebVitals.ts +0 -15
  117. package/frontend/src/setupTests.ts +0 -5
  118. package/frontend/src/state.ts +0 -82
  119. package/frontend/src/useAuthorized.ts +0 -17
  120. package/frontend/src/useFetchList.ts +0 -144
  121. package/frontend/src/useTheme.ts +0 -23
  122. package/frontend/tsconfig.json +0 -26
  123. package/frontend/vite.config.ts +0 -21
  124. package/src/QuickZipStream.ts +0 -279
  125. package/src/ThrottledStream.ts +0 -98
  126. package/src/adminApis.ts +0 -161
  127. package/src/api.accounts.ts +0 -78
  128. package/src/api.auth.ts +0 -131
  129. package/src/api.file_list.ts +0 -102
  130. package/src/api.helpers.ts +0 -30
  131. package/src/api.monitor.ts +0 -106
  132. package/src/api.plugins.ts +0 -139
  133. package/src/api.vfs.ts +0 -182
  134. package/src/apiMiddleware.ts +0 -124
  135. package/src/block.ts +0 -35
  136. package/src/commands.ts +0 -122
  137. package/src/config.ts +0 -166
  138. package/src/connections.ts +0 -60
  139. package/src/const.ts +0 -57
  140. package/src/crypt.ts +0 -16
  141. package/src/debounceAsync.ts +0 -51
  142. package/src/events.ts +0 -6
  143. package/src/frontEndApis.ts +0 -17
  144. package/src/github.ts +0 -102
  145. package/src/index.ts +0 -53
  146. package/src/listen.ts +0 -220
  147. package/src/log.ts +0 -128
  148. package/src/middlewares.ts +0 -176
  149. package/src/misc.ts +0 -149
  150. package/src/pbkdf2.ts +0 -83
  151. package/src/perm.ts +0 -194
  152. package/src/plugins.ts +0 -342
  153. package/src/serveFile.ts +0 -104
  154. package/src/serveGuiFiles.ts +0 -95
  155. package/src/sse.ts +0 -29
  156. package/src/throttler.ts +0 -106
  157. package/src/update.ts +0 -67
  158. package/src/util-files.ts +0 -137
  159. package/src/util-generators.ts +0 -29
  160. package/src/util-http.ts +0 -29
  161. package/src/vfs.ts +0 -258
  162. package/src/watchLoad.ts +0 -75
  163. package/src/zip.ts +0 -69
@@ -1,96 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import { createElement as h } from 'react'
4
- import { Box, Button, LinearProgress, Link } from '@mui/material'
5
- import { apiCall, useApi, useApiEx, useApiList } from './api'
6
- import { Dict, dontBotherWithKeys, InLink, objSameKeys, onlyTruthy } from './misc'
7
- import { CheckCircle, Error, Info, Launch, Warning } from '@mui/icons-material'
8
- import md from './md'
9
- import { useSnapState } from './state'
10
- import { confirmDialog } from './dialog'
11
- import { isCertError, isKeyError, makeCertAndSave } from './ConfigPage'
12
- import { VfsNode } from './VfsPage'
13
- import { Account } from './AccountsPage'
14
-
15
- interface ServerStatus { listening: boolean, port: number, error?: string, busy?: string }
16
-
17
- export default function HomePage() {
18
- const SOLUTION_SEP = " — "
19
- const { username } = useSnapState()
20
- const { data: status, reload: reloadStatus, element: statusEl } = useApiEx<Dict<ServerStatus>>('get_status')
21
- const { data: vfs } = useApiEx<{ root?: VfsNode }>('get_vfs')
22
- const [account] = useApi<Account>(username && 'get_account')
23
- const { data: cfg, reload: reloadCfg } = useApiEx('get_config', { only: ['https_port', 'cert', 'private_key', 'proxies', 'ignore_proxies'] })
24
- const { list: plugins } = useApiList('get_plugins')
25
- if (statusEl || !status)
26
- return statusEl
27
- const { http, https } = status
28
- const goSecure = !http?.listening && https?.listening ? 's' : ''
29
- const srv = goSecure ? https : (http?.listening && http)
30
- const href = srv && `http${goSecure}://`+window.location.hostname + (srv.port === (goSecure ? 443 : 80) ? '' : ':'+srv.port)
31
- const errorMap = objSameKeys(status, v =>
32
- v.busy ? [`port ${v.port} already used by ${v.busy}${SOLUTION_SEP}choose a `, cfgLink('different port'), ` or stop ${v.busy}`]
33
- : v.error )
34
- const errors = errorMap && onlyTruthy(Object.entries(errorMap).map(([k,v]) =>
35
- v && [md(`Protocol _${k}_ cannot work: `), v,
36
- (isCertError(v) || isKeyError(v)) && [
37
- SOLUTION_SEP, h(Link, { sx: { cursor: 'pointer' }, onClick() { makeCertAndSave().then(reloadCfg).then(reloadStatus) } }, "make one"),
38
- " or ", SOLUTION_SEP, cfgLink("provide adequate files")
39
- ]]))
40
- return h(Box, { display:'flex', gap: 2, flexDirection:'column' },
41
- username && entry('', "Welcome "+username),
42
- errors.length ? dontBotherWithKeys(errors.map(msg => entry('error', dontBotherWithKeys(msg))))
43
- : entry('success', "Server is working"),
44
- !vfs ? h(LinearProgress)
45
- : !vfs.root?.children?.length && !vfs.root?.source ? entry('warning', "You have no files shared", SOLUTION_SEP, fsLink("add some"))
46
- : entry('', md("Here you manage your server. There is a _separated_ interface to access your shared files: "),
47
- h(Link, { target:'frontend', href: '/' }, "Frontend interface", h(Launch, { sx: { verticalAlign: 'sub', ml: '.2em' } }))),
48
- !href && entry('warning', "Frontend unreachable: ",
49
- ['http','https'].map(k => k + " " + (errorMap[k] ? "is in error" : "is off")).join(', '),
50
- !errors.length && [ SOLUTION_SEP, cfgLink("switch http or https on") ]
51
- ),
52
- plugins.find(x => x.badApi) && entry('warning', "Some plugins may be incompatible"),
53
- !account?.adminActualAccess && entry('', md("You are accessing on _localhost_ where permission is not required"),
54
- SOLUTION_SEP, h(InLink, { to:'accounts' }, "give admin access to an account to be able to access from other computers") ),
55
- proxyWarning(cfg, status) && entry('warning', "A proxy was detected but none is configured",
56
- SOLUTION_SEP, cfgLink("set the number of proxies"),
57
- SOLUTION_SEP, "unless you are sure you can ", h(Button, {
58
- async onClick() {
59
- if (await confirmDialog("Go on only if you know what you are doing")
60
- && await apiCall('set_config', { values: { ignore_proxies: true } }))
61
- reloadCfg()
62
- }
63
- }, "ignore this warning")),
64
- status.frpDetected && entry('warning', `FRP is detected. It should not be used with "type = tcp" with HFS. Possible solutions are`,
65
- h('ol',{},
66
- h('li',{}, `configure FRP with type=http (best solution)`),
67
- h('li',{}, md(`configure FRP to connect to HFS _not_ with 127.0.0.1 (safe, but you won't see users' IPs)`)),
68
- h('li',{}, `disable "admin access for localhost" in HFS (safe, but you won't see users' IPs)`),
69
- ))
70
- )
71
- }
72
-
73
- type Color = '' | 'success' | 'warning' | 'error'
74
-
75
- function entry(color: Color, ...content: any[]) {
76
- return h(Box, {
77
- fontSize: 'x-large',
78
- color: th => color && th.palette[color]?.main,
79
- },
80
- h(({ success: CheckCircle, info: Info, '': Info, warning: Warning, error: Error })[color], {
81
- sx: { mb: '-3px', mr: 1 }
82
- }),
83
- ...content)
84
- }
85
-
86
- function fsLink(text=`File System page`) {
87
- return h(InLink, { to:'fs' }, text)
88
- }
89
-
90
- function cfgLink(text=`Configuration page`) {
91
- return h(InLink, { to:'configuration' }, text)
92
- }
93
-
94
- export function proxyWarning(cfg: any, status: any) {
95
- return cfg && !cfg.proxies && !cfg.ignore_proxies && status?.proxyDetected
96
- }
@@ -1,158 +0,0 @@
1
- import { apiCall, useApiList } from './api'
2
- import { createElement as h, Fragment } from 'react'
3
- import { Alert, Box, Tooltip } from '@mui/material'
4
- import { DataGrid } from '@mui/x-data-grid'
5
- import { Delete, Error, GitHub, PlayCircle, Settings, StopCircle, Upgrade } from '@mui/icons-material'
6
- import { IconBtn, xlate } from './misc'
7
- import { formDialog, toast } from './dialog'
8
- import _ from 'lodash'
9
- import { BoolField, Field, MultiSelectField, NumberField, SelectField, StringField } from '@hfs/mui-grid-form'
10
- import { ArrayField } from './ArrayField'
11
- import FileField from './FileField'
12
-
13
- export default function InstalledPlugins({ updates }: { updates?: true }) {
14
- const { list, setList, error, initializing } = useApiList(updates ? 'get_plugin_updates' : 'get_plugins')
15
- if (error)
16
- return showError(error)
17
- return h(DataGrid, {
18
- rows: list.length ? list : [], // workaround for DataGrid bug causing 'no rows' message to be not displayed after 'loading' was also used
19
- loading: initializing,
20
- disableColumnSelector: true,
21
- disableColumnMenu: true,
22
- columnVisibilityModel: {
23
- started: !updates,
24
- },
25
- localeText: updates && { noRowsLabel: "No updates available. Only online plugins are checked." },
26
- columns: [
27
- {
28
- field: 'id',
29
- headerName: "name",
30
- flex: .3,
31
- minWidth: 150,
32
- renderCell({ row, value }) {
33
- return h(Fragment, {},
34
- value,
35
- typeof row.badApi === 'string' && h(Tooltip, { title: row.badApi, children: h(Error, { color: 'warning', sx: { ml: 1 } }) }),
36
- repoLink(row.repo),
37
- )
38
- }
39
- },
40
- {
41
- field: 'version',
42
- width: 70,
43
- },
44
- {
45
- field: 'description',
46
- flex: 1,
47
- },
48
- {
49
- field: "actions",
50
- width: 120,
51
- align: 'center',
52
- headerAlign: 'center',
53
- hideSortIcons: true,
54
- disableColumnMenu: true,
55
- renderCell({ row }) {
56
- const { config, id } = row
57
- if (updates)
58
- return h(UpdateButton, { id, then: () => setList(list.filter(x => x.id !== id)) })
59
- return h('div', {},
60
- h(IconBtn, row.started ? {
61
- icon: StopCircle,
62
- title: h(Box, {}, `Stop ${id}`, h('br'), `Started ` + new Date(row.started as string).toLocaleString()),
63
- color: 'success',
64
- onClick: () =>
65
- apiCall('set_plugin', { id, enabled: false }).then(() =>
66
- toast("Plugin is stopping", h(StopCircle, { color: 'warning' })))
67
- } : {
68
- icon: PlayCircle,
69
- title: `Start ${id}`,
70
- onClick: () => startPlugin(id),
71
- }),
72
- h(IconBtn, {
73
- icon: Settings,
74
- title: "Configuration",
75
- disabled: !config && "No configuration available for this plugin",
76
- async onClick() {
77
- const pl = await apiCall('get_plugin', { id })
78
- const values = await formDialog({
79
- title: `${id} configuration`,
80
- fields: [ h(Box, {}, row.description), ...makeFields(config) ],
81
- values: pl.config,
82
- ...row.configDialog,
83
- })
84
- if (!values || _.isEqual(pl.config, values)) return
85
- await apiCall('set_plugin', { id, config: values })
86
- toast("Configuration saved")
87
- }
88
- }),
89
- h(IconBtn, {
90
- icon: Delete,
91
- title: "Uninstall",
92
- confirm: "Remove?",
93
- async onClick() {
94
- await apiCall('uninstall_plugin', { id })
95
- toast("Plugin uninstalled")
96
- }
97
- }),
98
- )
99
- }
100
- },
101
- ]
102
- })
103
- }
104
-
105
- function makeFields(config: any) {
106
- return Object.entries(config).map(([k,o]: [string,any]) => {
107
- if (!_.isPlainObject(o))
108
- return o
109
- let { type, defaultValue, fields, $column, $width, ...rest } = o
110
- const comp = (type2comp as any)[type] as Field<any> | undefined
111
- if (comp === ArrayField)
112
- fields = makeFields(fields)
113
- if (defaultValue !== undefined && type === 'boolean')
114
- rest.placeholder = `Default value is ${JSON.stringify(defaultValue)}`
115
- return { k, comp, fields, ...rest }
116
- })
117
- }
118
-
119
- const type2comp = {
120
- string: StringField,
121
- number: NumberField,
122
- boolean: BoolField,
123
- select: SelectField,
124
- multiselect: MultiSelectField,
125
- array: ArrayField,
126
- real_path: FileField,
127
- }
128
-
129
- export function repoLink(repo?: string) {
130
- return repo && h(IconBtn, {
131
- icon: GitHub,
132
- title: "Open web page",
133
- link: 'https://github.com/' + repo,
134
- })
135
- }
136
-
137
- export function showError(error: any) {
138
- return h(Alert, { severity: 'error' }, xlate(error, {
139
- ENOTFOUND: "Couldn't reach github.com"
140
- }))
141
- }
142
-
143
- export function UpdateButton({ id, then }: { id: string, then: (id:string)=>void }) {
144
- return h(IconBtn, {
145
- icon: Upgrade,
146
- title: "Update",
147
- async onClick() {
148
- await apiCall('update_plugin', { id })
149
- then?.(id)
150
- toast("Plugin updated")
151
- }
152
- })
153
- }
154
-
155
- export function startPlugin(id: string) {
156
- return apiCall('set_plugin', { id, enabled: true }).then(() =>
157
- toast("Plugin is starting", h(PlayCircle, { color: 'success' })))
158
- }
@@ -1,75 +0,0 @@
1
- import { state, useSnapState } from './state'
2
- import { createElement as h, Fragment, useState } from 'react'
3
- import { Center } from './misc'
4
- import { Form } from '@hfs/mui-grid-form'
5
- import { apiCall } from './api'
6
- import { srpSequence } from '@hfs/shared'
7
- import { Alert } from '@mui/material'
8
-
9
- export function LoginRequired({ children }: any) {
10
- const { loginRequired } = useSnapState()
11
- if (loginRequired)
12
- return h(LoginForm)
13
- return h(Fragment, {}, children)
14
- }
15
-
16
- function LoginForm() {
17
- const [values, setValues] = useState({ username: '', password: '' })
18
- const [error, setError] = useState('')
19
- return h(Center, {},
20
- h(Form, {
21
- values,
22
- set(v, k) {
23
- setValues({ ...values, [k]: v })
24
- },
25
- fields: [
26
- { k: 'username', autoComplete: 'username', autoFocus: true, required: true },
27
- { k: 'password', type: 'password', autoComplete: 'current-password', required: true },
28
- ],
29
- addToBar: [ error && h(Alert, { severity: 'error', sx: { flex: 1 } }, error) ],
30
- saveOnEnter: true,
31
- save: {
32
- children: "Enter",
33
- startIcon: null,
34
- async onClick() {
35
- try {
36
- setError('')
37
- await login(values.username, values.password)
38
- }
39
- catch(e) {
40
- setError(String(e))
41
- }
42
- }
43
- }
44
- })
45
- )
46
- }
47
-
48
- async function login(username: string, password: string) {
49
- const res = await srpSequence(username, password, apiCall).catch(err => {
50
- throw err?.code === 401 ? "Wrong username or password"
51
- : err === 'trust' ? "Login aborted: server identity cannot be trusted"
52
- : err?.name === 'AbortError' ? "Server didn't respond"
53
- : (err?.message || "Unknown error")
54
- })
55
- if (!res.adminUrl)
56
- throw "This account has no Admin access"
57
-
58
- // login was successful, update state
59
- state.loginRequired = false
60
- sessionRefresher(res)
61
- }
62
-
63
- // @ts-ignore
64
- sessionRefresher(window.SESSION)
65
-
66
- function sessionRefresher(response: any) {
67
- if (!response) return
68
- const { exp, username } = response
69
- state.username = username
70
- if (!username || !exp) return
71
- const delta = new Date(exp).getTime() - Date.now()
72
- const t = Math.min(delta - 30_000, 600_000)
73
- console.debug('session refresh in', Math.round(t/1000))
74
- setTimeout(() => apiCall('refresh_session').then(sessionRefresher), t)
75
- }
@@ -1,27 +0,0 @@
1
- import { createElement as h } from "react"
2
- import { Alert, Box, Button } from '@mui/material'
3
- import { apiCall, useApiEx } from './api'
4
- import { alertDialog } from "./dialog"
5
- import { useSnapState } from './state'
6
-
7
- export default function LogoutPage() {
8
- const { element } = useApiEx('get_config', { only: [] }) // sort of noop, just to get the 'element' part
9
- const { username } = useSnapState()
10
- if (element)
11
- return element
12
- if (!username)
13
- return h(Alert, { severity: 'info' }, "You are not logged in, because authentication is not required on localhost")
14
- return h(Box, { display: 'flex', flexDirection:'column', gap: 2 },
15
- "You are logged in as " + username,
16
- h(Box, {},
17
- h(Button, {
18
- size: 'large',
19
- variant: 'contained',
20
- onClick() {
21
- apiCall('logout').catch(err => // we expect 401
22
- err.code !== 401 && alertDialog(err))
23
- }
24
- }, "Yes, I want to logout")
25
- )
26
- )
27
- }
@@ -1,75 +0,0 @@
1
- import { createElement as h, Fragment, useState } from 'react';
2
- import { Tab, Tabs } from '@mui/material'
3
- import { useApiList } from './api'
4
- import { DataGrid } from '@mui/x-data-grid'
5
- import { formatBytes } from '@hfs/shared'
6
- import { logLabels } from './ConfigPage'
7
- import { typedKeys } from './misc';
8
-
9
- export default function LogsPage() {
10
- const [tab, setTab] = useState(0)
11
- const files = typedKeys(logLabels)
12
- return h(Fragment, {},
13
- h(Tabs, { value: tab, onChange(ev,i){ setTab(i) } },
14
- files.map(f => h(Tab, { label: logLabels[f], key: f })) ),
15
- h(LogFile, { key: tab, file: files[tab] }), // without key, some state is accidentally preserved across files
16
- )
17
- }
18
-
19
- function LogFile({ file }: { file: string }) {
20
- const { list, error, connecting } = useApiList('get_log', { file }, { addId: true })
21
- if (error)
22
- return error
23
- return h(DataGrid, {
24
- loading: connecting,
25
- rows: list as any,
26
- componentsProps: {
27
- pagination: {
28
- showFirstButton: true,
29
- showLastButton: true,
30
- }
31
- },
32
- columns: [
33
- {
34
- field: 'ip',
35
- headerName: "Address",
36
- flex: 1,
37
- minWidth: 100,
38
- maxWidth: 400,
39
- },
40
- {
41
- field: 'user',
42
- headerName: "Username",
43
- flex: 1,
44
- },
45
- {
46
- field: 'ts',
47
- headerName: "Timestamp",
48
- type: 'dateTime',
49
- width: 200,
50
- valueFormatter: ({ value }) => new Date(value as string).toLocaleString()
51
- },
52
- {
53
- field: 'method',
54
- headerName: "Method",
55
- },
56
- {
57
- field: 'status',
58
- headerName: "Code",
59
- type: 'number',
60
- },
61
- {
62
- field: 'length',
63
- headerName: "Size",
64
- type: 'number',
65
- valueFormatter: ({ value }) => formatBytes(value as number)
66
- },
67
- {
68
- field: 'uri',
69
- headerName: "URI",
70
- flex: 2,
71
- minWidth: 100,
72
- },
73
- ]
74
- })
75
- }
@@ -1,74 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import { createElement as h, FC } from 'react';
4
- import { List, ListItemButton, ListItemIcon, ListItemText, Box, Typography } from '@mui/material'
5
- import {
6
- AccountTree,
7
- Extension,
8
- History,
9
- Logout,
10
- ManageAccounts,
11
- Monitor,
12
- Public,
13
- Settings,
14
- SvgIconComponent
15
- } from '@mui/icons-material'
16
- import _ from 'lodash'
17
- import { NavLink } from 'react-router-dom'
18
- import MonitorPage from './MonitorPage'
19
- import ConfigPage from './ConfigPage';
20
- import VfsPage from './VfsPage';
21
- import AccountsPage from './AccountsPage';
22
- import HomePage from './HomePage'
23
- import LogoutPage from './LogoutPage';
24
- import LogsPage from './LogsPage';
25
- import { useApi } from './api'
26
- import PluginsPage from './PluginsPage';
27
-
28
- interface MenuEntry {
29
- path: string
30
- icon: SvgIconComponent
31
- label?: string
32
- title?: string
33
- comp: FC
34
- }
35
-
36
- export const mainMenu: MenuEntry[] = [
37
- { path: '', icon: Public, label: 'Home', title: "Admin panel", comp: HomePage },
38
- { path: 'fs', icon: AccountTree, label: "Shared files", comp: VfsPage },
39
- { path: 'accounts', icon: ManageAccounts, comp: AccountsPage },
40
- { path: 'configuration', icon: Settings, comp: ConfigPage },
41
- { path: 'monitoring', icon: Monitor, comp: MonitorPage },
42
- { path: 'logs', icon: History, comp: LogsPage },
43
- { path: 'plugins', icon: Extension, comp: PluginsPage },
44
- { path: 'logout', icon: Logout, comp: LogoutPage }
45
- ]
46
-
47
- let version: any // cache 'version', as it won't change at runtime, while the Drawer mechanism will unmount our menu each time
48
- export default function Menu({ onSelect }: { onSelect: ()=>void }) {
49
- const [status] = useApi(!version && 'get_status')
50
- version ||= status?.version?.replace('-', ' ')
51
- return h(List, { sx:{ pr:1, bgcolor: 'primary.main', color:'primary.contrastText', minHeight: '100%', boxSizing: 'border-box' } },
52
- h(Box, { display: 'flex', px: 2, py: 1, gap: 2, alignItems: 'flex-end' },
53
- h(Typography, { variant:'h3' }, 'HFS'),
54
- h(Box, { pb: 1, fontSize: 'small' }, version),
55
- ),
56
- mainMenu.map(it =>
57
- h(ListItemButton, {
58
- key: it.path,
59
- to: it.path,
60
- component: NavLink,
61
- onClick: onSelect,
62
- // @ts-ignore
63
- style: ({ isActive }) => isActive ? { textDecoration: 'underline' } : {},
64
- children: undefined, // shut up ts
65
- },
66
- it.icon && h(ListItemIcon, { sx:{ color: 'primary.contrastText' } }, h(it.icon)),
67
- h(ListItemText, { primary: getMenuLabel(it) })
68
- ) )
69
- )
70
- }
71
-
72
- export function getMenuLabel(it: MenuEntry) {
73
- return it && (it.label ?? _.capitalize(it.path))
74
- }
@@ -1,38 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import React, { createElement as h, useCallback } from 'react'
4
- import { Button, Menu, MenuItem } from '@mui/material'
5
-
6
- interface Props { items: any[], [rest:string]:any }
7
-
8
- export default function MenuButton({ items, ...rest }: Props) {
9
- const [anchorEl, setAnchorEl] = React.useState<HTMLElement>()
10
- const open = Boolean(anchorEl)
11
- const onClose = useCallback(() => setAnchorEl(undefined), [])
12
- return h(React.Fragment, {},
13
- h(Button, {
14
- 'aria-controls': open ? 'basic-menu' : undefined,
15
- 'aria-haspopup': 'true',
16
- 'aria-expanded': open ? 'true' : undefined,
17
- onClick: (event: React.MouseEvent<HTMLButtonElement>) => {
18
- setAnchorEl(event.currentTarget)
19
- },
20
- ...rest,
21
- }),
22
- h(Menu, {
23
- anchorEl,
24
- open,
25
- onClose,
26
- MenuListProps: { 'aria-labelledby': 'basic-button' },
27
- children: items.map((it,idx) =>
28
- h(MenuItem, {
29
- key: idx,
30
- ...it,
31
- onClick() {
32
- onClose()
33
- it.onClick?.apply(this, arguments)
34
- }
35
- }) )
36
- })
37
- )
38
- }