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.
- package/admin/assets/index.bb5198ec.js +281 -0
- package/admin/assets/index.dcc78777.css +1 -0
- package/admin/assets/sha512.9dfe82e1.js +8 -0
- package/admin/index.html +3 -1
- package/admin/{public/logo.svg → logo.svg} +0 -0
- package/frontend/assets/index.27a78796.js +85 -0
- package/frontend/assets/index.93366732.css +1 -0
- package/frontend/assets/sha512.6af42937.js +8 -0
- package/frontend/{public/fontello.css → fontello.css} +0 -0
- package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
- package/frontend/index.html +3 -1
- package/package.json +1 -1
- package/src/QuickZipStream.js +285 -0
- package/src/ThrottledStream.js +93 -0
- package/src/adminApis.js +169 -0
- package/src/api.accounts.js +59 -0
- package/src/api.auth.js +128 -0
- package/src/api.file_list.js +103 -0
- package/src/api.helpers.js +32 -0
- package/src/api.monitor.js +102 -0
- package/src/api.plugins.js +127 -0
- package/src/api.vfs.js +164 -0
- package/src/apiMiddleware.js +120 -0
- package/src/block.js +33 -0
- package/src/commands.js +124 -0
- package/src/config.js +168 -0
- package/src/connections.js +57 -0
- package/src/const.js +83 -0
- package/src/crypt.js +21 -0
- package/src/debounceAsync.js +48 -0
- package/src/events.js +9 -0
- package/src/frontEndApis.js +38 -0
- package/src/github.js +102 -0
- package/src/index.js +56 -0
- package/src/listen.js +235 -0
- package/src/log.js +137 -0
- package/src/middlewares.js +175 -0
- package/src/misc.js +160 -0
- package/src/pbkdf2.js +74 -0
- package/src/perm.js +181 -0
- package/src/plugins.js +343 -0
- package/src/serveFile.js +105 -0
- package/src/serveGuiFiles.js +113 -0
- package/src/sse.js +29 -0
- package/src/throttler.js +91 -0
- package/src/update.js +69 -0
- package/src/util-files.js +148 -0
- package/src/util-generators.js +30 -0
- package/src/util-http.js +30 -0
- package/src/vfs.js +230 -0
- package/src/watchLoad.js +73 -0
- package/src/zip.js +72 -0
- package/admin/.DS_Store +0 -0
- package/admin/.eslintrc +0 -8
- package/admin/.gitignore +0 -23
- package/admin/package.json +0 -67
- package/admin/src/AccountForm.ts +0 -92
- package/admin/src/AccountsPage.ts +0 -143
- package/admin/src/App.ts +0 -83
- package/admin/src/ArrayField.ts +0 -84
- package/admin/src/ConfigPage.ts +0 -279
- package/admin/src/FileField.ts +0 -52
- package/admin/src/FileForm.ts +0 -148
- package/admin/src/FilePicker.ts +0 -166
- package/admin/src/HomePage.ts +0 -96
- package/admin/src/InstalledPlugins.ts +0 -158
- package/admin/src/LoginRequired.ts +0 -75
- package/admin/src/LogoutPage.ts +0 -27
- package/admin/src/LogsPage.ts +0 -75
- package/admin/src/MainMenu.ts +0 -74
- package/admin/src/MenuButton.ts +0 -38
- package/admin/src/MonitorPage.ts +0 -200
- package/admin/src/OnlinePlugins.ts +0 -101
- package/admin/src/PermField.ts +0 -80
- package/admin/src/PluginsPage.ts +0 -27
- package/admin/src/VfsMenuBar.ts +0 -58
- package/admin/src/VfsPage.ts +0 -124
- package/admin/src/VfsTree.ts +0 -95
- package/admin/src/addFiles.ts +0 -59
- package/admin/src/api.ts +0 -246
- package/admin/src/dialog.ts +0 -203
- package/admin/src/index.css +0 -21
- package/admin/src/index.ts +0 -10
- package/admin/src/md.ts +0 -31
- package/admin/src/misc.ts +0 -141
- package/admin/src/react-app-env.d.ts +0 -1
- package/admin/src/reportWebVitals.ts +0 -15
- package/admin/src/setupTests.ts +0 -5
- package/admin/src/state.ts +0 -40
- package/admin/src/theme.ts +0 -37
- package/admin/tsconfig.json +0 -26
- package/admin/vite.config.ts +0 -32
- package/frontend/.DS_Store +0 -0
- package/frontend/.eslintrc +0 -8
- package/frontend/.gitignore +0 -23
- package/frontend/package.json +0 -51
- package/frontend/src/App.ts +0 -25
- package/frontend/src/Breadcrumbs.ts +0 -43
- package/frontend/src/BrowseFiles.ts +0 -141
- package/frontend/src/Head.ts +0 -45
- package/frontend/src/UserPanel.ts +0 -52
- package/frontend/src/api.ts +0 -78
- package/frontend/src/components.ts +0 -54
- package/frontend/src/dialog.css +0 -76
- package/frontend/src/dialog.ts +0 -105
- package/frontend/src/icons.ts +0 -46
- package/frontend/src/index.scss +0 -307
- package/frontend/src/index.ts +0 -10
- package/frontend/src/login.ts +0 -50
- package/frontend/src/menu.ts +0 -188
- package/frontend/src/misc.ts +0 -54
- package/frontend/src/options.ts +0 -52
- package/frontend/src/react-app-env.d.ts +0 -1
- package/frontend/src/reportWebVitals.ts +0 -15
- package/frontend/src/setupTests.ts +0 -5
- package/frontend/src/state.ts +0 -82
- package/frontend/src/useAuthorized.ts +0 -17
- package/frontend/src/useFetchList.ts +0 -144
- package/frontend/src/useTheme.ts +0 -23
- package/frontend/tsconfig.json +0 -26
- package/frontend/vite.config.ts +0 -21
- package/src/QuickZipStream.ts +0 -279
- package/src/ThrottledStream.ts +0 -98
- package/src/adminApis.ts +0 -161
- package/src/api.accounts.ts +0 -78
- package/src/api.auth.ts +0 -131
- package/src/api.file_list.ts +0 -102
- package/src/api.helpers.ts +0 -30
- package/src/api.monitor.ts +0 -106
- package/src/api.plugins.ts +0 -139
- package/src/api.vfs.ts +0 -182
- package/src/apiMiddleware.ts +0 -124
- package/src/block.ts +0 -35
- package/src/commands.ts +0 -122
- package/src/config.ts +0 -166
- package/src/connections.ts +0 -60
- package/src/const.ts +0 -57
- package/src/crypt.ts +0 -16
- package/src/debounceAsync.ts +0 -51
- package/src/events.ts +0 -6
- package/src/frontEndApis.ts +0 -17
- package/src/github.ts +0 -102
- package/src/index.ts +0 -53
- package/src/listen.ts +0 -220
- package/src/log.ts +0 -128
- package/src/middlewares.ts +0 -176
- package/src/misc.ts +0 -149
- package/src/pbkdf2.ts +0 -83
- package/src/perm.ts +0 -194
- package/src/plugins.ts +0 -342
- package/src/serveFile.ts +0 -104
- package/src/serveGuiFiles.ts +0 -95
- package/src/sse.ts +0 -29
- package/src/throttler.ts +0 -106
- package/src/update.ts +0 -67
- package/src/util-files.ts +0 -137
- package/src/util-generators.ts +0 -29
- package/src/util-http.ts +0 -29
- package/src/vfs.ts +0 -258
- package/src/watchLoad.ts +0 -75
- package/src/zip.ts +0 -69
package/admin/src/LogsPage.ts
DELETED
|
@@ -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
|
-
}
|
package/admin/src/MainMenu.ts
DELETED
|
@@ -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
|
-
}
|
package/admin/src/MenuButton.ts
DELETED
|
@@ -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
|
-
}
|
package/admin/src/MonitorPage.ts
DELETED
|
@@ -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
|
-
|
package/admin/src/PermField.ts
DELETED
|
@@ -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
|
-
|
package/admin/src/PluginsPage.ts
DELETED
|
@@ -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
|
-
}
|
package/admin/src/VfsMenuBar.ts
DELETED
|
@@ -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
|
-
}
|