admin/fs: system integration (Windows only) #347
Massimo Melina committed
Oct 17, 2023 at 10:58 UTC
0d8f2efd6a96ea1b6f3c53055e74a14823962d13
4 files changed
+54
-7
admin/src/VfsMenuBar.ts
+26
-3
@@ -2,13 +2,15 @@
2
3
import { createElement as h } from 'react'
4
import { Box } from '@mui/material'
5
-import { Add } from '@mui/icons-material'
5
+import { Add, Microsoft } from '@mui/icons-material'
6
import { reloadVfs } from './VfsPage'
7
import addFiles, { addVirtual } from './addFiles'
8
import MenuButton from './MenuButton'
9
-import { reloadBtn } from './misc'
9
+import { basename, Btn, reloadBtn } from './misc'
10
+import { apiCall } from './api'
11
+import { alertDialog, confirmDialog } from './dialog'
12
11
-export default function VfsMenuBar() {
13
+export default function VfsMenuBar({ status }: any) {
14
return h(Box, {
15
display: 'flex',
16
gap: 2,
@@ -30,5 +32,26 @@ export default function VfsMenuBar() {
32
]
33
}, "Add"),
34
reloadBtn(() => reloadVfs()),
35
+ status?.platform === 'win32' && h(Btn, {
36
+ icon: Microsoft,
37
+ variant: 'outlined',
38
+ onClick: windowsIntegration
39
+ }, "System integration"),
40
)
41
}
42
+
43
+async function windowsIntegration() {
44
+ const msg = h(Box, {}, "We are going to add a command in the right-click of Windows File Manager",
45
+ h('img', { src: 'win-shell.png', style: {
46
+ display: 'block',
47
+ width: 'min(30em, 80vw)',
48
+ marginTop: '1em',
49
+ } }),
50
+ )
51
+ if (!await confirmDialog(msg)) return
52
+ const hint = alertDialog("Click YES to the next 2 dialogs. The second dialog may not appear, and you need to click on the bottom bar.", 'warning')
53
+ const { finish } = await apiCall('windows_integration', {}, { timeout: 60 })
54
+ hint.close()
55
+ return finish ? alertDialog("To finish the process, please execute the file you'll find on your desktop: " + basename(finish))
56
+ : alertDialog("Done!", 'success')
57
+}
\ No newline at end of file
admin/src/VfsPage.ts
+1
-1
@@ -119,7 +119,7 @@ export default function VfsPage() {
119
h(Grid, { item:true, [sideBreakpoint]: 6, lg: 5, xl: 4 },
120
h(Typography, { variant: 'h6', mb:1, }, "Virtual File System"),
121
h(Alert, { severity: 'info' }, "If you rename or delete here, it's virtual, and only affects what is presented to the users"),
122
- h(VfsMenuBar),
122
+ h(VfsMenuBar, { status }),
123
vfs && h(VfsTree, { id2node })),
124
isSideBreakpoint && sideContent && h(Grid, { item:true, [sideBreakpoint]: true, maxWidth:'100%' },
125
h(Card, { sx: { overflow: 'initial' } }, // overflow is incompatible with stickyBar
src/adminApis.ts
+1
@@ -102,6 +102,7 @@ export const adminApis: ApiHandlers = {
102
apiVersion: API_VERSION,
103
compatibleApiVersion: COMPATIBLE_API_VERSION,
104
...await getServerStatus(),
105
+ platform: process.platform,
106
urls: await getUrls(),
107
ips: await getIps(false),
108
baseUrl: getBaseUrlOrDefault(),
src/api.vfs.ts
+26
-3
@@ -3,7 +3,7 @@
3
import { getNodeName, isSameFilenameAs, nodeIsDirectory, saveVfs, urlToNode, vfs, VfsNode, applyParentToChild,
4
permsFromParent } from './vfs'
5
import _ from 'lodash'
6
-import { stat } from 'fs/promises'
6
+import { stat, writeFile } from 'fs/promises'
7
import { ApiError, ApiHandlers, SendListReadable } from './apiMiddleware'
8
import { dirname, extname, join, resolve } from 'path'
9
import { dirStream, enforceFinal, isDirectory, isWindowsDrive, makeMatcher, PERM_KEYS, VfsNodeAdminSend } from './misc'
@@ -13,7 +13,9 @@ import {
13
} from './const'
14
import { getDrives } from './util-os'
15
import { Stats } from 'fs'
16
-import { getBaseUrlOrDefault } from './listen'
16
+import { getBaseUrlOrDefault, getServerStatus } from './listen'
17
+import { homedir } from 'os'
18
+import open from 'open'
19
20
// to manipulate the tree we need the original node
21
async function urlToNodeOriginal(uri: string) {
@@ -204,7 +206,11 @@ const apis: ApiHandlers = {
206
}
207
}
208
})
207
- }
209
+ },
210
+
211
+ async windows_integration() {
212
+ return { finish: await windowsIntegration() }
213
+ },
214
215
}
216
@@ -224,4 +230,21 @@ function simplifyName(node: VfsNode) {
230
const { name, ...noName } = node
231
if (getNodeName(noName) === name)
232
delete node.name
233
+}
234
+
235
+async function windowsIntegration() {
236
+ const status = await getServerStatus()
237
+ const url = 'http://localhost:' + status.http.port
238
+ const content = `Windows Registry Editor Version 5.00
239
+ `+ ['*', 'Directory'].map(k => `
240
+[HKEY_CLASSES_ROOT\\${k}\\shell\\AddToHFS3]
241
+@="Add to HFS (new)"
242
+
243
+[HKEY_CLASSES_ROOT\\${k}\\shell\\AddToHFS3\\command]
244
+@="powershell -Command \\"$p = '%1'.Replace('\\\\', '\\\\\\\\'); $j = '{ \\\\\\"source\\\\\\": \\\\\\"' + $p + '\\\\\\" }'; $wsh = New-Object -ComObject Wscript.Shell; try { $res = Invoke-WebRequest -Uri '${url}/~/api/add_vfs' -Method POST -Headers @{ 'x-hfs-anti-csrf' = '1' } -ContentType 'application/json' -TimeoutSec 1 -Body $j; $json = $res.Content | ConvertFrom-Json; $link = $json.link; $link | Set-Clipboard; } catch { $wsh.Popup('Server is down', 0, 'Error', 16); }\\""
245
+ `)
246
+ const path = homedir() + '\\desktop\\hfs-windows-menu.reg'
247
+ await writeFile(path, content, 'utf8')
248
+ try { await open(path, { wait: true}) }
249
+ catch { return path }
250
}
\ No newline at end of file