can_archive

Massimo Melina committed Oct 2, 2023 at 15:47 UTC d5d04b415c93eb8c59ff7006e7cfe98ecee8d17c
4 files changed +23 -19
admin/src/FileForm.ts
+15 -13
@@ -55,8 +55,9 @@ export default function FileForm({ file, anyMask, addToBar, statusApi }: FileFor
55 }
56 return _.defaults(ret, defaultPerms)
57 }, [parent])
58 - const showTimestamps = hasSource && Boolean(values.ctime)
59 - const showSize = hasSource && !realFolder
58 + const lg = useBreakpoint('lg')
59 + const showTimestamps = lg || hasSource
60 + const showSize = lg || (hasSource && !realFolder)
61 const showAccept = file.accept! > '' || isDir && (file.can_upload ?? inheritedPerms.can_upload)
62 const barColors = useDialogBarColors()
63
@@ -102,26 +103,27 @@ export default function FileForm({ file, anyMask, addToBar, statusApi }: FileFor
103 },
104 fields: [
105 isRoot ? h(Alert,{ severity: 'info' }, "This is Home, the root of your shared files. Options set here will be applied to all files.")
105 - : { k: 'name', required: true, helperText: hasSource && "You can decide a name that's different from the one on your disk" },
106 - { k: 'id', comp: LinkField, statusApi },
107 - { k: 'source', label: "Source on disk", comp: FileField, files: !isDir, folders: isDir, multiline: true,
106 + : { k: 'name', required: true, xl: 6, helperText: hasSource && "You can decide a name that's different from the one on your disk" },
107 + { k: 'source', label: "Source on disk", xl: true, comp: FileField, files: !isDir, folders: isDir, multiline: true,
108 helperText: !values.source && "Not on disk, this is a virtual folder",
109 },
110 + { k: 'id', comp: LinkField, statusApi, xs: 12 },
111 perm('can_read', "Who can see but not download will be asked to login"),
112 perm('can_see', "If you can't see, you may still download with a direct link"),
113 + perm('can_archive', "Should this be included when user downloads as ZIP", { label: "Who can zip", lg: isDir ? true : 12 }),
114 isDir && perm('can_list', "Permission to see content of folders", { contentText: "subfolders" }),
115 isDir && perm('can_delete', [needSourceWarning, "Those who can delete can also rename"]),
114 - isDir && perm('can_upload', needSourceWarning, { lg: showAccept ? 6 : 12, contentText: "subfolders" }),
115 - showAccept && { k: 'accept', label: "Accept on upload", placeholder: "anything", lg: 6,
116 - helperText: h(Link, { href: ACCEPT_LINK, target: '_blank' }, "Example: .zip") },
116 + isDir && perm('can_upload', needSourceWarning, { contentText: "subfolders" }),
117 showSize && { k: 'size', comp: DisplayField, lg: 4, toField: formatBytes },
118 - showTimestamps && { k: 'ctime', comp: DisplayField, md: 6, lg: showSize && 4, label: 'Created', toField: formatTimestamp },
119 - showTimestamps && { k: 'mtime', comp: DisplayField, md: 6, lg: showSize && 4, label: 'Modified', toField: formatTimestamp },
120 - file.website && { k: 'default', comp: BoolField, label:"Serve index.html",
118 + showTimestamps && { k: 'ctime', comp: DisplayField, md: 6, lg: showSize && 4, label: "Created", toField: formatTimestamp },
119 + showTimestamps && { k: 'mtime', comp: DisplayField, md: 6, lg: showSize && 4, label: "Modified", toField: formatTimestamp },
120 + showAccept && { k: 'accept', label: "Accept on upload", placeholder: "anything", xl: file.website ? 4 : 12,
121 + helperText: h(Link, { href: ACCEPT_LINK, target: '_blank' }, "Example: .zip") },
122 + file.website && { k: 'default', comp: BoolField, label:"Serve index.html", xl: true,
123 toField: Boolean, fromField: (v:boolean) => v ? 'index.html' : null,
124 helperText: md("This folder may be a website because contains `index.html`. Enabling this will show the website instead of the list of files.")
125 },
124 - isDir && { k: 'masks', multiline: true, lg: true,
126 + isDir && { k: 'masks', multiline: true,
127 toField: yaml.stringify, fromField: v => v ? yaml.parse(v) : undefined,
128 sx: { '& textarea': { fontFamily: 'monospace' } },
129 helperText: ["Special field, leave empty unless you know what you are doing. YAML syntax. ", wikiLink('Permissions', "(examples)")]
@@ -133,7 +135,7 @@ export default function FileForm({ file, anyMask, addToBar, statusApi }: FileFor
135 return {
136 showInherited: anyMask, // with masks, you may need to set a permission to override the mask
137 otherPerms: _.without(Object.keys(defaultPerms), perm).map(x => ({ value: x, label: "As " +perm2word(x) })),
136 - k: perm, lg: 6, comp: WhoField, parent, accounts, helperText,
138 + k: perm, lg: 6, xl: 4, comp: WhoField, parent, accounts, helperText,
139 label: "Who can " + perm2word(perm),
140 inherit: inheritedPerms[perm],
141 byMasks: byMasks?.[perm],
admin/src/VfsPage.ts
+3 -3
@@ -114,14 +114,14 @@ export default function VfsPage() {
114 reactJoin(" or ", urls.slice(0,3).map(href => h(Link, { href }, href)))
115 ]
116 }
117 - return h(Grid, { container:true, rowSpacing: 1, maxWidth: '80em', columnSpacing: 2 },
117 + return h(Grid, { container:true, rowSpacing: 1, columnSpacing: 2 },
118 alert && h(Grid, { item: true, mb: 2, xs: 12 }, h(Alert, alert)),
119 - h(Grid, { item:true, [sideBreakpoint]: 6, lg: 5 },
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),
123 vfs && h(VfsTree, { id2node })),
124 - isSideBreakpoint && sideContent && h(Grid, { item:true, [sideBreakpoint]: 6, lg: 7, maxWidth:'100%' },
124 + isSideBreakpoint && sideContent && h(Grid, { item:true, [sideBreakpoint]: true, maxWidth:'100%' },
125 h(Card, {}, h(CardContent, {}, sideContent) ))
126 )
127 }
src/cross.ts
+2
@@ -41,6 +41,7 @@ export interface VfsPerms {
41 can_list?: Who
42 can_upload?: Who
43 can_delete?: Who
44 + can_archive?: Who
45 }
46 export const WHO_ANYONE = true
47 export const WHO_NO_ONE = false
@@ -60,6 +61,7 @@ export const defaultPerms: Required<VfsPerms> = {
61 can_list: 'can_read',
62 can_upload: WHO_NO_ONE,
63 can_delete: WHO_NO_ONE,
64 + can_archive: 'can_read'
65 }
66
67 export const PERM_KEYS = typedKeys(defaultPerms)
src/zip.ts
+3 -3
@@ -20,7 +20,7 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
20 const name = list?.length === 1 ? decodeURIComponent(basename(list[0]!)) : getNodeName(node)
21 ctx.attachment((isWindowsDrive(name) ? name[0] : (name || 'archive')) + '.zip')
22 const filter = pattern2filter(String(ctx.query.search||''))
23 - const walker = !list ? walkNode(node, ctx, Infinity, '', 'can_read')
23 + const walker = !list ? walkNode(node, ctx, Infinity, '', 'can_archive')
24 : (async function*(): AsyncIterableIterator<VfsNode> {
25 for await (const uri of list) {
26 const subNode = await urlToNode(uri, ctx, node)
@@ -29,7 +29,7 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
29 if (await nodeIsDirectory(subNode)) { // a directory needs to walked
30 if (hasPermission(subNode, 'can_list',ctx)) {
31 yield subNode // it could be empty
32 - yield* walkNode(subNode, ctx, Infinity, decodeURI(uri) + '/', 'can_read')
32 + yield* walkNode(subNode, ctx, Infinity, decodeURI(uri) + '/', 'can_archive')
33 }
34 continue
35 }
@@ -39,7 +39,7 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
39 }
40 })()
41 const mappedWalker = filterMapGenerator(walker, async (el:VfsNode) => {
42 - if (!hasPermission(el, 'can_read', ctx)) return // the fact you see it doesn't mean you can read it
42 + if (!hasPermission(el, 'can_archive', ctx)) return // the fact you see it doesn't mean you can get it
43 const { source } = el
44 const name = getNodeName(el)
45 if (ctx.req.aborted || !filter(name))