hfs 0.26.8 → 0.26.9

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 (161) hide show
  1. package/admin/assets/index.bb5198ec.js +281 -0
  2. package/admin/assets/index.dcc78777.css +1 -0
  3. package/admin/assets/sha512.9dfe82e1.js +8 -0
  4. package/admin/index.html +3 -1
  5. package/admin/{public/logo.svg → logo.svg} +0 -0
  6. package/frontend/assets/index.27a78796.js +85 -0
  7. package/frontend/assets/index.93366732.css +1 -0
  8. package/frontend/assets/sha512.6af42937.js +8 -0
  9. package/frontend/{public/fontello.css → fontello.css} +0 -0
  10. package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
  11. package/frontend/index.html +3 -1
  12. package/package.json +1 -1
  13. package/src/QuickZipStream.js +285 -0
  14. package/src/ThrottledStream.js +93 -0
  15. package/src/adminApis.js +169 -0
  16. package/src/api.accounts.js +59 -0
  17. package/src/api.auth.js +128 -0
  18. package/src/api.file_list.js +103 -0
  19. package/src/api.helpers.js +32 -0
  20. package/src/api.monitor.js +102 -0
  21. package/src/api.plugins.js +127 -0
  22. package/src/api.vfs.js +164 -0
  23. package/src/apiMiddleware.js +120 -0
  24. package/src/block.js +33 -0
  25. package/src/commands.js +124 -0
  26. package/src/config.js +168 -0
  27. package/src/connections.js +57 -0
  28. package/src/const.js +83 -0
  29. package/src/crypt.js +21 -0
  30. package/src/debounceAsync.js +48 -0
  31. package/src/events.js +9 -0
  32. package/src/frontEndApis.js +38 -0
  33. package/src/github.js +102 -0
  34. package/src/index.js +56 -0
  35. package/src/listen.js +235 -0
  36. package/src/log.js +137 -0
  37. package/src/middlewares.js +175 -0
  38. package/src/misc.js +160 -0
  39. package/src/pbkdf2.js +74 -0
  40. package/src/perm.js +181 -0
  41. package/src/plugins.js +343 -0
  42. package/src/serveFile.js +105 -0
  43. package/src/serveGuiFiles.js +113 -0
  44. package/src/sse.js +29 -0
  45. package/src/throttler.js +91 -0
  46. package/src/update.js +69 -0
  47. package/src/util-files.js +148 -0
  48. package/src/util-generators.js +30 -0
  49. package/src/util-http.js +30 -0
  50. package/src/vfs.js +230 -0
  51. package/src/watchLoad.js +73 -0
  52. package/src/zip.js +72 -0
  53. package/admin/.DS_Store +0 -0
  54. package/admin/.eslintrc +0 -8
  55. package/admin/.gitignore +0 -23
  56. package/admin/package.json +0 -67
  57. package/admin/src/AccountForm.ts +0 -92
  58. package/admin/src/AccountsPage.ts +0 -143
  59. package/admin/src/App.ts +0 -83
  60. package/admin/src/ArrayField.ts +0 -84
  61. package/admin/src/ConfigPage.ts +0 -279
  62. package/admin/src/FileField.ts +0 -52
  63. package/admin/src/FileForm.ts +0 -148
  64. package/admin/src/FilePicker.ts +0 -166
  65. package/admin/src/HomePage.ts +0 -96
  66. package/admin/src/InstalledPlugins.ts +0 -158
  67. package/admin/src/LoginRequired.ts +0 -75
  68. package/admin/src/LogoutPage.ts +0 -27
  69. package/admin/src/LogsPage.ts +0 -75
  70. package/admin/src/MainMenu.ts +0 -74
  71. package/admin/src/MenuButton.ts +0 -38
  72. package/admin/src/MonitorPage.ts +0 -200
  73. package/admin/src/OnlinePlugins.ts +0 -101
  74. package/admin/src/PermField.ts +0 -80
  75. package/admin/src/PluginsPage.ts +0 -27
  76. package/admin/src/VfsMenuBar.ts +0 -58
  77. package/admin/src/VfsPage.ts +0 -124
  78. package/admin/src/VfsTree.ts +0 -95
  79. package/admin/src/addFiles.ts +0 -59
  80. package/admin/src/api.ts +0 -246
  81. package/admin/src/dialog.ts +0 -203
  82. package/admin/src/index.css +0 -21
  83. package/admin/src/index.ts +0 -10
  84. package/admin/src/md.ts +0 -31
  85. package/admin/src/misc.ts +0 -141
  86. package/admin/src/react-app-env.d.ts +0 -1
  87. package/admin/src/reportWebVitals.ts +0 -15
  88. package/admin/src/setupTests.ts +0 -5
  89. package/admin/src/state.ts +0 -40
  90. package/admin/src/theme.ts +0 -37
  91. package/admin/tsconfig.json +0 -26
  92. package/admin/vite.config.ts +0 -32
  93. package/frontend/.DS_Store +0 -0
  94. package/frontend/.eslintrc +0 -8
  95. package/frontend/.gitignore +0 -23
  96. package/frontend/package.json +0 -51
  97. package/frontend/src/App.ts +0 -25
  98. package/frontend/src/Breadcrumbs.ts +0 -43
  99. package/frontend/src/BrowseFiles.ts +0 -141
  100. package/frontend/src/Head.ts +0 -45
  101. package/frontend/src/UserPanel.ts +0 -52
  102. package/frontend/src/api.ts +0 -78
  103. package/frontend/src/components.ts +0 -54
  104. package/frontend/src/dialog.css +0 -76
  105. package/frontend/src/dialog.ts +0 -105
  106. package/frontend/src/icons.ts +0 -46
  107. package/frontend/src/index.scss +0 -307
  108. package/frontend/src/index.ts +0 -10
  109. package/frontend/src/login.ts +0 -50
  110. package/frontend/src/menu.ts +0 -188
  111. package/frontend/src/misc.ts +0 -54
  112. package/frontend/src/options.ts +0 -52
  113. package/frontend/src/react-app-env.d.ts +0 -1
  114. package/frontend/src/reportWebVitals.ts +0 -15
  115. package/frontend/src/setupTests.ts +0 -5
  116. package/frontend/src/state.ts +0 -82
  117. package/frontend/src/useAuthorized.ts +0 -17
  118. package/frontend/src/useFetchList.ts +0 -144
  119. package/frontend/src/useTheme.ts +0 -23
  120. package/frontend/tsconfig.json +0 -26
  121. package/frontend/vite.config.ts +0 -21
  122. package/src/QuickZipStream.ts +0 -279
  123. package/src/ThrottledStream.ts +0 -98
  124. package/src/adminApis.ts +0 -161
  125. package/src/api.accounts.ts +0 -78
  126. package/src/api.auth.ts +0 -131
  127. package/src/api.file_list.ts +0 -102
  128. package/src/api.helpers.ts +0 -30
  129. package/src/api.monitor.ts +0 -106
  130. package/src/api.plugins.ts +0 -139
  131. package/src/api.vfs.ts +0 -182
  132. package/src/apiMiddleware.ts +0 -124
  133. package/src/block.ts +0 -35
  134. package/src/commands.ts +0 -122
  135. package/src/config.ts +0 -166
  136. package/src/connections.ts +0 -60
  137. package/src/const.ts +0 -57
  138. package/src/crypt.ts +0 -16
  139. package/src/debounceAsync.ts +0 -51
  140. package/src/events.ts +0 -6
  141. package/src/frontEndApis.ts +0 -17
  142. package/src/github.ts +0 -102
  143. package/src/index.ts +0 -53
  144. package/src/listen.ts +0 -220
  145. package/src/log.ts +0 -128
  146. package/src/middlewares.ts +0 -176
  147. package/src/misc.ts +0 -149
  148. package/src/pbkdf2.ts +0 -83
  149. package/src/perm.ts +0 -194
  150. package/src/plugins.ts +0 -342
  151. package/src/serveFile.ts +0 -104
  152. package/src/serveGuiFiles.ts +0 -95
  153. package/src/sse.ts +0 -29
  154. package/src/throttler.ts +0 -106
  155. package/src/update.ts +0 -67
  156. package/src/util-files.ts +0 -137
  157. package/src/util-generators.ts +0 -29
  158. package/src/util-http.ts +0 -29
  159. package/src/vfs.ts +0 -258
  160. package/src/watchLoad.ts +0 -75
  161. package/src/zip.ts +0 -69
@@ -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
- }
@@ -1,200 +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 _ from "lodash"
4
- import { createElement as h, useMemo, Fragment, useState } from "react"
5
- import { apiCall, useApiEvents, useApiEx, useApiList } from "./api"
6
- import { PauseCircle, PlayCircle, Delete, Lock, Block, FolderZip } from '@mui/icons-material'
7
- import { Alert, Box, Chip, ChipProps } from '@mui/material'
8
- import { DataGrid } from "@mui/x-data-grid"
9
- import { formatBytes, IconBtn, iconTooltip, manipulateConfig, useBreakpoint } from "./misc"
10
- import { Field, SelectField } from '@hfs/mui-grid-form'
11
- import { GridColumns } from '@mui/x-data-grid/models/colDef/gridColDef'
12
- import { StandardCSSProperties } from '@mui/system/styleFunctionSx/StandardCssProperties'
13
-
14
- export default function MonitorPage() {
15
- return h(Fragment, {},
16
- h(MoreInfo),
17
- h(Connections),
18
- )
19
- }
20
-
21
- const isoDateRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
22
-
23
- function MoreInfo() {
24
- const { data: status, element } = useApiEx('get_status')
25
- const { data: connections } = useApiEvents('get_connection_stats')
26
- if (status && connections)
27
- Object.assign(status, connections)
28
- const md = useBreakpoint('md')
29
- const sm = useBreakpoint('sm')
30
- return element || h(Box, { display: 'flex', flexWrap: 'wrap', gap: '1em', mb: 2 },
31
- md && pair('started'),
32
- md && pair('http', { label: "HTTP", render: port }),
33
- md && pair('https', { label: "HTTPS", render: port }),
34
- sm && pair('connections'),
35
- pair('sent', { render: formatBytes, minWidth: '4em' }),
36
- pair('outSpeed', { label: "Output speed", render: formatSpeed }),
37
- )
38
-
39
- type Color = ChipProps['color']
40
- type Render = (v: any) => [string, Color?] | string
41
- interface PairOptions {
42
- label?: string
43
- render?: Render
44
- minWidth?: StandardCSSProperties['minWidth']
45
- }
46
-
47
- function pair(k: string, { label, minWidth, render }: PairOptions={}) {
48
- let v = _.get(status, k)
49
- if (v === undefined)
50
- return null
51
- if (typeof v === 'string' && isoDateRe.test(v))
52
- v = new Date(v).toLocaleString()
53
- let color: Color = undefined
54
- if (render) {
55
- v = render(v)
56
- if (Array.isArray(v))
57
- [v, color] = v
58
- }
59
- if (!label)
60
- label = _.capitalize(k.replaceAll('_', ' '))
61
- return h(Chip, {
62
- variant: 'filled',
63
- color,
64
- label: h(Fragment, {},
65
- h('b',{},label),
66
- ': ',
67
- h('span', { style:{ display: 'inline-block', minWidth } }, v),
68
- ),
69
- })
70
- }
71
-
72
- function port(v: any): ReturnType<Render> {
73
- return v.listening ? ["port " + v.port, 'success']
74
- : v.error ? [v.error, 'error']
75
- : "off"
76
- }
77
-
78
- }
79
-
80
- function Connections() {
81
- const { list, error } = useApiList('get_connections')
82
- const [filtered, setFiltered] = useState(true)
83
- const [paused, setPaused] = useState(false)
84
- const rows = useMemo(() =>
85
- list?.filter((x: any) => !filtered || x.path).map((x: any, id: number) => ({ id, ...x })),
86
- [!paused && list, filtered]) //eslint-disable-line
87
- // if I don't memo 'columns', it won't keep hiding status
88
- const columns = useMemo<GridColumns<any>>(() => [
89
- {
90
- field: 'ip',
91
- headerName: "Address",
92
- flex: 1,
93
- maxWidth: 400,
94
- valueGetter: ({ row, value }) => (row.v === 6 ? `[${value}]` : value) + ' :' + row.port
95
- },
96
- {
97
- field: 'user',
98
- headerName: "User",
99
- },
100
- {
101
- field: 'started',
102
- headerName: "Started",
103
- type: 'dateTime',
104
- width: 130,
105
- valueFormatter: ({ value }) => new Date(value as string).toLocaleTimeString()
106
- },
107
- {
108
- field: 'path',
109
- headerName: "File",
110
- flex: 1,
111
- renderCell({ value, row }) {
112
- if (!value) return
113
- if (row.archive)
114
- return h(Fragment, {},
115
- h(FolderZip, { sx: { mr: 1 } }),
116
- row.archive,
117
- h(Box, { ml: 2, color: 'text.secondary' }, value)
118
- )
119
- const i = value?.lastIndexOf('/')
120
- return h(Fragment, {}, value.slice(i + 1),
121
- i > 0 && h(Box, { ml: 2, color: 'text.secondary' }, value.slice(0, i)))
122
- }
123
- },
124
- {
125
- field: 'v',
126
- headerName: "Protocol",
127
- align: 'center',
128
- hide: true,
129
- renderCell: ({ value, row }) => h(Fragment, {},
130
- "IPv" + value,
131
- row.secure && iconTooltip(Lock, "HTTPS", { opacity: .5 })
132
- )
133
- },
134
- {
135
- field: 'outSpeed',
136
- headerName: "Speed",
137
- type: 'number',
138
- valueFormatter: ({ value }) => formatSpeed(value)
139
- },
140
- {
141
- field: 'sent',
142
- headerName: "Total",
143
- type: 'number',
144
- valueFormatter: ({ value }) => formatBytes(value as number)
145
- },
146
- {
147
- field: "Actions",
148
- width: 80,
149
- align: 'center',
150
- hideSortIcons: true,
151
- disableColumnMenu: true,
152
- renderCell({ row }) {
153
- return h('div', {},
154
- h(IconBtn, {
155
- icon: Delete,
156
- title: "Disconnect",
157
- onClick: () => apiCall('disconnect', _.pick(row, ['ip', 'port'])),
158
- }),
159
- h(IconBtn, {
160
- icon: Block,
161
- title: "Block IP",
162
- onClick: () => blockIp(row.ip),
163
- }),
164
- )
165
- }
166
- }
167
- ], [])
168
- return h(Fragment, {},
169
- h(Box, { display: 'flex', alignItems: 'center' },
170
- h(SelectField as Field<boolean>, {
171
- fullWidth: false,
172
- value: filtered,
173
- onChange: setFiltered as any,
174
- options: { "Show only downloads": true, "Show all connections": false }
175
- }),
176
-
177
- h(Box, { flex: 1 }),
178
- h(IconBtn, {
179
- title: paused ? "Resume" : "Pause",
180
- icon: paused ? PlayCircle : PauseCircle,
181
- sx: { mr: 1 },
182
- onClick() {
183
- setPaused(!paused)
184
- }
185
- }),
186
- ),
187
- error ? h(Alert, { severity: 'error' }, error)
188
- : h(DataGrid, { rows, columns,
189
- localeText: filtered ? { noRowsLabel: "No downloads at the moment" } : undefined,
190
- })
191
- )
192
- }
193
-
194
- function blockIp(ip: string) {
195
- return manipulateConfig('block', data => [...data, { ip }])
196
- }
197
-
198
- function formatSpeed(value: number) {
199
- return !value ? '' : formatBytes(value * 1000, { post: "B/s", k: 1000, digits: 1 })
200
- }
@@ -1,101 +0,0 @@
1
- import { apiCall, useApiList } from './api'
2
- import { Fragment, createElement as h, useState } from 'react'
3
- import { DataGrid } from '@mui/x-data-grid'
4
- import { IconBtn } from './misc'
5
- import { Download, Search } from '@mui/icons-material'
6
- import { confirmDialog } from './dialog'
7
- import { StringField } from '@hfs/mui-grid-form'
8
- import { useDebounce } from 'use-debounce'
9
- import { repoLink, showError, startPlugin, UpdateButton } from './InstalledPlugins'
10
- import { state, useSnapState } from './state'
11
- import _ from 'lodash'
12
- import md from './md'
13
-
14
- export default function OnlinePlugins() {
15
- const [search, setSearch] = useState('')
16
- const [debouncedSearch] = useDebounce(search, 1000)
17
- const { list, error, initializing, updateList } = useApiList('search_online_plugins', { text: debouncedSearch })
18
- const snap = useSnapState()
19
- if (error)
20
- return showError(error)
21
- return h(Fragment, {},
22
- h(StringField, {
23
- value: search,
24
- onChange: setSearch as any,
25
- start: h(Search),
26
- typing: true,
27
- label: "Search text"
28
- }),
29
- h(DataGrid, {
30
- rows: list.length ? list : [], // workaround for DataGrid bug causing 'no rows' message to be not displayed after 'loading' was also used
31
- localeText: { noRowsLabel: "No compatible plugins have been found" },
32
- loading: initializing,
33
- columnVisibilityModel: snap.onlinePluginsColumns,
34
- onColumnVisibilityModelChange: newModel => Object.assign(state.onlinePluginsColumns, newModel),
35
- columns: [
36
- {
37
- field: 'id',
38
- headerName: "name",
39
- flex: 1,
40
- },
41
- {
42
- field: 'version',
43
- width: 70,
44
- },
45
- {
46
- field: 'pushed_at',
47
- headerName: "last update",
48
- valueGetter: ({ value }) => new Date(value).toLocaleDateString(),
49
- },
50
- {
51
- field: 'license',
52
- width: 80,
53
- },
54
- {
55
- field: 'description',
56
- flex: 3,
57
- },
58
- {
59
- field: 'stargazers_count',
60
- width: 50,
61
- headerName: "stars",
62
- align: 'center',
63
- },
64
- {
65
- field: "actions",
66
- width: 80,
67
- align: 'center',
68
- hideSortIcons: true,
69
- disableColumnMenu: true,
70
- hideable: false,
71
- renderCell({ row }) {
72
- const { id, branch } = row
73
- return h('div', {},
74
- repoLink(id),
75
- row.update ? h(UpdateButton, {
76
- id,
77
- then() {
78
- updateList(list =>
79
- _.find(list, { id }).update = false )
80
- }
81
- }) : h(IconBtn, {
82
- icon: Download,
83
- title: "Install",
84
- progress: row.downloading,
85
- disabled: row.installed && "Already installed",
86
- tooltipProps: { placement:'bottom-end' }, // workaround problem with horizontal scrolling by moving the tooltip leftward
87
- confirm: "WARNING - Proceed only if you trust this author and this plugin",
88
- async onClick() {
89
- const { id: installedId } = await apiCall('download_plugin', { id, branch })
90
- if (await confirmDialog(md(`Plugin /${id}/ installed.\nDo you want to start it now?`)))
91
- await startPlugin(installedId)
92
- }
93
- })
94
- )
95
- }
96
- },
97
- ]
98
- })
99
- )
100
- }
101
-
@@ -1,80 +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 { Dict, useStateMounted } from './misc'
4
- import { createElement as h, Fragment } from 'react'
5
- import { Button, Grid } from '@mui/material'
6
- import { Field, FieldProps, SelectField } from '@hfs/mui-grid-form'
7
- import _ from 'lodash'
8
- import { useApiEx } from './api'
9
-
10
- export default function PermField({ label, value, onChange }: FieldProps<Dict<string> | null> & { keyLabel:string }) {
11
- const [temp, setTemp] = useStateMounted<string|undefined>(undefined)
12
- const { data, element } = useApiEx('get_usernames')
13
- const usernames = data?.list || []
14
-
15
- const permOptions = [{ label:'read', value:'r' }, { label:'none', value:'' }]
16
- const usernamesLeft = _.difference(usernames, Object.keys(value||{}))
17
-
18
- return h(Grid, { container: true },
19
- label && h(Grid, { item: true, xs: 12, pl: 2, py: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between' },
20
- label,
21
- !element && h(Button, {
22
- onClick(event){
23
- setTemp(undefined)
24
- onChange(null, { event, was: value })
25
- }
26
- }, 'Clear')),
27
- element || h(Fragment, {},
28
- // existing entries
29
- Object.entries(value||{}).map(([username, perm]) => [
30
- h(Grid, { key:'k', item: true, xs: 6 },
31
- h(SelectField as Field<string>, {
32
- label: 'Username',
33
- options: usernames,
34
- value: username,
35
- onChange(v, { was, ...rest }){
36
- const copy: any = { ...value, [v]: value![was!] }
37
- delete copy[was!]
38
- onChange(copy, { was:value, ...rest })
39
- }
40
- })),
41
- h(Grid, { key:'v', item: true, xs: 6 },
42
- h(SelectField as Field<string>, {
43
- label: 'Access',
44
- options: permOptions,
45
- value: perm,
46
- onChange(v, { was, ...rest }){
47
- const copy = { ...value }
48
- if (v)
49
- copy[username] = v
50
- else
51
- delete copy[username]
52
- onChange( _.isEmpty(copy) ? null : copy, { was:value, ...rest })
53
- }
54
- })),
55
- ]),
56
- // row for new entries
57
- !usernamesLeft.length && h(Grid, { item: true, xs: 12, py: 1, px: 2, color:'text.secondary' }, "No accounts left"),
58
- usernamesLeft.length>0 && h(Grid, { item: true, xs: 6 },
59
- h(SelectField as Field<string>, {
60
- label: value ? "Add access to" : "Restrict access to ",
61
- value: temp,
62
- options: usernamesLeft,
63
- onChange: setTemp as any,
64
- })),
65
- usernamesLeft.length>0 && h(Grid, { item: true, xs: 6 },
66
- h(SelectField as Field<string>, {
67
- value: undefined,
68
- label: temp && "Select access type",
69
- disabled: !temp,
70
- options: permOptions,
71
- onChange(v, rest) {
72
- if (v)
73
- onChange({ ...value, [temp!]: v }, { ...rest, was: value })
74
- setTemp(undefined)
75
- }
76
- }))
77
- )
78
- )
79
- }
80
-
@@ -1,27 +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, Fragment, useState } from "react"
4
- import { Tab, Tabs } from '@mui/material'
5
- import InstalledPlugins from "./InstalledPlugins"
6
- import OnlinePlugins from "./OnlinePlugins"
7
-
8
- const TABS = {
9
- "Installed": InstalledPlugins,
10
- "Search online": OnlinePlugins,
11
- "Check updates": () => h(InstalledPlugins, { updates: true }),
12
- }
13
- const LABELS = Object.keys(TABS)
14
- const PANES = Object.values(TABS)
15
-
16
- export default function PluginsPage() {
17
- const [tab, setTab] = useState(0)
18
- return h(Fragment, {},
19
- h(Tabs, {
20
- value: tab,
21
- onChange(ev, i) {
22
- setTab(i)
23
- }
24
- }, LABELS.map(label => h(Tab, { label, key: label })) ),
25
- h(PANES[tab])
26
- )
27
- }
@@ -1,58 +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 { state, useSnapState } from './state'
4
- import { createElement as h } from 'react'
5
- import { Box, Button } from '@mui/material'
6
- import { Add, Delete, Refresh } from '@mui/icons-material'
7
- import { alertDialog, confirmDialog } from './dialog'
8
- import { apiCall } from './api'
9
- import { reloadVfs } from './VfsPage'
10
- import addFiles, { addVirtual } from './addFiles'
11
- import MenuButton from './MenuButton'
12
- import { IconBtn } from './misc'
13
-
14
- export default function VfsMenuBar() {
15
- const { selectedFiles } = useSnapState()
16
- return h(Box, {
17
- display: 'flex',
18
- gap: 2,
19
- mb: 2,
20
- sx: {
21
- position: 'sticky',
22
- top: 0,
23
- zIndex: 2,
24
- backgroundColor: 'background.paper',
25
- width: 'fit-content',
26
- },
27
- },
28
- h(MenuButton, {
29
- variant: 'contained',
30
- startIcon: h(Add),
31
- items: [
32
- { children: "from disk", onClick: addFiles },
33
- { children: "virtual folder", onClick: addVirtual }
34
- ]
35
- }, "Add"),
36
- h(Button, { onClick: removeFiles, disabled: !selectedFiles.length, startIcon: h(Delete) }, "Remove"),
37
- h(IconBtn, { icon: Refresh, title: "Reload", onClick(){ reloadVfs() } }),
38
- )
39
- }
40
-
41
- async function removeFiles() {
42
- const f = state.selectedFiles
43
- if (!f.length) return
44
- if (await confirmDialog(`Remove ${f.length} item(s)?`)) {
45
- try {
46
- const uris = f.map(x => x.id)
47
- const { errors } = await apiCall('del_vfs', { uris })
48
- const urisThatFailed = uris.filter((uri, idx) => errors[idx])
49
- if (urisThatFailed.length)
50
- return alertDialog("Following elements couldn't be removed: " + urisThatFailed.join(', '), 'error')
51
- reloadVfs()
52
- }
53
- catch(e) {
54
- await alertDialog(e as Error)
55
- }
56
- }
57
-
58
- }