admin/fs: offer removal after system integration
Massimo Melina committed
Oct 22, 2023 at 09:34 UTC
2f3b33b1b3efb29266cc1d2ee14514d8137776d0
5 files changed
+54
-22
admin/public/win-shell.png
Binary files /dev/null and b/admin/public/win-shell.png differ
admin/src/VfsMenuBar.ts
+18
-6
@@ -7,10 +7,11 @@ import { reloadVfs } from './VfsPage'
7
import addFiles, { addVirtual } from './addFiles'
8
import MenuButton from './MenuButton'
9
import { basename, Btn, reloadBtn } from './misc'
10
-import { apiCall } from './api'
10
+import { apiCall, useApi } from './api'
11
import { alertDialog, confirmDialog } from './dialog'
12
13
export default function VfsMenuBar({ status }: any) {
14
+ const { data: integrated, reload } = useApi(status?.platform === 'win32' && 'windows_integrated')
15
return h(Box, {
16
display: 'flex',
17
gap: 2,
@@ -35,8 +36,15 @@ export default function VfsMenuBar({ status }: any) {
36
status?.platform === 'win32' && h(Btn, {
37
icon: Microsoft,
38
variant: 'outlined',
38
- onClick: windowsIntegration
39
- }, "System integration"),
39
+ ...(!integrated?.is ? {
40
+ children: "System integration",
41
+ onClick: () => windowsIntegration().then(reload),
42
+ } : {
43
+ confirm: true,
44
+ children: "Remove integration",
45
+ onClick: () => exec('windows_remove').then(reload),
46
+ })
47
+ }),
48
)
49
}
50
@@ -48,10 +56,14 @@ async function windowsIntegration() {
56
marginTop: '1em',
57
} }),
58
)
51
- if (!await confirmDialog(msg)) return
59
+ return await confirmDialog(msg)
60
+ && exec('windows_integration')
61
+}
62
+
63
+async function exec(api: string) {
64
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 })
65
+ const { finish } = await apiCall(api, {}, { timeout: false })
66
hint.close()
67
return finish ? alertDialog("To finish the process, please execute the file you'll find on your desktop: " + basename(finish))
68
: alertDialog("Done!", 'success')
57
-}
\ No newline at end of file
69
+}
admin/src/addFiles.ts
+5
-3
@@ -23,8 +23,9 @@ export default function addFiles() {
23
h(FilePicker, {
24
from: parent.source,
25
async onSelect(sel) {
26
- const errs = onlyTruthy(await Promise.all(sel.map(source =>
27
- apiCall('add_vfs', { parent: parent.id, source }).then(() => null, e => [source, e.message]))))
26
+ const res = await Promise.all(sel.map(source =>
27
+ apiCall('add_vfs', { parent: parent.id, source }).then(r => r, e => [source, e.message])))
28
+ const errs = res.filter(Array.isArray)
29
if (errs.length)
30
await alertDialog(h(Box, {},
31
"Some elements have been rejected",
@@ -33,7 +34,8 @@ export default function addFiles() {
34
h('li', { key: file }, file, ': ', err))
35
)
36
), 'error')
36
- reloadVfs()
37
+ const good = res.filter(x => x.name).map(x => parent.id + x.name)
38
+ reloadVfs(good)
39
close()
40
}
41
})
admin/src/mui.ts
+2
-2
@@ -134,7 +134,7 @@ interface BtnProps extends Omit<LoadingButtonProps,'disabled'|'title'|'onClick'>
134
disabled?: boolean | string
135
progress?: boolean | number
136
link?: string
137
- confirm?: string
137
+ confirm?: boolean | string
138
tooltipProps?: TooltipProps
139
onClick: (...args: Parameters<NonNullable<ButtonProps['onClick']>>) => Promisable<any>
140
}
@@ -156,7 +156,7 @@ export function Btn({ icon, title, onClick, disabled, progress, link, tooltipPro
156
disabled,
157
...rest,
158
async onClick(...args) {
159
- if (confirm && !await confirmDialog(confirm)) return
159
+ if (confirm && !await confirmDialog(confirm === true ? "Are you sure?" : confirm)) return
160
const ret = onClick?.apply(this,args)
161
if (ret && ret instanceof Promise) {
162
setLoading(true)
src/api.vfs.ts
+29
-11
@@ -16,6 +16,8 @@ import { Stats } from 'fs'
16
import { getBaseUrlOrDefault, getServerStatus } from './listen'
17
import { homedir } from 'os'
18
import open from 'open'
19
+import { promisify } from 'util'
20
+import { execFile } from 'child_process'
21
22
// to manipulate the tree we need the original node
23
async function urlToNodeOriginal(uri: string) {
@@ -212,6 +214,17 @@ const apis: ApiHandlers = {
214
return { finish: await windowsIntegration() }
215
},
216
217
+ async windows_integrated() {
218
+ return {
219
+ is: await promisify(execFile)('reg', ['query', WINDOWS_REG_KEY])
220
+ .then(x => x.stdout.includes('REG_SZ'), () => false)
221
+ }
222
+ },
223
+
224
+ async windows_remove() { // not using `reg delete` because it doesn't ask for admin permissions
225
+ return { finish: await runReg(['*', 'Directory'].map(k => `\n\n[-${WINDOWS_REG_KEY.replace('*', k)}]`).join('')) }
226
+ },
227
+
228
}
229
230
export default apis
@@ -232,22 +245,27 @@ function simplifyName(node: VfsNode) {
245
delete node.name
246
}
247
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)"
248
+const WINDOWS_REG_KEY = 'HKEY_CLASSES_ROOT\\*\\shell\\AddToHFS3'
249
+const WINDOWS_REG_KEY2 = WINDOWS_REG_KEY + '\\command'
250
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
- `)
251
+export async function runReg(content: string) {
252
const path = homedir() + '\\desktop\\hfs-windows-menu.reg'
247
- await writeFile(path, content, 'utf8')
253
+ await writeFile(path, 'Windows Registry Editor Version 5.00\n\n' + content, 'utf8')
254
try {
255
await open(path, { wait: true})
256
await unlink(path)
257
}
258
catch { return path }
259
+}
260
+
261
+export async function windowsIntegration() {
262
+ const status = await getServerStatus()
263
+ const url = 'http://localhost:' + status.http.port
264
+ return runReg(['*', 'Directory'].map(k => `
265
+[${WINDOWS_REG_KEY.replace('*', k)}]
266
+@="Add to HFS (new)"
267
+
268
+[${WINDOWS_REG_KEY2.replace('*', k)}]
269
+@="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); }\\""
270
+ `).join(''))
271
}
\ No newline at end of file