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))