Compare commits
384 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a722bcc13f | ||
|
|
77fe3cfc60 | ||
|
|
470f93cefc | ||
|
|
92fde4dd12 | ||
|
|
95bc92955f | ||
|
|
f2f914221c | ||
|
|
c2d8038c63 | ||
|
|
cb8ac5ebf1 | ||
|
|
aa78e3ab1f | ||
|
|
bc00165094 | ||
|
|
94ef59602f | ||
|
|
14e2f84ceb | ||
|
|
f228fa5540 | ||
|
|
f2d2c1cbf8 | ||
|
|
d9be370e24 | ||
|
|
727c63b98e | ||
|
|
34dfb49b71 | ||
|
|
0b0a704d44 | ||
|
|
2d99d0bf13 | ||
|
|
1790df2090 | ||
|
|
10570ade44 | ||
|
|
43526d9d1a | ||
|
|
2636f876ab | ||
|
|
eed9da1471 | ||
|
|
9a2ebbabe2 | ||
|
|
716396a726 | ||
|
|
0727496601 | ||
|
|
194030fcfc | ||
|
|
b3b644527d | ||
|
|
7e5beeff46 | ||
|
|
a47b69bcec | ||
|
|
6ec6a23861 | ||
|
|
c9cc0d3d5d | ||
|
|
28d2b35718 | ||
|
|
b4f131be50 | ||
|
|
d0b359561f | ||
|
|
453636dfe2 | ||
|
|
b1605aa6d3 | ||
|
|
23503b80a4 | ||
|
|
0d69fbd9a3 | ||
|
|
0d665e528f | ||
|
|
de0b8bb7b2 | ||
|
|
84da110085 | ||
|
|
6b0d49b1fc | ||
|
|
4c20772e11 | ||
|
|
68f8348dde | ||
|
|
5023e77296 | ||
|
|
95316cbe8c | ||
|
|
cd454bae51 | ||
|
|
241201657c | ||
|
|
9eefaddd9b | ||
|
|
d6d47bbd6b | ||
|
|
82c883f95e | ||
|
|
dd40b0d9b9 | ||
|
|
963837ef1d | ||
|
|
66863b72f7 | ||
|
|
89773447a5 | ||
|
|
6d899a6335 | ||
|
|
28672c0114 | ||
|
|
b8300b7121 | ||
|
|
584ef4d4bd | ||
|
|
e8295a944a | ||
|
|
f8f5698ad0 | ||
|
|
700f32718e | ||
|
|
54d92a2708 | ||
|
|
ba47e3b2fe | ||
|
|
6e5405eeed | ||
|
|
45326e664f | ||
|
|
6ce44f7092 | ||
|
|
b320419088 | ||
|
|
ca183a4fb8 | ||
|
|
895bb755cd | ||
|
|
a9e715dc50 | ||
|
|
7cb046c542 | ||
|
|
cd03faf0fc | ||
|
|
87ba03b224 | ||
|
|
6458f91e1c | ||
|
|
312ebbbcc0 | ||
|
|
060a7ad80c | ||
|
|
ae893abc5f | ||
|
|
12d6415f7f | ||
|
|
897ac75281 | ||
|
|
cec551c3de | ||
|
|
cb98c913d4 | ||
|
|
55a9d945cc | ||
|
|
cc7ec4f0c5 | ||
|
|
265b81a52b | ||
|
|
b42b09ccbe | ||
|
|
118071ba4b | ||
|
|
73b85eced4 | ||
|
|
997a0a433f | ||
|
|
0d7e344ca3 | ||
|
|
1884d50c3b | ||
|
|
f5fad7a01d | ||
|
|
5c2ed2b2f9 | ||
|
|
05475eb4fc | ||
|
|
9e6cc302c0 | ||
|
|
d422421cf9 | ||
|
|
23a3ef069e | ||
|
|
2a81ea90db | ||
|
|
5fb7207d65 | ||
|
|
d79d864825 | ||
|
|
d249b8b202 | ||
|
|
e9bd68f3b0 | ||
|
|
506e088236 | ||
|
|
c906d296be | ||
|
|
3b7f6ccf8e | ||
|
|
f1a7d2f8d0 | ||
|
|
fb13ded8e8 | ||
|
|
85e4ff67e4 | ||
|
|
6250efa208 | ||
|
|
f1e1a27408 | ||
|
|
076358ab79 | ||
|
|
d1efc14bb9 | ||
|
|
508b7b326f | ||
|
|
d1284972a3 | ||
|
|
cdba1d0c52 | ||
|
|
ec28375208 | ||
|
|
01068a9217 | ||
|
|
7f01753bc5 | ||
|
|
0e223a056e | ||
|
|
9d08f9bed1 | ||
|
|
2cabeb8f68 | ||
|
|
7aaebab348 | ||
|
|
928cdfe2ae | ||
|
|
edb7b4dc17 | ||
|
|
85ee63af43 | ||
|
|
74b23a0bc5 | ||
|
|
be6c0bb850 | ||
|
|
ddb670ae1e | ||
|
|
4752758cf7 | ||
|
|
bd8aab4cba | ||
|
|
c61ede4153 | ||
|
|
efd46d6bd3 | ||
|
|
f2a8abb264 | ||
|
|
978aadc9b1 | ||
|
|
0626f07270 | ||
|
|
0afc8c9e5c | ||
|
|
28480c6acd | ||
|
|
826cdddca7 | ||
|
|
ec92ad9f47 | ||
|
|
ce97b9b9fd | ||
|
|
bbd93e111d | ||
|
|
f7c7d50e54 | ||
|
|
854d8bb705 | ||
|
|
7c0f261a97 | ||
|
|
7b9861b2c6 | ||
|
|
3a8fcbf4bc | ||
|
|
83d9247df6 | ||
|
|
d32286a13d | ||
|
|
621936f461 | ||
|
|
802e715fae | ||
|
|
adcafff384 | ||
|
|
a5ce1cf1e1 | ||
|
|
87d18a3089 | ||
|
|
e7fc0e97d6 | ||
|
|
34bdb8fcfc | ||
|
|
13b04f7672 | ||
|
|
76b9b2fa37 | ||
|
|
896d7cfbed | ||
|
|
0fb1b0840f | ||
|
|
1c0250075b | ||
|
|
cf2af817b9 | ||
|
|
ec24f79204 | ||
|
|
be902be453 | ||
|
|
888e08792e | ||
|
|
adc6ef22d9 | ||
|
|
0318d39112 | ||
|
|
abcfa0a05b | ||
|
|
a4b5c99ebb | ||
|
|
546e61a403 | ||
|
|
dcb68bd7a8 | ||
|
|
d411720234 | ||
|
|
1ae887d77c | ||
|
|
30465a771e | ||
|
|
f004b48b99 | ||
|
|
fc5e2247f6 | ||
|
|
5956647bd0 | ||
|
|
87eaf3ce5c | ||
|
|
73eba60210 | ||
|
|
4597ceb3a6 | ||
|
|
c0c25344c8 | ||
|
|
5efb36103f | ||
|
|
c0575a68ee | ||
|
|
ffd8a3a637 | ||
|
|
425ec275e9 | ||
|
|
3b9f336634 | ||
|
|
f792c31046 | ||
|
|
55a54ff89e | ||
|
|
78a40c9b14 | ||
|
|
17f32d16cc | ||
|
|
d57a0f2ae1 | ||
|
|
d6fdfef243 | ||
|
|
62d28dc89e | ||
|
|
57c65156f7 | ||
|
|
6e54dff40d | ||
|
|
0e722c8df1 | ||
|
|
f05479865c | ||
|
|
4e4055e7a8 | ||
|
|
7414ca10b3 | ||
|
|
1e17dfa6cb | ||
|
|
64d6d9e93b | ||
|
|
68902312cc | ||
|
|
a52b50b706 | ||
|
|
2527bdbfe1 | ||
|
|
473aaf13be | ||
|
|
0844b597f8 | ||
|
|
d45d7f92fb | ||
|
|
b3a822b4e8 | ||
|
|
788fadbd5e | ||
|
|
40f29e1e9b | ||
|
|
a036a25e1d | ||
|
|
abed362dc5 | ||
|
|
ce78299464 | ||
|
|
030f6607f0 | ||
|
|
c3a4e33245 | ||
|
|
aabf0843ab | ||
|
|
748e4acfb6 | ||
|
|
6e48a6b512 | ||
|
|
88500ab219 | ||
|
|
d0f8c141e1 | ||
|
|
34a1bf1380 | ||
|
|
b87ba12a7d | ||
|
|
bb0d048235 | ||
|
|
b991c65d8b | ||
|
|
b3b5db351f | ||
|
|
9562e06b92 | ||
|
|
7fc4899507 | ||
|
|
d649ae6ff7 | ||
|
|
633579e738 | ||
|
|
a65cb32d70 | ||
|
|
f1a7bc54ea | ||
|
|
8e1815944b | ||
|
|
db924c475a | ||
|
|
d970bb7de7 | ||
|
|
1fa91adae4 | ||
|
|
604487920d | ||
|
|
72e74d421c | ||
|
|
df5fc427ef | ||
|
|
12088154fe | ||
|
|
8ec27734bb | ||
|
|
1e6a0939a2 | ||
|
|
e58daaac83 | ||
|
|
7d0f25e530 | ||
|
|
cba41a1a32 | ||
|
|
997f21fc55 | ||
|
|
ead7fb4233 | ||
|
|
4590884a34 | ||
|
|
cc6689ac3a | ||
|
|
3ab225a101 | ||
|
|
31b70a7736 | ||
|
|
bbeadee98e | ||
|
|
b8169b6ebe | ||
|
|
df42e352f7 | ||
|
|
1f985fe72f | ||
|
|
575296d7fc | ||
|
|
b93dc9f200 | ||
|
|
6255f737ba | ||
|
|
3ed2144a0e | ||
|
|
a437761d03 | ||
|
|
b432e1bf46 | ||
|
|
8dd59e3e67 | ||
|
|
fd5543407a | ||
|
|
48d012ff92 | ||
|
|
2c4eae5ca2 | ||
|
|
629646122f | ||
|
|
d4f284f1a3 | ||
|
|
ff3b5b39a5 | ||
|
|
9667980f2d | ||
|
|
188a34f835 | ||
|
|
f9cd5f11d9 | ||
|
|
adedf0178b | ||
|
|
4e15b82896 | ||
|
|
ed0ea34161 | ||
|
|
e2ffd36073 | ||
|
|
6bd2a1852f | ||
|
|
371236e364 | ||
|
|
6cbdc9d7c5 | ||
|
|
a94125f3f2 | ||
|
|
2f5f5d75a7 | ||
|
|
02f2284f3b | ||
|
|
2b1305a315 | ||
|
|
ec78f67abd | ||
|
|
25c04af500 | ||
|
|
c0391d866e | ||
|
|
dcb97be587 | ||
|
|
c6eb98aef2 | ||
|
|
1c6e15c064 | ||
|
|
711a3a30b0 | ||
|
|
e203ca14f4 | ||
|
|
0f1b69b625 | ||
|
|
176eaad70b | ||
|
|
486dfe4e63 | ||
|
|
81cf4bab99 | ||
|
|
9c3f563f83 | ||
|
|
6b42781c21 | ||
|
|
891a0d1bd0 | ||
|
|
3b9063dc63 | ||
|
|
2bfdffb9c4 | ||
|
|
9f8685bf10 | ||
|
|
b58bc414bf | ||
|
|
0b81723118 | ||
|
|
66418ec064 | ||
|
|
d87640a4f1 | ||
|
|
e5580ac0c4 | ||
|
|
b92c800e00 | ||
|
|
e370fbe500 | ||
|
|
89d4d828b9 | ||
|
|
d004015f03 | ||
|
|
ba5b5fbfe3 | ||
|
|
dd29a87107 | ||
|
|
b394540f53 | ||
|
|
3ed5f8c0bd | ||
|
|
5d5cef2a87 | ||
|
|
9264e344d7 | ||
|
|
0a46ac3e1b | ||
|
|
a438fc746f | ||
|
|
0c8ffaf73e | ||
|
|
6c1bbb3248 | ||
|
|
e663c60a89 | ||
|
|
e1e8979e0b | ||
|
|
de53b24536 | ||
|
|
bc518a0e82 | ||
|
|
2ed87febcb | ||
|
|
ee169b3a46 | ||
|
|
90d690c187 | ||
|
|
0e7d4ef110 | ||
|
|
7a6397af22 | ||
|
|
ac512612e7 | ||
|
|
95fc3dfdfb | ||
|
|
39be89780e | ||
|
|
2642333928 | ||
|
|
7e1d745435 | ||
|
|
218e638f88 | ||
|
|
cad2a989c1 | ||
|
|
f844aeb2b4 | ||
|
|
7847763a31 | ||
|
|
3f49bc382e | ||
|
|
3ae9e518a3 | ||
|
|
1e5ced6737 | ||
|
|
fa67652ba4 | ||
|
|
62106cc0a4 | ||
|
|
99740e3eab | ||
|
|
c12adbb594 | ||
|
|
157b2da133 | ||
|
|
208f21728f | ||
|
|
0377080da6 | ||
|
|
f5c48c9679 | ||
|
|
99ef1308ea | ||
|
|
9f075c16c5 | ||
|
|
2f17f19425 | ||
|
|
06f00e9664 | ||
|
|
c681174adf | ||
|
|
4a12ce1888 | ||
|
|
4e39f2387a | ||
|
|
e354098b96 | ||
|
|
9086720c3c | ||
|
|
2bb9171e32 | ||
|
|
294efef38c | ||
|
|
4f1d25fba7 | ||
|
|
d3e363a4d5 | ||
|
|
a10f286f0f | ||
|
|
390c53097f | ||
|
|
4c30b2c665 | ||
|
|
325e6e0904 | ||
|
|
56ec440272 | ||
|
|
0af5e07eed | ||
|
|
8a764ceb67 | ||
|
|
fe829aa850 | ||
|
|
2ca22656d6 | ||
|
|
cb7fa99fd3 | ||
|
|
441639a8d5 | ||
|
|
a78e1d504b | ||
|
|
02f6b0ec61 | ||
|
|
8c60cc7084 | ||
|
|
a7e4596e97 | ||
|
|
97d53ceb2e | ||
|
|
a49fb20885 | ||
|
|
2d5e97e140 | ||
|
|
ebdf5a0601 | ||
|
|
9ca02c90ed | ||
|
|
0595638228 | ||
|
|
1f4d0cc3cd | ||
|
|
e6c0d1c28a |
92
.circleci/config.yml
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
docker:
|
||||||
|
- image: golangci/golangci-lint:v1.27.0
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: golangci-lint run -v
|
||||||
|
build-node:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: "Build"
|
||||||
|
command: ./wizard.sh -a
|
||||||
|
- run:
|
||||||
|
name: "Cleanup"
|
||||||
|
command: rm -rf frontend/node_modules
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: .
|
||||||
|
paths:
|
||||||
|
- '*'
|
||||||
|
test:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.14.6
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: "Test"
|
||||||
|
command: go test ./...
|
||||||
|
build-go:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.14.6
|
||||||
|
steps:
|
||||||
|
- attach_workspace:
|
||||||
|
at: '~/project'
|
||||||
|
- run:
|
||||||
|
name: "Compile"
|
||||||
|
command: GOOS=linux GOARCH=amd64 ./wizard.sh -c
|
||||||
|
- run:
|
||||||
|
name: "Cleanup"
|
||||||
|
command: |
|
||||||
|
rm -rf frontend/build
|
||||||
|
git checkout -- go.sum # TODO: why is it being changed?
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: .
|
||||||
|
paths:
|
||||||
|
- '*'
|
||||||
|
release:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.14.6
|
||||||
|
steps:
|
||||||
|
- attach_workspace:
|
||||||
|
at: '~/project'
|
||||||
|
- setup_remote_docker
|
||||||
|
- run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
|
||||||
|
- run: curl -sL https://git.io/goreleaser | bash
|
||||||
|
- run: docker logout
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
build-workflow:
|
||||||
|
jobs:
|
||||||
|
- lint:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /.*/
|
||||||
|
- test:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /.*/
|
||||||
|
- build-node:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /.*/
|
||||||
|
- build-go:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /.*/
|
||||||
|
requires:
|
||||||
|
- build-node
|
||||||
|
- lint
|
||||||
|
- test
|
||||||
|
- release:
|
||||||
|
context: deploy
|
||||||
|
requires:
|
||||||
|
- build-go
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /^v.*/
|
||||||
|
branches:
|
||||||
|
ignore: /.*/
|
||||||
6
.github/ISSUE_TEMPLATE/caddy_bug_report.md
vendored
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
name: Caddy related bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
---
|
|
||||||
|
|
||||||
### Please open the issue on https://github.com/filebrowser/caddy/issues/new
|
|
||||||
26
.gitignore
vendored
@@ -3,4 +3,28 @@
|
|||||||
*.bak
|
*.bak
|
||||||
_old
|
_old
|
||||||
rice-box.go
|
rice-box.go
|
||||||
.idea/
|
.idea/
|
||||||
|
filebrowser
|
||||||
|
dist/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/frontend/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw*
|
||||||
|
|||||||
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "frontend"]
|
|
||||||
path = frontend
|
|
||||||
url = https://github.com/filebrowser/frontend
|
|
||||||
132
.golangci.yml
@@ -1,20 +1,132 @@
|
|||||||
run:
|
linters-settings:
|
||||||
deadline: 5m
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
exhaustive:
|
||||||
|
default-signifies-exhaustive: false
|
||||||
|
funlen:
|
||||||
|
lines: 100
|
||||||
|
statements: 50
|
||||||
|
goconst:
|
||||||
|
min-len: 2
|
||||||
|
min-occurrences: 2
|
||||||
|
gocritic:
|
||||||
|
enabled-tags:
|
||||||
|
- diagnostic
|
||||||
|
- experimental
|
||||||
|
- opinionated
|
||||||
|
- performance
|
||||||
|
- style
|
||||||
|
disabled-checks:
|
||||||
|
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||||
|
- ifElseChain
|
||||||
|
- octalLiteral
|
||||||
|
- whyNoLint
|
||||||
|
- wrapperFunc
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 15
|
||||||
|
goimports:
|
||||||
|
local-prefixes: github.com/filebrowser/filebrowser
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
gomnd:
|
||||||
|
settings:
|
||||||
|
mnd:
|
||||||
|
# don't include the "operation" and "assign"
|
||||||
|
checks: argument,case,condition,return
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
lll:
|
||||||
|
line-length: 140
|
||||||
|
maligned:
|
||||||
|
suggest-new: true
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
nolintlint:
|
||||||
|
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
|
||||||
|
allow-unused: false # report any unused nolint directives
|
||||||
|
require-explanation: false # don't require an explanation for nolint directives
|
||||||
|
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
|
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||||
|
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
|
||||||
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
|
- bodyclose
|
||||||
- deadcode
|
- deadcode
|
||||||
|
- depguard
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- errcheck
|
||||||
|
- funlen
|
||||||
|
- gochecknoinits
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- golint
|
||||||
|
- gomnd
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- gosimple
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- interfacer
|
- interfacer
|
||||||
- maligned
|
- lll
|
||||||
- megacheck
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- nolintlint
|
||||||
|
- rowserrcheck
|
||||||
|
- scopelint
|
||||||
|
- staticcheck
|
||||||
- structcheck
|
- structcheck
|
||||||
|
- stylecheck
|
||||||
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unused
|
||||||
- varcheck
|
- varcheck
|
||||||
enable-all: false
|
- whitespace
|
||||||
disable-all: true
|
- prealloc
|
||||||
# presets:
|
|
||||||
# - bugs
|
# don't enable:
|
||||||
# - unused
|
# - asciicheck
|
||||||
fast: false
|
# - exhaustive (TODO: enable after next release; current release at time of writing is v1.27)
|
||||||
|
# - gochecknoglobals
|
||||||
|
# - gocognit
|
||||||
|
# - godot
|
||||||
|
# - godox
|
||||||
|
# - goerr113
|
||||||
|
# - maligned
|
||||||
|
# - nestif
|
||||||
|
# - testpackage
|
||||||
|
# - wsl
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: cmd/.*.go
|
||||||
|
linters:
|
||||||
|
- gochecknoinits
|
||||||
|
- path: .*_test.go
|
||||||
|
linters:
|
||||||
|
- lll
|
||||||
|
- gochecknoinits
|
||||||
|
- gocyclo
|
||||||
|
- funlen
|
||||||
|
- dupl
|
||||||
|
- scopelint
|
||||||
|
- text: "Auther"
|
||||||
|
linters:
|
||||||
|
- misspell
|
||||||
|
|
||||||
|
run:
|
||||||
|
skip-dirs:
|
||||||
|
- frontend/
|
||||||
|
skip-files:
|
||||||
|
- http/rice-box.go
|
||||||
|
|
||||||
|
# golangci.com configuration
|
||||||
|
# https://github.com/golangci/golangci/wiki/Configuration
|
||||||
|
service:
|
||||||
|
golangci-lint-version: 1.27.x # use the fixed version to not introduce new linters unexpectedly
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
project_name: filebrowser
|
project_name: filebrowser
|
||||||
|
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod download
|
||||||
|
|
||||||
build:
|
build:
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X github.com/filebrowser/filebrowser/v2/version.Version={{ .Version }} -X github.com/filebrowser/filebrowser/v2/version.CommitSHA={{ .ShortCommit }}
|
||||||
main: main.go
|
main: main.go
|
||||||
binary: filebrowser
|
binary: filebrowser
|
||||||
goos:
|
goos:
|
||||||
@@ -35,24 +44,64 @@ build:
|
|||||||
- goos: solaris
|
- goos: solaris
|
||||||
goarch: arm
|
goarch: arm
|
||||||
|
|
||||||
archive:
|
archives:
|
||||||
name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
|
-
|
||||||
format: tar.gz
|
name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
|
||||||
format_overrides:
|
format: tar.gz
|
||||||
- goos: windows
|
format_overrides:
|
||||||
format: zip
|
- goos: windows
|
||||||
|
format: zip
|
||||||
release:
|
|
||||||
disable: true
|
|
||||||
|
|
||||||
dockers:
|
dockers:
|
||||||
-
|
-
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
binaries:
|
||||||
|
- filebrowser
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
goarm: ''
|
goarm: ''
|
||||||
image_templates:
|
image_templates:
|
||||||
- "filebrowser/filebrowser:latest"
|
- "filebrowser/filebrowser:latest"
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}"
|
- "filebrowser/filebrowser:{{ .Tag }}"
|
||||||
skip_push: true
|
- "filebrowser/filebrowser:v{{ .Major }}"
|
||||||
|
extra_files:
|
||||||
|
- .docker.json
|
||||||
|
-
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
binaries:
|
||||||
|
- filebrowser
|
||||||
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: '5'
|
||||||
|
image_templates:
|
||||||
|
- "filebrowser/filebrowser:pi"
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-pi"
|
||||||
|
- "filebrowser/filebrowser:v{{ .Major }}-pi"
|
||||||
|
extra_files:
|
||||||
|
- .docker.json
|
||||||
|
-
|
||||||
|
dockerfile: Dockerfile.alpine
|
||||||
|
binaries:
|
||||||
|
- filebrowser
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
goarm: ''
|
||||||
|
image_templates:
|
||||||
|
- "filebrowser/filebrowser:alpine"
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-alpine"
|
||||||
|
- "filebrowser/filebrowser:v{{ .Major }}-alpine"
|
||||||
|
extra_files:
|
||||||
|
- .docker.json
|
||||||
|
-
|
||||||
|
dockerfile: Dockerfile.debian
|
||||||
|
binaries:
|
||||||
|
- filebrowser
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
goarm: ''
|
||||||
|
image_templates:
|
||||||
|
- "filebrowser/filebrowser:debian"
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-debian"
|
||||||
|
- "filebrowser/filebrowser:v{{ .Major }}-debian"
|
||||||
extra_files:
|
extra_files:
|
||||||
- .docker.json
|
- .docker.json
|
||||||
|
|||||||
49
.travis.yml
@@ -1,49 +0,0 @@
|
|||||||
os: linux
|
|
||||||
services: docker
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.12.x
|
|
||||||
install: skip
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- pass
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- USE_DOCKER="true"
|
|
||||||
- GO111MODULE=on
|
|
||||||
stages:
|
|
||||||
- lint
|
|
||||||
- build
|
|
||||||
- release
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- http/rice-box.go
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- stage: lint
|
|
||||||
script: ./wizard.sh -l
|
|
||||||
- stage: build
|
|
||||||
script: ./wizard.sh -b
|
|
||||||
deploy:
|
|
||||||
provider: script
|
|
||||||
skip_cleanup: true
|
|
||||||
script: ./wizard.sh -p
|
|
||||||
on:
|
|
||||||
tags: false
|
|
||||||
repo: filebrowser/filebrowser
|
|
||||||
branch: master
|
|
||||||
- stage: release
|
|
||||||
script: ./wizard.sh -r "$TRAVIS_TAG"
|
|
||||||
if: tag IS present
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
skip_cleanup: true
|
|
||||||
api_key:
|
|
||||||
secure: GCURbl9xmjOmeNc7cYSvfSwbEp46cacWmJRczcsU6rQa0aWqzjELYdyIsl6HWW+o0dzuZvbWRD6muxYqIud92oPLYDuXSnra9tM3mCjswrjiPCJ57bksWkSPBfFQcxIyB6c3o+A/FMnX3nnSE/2r5HYZnPNFbEcBbC7WSgwx9ejXUuyWn1PUFK9YQWANdl6J7b7EKsk+9MxS9Pmw6M2ycBwX8ScUQdofkUPvR/nqlXISm+3hs30VubqQi9Ha6DM9Bw3aFK3/Ts/ujCOxP1ZoMCBZ6tfnaQOElIG96WTwnt77eDYlZezBOLym3Z18iif+Qny+XndFKDbexaiUT06VlWFXCKtt3iLs6HJwRcjmiHmB0Z3v+W4cKPl3cEyxxrU2aal54k1PBhU+5L0Xc8ileKbDMYg5tps88zWHNefeZVfaxYSVrmUHkuygMe481oaBLacDXTxs4t6XEpStREuLmvx9NLTwTFAbWjMNM0PqlueDMxO4bdwNvzXg/TcKLWV9FezqAlre8lFNZK5wX6lKFVSZ3hFjxCfwrJL2cPwg5A8Yd5EOC4Nh81WdgYuFGOxZzMAoSJlaVRvQS1trCUP/++ONnDep3ExSxvw4B7vijGZWeXUhrOMiPQHXu+t6BnrlnDjQ4gi44QTW0y/iM2WC2DBKfgYjAKwyHx13hFrmOCg=
|
|
||||||
file: "dist/*.*"
|
|
||||||
file_glob: true
|
|
||||||
on:
|
|
||||||
repo: filebrowser/filebrowser
|
|
||||||
all_branches: true
|
|
||||||
tags: true
|
|
||||||
10
.tx/config
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
lang_map = pt_BR: pt-br, zh_CN: zh-cn, zh_HK: zh-hk, zh_TW: zh-tw, nl_BE: nl-be, sv_SE: sv-se
|
||||||
|
|
||||||
|
[file-browser.file-browser]
|
||||||
|
file_filter = frontend/src/i18n/<lang>.json
|
||||||
|
minimum_perc = 50
|
||||||
|
source_file = frontend/src/i18n/en.json
|
||||||
|
source_lang = en
|
||||||
|
type = KEYVALUEJSON
|
||||||
87
CHANGELOG.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.6.0](https://github.com/filebrowser/filebrowser/compare/v2.5.0...v2.6.0) (2020-07-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add lazy load of image thumbnails ([bc00165](https://github.com/filebrowser/filebrowser/commit/bc001650944ae963b12b5b2538a68de7cd0d8f82))
|
||||||
|
* add param to disable img resizing ([aa78e3a](https://github.com/filebrowser/filebrowser/commit/aa78e3ab1fcae6f618e811ba4e315a7a209f9df2))
|
||||||
|
* cache resized images ([95bc929](https://github.com/filebrowser/filebrowser/commit/95bc92955f391ece22c40d9592f2a3e6e26907b9))
|
||||||
|
* limit image resize workers ([94ef596](https://github.com/filebrowser/filebrowser/commit/94ef59602fb50fc21b1164feda90a3b9aeb5e972))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* conflict handling on upload button ([f228fa5](https://github.com/filebrowser/filebrowser/commit/f228fa55408824618e9f0879da67c86d22b0d324))
|
||||||
|
* drop feedback ([f2d2c1c](https://github.com/filebrowser/filebrowser/commit/f2d2c1cbf85fba3edffb7b079f121ed3f0bc1e02))
|
||||||
|
* missing error message ([d9be370](https://github.com/filebrowser/filebrowser/commit/d9be370e2474b8070fa58db920c9481270cc4a48))
|
||||||
|
* parent verification on copy ([727c63b](https://github.com/filebrowser/filebrowser/commit/727c63b98e2964d0960d25914c296570f6c79478))
|
||||||
|
* path separator inconsistency on rename ([34dfb49](https://github.com/filebrowser/filebrowser/commit/34dfb49b719c948e709a4639b4af2c5cb73b3887))
|
||||||
|
|
||||||
|
## [2.5.0](https://github.com/filebrowser/filebrowser/compare/v2.4.0...v2.5.0) (2020-07-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add previewer title and loading indicator ([716396a](https://github.com/filebrowser/filebrowser/commit/716396a726329f0ba42fc34167dd07497c5bf47c))
|
||||||
|
* duplicate files in the same directory ([43526d9](https://github.com/filebrowser/filebrowser/commit/43526d9d1a8c837245e3f5059e0b4737583eeaeb))
|
||||||
|
* file copy, move and paste conflict checking ([eed9da1](https://github.com/filebrowser/filebrowser/commit/eed9da1471723ed3fbe6c00b1d6362b1c5fd8b04))
|
||||||
|
* rename option on replace prompt ([2636f87](https://github.com/filebrowser/filebrowser/commit/2636f876ab8f88eea6d9548de524ca2339eb0843))
|
||||||
|
* upload queue ([6ec6a23](https://github.com/filebrowser/filebrowser/commit/6ec6a2386173410f5cab9941dbf1bacb6b70ddd2))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* blinking previewer ([9a2ebba](https://github.com/filebrowser/filebrowser/commit/9a2ebbabe2e9f0c292701d33f36f9b7a457b1164))
|
||||||
|
* dark theme colors ([b3b6445](https://github.com/filebrowser/filebrowser/commit/b3b644527d5673e16e61d404ff58a3c7bd6b6637))
|
||||||
|
* directory conflict checking ([7e5beef](https://github.com/filebrowser/filebrowser/commit/7e5beeff464e75ab185c430cd96e7cc67209ccc1))
|
||||||
|
* prompt before closing window ([194030f](https://github.com/filebrowser/filebrowser/commit/194030fcfcf54a2cf5e2f8ececcbb4754474d8f8))
|
||||||
|
* remove incomplete uploaded files ([0727496](https://github.com/filebrowser/filebrowser/commit/0727496601a9918c8131c56f62419bfac7ac589a))
|
||||||
|
* reset clipboard after pasting cutted files ([10570ad](https://github.com/filebrowser/filebrowser/commit/10570ade442b573ebe00af08369e28b1b0688df6))
|
||||||
|
|
||||||
|
## [2.4.0](https://github.com/filebrowser/filebrowser/compare/v2.3.0...v2.4.0) (2020-07-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* full screen editor ([0d665e5](https://github.com/filebrowser/filebrowser/commit/0d665e528f880ceda0976ceed66070ac34de7969))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add preview bypass for .gif files ([#1012](https://github.com/filebrowser/filebrowser/issues/1012)) ([453636d](https://github.com/filebrowser/filebrowser/commit/453636dfe2bbf177c74617862eb763485d4774bf))
|
||||||
|
* prompt key shortcut conflict ([0d69fbd](https://github.com/filebrowser/filebrowser/commit/0d69fbd9a342aa2695859021df0c423e3ae4a4fa))
|
||||||
|
|
||||||
|
## [2.3.0](https://github.com/filebrowser/filebrowser/compare/v2.2.0...v2.3.0) (2020-06-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add image thumbnails support ([#980](https://github.com/filebrowser/filebrowser/issues/980)) ([6b0d49b](https://github.com/filebrowser/filebrowser/commit/6b0d49b1fc8bdce89576ba91cc0b8ec594fcd625))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* typo in image_templates (apline -> alpine) ([#1005](https://github.com/filebrowser/filebrowser/issues/1005)) ([84da110](https://github.com/filebrowser/filebrowser/commit/84da11008516a371fc0446d97863dc14d337aa25))
|
||||||
|
|
||||||
|
## [2.2.0](https://github.com/filebrowser/filebrowser/compare/v2.1.2...v2.2.0) (2020-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add alpine and debian docker images ([66863b7](https://github.com/filebrowser/filebrowser/commit/66863b72f7664e6cb9417f7da542a92fa77ca635))
|
||||||
|
* add folder upload ([#981](https://github.com/filebrowser/filebrowser/issues/981)) ([8977344](https://github.com/filebrowser/filebrowser/commit/89773447a56675b298394149d7a05c5df4039f14)), closes [filebrowser/filebrowser#741](https://github.com/filebrowser/filebrowser/issues/741)
|
||||||
|
* add key shortcuts ([95316cb](https://github.com/filebrowser/filebrowser/commit/95316cbe8c8ac3dbb28310bc11ec347c0caf699b))
|
||||||
|
* upload progress based on total size ([#993](https://github.com/filebrowser/filebrowser/issues/993)) ([cd454ba](https://github.com/filebrowser/filebrowser/commit/cd454bae51f40b1249e6fa6133c2949970eb3018))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add a workaround to fix window freezing when viewing a large file [#992](https://github.com/filebrowser/filebrowser/issues/992) ([2412016](https://github.com/filebrowser/filebrowser/commit/241201657c2bf01806d02a297eb846b26102a479))
|
||||||
|
* apply all fs user rulles ([68f8348](https://github.com/filebrowser/filebrowser/commit/68f8348ddeecba570a361e7aba4546052cc3e356))
|
||||||
|
* frontend token validation ([dd40b0d](https://github.com/filebrowser/filebrowser/commit/dd40b0d9b9cc6268a611306ac4684a1af852b79d)), closes [filebrowser/filebrowser#638](https://github.com/filebrowser/filebrowser/issues/638)
|
||||||
|
* multiple selection count ([963837e](https://github.com/filebrowser/filebrowser/commit/963837ef1dc6e2e84fcf924606ce388ac30f3891))
|
||||||
|
* save event hook ([82c883f](https://github.com/filebrowser/filebrowser/commit/82c883f95eead9eebe215e230f74773c945f864a)), closes [filebrowser/filebrowser#696](https://github.com/filebrowser/filebrowser/issues/696)
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
FROM scratch
|
FROM alpine:latest as alpine
|
||||||
|
RUN apk --update add ca-certificates
|
||||||
|
RUN apk --update add mailcap
|
||||||
|
|
||||||
COPY --from=filebrowser/dev /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
FROM scratch
|
||||||
|
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
COPY --from=alpine /etc/mime.types /etc/mime.types
|
||||||
|
|
||||||
VOLUME /srv
|
VOLUME /srv
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|||||||
11
Dockerfile.alpine
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM alpine:latest as alpine
|
||||||
|
RUN apk --update add ca-certificates
|
||||||
|
RUN apk --update add mailcap
|
||||||
|
|
||||||
|
VOLUME /srv
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
COPY .docker.json /.filebrowser.json
|
||||||
|
COPY filebrowser /filebrowser
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/filebrowser" ]
|
||||||
9
Dockerfile.debian
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FROM debian:buster
|
||||||
|
|
||||||
|
VOLUME /srv
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
COPY .docker.json /.filebrowser.json
|
||||||
|
COPY filebrowser /filebrowser
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/filebrowser" ]
|
||||||
16
README.md
@@ -10,22 +10,24 @@
|
|||||||
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
||||||
[](http://webchat.freenode.net/?channels=%23filebrowser)
|
[](http://webchat.freenode.net/?channels=%23filebrowser)
|
||||||
|
|
||||||
> ℹ INFO: **This project is not under active development ATM. A small group of developers keeps the project alive, but due to lack of time, we can't continue adding new features or doing deep changes. Please read [#532](https://github.com/filebrowser/filebrowser/issues/532) for more info!**
|
|
||||||
|
|
||||||
filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app or as a middleware.
|
filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app or as a middleware.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
Please refer to our docs at [docs.filebrowser.xyz/features](https://docs.filebrowser.xyz/features)
|
Please refer to our docs at [https://filebrowser.org/features](https://filebrowser.org/features)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Please refer to our docs at [docs.filebrowser.xyz](https://docs.filebrowser.xyz/).
|
For installation instructions please refer to our docs at [https://filebrowser.org/installation](https://filebrowser.org/installation).
|
||||||
|
|
||||||
## Usage
|
## Configuration
|
||||||
|
|
||||||
Please refer to our docs at [docs.filebrowser.xyz/usage](https://docs.filebrowser.xyz/usage).
|
[Authentication Method](https://filebrowser.org/configuration/authentication-method) - You can change the way the user authenticates with the filebrowser server
|
||||||
|
|
||||||
|
[Commander Runner](https://filebrowser.org/configuration/command-runner) - The command runner is a feature that enables you to execute any shell command you want before or after a certain event.
|
||||||
|
|
||||||
|
[Custom Branding](https://filebrowser.org/configuration/custom-branding) - You can customize your File Browser installation by change its name to any other you want, by adding a global custom style sheet and by using your own logotype if you want.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Please refer to our docs at [docs.filebrowser.xyz/contributing](https://docs.filebrowser.xyz/contributing).
|
If you're interested in contributing to this project, our docs are best places to start [https://filebrowser.org/contributing](https://filebrowser.org/contributing).
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type jsonCred struct {
|
|||||||
ReCaptcha string `json:"recaptcha"`
|
ReCaptcha string `json:"recaptcha"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONAuth is a json implementaion of an Auther.
|
// JSONAuth is a json implementation of an Auther.
|
||||||
type JSONAuth struct {
|
type JSONAuth struct {
|
||||||
ReCaptcha *ReCaptcha `json:"recaptcha" yaml:"recaptcha"`
|
ReCaptcha *ReCaptcha `json:"recaptcha" yaml:"recaptcha"`
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ func (a JSONAuth) Auth(r *http.Request, sto *users.Storage, root string) (*users
|
|||||||
|
|
||||||
// If ReCaptcha is enabled, check the code.
|
// If ReCaptcha is enabled, check the code.
|
||||||
if a.ReCaptcha != nil && len(a.ReCaptcha.Secret) > 0 {
|
if a.ReCaptcha != nil && len(a.ReCaptcha.Secret) > 0 {
|
||||||
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha)
|
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:shadow
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -66,7 +66,7 @@ func (a JSONAuth) LoginPage() bool {
|
|||||||
|
|
||||||
const reCaptchaAPI = "/recaptcha/api/siteverify"
|
const reCaptchaAPI = "/recaptcha/api/siteverify"
|
||||||
|
|
||||||
// ReCaptcha identifies a recaptcha conenction.
|
// ReCaptcha identifies a recaptcha connection.
|
||||||
type ReCaptcha struct {
|
type ReCaptcha struct {
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
@@ -76,7 +76,7 @@ type ReCaptcha struct {
|
|||||||
// Ok checks if a reCaptcha responde is correct.
|
// Ok checks if a reCaptcha responde is correct.
|
||||||
func (r *ReCaptcha) Ok(response string) (bool, error) {
|
func (r *ReCaptcha) Ok(response string) (bool, error) {
|
||||||
body := url.Values{}
|
body := url.Values{}
|
||||||
body.Set("secret", r.Key)
|
body.Set("secret", r.Secret)
|
||||||
body.Add("response", response)
|
body.Add("response", response)
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
@@ -89,6 +89,7 @@ func (r *ReCaptcha) Ok(response string) (bool, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ type Storage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewStorage creates a auth storage from a backend.
|
// NewStorage creates a auth storage from a backend.
|
||||||
func NewStorage(back StorageBackend, users *users.Storage) *Storage {
|
func NewStorage(back StorageBackend, userStore *users.Storage) *Storage {
|
||||||
return &Storage{back: back, users: users}
|
return &Storage{back: back, users: userStore}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get wraps a StorageBackend.Get.
|
// Get wraps a StorageBackend.Get.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ var cmdsAddCmd = &cobra.Command{
|
|||||||
Use: "add <event> <command>",
|
Use: "add <event> <command>",
|
||||||
Short: "Add a command to run on a specific event",
|
Short: "Add a command to run on a specific event",
|
||||||
Long: `Add a command to run on a specific event.`,
|
Long: `Add a command to run on a specific event.`,
|
||||||
Args: cobra.MinimumNArgs(2),
|
Args: cobra.MinimumNArgs(2), //nolint:mnd
|
||||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
s, err := d.store.Settings.Get()
|
s, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ You can also specify an optional parameter (index_end) so
|
|||||||
you can remove all commands from 'index' to 'index_end',
|
you can remove all commands from 'index' to 'index_end',
|
||||||
including 'index_end'.`,
|
including 'index_end'.`,
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil {
|
if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { //nolint:mnd
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ including 'index_end'.`,
|
|||||||
i, err := strconv.Atoi(args[1])
|
i, err := strconv.Atoi(args[1])
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
f := i
|
f := i
|
||||||
if len(args) == 3 {
|
if len(args) == 3 { //nolint:mnd
|
||||||
f, err = strconv.Atoi(args[2])
|
f, err = strconv.Atoi(args[2])
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/auth"
|
"github.com/filebrowser/filebrowser/v2/auth"
|
||||||
"github.com/filebrowser/filebrowser/v2/errors"
|
"github.com/filebrowser/filebrowser/v2/errors"
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -44,15 +45,39 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
|||||||
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuthentication(flags *pflag.FlagSet) (settings.AuthMethod, auth.Auther) {
|
//nolint:gocyclo
|
||||||
|
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther) {
|
||||||
method := settings.AuthMethod(mustGetString(flags, "auth.method"))
|
method := settings.AuthMethod(mustGetString(flags, "auth.method"))
|
||||||
|
|
||||||
|
var defaultAuther map[string]interface{}
|
||||||
|
if len(defaults) > 0 {
|
||||||
|
if hasAuth := defaults[0]; hasAuth != true {
|
||||||
|
for _, arg := range defaults {
|
||||||
|
switch def := arg.(type) {
|
||||||
|
case *settings.Settings:
|
||||||
|
method = def.AuthMethod
|
||||||
|
case auth.Auther:
|
||||||
|
ms, err := json.Marshal(def)
|
||||||
|
checkErr(err)
|
||||||
|
err = json.Unmarshal(ms, &defaultAuther)
|
||||||
|
checkErr(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var auther auth.Auther
|
var auther auth.Auther
|
||||||
if method == auth.MethodProxyAuth {
|
if method == auth.MethodProxyAuth {
|
||||||
header := mustGetString(flags, "auth.header")
|
header := mustGetString(flags, "auth.header")
|
||||||
|
|
||||||
if header == "" {
|
if header == "" {
|
||||||
panic(nerrors.New("you must set the flag 'auth.header' for method 'proxy'"))
|
header = defaultAuther["header"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if header == "" {
|
||||||
|
checkErr(nerrors.New("you must set the flag 'auth.header' for method 'proxy'"))
|
||||||
|
}
|
||||||
|
|
||||||
auther = &auth.ProxyAuth{Header: header}
|
auther = &auth.ProxyAuth{Header: header}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +87,22 @@ func getAuthentication(flags *pflag.FlagSet) (settings.AuthMethod, auth.Auther)
|
|||||||
|
|
||||||
if method == auth.MethodJSONAuth {
|
if method == auth.MethodJSONAuth {
|
||||||
jsonAuth := &auth.JSONAuth{}
|
jsonAuth := &auth.JSONAuth{}
|
||||||
|
|
||||||
host := mustGetString(flags, "recaptcha.host")
|
host := mustGetString(flags, "recaptcha.host")
|
||||||
key := mustGetString(flags, "recaptcha.key")
|
key := mustGetString(flags, "recaptcha.key")
|
||||||
secret := mustGetString(flags, "recaptcha.secret")
|
secret := mustGetString(flags, "recaptcha.secret")
|
||||||
|
|
||||||
|
if key == "" {
|
||||||
|
if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
|
||||||
|
key = kmap["key"].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if secret == "" {
|
||||||
|
if smap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
|
||||||
|
secret = smap["secret"].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if key != "" && secret != "" {
|
if key != "" && secret != "" {
|
||||||
jsonAuth.ReCaptcha = &auth.ReCaptcha{
|
jsonAuth.ReCaptcha = &auth.ReCaptcha{
|
||||||
Host: host,
|
Host: host,
|
||||||
@@ -74,7 +110,6 @@ func getAuthentication(flags *pflag.FlagSet) (settings.AuthMethod, auth.Auther)
|
|||||||
Secret: secret,
|
Secret: secret,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auther = jsonAuth
|
auther = jsonAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +136,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
|
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
|
||||||
fmt.Fprintf(w, "\tBase URL:\t%s\n", ser.BaseURL)
|
fmt.Fprintf(w, "\tBase URL:\t%s\n", ser.BaseURL)
|
||||||
fmt.Fprintf(w, "\tRoot:\t%s\n", ser.Root)
|
fmt.Fprintf(w, "\tRoot:\t%s\n", ser.Root)
|
||||||
|
fmt.Fprintf(w, "\tSocket:\t%s\n", ser.Socket)
|
||||||
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
|
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
|
||||||
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
||||||
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/auth"
|
"github.com/filebrowser/filebrowser/v2/auth"
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -55,7 +56,7 @@ The path must be for a json or yaml file.`,
|
|||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
var rawAuther interface{}
|
var rawAuther interface{}
|
||||||
if filepath.Ext(args[0]) != ".json" {
|
if filepath.Ext(args[0]) != ".json" { //nolint:goconst
|
||||||
rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{}))
|
rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{}))
|
||||||
} else {
|
} else {
|
||||||
rawAuther = file.Auther
|
rawAuther = file.Auther
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -43,6 +44,7 @@ override the options.`,
|
|||||||
|
|
||||||
ser := &settings.Server{
|
ser := &settings.Server{
|
||||||
Address: mustGetString(flags, "address"),
|
Address: mustGetString(flags, "address"),
|
||||||
|
Socket: mustGetString(flags, "socket"),
|
||||||
Root: mustGetString(flags, "root"),
|
Root: mustGetString(flags, "root"),
|
||||||
BaseURL: mustGetString(flags, "baseurl"),
|
BaseURL: mustGetString(flags, "baseurl"),
|
||||||
TLSKey: mustGetString(flags, "key"),
|
TLSKey: mustGetString(flags, "key"),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/auth"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
@@ -34,6 +33,8 @@ you want to change. Other options will remain unchanged.`,
|
|||||||
ser.BaseURL = mustGetString(flags, flag.Name)
|
ser.BaseURL = mustGetString(flags, flag.Name)
|
||||||
case "root":
|
case "root":
|
||||||
ser.Root = mustGetString(flags, flag.Name)
|
ser.Root = mustGetString(flags, flag.Name)
|
||||||
|
case "socket":
|
||||||
|
ser.Socket = mustGetString(flags, flag.Name)
|
||||||
case "cert":
|
case "cert":
|
||||||
ser.TLSCert = mustGetString(flags, flag.Name)
|
ser.TLSCert = mustGetString(flags, flag.Name)
|
||||||
case "key":
|
case "key":
|
||||||
@@ -61,16 +62,15 @@ you want to change. Other options will remain unchanged.`,
|
|||||||
|
|
||||||
getUserDefaults(flags, &set.Defaults, false)
|
getUserDefaults(flags, &set.Defaults, false)
|
||||||
|
|
||||||
var auther auth.Auther
|
// read the defaults
|
||||||
if hasAuth {
|
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||||
set.AuthMethod, auther = getAuthentication(flags)
|
checkErr(err)
|
||||||
err = d.store.Auth.Save(auther)
|
|
||||||
checkErr(err)
|
|
||||||
} else {
|
|
||||||
auther, err = d.store.Auth.Get(set.AuthMethod)
|
|
||||||
checkErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// check if there are new flags for existing auth method
|
||||||
|
set.AuthMethod, auther = getAuthentication(flags, hasAuth, set, auther)
|
||||||
|
|
||||||
|
err = d.store.Auth.Save(auther)
|
||||||
|
checkErr(err)
|
||||||
err = d.store.Settings.Save(set)
|
err = d.store.Settings.Save(set)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
err = d.store.Settings.SaveServer(ser)
|
err = d.store.Settings.SaveServer(ser)
|
||||||
|
|||||||
12
cmd/docs.go
@@ -88,7 +88,7 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
|
|||||||
|
|
||||||
short := cmd.Short
|
short := cmd.Short
|
||||||
long := cmd.Long
|
long := cmd.Long
|
||||||
if len(long) == 0 {
|
if long == "" {
|
||||||
long = short
|
long = short
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,21 +106,21 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
|
|||||||
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
|
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
|
||||||
}
|
}
|
||||||
|
|
||||||
printOptions(buf, cmd, name)
|
printOptions(buf, cmd)
|
||||||
_, err := buf.WriteTo(w)
|
_, err := buf.WriteTo(w)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
|
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
|
||||||
buf.WriteString("| Name | Shorthand | Usage |\n")
|
_, _ = buf.WriteString("| Name | Shorthand | Usage |\n")
|
||||||
buf.WriteString("|------|-----------|-------|\n")
|
_, _ = buf.WriteString("|------|-----------|-------|\n")
|
||||||
|
|
||||||
fs.VisitAll(func(f *pflag.Flag) {
|
fs.VisitAll(func(f *pflag.Flag) {
|
||||||
buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n")
|
_, _ = buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) {
|
func printOptions(buf *bytes.Buffer, cmd *cobra.Command) {
|
||||||
flags := cmd.NonInheritedFlags()
|
flags := cmd.NonInheritedFlags()
|
||||||
flags.SetOutput(buf)
|
flags.SetOutput(buf)
|
||||||
if flags.HasAvailableFlags() {
|
if flags.HasAvailableFlags() {
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
114
cmd/root.go
@@ -2,24 +2,31 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/auth"
|
|
||||||
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/storage"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
v "github.com/spf13/viper"
|
v "github.com/spf13/viper"
|
||||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/auth"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/diskcache"
|
||||||
|
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/img"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/storage"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -50,7 +57,12 @@ func addServerFlags(flags *pflag.FlagSet) {
|
|||||||
flags.StringP("cert", "t", "", "tls certificate")
|
flags.StringP("cert", "t", "", "tls certificate")
|
||||||
flags.StringP("key", "k", "", "tls key")
|
flags.StringP("key", "k", "", "tls key")
|
||||||
flags.StringP("root", "r", ".", "root to prepend to relative paths")
|
flags.StringP("root", "r", ".", "root to prepend to relative paths")
|
||||||
|
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
|
||||||
flags.StringP("baseurl", "b", "", "base url")
|
flags.StringP("baseurl", "b", "", "base url")
|
||||||
|
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
||||||
|
flags.Int("img-processors", 4, "image processors count")
|
||||||
|
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||||
|
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@@ -98,6 +110,24 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
quickSetup(cmd.Flags(), d)
|
quickSetup(cmd.Flags(), d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build img service
|
||||||
|
workersCount, err := cmd.Flags().GetInt("img-processors")
|
||||||
|
checkErr(err)
|
||||||
|
if workersCount < 1 {
|
||||||
|
log.Fatal("Image resize workers count could not be < 1")
|
||||||
|
}
|
||||||
|
imgSvc := img.New(workersCount)
|
||||||
|
|
||||||
|
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
||||||
|
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
||||||
|
checkErr(err)
|
||||||
|
if cacheDir != "" {
|
||||||
|
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
|
||||||
|
log.Fatalf("can't make directory %s: %s", cacheDir, err)
|
||||||
|
}
|
||||||
|
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||||
|
}
|
||||||
|
|
||||||
server := getRunParams(cmd.Flags(), d.store)
|
server := getRunParams(cmd.Flags(), d.store)
|
||||||
setupLog(server.Log)
|
setupLog(server.Log)
|
||||||
|
|
||||||
@@ -109,19 +139,29 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
|
|
||||||
var listener net.Listener
|
var listener net.Listener
|
||||||
|
|
||||||
if server.TLSKey != "" && server.TLSCert != "" {
|
switch {
|
||||||
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey)
|
case server.Socket != "":
|
||||||
|
listener, err = net.Listen("unix", server.Socket)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
listener, err = tls.Listen("tcp", adr, &tls.Config{Certificates: []tls.Certificate{cer}})
|
case server.TLSKey != "" && server.TLSCert != "":
|
||||||
|
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:shadow
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
} else {
|
listener, err = tls.Listen("tcp", adr, &tls.Config{Certificates: []tls.Certificate{cer}}) //nolint:shadow
|
||||||
listener, err = net.Listen("tcp", adr)
|
checkErr(err)
|
||||||
|
default:
|
||||||
|
listener, err = net.Listen("tcp", adr) //nolint:shadow
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler, err := fbhttp.NewHandler(d.store, server)
|
sigc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go cleanupHandler(listener, sigc)
|
||||||
|
|
||||||
|
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
log.Println("Listening on", listener.Addr().String())
|
log.Println("Listening on", listener.Addr().String())
|
||||||
if err := http.Serve(listener, handler); err != nil {
|
if err := http.Serve(listener, handler); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -129,6 +169,14 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
}, pythonConfig{allowNoDB: true}),
|
}, pythonConfig{allowNoDB: true}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfacer
|
||||||
|
sig := <-c
|
||||||
|
log.Printf("Caught signal %s: shutting down.", sig)
|
||||||
|
listener.Close()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
||||||
server, err := st.Settings.GetServer()
|
server, err := st.Settings.GetServer()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
@@ -141,26 +189,53 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
|||||||
server.BaseURL = val
|
server.BaseURL = val
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getParamB(flags, "address"); set {
|
|
||||||
server.Address = val
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, set := getParamB(flags, "port"); set {
|
|
||||||
server.Port = val
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, set := getParamB(flags, "log"); set {
|
if val, set := getParamB(flags, "log"); set {
|
||||||
server.Log = val
|
server.Log = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSocketSet := false
|
||||||
|
isAddrSet := false
|
||||||
|
|
||||||
|
if val, set := getParamB(flags, "address"); set {
|
||||||
|
server.Address = val
|
||||||
|
isAddrSet = isAddrSet || set
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, set := getParamB(flags, "port"); set {
|
||||||
|
server.Port = val
|
||||||
|
isAddrSet = isAddrSet || set
|
||||||
|
}
|
||||||
|
|
||||||
if val, set := getParamB(flags, "key"); set {
|
if val, set := getParamB(flags, "key"); set {
|
||||||
server.TLSKey = val
|
server.TLSKey = val
|
||||||
|
isAddrSet = isAddrSet || set
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getParamB(flags, "cert"); set {
|
if val, set := getParamB(flags, "cert"); set {
|
||||||
server.TLSCert = val
|
server.TLSCert = val
|
||||||
|
isAddrSet = isAddrSet || set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if val, set := getParamB(flags, "socket"); set {
|
||||||
|
server.Socket = val
|
||||||
|
isSocketSet = isSocketSet || set
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAddrSet && isSocketSet {
|
||||||
|
checkErr(errors.New("--socket flag cannot be used with --address, --port, --key nor --cert"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not use saved Socket if address was manually set.
|
||||||
|
if isAddrSet && server.Socket != "" {
|
||||||
|
server.Socket = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
_, disableThumbnails := getParamB(flags, "disable-thumbnails")
|
||||||
|
server.EnableThumbnails = !disableThumbnails
|
||||||
|
|
||||||
|
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
|
||||||
|
server.ResizePreview = !disablePreviewResize
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,5 +382,4 @@ func initConfig() {
|
|||||||
} else {
|
} else {
|
||||||
cfgFile = "Using config file: " + v.ConfigFileUsed()
|
cfgFile = "Using config file: " + v.ConfigFileUsed()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rulesCmd.AddCommand(rulesRmCommand)
|
rulesCmd.AddCommand(rulesRmCommand)
|
||||||
rulesRmCommand.Flags().Uint("index", 0, "index of rule to remove")
|
rulesRmCommand.Flags().Uint("index", 0, "index of rule to remove")
|
||||||
rulesRmCommand.MarkFlagRequired("index")
|
_ = rulesRmCommand.MarkFlagRequired("index")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rulesRmCommand = &cobra.Command{
|
var rulesRmCommand = &cobra.Command{
|
||||||
@@ -43,7 +44,7 @@ including 'index_end'.`,
|
|||||||
i, err := strconv.Atoi(args[0])
|
i, err := strconv.Atoi(args[0])
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
f := i
|
f := i
|
||||||
if len(args) == 2 {
|
if len(args) == 2 { //nolint:mnd
|
||||||
f, err = strconv.Atoi(args[1])
|
f, err = strconv.Atoi(args[1])
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|||||||
23
cmd/rules.go
@@ -3,12 +3,13 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/rules"
|
"github.com/filebrowser/filebrowser/v2/rules"
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/storage"
|
"github.com/filebrowser/filebrowser/v2/storage"
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -18,8 +19,8 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rulesCmd = &cobra.Command{
|
var rulesCmd = &cobra.Command{
|
||||||
Use: "rules",
|
Use: "rules",
|
||||||
Short: "Rules management utility",
|
Short: "Rules management utility",
|
||||||
Long: `On each subcommand you'll have available at least two flags:
|
Long: `On each subcommand you'll have available at least two flags:
|
||||||
"username" and "id". You must either set only one of them
|
"username" and "id". You must either set only one of them
|
||||||
or none. If you set one of them, the command will apply to
|
or none. If you set one of them, the command will apply to
|
||||||
@@ -28,14 +29,14 @@ rules.`,
|
|||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRules(st *storage.Storage, cmd *cobra.Command, users func(*users.User), global func(*settings.Settings)) {
|
func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User), globalFn func(*settings.Settings)) {
|
||||||
id := getUserIdentifier(cmd.Flags())
|
id := getUserIdentifier(cmd.Flags())
|
||||||
if id != nil {
|
if id != nil {
|
||||||
user, err := st.Users.Get("", id)
|
user, err := st.Users.Get("", id)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
if users != nil {
|
if usersFn != nil {
|
||||||
users(user)
|
usersFn(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
printRules(user.Rules, id)
|
printRules(user.Rules, id)
|
||||||
@@ -45,8 +46,8 @@ func runRules(st *storage.Storage, cmd *cobra.Command, users func(*users.User),
|
|||||||
s, err := st.Settings.Get()
|
s, err := st.Settings.Get()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
if global != nil {
|
if globalFn != nil {
|
||||||
global(s)
|
globalFn(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
printRules(s.Rules, id)
|
printRules(s.Rules, id)
|
||||||
@@ -65,14 +66,14 @@ func getUserIdentifier(flags *pflag.FlagSet) interface{} {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printRules(rules []rules.Rule, id interface{}) {
|
func printRules(rulez []rules.Rule, id interface{}) {
|
||||||
if id == nil {
|
if id == nil {
|
||||||
fmt.Printf("Global Rules:\n\n")
|
fmt.Printf("Global Rules:\n\n")
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Rules for user %v:\n\n", id)
|
fmt.Printf("Rules for user %v:\n\n", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
for id, rule := range rules {
|
for id, rule := range rulez {
|
||||||
fmt.Printf("(%d) ", id)
|
fmt.Printf("(%d) ", id)
|
||||||
if rule.Regex {
|
if rule.Regex {
|
||||||
if rule.Allow {
|
if rule.Allow {
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/rules"
|
"github.com/filebrowser/filebrowser/v2/rules"
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -10,7 +11,7 @@ func init() {
|
|||||||
|
|
||||||
upgradeCmd.Flags().String("old.database", "", "")
|
upgradeCmd.Flags().String("old.database", "", "")
|
||||||
upgradeCmd.Flags().String("old.config", "", "")
|
upgradeCmd.Flags().String("old.config", "", "")
|
||||||
upgradeCmd.MarkFlagRequired("old.database")
|
_ = upgradeCmd.MarkFlagRequired("old.database")
|
||||||
}
|
}
|
||||||
|
|
||||||
var upgradeCmd = &cobra.Command{
|
var upgradeCmd = &cobra.Command{
|
||||||
|
|||||||
44
cmd/users.go
@@ -7,10 +7,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -24,38 +25,38 @@ var usersCmd = &cobra.Command{
|
|||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
func printUsers(users []*users.User) {
|
func printUsers(usrs []*users.User) {
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||||
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
|
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
|
||||||
|
|
||||||
for _, user := range users {
|
for _, u := range usrs {
|
||||||
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n",
|
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n",
|
||||||
user.ID,
|
u.ID,
|
||||||
user.Username,
|
u.Username,
|
||||||
user.Scope,
|
u.Scope,
|
||||||
user.Locale,
|
u.Locale,
|
||||||
user.ViewMode,
|
u.ViewMode,
|
||||||
user.Perm.Admin,
|
u.Perm.Admin,
|
||||||
user.Perm.Execute,
|
u.Perm.Execute,
|
||||||
user.Perm.Create,
|
u.Perm.Create,
|
||||||
user.Perm.Rename,
|
u.Perm.Rename,
|
||||||
user.Perm.Modify,
|
u.Perm.Modify,
|
||||||
user.Perm.Delete,
|
u.Perm.Delete,
|
||||||
user.Perm.Share,
|
u.Perm.Share,
|
||||||
user.Perm.Download,
|
u.Perm.Download,
|
||||||
user.LockPassword,
|
u.LockPassword,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseUsernameOrID(arg string) (string, uint) {
|
func parseUsernameOrID(arg string) (username string, id uint) {
|
||||||
id, err := strconv.ParseUint(arg, 10, 0)
|
id64, err := strconv.ParseUint(arg, 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return arg, 0
|
return arg, 0
|
||||||
}
|
}
|
||||||
return "", uint(id)
|
return "", uint(id64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addUserFlags(flags *pflag.FlagSet) {
|
func addUserFlags(flags *pflag.FlagSet) {
|
||||||
@@ -84,6 +85,7 @@ func getViewMode(flags *pflag.FlagSet) users.ViewMode {
|
|||||||
return viewMode
|
return viewMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) {
|
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) {
|
||||||
visit := func(flag *pflag.Flag) {
|
visit := func(flag *pflag.Flag) {
|
||||||
switch flag.Name {
|
switch flag.Name {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -14,7 +15,7 @@ var usersAddCmd = &cobra.Command{
|
|||||||
Use: "add <username> <password>",
|
Use: "add <username> <password>",
|
||||||
Short: "Create a new user",
|
Short: "Create a new user",
|
||||||
Long: `Create a new user and add it to the database.`,
|
Long: `Create a new user and add it to the database.`,
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2), //nolint:mnd
|
||||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
s, err := d.store.Settings.Get()
|
s, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
@@ -33,9 +34,9 @@ var usersAddCmd = &cobra.Command{
|
|||||||
|
|
||||||
servSettings, err := d.store.Settings.GetServer()
|
servSettings, err := d.store.Settings.GetServer()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
//since getUserDefaults() polluted s.Defaults.Scope
|
// since getUserDefaults() polluted s.Defaults.Scope
|
||||||
//which makes the Scope not the one saved in the db
|
// which makes the Scope not the one saved in the db
|
||||||
//we need the right s.Defaults.Scope here
|
// we need the right s.Defaults.Scope here
|
||||||
s2, err := d.store.Settings.Get()
|
s2, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -65,8 +67,7 @@ list or set it to 0.`,
|
|||||||
// with the new username. If there is, print an error and cancel the
|
// with the new username. If there is, print an error and cancel the
|
||||||
// operation
|
// operation
|
||||||
if user.Username != onDB.Username {
|
if user.Username != onDB.Username {
|
||||||
conflictuous, err := d.store.Users.Get("", user.Username)
|
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:shadow
|
||||||
if err == nil {
|
|
||||||
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
|
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,6 +83,7 @@ list or set it to 0.`,
|
|||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
func usernameConflictError(username string, original, new uint) error {
|
func usernameConflictError(username string, originalID, newID uint) error {
|
||||||
return errors.New("can't import user with ID " + strconv.Itoa(int(new)) + " and username \"" + username + "\" because the username is already registred with the user " + strconv.Itoa(int(original)))
|
return fmt.Errorf(`can't import user with ID %d and username "%s" because the username is already registred with the user %d`,
|
||||||
|
newID, username, originalID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
13
cmd/utils.go
@@ -9,12 +9,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/storage"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/storage"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkErr(err error) {
|
func checkErr(err error) {
|
||||||
@@ -70,7 +71,9 @@ func dbExists(path string) (bool, error) {
|
|||||||
d := filepath.Dir(path)
|
d := filepath.Dir(path)
|
||||||
_, err = os.Stat(d)
|
_, err = os.Stat(d)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
os.MkdirAll(d, 0700)
|
if err := os.MkdirAll(d, 0700); err != nil { //nolint:shadow
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +116,7 @@ func marshal(filename string, data interface{}) error {
|
|||||||
encoder := json.NewEncoder(fd)
|
encoder := json.NewEncoder(fd)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
return encoder.Encode(data)
|
return encoder.Encode(data)
|
||||||
case ".yml", ".yaml":
|
case ".yml", ".yaml": //nolint:goconst
|
||||||
encoder := yaml.NewEncoder(fd)
|
encoder := yaml.NewEncoder(fd)
|
||||||
return encoder.Encode(data)
|
return encoder.Encode(data)
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/version"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -15,6 +16,6 @@ var versionCmd = &cobra.Command{
|
|||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Print the version number",
|
Short: "Print the version number",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Println("File Browser Version " + version.Version)
|
fmt.Println("File Browser v" + version.Version + "/" + version.CommitSHA)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
11
diskcache/cache.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package diskcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Interface interface {
|
||||||
|
Store(ctx context.Context, key string, value []byte) error
|
||||||
|
Load(ctx context.Context, key string) (value []byte, exist bool, err error)
|
||||||
|
Delete(ctx context.Context, key string) error
|
||||||
|
}
|
||||||
110
diskcache/file_cache.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package diskcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha1" //nolint:gosec
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileCache struct {
|
||||||
|
fs afero.Fs
|
||||||
|
|
||||||
|
// granular locks
|
||||||
|
scopedLocks struct {
|
||||||
|
sync.Mutex
|
||||||
|
sync.Once
|
||||||
|
locks map[string]sync.Locker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(fs afero.Fs, root string) *FileCache {
|
||||||
|
return &FileCache{
|
||||||
|
fs: afero.NewBasePathFs(fs, root),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileCache) Store(ctx context.Context, key string, value []byte) error {
|
||||||
|
mu := f.getScopedLocks(key)
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
fileName := f.getFileName(key)
|
||||||
|
if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileCache) Load(ctx context.Context, key string) (value []byte, exist bool, err error) {
|
||||||
|
r, ok, err := f.open(key)
|
||||||
|
if err != nil || !ok {
|
||||||
|
return nil, ok, err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
value, err = ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return value, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileCache) Delete(ctx context.Context, key string) error {
|
||||||
|
mu := f.getScopedLocks(key)
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
fileName := f.getFileName(key)
|
||||||
|
if err := f.fs.Remove(fileName); err != nil && err != os.ErrNotExist {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileCache) open(key string) (afero.File, bool, error) {
|
||||||
|
fileName := f.getFileName(key)
|
||||||
|
file, err := f.fs.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getScopedLocks pull lock from the map if found or create a new one
|
||||||
|
func (f *FileCache) getScopedLocks(key string) (lock sync.Locker) {
|
||||||
|
f.scopedLocks.Do(func() { f.scopedLocks.locks = map[string]sync.Locker{} })
|
||||||
|
|
||||||
|
f.scopedLocks.Lock()
|
||||||
|
lock, ok := f.scopedLocks.locks[key]
|
||||||
|
if !ok {
|
||||||
|
lock = &sync.Mutex{}
|
||||||
|
f.scopedLocks.locks[key] = lock
|
||||||
|
}
|
||||||
|
f.scopedLocks.Unlock()
|
||||||
|
|
||||||
|
return lock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileCache) getFileName(key string) string {
|
||||||
|
hasher := sha1.New() //nolint:gosec
|
||||||
|
_, _ = hasher.Write([]byte(key))
|
||||||
|
hash := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
return fmt.Sprintf("%s/%s/%s", hash[:1], hash[1:3], hash)
|
||||||
|
}
|
||||||
55
diskcache/file_cache_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package diskcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileCache(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
const (
|
||||||
|
key = "key"
|
||||||
|
value = "some text"
|
||||||
|
newValue = "new text"
|
||||||
|
cacheRoot = "/cache"
|
||||||
|
cachedFilePath = "a/62/a62f2225bf70bfaccbc7f1ef2a397836717377de"
|
||||||
|
)
|
||||||
|
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
cache := New(fs, "/cache")
|
||||||
|
|
||||||
|
// store new key
|
||||||
|
err := cache.Store(ctx, key, []byte(value))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkValue(t, ctx, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, value)
|
||||||
|
|
||||||
|
// update existing key
|
||||||
|
err = cache.Store(ctx, key, []byte(newValue))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkValue(t, ctx, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, newValue)
|
||||||
|
|
||||||
|
// delete key
|
||||||
|
err = cache.Delete(ctx, key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
exists, err := afero.Exists(fs, filepath.Join(cacheRoot, cachedFilePath))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath string, cache *FileCache, key, wantValue string) { //nolint:golint
|
||||||
|
t.Helper()
|
||||||
|
// check actual file content
|
||||||
|
b, err := afero.ReadFile(fs, fileFullPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, wantValue, string(b))
|
||||||
|
|
||||||
|
// check cache content
|
||||||
|
b, ok, err := cache.Load(ctx, key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, wantValue, string(b))
|
||||||
|
}
|
||||||
24
diskcache/noop_cache.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package diskcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NoOp struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNoOp() *NoOp {
|
||||||
|
return &NoOp{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NoOp) Store(ctx context.Context, key string, value []byte) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NoOp) Load(ctx context.Context, key string) (value []byte, exist bool, err error) {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NoOp) Delete(ctx context.Context, key string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,15 +3,18 @@ package errors
|
|||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrEmptyKey = errors.New("empty key")
|
ErrEmptyKey = errors.New("empty key")
|
||||||
ErrExist = errors.New("the resource already exists")
|
ErrExist = errors.New("the resource already exists")
|
||||||
ErrNotExist = errors.New("the resource does not exist")
|
ErrNotExist = errors.New("the resource does not exist")
|
||||||
ErrEmptyPassword = errors.New("password is empty")
|
ErrEmptyPassword = errors.New("password is empty")
|
||||||
ErrEmptyUsername = errors.New("username is empty")
|
ErrEmptyUsername = errors.New("username is empty")
|
||||||
ErrEmptyRequest = errors.New("empty request")
|
ErrEmptyRequest = errors.New("empty request")
|
||||||
ErrScopeIsRelative = errors.New("scope is a relative path")
|
ErrScopeIsRelative = errors.New("scope is a relative path")
|
||||||
ErrInvalidDataType = errors.New("invalid data type")
|
ErrInvalidDataType = errors.New("invalid data type")
|
||||||
ErrIsDirectory = errors.New("file is directory")
|
ErrIsDirectory = errors.New("file is directory")
|
||||||
ErrInvalidOption = errors.New("invalid option")
|
ErrInvalidOption = errors.New("invalid option")
|
||||||
ErrInvalidAuthMethod = errors.New("invalid auth method")
|
ErrInvalidAuthMethod = errors.New("invalid auth method")
|
||||||
|
ErrPermissionDenied = errors.New("permission denied")
|
||||||
|
ErrInvalidRequestParams = errors.New("invalid request params")
|
||||||
|
ErrSourceIsParent = errors.New("source is parent")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package files
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5" //nolint:gosec
|
||||||
"crypto/sha1"
|
"crypto/sha1" //nolint:gosec
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@@ -17,9 +17,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/errors"
|
"github.com/filebrowser/filebrowser/v2/errors"
|
||||||
"github.com/filebrowser/filebrowser/v2/rules"
|
"github.com/filebrowser/filebrowser/v2/rules"
|
||||||
"github.com/spf13/afero"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileInfo describes a file.
|
// FileInfo describes a file.
|
||||||
@@ -74,7 +75,10 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
|||||||
|
|
||||||
if opts.Expand {
|
if opts.Expand {
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
return file, file.readListing(opts.Checker)
|
if err := file.readListing(opts.Checker); err != nil { //nolint:shadow
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.detectType(opts.Modify, true)
|
err = file.detectType(opts.Modify, true)
|
||||||
@@ -105,6 +109,7 @@ func (i *FileInfo) Checksum(algo string) error {
|
|||||||
|
|
||||||
var h hash.Hash
|
var h hash.Hash
|
||||||
|
|
||||||
|
//nolint:gosec
|
||||||
switch algo {
|
switch algo {
|
||||||
case "md5":
|
case "md5":
|
||||||
h = md5.New()
|
h = md5.New()
|
||||||
@@ -127,6 +132,8 @@ func (i *FileInfo) Checksum(algo string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:goconst
|
||||||
|
//TODO: use constants
|
||||||
func (i *FileInfo) detectType(modify, saveContent bool) error {
|
func (i *FileInfo) detectType(modify, saveContent bool) error {
|
||||||
// failing to detect the type should not return error.
|
// failing to detect the type should not return error.
|
||||||
// imagine the situation where a file in a dir with thousands
|
// imagine the situation where a file in a dir with thousands
|
||||||
@@ -198,9 +205,9 @@ func (i *FileInfo) detectSubtitles() {
|
|||||||
|
|
||||||
// TODO: detect multiple languages. Base.Lang.vtt
|
// TODO: detect multiple languages. Base.Lang.vtt
|
||||||
|
|
||||||
path := strings.TrimSuffix(i.Path, ext) + ".vtt"
|
fPath := strings.TrimSuffix(i.Path, ext) + ".vtt"
|
||||||
if _, err := i.Fs.Stat(path); err == nil {
|
if _, err := i.Fs.Stat(fPath); err == nil {
|
||||||
i.Subtitles = append(i.Subtitles, path)
|
i.Subtitles = append(i.Subtitles, fPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,16 +226,16 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
|
|||||||
|
|
||||||
for _, f := range dir {
|
for _, f := range dir {
|
||||||
name := f.Name()
|
name := f.Name()
|
||||||
path := path.Join(i.Path, name)
|
fPath := path.Join(i.Path, name)
|
||||||
|
|
||||||
if !checker.Check(path) {
|
if !checker.Check(fPath) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(f.Mode().String(), "L") {
|
if strings.HasPrefix(f.Mode().String(), "L") {
|
||||||
// It's a symbolic link. We try to follow it. If it doesn't work,
|
// It's a symbolic link. We try to follow it. If it doesn't work,
|
||||||
// we stay with the link information instead if the target's.
|
// we stay with the link information instead if the target's.
|
||||||
info, err := i.Fs.Stat(path)
|
info, err := i.Fs.Stat(fPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f = info
|
f = info
|
||||||
}
|
}
|
||||||
@@ -242,7 +249,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error {
|
|||||||
Mode: f.Mode(),
|
Mode: f.Mode(),
|
||||||
IsDir: f.IsDir(),
|
IsDir: f.IsDir(),
|
||||||
Extension: filepath.Ext(name),
|
Extension: filepath.Ext(name),
|
||||||
Path: path,
|
Path: fPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ type Listing struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplySort applies the sort order using .Order and .Sort
|
// ApplySort applies the sort order using .Order and .Sort
|
||||||
|
//nolint:goconst
|
||||||
func (l Listing) ApplySort() {
|
func (l Listing) ApplySort() {
|
||||||
// Check '.Order' to know how to sort
|
// Check '.Order' to know how to sort
|
||||||
|
// TODO: use enum
|
||||||
if !l.Sorting.Asc {
|
if !l.Sorting.Asc {
|
||||||
switch l.Sorting.By {
|
switch l.Sorting.By {
|
||||||
case "name":
|
case "name":
|
||||||
@@ -62,11 +64,11 @@ func (l byName) Swap(i, j int) {
|
|||||||
// Treat upper and lower case equally
|
// Treat upper and lower case equally
|
||||||
func (l byName) Less(i, j int) bool {
|
func (l byName) Less(i, j int) bool {
|
||||||
if l.Items[i].IsDir && !l.Items[j].IsDir {
|
if l.Items[i].IsDir && !l.Items[j].IsDir {
|
||||||
return true
|
return l.Sorting.Asc
|
||||||
}
|
}
|
||||||
|
|
||||||
if !l.Items[i].IsDir && l.Items[j].IsDir {
|
if !l.Items[i].IsDir && l.Items[j].IsDir {
|
||||||
return false
|
return !l.Sorting.Asc
|
||||||
}
|
}
|
||||||
|
|
||||||
return natural.Less(strings.ToLower(l.Items[j].Name), strings.ToLower(l.Items[i].Name))
|
return natural.Less(strings.ToLower(l.Items[j].Name), strings.ToLower(l.Items[i].Name))
|
||||||
|
|||||||
@@ -4,41 +4,45 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isBinary(content []byte, n int) bool {
|
func isBinary(content []byte, _ int) bool {
|
||||||
maybeStr := string(content)
|
maybeStr := string(content)
|
||||||
runeCnt := utf8.RuneCount(content)
|
runeCnt := utf8.RuneCount(content)
|
||||||
runeIndex := 0
|
runeIndex := 0
|
||||||
gotRuneErrCnt := 0
|
gotRuneErrCnt := 0
|
||||||
firstRuneErrIndex := -1
|
firstRuneErrIndex := -1
|
||||||
|
|
||||||
for _, b := range maybeStr {
|
const (
|
||||||
// 8 and below are control chars (e.g. backspace, null, eof, etc)
|
// 8 and below are control chars (e.g. backspace, null, eof, etc)
|
||||||
if b <= 8 {
|
maxControlCharsCode = 8
|
||||||
|
// 0xFFFD(65533) is the "error" Rune or "Unicode replacement character"
|
||||||
|
// see https://golang.org/pkg/unicode/utf8/#pkg-constants
|
||||||
|
unicodeReplacementChar = 0xFFFD
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, b := range maybeStr {
|
||||||
|
if b <= maxControlCharsCode {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0xFFFD(65533) is the "error" Rune or "Unicode replacement character"
|
if b == unicodeReplacementChar {
|
||||||
// see https://golang.org/pkg/unicode/utf8/#pkg-constants
|
// if it is not the last (utf8.UTFMax - x) rune
|
||||||
if b == 0xFFFD {
|
|
||||||
//if it is not the last (utf8.UTFMax - x) rune
|
|
||||||
if runeCnt > utf8.UTFMax && runeIndex < runeCnt-utf8.UTFMax {
|
if runeCnt > utf8.UTFMax && runeIndex < runeCnt-utf8.UTFMax {
|
||||||
return true
|
return true
|
||||||
} else {
|
}
|
||||||
//else it is the last (utf8.UTFMax - x) rune
|
// else it is the last (utf8.UTFMax - x) rune
|
||||||
//there maybe Vxxx, VVxx, VVVx, thus, we may got max 3 0xFFFD rune (asume V is the byte we got)
|
// there maybe Vxxx, VVxx, VVVx, thus, we may got max 3 0xFFFD rune (assume V is the byte we got)
|
||||||
//for Chinese, it can only be Vxx, VVx, we may got max 2 0xFFFD rune
|
// for Chinese, it can only be Vxx, VVx, we may got max 2 0xFFFD rune
|
||||||
gotRuneErrCnt++
|
gotRuneErrCnt++
|
||||||
|
|
||||||
//mark the first time
|
// mark the first time
|
||||||
if firstRuneErrIndex == -1 {
|
if firstRuneErrIndex == -1 {
|
||||||
firstRuneErrIndex = runeIndex
|
firstRuneErrIndex = runeIndex
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
runeIndex++
|
runeIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
//if last (utf8.UTFMax - x ) rune has the "error" Rune, but not all
|
// if last (utf8.UTFMax - x ) rune has the "error" Rune, but not all
|
||||||
if firstRuneErrIndex != -1 && gotRuneErrCnt != runeCnt-firstRuneErrIndex {
|
if firstRuneErrIndex != -1 && gotRuneErrCnt != runeCnt-firstRuneErrIndex {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
// CopyDir copies a directory from source to dest and all
|
// CopyDir copies a directory from source to dest and all
|
||||||
// of its sub-directories. It doesn't stop if it finds an error
|
// of its sub-directories. It doesn't stop if it finds an error
|
||||||
// during the copy. Returns an error if any.
|
// during the copy. Returns an error if any.
|
||||||
func CopyDir(fs afero.Fs, source string, dest string) error {
|
func CopyDir(fs afero.Fs, source, dest string) error {
|
||||||
// Get properties of source.
|
// Get properties of source.
|
||||||
srcinfo, err := fs.Stat(source)
|
srcinfo, err := fs.Stat(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fileutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
|
|
||||||
// CopyFile copies a file from source to dest and returns
|
// CopyFile copies a file from source to dest and returns
|
||||||
// an error if any.
|
// an error if any.
|
||||||
func CopyFile(fs afero.Fs, source string, dest string) error {
|
func CopyFile(fs afero.Fs, source, dest string) error {
|
||||||
// Open the source file.
|
// Open the source file.
|
||||||
src, err := fs.Open(source)
|
src, err := fs.Open(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -25,7 +26,7 @@ func CopyFile(fs afero.Fs, source string, dest string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the destination file.
|
// Create the destination file.
|
||||||
dst, err := fs.Create(dest)
|
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
1
frontend
5
frontend/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/app'
|
||||||
|
]
|
||||||
|
}
|
||||||
13845
frontend/package-lock.json
generated
Normal file
62
frontend/package.json
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"name": "filebrowser-frontend",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"watch": "vue-cli-service build --watch",
|
||||||
|
"lint": "vue-cli-service lint --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ace-builds": "^1.4.7",
|
||||||
|
"clipboard": "^2.0.4",
|
||||||
|
"js-base64": "^2.5.1",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"lodash.throttle": "^4.1.1",
|
||||||
|
"material-design-icons": "^3.0.1",
|
||||||
|
"moment": "^2.24.0",
|
||||||
|
"normalize.css": "^8.0.1",
|
||||||
|
"noty": "^3.2.0-beta",
|
||||||
|
"qrcode.vue": "^1.7.0",
|
||||||
|
"vue": "^2.6.10",
|
||||||
|
"vue-i18n": "^8.15.3",
|
||||||
|
"vue-lazyload": "^1.3.3",
|
||||||
|
"vue-router": "^3.1.3",
|
||||||
|
"vuex": "^3.1.2",
|
||||||
|
"vuex-router-sync": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/cli-plugin-babel": "^4.1.2",
|
||||||
|
"@vue/cli-plugin-eslint": "^4.1.1",
|
||||||
|
"@vue/cli-service": "^4.1.2",
|
||||||
|
"babel-eslint": "^10.0.3",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-vue": "^6.1.2",
|
||||||
|
"vue-template-compiler": "^2.6.10"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "babel-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postcss": {
|
||||||
|
"plugins": {
|
||||||
|
"autoprefixer": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not ie <= 8"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
frontend/public/img/icons/android-chrome-192x192.png
Normal file
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
BIN
frontend/public/img/icons/android-chrome-512x512.png
Normal file
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
frontend/public/img/icons/apple-touch-icon.png
Normal file
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
9
frontend/public/img/icons/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#455a64</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
||||||
BIN
frontend/public/img/icons/favicon-16x16.png
Normal file
|
Before Width: | Height: | Size: 843 B After Width: | Height: | Size: 843 B |
BIN
frontend/public/img/icons/favicon-32x32.png
Normal file
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
frontend/public/img/icons/favicon.ico
Normal file
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
frontend/public/img/icons/mstile-144x144.png
Normal file
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
BIN
frontend/public/img/icons/mstile-150x150.png
Normal file
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
BIN
frontend/public/img/icons/mstile-310x150.png
Normal file
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
BIN
frontend/public/img/icons/mstile-310x310.png
Normal file
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
BIN
frontend/public/img/icons/mstile-70x70.png
Normal file
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
42
frontend/public/img/icons/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M3245 6989 c-522 -39 -1042 -197 -1480 -449 -849 -488 -1459 -1308
|
||||||
|
-1673 -2250 -177 -776 -89 -1582 250 -2301 368 -778 1052 -1418 1857 -1739
|
||||||
|
903 -359 1927 -325 2812 92 778 368 1418 1052 1739 1857 359 903 325 1927 -92
|
||||||
|
2812 -296 627 -806 1175 -1423 1529 -587 338 -1308 500 -1990 449z m555 -580
|
||||||
|
c519 -51 1018 -245 1446 -565 788 -588 1229 -1526 1174 -2496 -16 -277 -58
|
||||||
|
-500 -145 -763 -144 -440 -378 -819 -710 -1150 -452 -452 -1005 -730 -1655
|
||||||
|
-832 -91 -14 -175 -18 -405 -18 -304 0 -369 6 -595 51 -1105 223 -1999 1092
|
||||||
|
-2259 2197 -52 221 -73 412 -73 667 0 397 64 732 204 1080 304 752 886 1334
|
||||||
|
1638 1638 431 174 895 238 1380 191z"/>
|
||||||
|
<path d="M2670 5215 c0 -13 -44 -15 -335 -15 -352 0 -383 -3 -399 -45 -3 -9
|
||||||
|
-6 -758 -6 -1663 0 -1168 -3 -1643 -11 -1632 -8 11 -9 8 -4 -15 3 -16 17 -41
|
||||||
|
31 -55 l24 -25 1530 0 1530 0 24 25 c14 14 26 36 27 50 1 14 1 711 1 1550 l-2
|
||||||
|
1526 -228 142 -229 142 -136 0 -137 0 0 -600 0 -600 -705 0 -705 0 0 615 0
|
||||||
|
615 -135 0 c-113 0 -135 -2 -135 -15z m-264 -190 c57 -29 89 -71 103 -137 35
|
||||||
|
-154 -98 -282 -258 -247 -55 12 -122 62 -148 113 -36 69 -12 186 49 243 62 58
|
||||||
|
170 70 254 28z m2316 -1702 c17 -15 18 -49 18 -670 l0 -653 -1245 0 -1245 0 0
|
||||||
|
654 c0 582 2 656 16 670 14 14 139 16 1226 16 1113 0 1213 -1 1230 -17z
|
||||||
|
m-2602 -1363 c40 -40 13 -100 -43 -100 -60 0 -88 59 -47 100 11 11 31 20 45
|
||||||
|
20 14 0 34 -9 45 -20z m2840 0 c41 -41 11 -100 -52 -100 -35 0 -58 24 -58 60
|
||||||
|
0 54 71 79 110 40z"/>
|
||||||
|
<path d="M2431 3091 c-7 -13 -7 -23 2 -35 11 -15 97 -16 1067 -14 l1055 3 0
|
||||||
|
30 0 30 -1057 3 c-1023 2 -1058 1 -1067 -17z"/>
|
||||||
|
<path d="M2436 2675 c-19 -19 -11 -41 17 -49 41 -11 2067 -7 2088 4 23 13 25
|
||||||
|
46 3 54 -9 3 -483 6 -1054 6 -919 0 -1040 -2 -1054 -15z"/>
|
||||||
|
<path d="M2447 2273 c-14 -4 -17 -13 -15 -36 l3 -32 1049 -3 c767 -1 1052 1
|
||||||
|
1062 9 20 16 17 47 -5 59 -20 10 -2055 13 -2094 3z"/>
|
||||||
|
<path d="M3822 5027 c-21 -23 -22 -30 -22 -293 0 -258 1 -271 20 -292 27 -29
|
||||||
|
56 -35 140 -30 56 3 75 8 93 26 22 22 22 26 22 298 l0 276 -24 19 c-19 16 -40
|
||||||
|
19 -115 19 -84 0 -95 -2 -114 -23z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
147
frontend/public/img/logo.svg
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xml:space="preserve"
|
||||||
|
width="560"
|
||||||
|
height="560"
|
||||||
|
version="1.1"
|
||||||
|
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
|
||||||
|
viewBox="0 0 560 560"
|
||||||
|
id="svg44"
|
||||||
|
sodipodi:docname="icon_raw.svg"
|
||||||
|
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||||
|
inkscape:export-filename="/home/umarcor/filebrowser/logo/icon_raw.svg.png"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"><metadata
|
||||||
|
id="metadata48"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1366"
|
||||||
|
inkscape:window-height="711"
|
||||||
|
id="namedview46"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.33714286"
|
||||||
|
inkscape:cx="-172.33051"
|
||||||
|
inkscape:cy="280"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="20"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg44" />
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<style
|
||||||
|
type="text/css"
|
||||||
|
id="style2">
|
||||||
|
<![CDATA[
|
||||||
|
.fil1 {fill:#FEFEFE}
|
||||||
|
.fil6 {fill:#006498}
|
||||||
|
.fil7 {fill:#0EA5EB}
|
||||||
|
.fil8 {fill:#2979FF}
|
||||||
|
.fil3 {fill:#2BBCFF}
|
||||||
|
.fil0 {fill:#455A64}
|
||||||
|
.fil4 {fill:#53C6FC}
|
||||||
|
.fil5 {fill:#BDEAFF}
|
||||||
|
.fil2 {fill:#332C2B;fill-opacity:0.149020}
|
||||||
|
]]>
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
id="g85"
|
||||||
|
transform="translate(-70,-70)"><path
|
||||||
|
class="fil1"
|
||||||
|
d="M 350,71 C 504,71 629,196 629,350 629,504 504,629 350,629 196,629 71,504 71,350 71,196 196,71 350,71 Z"
|
||||||
|
id="path9"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#fefefe" /><path
|
||||||
|
class="fil2"
|
||||||
|
d="M 475,236 593,387 C 596,503 444,639 301,585 L 225,486 339,330 c 0,0 138,-95 136,-94 z"
|
||||||
|
id="path11"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#332c2b;fill-opacity:0.14902003" /><path
|
||||||
|
class="fil3"
|
||||||
|
d="m 231,211 h 208 l 38,24 v 246 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 V 219 c 0,-5 3,-8 8,-8 z"
|
||||||
|
id="path13"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#2bbcff" /><path
|
||||||
|
class="fil4"
|
||||||
|
d="m 231,211 h 208 l 38,24 v 2 L 440,214 H 231 c -4,0 -7,3 -7,7 v 263 c -1,-1 -1,-2 -1,-3 V 219 c 0,-5 3,-8 8,-8 z"
|
||||||
|
id="path15"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#53c6fc" /><polygon
|
||||||
|
class="fil5"
|
||||||
|
points="305,212 418,212 418,310 305,310 "
|
||||||
|
id="polygon17"
|
||||||
|
style="fill:#bdeaff" /><path
|
||||||
|
class="fil5"
|
||||||
|
d="m 255,363 h 189 c 3,0 5,2 5,4 V 483 H 250 V 367 c 0,-2 2,-4 5,-4 z"
|
||||||
|
id="path19"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#bdeaff" /><polygon
|
||||||
|
class="fil6"
|
||||||
|
points="250,470 449,470 449,483 250,483 "
|
||||||
|
id="polygon21"
|
||||||
|
style="fill:#006498" /><path
|
||||||
|
class="fil6"
|
||||||
|
d="m 380,226 h 10 c 3,0 6,2 6,5 v 40 c 0,3 -3,6 -6,6 h -10 c -3,0 -6,-3 -6,-6 v -40 c 0,-3 3,-5 6,-5 z"
|
||||||
|
id="path23"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#006498" /><path
|
||||||
|
class="fil1"
|
||||||
|
d="m 254,226 c 10,0 17,7 17,17 0,9 -7,16 -17,16 -9,0 -17,-7 -17,-16 0,-10 8,-17 17,-17 z"
|
||||||
|
id="path25"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#fefefe" /><path
|
||||||
|
class="fil6"
|
||||||
|
d="m 267,448 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,3 -3,3 H 267 c -2,0 -3,-2 -3,-3 v 0 c 0,-2 1,-3 3,-3 z"
|
||||||
|
id="path27"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#006498" /><path
|
||||||
|
class="fil6"
|
||||||
|
d="m 267,415 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,2 -3,2 H 267 c -2,0 -3,-1 -3,-2 v 0 c 0,-2 1,-3 3,-3 z"
|
||||||
|
id="path29"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#006498" /><path
|
||||||
|
class="fil6"
|
||||||
|
d="m 267,381 h 165 c 2,0 3,2 3,3 v 0 c 0,2 -1,3 -3,3 H 267 c -2,0 -3,-1 -3,-3 v 0 c 0,-1 1,-3 3,-3 z"
|
||||||
|
id="path31"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#006498" /><path
|
||||||
|
class="fil1"
|
||||||
|
d="m 236,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
|
||||||
|
id="path33"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#fefefe" /><path
|
||||||
|
class="fil1"
|
||||||
|
d="m 463,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
|
||||||
|
id="path35"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#fefefe" /><polygon
|
||||||
|
class="fil6"
|
||||||
|
points="305,212 284,212 284,310 305,310 "
|
||||||
|
id="polygon37"
|
||||||
|
style="fill:#006498" /><path
|
||||||
|
class="fil7"
|
||||||
|
d="m 477,479 v 2 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 v -2 c 0,4 3,8 8,8 h 238 c 5,0 8,-4 8,-8 z"
|
||||||
|
id="path39"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#0ea5eb" /><path
|
||||||
|
class="fil8"
|
||||||
|
d="M 350,70 C 505,70 630,195 630,350 630,505 505,630 350,630 195,630 70,505 70,350 70,195 195,70 350,70 Z m 0,46 C 479,116 584,221 584,350 584,479 479,584 350,584 221,584 116,479 116,350 116,221 221,116 350,116 Z"
|
||||||
|
id="path41"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#2979ff" /></g>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
143
frontend/public/index.html
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||||
|
|
||||||
|
[{[ if .ReCaptcha -]}]
|
||||||
|
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
|
||||||
|
[{[ end ]}]
|
||||||
|
|
||||||
|
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
|
||||||
|
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||||
|
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
|
||||||
|
<meta name="theme-color" content="#2979ff">
|
||||||
|
|
||||||
|
<!-- Add to home screen for Safari on iOS -->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="assets">
|
||||||
|
<link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon-152x152.png">
|
||||||
|
|
||||||
|
<!-- Add to home screen for Windows -->
|
||||||
|
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/msapplication-icon-144x144.png">
|
||||||
|
<meta name="msapplication-TileColor" content="#2979ff">
|
||||||
|
|
||||||
|
<!-- Inject Some Variables and generate the manifest json -->
|
||||||
|
<script>
|
||||||
|
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
|
||||||
|
|
||||||
|
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
||||||
|
var dynamicManifest = {
|
||||||
|
"name": window.FileBrowser.Name || 'File Browser',
|
||||||
|
"short_name": window.FileBrowser.Name || 'File Browser',
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": fullStaticURL + "/img/icons/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": fullStaticURL + "/img/icons/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": window.location.origin + window.FileBrowser.BaseURL,
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#455a64"
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringManifest = JSON.stringify(dynamicManifest);
|
||||||
|
const blob = new Blob([stringManifest], {type: 'application/json'});
|
||||||
|
const manifestURL = URL.createObjectURL(blob);
|
||||||
|
document.querySelector('#manifestPlaceholder').setAttribute('href', manifestURL);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#loading {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
z-index: 9999;
|
||||||
|
transition: .1s ease opacity;
|
||||||
|
-webkit-transition: .1s ease opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading.done {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 70px;
|
||||||
|
text-align: center;
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
-webkit-transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner > div {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||||
|
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner .bounce1 {
|
||||||
|
-webkit-animation-delay: -0.32s;
|
||||||
|
animation-delay: -0.32s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner .bounce2 {
|
||||||
|
-webkit-animation-delay: -0.16s;
|
||||||
|
animation-delay: -0.16s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes sk-bouncedelay {
|
||||||
|
0%, 80%, 100% { -webkit-transform: scale(0) }
|
||||||
|
40% { -webkit-transform: scale(1.0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sk-bouncedelay {
|
||||||
|
0%, 80%, 100% {
|
||||||
|
-webkit-transform: scale(0);
|
||||||
|
transform: scale(0);
|
||||||
|
} 40% {
|
||||||
|
-webkit-transform: scale(1.0);
|
||||||
|
transform: scale(1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<div id="loading">
|
||||||
|
<div class="spinner">
|
||||||
|
<div class="bounce1"></div>
|
||||||
|
<div class="bounce2"></div>
|
||||||
|
<div class="bounce3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[{[ if .Theme -]}]
|
||||||
|
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css" />
|
||||||
|
[{[ end ]}]
|
||||||
|
[{[ if .CSS -]}]
|
||||||
|
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
||||||
|
[{[ end ]}]
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
frontend/public/manifest.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "File Browser",
|
||||||
|
"short_name": "File Browser",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "./img/icons/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "./static/img/icons/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#455a64"
|
||||||
|
}
|
||||||
200
frontend/public/themes/dark.css
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
:root {
|
||||||
|
--background: #141D24;
|
||||||
|
--surfacePrimary: #20292F;
|
||||||
|
--surfaceSecondary: #3A4147;
|
||||||
|
--divider: rgba(255, 255, 255, 0.12);
|
||||||
|
--icon: #ffffff;
|
||||||
|
--textPrimary: rgba(255, 255, 255, 0.87);
|
||||||
|
--textSecondary: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading {
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
|
#loading .spinner div, #previewer .loading .spinner div {
|
||||||
|
background: var(--icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
#login {
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#search #input {
|
||||||
|
background: var(--surfaceSecondary);
|
||||||
|
border-color: var(--surfacePrimary);
|
||||||
|
}
|
||||||
|
#search #input input::placeholder {
|
||||||
|
color: var(--textSecondary);
|
||||||
|
}
|
||||||
|
#search.active #input {
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
}
|
||||||
|
#search.active input {
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
#search #result {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
#search .boxes {
|
||||||
|
background: var(--surfaceSecondary);
|
||||||
|
}
|
||||||
|
#search .boxes h3 {
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
color: var(--textPrimary) !important;
|
||||||
|
}
|
||||||
|
.action:hover {
|
||||||
|
background-color: rgba(255, 255, 255, .1);
|
||||||
|
}
|
||||||
|
.action i {
|
||||||
|
color: var(--icon) !important;
|
||||||
|
}
|
||||||
|
.action .counter {
|
||||||
|
border-color: var(--surfacePrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > div {
|
||||||
|
border-color: var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
#breadcrumbs {
|
||||||
|
border-color: var(--divider);
|
||||||
|
color: var(--textPrimary) !important;
|
||||||
|
}
|
||||||
|
#breadcrumbs span {
|
||||||
|
color: var(--textPrimary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#listing .item {
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
color: var(--textPrimary);
|
||||||
|
border-color: var(--divider) !important;
|
||||||
|
}
|
||||||
|
#listing .item i {
|
||||||
|
color: var(--icon);
|
||||||
|
}
|
||||||
|
#listing .item .modified {
|
||||||
|
color: var(--textSecondary);
|
||||||
|
}
|
||||||
|
#listing h2,
|
||||||
|
#listing.list .header span {
|
||||||
|
color: var(--textPrimary) !important;
|
||||||
|
}
|
||||||
|
#listing.list .header span {
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
#listing.list .header i {
|
||||||
|
color: var(--icon);
|
||||||
|
}
|
||||||
|
#listing.list .item.header {
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
.button--flat:hover {
|
||||||
|
background: var(--surfaceSecondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h3,
|
||||||
|
.dashboard #nav,
|
||||||
|
.dashboard p label {
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
.card#share ul li input,
|
||||||
|
.card#share ul li select,
|
||||||
|
.input {
|
||||||
|
background: var(--surfaceSecondary);
|
||||||
|
color: var(--textPrimary);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
.input:hover,
|
||||||
|
.input:focus {
|
||||||
|
border-color: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
.input--red {
|
||||||
|
background: #73302D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input--green {
|
||||||
|
background: #147A41;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard #nav li,
|
||||||
|
.collapsible {
|
||||||
|
border-color: var(--divider);
|
||||||
|
}
|
||||||
|
.collapsible > label * {
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
color: var(--textSecondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list li:hover {
|
||||||
|
background: var(--surfaceSecondary);
|
||||||
|
}
|
||||||
|
.file-list li:before {
|
||||||
|
color: var(--textSecondary);
|
||||||
|
}
|
||||||
|
.file-list li[aria-selected=true]:before {
|
||||||
|
color: var(--icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell {
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
.shell__result {
|
||||||
|
border-top: 1px solid var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container {
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-container .bar {
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 736px) {
|
||||||
|
#file-selection {
|
||||||
|
background: var(--surfaceSecondary) !important;
|
||||||
|
}
|
||||||
|
#file-selection span {
|
||||||
|
color: var(--textPrimary) !important;
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
background: var(--surfaceSecondary) !important;
|
||||||
|
}
|
||||||
|
#dropdown {
|
||||||
|
background: var(--surfaceSecondary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.share__box, .share__box__download {
|
||||||
|
background: var(--surfaceSecondary) !important;
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
.share__box__download {
|
||||||
|
border-bottom-color: var(--divider);
|
||||||
|
}
|
||||||
23
frontend/src/App.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'app',
|
||||||
|
mounted () {
|
||||||
|
const loading = document.getElementById('loading')
|
||||||
|
loading.classList.add('done')
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
loading.parentNode.removeChild(loading)
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import './css/styles.css';
|
||||||
|
</style>
|
||||||
16
frontend/src/api/commands.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { removePrefix } from './utils'
|
||||||
|
import { baseURL } from '@/utils/constants'
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
|
const ssl = (window.location.protocol === 'https:')
|
||||||
|
const protocol = (ssl ? 'wss:' : 'ws:')
|
||||||
|
|
||||||
|
export default function command(url, command, onmessage, onclose) {
|
||||||
|
url = removePrefix(url)
|
||||||
|
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`
|
||||||
|
|
||||||
|
let conn = new window.WebSocket(url)
|
||||||
|
conn.onopen = () => conn.send(command)
|
||||||
|
conn.onmessage = onmessage
|
||||||
|
conn.onclose = onclose
|
||||||
|
}
|
||||||
139
frontend/src/api/files.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { fetchURL, removePrefix } from './utils'
|
||||||
|
import { baseURL } from '@/utils/constants'
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
|
export async function fetch (url) {
|
||||||
|
url = removePrefix(url)
|
||||||
|
|
||||||
|
const res = await fetchURL(`/api/resources${url}`, {})
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
let data = await res.json()
|
||||||
|
data.url = `/files${url}`
|
||||||
|
|
||||||
|
if (data.isDir) {
|
||||||
|
if (!data.url.endsWith('/')) data.url += '/'
|
||||||
|
data.items = data.items.map((item, index) => {
|
||||||
|
item.index = index
|
||||||
|
item.url = `${data.url}${encodeURIComponent(item.name)}`
|
||||||
|
|
||||||
|
if (item.isDir) {
|
||||||
|
item.url += '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
throw new Error(res.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resourceAction (url, method, content) {
|
||||||
|
url = removePrefix(url)
|
||||||
|
|
||||||
|
let opts = { method }
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
opts.body = content
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetchURL(`/api/resources${url}`, opts)
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(await res.text())
|
||||||
|
} else {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove (url) {
|
||||||
|
return resourceAction(url, 'DELETE')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function put (url, content = '') {
|
||||||
|
return resourceAction(url, 'PUT', content)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function download (format, ...files) {
|
||||||
|
let url = `${baseURL}/api/raw`
|
||||||
|
|
||||||
|
if (files.length === 1) {
|
||||||
|
url += removePrefix(files[0]) + '?'
|
||||||
|
} else {
|
||||||
|
let arg = ''
|
||||||
|
|
||||||
|
for (let file of files) {
|
||||||
|
arg += removePrefix(file) + ','
|
||||||
|
}
|
||||||
|
|
||||||
|
arg = arg.substring(0, arg.length - 1)
|
||||||
|
arg = encodeURIComponent(arg)
|
||||||
|
url += `/?files=${arg}&`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format !== null) {
|
||||||
|
url += `algo=${format}&`
|
||||||
|
}
|
||||||
|
|
||||||
|
url += `auth=${store.state.jwt}`
|
||||||
|
window.open(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function post (url, content = '', overwrite = false, onupload) {
|
||||||
|
url = removePrefix(url)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let request = new XMLHttpRequest()
|
||||||
|
request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true)
|
||||||
|
request.setRequestHeader('X-Auth', store.state.jwt)
|
||||||
|
|
||||||
|
if (typeof onupload === 'function') {
|
||||||
|
request.upload.onprogress = onupload
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onload = () => {
|
||||||
|
if (request.status === 200) {
|
||||||
|
resolve(request.responseText)
|
||||||
|
} else if (request.status === 409) {
|
||||||
|
reject(request.status)
|
||||||
|
} else {
|
||||||
|
reject(request.responseText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onerror = (error) => {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.send(content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveCopy (items, copy = false, overwrite = false, rename = false) {
|
||||||
|
let promises = []
|
||||||
|
|
||||||
|
for (let item of items) {
|
||||||
|
const from = removePrefix(item.from)
|
||||||
|
const to = encodeURIComponent(removePrefix(item.to))
|
||||||
|
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}&override=${overwrite}&rename=${rename}`
|
||||||
|
promises.push(resourceAction(url, 'PATCH'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function move (items, overwrite = false, rename = false) {
|
||||||
|
return moveCopy(items, false, overwrite, rename)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copy (items, overwrite = false, rename = false) {
|
||||||
|
return moveCopy(items, true, overwrite, rename)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checksum (url, algo) {
|
||||||
|
const data = await resourceAction(`${url}?checksum=${algo}`, 'GET')
|
||||||
|
return (await data.json()).checksums[algo]
|
||||||
|
}
|
||||||
15
frontend/src/api/index.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import * as files from './files'
|
||||||
|
import * as share from './share'
|
||||||
|
import * as users from './users'
|
||||||
|
import * as settings from './settings'
|
||||||
|
import search from './search'
|
||||||
|
import commands from './commands'
|
||||||
|
|
||||||
|
export {
|
||||||
|
files,
|
||||||
|
share,
|
||||||
|
users,
|
||||||
|
settings,
|
||||||
|
commands,
|
||||||
|
search
|
||||||
|
}
|
||||||
8
frontend/src/api/search.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { fetchJSON, removePrefix } from './utils'
|
||||||
|
|
||||||
|
export default async function search (url, query) {
|
||||||
|
url = removePrefix(url)
|
||||||
|
query = encodeURIComponent(query)
|
||||||
|
|
||||||
|
return fetchJSON(`/api/search${url}?query=${query}`, {})
|
||||||
|
}
|
||||||
16
frontend/src/api/settings.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { fetchURL, fetchJSON } from './utils'
|
||||||
|
|
||||||
|
export function get () {
|
||||||
|
return fetchJSON(`/api/settings`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function update (settings) {
|
||||||
|
const res = await fetchURL(`/api/settings`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(settings)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(res.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
32
frontend/src/api/share.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { fetchURL, fetchJSON, removePrefix } from './utils'
|
||||||
|
|
||||||
|
export async function getHash(hash) {
|
||||||
|
return fetchJSON(`/api/public/share/${hash}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function get(url) {
|
||||||
|
url = removePrefix(url)
|
||||||
|
return fetchJSON(`/api/share${url}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(hash) {
|
||||||
|
const res = await fetchURL(`/api/share/${hash}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(res.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(url, expires = '', unit = 'hours') {
|
||||||
|
url = removePrefix(url)
|
||||||
|
url = `/api/share${url}`
|
||||||
|
if (expires !== '') {
|
||||||
|
url += `?expires=${expires}&unit=${unit}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchJSON(url, {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
}
|
||||||
52
frontend/src/api/users.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { fetchURL, fetchJSON } from './utils'
|
||||||
|
|
||||||
|
export async function getAll () {
|
||||||
|
return fetchJSON(`/api/users`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function get (id) {
|
||||||
|
return fetchJSON(`/api/users/${id}`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create (user) {
|
||||||
|
const res = await fetchURL(`/api/users`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
what: 'user',
|
||||||
|
which: [],
|
||||||
|
data: user
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.status === 201) {
|
||||||
|
return res.headers.get('Location')
|
||||||
|
} else {
|
||||||
|
throw new Error(res.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function update (user, which = ['all']) {
|
||||||
|
const res = await fetchURL(`/api/users/${user.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({
|
||||||
|
what: 'user',
|
||||||
|
which: which,
|
||||||
|
data: user
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(res.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove (id) {
|
||||||
|
const res = await fetchURL(`/api/users/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(res.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
frontend/src/api/utils.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import store from '@/store'
|
||||||
|
import { renew } from '@/utils/auth'
|
||||||
|
import { baseURL } from '@/utils/constants'
|
||||||
|
|
||||||
|
export async function fetchURL (url, opts) {
|
||||||
|
opts = opts || {}
|
||||||
|
opts.headers = opts.headers || {}
|
||||||
|
|
||||||
|
let { headers, ...rest } = opts
|
||||||
|
|
||||||
|
const res = await fetch(`${baseURL}${url}`, {
|
||||||
|
headers: {
|
||||||
|
'X-Auth': store.state.jwt,
|
||||||
|
...headers
|
||||||
|
},
|
||||||
|
...rest
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.headers.get('X-Renew-Token') === 'true') {
|
||||||
|
await renew(store.state.jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchJSON (url, opts) {
|
||||||
|
const res = await fetchURL(url, opts)
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.json()
|
||||||
|
} else {
|
||||||
|
throw new Error(res.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removePrefix (url) {
|
||||||
|
if (url.startsWith('/files')) {
|
||||||
|
url = url.slice(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url === '') url = '/'
|
||||||
|
if (url[0] !== '/') url = '/' + url
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
BIN
frontend/src/assets/fonts/roboto/medium-cyrillic-ext.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/medium-cyrillic.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/medium-greek-ext.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/medium-greek.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/medium-latin-ext.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/medium-latin.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/medium-vietnamese.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/normal-cyrillic-ext.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/normal-cyrillic.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/normal-greek-ext.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/normal-greek.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/normal-latin-ext.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/normal-latin.woff2
Normal file
BIN
frontend/src/assets/fonts/roboto/normal-vietnamese.woff2
Normal file
182
frontend/src/components/Header.vue
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<header v-if="!isEditor">
|
||||||
|
<div>
|
||||||
|
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
|
||||||
|
<i class="material-icons">menu</i>
|
||||||
|
</button>
|
||||||
|
<img :src="logoURL" alt="File Browser">
|
||||||
|
<search v-if="isLogged"></search>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<template v-if="isLogged">
|
||||||
|
<button @click="openSearch" :aria-label="$t('buttons.search')" :title="$t('buttons.search')" class="search-button action">
|
||||||
|
<i class="material-icons">search</i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
|
||||||
|
<i class="material-icons">more_vert</i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Menu that shows on listing AND mobile when there are files selected -->
|
||||||
|
<div id="file-selection" v-if="isMobile && isListing">
|
||||||
|
<span v-if="selectedCount > 0">{{ selectedCount }} selected</span>
|
||||||
|
<share-button v-show="showShareButton"></share-button>
|
||||||
|
<rename-button v-show="showRenameButton"></rename-button>
|
||||||
|
<copy-button v-show="showCopyButton"></copy-button>
|
||||||
|
<move-button v-show="showMoveButton"></move-button>
|
||||||
|
<delete-button v-show="showDeleteButton"></delete-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- This buttons are shown on a dropdown on mobile phones -->
|
||||||
|
<div id="dropdown" :class="{ active: showMore }">
|
||||||
|
<div v-if="!isListing || !isMobile">
|
||||||
|
<share-button v-show="showShareButton"></share-button>
|
||||||
|
<rename-button v-show="showRenameButton"></rename-button>
|
||||||
|
<copy-button v-show="showCopyButton"></copy-button>
|
||||||
|
<move-button v-show="showMoveButton"></move-button>
|
||||||
|
<delete-button v-show="showDeleteButton"></delete-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<shell-button v-show="user.perm.execute" />
|
||||||
|
<switch-button v-show="isListing"></switch-button>
|
||||||
|
<download-button v-show="showDownloadButton"></download-button>
|
||||||
|
<upload-button v-show="showUpload"></upload-button>
|
||||||
|
<info-button v-show="isFiles"></info-button>
|
||||||
|
|
||||||
|
<button v-show="isListing" @click="toggleMultipleSelection" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action" >
|
||||||
|
<i class="material-icons">check_circle</i>
|
||||||
|
<span>{{ $t('buttons.select') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Search from './Search'
|
||||||
|
import InfoButton from './buttons/Info'
|
||||||
|
import DeleteButton from './buttons/Delete'
|
||||||
|
import RenameButton from './buttons/Rename'
|
||||||
|
import UploadButton from './buttons/Upload'
|
||||||
|
import DownloadButton from './buttons/Download'
|
||||||
|
import SwitchButton from './buttons/SwitchView'
|
||||||
|
import MoveButton from './buttons/Move'
|
||||||
|
import CopyButton from './buttons/Copy'
|
||||||
|
import ShareButton from './buttons/Share'
|
||||||
|
import ShellButton from './buttons/Shell'
|
||||||
|
import {mapGetters, mapState} from 'vuex'
|
||||||
|
import { logoURL } from '@/utils/constants'
|
||||||
|
import * as api from '@/api'
|
||||||
|
import buttons from '@/utils/buttons'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'header-layout',
|
||||||
|
components: {
|
||||||
|
Search,
|
||||||
|
InfoButton,
|
||||||
|
DeleteButton,
|
||||||
|
ShareButton,
|
||||||
|
RenameButton,
|
||||||
|
DownloadButton,
|
||||||
|
CopyButton,
|
||||||
|
UploadButton,
|
||||||
|
SwitchButton,
|
||||||
|
MoveButton,
|
||||||
|
ShellButton
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
width: window.innerWidth,
|
||||||
|
pluginData: {
|
||||||
|
api,
|
||||||
|
buttons,
|
||||||
|
'store': this.$store,
|
||||||
|
'router': this.$router
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.width = window.innerWidth
|
||||||
|
})
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'selectedCount',
|
||||||
|
'isFiles',
|
||||||
|
'isEditor',
|
||||||
|
'isListing',
|
||||||
|
'isLogged'
|
||||||
|
]),
|
||||||
|
...mapState([
|
||||||
|
'req',
|
||||||
|
'user',
|
||||||
|
'loading',
|
||||||
|
'reload',
|
||||||
|
'multiple'
|
||||||
|
]),
|
||||||
|
logoURL: () => logoURL,
|
||||||
|
isMobile () {
|
||||||
|
return this.width <= 736
|
||||||
|
},
|
||||||
|
showUpload () {
|
||||||
|
return this.isListing && this.user.perm.create
|
||||||
|
},
|
||||||
|
showDownloadButton () {
|
||||||
|
return this.isFiles && this.user.perm.download
|
||||||
|
},
|
||||||
|
showDeleteButton () {
|
||||||
|
return this.isFiles && (this.isListing
|
||||||
|
? (this.selectedCount !== 0 && this.user.perm.delete)
|
||||||
|
: this.user.perm.delete)
|
||||||
|
},
|
||||||
|
showRenameButton () {
|
||||||
|
return this.isFiles && (this.isListing
|
||||||
|
? (this.selectedCount === 1 && this.user.perm.rename)
|
||||||
|
: this.user.perm.rename)
|
||||||
|
},
|
||||||
|
showShareButton () {
|
||||||
|
return this.isFiles && (this.isListing
|
||||||
|
? (this.selectedCount === 1 && this.user.perm.share)
|
||||||
|
: this.user.perm.share)
|
||||||
|
},
|
||||||
|
showMoveButton () {
|
||||||
|
return this.isFiles && (this.isListing
|
||||||
|
? (this.selectedCount > 0 && this.user.perm.rename)
|
||||||
|
: this.user.perm.rename)
|
||||||
|
},
|
||||||
|
showCopyButton () {
|
||||||
|
return this.isFiles && (this.isListing
|
||||||
|
? (this.selectedCount > 0 && this.user.perm.create)
|
||||||
|
: this.user.perm.create)
|
||||||
|
},
|
||||||
|
showMore () {
|
||||||
|
return this.isFiles && this.$store.state.show === 'more'
|
||||||
|
},
|
||||||
|
showOverlay () {
|
||||||
|
return this.showMore
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openSidebar () {
|
||||||
|
this.$store.commit('showHover', 'sidebar')
|
||||||
|
},
|
||||||
|
openMore () {
|
||||||
|
this.$store.commit('showHover', 'more')
|
||||||
|
},
|
||||||
|
openSearch () {
|
||||||
|
this.$store.commit('showHover', 'search')
|
||||||
|
},
|
||||||
|
toggleMultipleSelection () {
|
||||||
|
this.$store.commit('multiple', !this.multiple)
|
||||||
|
this.resetPrompts()
|
||||||
|
},
|
||||||
|
resetPrompts () {
|
||||||
|
this.$store.commit('closeHovers')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
192
frontend/src/components/Search.vue
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<template>
|
||||||
|
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
|
||||||
|
<div id="input">
|
||||||
|
<button
|
||||||
|
v-if="active"
|
||||||
|
class="action"
|
||||||
|
@click="close"
|
||||||
|
:aria-label="$t('buttons.close')"
|
||||||
|
:title="$t('buttons.close')"
|
||||||
|
>
|
||||||
|
<i class="material-icons">arrow_back</i>
|
||||||
|
</button>
|
||||||
|
<i v-else class="material-icons">search</i>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
@keyup.exact="keyup"
|
||||||
|
@keyup.enter="submit"
|
||||||
|
ref="input"
|
||||||
|
:autofocus="active"
|
||||||
|
v-model.trim="value"
|
||||||
|
:aria-label="$t('search.search')"
|
||||||
|
:placeholder="$t('search.search')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="result" ref="result">
|
||||||
|
<div>
|
||||||
|
<template v-if="isEmpty">
|
||||||
|
<p>{{ text }}</p>
|
||||||
|
|
||||||
|
<template v-if="value.length === 0">
|
||||||
|
<div class="boxes">
|
||||||
|
<h3>{{ $t('search.types') }}</h3>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
v-for="(v,k) in boxes"
|
||||||
|
:key="k"
|
||||||
|
role="button"
|
||||||
|
@click="init('type:'+k)"
|
||||||
|
:aria-label="$t('search.'+v.label)"
|
||||||
|
>
|
||||||
|
<i class="material-icons">{{v.icon}}</i>
|
||||||
|
<p>{{ $t('search.'+v.label) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<ul v-show="results.length > 0">
|
||||||
|
<li v-for="(s,k) in filteredResults" :key="k">
|
||||||
|
<router-link @click.native="close" :to="'./' + s.path">
|
||||||
|
<i v-if="s.dir" class="material-icons">folder</i>
|
||||||
|
<i v-else class="material-icons">insert_drive_file</i>
|
||||||
|
<span>./{{ s.path }}</span>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p id="renew">
|
||||||
|
<i class="material-icons spin">autorenew</i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState, mapGetters, mapMutations } from "vuex"
|
||||||
|
import url from "@/utils/url"
|
||||||
|
import { search } from "@/api"
|
||||||
|
|
||||||
|
var boxes = {
|
||||||
|
image: { label: "images", icon: "insert_photo" },
|
||||||
|
audio: { label: "music", icon: "volume_up" },
|
||||||
|
video: { label: "video", icon: "movie" },
|
||||||
|
pdf: { label: "pdf", icon: "picture_as_pdf" }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "search",
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
value: "",
|
||||||
|
active: false,
|
||||||
|
ongoing: false,
|
||||||
|
results: [],
|
||||||
|
reload: false,
|
||||||
|
resultsCount: 50,
|
||||||
|
scrollable: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show (val, old) {
|
||||||
|
this.active = val === "search"
|
||||||
|
|
||||||
|
if (old === "search" && !this.active) {
|
||||||
|
if (this.reload) {
|
||||||
|
this.setReload(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.style.overflow = "auto"
|
||||||
|
this.reset()
|
||||||
|
this.value = ''
|
||||||
|
this.active = false
|
||||||
|
this.$refs.input.blur()
|
||||||
|
} else if (this.active) {
|
||||||
|
this.reload = false
|
||||||
|
this.$refs.input.focus()
|
||||||
|
document.body.style.overflow = "hidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value () {
|
||||||
|
if (this.results.length) {
|
||||||
|
this.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["user", "show"]),
|
||||||
|
...mapGetters(["isListing"]),
|
||||||
|
boxes() {
|
||||||
|
return boxes
|
||||||
|
},
|
||||||
|
isEmpty() {
|
||||||
|
return this.results.length === 0
|
||||||
|
},
|
||||||
|
text() {
|
||||||
|
if (this.ongoing) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.value === '' ? this.$t("search.typeToSearch") : this.$t("search.pressToSearch")
|
||||||
|
},
|
||||||
|
filteredResults () {
|
||||||
|
return this.results.slice(0, this.resultsCount)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$refs.result.addEventListener('scroll', event => {
|
||||||
|
if (event.target.offsetHeight + event.target.scrollTop >= event.target.scrollHeight - 100) {
|
||||||
|
this.resultsCount += 50
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations(["showHover", "closeHovers", "setReload"]),
|
||||||
|
open() {
|
||||||
|
this.showHover("search")
|
||||||
|
},
|
||||||
|
close(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
event.preventDefault()
|
||||||
|
this.closeHovers()
|
||||||
|
},
|
||||||
|
keyup(event) {
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
this.close(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.results.length = 0
|
||||||
|
},
|
||||||
|
init (string) {
|
||||||
|
this.value = `${string} `
|
||||||
|
this.$refs.input.focus()
|
||||||
|
},
|
||||||
|
reset () {
|
||||||
|
this.ongoing = false
|
||||||
|
this.resultsCount = 50
|
||||||
|
this.results = []
|
||||||
|
},
|
||||||
|
async submit(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (this.value === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = this.$route.path
|
||||||
|
if (!this.isListing) {
|
||||||
|
path = url.removeLastDir(path) + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ongoing = true
|
||||||
|
|
||||||
|
|
||||||
|
this.results = await search(path, this.value)
|
||||||
|
this.ongoing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
115
frontend/src/components/Shell.vue
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<div @click="focus" class="shell" ref="scrollable" :class="{ ['shell--hidden']: !showShell}">
|
||||||
|
<div v-for="(c, index) in content" :key="index" class="shell__result" >
|
||||||
|
<div class="shell__prompt"><i class="material-icons">chevron_right</i></div>
|
||||||
|
<pre class="shell__text">{{ c.text }}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shell__result" :class="{ 'shell__result--hidden': !canInput }" >
|
||||||
|
<div class="shell__prompt"><i class="material-icons">chevron_right</i></div>
|
||||||
|
<pre
|
||||||
|
tabindex="0"
|
||||||
|
ref="input"
|
||||||
|
class="shell__text"
|
||||||
|
contenteditable="true"
|
||||||
|
@keydown.prevent.38="historyUp"
|
||||||
|
@keydown.prevent.40="historyDown"
|
||||||
|
@keypress.prevent.enter="submit" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapMutations, mapState, mapGetters } from 'vuex'
|
||||||
|
import { commands } from '@/api'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'shell',
|
||||||
|
computed: {
|
||||||
|
...mapState([ 'user', 'showShell' ]),
|
||||||
|
...mapGetters([ 'isFiles', 'isLogged' ]),
|
||||||
|
path: function () {
|
||||||
|
if (this.isFiles) {
|
||||||
|
return this.$route.path
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
content: [],
|
||||||
|
history: [],
|
||||||
|
historyPos: 0,
|
||||||
|
canInput: true
|
||||||
|
}),
|
||||||
|
methods: {
|
||||||
|
...mapMutations([ 'toggleShell' ]),
|
||||||
|
scroll: function () {
|
||||||
|
this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight
|
||||||
|
},
|
||||||
|
focus: function () {
|
||||||
|
this.$refs.input.focus()
|
||||||
|
},
|
||||||
|
historyUp () {
|
||||||
|
if (this.historyPos > 0) {
|
||||||
|
this.$refs.input.innerText = this.history[--this.historyPos]
|
||||||
|
this.focus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
historyDown () {
|
||||||
|
if (this.historyPos >= 0 && this.historyPos < this.history.length - 1) {
|
||||||
|
this.$refs.input.innerText = this.history[++this.historyPos]
|
||||||
|
this.focus()
|
||||||
|
} else {
|
||||||
|
this.historyPos = this.history.length
|
||||||
|
this.$refs.input.innerText = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit: function (event) {
|
||||||
|
const cmd = event.target.innerText.trim()
|
||||||
|
|
||||||
|
if (cmd === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd === 'clear') {
|
||||||
|
this.content = []
|
||||||
|
event.target.innerHTML = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd === 'exit') {
|
||||||
|
event.target.innerHTML = ''
|
||||||
|
this.toggleShell()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canInput = false
|
||||||
|
event.target.innerHTML = ''
|
||||||
|
|
||||||
|
let results = {
|
||||||
|
text: `${cmd}\n\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.history.push(cmd)
|
||||||
|
this.historyPos = this.history.length
|
||||||
|
this.content.push(results)
|
||||||
|
|
||||||
|
commands(
|
||||||
|
this.path,
|
||||||
|
cmd,
|
||||||
|
event => {
|
||||||
|
results.text += `${event.data}\n`
|
||||||
|
this.scroll()
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
results.text = results.text.trimEnd()
|
||||||
|
this.canInput = true
|
||||||
|
this.$refs.input.focus()
|
||||||
|
this.scroll()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
82
frontend/src/components/Sidebar.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<nav :class="{active}">
|
||||||
|
<template v-if="isLogged">
|
||||||
|
<router-link class="action" to="/files/" :aria-label="$t('sidebar.myFiles')" :title="$t('sidebar.myFiles')">
|
||||||
|
<i class="material-icons">folder</i>
|
||||||
|
<span>{{ $t('sidebar.myFiles') }}</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<div v-if="user.perm.create">
|
||||||
|
<button @click="$store.commit('showHover', 'newDir')" class="action" :aria-label="$t('sidebar.newFolder')" :title="$t('sidebar.newFolder')">
|
||||||
|
<i class="material-icons">create_new_folder</i>
|
||||||
|
<span>{{ $t('sidebar.newFolder') }}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button @click="$store.commit('showHover', 'newFile')" class="action" :aria-label="$t('sidebar.newFile')" :title="$t('sidebar.newFile')">
|
||||||
|
<i class="material-icons">note_add</i>
|
||||||
|
<span>{{ $t('sidebar.newFile') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<router-link class="action" to="/settings" :aria-label="$t('sidebar.settings')" :title="$t('sidebar.settings')">
|
||||||
|
<i class="material-icons">settings_applications</i>
|
||||||
|
<span>{{ $t('sidebar.settings') }}</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<button v-if="authMethod == 'json'" @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')">
|
||||||
|
<i class="material-icons">exit_to_app</i>
|
||||||
|
<span>{{ $t('sidebar.logout') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<router-link class="action" to="/login" :aria-label="$t('sidebar.login')" :title="$t('sidebar.login')">
|
||||||
|
<i class="material-icons">exit_to_app</i>
|
||||||
|
<span>{{ $t('sidebar.login') }}</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link v-if="signup" class="action" to="/login" :aria-label="$t('sidebar.signup')" :title="$t('sidebar.signup')">
|
||||||
|
<i class="material-icons">person_add</i>
|
||||||
|
<span>{{ $t('sidebar.signup') }}</span>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<p class="credits">
|
||||||
|
<span>
|
||||||
|
<span v-if="disableExternal">File Browser</span>
|
||||||
|
<a v-else rel="noopener noreferrer" target="_blank" href="https://github.com/filebrowser/filebrowser">File Browser</a>
|
||||||
|
<span> {{ version }}</span>
|
||||||
|
</span>
|
||||||
|
<span><a @click="help">{{ $t('sidebar.help') }}</a></span>
|
||||||
|
</p>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState, mapGetters } from 'vuex'
|
||||||
|
import * as auth from '@/utils/auth'
|
||||||
|
import { version, signup, disableExternal, noAuth, authMethod } from '@/utils/constants'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'sidebar',
|
||||||
|
computed: {
|
||||||
|
...mapState([ 'user' ]),
|
||||||
|
...mapGetters([ 'isLogged' ]),
|
||||||
|
active () {
|
||||||
|
return this.$store.state.show === 'sidebar'
|
||||||
|
},
|
||||||
|
signup: () => signup,
|
||||||
|
version: () => version,
|
||||||
|
disableExternal: () => disableExternal,
|
||||||
|
noAuth: () => noAuth,
|
||||||
|
authMethod: () => authMethod
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
help () {
|
||||||
|
this.$store.commit('showHover', 'help')
|
||||||
|
},
|
||||||
|
logout: auth.logout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
17
frontend/src/components/buttons/Copy.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
|
||||||
|
<i class="material-icons">content_copy</i>
|
||||||
|
<span>{{ $t('buttons.copyFile') }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'copy-button',
|
||||||
|
methods: {
|
||||||
|
show: function () {
|
||||||
|
this.$store.commit('showHover', 'copy')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
17
frontend/src/components/buttons/Delete.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
|
||||||
|
<i class="material-icons">delete</i>
|
||||||
|
<span>{{ $t('buttons.delete') }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'delete-button',
|
||||||
|
methods: {
|
||||||
|
show: function () {
|
||||||
|
this.$store.commit('showHover', 'delete')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
35
frontend/src/components/buttons/Download.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
|
||||||
|
<i class="material-icons">file_download</i>
|
||||||
|
<span>{{ $t('buttons.download') }}</span>
|
||||||
|
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapGetters, mapState} from 'vuex'
|
||||||
|
import { files as api } from '@/api'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'download-button',
|
||||||
|
computed: {
|
||||||
|
...mapState(['req', 'selected']),
|
||||||
|
...mapGetters(['isListing', 'selectedCount'])
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
download: function () {
|
||||||
|
if (!this.isListing) {
|
||||||
|
api.download(null, this.$route.path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||||
|
api.download(null, this.req.items[this.selected[0]].url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.commit('showHover', 'download')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
17
frontend/src/components/buttons/Info.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
|
||||||
|
<i class="material-icons">info</i>
|
||||||
|
<span>{{ $t('buttons.info') }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'info-button',
|
||||||
|
methods: {
|
||||||
|
show: function () {
|
||||||
|
this.$store.commit('showHover', 'info')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
17
frontend/src/components/buttons/Move.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
|
||||||
|
<i class="material-icons">forward</i>
|
||||||
|
<span>{{ $t('buttons.moveFile') }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'move-button',
|
||||||
|
methods: {
|
||||||
|
show: function () {
|
||||||
|
this.$store.commit('showHover', 'move')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
17
frontend/src/components/buttons/Rename.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
|
||||||
|
<i class="material-icons">mode_edit</i>
|
||||||
|
<span>{{ $t('buttons.rename') }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'rename-button',
|
||||||
|
methods: {
|
||||||
|
show: function () {
|
||||||
|
this.$store.commit('showHover', 'rename')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||