admin/fs: icon for missing source
Massimo Melina committed
Feb 15, 2023 at 23:40 UTC
e2588aefc4fa4e5c80517ca491cdc260868d4bf1
3 files changed
+15
-17
admin/src/VfsTree.ts
+4
-2
@@ -5,7 +5,7 @@ import { createElement as h, ReactElement, useRef, useState } from 'react'
5
import { TreeItem, TreeView } from '@mui/lab'
6
import {
7
ChevronRight, ExpandMore, TheaterComedy, Folder, Home,
8
- InsertDriveFileOutlined, Lock, RemoveRedEye, Web, Upload, Cloud, Delete
8
+ InsertDriveFileOutlined, Lock, RemoveRedEye, Web, Upload, Cloud, Delete, HighlightOff
9
} from '@mui/icons-material'
10
import { Box } from '@mui/material'
11
import { reloadVfs, VfsNode, Who } from './VfsPage'
@@ -99,7 +99,9 @@ export default function VfsTree({ id2node }:{ id2node: Map<string, VfsNode> }) {
99
isRestricted(node.can_read) && iconTooltip(Lock, "Restrictions on who can download"),
100
node.default && iconTooltip(Web, "Act as website"),
101
node.masks && iconTooltip(TheaterComedy, "Masks"),
102
- ), ),
102
+ node.size === -1 && iconTooltip(HighlightOff, "Source missing")
103
+ ),
104
+ ),
105
isRoot ? "Home"
106
// special rendering if the whole source is not too long, and the name was not customized
107
: source?.length! < 45 && source?.endsWith(name) ? h('span', {},
src/api.vfs.ts
+10
-14
@@ -12,6 +12,7 @@ import {
12
} from './const'
13
import { isMatch } from 'micromatch'
14
import { getDrives } from './util-os'
15
+import { Stats } from 'fs'
16
17
type VfsAdmin = {
18
type?: string,
@@ -37,25 +38,20 @@ const apis: ApiHandlers = {
38
}
39
40
async function recur(node: VfsNode): Promise<VfsAdmin> {
40
- const dir = await nodeIsDirectory(node)
41
- const stats: Pick<VfsAdmin, 'size' | 'ctime' | 'mtime'> = {}
42
- try {
43
- if (!dir)
44
- Object.assign(stats, _.pick(await stat(node.source!), ['size', 'ctime', 'mtime']))
45
- }
46
- catch {
47
- stats.size = -1
48
- }
49
- if (stats && Number(stats.mtime) === Number(stats.ctime))
50
- delete stats.mtime
41
+ const stats: false | Stats = Boolean(node.source) && await stat(node.source!).catch(e => false)
42
+ const isDir = !node.source || stats && stats.isDirectory()
43
+ const copyStats: Pick<VfsAdmin, 'size' | 'ctime' | 'mtime'> = stats ? _.pick(stats, ['size', 'ctime', 'mtime'])
44
+ : { size: node.source ? -1 : undefined }
45
+ if (copyStats.mtime && Number(copyStats.mtime) === Number(copyStats.ctime))
46
+ delete copyStats.mtime
47
const isRoot = node === vfs
48
return {
53
- ...stats,
49
+ ...copyStats,
50
...node,
55
- website: dir && node.source && await stat(join(node.source, 'index.html')).then(() => true, () => undefined)
51
+ website: isDir && node.source && await stat(join(node.source, 'index.html')).then(() => true, () => undefined)
52
|| undefined,
53
name: isRoot ? undefined : getNodeName(node),
58
- type: dir ? 'folder' : undefined,
54
+ type: isDir ? 'folder' : undefined,
55
children: node.children && await Promise.all(node.children.map(recur)),
56
}
57
}
src/serveFile.ts
+1
-1
@@ -76,7 +76,7 @@ export function serveFile(source:string, mime?:string, content?: string | Buffer
76
const range = getRange(ctx, stats.size)
77
ctx.body = createReadStream(source, range)
78
}
79
- catch {
79
+ catch (e: any) {
80
return ctx.status = HTTP_NOT_FOUND
81
}
82
}