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 }