custom.html
Massimo Melina committed
Feb 24, 2023 at 19:54 UTC
9b9ba605ba61723152fedd96d8c291cb5fdf9b4b
7 files changed
+62
-14
README.md
-1
@@ -165,7 +165,6 @@ an *env* called `HFS_CONFIG`. Any relative path provided is relative to the *cwd
165
Syntax supports, other than simple address, `*` as wildcard and CIDR format.
166
- `plugins_config` this is a generic place where you can find/put configuration for each plugin, at least those that need configuration.
167
- `enable_plugins` if a plugin is not present here, it won't run. Defaults is `[ antibrute ]`.
168
-- `custom_header` provide HTML code to be put at the top of your Frontend. Default is none.
168
- `localhost_admin` should Admin be accessed without credentials when on localhost. Default is true.
169
- `proxies` number of proxies between server and clients to be trusted about providing clients' IP addresses. Default is 0.
170
- `keep_unfinished_uploads` should unfinished uploads be deleted immediately when interrupted. Default is true.
admin/src/OptionsPage.ts
-3
@@ -128,9 +128,6 @@ export default function OptionsPage() {
128
label: "Min. available disk space", helperText: "Reject uploads that don't comply" },
129
{ k: 'zip_calculate_size_for_seconds', comp: NumberField, label: "Calculate ZIP size for", unit: "seconds",
130
helperText: "If time is not enough, the browser will not show download percentage" },
131
- { k: 'custom_header', multiline: true, sm: 12, md: 6, sx: { '& textarea': { fontFamily: 'monospace' } },
132
- helperText: "Any HTML code here will be displayed on top of the Frontend"
133
- },
131
{ k: 'mime', comp: StringStringField,
132
keyLabel: "Files", keyWidth: 7,
133
valueLabel: "Mime type", valueWidth: 4
frontend/src/components.ts
+6
-4
@@ -54,12 +54,14 @@ export function Html({ code, ...rest }:{ code:string } & HTMLAttributes<any>) {
54
}
55
56
export function useCustomCode(name: string, props={}) {
57
- const code = useMemo(()=>
58
- hfsEvent(name, props)
57
+ const code = useMemo(() => {
58
+ const ret = hfsEvent(name, props)
59
.filter(x => x === 0 || x)
60
.map((x, key) => isValidElement(x) ? h(Fragment, { key }, x)
61
: typeof x === 'string' ? h(Html, { key, code: x })
62
- : h('div', { key }, x)),
63
- Object.values(props))
62
+ : h('div', { key }, x))
63
+ ret.push((window as any).HFS.customHtml[name] || null)
64
+ return ret
65
+ }, Object.values(props))
66
return h(Fragment, { children: code })
67
}
src/customHtml.ts
new
+40
@@ -0,0 +1,40 @@
1
+import { existsSync, writeFileSync } from 'fs'
2
+import events from './events'
3
+import { prefix } from './misc'
4
+import { customHeader } from './frontEndApis'
5
+import { watchLoad } from './watchLoad'
6
+import { proxy } from 'valtio'
7
+
8
+export const customHtmlState = proxy<{
9
+ sections: Map<string,string>
10
+}>({
11
+ sections: new Map()
12
+})
13
+
14
+const FILE = 'custom.html'
15
+
16
+if (!existsSync(FILE))
17
+ events.once('config ready', () => {
18
+ const legacy = prefix('[beforeHeader]\n', customHeader.get())
19
+ writeFileSync(FILE, legacy)
20
+ customHeader.set(undefined) // get rid of it
21
+ })
22
+watchLoad(FILE, data => {
23
+ const re = /^\[(\w+)] *$/gm
24
+ customHtmlState.sections.clear()
25
+ if (!data) return
26
+ let name: string | undefined = 'top'
27
+ do {
28
+ let last = re.lastIndex
29
+ const match = re.exec(data)
30
+ const content = data.slice(last, !match ? undefined : re.lastIndex - (match?.[0]?.length || 0)).trim()
31
+ if (content)
32
+ customHtmlState.sections.set(name, content)
33
+ name = match?.[1]
34
+ } while (name)
35
+})
36
+
37
+export function getSection(name: string) {
38
+ return customHtmlState.sections.get(name) || ''
39
+}
40
+
src/frontEndApis.ts
+1
-1
@@ -20,7 +20,7 @@ import { mkdir, readFile, rm } from 'fs/promises'
20
import { join } from 'path'
21
import { wantArray } from './misc'
22
23
-const customHeader = defineConfig('custom_header')
23
+export const customHeader = defineConfig<string | undefined>('custom_header')
24
25
export const frontEndApis: ApiHandlers = {
26
file_list,
src/misc.ts
+1
-1
@@ -20,7 +20,7 @@ export function enforceFinal(sub:string, s:string) {
20
return s.endsWith(sub) ? s : s+sub
21
}
22
23
-export function prefix(pre:string, v:string|number, post:string='') {
23
+export function prefix(pre:string, v:string|number|undefined, post:string='') {
24
return v ? pre+v+post : ''
25
}
26
src/serveGuiFiles.ts
+14
-4
@@ -18,6 +18,8 @@ import { ApiError } from './apiMiddleware'
18
import { join, extname } from 'path'
19
import { getOrSet, newObj, onlyTruthy } from './misc'
20
import { favicon, title } from './adminApis'
21
+import { subscribe } from 'valtio'
22
+import { customHtmlState, getSection } from './customHtml'
23
import _ from 'lodash'
24
25
// in case of dev env we have our static files within the 'dist' folder'
@@ -25,7 +27,8 @@ const DEV_STATIC = process.env.DEV ? 'dist/' : ''
27
28
function serveStatic(uri: string): Koa.Middleware {
29
const folder = uri.slice(2,-1) // we know folder is very similar to uri
28
- const cache: Record<string, Promise<string>> = {}
30
+ let cache: Record<string, Promise<string>> = {}
31
+ subscribe(customHtmlState, () => cache = {}) // reset cache at every change
32
return async (ctx, next) => {
33
if(ctx.method === 'OPTIONS') {
34
ctx.status = HTTP_NO_CONTENT
@@ -82,7 +85,7 @@ async function treatIndex(ctx: Koa.Context, body: string, filesUri: string) {
85
configs = getPluginInfo(name).onFrontendConfig?.(configs) || configs
86
return !_.isEmpty(configs) && [name, configs]
87
})))
85
- return body
88
+ let ret = body
89
.replace(/((?:src|href) *= *['"])\/?(?![a-z]+:\/\/)/g, '$1' + filesUri)
90
.replace('<HFS/>', () => `
91
${!isFrontend ? '' : `
@@ -94,8 +97,10 @@ async function treatIndex(ctx: Koa.Context, body: string, filesUri: string) {
97
VERSION,
98
API_VERSION,
99
session: session instanceof ApiError ? null : session,
97
- plugins
98
- }, null, 4)}
100
+ plugins,
101
+ customHtml: _.omit(Object.fromEntries(customHtmlState.sections),
102
+ ['top','bottom']), // excluding sections we apply in this phase
103
+ }, null, 4)}
104
document.documentElement.setAttribute('ver', '${VERSION.split('-')[0] /*for style selectors*/}')
105
</script>
106
<style>
@@ -107,6 +112,11 @@ async function treatIndex(ctx: Koa.Context, body: string, filesUri: string) {
112
${css.map(uri => `<link rel='stylesheet' type='text/css' href='${uri}'/>`).join('\n')}
113
${js.map(uri => `<script defer src='${uri}'></script>`).join('\n')}
114
`)
115
+ if (isFrontend)
116
+ ret = ret
117
+ .replace('<body>', '<body>' + getSection('top'))
118
+ .replace('</body>', getSection('bottom') + '</body>')
119
+ return ret
120
}
121
122
function serializeCss(v: any) {