mirror of
https://github.com/filebrowser/filebrowser.git
synced 2025-06-17 14:53:00 +00:00
Compare commits
No commits in common. "master" and "v2.20.1" have entirely different histories.
@ -1,5 +1,3 @@
|
|||||||
*
|
*
|
||||||
!docker/*
|
!docker/*
|
||||||
!healthcheck.sh
|
|
||||||
!docker_config.json
|
|
||||||
!filebrowser
|
!filebrowser
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -2,4 +2,4 @@
|
|||||||
# Unless a later match takes precedence, @o1egl will be requested for
|
# Unless a later match takes precedence, @o1egl will be requested for
|
||||||
# review when someone opens a pull request.
|
# review when someone opens a pull request.
|
||||||
|
|
||||||
* @o1egl @hacdias
|
* @o1egl
|
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
---
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
<!-- A clear and concise description of what the issue is about. What are you trying to do? -->
|
||||||
|
|
||||||
|
**Expected behaviour**
|
||||||
|
<!-- What did you expect to happen? -->
|
||||||
|
|
||||||
|
**What is happening instead?**
|
||||||
|
<!-- Please, give full error messages and/or log. -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context about the problem here. If applicable, add screenshots to help explain your problem. -->
|
||||||
|
|
||||||
|
**How to reproduce?**
|
||||||
|
<!-- Tell us how to reproduce this issue. How can someone who is starting from scratch reproduce this behaviour as minimally as possible? -->
|
||||||
|
|
||||||
|
**Files**
|
||||||
|
<!-- A list of relevant files for this issue. Large files can be uploaded one-by-one or in a tarball/zipfile. -->
|
43
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
43
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,43 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: Report a bug in FileBrowser.
|
|
||||||
labels: [bug, triage]
|
|
||||||
body:
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Checklist
|
|
||||||
description: Please verify that you've followed these steps
|
|
||||||
options:
|
|
||||||
- label: This is a bug report, not a question.
|
|
||||||
required: true
|
|
||||||
- label: I have searched on the [issue tracker](https://github.com/filebrowser/filebrowser/issues?q=is%3Aissue) for my bug.
|
|
||||||
required: true
|
|
||||||
- label: I am running the latest [FileBrowser version](https://github.com/filebrowser/filebrowser/releases) or have an issue updating.
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: version
|
|
||||||
attributes:
|
|
||||||
label: Version
|
|
||||||
render: Text
|
|
||||||
description: |
|
|
||||||
Enter the version of FileBrowser you are using.
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: |
|
|
||||||
A clear and concise description of what the issue is about. What are you trying to do?
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: What did you expect to happen?
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: What actually happened?
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Reproduction Steps
|
|
||||||
description: |
|
|
||||||
Tell us how to reproduce this issue. How can someone who is starting from scratch reproduce this behavior as minimally as possible?
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Files
|
|
||||||
description: |
|
|
||||||
A list of relevant files for this issue. Large files can be uploaded one-by-one or in a tarball/zipfile.
|
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: GitHub Discussions
|
|
||||||
url: https://github.com/filebrowser/filebrowser/discussions
|
|
||||||
about: Please ask questions and discuss features here.
|
|
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
<!-- Add a clear and concise description of what the problem is. E.g. *I'm always frustrated when [...]* -->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
<!-- Add a clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
<!-- Add a clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,16 +1,20 @@
|
|||||||
## Description
|
**Description**
|
||||||
|
<!--
|
||||||
|
Please explain the changes you made here.
|
||||||
|
If the feature changes current behaviour, explain why your solution is better.
|
||||||
|
-->
|
||||||
|
|
||||||
<!-- Please explain the changes you made here. -->
|
:rotating_light: Before submitting your PR, please read [community](https://github.com/filebrowser/community), and indicate which issues (in any of the repos) are either fixed or closed by this PR. See [GitHub Help: Closing issues using keywords](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||||
|
|
||||||
## Additional Information
|
- [ ] DO make sure you are requesting to **pull a topic/feature/bugfix branch** (right side). Don't request your master!
|
||||||
|
- [ ] DO make sure you are making a pull request against the **master branch** (left side). Also you should start *your branch* off *our master*.
|
||||||
|
- [ ] DO make sure that File Browser can be successfully built. See [builds](https://github.com/filebrowser/community/blob/master/builds.md) and [development](https://github.com/filebrowser/community/blob/master/development.md).
|
||||||
|
- [ ] DO make sure that related issues are opened in other repositories. I.e., the frontend, caddy plugins or the web page need to be updated accordingly.
|
||||||
|
- [ ] AVOID breaking the continuous integration build.
|
||||||
|
|
||||||
<!-- If it is a relatively large or complex change, please add more information to explain what you did, how you did it, if you considered any alternatives, etc. -->
|
**Further comments**
|
||||||
|
<!--
|
||||||
|
If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did, what alternatives you considered, etc.
|
||||||
|
|
||||||
## Checklist
|
:heart: Thank you!
|
||||||
|
-->
|
||||||
Before submitting your PR, please indicate which issues are either fixed or closed by this PR. See [GitHub Help: Closing issues using keywords](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
|
||||||
|
|
||||||
- [ ] I am aware the project is currently in maintenance-only mode. See [README](https://github.com/filebrowser/community/blob/master/README.md)
|
|
||||||
- [ ] I am aware that translations MUST be made through [Transifex](https://app.transifex.com/file-browser/file-browser/) and that this PR is NOT a translation update
|
|
||||||
- [ ] I am making a PR against the `master` branch.
|
|
||||||
- [ ] I am sure File Browser can be successfully built. See [builds](https://github.com/filebrowser/community/blob/master/builds.md) and [development](https://github.com/filebrowser/community/blob/master/development.md).
|
|
||||||
|
69
.github/workflows/main.yaml
vendored
69
.github/workflows/main.yaml
vendored
@ -3,9 +3,9 @@ name: main
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "master"
|
- 'master'
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- 'v*'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -13,27 +13,32 @@ jobs:
|
|||||||
lint-frontend:
|
lint-frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
package_json_file: "frontend/package.json"
|
node-version: '14'
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22.x"
|
|
||||||
cache: "pnpm"
|
|
||||||
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
|
||||||
- run: make lint-frontend
|
- run: make lint-frontend
|
||||||
lint-backend:
|
lint-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.23.0
|
go-version: 1.17
|
||||||
- run: make lint-backend
|
- run: make lint-backend
|
||||||
|
lint-commints:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '14'
|
||||||
|
- run: make lint-commits
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [lint-frontend, lint-backend]
|
needs: [lint-frontend, lint-backend, lint-commints]
|
||||||
steps:
|
steps:
|
||||||
- run: echo "done"
|
- run: echo "done"
|
||||||
|
|
||||||
@ -41,23 +46,18 @@ jobs:
|
|||||||
test-frontend:
|
test-frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
package_json_file: "frontend/package.json"
|
node-version: '14'
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22.x"
|
|
||||||
cache: "pnpm"
|
|
||||||
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
|
||||||
- run: make test-frontend
|
- run: make test-frontend
|
||||||
test-backend:
|
test-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.23.0
|
go-version: 1.17
|
||||||
- run: make test-backend
|
- run: make test-backend
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -71,25 +71,20 @@ jobs:
|
|||||||
needs: [lint, test]
|
needs: [lint, test]
|
||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.23.0
|
go-version: 1.17
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
package_json_file: "frontend/package.json"
|
node-version: '14'
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22.x"
|
|
||||||
cache: "pnpm"
|
|
||||||
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v1
|
||||||
- name: Build frontend
|
- name: Build fronetend
|
||||||
run: make build-frontend
|
run: make build-frontend
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
@ -100,6 +95,6 @@ jobs:
|
|||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean
|
args: release --rm-dist
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
46
.github/workflows/pr-lint.yaml
vendored
46
.github/workflows/pr-lint.yaml
vendored
@ -1,46 +0,0 @@
|
|||||||
name: "Lint PR"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- reopened
|
|
||||||
- edited
|
|
||||||
- synchronize
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
main:
|
|
||||||
name: Validate PR title
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: amannn/action-semantic-pull-request@v5
|
|
||||||
id: lint_pr_title
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- uses: marocchino/sticky-pull-request-comment@v2
|
|
||||||
# When the previous steps fails, the workflow would stop. By adding this
|
|
||||||
# condition you can continue the execution with the populated error message.
|
|
||||||
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
|
||||||
with:
|
|
||||||
header: pr-title-lint-error
|
|
||||||
message: |
|
|
||||||
Hey there and thank you for opening this pull request! 👋🏼
|
|
||||||
|
|
||||||
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
|
|
||||||
|
|
||||||
Details:
|
|
||||||
|
|
||||||
```
|
|
||||||
${{ steps.lint_pr_title.outputs.error_message }}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Delete a previous comment when the issue has been resolved
|
|
||||||
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
|
||||||
uses: marocchino/sticky-pull-request-comment@v2
|
|
||||||
with:
|
|
||||||
header: pr-title-lint-error
|
|
||||||
delete: true
|
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -29,15 +29,3 @@ yarn-error.log*
|
|||||||
*.sw*
|
*.sw*
|
||||||
bin/
|
bin/
|
||||||
build/
|
build/
|
||||||
|
|
||||||
# Vue distributable files
|
|
||||||
/frontend/dist/*
|
|
||||||
!/frontend/dist/.gitkeep
|
|
||||||
|
|
||||||
# Playwright files
|
|
||||||
/frontend/test-results/
|
|
||||||
/frontend/playwright-report/
|
|
||||||
/frontend/playwright/.cache/
|
|
||||||
|
|
||||||
default.nix
|
|
||||||
Dockerfile.dev
|
|
||||||
|
@ -6,6 +6,8 @@ linters-settings:
|
|||||||
funlen:
|
funlen:
|
||||||
lines: 100
|
lines: 100
|
||||||
statements: 50
|
statements: 50
|
||||||
|
gci:
|
||||||
|
local-prefixes: github.com/filebrowser/filebrowser
|
||||||
goconst:
|
goconst:
|
||||||
min-len: 2
|
min-len: 2
|
||||||
min-occurrences: 2
|
min-occurrences: 2
|
||||||
@ -27,31 +29,23 @@ linters-settings:
|
|||||||
goimports:
|
goimports:
|
||||||
local-prefixes: github.com/filebrowser/filebrowser
|
local-prefixes: github.com/filebrowser/filebrowser
|
||||||
gomnd:
|
gomnd:
|
||||||
|
settings:
|
||||||
|
mnd:
|
||||||
# don't include the "operation" and "assign"
|
# don't include the "operation" and "assign"
|
||||||
checks:
|
checks: argument,case,condition,return
|
||||||
- argument
|
|
||||||
- case
|
|
||||||
- condition
|
|
||||||
- return
|
|
||||||
ignored-numbers:
|
|
||||||
- '0'
|
|
||||||
- '1'
|
|
||||||
- '2'
|
|
||||||
- '3'
|
|
||||||
ignored-functions:
|
|
||||||
- strings.SplitN
|
|
||||||
govet:
|
govet:
|
||||||
enable:
|
check-shadowing: true
|
||||||
- nilness
|
|
||||||
- shadow
|
|
||||||
lll:
|
lll:
|
||||||
line-length: 140
|
line-length: 140
|
||||||
|
maligned:
|
||||||
|
suggest-new: true
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
nolintlint:
|
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
|
allow-unused: false # report any unused nolint directives
|
||||||
require-explanation: false # require an explanation for nolint directives
|
require-explanation: false # don't require an explanation for nolint directives
|
||||||
require-specific: true # require nolint directives to be specific about which linter is being skipped
|
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.
|
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||||
@ -59,19 +53,18 @@ linters:
|
|||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
|
- deadcode
|
||||||
|
- depguard
|
||||||
- dogsled
|
- dogsled
|
||||||
- dupl
|
- dupl
|
||||||
- errcheck
|
- errcheck
|
||||||
- errorlint
|
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- funlen
|
- funlen
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gochecknoinits
|
- gochecknoinits
|
||||||
- goconst
|
- goconst
|
||||||
- gocritic
|
- gocritic
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- godox
|
|
||||||
- goimports
|
- goimports
|
||||||
- gomnd
|
- gomnd
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
@ -83,21 +76,19 @@ linters:
|
|||||||
- misspell
|
- misspell
|
||||||
- nakedret
|
- nakedret
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- prealloc
|
|
||||||
- revive
|
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
- stylecheck
|
- stylecheck
|
||||||
- testifylint
|
|
||||||
- typecheck
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- unused
|
- unused
|
||||||
|
- varcheck
|
||||||
- whitespace
|
- whitespace
|
||||||
|
- prealloc
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-dirs:
|
|
||||||
- frontend/
|
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- path: cmd/.*.go
|
- path: cmd/.*.go
|
||||||
linters:
|
linters:
|
||||||
@ -118,4 +109,12 @@ issues:
|
|||||||
- gomnd
|
- gomnd
|
||||||
|
|
||||||
run:
|
run:
|
||||||
timeout: 5m
|
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,12 +1,10 @@
|
|||||||
version: 2
|
|
||||||
|
|
||||||
project_name: filebrowser
|
project_name: filebrowser
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- GO111MODULE=on
|
- GO111MODULE=on
|
||||||
|
|
||||||
builds:
|
build:
|
||||||
- env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X github.com/filebrowser/filebrowser/v2/version.Version={{ .Version }} -X github.com/filebrowser/filebrowser/v2/version.CommitSHA={{ .ShortCommit }}
|
- -s -w -X github.com/filebrowser/filebrowser/v2/version.Version={{ .Version }} -X github.com/filebrowser/filebrowser/v2/version.CommitSHA={{ .ShortCommit }}
|
||||||
@ -22,7 +20,6 @@ builds:
|
|||||||
- 386
|
- 386
|
||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
- riscv64
|
|
||||||
goarm:
|
goarm:
|
||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
@ -36,10 +33,10 @@ builds:
|
|||||||
archives:
|
archives:
|
||||||
-
|
-
|
||||||
name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
|
name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
|
||||||
formats: [ 'tar.gz' ]
|
format: tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
formats: [ 'zip' ]
|
format: zip
|
||||||
|
|
||||||
dockers:
|
dockers:
|
||||||
-
|
-
|
||||||
@ -60,7 +57,6 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
|
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker_config.json
|
- docker_config.json
|
||||||
- healthcheck.sh
|
|
||||||
-
|
-
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
use: buildx
|
use: buildx
|
||||||
@ -79,7 +75,6 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
|
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker_config.json
|
- docker_config.json
|
||||||
- healthcheck.sh
|
|
||||||
-
|
-
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
use: buildx
|
use: buildx
|
||||||
@ -99,7 +94,6 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:v{{ .Major }}-armv6"
|
- "filebrowser/filebrowser:v{{ .Major }}-armv6"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker_config.json
|
- docker_config.json
|
||||||
- healthcheck.sh
|
|
||||||
-
|
-
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
use: buildx
|
use: buildx
|
||||||
@ -119,7 +113,6 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
|
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker_config.json
|
- docker_config.json
|
||||||
- healthcheck.sh
|
|
||||||
## s6 based docker images
|
## s6 based docker images
|
||||||
-
|
-
|
||||||
dockerfile: Dockerfile.s6
|
dockerfile: Dockerfile.s6
|
||||||
@ -139,7 +132,6 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:v{{ .Major }}-amd64-s6"
|
- "filebrowser/filebrowser:v{{ .Major }}-amd64-s6"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker/root
|
- docker/root
|
||||||
- healthcheck.sh
|
|
||||||
-
|
-
|
||||||
dockerfile: Dockerfile.s6.aarch64
|
dockerfile: Dockerfile.s6.aarch64
|
||||||
use: buildx
|
use: buildx
|
||||||
@ -158,55 +150,91 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:v{{ .Major }}-arm64-s6"
|
- "filebrowser/filebrowser:v{{ .Major }}-arm64-s6"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker/root
|
- docker/root
|
||||||
- healthcheck.sh
|
-
|
||||||
|
dockerfile: Dockerfile.s6.armhf
|
||||||
|
use: buildx
|
||||||
|
build_flag_templates:
|
||||||
|
- "--pull"
|
||||||
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
|
- "--label=org.opencontainers.image.name={{.ProjectName}}"
|
||||||
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=org.opencontainers.image.source={{.GitURL}}"
|
||||||
|
- "--platform=linux/arm/v6"
|
||||||
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: '6'
|
||||||
|
image_templates:
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-armv6-s6"
|
||||||
|
- "filebrowser/filebrowser:v{{ .Major }}-armv6-s6"
|
||||||
|
extra_files:
|
||||||
|
- docker/root
|
||||||
|
-
|
||||||
|
dockerfile: Dockerfile.s6.armhf
|
||||||
|
use: buildx
|
||||||
|
build_flag_templates:
|
||||||
|
- "--pull"
|
||||||
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
|
- "--label=org.opencontainers.image.name={{.ProjectName}}"
|
||||||
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=org.opencontainers.image.source={{.GitURL}}"
|
||||||
|
- "--platform=linux/arm/v7"
|
||||||
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: '7'
|
||||||
|
image_templates:
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-armv7-s6"
|
||||||
|
- "filebrowser/filebrowser:v{{ .Major }}-armv7-s6"
|
||||||
|
extra_files:
|
||||||
|
- docker/root
|
||||||
docker_manifests:
|
docker_manifests:
|
||||||
- name_template: "filebrowser/filebrowser:latest"
|
- name_template: "filebrowser/filebrowser:latest"
|
||||||
image_templates:
|
image_templates:
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
|
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
|
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
|
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
|
||||||
- name_template: "filebrowser/filebrowser:{{ .Tag }}"
|
- name_template: "filebrowser/filebrowser:{{ .Tag }}"
|
||||||
image_templates:
|
image_templates:
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
|
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
|
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
|
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
|
||||||
- name_template: "filebrowser/filebrowser:v{{ .Major }}"
|
- name_template: "filebrowser/filebrowser:v{{ .Major }}"
|
||||||
image_templates:
|
image_templates:
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
|
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
|
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
|
||||||
|
- "filebrowser/filebrowser:v{{ .Major }}-armv6"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
|
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
|
||||||
## s6 image manifests
|
## s6 image manifests
|
||||||
- name_template: "filebrowser/filebrowser:s6"
|
- name_template: "filebrowser/filebrowser:s6"
|
||||||
image_templates:
|
image_templates:
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-amd64-s6"
|
- "filebrowser/filebrowser:{{ .Tag }}-amd64-s6"
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-arm64-s6"
|
- "filebrowser/filebrowser:{{ .Tag }}-arm64-s6"
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-armv6-s6"
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-armv7-s6"
|
||||||
- name_template: "filebrowser/filebrowser:{{ .Tag }}-s6"
|
- name_template: "filebrowser/filebrowser:{{ .Tag }}-s6"
|
||||||
image_templates:
|
image_templates:
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-amd64-s6"
|
- "filebrowser/filebrowser:{{ .Tag }}-amd64-s6"
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-arm64-s6"
|
- "filebrowser/filebrowser:{{ .Tag }}-arm64-s6"
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-armv6-s6"
|
||||||
|
- "filebrowser/filebrowser:{{ .Tag }}-armv7-s6"
|
||||||
- name_template: "filebrowser/filebrowser:v{{ .Major }}-s6"
|
- name_template: "filebrowser/filebrowser:v{{ .Major }}-s6"
|
||||||
image_templates:
|
image_templates:
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-amd64-s6"
|
- "filebrowser/filebrowser:v{{ .Major }}-amd64-s6"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-arm64-s6"
|
- "filebrowser/filebrowser:v{{ .Major }}-arm64-s6"
|
||||||
|
- "filebrowser/filebrowser:v{{ .Major }}-armv6-s6"
|
||||||
homebrew_casks:
|
- "filebrowser/filebrowser:v{{ .Major }}-armv7-s6"
|
||||||
|
brews:
|
||||||
- name: filebrowser
|
- name: filebrowser
|
||||||
repository:
|
tap:
|
||||||
owner: filebrowser
|
owner: filebrowser
|
||||||
name: homebrew-tap
|
name: homebrew-tap
|
||||||
|
folder: Formula
|
||||||
|
homepage: https://filebrowser.org
|
||||||
commit_author:
|
commit_author:
|
||||||
name: FileBrowser Robot
|
name: FileBrowser Robot
|
||||||
email: robot@filebrowser.org
|
email: robot@filebrowser.org
|
||||||
homepage: https://github.com/filebrowser/filebrowser
|
|
||||||
description: File Browser is a create-your-own-cloud-kind of software where you can install it on a server, direct it to a path and then access your files through a nice web interface
|
description: File Browser is a create-your-own-cloud-kind of software where you can install it on a server, direct it to a path and then access your files through a nice web interface
|
||||||
license: "MIT"
|
license: "MIT"
|
||||||
# make the old formula conflict with the cask:
|
|
||||||
conflicts:
|
|
||||||
- formula: filebrowser
|
|
||||||
# if your app/binary isn't signed and notarized, you'll need this:
|
|
||||||
hooks:
|
|
||||||
post:
|
|
||||||
install: |
|
|
||||||
if system_command("/usr/bin/xattr", args: ["-h"]).exit_status == 0
|
|
||||||
system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/filebrowser"]
|
|
||||||
end
|
|
||||||
|
10
.tx/config
Normal file
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
|
493
CHANGELOG.md
493
CHANGELOG.md
@ -2,499 +2,6 @@
|
|||||||
|
|
||||||
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.
|
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.32.3](https://github.com/filebrowser/filebrowser/compare/v2.32.2...v2.32.3) (2025-06-17)
|
|
||||||
|
|
||||||
### [2.32.2](https://github.com/filebrowser/filebrowser/compare/v2.32.1...v2.32.2) (2025-06-17)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* updated for project File Browser ([#5159](https://github.com/filebrowser/filebrowser/issues/5159)) ([c34c0af](https://github.com/filebrowser/filebrowser/commit/c34c0afecf3242b16ad5d5584cd90a6ad323361c))
|
|
||||||
|
|
||||||
### [2.32.1](https://github.com/filebrowser/filebrowser/compare/v2.32.0...v2.32.1) (2025-06-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add Vietnamese translation ([#3840](https://github.com/filebrowser/filebrowser/issues/3840)) ([56b80b6](https://github.com/filebrowser/filebrowser/commit/56b80b6d9b4710538765ba7df5da1f03898f6b81))
|
|
||||||
* improve pt-br translations with new keys and refinements ([#4903](https://github.com/filebrowser/filebrowser/issues/4903)) ([a882fb6](https://github.com/filebrowser/filebrowser/commit/a882fb6c85ab6ccc845ed0bf3908d8e5e60ce346))
|
|
||||||
* update translation ko.json ([#3852](https://github.com/filebrowser/filebrowser/issues/3852)) ([d9ebd65](https://github.com/filebrowser/filebrowser/commit/d9ebd65ffcf9b2166fec708d51849796d12b16e0))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* err shadowing lint ([c606a01](https://github.com/filebrowser/filebrowser/commit/c606a01a2d20932fb32ee896234d57631f8c47e4))
|
|
||||||
* generate random admin password on quick setup ([a46acba](https://github.com/filebrowser/filebrowser/commit/a46acba5f92ee044661880d6ae349e289d984328)), closes [#3646](https://github.com/filebrowser/filebrowser/issues/3646)
|
|
||||||
* imports lint ([54b91b8](https://github.com/filebrowser/filebrowser/commit/54b91b8ff0b8ee1f02f72425ab97d27a5d942fc3))
|
|
||||||
* set videojs locale ([#3742](https://github.com/filebrowser/filebrowser/issues/3742)) ([71a8f56](https://github.com/filebrowser/filebrowser/commit/71a8f5662c207e3cd4ee714a5b5a961121f510cd))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps-dev:** bump vite from 6.0.11 to 6.1.6 in /frontend ([#3886](https://github.com/filebrowser/filebrowser/issues/3886)) ([5355629](https://github.com/filebrowser/filebrowser/commit/5355629fd1e7bd85ee3222fca22da899ba23ea95))
|
|
||||||
* **deps:** bump golang.org/x/crypto from 0.31.0 to 0.35.0 ([#3865](https://github.com/filebrowser/filebrowser/issues/3865)) ([0ba9505](https://github.com/filebrowser/filebrowser/commit/0ba9505a19cb369653fc9f8260dc02fcc6587629))
|
|
||||||
* **deps:** bump golang.org/x/net from 0.33.0 to 0.38.0 ([#3869](https://github.com/filebrowser/filebrowser/issues/3869)) ([cfea84f](https://github.com/filebrowser/filebrowser/commit/cfea84fd5e7ec9c1d2366293e5db12baaa4e3a81))
|
|
||||||
* **deps:** bump vue-i18n from 11.0.1 to 11.1.2 in /frontend ([#3786](https://github.com/filebrowser/filebrowser/issues/3786)) ([35d1c09](https://github.com/filebrowser/filebrowser/commit/35d1c092434b80b22c89a614a02122e9f5965b39))
|
|
||||||
|
|
||||||
## [2.32.0](https://github.com/filebrowser/filebrowser/compare/v2.31.2...v2.32.0) (2025-01-31)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* create user on proxy authentication if user does not exist ([#3569](https://github.com/filebrowser/filebrowser/issues/3569)) ([209acf2](https://github.com/filebrowser/filebrowser/commit/209acf2429b06e2e8d78218937c59fd7e7edd1be))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add proper healthcheck for S6 containers ([#3691](https://github.com/filebrowser/filebrowser/issues/3691)) ([045064f](https://github.com/filebrowser/filebrowser/commit/045064f8b8bf9f86058e877448085e38da8b3f2e))
|
|
||||||
* disk usage refreshing ([#3692](https://github.com/filebrowser/filebrowser/issues/3692)) ([bbdd313](https://github.com/filebrowser/filebrowser/commit/bbdd313705b8d253f0c47ad717a6e47b2f46e719))
|
|
||||||
* Fix user creation on proxy auth ([#3666](https://github.com/filebrowser/filebrowser/issues/3666)) ([5300d00](https://github.com/filebrowser/filebrowser/commit/5300d00d2e7dbb80a252aff57e100113f02506c3))
|
|
||||||
* prompts disappearing on copy / move / upload ([#3537](https://github.com/filebrowser/filebrowser/issues/3537)) ([d1c84a8](https://github.com/filebrowser/filebrowser/commit/d1c84a84123c77dede05c023b3697a432b56122c))
|
|
||||||
|
|
||||||
|
|
||||||
### Refactorings
|
|
||||||
|
|
||||||
* Fix eslint warnings ([#3698](https://github.com/filebrowser/filebrowser/issues/3698)) ([0201f9c](https://github.com/filebrowser/filebrowser/commit/0201f9c5c4dd2a4d5a3503e59cdb8045e8d3a91f)), closes [#3407](https://github.com/filebrowser/filebrowser/issues/3407)
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps:** bump cross-spawn from 7.0.3 to 7.0.6 in /tools ([#3601](https://github.com/filebrowser/filebrowser/issues/3601)) ([25372ed](https://github.com/filebrowser/filebrowser/commit/25372edb5c0e616e82b76b5f523633af57d347e0))
|
|
||||||
* **deps:** bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 ([#3574](https://github.com/filebrowser/filebrowser/issues/3574)) ([2fdea73](https://github.com/filebrowser/filebrowser/commit/2fdea73430011846276a1cda52458f1d670f5ea7))
|
|
||||||
* **deps:** bump golang.org/x/crypto from 0.26.0 to 0.31.0 ([#3634](https://github.com/filebrowser/filebrowser/issues/3634)) ([e92dbb4](https://github.com/filebrowser/filebrowser/commit/e92dbb4bb8b7894264fbf0a48a641712c3b68766))
|
|
||||||
* **deps:** bump golang.org/x/net from 0.23.0 to 0.33.0 ([#3712](https://github.com/filebrowser/filebrowser/issues/3712)) ([1194cfe](https://github.com/filebrowser/filebrowser/commit/1194cfe0097a70399c1f06cf0f514b9d70fa463c))
|
|
||||||
* **deps:** bump vue-i18n from 9.10.2 to 9.14.2 in /frontend ([#3618](https://github.com/filebrowser/filebrowser/issues/3618)) ([0659594](https://github.com/filebrowser/filebrowser/commit/065959451d3ba12019c6151274aa4e6904cdca99))
|
|
||||||
* fix go releaser ([ba797cd](https://github.com/filebrowser/filebrowser/commit/ba797cda3135eddb9b7165dc5ceb932399cb54df))
|
|
||||||
* update to node 22 and pnpm ([#3616](https://github.com/filebrowser/filebrowser/issues/3616)) ([d51a343](https://github.com/filebrowser/filebrowser/commit/d51a3438201274a1b826be1b775ca1035ade20c5))
|
|
||||||
|
|
||||||
### [2.31.2](https://github.com/filebrowser/filebrowser/compare/v2.31.1...v2.31.2) (2024-10-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* added whitespace before version ([#3510](https://github.com/filebrowser/filebrowser/issues/3510)) ([2b37e69](https://github.com/filebrowser/filebrowser/commit/2b37e696c9bde4d0c453de236a3555d982346bbb))
|
|
||||||
* change location of custom init scripts ([#3493](https://github.com/filebrowser/filebrowser/issues/3493)) ([406d4f7](https://github.com/filebrowser/filebrowser/commit/406d4f78845a1684df7c9c457b208f4dd9b2a930))
|
|
||||||
* files list alignment ([#3494](https://github.com/filebrowser/filebrowser/issues/3494)) ([64400ff](https://github.com/filebrowser/filebrowser/commit/64400ffda8b09f66b8662a3c9400235139800a4d))
|
|
||||||
* german translation spelling typos ([#3469](https://github.com/filebrowser/filebrowser/issues/3469)) ([1e7c415](https://github.com/filebrowser/filebrowser/commit/1e7c41505fb6a3b9baa1534787492a186e09bcfb))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps-dev:** bump vite from 5.2.7 to 5.4.6 in /frontend ([#3496](https://github.com/filebrowser/filebrowser/issues/3496)) ([ec7b643](https://github.com/filebrowser/filebrowser/commit/ec7b643e8e9499f7ff226ec7f8e63a9df9890352))
|
|
||||||
* **deps:** bump rollup from 4.21.3 to 4.22.4 in /frontend ([#3504](https://github.com/filebrowser/filebrowser/issues/3504)) ([03d74ee](https://github.com/filebrowser/filebrowser/commit/03d74ee7582196c09720f8d488056339f06c446c))
|
|
||||||
|
|
||||||
### [2.31.1](https://github.com/filebrowser/filebrowser/compare/v2.31.0...v2.31.1) (2024-08-30)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* command not found in shell ([#3438](https://github.com/filebrowser/filebrowser/issues/3438)) ([121d9ab](https://github.com/filebrowser/filebrowser/commit/121d9abecdc7d4e923cfc5023519995938a6ccae))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* update to alpine 3.20 ([#3447](https://github.com/filebrowser/filebrowser/issues/3447)) ([7de6bc4](https://github.com/filebrowser/filebrowser/commit/7de6bc4a912b5734dd0df02ed8391e78619e2615))
|
|
||||||
|
|
||||||
## [2.31.0](https://github.com/filebrowser/filebrowser/compare/v2.30.0...v2.31.0) (2024-08-29)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add Czech translation ([#3416](https://github.com/filebrowser/filebrowser/issues/3416)) ([8e67a12](https://github.com/filebrowser/filebrowser/commit/8e67a12f260caefcbe419c2281025b9b15f02bf3))
|
|
||||||
* Added epub preview. Resolves [#3375](https://github.com/filebrowser/filebrowser/issues/3375) ([#3376](https://github.com/filebrowser/filebrowser/issues/3376)) ([99a6382](https://github.com/filebrowser/filebrowser/commit/99a6382b320874e94f9bd74708f46dd9a7485d3c))
|
|
||||||
* implement markdown file preview in Ace editor ([#3431](https://github.com/filebrowser/filebrowser/issues/3431)) ([b0f4604](https://github.com/filebrowser/filebrowser/commit/b0f4604f44e6a35e07df3000f106f523cd942cfc))
|
|
||||||
* support mime type for epub extension ([#3425](https://github.com/filebrowser/filebrowser/issues/3425)) ([f6f7e5f](https://github.com/filebrowser/filebrowser/commit/f6f7e5fea3ff7073ee652008a51cb5445a6f3d5d))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* clipboard copy in safari ([#3261](https://github.com/filebrowser/filebrowser/issues/3261)) ([1fccc5d](https://github.com/filebrowser/filebrowser/commit/1fccc5d649add2a56c55e75cf9dec4851e6d7cbf))
|
|
||||||
* CSS selectors for listing icons ([#3277](https://github.com/filebrowser/filebrowser/issues/3277)) ([2a90cdf](https://github.com/filebrowser/filebrowser/commit/2a90cdfdaff8655c7cb1167c01994a0978dece8f))
|
|
||||||
* fix catalan i18n file ([090272e](https://github.com/filebrowser/filebrowser/commit/090272e3b7c56a940c4aa2d28f860c574aa17d53))
|
|
||||||
* fixing an issue where the upload indicator would "jump" around in the UI ([#3354](https://github.com/filebrowser/filebrowser/issues/3354)) ([7be5644](https://github.com/filebrowser/filebrowser/commit/7be564495226bc6846289a56edb8893511036c6e))
|
|
||||||
* **frontend:** N files selected hint use i18n ([#3390](https://github.com/filebrowser/filebrowser/issues/3390)) ([10bf3cf](https://github.com/filebrowser/filebrowser/commit/10bf3cffbf8eb7d95fe4e1cc6acf1012329744b9))
|
|
||||||
* pdf preview header ([#3274](https://github.com/filebrowser/filebrowser/issues/3274)) ([a838868](https://github.com/filebrowser/filebrowser/commit/a8388689f3019083f263845900f683ddc13884dc))
|
|
||||||
* pull down to refresh within editor ([#3378](https://github.com/filebrowser/filebrowser/issues/3378)) ([21783ed](https://github.com/filebrowser/filebrowser/commit/21783ed91a13ad52afdb411e43faf14fb6ef6e42))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* bump go libs ([b596567](https://github.com/filebrowser/filebrowser/commit/b596567c6163d57eaefbf3e30d84cfca65c24cdf))
|
|
||||||
* bump go version to 1.23.0 ([364fdaa](https://github.com/filebrowser/filebrowser/commit/364fdaaf0c1eace82ff8637d337cc1b32e5e9972))
|
|
||||||
* bump golangci-lint to 1.60.3 ([a6347c8](https://github.com/filebrowser/filebrowser/commit/a6347c88586e584b4565277b0010fa9ff2576b1f))
|
|
||||||
* **deps-dev:** bump braces from 3.0.2 to 3.0.3 in /frontend ([#3316](https://github.com/filebrowser/filebrowser/issues/3316)) ([e8589be](https://github.com/filebrowser/filebrowser/commit/e8589be6409a2b29edd44ee2edd3fbf6b2d72724))
|
|
||||||
* **deps-dev:** bump ws from 8.16.0 to 8.17.1 in /frontend ([#3321](https://github.com/filebrowser/filebrowser/issues/3321)) ([c3465f9](https://github.com/filebrowser/filebrowser/commit/c3465f99136506d51b813be4f31b289e708da0ce))
|
|
||||||
* **deps:** bump golang.org/x/image from 0.15.0 to 0.18.0 ([#3335](https://github.com/filebrowser/filebrowser/issues/3335)) ([30a8ddf](https://github.com/filebrowser/filebrowser/commit/30a8ddf113862e3de2c09547662b7f2af8a30dfe))
|
|
||||||
* fix goreleaser file ([056cfa8](https://github.com/filebrowser/filebrowser/commit/056cfa8facdca4c397a6b245028d4c9d3f0ca518))
|
|
||||||
|
|
||||||
## [2.30.0](https://github.com/filebrowser/filebrowser/compare/v2.29.0...v2.30.0) (2024-05-19)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* allow multi-select with SHIFT key in singleClick mode ([#3185](https://github.com/filebrowser/filebrowser/issues/3185)) ([2e47a03](https://github.com/filebrowser/filebrowser/commit/2e47a038d63de8f848b070578c1d71f765438a24))
|
|
||||||
* Enhance MIME Type Detection for Additional File Extensions ([#3183](https://github.com/filebrowser/filebrowser/issues/3183)) ([be62f56](https://github.com/filebrowser/filebrowser/commit/be62f56782551e17d6d5dc23bc29cc56ef961a66))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add overlay for sidebar on mobile ([#3197](https://github.com/filebrowser/filebrowser/issues/3197)) ([3b48f75](https://github.com/filebrowser/filebrowser/commit/3b48f75301287fe94cbbacff184b4db03f37f7ea))
|
|
||||||
* current folder name in page title ([#3200](https://github.com/filebrowser/filebrowser/issues/3200)) ([e336a25](https://github.com/filebrowser/filebrowser/commit/e336a25ad29ed8b956169d426992860a877ee551))
|
|
||||||
* Fixing the inability to play MKV video files online and enhancing the auxiliary features of the VideoPlayer. ([#3181](https://github.com/filebrowser/filebrowser/issues/3181)) ([782375b](https://github.com/filebrowser/filebrowser/commit/782375b1cb4c4f954468c30ec277ce021c82b40d))
|
|
||||||
* shell window size ([#3198](https://github.com/filebrowser/filebrowser/issues/3198)) ([4c5b612](https://github.com/filebrowser/filebrowser/commit/4c5b612cb2563817f9da50413c7cf9e89b4c4d4a))
|
|
||||||
* The file type icon in the file list is sensitive to the case of the suffix name ([#3187](https://github.com/filebrowser/filebrowser/issues/3187)) ([a9c327c](https://github.com/filebrowser/filebrowser/commit/a9c327cc0687796a3c7bfafd4ddabf4342859e31))
|
|
||||||
|
|
||||||
## [2.29.0](https://github.com/filebrowser/filebrowser/compare/v2.28.0...v2.29.0) (2024-04-30)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Display Upload Progress as Percentage and File Size / Total File Size ([#3111](https://github.com/filebrowser/filebrowser/issues/3111)) ([236ca63](https://github.com/filebrowser/filebrowser/commit/236ca637f99e373adfeaaefc5db6af50bd15b6bf))
|
|
||||||
* migrate to vue 3 ([#2689](https://github.com/filebrowser/filebrowser/issues/2689)) ([5100e58](https://github.com/filebrowser/filebrowser/commit/5100e587d73831ecdb5e3bd35a78fef96ad248a4))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* abort upload behavior to properly handle server-side deletion and frontend state reset ([#3114](https://github.com/filebrowser/filebrowser/issues/3114)) ([434e49b](https://github.com/filebrowser/filebrowser/commit/434e49bf59e4ddf7ec90893fa3fd53faee8c9cbb))
|
|
||||||
* apply proper zindex to modal dialogs ([#3172](https://github.com/filebrowser/filebrowser/issues/3172)) ([821f51e](https://github.com/filebrowser/filebrowser/commit/821f51ea5ad1f5c2eb72441bc761031cacee43e1))
|
|
||||||
* correct list item selector ([#3126](https://github.com/filebrowser/filebrowser/issues/3126)) ([#3147](https://github.com/filebrowser/filebrowser/issues/3147)) ([22a05e1](https://github.com/filebrowser/filebrowser/commit/22a05e1f02a083cf7b630e16873dad0de89b7854))
|
|
||||||
* don't redirect to login when no auth ([#3165](https://github.com/filebrowser/filebrowser/issues/3165)) ([da5a6e0](https://github.com/filebrowser/filebrowser/commit/da5a6e051faa80134c2adf4e621426cbdf046c88))
|
|
||||||
* Frontend bug, administrators unable to delete users ([#3170](https://github.com/filebrowser/filebrowser/issues/3170)) ([bee71d9](https://github.com/filebrowser/filebrowser/commit/bee71d93fee137cdd807cd8f7716c7da0830fae7))
|
|
||||||
* handle quotes in healthcheck.sh ([#3130](https://github.com/filebrowser/filebrowser/issues/3130)) ([18f04a7](https://github.com/filebrowser/filebrowser/commit/18f04a7d26186927f51f46354f3b2164a68f1b41))
|
|
||||||
* the copy method in clipboard.ts ([#3177](https://github.com/filebrowser/filebrowser/issues/3177)) ([4786187](https://github.com/filebrowser/filebrowser/commit/4786187852b8eef07e40aa00cd159ccc1e7e79dc))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* bump go version to 1.22.1 ([bbd0abb](https://github.com/filebrowser/filebrowser/commit/bbd0abbdfdbb3ddf3326247b7c6d925751dfabcb))
|
|
||||||
* bump go version to 1.22.2 ([#3158](https://github.com/filebrowser/filebrowser/issues/3158)) ([a9da7fd](https://github.com/filebrowser/filebrowser/commit/a9da7fd56c849b5a13133136b35ef5ebee622962))
|
|
||||||
* **deps:** bump golang.org/x/net from 0.22.0 to 0.23.0 ([#3133](https://github.com/filebrowser/filebrowser/issues/3133)) ([6b77b8d](https://github.com/filebrowser/filebrowser/commit/6b77b8d683f7357ef71af678550e78910c10ddeb))
|
|
||||||
|
|
||||||
## [2.28.0](https://github.com/filebrowser/filebrowser/compare/v2.27.0...v2.28.0) (2024-04-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* allow to configure if home directory is automatically created from cli ([#2963](https://github.com/filebrowser/filebrowser/issues/2963)) ([a4b089a](https://github.com/filebrowser/filebrowser/commit/a4b089a6dbf9821ecede428cd7d13e69c8b85231))
|
|
||||||
* auto hiding header bar in preview to enlarge the preview window ([#3024](https://github.com/filebrowser/filebrowser/issues/3024)) ([d706506](https://github.com/filebrowser/filebrowser/commit/d70650689c34ce9f631fda6a453fd521faef22fa))
|
|
||||||
* close editor when click escape key ([#2947](https://github.com/filebrowser/filebrowser/issues/2947)) ([70c8261](https://github.com/filebrowser/filebrowser/commit/70c826133b8578b8712e6db8f762a15a076cd9a9))
|
|
||||||
* enable preview in shared folder ([#3055](https://github.com/filebrowser/filebrowser/issues/3055)) ([4c233c3](https://github.com/filebrowser/filebrowser/commit/4c233c3db39ea5a00d6e602ec0ecbddecb590877))
|
|
||||||
* focus editor when opened ([#2946](https://github.com/filebrowser/filebrowser/issues/2946)) ([b19710e](https://github.com/filebrowser/filebrowser/commit/b19710efca6daa7af56dc211d0051d500d2eea22))
|
|
||||||
* freezing the list in the background while previewing a file ([#3004](https://github.com/filebrowser/filebrowser/issues/3004)) ([e167c3e](https://github.com/filebrowser/filebrowser/commit/e167c3e1efed8b16be45d994a8d443fda1d8cf49))
|
|
||||||
* prompt to confirm discard editor changes ([#2948](https://github.com/filebrowser/filebrowser/issues/2948)) ([fb1a09c](https://github.com/filebrowser/filebrowser/commit/fb1a09c7c172b913c12b30975ca545e505df0c05))
|
|
||||||
* select multiple files with ctrl even with singleClick option ([#2953](https://github.com/filebrowser/filebrowser/issues/2953)) ([d49c3df](https://github.com/filebrowser/filebrowser/commit/d49c3dfacfc0ff07e620b3ad2700e64927b06235))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* dashboard buttons position in rtl layout ([#2949](https://github.com/filebrowser/filebrowser/issues/2949)) ([2cfee21](https://github.com/filebrowser/filebrowser/commit/2cfee2183c98d0cb67fc4e9788644ed4278e25bc))
|
|
||||||
* editor discard prompt ([#2990](https://github.com/filebrowser/filebrowser/issues/2990)) ([34a0817](https://github.com/filebrowser/filebrowser/commit/34a08170c894321d49bb843e259a0e59e2245998))
|
|
||||||
* files and directories are created with the correct permissions ([#2966](https://github.com/filebrowser/filebrowser/issues/2966)) ([5c5ab6b](https://github.com/filebrowser/filebrowser/commit/5c5ab6b8750a5168f0ae2a26bd5de41e0b6d9637))
|
|
||||||
* fix lint warnings ([#2976](https://github.com/filebrowser/filebrowser/issues/2976)) ([fe5ca74](https://github.com/filebrowser/filebrowser/commit/fe5ca74aa1e4257e5cb36f1de58daa0c3548319f))
|
|
||||||
* **healthcheck:** use address configured if not empty ([#2938](https://github.com/filebrowser/filebrowser/issues/2938)) ([81cd8fc](https://github.com/filebrowser/filebrowser/commit/81cd8fc6d307b00af278beefcdbad4158a128fea))
|
|
||||||
* keyboard shortcut to confirm prompts ([#2932](https://github.com/filebrowser/filebrowser/issues/2932)) ([ff9502f](https://github.com/filebrowser/filebrowser/commit/ff9502ff34790c46f31d175911cd51c9b62804fb))
|
|
||||||
* moment locale ([#2952](https://github.com/filebrowser/filebrowser/issues/2952)) ([883383a](https://github.com/filebrowser/filebrowser/commit/883383a5715d82883c51138dfb547805dfad2a3c))
|
|
||||||
* shell direction ([#2980](https://github.com/filebrowser/filebrowser/issues/2980)) ([6d7ba65](https://github.com/filebrowser/filebrowser/commit/6d7ba65faf576ee4ed095f3d0c41775b21e498de))
|
|
||||||
* stay in the same position after renaming or deleting ([#3039](https://github.com/filebrowser/filebrowser/issues/3039)) ([cdf8def](https://github.com/filebrowser/filebrowser/commit/cdf8def3304315bef261da7f52f8599d90b1f0f0))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps-dev:** bump vite from 4.4.12 to 4.5.2 in /frontend ([#2951](https://github.com/filebrowser/filebrowser/issues/2951)) ([bf36cc0](https://github.com/filebrowser/filebrowser/commit/bf36cc00f1369dd10a422f230ccabcbeefae1517))
|
|
||||||
* **deps:** bump google.golang.org/protobuf from 1.31.0 to 1.33.0 ([#3045](https://github.com/filebrowser/filebrowser/issues/3045)) ([05bfae2](https://github.com/filebrowser/filebrowser/commit/05bfae264a7a477d1b7db582f06f4efb24d26ec9))
|
|
||||||
* **deps:** bump google.golang.org/protobuf in /tools ([#3044](https://github.com/filebrowser/filebrowser/issues/3044)) ([7797a4e](https://github.com/filebrowser/filebrowser/commit/7797a4ef18038a877df31bd34f2ebf70d18823f8))
|
|
||||||
|
|
||||||
## [2.27.0](https://github.com/filebrowser/filebrowser/compare/v2.26.0...v2.27.0) (2024-01-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* allow setting theme via cli ([#2881](https://github.com/filebrowser/filebrowser/issues/2881)) ([748af71](https://github.com/filebrowser/filebrowser/commit/748af7172ce96f0b66c394e88839bd57c194ffc7))
|
|
||||||
* display image resolutions in file details ([#2830](https://github.com/filebrowser/filebrowser/issues/2830)) ([a09dfa8](https://github.com/filebrowser/filebrowser/commit/a09dfa8d9f190243d811a841de44c4abb4403d87))
|
|
||||||
* make user session timeout configurable by flags ([#2845](https://github.com/filebrowser/filebrowser/issues/2845)) ([391a078](https://github.com/filebrowser/filebrowser/commit/391a078cd486e618c95a0c5850326076cbc025b6))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* delete message when delete file from preview ([3264cea](https://github.com/filebrowser/filebrowser/commit/3264cea8307dca9ab5463dc81f2a10a817eb3d54))
|
|
||||||
* fix typo ([#2843](https://github.com/filebrowser/filebrowser/issues/2843)) ([4dbc802](https://github.com/filebrowser/filebrowser/commit/4dbc802972c930f5f42fc27507fac35c28c42afd))
|
|
||||||
* set correct port in docker healthcheck ([#2812](https://github.com/filebrowser/filebrowser/issues/2812)) ([d59ad59](https://github.com/filebrowser/filebrowser/commit/d59ad594b8649f57f61453b0dfbc350c57b690a2))
|
|
||||||
* typo in build error [#2903](https://github.com/filebrowser/filebrowser/issues/2903) ([#2904](https://github.com/filebrowser/filebrowser/issues/2904)) ([c4e955a](https://github.com/filebrowser/filebrowser/commit/c4e955acf4a1a8f8e8e94f697ffc838515e69a60))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps-dev:** bump vite from 4.4.9 to 4.4.12 in /frontend ([#2862](https://github.com/filebrowser/filebrowser/issues/2862)) ([fc2ee37](https://github.com/filebrowser/filebrowser/commit/fc2ee373536584d024f7def62f350bdbb712d927))
|
|
||||||
* **deps:** bump golang.org/x/crypto from 0.14.0 to 0.17.0 ([#2890](https://github.com/filebrowser/filebrowser/issues/2890)) ([821fba4](https://github.com/filebrowser/filebrowser/commit/821fba41a25ba99d47641f01b10ac51960157888))
|
|
||||||
|
|
||||||
## [2.26.0](https://github.com/filebrowser/filebrowser/compare/v2.25.0...v2.26.0) (2023-11-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add modern greek translation ([#2778](https://github.com/filebrowser/filebrowser/issues/2778)) ([c3079d3](https://github.com/filebrowser/filebrowser/commit/c3079d30e22385d7e677f172324cd9cbab6487ce))
|
|
||||||
* make user session timeout configurable ([#2753](https://github.com/filebrowser/filebrowser/issues/2753)) ([7fabadc](https://github.com/filebrowser/filebrowser/commit/7fabadc871ea91ea22fe9454e2ca4b33e5c211be))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* avoid the front-end calling api/renew loop ([#2792](https://github.com/filebrowser/filebrowser/issues/2792)) ([edd808f](https://github.com/filebrowser/filebrowser/commit/edd808f124f4ada99bcbe4bca98ddbe20e5a424c))
|
|
||||||
* disable static resource files listing ([da1fe7c](https://github.com/filebrowser/filebrowser/commit/da1fe7c9d76a9c6a25bfa19ebd6cf8023eff5d62))
|
|
||||||
* display file size as base 2 (KiB instead of KB) ([#2779](https://github.com/filebrowser/filebrowser/issues/2779)) ([cdcd9a3](https://github.com/filebrowser/filebrowser/commit/cdcd9a313aa50c2e6806a182b6838462d42dcafe))
|
|
||||||
* goreleaser yaml ([4d0a68e](https://github.com/filebrowser/filebrowser/commit/4d0a68e7875274f4c939f2bfa15739a9b0ecf70a))
|
|
||||||
* revert fetchURL changes in auth (Fixes [#2729](https://github.com/filebrowser/filebrowser/issues/2729)) ([#2739](https://github.com/filebrowser/filebrowser/issues/2739)) ([bd3c194](https://github.com/filebrowser/filebrowser/commit/bd3c1941ff8289a5dae877e08f7e25fa9b2a92c5))
|
|
||||||
* solve docker build failed issue ([#2797](https://github.com/filebrowser/filebrowser/issues/2797)) ([6a31af6](https://github.com/filebrowser/filebrowser/commit/6a31af6c0a144128af865d802c8039fa5250e946))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps-dev:** bump postcss from 8.4.27 to 8.4.31 in /frontend ([#2749](https://github.com/filebrowser/filebrowser/issues/2749)) ([21d361a](https://github.com/filebrowser/filebrowser/commit/21d361ad308d109d2a6b323597019aaa09ce1781))
|
|
||||||
* **deps:** bump @babel/traverse in /frontend ([#2775](https://github.com/filebrowser/filebrowser/issues/2775)) ([bb4bb50](https://github.com/filebrowser/filebrowser/commit/bb4bb508a9d71516e8fa80b3a6285fe002a059d2))
|
|
||||||
* **deps:** bump golang.org/x/image from 0.5.0 to 0.10.0 ([#2800](https://github.com/filebrowser/filebrowser/issues/2800)) ([a744bd2](https://github.com/filebrowser/filebrowser/commit/a744bd224f0ff1efc53ab94481fa76ef68788df1))
|
|
||||||
* **deps:** bump golang.org/x/net from 0.11.0 to 0.17.0 ([#2758](https://github.com/filebrowser/filebrowser/issues/2758)) ([d574fb6](https://github.com/filebrowser/filebrowser/commit/d574fb6d1af41ec31778b0f402674e5111a7875d))
|
|
||||||
* fix deprecated goreleaser config options ([38f7788](https://github.com/filebrowser/filebrowser/commit/38f77882559133b9ff330cfb955a9d4ea4728cf8))
|
|
||||||
|
|
||||||
## [2.25.0](https://github.com/filebrowser/filebrowser/compare/v2.24.2...v2.25.0) (2023-09-14)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add new folder button to move/create dialogs ([#2667](https://github.com/filebrowser/filebrowser/issues/2667)) ([5994224](https://github.com/filebrowser/filebrowser/commit/599422446849fa37d5ab448bbf464afb7304b99d))
|
|
||||||
* added shell resizing ([#2648](https://github.com/filebrowser/filebrowser/issues/2648)) ([584b706](https://github.com/filebrowser/filebrowser/commit/584b706b1e310297acc2580c60442ff5c11ae432))
|
|
||||||
* implement abort upload functionality ([#2673](https://github.com/filebrowser/filebrowser/issues/2673)) ([a404fb0](https://github.com/filebrowser/filebrowser/commit/a404fb043da2573bf04385863b2d34b1f918b8e1))
|
|
||||||
* implement upload speed calculation and ETA estimation ([#2677](https://github.com/filebrowser/filebrowser/issues/2677)) ([ecdd684](https://github.com/filebrowser/filebrowser/commit/ecdd684bf1d537a4591caa38348102b61dd51e5d))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* refactor path resolution logic for project root ([#2674](https://github.com/filebrowser/filebrowser/issues/2674)) ([95fec7f](https://github.com/filebrowser/filebrowser/commit/95fec7f69430c108e5cf95c428db9d671cd97a94))
|
|
||||||
* tus upload with cloudflare proxy ([36af01d](https://github.com/filebrowser/filebrowser/commit/36af01daa6e04005ce3d18985eebaeef06f7393d)), closes [#2593](https://github.com/filebrowser/filebrowser/issues/2593)
|
|
||||||
|
|
||||||
|
|
||||||
### Refactorings
|
|
||||||
|
|
||||||
* migrate frontend tooling to vite 4 ([#2645](https://github.com/filebrowser/filebrowser/issues/2645)) ([8838a09](https://github.com/filebrowser/filebrowser/commit/8838a09cf5104deac22b6143050588040c6825e6))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* bump go version to 1.21.0 ([#2672](https://github.com/filebrowser/filebrowser/issues/2672)) ([2c97573](https://github.com/filebrowser/filebrowser/commit/2c97573301a1b13179678fb7f9bd8316539ecdff))
|
|
||||||
* bump node version to 18 ([#2671](https://github.com/filebrowser/filebrowser/issues/2671)) ([70eba7e](https://github.com/filebrowser/filebrowser/commit/70eba7ecc9d19545c0899ae40eb3897a7c48562f))
|
|
||||||
|
|
||||||
|
|
||||||
### Performance improvements
|
|
||||||
|
|
||||||
* **backend:** optimize subtitles detection performance ([#2637](https://github.com/filebrowser/filebrowser/issues/2637)) ([374bbd3](https://github.com/filebrowser/filebrowser/commit/374bbd3ec199fddbe491ab2b74e520a10a73e54b))
|
|
||||||
|
|
||||||
### [2.24.2](https://github.com/filebrowser/filebrowser/compare/v2.24.1...v2.24.2) (2023-08-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* 403 error error when uploading ([#2598](https://github.com/filebrowser/filebrowser/issues/2598)) ([289c8e6](https://github.com/filebrowser/filebrowser/commit/289c8e6f32eb520cc711389f6b6a4ed94a73ecd4))
|
|
||||||
* config init for branding.disableUsedPercentage ([#2576](https://github.com/filebrowser/filebrowser/issues/2576)) ([#2596](https://github.com/filebrowser/filebrowser/issues/2596)) ([ff1e0b8](https://github.com/filebrowser/filebrowser/commit/ff1e0b8185faf14b1f8e91830ca5e71e68ab672e))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* add riscv64 binary releases ([#2587](https://github.com/filebrowser/filebrowser/issues/2587)) ([0ac3968](https://github.com/filebrowser/filebrowser/commit/0ac39684f175487314e97403406f4d2c482e3d79))
|
|
||||||
|
|
||||||
### [2.24.1](https://github.com/filebrowser/filebrowser/compare/v2.24.0...v2.24.1) (2023-07-31)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add directory creation code to partial upload handler ([#2575](https://github.com/filebrowser/filebrowser/issues/2575)) ([#2580](https://github.com/filebrowser/filebrowser/issues/2580)) ([912f27a](https://github.com/filebrowser/filebrowser/commit/912f27a9e3286ee4bf2a27b366a1d35b3b55799c))
|
|
||||||
* resolved CSS rendering issue in Chrome browser ([#2582](https://github.com/filebrowser/filebrowser/issues/2582)) ([2a4a46c](https://github.com/filebrowser/filebrowser/commit/2a4a46c61a5d5376bea65b28d0eb6a7ec2fdf4e5))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **backend:** upgrade golangci-lint to v1.53.3 ([efd41cc](https://github.com/filebrowser/filebrowser/commit/efd41cc4c147b8d2d5e61fb2642df8d934f49362))
|
|
||||||
|
|
||||||
## [2.24.0](https://github.com/filebrowser/filebrowser/compare/v2.23.0...v2.24.0) (2023-07-29)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add a healthcheck script that works with a dynamic port ([#2510](https://github.com/filebrowser/filebrowser/issues/2510)) ([ff4375c](https://github.com/filebrowser/filebrowser/commit/ff4375cf6ce849459889f892dd91304703c52dcd))
|
|
||||||
* add a new setting that disables the display of the disk usage ([#2136](https://github.com/filebrowser/filebrowser/issues/2136)) ([428c1c6](https://github.com/filebrowser/filebrowser/commit/428c1c606d1b858ed0eb58b7c31f570bc6a9b792))
|
|
||||||
* add Hungarian translation ([#2232](https://github.com/filebrowser/filebrowser/issues/2232)) ([11e9202](https://github.com/filebrowser/filebrowser/commit/11e92021607e12efff9fb2d5c8728483eee31199))
|
|
||||||
* add option to copy download links from shares ([#2442](https://github.com/filebrowser/filebrowser/issues/2442)) ([a4ef02a](https://github.com/filebrowser/filebrowser/commit/a4ef02a47b53742a0ac1f639563b0c67116619c8))
|
|
||||||
* integrate tus.io for resumable and chunked uploads ([#2145](https://github.com/filebrowser/filebrowser/issues/2145)) ([7b35815](https://github.com/filebrowser/filebrowser/commit/7b35815754690540f76e3ffe114eedb47cfd5c7e))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* added an early return on non-existent items ([#2571](https://github.com/filebrowser/filebrowser/issues/2571)) ([2744f7d](https://github.com/filebrowser/filebrowser/commit/2744f7d5b9106c7c2eec69010e550e0939c23d80))
|
|
||||||
* build on FreeBSD and non-Linux platforms ([#2332](https://github.com/filebrowser/filebrowser/issues/2332)) ([60d1e2d](https://github.com/filebrowser/filebrowser/commit/60d1e2d2913cce591fbee97337bd58310480269f))
|
|
||||||
* error while using fallback of dir move ([#2349](https://github.com/filebrowser/filebrowser/issues/2349)) ([853ec90](https://github.com/filebrowser/filebrowser/commit/853ec906efbdee9013c5d34ed1d9b8fee88a6b29))
|
|
||||||
* filter ANSI color for shell ([#2529](https://github.com/filebrowser/filebrowser/issues/2529)) ([9bcfa90](https://github.com/filebrowser/filebrowser/commit/9bcfa900f904fe683c8d9085947f57932bfe22a0))
|
|
||||||
* goreleaser docker build ([051104b](https://github.com/filebrowser/filebrowser/commit/051104bfa061720d4402c612e61bb0fc80a946bf))
|
|
||||||
* solve broken Docker build with alpine image ([#2486](https://github.com/filebrowser/filebrowser/issues/2486)) ([b8ee340](https://github.com/filebrowser/filebrowser/commit/b8ee3404ee480ef1fd439543ab6d46f318ff3647))
|
|
||||||
* video preview click next or prev button subtitles not update ([#2423](https://github.com/filebrowser/filebrowser/issues/2423)) ([6744cd4](https://github.com/filebrowser/filebrowser/commit/6744cd47cef87e3a76a2190bdf123b6c2197fe6f))
|
|
||||||
* xss vulnerability in /api/raw ([#2570](https://github.com/filebrowser/filebrowser/issues/2570)) ([#2572](https://github.com/filebrowser/filebrowser/issues/2572)) ([b508ac3](https://github.com/filebrowser/filebrowser/commit/b508ac3d4f7f0f75d6b49c99bdc661a6d2173f30))
|
|
||||||
|
|
||||||
|
|
||||||
### Refactorings
|
|
||||||
|
|
||||||
* replace username old focus logic with the autofocus attribute ([#2223](https://github.com/filebrowser/filebrowser/issues/2223)) ([2b2c108](https://github.com/filebrowser/filebrowser/commit/2b2c1085fb50ad68612ad438e527fd316d8aafee))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **backend:** bump go version to 1.20.1 ([fa95299](https://github.com/filebrowser/filebrowser/commit/fa95299df4aa7e4c54d872e786a91ded5bdb01c1))
|
|
||||||
* **backend:** bump go version to 1.20.6 ([9bf6b85](https://github.com/filebrowser/filebrowser/commit/9bf6b856e5411e635ba9102ff53dfe927183848e))
|
|
||||||
* **deps-dev:** bump word-wrap from 1.2.3 to 1.2.4 in /frontend ([#2556](https://github.com/filebrowser/filebrowser/issues/2556)) ([bb34862](https://github.com/filebrowser/filebrowser/commit/bb3486286c0da112ad97456ad258ddcdfe17c154))
|
|
||||||
* **deps:** bump minimatch from 3.0.4 to 3.1.2 in /tools ([#2561](https://github.com/filebrowser/filebrowser/issues/2561)) ([a664ba1](https://github.com/filebrowser/filebrowser/commit/a664ba1f9df45c7f6d03492c85466c5aa07c740e))
|
|
||||||
* **deps:** bump semver from 5.7.1 to 5.7.2 in /tools ([#2546](https://github.com/filebrowser/filebrowser/issues/2546)) ([c2f1423](https://github.com/filebrowser/filebrowser/commit/c2f1423c02e4736f4c243c3164dc671879e065f3))
|
|
||||||
* remove armv6-s6 docker target ([66dfbb3](https://github.com/filebrowser/filebrowser/commit/66dfbb303cf792b7b01650d0125d948ab8d81ddd))
|
|
||||||
* remove armv7-s6 docker target ([4d77ce0](https://github.com/filebrowser/filebrowser/commit/4d77ce0955644551f891af3e4098c37e9cc37e40))
|
|
||||||
|
|
||||||
## [2.23.0](https://github.com/filebrowser/filebrowser/compare/v2.22.4...v2.23.0) (2022-11-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add rtl support ([#2178](https://github.com/filebrowser/filebrowser/issues/2178)) ([2c14146](https://github.com/filebrowser/filebrowser/commit/2c14146a314bb271be66a36c63b64852a2848e26))
|
|
||||||
* hebrew translation ([#2168](https://github.com/filebrowser/filebrowser/issues/2168)) ([a49105d](https://github.com/filebrowser/filebrowser/commit/a49105db1d5f0d8f3d6641940ea86da959ffe006))
|
|
||||||
* hook authentication method ([dda9a38](https://github.com/filebrowser/filebrowser/commit/dda9a389f387e94643a9a2ae56027260b210152a))
|
|
||||||
* update Polish translation ([#2089](https://github.com/filebrowser/filebrowser/issues/2089)) ([57c99e0](https://github.com/filebrowser/filebrowser/commit/57c99e0e261b4ed4c2cf468ce3ab09f1a440b359))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* missing video controls on mobile ([#2180](https://github.com/filebrowser/filebrowser/issues/2180)) ([a5757b9](https://github.com/filebrowser/filebrowser/commit/a5757b94e8ed492d454b9e427b7f45824cc56c5c))
|
|
||||||
* modify the delete confirmation interface logic. ([#2138](https://github.com/filebrowser/filebrowser/issues/2138)) ([0401adf](https://github.com/filebrowser/filebrowser/commit/0401adf7f4dd76760fe26b5baee02ebc726b51a9))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps:** bump ansi-html and webpack-dev-server in /frontend ([#2184](https://github.com/filebrowser/filebrowser/issues/2184)) ([3a0dace](https://github.com/filebrowser/filebrowser/commit/3a0dace9a93f9d57855801de548891010cf0830e))
|
|
||||||
* **deps:** bump terser from 4.8.0 to 4.8.1 in /frontend ([#2054](https://github.com/filebrowser/filebrowser/issues/2054)) ([aaed985](https://github.com/filebrowser/filebrowser/commit/aaed985699b3c63092ecb02c8bc07634123360ab))
|
|
||||||
|
|
||||||
### [2.22.4](https://github.com/filebrowser/filebrowser/compare/v2.22.3...v2.22.4) (2022-07-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* disable cookie auth for non GET requests ([80030de](https://github.com/filebrowser/filebrowser/commit/80030dee32d161043766d57ba4e0ad0b0d99290b))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps:** bump moment from 2.29.2 to 2.29.4 in /frontend ([#2036](https://github.com/filebrowser/filebrowser/issues/2036)) ([cb43770](https://github.com/filebrowser/filebrowser/commit/cb437700255e41ff559b9f5a99ab4290b2f8df87))
|
|
||||||
* **deps:** bump shell-quote from 1.7.2 to 1.7.3 in /frontend ([#2025](https://github.com/filebrowser/filebrowser/issues/2025)) ([eaba7e5](https://github.com/filebrowser/filebrowser/commit/eaba7e5255f960141e0fc1557f87073df9f6d66a))
|
|
||||||
|
|
||||||
### [2.22.3](https://github.com/filebrowser/filebrowser/compare/v2.22.2...v2.22.3) (2022-07-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* use correct field name in user put api ([#2026](https://github.com/filebrowser/filebrowser/issues/2026)) ([d94acdd](https://github.com/filebrowser/filebrowser/commit/d94acdd89a0069fe87107024fd332a0d59a112fc))
|
|
||||||
|
|
||||||
### [2.22.2](https://github.com/filebrowser/filebrowser/compare/v2.22.1...v2.22.2) (2022-07-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* display disk capacity in a correct format ([#2013](https://github.com/filebrowser/filebrowser/issues/2013)) ([dec3d62](https://github.com/filebrowser/filebrowser/commit/dec3d629d42de567aa708154ebc4e03b5223608c))
|
|
||||||
* don't calculate usage for files ([#1973](https://github.com/filebrowser/filebrowser/issues/1973)) ([577c0ef](https://github.com/filebrowser/filebrowser/commit/577c0efa9cff13628d5e3bac710ef568a00949e0)), closes [#1972](https://github.com/filebrowser/filebrowser/issues/1972) [#1967](https://github.com/filebrowser/filebrowser/issues/1967)
|
|
||||||
* preview url building fix ([#1976](https://github.com/filebrowser/filebrowser/issues/1976)) ([dcf0bc6](https://github.com/filebrowser/filebrowser/commit/dcf0bc65bfcfc7df3804d7392598a92019468cf7))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **backend:** upgrade golangci-lint to 1.46.2 ([#1991](https://github.com/filebrowser/filebrowser/issues/1991)) ([8118afd](https://github.com/filebrowser/filebrowser/commit/8118afd0ac0d25f4503c98879369764c35e7408e))
|
|
||||||
|
|
||||||
### [2.22.1](https://github.com/filebrowser/filebrowser/compare/v2.22.0...v2.22.1) (2022-06-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* use correct basepath prefix for preview urls ([#1971](https://github.com/filebrowser/filebrowser/issues/1971)) ([1e7d3b2](https://github.com/filebrowser/filebrowser/commit/1e7d3b25c283c556d98c65f1c2f46db4e4178995))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **backend:** bump go version to 1.8.3 ([b16982d](https://github.com/filebrowser/filebrowser/commit/b16982df0f7da9eedb678455298b42ac55c86666))
|
|
||||||
|
|
||||||
## [2.22.0](https://github.com/filebrowser/filebrowser/compare/v2.21.1...v2.22.0) (2022-06-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add branding to the window title ([#1850](https://github.com/filebrowser/filebrowser/issues/1850)) ([f8dfbf7](https://github.com/filebrowser/filebrowser/commit/f8dfbf7eeecf3ee99ce906276777676f44e81e34))
|
|
||||||
* add disk usage information to the sidebar ([d1d8e3e](https://github.com/filebrowser/filebrowser/commit/d1d8e3e3405381b01317fe07ae729d70219415a7))
|
|
||||||
* automatically focus username field on login page ([596c732](https://github.com/filebrowser/filebrowser/commit/596c73288f5b53bd7e79ab8046136dc75ff078b9))
|
|
||||||
* invalid symlink icon ([b14b911](https://github.com/filebrowser/filebrowser/commit/b14b9114f837cacf9f7788e88c503142a81585be))
|
|
||||||
* page title localization ([8a43413](https://github.com/filebrowser/filebrowser/commit/8a43413f888440dc11b11c509abff45f706033d8))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* allow CSP inline styling ([5da9d74](https://github.com/filebrowser/filebrowser/commit/5da9d74da62c69c431361bcaf0c07dc1da237ea8))
|
|
||||||
* disable autocapitalize of login input (closes [#1910](https://github.com/filebrowser/filebrowser/issues/1910)) ([aed3af5](https://github.com/filebrowser/filebrowser/commit/aed3af58384697dc3de30f1450b837b0b74e4fa6))
|
|
||||||
* drag-and-drop folder upload ([e677c78](https://github.com/filebrowser/filebrowser/commit/e677c78471f09f8d2c21d63d7388e908924aa6d9))
|
|
||||||
* expired token error ([c3bd118](https://github.com/filebrowser/filebrowser/commit/c3bd1188aa396cbf00c593d259a9da0eddeeea3b))
|
|
||||||
* folder info on upload list ([d1d7b23](https://github.com/filebrowser/filebrowser/commit/d1d7b23da6cc0c9a2f2f3e17021ec4f13ea557dd))
|
|
||||||
* network error object message ([fc209f6](https://github.com/filebrowser/filebrowser/commit/fc209f64deff7a2793980d11ee738f7140c444cf))
|
|
||||||
* set correct scope when user home creation is enabled ([02730bb](https://github.com/filebrowser/filebrowser/commit/02730bb9bfa3bfbfa251bb4736fc4c08d33609ab))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **backend:** bump dependency versions ([7c9a75e](https://github.com/filebrowser/filebrowser/commit/7c9a75e72588f92d58fb58d32cdac352bce73b20))
|
|
||||||
* **deps:** bump async from 2.6.3 to 2.6.4 in /frontend ([#1933](https://github.com/filebrowser/filebrowser/issues/1933)) ([e5fa96b](https://github.com/filebrowser/filebrowser/commit/e5fa96b666eac2e46a02bde832488baca5f2cd6d))
|
|
||||||
* **deps:** bump eventsource from 1.1.0 to 1.1.1 in /frontend ([dd50369](https://github.com/filebrowser/filebrowser/commit/dd503695a1a8119a631643414d3a9070890f3f3c))
|
|
||||||
* **deps:** bump minimist from 1.2.5 to 1.2.6 in /frontend ([#1889](https://github.com/filebrowser/filebrowser/issues/1889)) ([a74c72d](https://github.com/filebrowser/filebrowser/commit/a74c72db451207e1275988f3d208fa6d6f0468a9))
|
|
||||||
* **deps:** bump minimist from 1.2.5 to 1.2.6 in /tools ([#1891](https://github.com/filebrowser/filebrowser/issues/1891)) ([f5b1e10](https://github.com/filebrowser/filebrowser/commit/f5b1e106183fb2192063a72fd195fc8c181ba8f9))
|
|
||||||
* **deps:** bump moment from 2.29.1 to 2.29.2 in /frontend ([#1900](https://github.com/filebrowser/filebrowser/issues/1900)) ([040584c](https://github.com/filebrowser/filebrowser/commit/040584c86563d869c7a05887ef1f781bce653033))
|
|
||||||
* **deps:** bump url-parse from 1.5.7 to 1.5.10 in /frontend ([#1841](https://github.com/filebrowser/filebrowser/issues/1841)) ([b2ad3f7](https://github.com/filebrowser/filebrowser/commit/b2ad3f73686a2abaa4fc62963fba6f83c9da9b5e))
|
|
||||||
* **frontend:** bump node version from 14 to 16 ([ac3ead8](https://github.com/filebrowser/filebrowser/commit/ac3ead8dcef9c64c6be8b5cbbceee143b2cc77a8))
|
|
||||||
* upgrade go version to 1.18.1 ([6bd34c7](https://github.com/filebrowser/filebrowser/commit/6bd34c76324780c1edd8625d5b22f5a84990852b))
|
|
||||||
|
|
||||||
### [2.21.1](https://github.com/filebrowser/filebrowser/compare/v2.21.0...v2.21.1) (2022-02-22)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* display user scope for admin users ([#1834](https://github.com/filebrowser/filebrowser/issues/1834)) ([6366cf0](https://github.com/filebrowser/filebrowser/commit/6366cf0b181f13eac38f69f1760d6f6f0586a5d1))
|
|
||||||
|
|
||||||
## [2.21.0](https://github.com/filebrowser/filebrowser/compare/v2.20.1...v2.21.0) (2022-02-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add colorized file type icons ([2948589](https://github.com/filebrowser/filebrowser/commit/2948589fcde6d1dca7f3ea52a621d8213fa3300c))
|
|
||||||
* add gallery view mode ([8888b9f](https://github.com/filebrowser/filebrowser/commit/8888b9f44640394df9e3583db4392472d7027a4b))
|
|
||||||
* add Ukrainian translation / update Russian translation ([#1753](https://github.com/filebrowser/filebrowser/issues/1753)) ([665e458](https://github.com/filebrowser/filebrowser/commit/665e45889cd333f1e3500e4bf38d15d229c9fe2a))
|
|
||||||
* add upload file list with progress ([#1825](https://github.com/filebrowser/filebrowser/issues/1825)) ([cf85404](https://github.com/filebrowser/filebrowser/commit/cf85404dd25cd7fdd73aa32878b4dc5f85ee3e96))
|
|
||||||
* smaller column width to fit 2 columns in landscape mobiles ([7870e89](https://github.com/filebrowser/filebrowser/commit/7870e89bc04f1494f2705795476b5f1c9d621e38))
|
|
||||||
* use real image path to calculate cache key ([c198723](https://github.com/filebrowser/filebrowser/commit/c1987237d05adcce77c614e5247a181ae5cdfacd))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* correctly handle non-ascii passwords for shared resources ([c782f21](https://github.com/filebrowser/filebrowser/commit/c782f21b0fa4511a15e7015117d075eaf5ea332c))
|
|
||||||
* don't expose scope for non-admin users ([0942fc7](https://github.com/filebrowser/filebrowser/commit/0942fc7042fd949cce91855169d0bcf16eb75771))
|
|
||||||
* open all the pdf files correctly ([#1742](https://github.com/filebrowser/filebrowser/issues/1742)) ([949f0f2](https://github.com/filebrowser/filebrowser/commit/949f0f277f6004904b3edfa716a8365ec93fa0fa))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps:** bump browserslist from 4.16.3 to 4.19.1 in /frontend ([8089007](https://github.com/filebrowser/filebrowser/commit/80890075e802e2a4217edbb01d6417122d702f5e))
|
|
||||||
* **deps:** bump dns-packet from 1.3.1 to 1.3.4 in /frontend ([a73d7f1](https://github.com/filebrowser/filebrowser/commit/a73d7f14b787935c6ebe525dba64b65f8ed733e2))
|
|
||||||
* **deps:** bump follow-redirects from 1.13.3 to 1.14.8 in /frontend ([f1f7f17](https://github.com/filebrowser/filebrowser/commit/f1f7f17ade8d40fc6cfb22c79960bce299876b56))
|
|
||||||
* **deps:** bump hosted-git-info from 2.8.8 to 2.8.9 in /frontend ([e7659ea](https://github.com/filebrowser/filebrowser/commit/e7659ea36bdf780ce17005f7170a2fef02a2d5e5))
|
|
||||||
* **deps:** bump path-parse from 1.0.6 to 1.0.7 in /frontend ([c014966](https://github.com/filebrowser/filebrowser/commit/c01496624a7ebfc8a7c256bd919a400367281cbb))
|
|
||||||
* **deps:** bump postcss from 7.0.35 to 7.0.39 in /frontend ([9182d33](https://github.com/filebrowser/filebrowser/commit/9182d33e1cc375473fb18989a92d20252884f096))
|
|
||||||
* **deps:** bump ssri from 6.0.1 to 6.0.2 in /frontend ([3717186](https://github.com/filebrowser/filebrowser/commit/371718634b11f32e68165f31c51b6b1139c829ec))
|
|
||||||
* **deps:** bump tar from 6.1.0 to 6.1.11 in /frontend ([010d16f](https://github.com/filebrowser/filebrowser/commit/010d16fc1d8f0200e5662943aef17ee89c5877b7))
|
|
||||||
* **deps:** bump url-parse from 1.5.1 to 1.5.4 in /frontend ([8906408](https://github.com/filebrowser/filebrowser/commit/8906408a8f0ed86d1e11ea90fc573b36815c9c0d))
|
|
||||||
* **deps:** bump url-parse from 1.5.4 to 1.5.7 in /frontend ([228ebea](https://github.com/filebrowser/filebrowser/commit/228ebea66cc871b33459406590a80ef906298e7d))
|
|
||||||
* **deps:** bump ws from 6.2.1 to 6.2.2 in /frontend ([73c8073](https://github.com/filebrowser/filebrowser/commit/73c80732d934bc8802a6d7c7a559cad37df405f0))
|
|
||||||
|
|
||||||
### [2.20.1](https://github.com/filebrowser/filebrowser/compare/v2.20.0...v2.20.1) (2021-12-21)
|
### [2.20.1](https://github.com/filebrowser/filebrowser/compare/v2.20.0...v2.20.1) (2021-12-21)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
RUN apk --update add ca-certificates \
|
RUN apk --update add ca-certificates \
|
||||||
mailcap \
|
mailcap \
|
||||||
curl \
|
curl
|
||||||
jq
|
|
||||||
|
|
||||||
COPY healthcheck.sh /healthcheck.sh
|
|
||||||
RUN chmod +x /healthcheck.sh # Make the script executable
|
|
||||||
|
|
||||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
||||||
CMD /healthcheck.sh || exit 1
|
CMD curl -f http://localhost/health || exit 1
|
||||||
|
|
||||||
VOLUME /srv
|
VOLUME /srv
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
FROM ghcr.io/linuxserver/baseimage-alpine:3.20
|
FROM ghcr.io/linuxserver/baseimage-alpine:3.14
|
||||||
|
|
||||||
RUN apk --update add ca-certificates \
|
RUN apk --update add ca-certificates \
|
||||||
mailcap \
|
mailcap \
|
||||||
curl \
|
curl
|
||||||
jq
|
|
||||||
|
|
||||||
COPY healthcheck.sh /healthcheck.sh
|
|
||||||
RUN chmod +x /healthcheck.sh # Make the script executable
|
|
||||||
|
|
||||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
||||||
CMD /healthcheck.sh || exit 1
|
CMD curl -f http://localhost/health || exit 1
|
||||||
|
|
||||||
# copy local files
|
# copy local files
|
||||||
COPY docker/root/ /
|
COPY docker/root/ /
|
||||||
RUN ln -s /config/settings.json /.filebrowser.json
|
|
||||||
COPY filebrowser /usr/bin/filebrowser
|
COPY filebrowser /usr/bin/filebrowser
|
||||||
|
|
||||||
# ports and volumes
|
# ports and volumes
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
FROM ghcr.io/linuxserver/baseimage-alpine:arm64v8-3.20
|
FROM ghcr.io/linuxserver/baseimage-alpine:arm64v8-3.14
|
||||||
|
|
||||||
RUN apk --update add ca-certificates \
|
RUN apk --update add ca-certificates \
|
||||||
mailcap \
|
mailcap \
|
||||||
curl \
|
curl
|
||||||
jq
|
|
||||||
|
|
||||||
COPY healthcheck.sh /healthcheck.sh
|
|
||||||
RUN chmod +x /healthcheck.sh # Make the script executable
|
|
||||||
|
|
||||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
||||||
CMD /healthcheck.sh || exit 1
|
CMD curl -f http://localhost/health || exit 1
|
||||||
|
|
||||||
# copy local files
|
# copy local files
|
||||||
COPY docker/root/ /
|
COPY docker/root/ /
|
||||||
RUN ln -s /config/settings.json /.filebrowser.json
|
|
||||||
COPY filebrowser /usr/bin/filebrowser
|
COPY filebrowser /usr/bin/filebrowser
|
||||||
|
|
||||||
# ports and volumes
|
# ports and volumes
|
||||||
|
16
Dockerfile.s6.armhf
Normal file
16
Dockerfile.s6.armhf
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM ghcr.io/linuxserver/baseimage-alpine:arm32v7-3.14
|
||||||
|
|
||||||
|
RUN apk --update add ca-certificates \
|
||||||
|
mailcap \
|
||||||
|
curl
|
||||||
|
|
||||||
|
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
||||||
|
CMD curl -f http://localhost/health || exit 1
|
||||||
|
|
||||||
|
# copy local files
|
||||||
|
COPY docker/root/ /
|
||||||
|
COPY filebrowser /usr/bin/filebrowser
|
||||||
|
|
||||||
|
# ports and volumes
|
||||||
|
VOLUME /srv /config /database
|
||||||
|
EXPOSE 80
|
7
Makefile
7
Makefile
@ -10,7 +10,7 @@ build: | build-frontend build-backend ## Build binary
|
|||||||
|
|
||||||
.PHONY: build-frontend
|
.PHONY: build-frontend
|
||||||
build-frontend: ## Build frontend
|
build-frontend: ## Build frontend
|
||||||
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run build
|
$Q cd frontend && npm ci && npm run build
|
||||||
|
|
||||||
.PHONY: build-backend
|
.PHONY: build-backend
|
||||||
build-backend: ## Build backend
|
build-backend: ## Build backend
|
||||||
@ -21,18 +21,17 @@ test: | test-frontend test-backend ## Run all tests
|
|||||||
|
|
||||||
.PHONY: test-frontend
|
.PHONY: test-frontend
|
||||||
test-frontend: ## Run frontend tests
|
test-frontend: ## Run frontend tests
|
||||||
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run typecheck
|
|
||||||
|
|
||||||
.PHONY: test-backend
|
.PHONY: test-backend
|
||||||
test-backend: ## Run backend tests
|
test-backend: ## Run backend tests
|
||||||
$Q $(go) test -v ./...
|
$Q $(go) test -v ./...
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: lint-frontend lint-backend ## Run all linters
|
lint: lint-frontend lint-backend lint-commits ## Run all linters
|
||||||
|
|
||||||
.PHONY: lint-frontend
|
.PHONY: lint-frontend
|
||||||
lint-frontend: ## Run frontend linters
|
lint-frontend: ## Run frontend linters
|
||||||
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run lint
|
$Q cd frontend && npm ci && npm run lint
|
||||||
|
|
||||||
.PHONY: lint-backend
|
.PHONY: lint-backend
|
||||||
lint-backend: | $(golangci-lint) ## Run backend linters
|
lint-backend: | $(golangci-lint) ## Run backend linters
|
||||||
|
37
README.md
37
README.md
@ -10,43 +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)
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
>
|
|
||||||
> This project is currently on **maintenance-only** mode, and is looking for new maintainers. For more information, please read the [discussion #4906](https://github.com/filebrowser/filebrowser/discussions/4906). Therefore, please note the following:
|
|
||||||
>
|
|
||||||
> - It can take a while until someone gets back to you. Please be patient.
|
|
||||||
> - [Issues][issues] are only being used to track bugs. Any unrelated issues will be converted into a [discussion][discussions].
|
|
||||||
> - No new features will be implemented until further notice. The priority is on triaging issues and merge bug fixes.
|
|
||||||
>
|
|
||||||
> If you're interested in maintaining this project, please reach out via the discussion above.
|
|
||||||
|
|
||||||
[issues]: https://github.com/filebrowser/filebrowser/issues
|
|
||||||
[discussions]: https://github.com/filebrowser/filebrowser/discussions
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
File Browser is a **create-your-own-cloud-kind** of software where you can install it on a server, direct it to a path and then access your files through a nice web interface. You have many available features!
|
Please refer to our docs at [https://filebrowser.org/features](https://filebrowser.org/features)
|
||||||
|
|
||||||
| Easy Login System | Sleek Interface | User Management |
|
|
||||||
| :----------------------: | :----------------------: | :----------------------: |
|
|
||||||
|  |  |  |
|
|
||||||
|
|
||||||
|
|
||||||
| File Editing | Custom Commands | Customization |
|
|
||||||
| :----------------------: | :----------------------: | :----------------------: |
|
|
||||||
|  |  |  |
|
|
||||||
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
For information on how to install File Browser, please check [docs/installation.md](./docs/installation.md).
|
For installation instructions please refer to our docs at [https://filebrowser.org/installation](https://filebrowser.org/installation).
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
For information on how to configure File Browser, please check [docs/configuration.md](./docs/configuration.md).
|
[Authentication Method](https://filebrowser.org/configuration/authentication-method) - You can change the way the user authenticates with the filebrowser server
|
||||||
|
|
||||||
|
[Command 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
|
||||||
|
|
||||||
For information on how to contribute to the project, including how translations are managed, please check [docs/contributing.md](./docs/contributing.md).
|
If you're interested in contributing to this project, our docs are best places to start [https://filebrowser.org/contributing](https://filebrowser.org/contributing).
|
||||||
|
@ -3,14 +3,13 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auther is the authentication interface.
|
// Auther is the authentication interface.
|
||||||
type Auther interface {
|
type Auther interface {
|
||||||
// Auth is called to authenticate a request.
|
// Auth is called to authenticate a request.
|
||||||
Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error)
|
Auth(r *http.Request, s users.Store, root string) (*users.User, error)
|
||||||
// LoginPage indicates if this auther needs a login page.
|
// LoginPage indicates if this auther needs a login page.
|
||||||
LoginPage() bool
|
LoginPage() bool
|
||||||
}
|
}
|
||||||
|
303
auth/hook.go
303
auth/hook.go
@ -1,303 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MethodHookAuth is used to identify hook auth.
|
|
||||||
const MethodHookAuth settings.AuthMethod = "hook"
|
|
||||||
|
|
||||||
type hookCred struct {
|
|
||||||
Password string `json:"password"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HookAuth is a hook implementation of an Auther.
|
|
||||||
type HookAuth struct {
|
|
||||||
Users users.Store `json:"-"`
|
|
||||||
Settings *settings.Settings `json:"-"`
|
|
||||||
Server *settings.Server `json:"-"`
|
|
||||||
Cred hookCred `json:"-"`
|
|
||||||
Fields hookFields `json:"-"`
|
|
||||||
Command string `json:"command"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth authenticates the user via a json in content body.
|
|
||||||
func (a *HookAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error) {
|
|
||||||
var cred hookCred
|
|
||||||
|
|
||||||
if r.Body == nil {
|
|
||||||
return nil, os.ErrPermission
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&cred)
|
|
||||||
if err != nil {
|
|
||||||
return nil, os.ErrPermission
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Users = usr
|
|
||||||
a.Settings = stg
|
|
||||||
a.Server = srv
|
|
||||||
a.Cred = cred
|
|
||||||
|
|
||||||
action, err := a.RunCommand()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case "auth":
|
|
||||||
u, err := a.SaveUser()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
case "block":
|
|
||||||
return nil, os.ErrPermission
|
|
||||||
case "pass":
|
|
||||||
u, err := a.Users.Get(a.Server.Root, a.Cred.Username)
|
|
||||||
if err != nil || !users.CheckPwd(a.Cred.Password, u.Password) {
|
|
||||||
return nil, os.ErrPermission
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid hook action: %s", action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginPage tells that hook auth requires a login page.
|
|
||||||
func (a *HookAuth) LoginPage() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunCommand starts the hook command and returns the action
|
|
||||||
func (a *HookAuth) RunCommand() (string, error) {
|
|
||||||
command := strings.Split(a.Command, " ")
|
|
||||||
envMapping := func(key string) string {
|
|
||||||
switch key {
|
|
||||||
case "USERNAME":
|
|
||||||
return a.Cred.Username
|
|
||||||
case "PASSWORD":
|
|
||||||
return a.Cred.Password
|
|
||||||
default:
|
|
||||||
return os.Getenv(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, arg := range command {
|
|
||||||
if i == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
command[i] = os.Expand(arg, envMapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
|
||||||
cmd.Env = append(os.Environ(), fmt.Sprintf("USERNAME=%s", a.Cred.Username))
|
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("PASSWORD=%s", a.Cred.Password))
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.GetValues(string(out))
|
|
||||||
|
|
||||||
return a.Fields.Values["hook.action"], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValues creates a map with values from the key-value format string
|
|
||||||
func (a *HookAuth) GetValues(s string) {
|
|
||||||
m := map[string]string{}
|
|
||||||
|
|
||||||
// make line breaks consistent on Windows platform
|
|
||||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
|
||||||
|
|
||||||
// iterate input lines
|
|
||||||
for _, val := range strings.Split(s, "\n") {
|
|
||||||
v := strings.SplitN(val, "=", 2)
|
|
||||||
|
|
||||||
// skips non key and value format
|
|
||||||
if len(v) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldKey := strings.TrimSpace(v[0])
|
|
||||||
fieldValue := strings.TrimSpace(v[1])
|
|
||||||
|
|
||||||
if a.Fields.IsValid(fieldKey) {
|
|
||||||
m[fieldKey] = fieldValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Fields.Values = m
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveUser updates the existing user or creates a new one when not found
|
|
||||||
func (a *HookAuth) SaveUser() (*users.User, error) {
|
|
||||||
u, err := a.Users.Get(a.Server.Root, a.Cred.Username)
|
|
||||||
if err != nil && !errors.Is(err, fbErrors.ErrNotExist) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if u == nil {
|
|
||||||
pass, err := users.HashPwd(a.Cred.Password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create user with the provided credentials
|
|
||||||
d := &users.User{
|
|
||||||
Username: a.Cred.Username,
|
|
||||||
Password: pass,
|
|
||||||
Scope: a.Settings.Defaults.Scope,
|
|
||||||
Locale: a.Settings.Defaults.Locale,
|
|
||||||
ViewMode: a.Settings.Defaults.ViewMode,
|
|
||||||
SingleClick: a.Settings.Defaults.SingleClick,
|
|
||||||
Sorting: a.Settings.Defaults.Sorting,
|
|
||||||
Perm: a.Settings.Defaults.Perm,
|
|
||||||
Commands: a.Settings.Defaults.Commands,
|
|
||||||
HideDotfiles: a.Settings.Defaults.HideDotfiles,
|
|
||||||
}
|
|
||||||
u = a.GetUser(d)
|
|
||||||
|
|
||||||
userHome, err := a.Settings.MakeUserDir(u.Username, u.Scope, a.Server.Root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("user: failed to mkdir user home dir: [%s]", userHome)
|
|
||||||
}
|
|
||||||
u.Scope = userHome
|
|
||||||
log.Printf("user: %s, home dir: [%s].", u.Username, userHome)
|
|
||||||
|
|
||||||
err = a.Users.Save(u)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if p := !users.CheckPwd(a.Cred.Password, u.Password); len(a.Fields.Values) > 1 || p {
|
|
||||||
u = a.GetUser(u)
|
|
||||||
|
|
||||||
// update the password when it doesn't match the current
|
|
||||||
if p {
|
|
||||||
pass, err := users.HashPwd(a.Cred.Password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u.Password = pass
|
|
||||||
}
|
|
||||||
|
|
||||||
// update user with provided fields
|
|
||||||
err := a.Users.Update(u)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser returns a User filled with hook values or provided defaults
|
|
||||||
func (a *HookAuth) GetUser(d *users.User) *users.User {
|
|
||||||
// adds all permissions when user is admin
|
|
||||||
isAdmin := a.Fields.GetBoolean("user.perm.admin", d.Perm.Admin)
|
|
||||||
perms := users.Permissions{
|
|
||||||
Admin: isAdmin,
|
|
||||||
Execute: isAdmin || a.Fields.GetBoolean("user.perm.execute", d.Perm.Execute),
|
|
||||||
Create: isAdmin || a.Fields.GetBoolean("user.perm.create", d.Perm.Create),
|
|
||||||
Rename: isAdmin || a.Fields.GetBoolean("user.perm.rename", d.Perm.Rename),
|
|
||||||
Modify: isAdmin || a.Fields.GetBoolean("user.perm.modify", d.Perm.Modify),
|
|
||||||
Delete: isAdmin || a.Fields.GetBoolean("user.perm.delete", d.Perm.Delete),
|
|
||||||
Share: isAdmin || a.Fields.GetBoolean("user.perm.share", d.Perm.Share),
|
|
||||||
Download: isAdmin || a.Fields.GetBoolean("user.perm.download", d.Perm.Download),
|
|
||||||
}
|
|
||||||
user := users.User{
|
|
||||||
ID: d.ID,
|
|
||||||
Username: d.Username,
|
|
||||||
Password: d.Password,
|
|
||||||
Scope: a.Fields.GetString("user.scope", d.Scope),
|
|
||||||
Locale: a.Fields.GetString("user.locale", d.Locale),
|
|
||||||
ViewMode: users.ViewMode(a.Fields.GetString("user.viewMode", string(d.ViewMode))),
|
|
||||||
SingleClick: a.Fields.GetBoolean("user.singleClick", d.SingleClick),
|
|
||||||
Sorting: files.Sorting{
|
|
||||||
Asc: a.Fields.GetBoolean("user.sorting.asc", d.Sorting.Asc),
|
|
||||||
By: a.Fields.GetString("user.sorting.by", d.Sorting.By),
|
|
||||||
},
|
|
||||||
Commands: a.Fields.GetArray("user.commands", d.Commands),
|
|
||||||
HideDotfiles: a.Fields.GetBoolean("user.hideDotfiles", d.HideDotfiles),
|
|
||||||
Perm: perms,
|
|
||||||
LockPassword: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &user
|
|
||||||
}
|
|
||||||
|
|
||||||
// hookFields is used to access fields from the hook
|
|
||||||
type hookFields struct {
|
|
||||||
Values map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// validHookFields contains names of the fields that can be used
|
|
||||||
var validHookFields = []string{
|
|
||||||
"hook.action",
|
|
||||||
"user.scope",
|
|
||||||
"user.locale",
|
|
||||||
"user.viewMode",
|
|
||||||
"user.singleClick",
|
|
||||||
"user.sorting.by",
|
|
||||||
"user.sorting.asc",
|
|
||||||
"user.commands",
|
|
||||||
"user.hideDotfiles",
|
|
||||||
"user.perm.admin",
|
|
||||||
"user.perm.execute",
|
|
||||||
"user.perm.create",
|
|
||||||
"user.perm.rename",
|
|
||||||
"user.perm.modify",
|
|
||||||
"user.perm.delete",
|
|
||||||
"user.perm.share",
|
|
||||||
"user.perm.download",
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid checks if the provided field is on the valid fields list
|
|
||||||
func (hf *hookFields) IsValid(field string) bool {
|
|
||||||
for _, val := range validHookFields {
|
|
||||||
if field == val {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetString returns the string value or provided default
|
|
||||||
func (hf *hookFields) GetString(k, dv string) string {
|
|
||||||
val, ok := hf.Values[k]
|
|
||||||
if ok {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
return dv
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBoolean returns the bool value or provided default
|
|
||||||
func (hf *hookFields) GetBoolean(k string, dv bool) bool {
|
|
||||||
val, ok := hf.Values[k]
|
|
||||||
if ok {
|
|
||||||
return val == "true"
|
|
||||||
}
|
|
||||||
return dv
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArray returns the array value or provided default
|
|
||||||
func (hf *hookFields) GetArray(k string, dv []string) []string {
|
|
||||||
val, ok := hf.Values[k]
|
|
||||||
if ok && strings.TrimSpace(val) != "" {
|
|
||||||
return strings.Split(val, " ")
|
|
||||||
}
|
|
||||||
return dv
|
|
||||||
}
|
|
@ -26,7 +26,7 @@ type JSONAuth struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auth authenticates the user via a json in content body.
|
// Auth authenticates the user via a json in content body.
|
||||||
func (a JSONAuth) Auth(r *http.Request, usr users.Store, _ *settings.Settings, srv *settings.Server) (*users.User, error) {
|
func (a JSONAuth) Auth(r *http.Request, sto users.Store, root string) (*users.User, error) {
|
||||||
var cred jsonCred
|
var cred jsonCred
|
||||||
|
|
||||||
if r.Body == nil {
|
if r.Body == nil {
|
||||||
@ -39,7 +39,7 @@ func (a JSONAuth) Auth(r *http.Request, usr users.Store, _ *settings.Settings, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If ReCaptcha is enabled, check the code.
|
// If ReCaptcha is enabled, check the code.
|
||||||
if a.ReCaptcha != nil && a.ReCaptcha.Secret != "" {
|
if a.ReCaptcha != nil && len(a.ReCaptcha.Secret) > 0 {
|
||||||
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:govet
|
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:govet
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -51,7 +51,7 @@ func (a JSONAuth) Auth(r *http.Request, usr users.Store, _ *settings.Settings, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := usr.Get(srv.Root, cred.Username)
|
u, err := sto.Get(root, cred.Username)
|
||||||
if err != nil || !users.CheckPwd(cred.Password, u.Password) {
|
if err != nil || !users.CheckPwd(cred.Password, u.Password) {
|
||||||
return nil, os.ErrPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@ const MethodNoAuth settings.AuthMethod = "noauth"
|
|||||||
type NoAuth struct{}
|
type NoAuth struct{}
|
||||||
|
|
||||||
// Auth uses authenticates user 1.
|
// Auth uses authenticates user 1.
|
||||||
func (a NoAuth) Auth(_ *http.Request, usr users.Store, _ *settings.Settings, srv *settings.Server) (*users.User, error) {
|
func (a NoAuth) Auth(r *http.Request, sto users.Store, root string) (*users.User, error) {
|
||||||
return usr.Get(srv.Root, uint(1))
|
return sto.Get(root, uint(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginPage tells that no auth doesn't require a login page.
|
// LoginPage tells that no auth doesn't require a login page.
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
fbErrors "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/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
@ -19,51 +18,16 @@ type ProxyAuth struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auth authenticates the user via an HTTP header.
|
// Auth authenticates the user via an HTTP header.
|
||||||
func (a ProxyAuth) Auth(r *http.Request, usr users.Store, setting *settings.Settings, srv *settings.Server) (*users.User, error) {
|
func (a ProxyAuth) Auth(r *http.Request, sto users.Store, root string) (*users.User, error) {
|
||||||
username := r.Header.Get(a.Header)
|
username := r.Header.Get(a.Header)
|
||||||
user, err := usr.Get(srv.Root, username)
|
user, err := sto.Get(root, username)
|
||||||
if errors.Is(err, fbErrors.ErrNotExist) {
|
if err == errors.ErrNotExist {
|
||||||
return a.createUser(usr, setting, srv, username)
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ProxyAuth) createUser(usr users.Store, setting *settings.Settings, srv *settings.Server, username string) (*users.User, error) {
|
|
||||||
const passwordSize = 32
|
|
||||||
randomPasswordBytes := make([]byte, passwordSize)
|
|
||||||
_, err := rand.Read(randomPasswordBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var hashedRandomPassword string
|
|
||||||
hashedRandomPassword, err = users.HashPwd(string(randomPasswordBytes))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &users.User{
|
|
||||||
Username: username,
|
|
||||||
Password: hashedRandomPassword,
|
|
||||||
LockPassword: true,
|
|
||||||
}
|
|
||||||
setting.Defaults.Apply(user)
|
|
||||||
|
|
||||||
var userHome string
|
|
||||||
userHome, err = setting.MakeUserDir(user.Username, user.Scope, srv.Root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
user.Scope = userHome
|
|
||||||
|
|
||||||
err = usr.Save(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginPage tells that proxy auth doesn't require a login page.
|
// LoginPage tells that proxy auth doesn't require a login page.
|
||||||
func (a ProxyAuth) LoginPage() bool {
|
func (a ProxyAuth) LoginPage() bool {
|
||||||
return false
|
return false
|
||||||
|
@ -14,8 +14,8 @@ 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:gomnd
|
||||||
Run: python(func(_ *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)
|
||||||
command := strings.Join(args[1:], " ")
|
command := strings.Join(args[1:], " ")
|
||||||
|
@ -14,7 +14,7 @@ var cmdsLsCmd = &cobra.Command{
|
|||||||
Short: "List all commands for each event",
|
Short: "List all commands for each event",
|
||||||
Long: `List all commands for each event.`,
|
Long: `List all commands for each event.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: python(func(cmd *cobra.Command, _ []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)
|
||||||
evt := mustGetString(cmd.Flags(), "event")
|
evt := mustGetString(cmd.Flags(), "event")
|
||||||
|
@ -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:gomnd
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ including 'index_end'.`,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: python(func(_ *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)
|
||||||
evt := args[0]
|
evt := args[0]
|
||||||
@ -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:gomnd
|
||||||
f, err = strconv.Atoi(args[2])
|
f, err = strconv.Atoi(args[2])
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
@ -31,23 +31,19 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
|||||||
addServerFlags(flags)
|
addServerFlags(flags)
|
||||||
addUserFlags(flags)
|
addUserFlags(flags)
|
||||||
flags.BoolP("signup", "s", false, "allow users to signup")
|
flags.BoolP("signup", "s", false, "allow users to signup")
|
||||||
flags.Bool("create-user-dir", false, "generate user's home directory automatically")
|
|
||||||
flags.String("shell", "", "shell command to which other commands should be appended")
|
flags.String("shell", "", "shell command to which other commands should be appended")
|
||||||
|
|
||||||
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
||||||
flags.String("auth.header", "", "HTTP header for auth.method=proxy")
|
flags.String("auth.header", "", "HTTP header for auth.method=proxy")
|
||||||
flags.String("auth.command", "", "command for auth.method=hook")
|
|
||||||
|
|
||||||
flags.String("recaptcha.host", "https://www.google.com", "use another host for ReCAPTCHA. recaptcha.net might be useful in China")
|
flags.String("recaptcha.host", "https://www.google.com", "use another host for ReCAPTCHA. recaptcha.net might be useful in China")
|
||||||
flags.String("recaptcha.key", "", "ReCaptcha site key")
|
flags.String("recaptcha.key", "", "ReCaptcha site key")
|
||||||
flags.String("recaptcha.secret", "", "ReCaptcha secret")
|
flags.String("recaptcha.secret", "", "ReCaptcha secret")
|
||||||
|
|
||||||
flags.String("branding.name", "", "replace 'File Browser' by this name")
|
flags.String("branding.name", "", "replace 'File Browser' by this name")
|
||||||
flags.String("branding.theme", "", "set the theme")
|
|
||||||
flags.String("branding.color", "", "set the theme color")
|
flags.String("branding.color", "", "set the theme color")
|
||||||
flags.String("branding.files", "", "path to directory with images and custom styles")
|
flags.String("branding.files", "", "path to directory with images and custom styles")
|
||||||
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
||||||
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
@ -118,20 +114,6 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
|
|||||||
auther = jsonAuth
|
auther = jsonAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
if method == auth.MethodHookAuth {
|
|
||||||
command := mustGetString(flags, "auth.command")
|
|
||||||
|
|
||||||
if command == "" {
|
|
||||||
command = defaultAuther["command"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if command == "" {
|
|
||||||
checkErr(nerrors.New("you must set the flag 'auth.command' for method 'hook'"))
|
|
||||||
}
|
|
||||||
|
|
||||||
auther = &auth.HookAuth{Command: command}
|
|
||||||
}
|
|
||||||
|
|
||||||
if auther == nil {
|
if auther == nil {
|
||||||
panic(errors.ErrInvalidAuthMethod)
|
panic(errors.ErrInvalidAuthMethod)
|
||||||
}
|
}
|
||||||
@ -140,7 +122,7 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) {
|
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) {
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd
|
||||||
|
|
||||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||||
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
||||||
@ -150,9 +132,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
||||||
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
|
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
|
||||||
fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Branding.DisableExternal)
|
fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Branding.DisableExternal)
|
||||||
fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Branding.DisableUsedPercentage)
|
|
||||||
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
|
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
|
||||||
fmt.Fprintf(w, "\tTheme:\t%s\n", set.Branding.Theme)
|
|
||||||
fmt.Fprintln(w, "\nServer:")
|
fmt.Fprintln(w, "\nServer:")
|
||||||
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
|
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
|
||||||
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
|
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
|
||||||
|
@ -13,7 +13,7 @@ var configCatCmd = &cobra.Command{
|
|||||||
Short: "Prints the configuration",
|
Short: "Prints the configuration",
|
||||||
Long: `Prints the configuration.`,
|
Long: `Prints the configuration.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: python(func(_ *cobra.Command, _ []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
set, err := d.store.Settings.Get()
|
set, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
ser, err := d.store.Settings.GetServer()
|
ser, err := d.store.Settings.GetServer()
|
||||||
|
@ -15,7 +15,7 @@ var configExportCmd = &cobra.Command{
|
|||||||
json or yaml file. This exported configuration can be changed,
|
json or yaml file. This exported configuration can be changed,
|
||||||
and imported again with 'config import' command.`,
|
and imported again with 'config import' command.`,
|
||||||
Args: jsonYamlArg,
|
Args: jsonYamlArg,
|
||||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
settings, err := d.store.Settings.Get()
|
settings, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ database.
|
|||||||
|
|
||||||
The path must be for a json or yaml file.`,
|
The path must be for a json or yaml file.`,
|
||||||
Args: jsonYamlArg,
|
Args: jsonYamlArg,
|
||||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
var key []byte
|
var key []byte
|
||||||
if d.hadDB {
|
if d.hadDB {
|
||||||
settings, err := d.store.Settings.Get()
|
settings, err := d.store.Settings.Get()
|
||||||
@ -70,8 +70,6 @@ The path must be for a json or yaml file.`,
|
|||||||
auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth)
|
auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth)
|
||||||
case auth.MethodProxyAuth:
|
case auth.MethodProxyAuth:
|
||||||
auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth)
|
auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth)
|
||||||
case auth.MethodHookAuth:
|
|
||||||
auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth)
|
|
||||||
default:
|
default:
|
||||||
checkErr(errors.New("invalid auth method"))
|
checkErr(errors.New("invalid auth method"))
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ this options can be changed in the future with the command
|
|||||||
to the defaults when creating new users and you don't
|
to the defaults when creating new users and you don't
|
||||||
override the options.`,
|
override the options.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
defaults := settings.UserDefaults{}
|
defaults := settings.UserDefaults{}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
getUserDefaults(flags, &defaults, true)
|
getUserDefaults(flags, &defaults, true)
|
||||||
@ -31,15 +31,12 @@ override the options.`,
|
|||||||
s := &settings.Settings{
|
s := &settings.Settings{
|
||||||
Key: generateKey(),
|
Key: generateKey(),
|
||||||
Signup: mustGetBool(flags, "signup"),
|
Signup: mustGetBool(flags, "signup"),
|
||||||
CreateUserDir: mustGetBool(flags, "create-user-dir"),
|
|
||||||
Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
|
Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
|
||||||
AuthMethod: authMethod,
|
AuthMethod: authMethod,
|
||||||
Defaults: defaults,
|
Defaults: defaults,
|
||||||
Branding: settings.Branding{
|
Branding: settings.Branding{
|
||||||
Name: mustGetString(flags, "branding.name"),
|
Name: mustGetString(flags, "branding.name"),
|
||||||
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
|
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
|
||||||
DisableUsedPercentage: mustGetBool(flags, "branding.disableUsedPercentage"),
|
|
||||||
Theme: mustGetString(flags, "branding.theme"),
|
|
||||||
Files: mustGetString(flags, "branding.files"),
|
Files: mustGetString(flags, "branding.files"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ var configSetCmd = &cobra.Command{
|
|||||||
Long: `Updates the configuration. Set the flags for the options
|
Long: `Updates the configuration. Set the flags for the options
|
||||||
you want to change. Other options will remain unchanged.`,
|
you want to change. Other options will remain unchanged.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
set, err := d.store.Settings.Get()
|
set, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
@ -49,18 +49,12 @@ you want to change. Other options will remain unchanged.`,
|
|||||||
hasAuth = true
|
hasAuth = true
|
||||||
case "shell":
|
case "shell":
|
||||||
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
|
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
|
||||||
case "create-user-dir":
|
|
||||||
set.CreateUserDir = mustGetBool(flags, flag.Name)
|
|
||||||
case "branding.name":
|
case "branding.name":
|
||||||
set.Branding.Name = mustGetString(flags, flag.Name)
|
set.Branding.Name = mustGetString(flags, flag.Name)
|
||||||
case "branding.color":
|
case "branding.color":
|
||||||
set.Branding.Color = mustGetString(flags, flag.Name)
|
set.Branding.Color = mustGetString(flags, flag.Name)
|
||||||
case "branding.theme":
|
|
||||||
set.Branding.Theme = mustGetString(flags, flag.Name)
|
|
||||||
case "branding.disableExternal":
|
case "branding.disableExternal":
|
||||||
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
|
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
|
||||||
case "branding.disableUsedPercentage":
|
|
||||||
set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name)
|
|
||||||
case "branding.files":
|
case "branding.files":
|
||||||
set.Branding.Files = mustGetString(flags, flag.Name)
|
set.Branding.Files = mustGetString(flags, flag.Name)
|
||||||
}
|
}
|
||||||
|
10
cmd/docs.go
10
cmd/docs.go
@ -39,12 +39,12 @@ var docsCmd = &cobra.Command{
|
|||||||
Use: "docs",
|
Use: "docs",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, _ []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
dir := mustGetString(cmd.Flags(), "path")
|
dir := mustGetString(cmd.Flags(), "path")
|
||||||
generateDocs(rootCmd, dir)
|
generateDocs(rootCmd, dir)
|
||||||
names := []string{}
|
names := []string{}
|
||||||
|
|
||||||
err := filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil || info.IsDir() {
|
if err != nil || info.IsDir() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -98,12 +98,12 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) {
|
|||||||
buf.WriteString(long + "\n\n")
|
buf.WriteString(long + "\n\n")
|
||||||
|
|
||||||
if cmd.Runnable() {
|
if cmd.Runnable() {
|
||||||
_, _ = fmt.Fprintf(buf, "```\n%s\n```\n\n", cmd.UseLine())
|
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Example != "" {
|
if len(cmd.Example) > 0 {
|
||||||
buf.WriteString("## Examples\n\n")
|
buf.WriteString("## Examples\n\n")
|
||||||
_, _ = fmt.Fprintf(buf, "```\n%s\n```\n\n", cmd.Example)
|
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
|
||||||
}
|
}
|
||||||
|
|
||||||
printOptions(buf, cmd)
|
printOptions(buf, cmd)
|
||||||
|
@ -17,7 +17,7 @@ var hashCmd = &cobra.Command{
|
|||||||
Short: "Hashes a password",
|
Short: "Hashes a password",
|
||||||
Long: `Hashes a password using bcrypt algorithm.`,
|
Long: `Hashes a password using bcrypt algorithm.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(_ *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
pwd, err := users.HashPwd(args[0])
|
pwd, err := users.HashPwd(args[0])
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
fmt.Println(pwd)
|
fmt.Println(pwd)
|
||||||
|
33
cmd/root.go
33
cmd/root.go
@ -64,7 +64,6 @@ func addServerFlags(flags *pflag.FlagSet) {
|
|||||||
flags.Uint32("socket-perm", 0666, "unix socket file permissions") //nolint:gomnd
|
flags.Uint32("socket-perm", 0666, "unix socket file permissions") //nolint:gomnd
|
||||||
flags.StringP("baseurl", "b", "", "base url")
|
flags.StringP("baseurl", "b", "", "base url")
|
||||||
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
||||||
flags.String("token-expiration-time", "2h", "user session timeout")
|
|
||||||
flags.Int("img-processors", 4, "image processors count") //nolint:gomnd
|
flags.Int("img-processors", 4, "image processors count") //nolint:gomnd
|
||||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||||
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
||||||
@ -76,7 +75,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Use: "filebrowser",
|
Use: "filebrowser",
|
||||||
Short: "A stylish web-based file browser",
|
Short: "A stylish web-based file browser",
|
||||||
Long: `File Browser CLI lets you create the database to use with File Browser,
|
Long: `File Browser CLI lets you create the database to use with File Browser,
|
||||||
manage your users and all the configurations without accessing the
|
manage your users and all the configurations without acessing the
|
||||||
web interface.
|
web interface.
|
||||||
|
|
||||||
If you've never run File Browser, you'll need to have a database for
|
If you've never run File Browser, you'll need to have a database for
|
||||||
@ -108,9 +107,9 @@ name in caps. So to set "database" via an env variable, you should
|
|||||||
set FB_DATABASE.
|
set FB_DATABASE.
|
||||||
|
|
||||||
Also, if the database path doesn't exist, File Browser will enter into
|
Also, if the database path doesn't exist, File Browser will enter into
|
||||||
the quick setup mode and a new database will be bootstrapped and a new
|
the quick setup mode and a new database will be bootstraped and a new
|
||||||
user created with the credentials from options "username" and "password".`,
|
user created with the credentials from options "username" and "password".`,
|
||||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
log.Println(cfgFile)
|
log.Println(cfgFile)
|
||||||
|
|
||||||
if !d.hadDB {
|
if !d.hadDB {
|
||||||
@ -182,7 +181,6 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
log.Println("Listening on", listener.Addr().String())
|
log.Println("Listening on", listener.Addr().String())
|
||||||
//nolint: gosec
|
|
||||||
if err := http.Serve(listener, handler); err != nil {
|
if err := http.Serve(listener, handler); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -262,10 +260,6 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
|||||||
_, disableExec := getParamB(flags, "disable-exec")
|
_, disableExec := getParamB(flags, "disable-exec")
|
||||||
server.EnableExec = !disableExec
|
server.EnableExec = !disableExec
|
||||||
|
|
||||||
if val, set := getParamB(flags, "token-expiration-time"); set {
|
|
||||||
server.TokenExpirationTime = val
|
|
||||||
}
|
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +315,6 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
Key: generateKey(),
|
Key: generateKey(),
|
||||||
Signup: false,
|
Signup: false,
|
||||||
CreateUserDir: false,
|
CreateUserDir: false,
|
||||||
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
|
||||||
Defaults: settings.UserDefaults{
|
Defaults: settings.UserDefaults{
|
||||||
Scope: ".",
|
Scope: ".",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
@ -337,15 +330,6 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
Download: true,
|
Download: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AuthMethod: "",
|
|
||||||
Branding: settings.Branding{},
|
|
||||||
Tus: settings.Tus{
|
|
||||||
ChunkSize: settings.DefaultTusChunkSize,
|
|
||||||
RetryCount: settings.DefaultTusRetryCount,
|
|
||||||
},
|
|
||||||
Commands: nil,
|
|
||||||
Shell: nil,
|
|
||||||
Rules: nil,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -378,13 +362,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
password := getParam(flags, "password")
|
password := getParam(flags, "password")
|
||||||
|
|
||||||
if password == "" {
|
if password == "" {
|
||||||
var pwd string
|
password, err = users.HashPwd("admin")
|
||||||
pwd, err = users.RandomPwd()
|
|
||||||
checkErr(err)
|
|
||||||
|
|
||||||
log.Println("Generated random admin password for quick setup:", pwd)
|
|
||||||
|
|
||||||
password, err = users.HashPwd(pwd)
|
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,8 +400,7 @@ func initConfig() {
|
|||||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
|
||||||
if err := v.ReadInConfig(); err != nil {
|
if err := v.ReadInConfig(); err != nil {
|
||||||
var configParseError v.ConfigParseError
|
if _, ok := err.(v.ConfigParseError); ok {
|
||||||
if errors.As(err, &configParseError) {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
cfgFile = "No config file used"
|
cfgFile = "No config file used"
|
||||||
|
@ -28,7 +28,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(1, 2)(cmd, args); err != nil {
|
if err := cobra.RangeArgs(1, 2)(cmd, args); err != nil { //nolint:gomnd
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,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:gomnd
|
||||||
f, err = strconv.Atoi(args[1])
|
f, err = strconv.Atoi(args[1])
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ var rulesLsCommand = &cobra.Command{
|
|||||||
Short: "List global rules or user specific rules",
|
Short: "List global rules or user specific rules",
|
||||||
Long: `List global rules or user specific rules.`,
|
Long: `List global rules or user specific rules.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
runRules(d.store, cmd, nil, nil)
|
runRules(d.store, cmd, nil, nil)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ var upgradeCmd = &cobra.Command{
|
|||||||
import share links because they are incompatible with
|
import share links because they are incompatible with
|
||||||
this version.`,
|
this version.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, _ []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
oldDB := mustGetString(flags, "old.database")
|
oldDB := mustGetString(flags, "old.database")
|
||||||
oldConf := mustGetString(flags, "old.config")
|
oldConf := mustGetString(flags, "old.config")
|
||||||
|
@ -26,7 +26,7 @@ var usersCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printUsers(usrs []*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) //nolint:gomnd
|
||||||
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
|
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
|
||||||
|
|
||||||
for _, u := range usrs {
|
for _, u := range usrs {
|
||||||
@ -53,7 +53,7 @@ func printUsers(usrs []*users.User) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseUsernameOrID(arg string) (username string, id uint) {
|
func parseUsernameOrID(arg string) (username string, id uint) {
|
||||||
id64, err := strconv.ParseUint(arg, 10, 64)
|
id64, err := strconv.ParseUint(arg, 10, 64) //nolint:gomnd
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return arg, 0
|
return arg, 0
|
||||||
}
|
}
|
||||||
|
@ -15,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:gomnd
|
||||||
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)
|
||||||
|
@ -14,7 +14,7 @@ var usersExportCmd = &cobra.Command{
|
|||||||
Long: `Export all users to a json or yaml file. Please indicate the
|
Long: `Export all users to a json or yaml file. Please indicate the
|
||||||
path to the file where you want to write the users.`,
|
path to the file where you want to write the users.`,
|
||||||
Args: jsonYamlArg,
|
Args: jsonYamlArg,
|
||||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
list, err := d.store.Users.Gets("")
|
list, err := d.store.Users.Gets("")
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ var usersLsCmd = &cobra.Command{
|
|||||||
Run: findUsers,
|
Run: findUsers,
|
||||||
}
|
}
|
||||||
|
|
||||||
var findUsers = python(func(_ *cobra.Command, args []string, d pythonData) {
|
var findUsers = python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
var (
|
var (
|
||||||
list []*users.User
|
list []*users.User
|
||||||
user *users.User
|
user *users.User
|
||||||
|
@ -60,7 +60,7 @@ list or set it to 0.`,
|
|||||||
// User exists in DB.
|
// User exists in DB.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !overwrite {
|
if !overwrite {
|
||||||
checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered"))
|
checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registred"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the usernames mismatch, check if there is another one in the DB
|
// If the usernames mismatch, check if there is another one in the DB
|
||||||
@ -84,6 +84,6 @@ list or set it to 0.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func usernameConflictError(username string, originalID, newID uint) error {
|
func usernameConflictError(username string, originalID, newID uint) error {
|
||||||
return fmt.Errorf(`can't import user with ID %d and username "%s" because the username is already registered with the user %d`,
|
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)
|
newID, username, originalID)
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ var usersRmCmd = &cobra.Command{
|
|||||||
Short: "Delete a user by username or id",
|
Short: "Delete a user by username or id",
|
||||||
Long: `Delete a user by username or id`,
|
Long: `Delete a user by username or id`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
username, id := parseUsernameOrID(args[0])
|
username, id := parseUsernameOrID(args[0])
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
15
cmd/utils.go
15
cmd/utils.go
@ -9,7 +9,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm"
|
||||||
"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"
|
||||||
@ -87,23 +87,16 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
|||||||
data := pythonData{hadDB: true}
|
data := pythonData{hadDB: true}
|
||||||
|
|
||||||
path := getParam(cmd.Flags(), "database")
|
path := getParam(cmd.Flags(), "database")
|
||||||
absPath, err := filepath.Abs(path)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
exists, err := dbExists(path)
|
exists, err := dbExists(path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
} else if exists && cfg.noDB {
|
} else if exists && cfg.noDB {
|
||||||
log.Fatal(absPath + " already exists")
|
log.Fatal(path + " already exists")
|
||||||
} else if !exists && !cfg.noDB && !cfg.allowNoDB {
|
} else if !exists && !cfg.noDB && !cfg.allowNoDB {
|
||||||
log.Fatal(absPath + " does not exist. Please run 'filebrowser config init' first.")
|
log.Fatal(path + " does not exist. Please run 'filebrowser config init' first.")
|
||||||
} else if !exists && !cfg.noDB {
|
|
||||||
log.Println("Warning: filebrowser.db can't be found. Initialing in " + strings.TrimSuffix(absPath, "filebrowser.db"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Using database: " + absPath)
|
|
||||||
data.hadDB = exists
|
data.hadDB = exists
|
||||||
db, err := storm.Open(path)
|
db, err := storm.Open(path)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
@ -188,7 +181,7 @@ func cleanUpMapValue(v interface{}) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// convertCmdStrToCmdArray checks if cmd string is blank (whitespace included)
|
// convertCmdStrToCmdArray checks if cmd string is blank (whitespace included)
|
||||||
// then returns empty string array, else returns the split word array of cmd.
|
// then returns empty string array, else returns the splitted word array of cmd.
|
||||||
// This is to ensure the result will never be []string{""}
|
// This is to ensure the result will never be []string{""}
|
||||||
func convertCmdStrToCmdArray(cmd string) []string {
|
func convertCmdStrToCmdArray(cmd string) []string {
|
||||||
var cmdArray []string
|
var cmdArray []string
|
||||||
|
@ -15,7 +15,7 @@ func init() {
|
|||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Print the version number",
|
Short: "Print the version number",
|
||||||
Run: func(_ *cobra.Command, _ []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Println("File Browser v" + version.Version + "/" + version.CommitSHA)
|
fmt.Println("File Browser v" + version.Version + "/" + version.CommitSHA)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
SHELL := /usr/bin/env bash
|
SHELL := /bin/bash
|
||||||
DATE ?= $(shell date +%FT%T%z)
|
DATE ?= $(shell date +%FT%T%z)
|
||||||
BASE_PATH := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
BASE_PATH := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||||
VERSION ?= $(shell git describe --tags --always --match=v* 2> /dev/null || \
|
VERSION ?= $(shell git describe --tags --always --match=v* 2> /dev/null || \
|
||||||
|
@ -31,7 +31,7 @@ func New(fs afero.Fs, root string) *FileCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileCache) Store(_ context.Context, key string, value []byte) error {
|
func (f *FileCache) Store(ctx context.Context, key string, value []byte) error {
|
||||||
mu := f.getScopedLocks(key)
|
mu := f.getScopedLocks(key)
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
@ -48,7 +48,7 @@ func (f *FileCache) Store(_ context.Context, key string, value []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileCache) Load(_ context.Context, key string) (value []byte, exist bool, err error) {
|
func (f *FileCache) Load(ctx context.Context, key string) (value []byte, exist bool, err error) {
|
||||||
r, ok, err := f.open(key)
|
r, ok, err := f.open(key)
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
return nil, ok, err
|
return nil, ok, err
|
||||||
@ -62,7 +62,7 @@ func (f *FileCache) Load(_ context.Context, key string) (value []byte, exist boo
|
|||||||
return value, true, nil
|
return value, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileCache) Delete(_ context.Context, key string) error {
|
func (f *FileCache) Delete(ctx context.Context, key string) error {
|
||||||
mu := f.getScopedLocks(key)
|
mu := f.getScopedLocks(key)
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
@ -40,7 +40,7 @@ func TestFileCache(t *testing.T) {
|
|||||||
require.False(t, exists)
|
require.False(t, exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath string, cache *FileCache, key, wantValue string) { //nolint:revive
|
func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath string, cache *FileCache, key, wantValue string) { //nolint:golint
|
||||||
t.Helper()
|
t.Helper()
|
||||||
// check actual file content
|
// check actual file content
|
||||||
b, err := afero.ReadFile(fs, fileFullPath)
|
b, err := afero.ReadFile(fs, fileFullPath)
|
||||||
|
@ -11,14 +11,14 @@ func NewNoOp() *NoOp {
|
|||||||
return &NoOp{}
|
return &NoOp{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NoOp) Store(_ context.Context, _ string, _ []byte) error {
|
func (n *NoOp) Store(ctx context.Context, key string, value []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NoOp) Load(_ context.Context, _ string) (value []byte, exist bool, err error) {
|
func (n *NoOp) Load(ctx context.Context, key string) (value []byte, exist bool, err error) {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NoOp) Delete(_ context.Context, _ string) error {
|
func (n *NoOp) Delete(ctx context.Context, key string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
0
docker/root/custom-cont-init.d/20-config → docker/root/etc/cont-init.d/20-config
Executable file → Normal file
0
docker/root/custom-cont-init.d/20-config → docker/root/etc/cont-init.d/20-config
Executable file → Normal file
0
docker/root/etc/services.d/filebrowser/run
Executable file → Normal file
0
docker/root/etc/services.d/filebrowser/run
Executable file → Normal file
Binary file not shown.
Before Width: | Height: | Size: 65 KiB |
Binary file not shown.
Before Width: | Height: | Size: 116 KiB |
Binary file not shown.
Before Width: | Height: | Size: 151 KiB |
Binary file not shown.
Before Width: | Height: | Size: 212 KiB |
Binary file not shown.
Before Width: | Height: | Size: 130 KiB |
Binary file not shown.
Before Width: | Height: | Size: 60 KiB |
@ -1,46 +0,0 @@
|
|||||||
# Code of Conduct
|
|
||||||
|
|
||||||
## Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
### Our Pledge
|
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
|
||||||
|
|
||||||
### Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment include:
|
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
|
||||||
* Being respectful of differing viewpoints and experiences
|
|
||||||
* Gracefully accepting constructive criticism
|
|
||||||
* Focusing on what is best for the community
|
|
||||||
* Showing empathy towards other community members
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
||||||
|
|
||||||
### Our Responsibilities
|
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
### Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
|
||||||
|
|
||||||
### Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hacdias@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
|
||||||
|
|
||||||
### Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.4, available at [https://contributor-covenant.org/version/1/4](https://contributor-covenant.org/version/1/4).
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
|||||||
# Configuration
|
|
||||||
|
|
||||||
Most of the configuration can be understood through our Command Line Interface documentation. Although there are some specific topics that we want to cover on this section.
|
|
||||||
|
|
||||||
## Custom Branding
|
|
||||||
|
|
||||||
You are able to customize your File Browser installation by changing its name to any other you want, by adding a global custom style sheet and by using your own logotype if you want. To address this, there are three configuration options that can be changed:
|
|
||||||
|
|
||||||
* **Name:** which is the instance name that will show up on login and signup pages. This won't replace the version message in the sidebar.
|
|
||||||
* **Disable external links:** this will disable any external links (except the ones to this documentation).
|
|
||||||
* **Folder:** is the path to a directory that can contain two items:
|
|
||||||
* **custom.css**, containing the styles you want to apply to your installation.
|
|
||||||
* **img** a directory whose files can replace the [default logotypes](../frontend/public/img) in the application.
|
|
||||||
|
|
||||||
These options can be either set via the CLI interface using the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
filebrowser config set --branding.name "My Name" \
|
|
||||||
--branding.files "/abs/path/to/my/dir" \
|
|
||||||
--branding.disableExternal
|
|
||||||
```
|
|
||||||
Or can be set under 'Branding directory path' in **Settings → Global Settings**.
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
>
|
|
||||||
> If using Docker then remember to bind this directory, for example as `/home/username/containers/filebrowser/branding:/branding`
|
|
||||||
|
|
||||||
For custom icons to be recognized you need to create `img` and `img/icons` directories and place the svg in the `branding/img` directory:
|
|
||||||
|
|
||||||
```
|
|
||||||
- filebrowser
|
|
||||||
- branding
|
|
||||||
- img
|
|
||||||
- icons
|
|
||||||
- logo.svg
|
|
||||||
- filebrowser.db
|
|
||||||
```
|
|
||||||
|
|
||||||
To replace the favicon you need to place this in the `img/icons` directory but also note that some of the other PNG icon types will be required too (see the default logotypes link above) as the browser will normally use the highest resolution option available (at a minimum the 16x16 and 32x32 options). You can use the [Real Favicon Generator](https://realfavicongenerator.net/) to generate these for you from your base image.
|
|
||||||
|
|
||||||
The icons are cached, to make the new ones appear more quickly open developer tools in your browser, then click on the Application tab, then Storage and then 'Clear Site Data'.
|
|
||||||
|
|
||||||
## Authentication Method
|
|
||||||
|
|
||||||
Right now, there are three possible authentication methods. Each one of them has its own capabilities and specification. If you are interested in contributing with one more authentication method, please [check the guidelines](./contributing.md).
|
|
||||||
|
|
||||||
### JSON Auth (default)
|
|
||||||
|
|
||||||
We call it JSON Authentication but it is just the default authentication method and the one that is provided by default if you don't make any changes. It is set by default, but if you've made changes before you can revert to using JSON auth:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
filebrowser config set --auth.method=json
|
|
||||||
```
|
|
||||||
|
|
||||||
This method can also be extended with **reCAPTCHA** verification during login:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
filebrowser config set --auth.method=json \
|
|
||||||
--recaptcha.key site-key \
|
|
||||||
--recaptcha.secret private-key
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, we use [Google's reCAPTCHA](https://developers.google.com/recaptcha/docs/display) service. If you live in China, or want to use other provider, you can change the host with the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
filebrowser config set --recaptcha.host https://recaptcha.net
|
|
||||||
```
|
|
||||||
|
|
||||||
Where `https://recaptcha.net` is any provider you want.
|
|
||||||
|
|
||||||
|
|
||||||
> [!CAUTION]
|
|
||||||
>
|
|
||||||
> Note that you **always** need to set the `--auth.method` flag when changing authentication configurations and that it will completely overwrite your current settings. [This is a known issue.](https://github.com/filebrowser/filebrowser/issues/715)
|
|
||||||
|
|
||||||
### Proxy Header
|
|
||||||
|
|
||||||
If you have a reverse proxy you want to use to login your users, you do it via our `proxy` authentication method. To configure this method, your proxy must send an HTTP header containing the username of the logged in user:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
filebrowser config set --auth.method=proxy --auth.header=X-My-Header
|
|
||||||
```
|
|
||||||
|
|
||||||
Where `X-My-Header` is the HTTP header provided by your proxy with the username.
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
>
|
|
||||||
> File Browser will blindly trust the provided header. If the proxy can be bypassed, an attacker could simply attach the header and get admin access.
|
|
||||||
|
|
||||||
### No Authentication
|
|
||||||
|
|
||||||
We also provide a no authentication mechanism for users that want to use File Browser privately such in a home network. By setting this authentication method, the user with **id 1** will be used as the default users. Creating more users won't have any effect.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
filebrowser config set --auth.method=noauth
|
|
||||||
```
|
|
||||||
|
|
||||||
## Command Runner
|
|
||||||
|
|
||||||
The command runner is a feature that enables you to execute any shell command you want before or after a certain event. Right now, these are the events:
|
|
||||||
|
|
||||||
* Copy
|
|
||||||
* Rename
|
|
||||||
* Upload
|
|
||||||
* Delete
|
|
||||||
* Save
|
|
||||||
|
|
||||||
Also, during the execution of the commands set for those hooks, there will be some environment variables available to help you perform your commands:
|
|
||||||
|
|
||||||
* `FILE` with the full absolute path to the changed file.
|
|
||||||
* `SCOPE` with the path to user's scope.
|
|
||||||
* `TRIGGER` with the name of the event.
|
|
||||||
* `USERNAME` with the user's username.
|
|
||||||
* `DESTINATION` with the absolute path to the destination. Only used for **copy** and **rename.**
|
|
||||||
|
|
||||||
At this moment, you can edit the commands via the command line interface, using the following commands \(please check the flag `--help` to know more about them\):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
filebrowser cmds add before_copy "echo $FILE"
|
|
||||||
filebrowser cmds rm before_copy 0
|
|
||||||
filebrowser cmds ls
|
|
||||||
```
|
|
||||||
|
|
||||||
Or you can use the web interface to manage them via **Settings** → **Global Settings**.
|
|
||||||
|
|
||||||
|
|
||||||
## Shell commands
|
|
||||||
|
|
||||||
Within Filebrowser you can toggle the shell (`< >` icon at the top right) and this will open a shell command window at the bottom of the screen.
|
|
||||||
|
|
||||||
**By default no commands are available as the command list is empty**
|
|
||||||
|
|
||||||
To enable commands these need to either be done on a per-user basis (including for the Admin user).
|
|
||||||
|
|
||||||
You can do this by adding them in Settings > User Management > (edit user) > Commands or to *apply to all new users created from that point forward* they can be set in Settings > Global Settings
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
>
|
|
||||||
> If using a proxy manager then remember to enable websockets support for the Filebrowser proxy
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
>
|
|
||||||
> If using Docker and you want to add a new command that is not in the base image then you will need to build a custom Docker image using `filebrowser/filebrowser` as a base image. For example to add 7z:
|
|
||||||
>
|
|
||||||
> ```docker
|
|
||||||
> FROM filebrowser/filebrowser
|
|
||||||
> RUN sudo apt install p7zip-full
|
|
||||||
> ```
|
|
@ -1,91 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
If you're interested in contributing to this project, this is the best place to start. Before contributing to this project, please take a bit of time to read our [Code of Conduct](./code-of-conduct.md). Also, note that this project is open-source and licensed under [Apache License 2.0](../LICENSE).
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
The backend side of the application is written in [Go](https://golang.org/), while the frontend (located on a subdirectory of the same name) is written in [Vue.js](https://vuejs.org/). Due to the tight coupling required by some features, basic knowledge of both Go and Vue.js is recommended.
|
|
||||||
|
|
||||||
* Learn Go: [https://github.com/golang/go/wiki/Learn](https://github.com/golang/go/wiki/Learn)
|
|
||||||
* Learn Vue.js: [https://vuejs.org/guide/introduction.html](https://vuejs.org/guide/introduction.html)
|
|
||||||
|
|
||||||
We encourage you to use git to manage your fork. To clone the main repository, just run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/filebrowser/filebrowser
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
We are using [Node.js](https://nodejs.org/en/) on the frontend to manage the build process. The steps to build it are:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# From the root of the repo, go to frontend/
|
|
||||||
cd frontend
|
|
||||||
|
|
||||||
# Install the dependencies
|
|
||||||
pnpm install
|
|
||||||
|
|
||||||
# Build the frontend
|
|
||||||
pnpm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
This will install the dependencies and build the frontend so you can then embed it into the Go app. Although, if you want to play with it, you'll get bored of building it after every change you do. So, you can run the command below to watch for changes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
|
|
||||||
First of all, you need to download the required dependencies. We are using the built-in `go mod` tool for dependency management. To get the modules, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go mod download
|
|
||||||
```
|
|
||||||
|
|
||||||
The magic of File Browser is that the static assets are bundled into the final binary. For that, we use [Go embed.FS](https://golang.org/pkg/embed/). The files from `frontend/dist` will be embedded during the build process.
|
|
||||||
|
|
||||||
To build File Browser is just like any other Go program:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a development build use the "dev" tag, this way the content inside the frontend folder will not be embedded in the binary but will be reloaded at every change:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build -tags dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Translations
|
|
||||||
|
|
||||||
Translations are managed on Transifex, which is an online website where everyone can contribute and translate strings for our project. It automatically syncs with the main language file \(in English\) and,, for you to contribute, you just need to:
|
|
||||||
|
|
||||||
1. Go to our Transifex web page: [app.transifex.com/file-browser/file-browser](https://app.transifex.com/file-browser/file-browser/)
|
|
||||||
2. Click on **Join the project** and pick your language. We'll accept you as soon as possible. If you're language is not on the list, please request it via the interface.
|
|
||||||
|
|
||||||
Translations are automatically pushed to GitHub via an integration.
|
|
||||||
|
|
||||||
## Authentication Provider
|
|
||||||
|
|
||||||
To build a new authentication provider, you need to implement the [Auther interface](https://github.com/filebrowser/filebrowser/blob/master/auth/auth.go), whose method will be called on the login page after the user has submitted their login data.
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Auther is the authentication interface.
|
|
||||||
type Auther interface {
|
|
||||||
// Auth is called to authenticate a request.
|
|
||||||
Auth(r *http.Request, s *users.Storage, root string) (*users.User, error)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
After implementing the interface you should:
|
|
||||||
|
|
||||||
1. Add it to [`auth` directory](https://github.com/filebrowser/filebrowser/blob/master/auth).
|
|
||||||
2. Add it to the [configuration parser](https://github.com/filebrowser/filebrowser/blob/master/cmd/config.go) for the CLI.
|
|
||||||
3. Add it to the [`authBackend.Get`](https://github.com/filebrowser/filebrowser/blob/master/storage/bolt/auth.go).
|
|
||||||
|
|
||||||
If you need to add more flags, please update the function `addConfigFlags`.
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
|||||||
# Installation
|
|
||||||
|
|
||||||
File Browser is a single binary and can be used as a standalone executable. Although, some might prefer to use it with [Docker](https://www.docker.com) or [Caddy](https://caddyserver.com), which is a fantastic web server that enables HTTPS by default. Its installation is quite straightforward independently on which system you want to use.
|
|
||||||
|
|
||||||
## Quick Setup
|
|
||||||
|
|
||||||
The quickest way for beginners to start using File Browser is by opening your terminal and executing the following commands:
|
|
||||||
|
|
||||||
### Brew
|
|
||||||
|
|
||||||
```sh
|
|
||||||
brew tap filebrowser/tap
|
|
||||||
brew install filebrowser
|
|
||||||
filebrowser -r /path/to/your/files
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unix
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
|
|
||||||
filebrowser -r /path/to/your/files
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
```sh
|
|
||||||
iwr -useb https://raw.githubusercontent.com/filebrowser/get/master/get.ps1 | iex
|
|
||||||
filebrowser -r /path/to/your/files
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuring
|
|
||||||
|
|
||||||
Done! It will bootstrap a database in which all the configurations and users are stored. Now, you can see on your command line the address in which your instance is running. You just need to go to that URL and use the following credentials:
|
|
||||||
|
|
||||||
* Username: `admin`
|
|
||||||
* Password: (printed in your console)
|
|
||||||
|
|
||||||
Although this is the fastest way to bootstrap an instance, we recommend you to take a look at other possible options, by checking `config init --help` and `config set --help`, to make the installation as safe and customized as it can be.
|
|
||||||
|
|
||||||
## Docker
|
|
||||||
|
|
||||||
File Browser is available as two different Docker images, which can be found on [Docker Hub](https://hub.docker.com/r/filebrowser/filebrowser).
|
|
||||||
|
|
||||||
### Alpine
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker run \
|
|
||||||
-v /path/to/srv:/srv \
|
|
||||||
-v /path/to/filebrowser.db:/database.db \
|
|
||||||
-v /path/to/.filebrowser.json:/.filebrowser.json \
|
|
||||||
-u $(id -u):$(id -g) \
|
|
||||||
-p 8080:80 \
|
|
||||||
filebrowser/filebrowser
|
|
||||||
```
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
- `/path/to/srv` contains the files root directory for File Browser
|
|
||||||
- `/path/to/filebrowser.db` is the `database.db`
|
|
||||||
- `/path/to/database` is the `.filebrowser.json`
|
|
||||||
|
|
||||||
> [!Warning]
|
|
||||||
>
|
|
||||||
> To use this image correctly, you need to first initialize a File Browser database outside of the Docker image and then start the Docker image with the database mounted. Otherwise, Docker will create an empty directory at the mounting point and fail to start.
|
|
||||||
|
|
||||||
### s6 overlay
|
|
||||||
|
|
||||||
The `s6` image is based on LinuxServer and leverages the [s6-overlay](https://github.com/just-containers/s6-overlay) system for a standard, highly customizable image. It should be used as follows:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run \
|
|
||||||
-v /path/to/srv:/srv \
|
|
||||||
-v /path/to/database:/database \
|
|
||||||
-v /path/to/config:/config \
|
|
||||||
-e PUID=$(id -u) \
|
|
||||||
-e PGID=$(id -g) \
|
|
||||||
-p 8080:80 \
|
|
||||||
filebrowser/filebrowser:s6
|
|
||||||
```
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
- `/path/to/srv` contains the files root directory for File Browser
|
|
||||||
- `/path/to/config` contains a `settings.json` file
|
|
||||||
- `/path/to/database` contains a `filebrowser.db` file
|
|
144
files/file.go
144
files/file.go
@ -6,35 +6,23 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"hash"
|
"hash"
|
||||||
"image"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"log"
|
"log"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
"github.com/filebrowser/filebrowser/v2/errors"
|
||||||
"github.com/filebrowser/filebrowser/v2/rules"
|
"github.com/filebrowser/filebrowser/v2/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PermFile = 0644
|
|
||||||
const PermDir = 0755
|
|
||||||
|
|
||||||
var (
|
|
||||||
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
|
||||||
reSubExts = regexp.MustCompile("(?i)(.vtt|.srt|.ass|.ssa)$")
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileInfo describes a file.
|
// FileInfo describes a file.
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
*Listing
|
*Listing
|
||||||
@ -52,8 +40,6 @@ type FileInfo struct {
|
|||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
Checksums map[string]string `json:"checksums,omitempty"`
|
Checksums map[string]string `json:"checksums,omitempty"`
|
||||||
Token string `json:"token,omitempty"`
|
Token string `json:"token,omitempty"`
|
||||||
currentDir []os.FileInfo `json:"-"`
|
|
||||||
Resolution *ImageResolution `json:"resolution,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileOptions are the options when getting a file info.
|
// FileOptions are the options when getting a file info.
|
||||||
@ -68,15 +54,10 @@ type FileOptions struct {
|
|||||||
Content bool
|
Content bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageResolution struct {
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFileInfo creates a File object from a path and a given user. This File
|
// NewFileInfo creates a File object from a path and a given user. This File
|
||||||
// object will be automatically filled depending on if it is a directory
|
// object will be automatically filled depending on if it is a directory
|
||||||
// or a file. If it's a video file, it will also detect any subtitles.
|
// or a file. If it's a video file, it will also detect any subtitles.
|
||||||
func NewFileInfo(opts *FileOptions) (*FileInfo, error) {
|
func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
||||||
if !opts.Checker.Check(opts.Path) {
|
if !opts.Checker.Check(opts.Path) {
|
||||||
return nil, os.ErrPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
@ -103,7 +84,7 @@ func NewFileInfo(opts *FileOptions) (*FileInfo, error) {
|
|||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func stat(opts *FileOptions) (*FileInfo, error) {
|
func stat(opts FileOptions) (*FileInfo, error) {
|
||||||
var file *FileInfo
|
var file *FileInfo
|
||||||
|
|
||||||
if lstaterFs, ok := opts.Fs.(afero.Lstater); ok {
|
if lstaterFs, ok := opts.Fs.(afero.Lstater); ok {
|
||||||
@ -166,7 +147,7 @@ func stat(opts *FileOptions) (*FileInfo, error) {
|
|||||||
// algorithm. The checksums data is saved on File object.
|
// algorithm. The checksums data is saved on File object.
|
||||||
func (i *FileInfo) Checksum(algo string) error {
|
func (i *FileInfo) Checksum(algo string) error {
|
||||||
if i.IsDir {
|
if i.IsDir {
|
||||||
return fbErrors.ErrIsDirectory
|
return errors.ErrIsDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.Checksums == nil {
|
if i.Checksums == nil {
|
||||||
@ -192,7 +173,7 @@ func (i *FileInfo) Checksum(algo string) error {
|
|||||||
case "sha512":
|
case "sha512":
|
||||||
h = sha512.New()
|
h = sha512.New()
|
||||||
default:
|
default:
|
||||||
return fbErrors.ErrInvalidOption
|
return errors.ErrInvalidOption
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.Copy(h, reader)
|
_, err = io.Copy(h, reader)
|
||||||
@ -204,20 +185,8 @@ func (i *FileInfo) Checksum(algo string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *FileInfo) RealPath() string {
|
|
||||||
if realPathFs, ok := i.Fs.(interface {
|
|
||||||
RealPath(name string) (fPath string, err error)
|
|
||||||
}); ok {
|
|
||||||
realPath, err := realPathFs.RealPath(i.Path)
|
|
||||||
if err == nil {
|
|
||||||
return realPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:goconst
|
//nolint:goconst
|
||||||
|
//TODO: use constants
|
||||||
func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
||||||
if IsNamedPipe(i.Mode) {
|
if IsNamedPipe(i.Mode) {
|
||||||
i.Type = "blob"
|
i.Type = "blob"
|
||||||
@ -249,15 +218,6 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
|||||||
return nil
|
return nil
|
||||||
case strings.HasPrefix(mimetype, "image"):
|
case strings.HasPrefix(mimetype, "image"):
|
||||||
i.Type = "image"
|
i.Type = "image"
|
||||||
resolution, err := calculateImageResolution(i.Fs, i.Path)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error calculating image resolution: %v", err)
|
|
||||||
} else {
|
|
||||||
i.Resolution = resolution
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case strings.HasSuffix(mimetype, "pdf"):
|
|
||||||
i.Type = "pdf"
|
|
||||||
return nil
|
return nil
|
||||||
case (strings.HasPrefix(mimetype, "text") || !isBinary(buffer)) && i.Size <= 10*1024*1024: // 10 MB
|
case (strings.HasPrefix(mimetype, "text") || !isBinary(buffer)) && i.Size <= 10*1024*1024: // 10 MB
|
||||||
i.Type = "text"
|
i.Type = "text"
|
||||||
@ -283,28 +243,6 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateImageResolution(fSys afero.Fs, filePath string) (*ImageResolution, error) {
|
|
||||||
file, err := fSys.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if cErr := file.Close(); cErr != nil {
|
|
||||||
log.Printf("Failed to close file: %v", cErr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
config, _, err := image.DecodeConfig(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ImageResolution{
|
|
||||||
Width: config.Width,
|
|
||||||
Height: config.Height,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *FileInfo) readFirstBytes() []byte {
|
func (i *FileInfo) readFirstBytes() []byte {
|
||||||
reader, err := i.Fs.Open(i.Path)
|
reader, err := i.Fs.Open(i.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -316,7 +254,7 @@ func (i *FileInfo) readFirstBytes() []byte {
|
|||||||
|
|
||||||
buffer := make([]byte, 512) //nolint:gomnd
|
buffer := make([]byte, 512) //nolint:gomnd
|
||||||
n, err := reader.Read(buffer)
|
n, err := reader.Read(buffer)
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
if err != nil && err != io.EOF {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
i.Type = "blob"
|
i.Type = "blob"
|
||||||
return nil
|
return nil
|
||||||
@ -334,57 +272,17 @@ func (i *FileInfo) detectSubtitles() {
|
|||||||
ext := filepath.Ext(i.Path)
|
ext := filepath.Ext(i.Path)
|
||||||
|
|
||||||
// detect multiple languages. Base*.vtt
|
// detect multiple languages. Base*.vtt
|
||||||
|
// TODO: give subtitles descriptive names (lang) and track attributes
|
||||||
parentDir := strings.TrimRight(i.Path, i.Name)
|
parentDir := strings.TrimRight(i.Path, i.Name)
|
||||||
var dir []os.FileInfo
|
dir, err := afero.ReadDir(i.Fs, parentDir)
|
||||||
if len(i.currentDir) > 0 {
|
if err == nil {
|
||||||
dir = i.currentDir
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
dir, err = afero.ReadDir(i.Fs, parentDir)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base := strings.TrimSuffix(i.Name, ext)
|
base := strings.TrimSuffix(i.Name, ext)
|
||||||
for _, f := range dir {
|
for _, f := range dir {
|
||||||
// load all supported subtitles from subs directories
|
if !f.IsDir() && strings.HasPrefix(f.Name(), base) && strings.HasSuffix(f.Name(), ".vtt") {
|
||||||
// should cover all instances of subtitle distributions
|
i.Subtitles = append(i.Subtitles, path.Join(parentDir, f.Name()))
|
||||||
// like tv-shows with multiple episodes in single dir
|
|
||||||
if f.IsDir() && reSubDirs.MatchString(f.Name()) {
|
|
||||||
subsDir := path.Join(parentDir, f.Name())
|
|
||||||
i.loadSubtitles(subsDir, base, true)
|
|
||||||
} else if isSubtitleMatch(f, base) {
|
|
||||||
i.addSubtitle(path.Join(parentDir, f.Name()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *FileInfo) loadSubtitles(subsPath, baseName string, recursive bool) {
|
|
||||||
dir, err := afero.ReadDir(i.Fs, subsPath)
|
|
||||||
if err == nil {
|
|
||||||
for _, f := range dir {
|
|
||||||
if isSubtitleMatch(f, "") {
|
|
||||||
i.addSubtitle(path.Join(subsPath, f.Name()))
|
|
||||||
} else if f.IsDir() && recursive && strings.HasPrefix(f.Name(), baseName) {
|
|
||||||
subsDir := path.Join(subsPath, f.Name())
|
|
||||||
i.loadSubtitles(subsDir, baseName, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsSupportedSubtitle(fileName string) bool {
|
|
||||||
return reSubExts.MatchString(fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSubtitleMatch(f fs.FileInfo, baseName string) bool {
|
|
||||||
return !f.IsDir() && strings.HasPrefix(f.Name(), baseName) &&
|
|
||||||
IsSupportedSubtitle(f.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *FileInfo) addSubtitle(fPath string) {
|
|
||||||
i.Subtitles = append(i.Subtitles, fPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
||||||
@ -408,7 +306,7 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
isSymlink, isInvalidLink := false, false
|
isSymlink := false
|
||||||
if IsSymlink(f.Mode()) {
|
if IsSymlink(f.Mode()) {
|
||||||
isSymlink = true
|
isSymlink = true
|
||||||
// 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,
|
||||||
@ -416,8 +314,6 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
|||||||
info, err := i.Fs.Stat(fPath)
|
info, err := i.Fs.Stat(fPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f = info
|
f = info
|
||||||
} else {
|
|
||||||
isInvalidLink = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,16 +327,6 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
|||||||
IsSymlink: isSymlink,
|
IsSymlink: isSymlink,
|
||||||
Extension: filepath.Ext(name),
|
Extension: filepath.Ext(name),
|
||||||
Path: fPath,
|
Path: fPath,
|
||||||
currentDir: dir,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !file.IsDir && strings.HasPrefix(mime.TypeByExtension(file.Extension), "image/") {
|
|
||||||
resolution, err := calculateImageResolution(file.Fs, file.Path)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error calculating resolution for image %s: %v", file.Path, err)
|
|
||||||
} else {
|
|
||||||
file.Resolution = resolution
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
@ -448,15 +334,11 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
|||||||
} else {
|
} else {
|
||||||
listing.NumFiles++
|
listing.NumFiles++
|
||||||
|
|
||||||
if isInvalidLink {
|
|
||||||
file.Type = "invalid_link"
|
|
||||||
} else {
|
|
||||||
err := file.detectType(true, false, readHeader)
|
err := file.detectType(true, false, readHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
listing.Items = append(listing.Items, file)
|
listing.Items = append(listing.Items, file)
|
||||||
}
|
}
|
||||||
|
@ -16,10 +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
|
//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":
|
||||||
|
609
files/mime.go
609
files/mime.go
@ -1,609 +0,0 @@
|
|||||||
package files
|
|
||||||
|
|
||||||
// This file contains code primarily sourced from::
|
|
||||||
// github.com/kataras/iris
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mime"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ContentBinaryHeaderValue header value for binary data.
|
|
||||||
ContentBinaryHeaderValue = "application/octet-stream"
|
|
||||||
// ContentWebassemblyHeaderValue header value for web assembly files.
|
|
||||||
ContentWebassemblyHeaderValue = "application/wasm"
|
|
||||||
// ContentHTMLHeaderValue is the string of text/html response header's content type value.
|
|
||||||
ContentHTMLHeaderValue = "text/html"
|
|
||||||
// ContentJSONHeaderValue header value for JSON data.
|
|
||||||
ContentJSONHeaderValue = "application/json"
|
|
||||||
// ContentJSONProblemHeaderValue header value for JSON API problem error.
|
|
||||||
// Read more at: https://tools.ietf.org/html/rfc7807
|
|
||||||
ContentJSONProblemHeaderValue = "application/problem+json"
|
|
||||||
// ContentXMLProblemHeaderValue header value for XML API problem error.
|
|
||||||
// Read more at: https://tools.ietf.org/html/rfc7807
|
|
||||||
ContentXMLProblemHeaderValue = "application/problem+xml"
|
|
||||||
// ContentJavascriptHeaderValue header value for JSONP & Javascript data.
|
|
||||||
ContentJavascriptHeaderValue = "text/javascript"
|
|
||||||
// ContentTextHeaderValue header value for Text data.
|
|
||||||
ContentTextHeaderValue = "text/plain"
|
|
||||||
// ContentXMLHeaderValue header value for XML data.
|
|
||||||
ContentXMLHeaderValue = "text/xml"
|
|
||||||
// ContentXMLUnreadableHeaderValue obsolete header value for XML.
|
|
||||||
ContentXMLUnreadableHeaderValue = "application/xml"
|
|
||||||
// ContentMarkdownHeaderValue custom key/content type, the real is the text/html.
|
|
||||||
ContentMarkdownHeaderValue = "text/markdown"
|
|
||||||
// ContentYAMLHeaderValue header value for YAML data.
|
|
||||||
ContentYAMLHeaderValue = "application/x-yaml"
|
|
||||||
// ContentYAMLTextHeaderValue header value for YAML plain text.
|
|
||||||
ContentYAMLTextHeaderValue = "text/yaml"
|
|
||||||
// ContentProtobufHeaderValue header value for Protobuf messages data.
|
|
||||||
ContentProtobufHeaderValue = "application/x-protobuf"
|
|
||||||
// ContentMsgPackHeaderValue header value for MsgPack data.
|
|
||||||
ContentMsgPackHeaderValue = "application/msgpack"
|
|
||||||
// ContentMsgPack2HeaderValue alternative header value for MsgPack data.
|
|
||||||
ContentMsgPack2HeaderValue = "application/x-msgpack"
|
|
||||||
// ContentFormHeaderValue header value for post form data.
|
|
||||||
ContentFormHeaderValue = "application/x-www-form-urlencoded"
|
|
||||||
// ContentFormMultipartHeaderValue header value for post multipart form data.
|
|
||||||
ContentFormMultipartHeaderValue = "multipart/form-data"
|
|
||||||
// ContentMultipartRelatedHeaderValue header value for multipart related data.
|
|
||||||
ContentMultipartRelatedHeaderValue = "multipart/related"
|
|
||||||
// ContentGRPCHeaderValue Content-Type header value for gRPC.
|
|
||||||
ContentGRPCHeaderValue = "application/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
var types = map[string]string{
|
|
||||||
".3dm": "x-world/x-3dmf",
|
|
||||||
".3dmf": "x-world/x-3dmf",
|
|
||||||
".7z": "application/x-7z-compressed",
|
|
||||||
".a": "application/octet-stream",
|
|
||||||
".aab": "application/x-authorware-bin",
|
|
||||||
".aam": "application/x-authorware-map",
|
|
||||||
".aas": "application/x-authorware-seg",
|
|
||||||
".abc": "text/vndabc",
|
|
||||||
".ace": "application/x-ace-compressed",
|
|
||||||
".acgi": "text/html",
|
|
||||||
".afl": "video/animaflex",
|
|
||||||
".ai": "application/postscript",
|
|
||||||
".aif": "audio/aiff",
|
|
||||||
".aifc": "audio/aiff",
|
|
||||||
".aiff": "audio/aiff",
|
|
||||||
".aim": "application/x-aim",
|
|
||||||
".aip": "text/x-audiosoft-intra",
|
|
||||||
".alz": "application/x-alz-compressed",
|
|
||||||
".ani": "application/x-navi-animation",
|
|
||||||
".aos": "application/x-nokia-9000-communicator-add-on-software",
|
|
||||||
".aps": "application/mime",
|
|
||||||
".apk": "application/vnd.android.package-archive",
|
|
||||||
".arc": "application/x-arc-compressed",
|
|
||||||
".arj": "application/arj",
|
|
||||||
".art": "image/x-jg",
|
|
||||||
".asf": "video/x-ms-asf",
|
|
||||||
".asm": "text/x-asm",
|
|
||||||
".asp": "text/asp",
|
|
||||||
".asx": "application/x-mplayer2",
|
|
||||||
".au": "audio/basic",
|
|
||||||
".avi": "video/x-msvideo",
|
|
||||||
".avs": "video/avs-video",
|
|
||||||
".bcpio": "application/x-bcpio",
|
|
||||||
".bin": "application/mac-binary",
|
|
||||||
".bmp": "image/bmp",
|
|
||||||
".boo": "application/book",
|
|
||||||
".book": "application/book",
|
|
||||||
".boz": "application/x-bzip2",
|
|
||||||
".bsh": "application/x-bsh",
|
|
||||||
".bz2": "application/x-bzip2",
|
|
||||||
".bz": "application/x-bzip",
|
|
||||||
".c++": ContentTextHeaderValue,
|
|
||||||
".c": "text/x-c",
|
|
||||||
".cab": "application/vnd.ms-cab-compressed",
|
|
||||||
".cat": "application/vndms-pkiseccat",
|
|
||||||
".cc": "text/x-c",
|
|
||||||
".ccad": "application/clariscad",
|
|
||||||
".cco": "application/x-cocoa",
|
|
||||||
".cdf": "application/cdf",
|
|
||||||
".cer": "application/pkix-cert",
|
|
||||||
".cha": "application/x-chat",
|
|
||||||
".chat": "application/x-chat",
|
|
||||||
".chrt": "application/vnd.kde.kchart",
|
|
||||||
".class": "application/java",
|
|
||||||
".com": ContentTextHeaderValue,
|
|
||||||
".conf": ContentTextHeaderValue,
|
|
||||||
".cpio": "application/x-cpio",
|
|
||||||
".cpp": "text/x-c",
|
|
||||||
".cpt": "application/mac-compactpro",
|
|
||||||
".crl": "application/pkcs-crl",
|
|
||||||
".crt": "application/pkix-cert",
|
|
||||||
".crx": "application/x-chrome-extension",
|
|
||||||
".csh": "text/x-scriptcsh",
|
|
||||||
".css": "text/css",
|
|
||||||
".csv": "text/csv",
|
|
||||||
".cxx": ContentTextHeaderValue,
|
|
||||||
".dar": "application/x-dar",
|
|
||||||
".dcr": "application/x-director",
|
|
||||||
".deb": "application/x-debian-package",
|
|
||||||
".deepv": "application/x-deepv",
|
|
||||||
".def": ContentTextHeaderValue,
|
|
||||||
".der": "application/x-x509-ca-cert",
|
|
||||||
".dif": "video/x-dv",
|
|
||||||
".dir": "application/x-director",
|
|
||||||
".divx": "video/divx",
|
|
||||||
".dl": "video/dl",
|
|
||||||
".dmg": "application/x-apple-diskimage",
|
|
||||||
".doc": "application/msword",
|
|
||||||
".dot": "application/msword",
|
|
||||||
".dp": "application/commonground",
|
|
||||||
".drw": "application/drafting",
|
|
||||||
".dump": "application/octet-stream",
|
|
||||||
".dv": "video/x-dv",
|
|
||||||
".dvi": "application/x-dvi",
|
|
||||||
".dwf": "drawing/x-dwf=(old)",
|
|
||||||
".dwg": "application/acad",
|
|
||||||
".dxf": "application/dxf",
|
|
||||||
".dxr": "application/x-director",
|
|
||||||
".el": "text/x-scriptelisp",
|
|
||||||
".elc": "application/x-bytecodeelisp=(compiled=elisp)",
|
|
||||||
".eml": "message/rfc822",
|
|
||||||
".env": "application/x-envoy",
|
|
||||||
".eps": "application/postscript",
|
|
||||||
".es": "application/x-esrehber",
|
|
||||||
".etx": "text/x-setext",
|
|
||||||
".evy": "application/envoy",
|
|
||||||
".exe": "application/octet-stream",
|
|
||||||
".f77": "text/x-fortran",
|
|
||||||
".f90": "text/x-fortran",
|
|
||||||
".f": "text/x-fortran",
|
|
||||||
".fdf": "application/vndfdf",
|
|
||||||
".fif": "application/fractals",
|
|
||||||
".fli": "video/fli",
|
|
||||||
".flo": "image/florian",
|
|
||||||
".flv": "video/x-flv",
|
|
||||||
".flx": "text/vndfmiflexstor",
|
|
||||||
".fmf": "video/x-atomic3d-feature",
|
|
||||||
".for": "text/x-fortran",
|
|
||||||
".fpx": "image/vndfpx",
|
|
||||||
".frl": "application/freeloader",
|
|
||||||
".funk": "audio/make",
|
|
||||||
".g3": "image/g3fax",
|
|
||||||
".g": ContentTextHeaderValue,
|
|
||||||
".gif": "image/gif",
|
|
||||||
".gl": "video/gl",
|
|
||||||
".gsd": "audio/x-gsm",
|
|
||||||
".gsm": "audio/x-gsm",
|
|
||||||
".gsp": "application/x-gsp",
|
|
||||||
".gss": "application/x-gss",
|
|
||||||
".gtar": "application/x-gtar",
|
|
||||||
".gz": "application/x-compressed",
|
|
||||||
".gzip": "application/x-gzip",
|
|
||||||
".h": "text/x-h",
|
|
||||||
".hdf": "application/x-hdf",
|
|
||||||
".help": "application/x-helpfile",
|
|
||||||
".hgl": "application/vndhp-hpgl",
|
|
||||||
".hh": "text/x-h",
|
|
||||||
".hlb": "text/x-script",
|
|
||||||
".hlp": "application/hlp",
|
|
||||||
".hpg": "application/vndhp-hpgl",
|
|
||||||
".hpgl": "application/vndhp-hpgl",
|
|
||||||
".hqx": "application/binhex",
|
|
||||||
".hta": "application/hta",
|
|
||||||
".htc": "text/x-component",
|
|
||||||
".htm": "text/html",
|
|
||||||
".html": "text/html",
|
|
||||||
".htmls": "text/html",
|
|
||||||
".htt": "text/webviewhtml",
|
|
||||||
".htx": "text/html",
|
|
||||||
".ice": "x-conference/x-cooltalk",
|
|
||||||
".ico": "image/x-icon",
|
|
||||||
".ics": "text/calendar",
|
|
||||||
".icz": "text/calendar",
|
|
||||||
".idc": ContentTextHeaderValue,
|
|
||||||
".ief": "image/ief",
|
|
||||||
".iefs": "image/ief",
|
|
||||||
".iges": "application/iges",
|
|
||||||
".igs": "application/iges",
|
|
||||||
".ima": "application/x-ima",
|
|
||||||
".imap": "application/x-httpd-imap",
|
|
||||||
".inf": "application/inf",
|
|
||||||
".ins": "application/x-internett-signup",
|
|
||||||
".ip": "application/x-ip2",
|
|
||||||
".isu": "video/x-isvideo",
|
|
||||||
".it": "audio/it",
|
|
||||||
".iv": "application/x-inventor",
|
|
||||||
".ivr": "i-world/i-vrml",
|
|
||||||
".ivy": "application/x-livescreen",
|
|
||||||
".jam": "audio/x-jam",
|
|
||||||
".jav": "text/x-java-source",
|
|
||||||
".java": "text/x-java-source",
|
|
||||||
".jcm": "application/x-java-commerce",
|
|
||||||
".jfif-tbnl": "image/jpeg",
|
|
||||||
".jfif": "image/jpeg",
|
|
||||||
".jnlp": "application/x-java-jnlp-file",
|
|
||||||
".jpe": "image/jpeg",
|
|
||||||
".jpeg": "image/jpeg",
|
|
||||||
".jpg": "image/jpeg",
|
|
||||||
".jps": "image/x-jps",
|
|
||||||
".js": ContentJavascriptHeaderValue,
|
|
||||||
".mjs": ContentJavascriptHeaderValue,
|
|
||||||
".json": ContentJSONHeaderValue,
|
|
||||||
".vue": ContentJavascriptHeaderValue,
|
|
||||||
".jut": "image/jutvision",
|
|
||||||
".kar": "audio/midi",
|
|
||||||
".karbon": "application/vnd.kde.karbon",
|
|
||||||
".kfo": "application/vnd.kde.kformula",
|
|
||||||
".flw": "application/vnd.kde.kivio",
|
|
||||||
".kml": "application/vnd.google-earth.kml+xml",
|
|
||||||
".kmz": "application/vnd.google-earth.kmz",
|
|
||||||
".kon": "application/vnd.kde.kontour",
|
|
||||||
".kpr": "application/vnd.kde.kpresenter",
|
|
||||||
".kpt": "application/vnd.kde.kpresenter",
|
|
||||||
".ksp": "application/vnd.kde.kspread",
|
|
||||||
".kwd": "application/vnd.kde.kword",
|
|
||||||
".kwt": "application/vnd.kde.kword",
|
|
||||||
".ksh": "text/x-scriptksh",
|
|
||||||
".la": "audio/nspaudio",
|
|
||||||
".lam": "audio/x-liveaudio",
|
|
||||||
".latex": "application/x-latex",
|
|
||||||
".lha": "application/lha",
|
|
||||||
".lhx": "application/octet-stream",
|
|
||||||
".list": ContentTextHeaderValue,
|
|
||||||
".lma": "audio/nspaudio",
|
|
||||||
".log": ContentTextHeaderValue,
|
|
||||||
".lsp": "text/x-scriptlisp",
|
|
||||||
".lst": ContentTextHeaderValue,
|
|
||||||
".lsx": "text/x-la-asf",
|
|
||||||
".ltx": "application/x-latex",
|
|
||||||
".lzh": "application/octet-stream",
|
|
||||||
".lzx": "application/lzx",
|
|
||||||
".m1v": "video/mpeg",
|
|
||||||
".m2a": "audio/mpeg",
|
|
||||||
".m2v": "video/mpeg",
|
|
||||||
".m3u": "audio/x-mpegurl",
|
|
||||||
".m": "text/x-m",
|
|
||||||
".man": "application/x-troff-man",
|
|
||||||
".manifest": "text/cache-manifest",
|
|
||||||
".map": "application/x-navimap",
|
|
||||||
".mar": ContentTextHeaderValue,
|
|
||||||
".mbd": "application/mbedlet",
|
|
||||||
".mc$": "application/x-magic-cap-package-10",
|
|
||||||
".mcd": "application/mcad",
|
|
||||||
".mcf": "text/mcf",
|
|
||||||
".mcp": "application/netmc",
|
|
||||||
".me": "application/x-troff-me",
|
|
||||||
".mht": "message/rfc822",
|
|
||||||
".mhtml": "message/rfc822",
|
|
||||||
".mid": "application/x-midi",
|
|
||||||
".midi": "application/x-midi",
|
|
||||||
".mif": "application/x-frame",
|
|
||||||
".mime": "message/rfc822",
|
|
||||||
".mjf": "audio/x-vndaudioexplosionmjuicemediafile",
|
|
||||||
".mjpg": "video/x-motion-jpeg",
|
|
||||||
".mm": "application/base64",
|
|
||||||
".mme": "application/base64",
|
|
||||||
".mod": "audio/mod",
|
|
||||||
".moov": "video/quicktime",
|
|
||||||
".mov": "video/quicktime",
|
|
||||||
".movie": "video/x-sgi-movie",
|
|
||||||
".mp2": "audio/mpeg",
|
|
||||||
".mp3": "audio/mpeg",
|
|
||||||
".mp4": "video/mp4",
|
|
||||||
".mpa": "audio/mpeg",
|
|
||||||
".mpc": "application/x-project",
|
|
||||||
".mpe": "video/mpeg",
|
|
||||||
".mpeg": "video/mpeg",
|
|
||||||
".mpg": "video/mpeg",
|
|
||||||
".mpga": "audio/mpeg",
|
|
||||||
".mpp": "application/vndms-project",
|
|
||||||
".mpt": "application/x-project",
|
|
||||||
".mpv": "application/x-project",
|
|
||||||
".mpx": "application/x-project",
|
|
||||||
".mrc": "application/marc",
|
|
||||||
".ms": "application/x-troff-ms",
|
|
||||||
".mv": "video/x-sgi-movie",
|
|
||||||
".my": "audio/make",
|
|
||||||
".mzz": "application/x-vndaudioexplosionmzz",
|
|
||||||
".nap": "image/naplps",
|
|
||||||
".naplps": "image/naplps",
|
|
||||||
".nc": "application/x-netcdf",
|
|
||||||
".ncm": "application/vndnokiaconfiguration-message",
|
|
||||||
".nif": "image/x-niff",
|
|
||||||
".niff": "image/x-niff",
|
|
||||||
".nix": "application/x-mix-transfer",
|
|
||||||
".nsc": "application/x-conference",
|
|
||||||
".nvd": "application/x-navidoc",
|
|
||||||
".o": "application/octet-stream",
|
|
||||||
".oda": "application/oda",
|
|
||||||
".odb": "application/vnd.oasis.opendocument.database",
|
|
||||||
".odc": "application/vnd.oasis.opendocument.chart",
|
|
||||||
".odf": "application/vnd.oasis.opendocument.formula",
|
|
||||||
".odg": "application/vnd.oasis.opendocument.graphics",
|
|
||||||
".odi": "application/vnd.oasis.opendocument.image",
|
|
||||||
".odm": "application/vnd.oasis.opendocument.text-master",
|
|
||||||
".odp": "application/vnd.oasis.opendocument.presentation",
|
|
||||||
".ods": "application/vnd.oasis.opendocument.spreadsheet",
|
|
||||||
".odt": "application/vnd.oasis.opendocument.text",
|
|
||||||
".oga": "audio/ogg",
|
|
||||||
".ogg": "audio/ogg",
|
|
||||||
".ogv": "video/ogg",
|
|
||||||
".omc": "application/x-omc",
|
|
||||||
".omcd": "application/x-omcdatamaker",
|
|
||||||
".omcr": "application/x-omcregerator",
|
|
||||||
".otc": "application/vnd.oasis.opendocument.chart-template",
|
|
||||||
".otf": "application/vnd.oasis.opendocument.formula-template",
|
|
||||||
".otg": "application/vnd.oasis.opendocument.graphics-template",
|
|
||||||
".oth": "application/vnd.oasis.opendocument.text-web",
|
|
||||||
".oti": "application/vnd.oasis.opendocument.image-template",
|
|
||||||
".otm": "application/vnd.oasis.opendocument.text-master",
|
|
||||||
".otp": "application/vnd.oasis.opendocument.presentation-template",
|
|
||||||
".ots": "application/vnd.oasis.opendocument.spreadsheet-template",
|
|
||||||
".ott": "application/vnd.oasis.opendocument.text-template",
|
|
||||||
".p10": "application/pkcs10",
|
|
||||||
".p12": "application/pkcs-12",
|
|
||||||
".p7a": "application/x-pkcs7-signature",
|
|
||||||
".p7c": "application/pkcs7-mime",
|
|
||||||
".p7m": "application/pkcs7-mime",
|
|
||||||
".p7r": "application/x-pkcs7-certreqresp",
|
|
||||||
".p7s": "application/pkcs7-signature",
|
|
||||||
".p": "text/x-pascal",
|
|
||||||
".part": "application/pro_eng",
|
|
||||||
".pas": "text/pascal",
|
|
||||||
".pbm": "image/x-portable-bitmap",
|
|
||||||
".pcl": "application/vndhp-pcl",
|
|
||||||
".pct": "image/x-pict",
|
|
||||||
".pcx": "image/x-pcx",
|
|
||||||
".pdb": "chemical/x-pdb",
|
|
||||||
".pdf": "application/pdf",
|
|
||||||
".pfunk": "audio/make",
|
|
||||||
".pgm": "image/x-portable-graymap",
|
|
||||||
".pic": "image/pict",
|
|
||||||
".pict": "image/pict",
|
|
||||||
".pkg": "application/x-newton-compatible-pkg",
|
|
||||||
".pko": "application/vndms-pkipko",
|
|
||||||
".pl": "text/x-scriptperl",
|
|
||||||
".plx": "application/x-pixclscript",
|
|
||||||
".pm4": "application/x-pagemaker",
|
|
||||||
".pm5": "application/x-pagemaker",
|
|
||||||
".pm": "text/x-scriptperl-module",
|
|
||||||
".png": "image/png",
|
|
||||||
".pnm": "application/x-portable-anymap",
|
|
||||||
".pot": "application/mspowerpoint",
|
|
||||||
".pov": "model/x-pov",
|
|
||||||
".ppa": "application/vndms-powerpoint",
|
|
||||||
".ppm": "image/x-portable-pixmap",
|
|
||||||
".pps": "application/mspowerpoint",
|
|
||||||
".ppt": "application/mspowerpoint",
|
|
||||||
".ppz": "application/mspowerpoint",
|
|
||||||
".pre": "application/x-freelance",
|
|
||||||
".prt": "application/pro_eng",
|
|
||||||
".ps": "application/postscript",
|
|
||||||
".psd": "application/octet-stream",
|
|
||||||
".pvu": "paleovu/x-pv",
|
|
||||||
".pwz": "application/vndms-powerpoint",
|
|
||||||
".py": "text/x-scriptphyton",
|
|
||||||
".pyc": "application/x-bytecodepython",
|
|
||||||
".qcp": "audio/vndqcelp",
|
|
||||||
".qd3": "x-world/x-3dmf",
|
|
||||||
".qd3d": "x-world/x-3dmf",
|
|
||||||
".qif": "image/x-quicktime",
|
|
||||||
".qt": "video/quicktime",
|
|
||||||
".qtc": "video/x-qtc",
|
|
||||||
".qti": "image/x-quicktime",
|
|
||||||
".qtif": "image/x-quicktime",
|
|
||||||
".ra": "audio/x-pn-realaudio",
|
|
||||||
".ram": "audio/x-pn-realaudio",
|
|
||||||
".rar": "application/x-rar-compressed",
|
|
||||||
".ras": "application/x-cmu-raster",
|
|
||||||
".rast": "image/cmu-raster",
|
|
||||||
".rexx": "text/x-scriptrexx",
|
|
||||||
".rf": "image/vndrn-realflash",
|
|
||||||
".rgb": "image/x-rgb",
|
|
||||||
".rm": "application/vndrn-realmedia",
|
|
||||||
".rmi": "audio/mid",
|
|
||||||
".rmm": "audio/x-pn-realaudio",
|
|
||||||
".rmp": "audio/x-pn-realaudio",
|
|
||||||
".rng": "application/ringing-tones",
|
|
||||||
".rnx": "application/vndrn-realplayer",
|
|
||||||
".roff": "application/x-troff",
|
|
||||||
".rp": "image/vndrn-realpix",
|
|
||||||
".rpm": "audio/x-pn-realaudio-plugin",
|
|
||||||
".rt": "text/vndrn-realtext",
|
|
||||||
".rtf": "text/richtext",
|
|
||||||
".rtx": "text/richtext",
|
|
||||||
".rv": "video/vndrn-realvideo",
|
|
||||||
".s": "text/x-asm",
|
|
||||||
".s3m": "audio/s3m",
|
|
||||||
".s7z": "application/x-7z-compressed",
|
|
||||||
".saveme": "application/octet-stream",
|
|
||||||
".sbk": "application/x-tbook",
|
|
||||||
".scm": "text/x-scriptscheme",
|
|
||||||
".sdml": ContentTextHeaderValue,
|
|
||||||
".sdp": "application/sdp",
|
|
||||||
".sdr": "application/sounder",
|
|
||||||
".sea": "application/sea",
|
|
||||||
".set": "application/set",
|
|
||||||
".sgm": "text/x-sgml",
|
|
||||||
".sgml": "text/x-sgml",
|
|
||||||
".sh": "text/x-scriptsh",
|
|
||||||
".shar": "application/x-bsh",
|
|
||||||
".shtml": "text/x-server-parsed-html",
|
|
||||||
".sid": "audio/x-psid",
|
|
||||||
".skd": "application/x-koan",
|
|
||||||
".skm": "application/x-koan",
|
|
||||||
".skp": "application/x-koan",
|
|
||||||
".skt": "application/x-koan",
|
|
||||||
".sit": "application/x-stuffit",
|
|
||||||
".sitx": "application/x-stuffitx",
|
|
||||||
".sl": "application/x-seelogo",
|
|
||||||
".smi": "application/smil",
|
|
||||||
".smil": "application/smil",
|
|
||||||
".snd": "audio/basic",
|
|
||||||
".sol": "application/solids",
|
|
||||||
".spc": "text/x-speech",
|
|
||||||
".spl": "application/futuresplash",
|
|
||||||
".spr": "application/x-sprite",
|
|
||||||
".sprite": "application/x-sprite",
|
|
||||||
".spx": "audio/ogg",
|
|
||||||
".src": "application/x-wais-source",
|
|
||||||
".ssi": "text/x-server-parsed-html",
|
|
||||||
".ssm": "application/streamingmedia",
|
|
||||||
".sst": "application/vndms-pkicertstore",
|
|
||||||
".step": "application/step",
|
|
||||||
".stl": "application/sla",
|
|
||||||
".stp": "application/step",
|
|
||||||
".sv4cpio": "application/x-sv4cpio",
|
|
||||||
".sv4crc": "application/x-sv4crc",
|
|
||||||
".svf": "image/vnddwg",
|
|
||||||
".svg": "image/svg+xml",
|
|
||||||
".svr": "application/x-world",
|
|
||||||
".swf": "application/x-shockwave-flash",
|
|
||||||
".t": "application/x-troff",
|
|
||||||
".talk": "text/x-speech",
|
|
||||||
".tar": "application/x-tar",
|
|
||||||
".tbk": "application/toolbook",
|
|
||||||
".tcl": "text/x-scripttcl",
|
|
||||||
".tcsh": "text/x-scripttcsh",
|
|
||||||
".tex": "application/x-tex",
|
|
||||||
".texi": "application/x-texinfo",
|
|
||||||
".texinfo": "application/x-texinfo",
|
|
||||||
".text": ContentTextHeaderValue,
|
|
||||||
".tgz": "application/gnutar",
|
|
||||||
".tif": "image/tiff",
|
|
||||||
".tiff": "image/tiff",
|
|
||||||
".tr": "application/x-troff",
|
|
||||||
".tsi": "audio/tsp-audio",
|
|
||||||
".tsp": "application/dsptype",
|
|
||||||
".tsv": "text/tab-separated-values",
|
|
||||||
".turbot": "image/florian",
|
|
||||||
".txt": ContentTextHeaderValue,
|
|
||||||
".uil": "text/x-uil",
|
|
||||||
".uni": "text/uri-list",
|
|
||||||
".unis": "text/uri-list",
|
|
||||||
".unv": "application/i-deas",
|
|
||||||
".uri": "text/uri-list",
|
|
||||||
".uris": "text/uri-list",
|
|
||||||
".ustar": "application/x-ustar",
|
|
||||||
".uu": "text/x-uuencode",
|
|
||||||
".uue": "text/x-uuencode",
|
|
||||||
".vcd": "application/x-cdlink",
|
|
||||||
".vcf": "text/x-vcard",
|
|
||||||
".vcard": "text/x-vcard",
|
|
||||||
".vcs": "text/x-vcalendar",
|
|
||||||
".vda": "application/vda",
|
|
||||||
".vdo": "video/vdo",
|
|
||||||
".vew": "application/groupwise",
|
|
||||||
".viv": "video/vivo",
|
|
||||||
".vivo": "video/vivo",
|
|
||||||
".vmd": "application/vocaltec-media-desc",
|
|
||||||
".vmf": "application/vocaltec-media-file",
|
|
||||||
".voc": "audio/voc",
|
|
||||||
".vos": "video/vosaic",
|
|
||||||
".vox": "audio/voxware",
|
|
||||||
".vqe": "audio/x-twinvq-plugin",
|
|
||||||
".vqf": "audio/x-twinvq",
|
|
||||||
".vql": "audio/x-twinvq-plugin",
|
|
||||||
".vrml": "application/x-vrml",
|
|
||||||
".vrt": "x-world/x-vrt",
|
|
||||||
".vsd": "application/x-visio",
|
|
||||||
".vst": "application/x-visio",
|
|
||||||
".vsw": "application/x-visio",
|
|
||||||
".w60": "application/wordperfect60",
|
|
||||||
".w61": "application/wordperfect61",
|
|
||||||
".w6w": "application/msword",
|
|
||||||
".wav": "audio/wav",
|
|
||||||
".wb1": "application/x-qpro",
|
|
||||||
".wbmp": "image/vnd.wap.wbmp",
|
|
||||||
".web": "application/vndxara",
|
|
||||||
".wiz": "application/msword",
|
|
||||||
".wk1": "application/x-123",
|
|
||||||
".wmf": "windows/metafile",
|
|
||||||
".wml": "text/vnd.wap.wml",
|
|
||||||
".wmlc": "application/vnd.wap.wmlc",
|
|
||||||
".wmls": "text/vnd.wap.wmlscript",
|
|
||||||
".wmlsc": "application/vnd.wap.wmlscriptc",
|
|
||||||
".word": "application/msword",
|
|
||||||
".wp5": "application/wordperfect",
|
|
||||||
".wp6": "application/wordperfect",
|
|
||||||
".wp": "application/wordperfect",
|
|
||||||
".wpd": "application/wordperfect",
|
|
||||||
".wq1": "application/x-lotus",
|
|
||||||
".wri": "application/mswrite",
|
|
||||||
".wrl": "application/x-world",
|
|
||||||
".wrz": "model/vrml",
|
|
||||||
".wsc": "text/scriplet",
|
|
||||||
".wsrc": "application/x-wais-source",
|
|
||||||
".wtk": "application/x-wintalk",
|
|
||||||
".x-png": "image/png",
|
|
||||||
".xbm": "image/x-xbitmap",
|
|
||||||
".xdr": "video/x-amt-demorun",
|
|
||||||
".xgz": "xgl/drawing",
|
|
||||||
".xif": "image/vndxiff",
|
|
||||||
".xl": "application/excel",
|
|
||||||
".xla": "application/excel",
|
|
||||||
".xlb": "application/excel",
|
|
||||||
".xlc": "application/excel",
|
|
||||||
".xld": "application/excel",
|
|
||||||
".xlk": "application/excel",
|
|
||||||
".xll": "application/excel",
|
|
||||||
".xlm": "application/excel",
|
|
||||||
".xls": "application/excel",
|
|
||||||
".xlt": "application/excel",
|
|
||||||
".xlv": "application/excel",
|
|
||||||
".xlw": "application/excel",
|
|
||||||
".xm": "audio/xm",
|
|
||||||
".xml": ContentXMLHeaderValue,
|
|
||||||
".xmz": "xgl/movie",
|
|
||||||
".xpix": "application/x-vndls-xpix",
|
|
||||||
".xpm": "image/x-xpixmap",
|
|
||||||
".xsr": "video/x-amt-showrun",
|
|
||||||
".xwd": "image/x-xwd",
|
|
||||||
".xyz": "chemical/x-pdb",
|
|
||||||
".z": "application/x-compress",
|
|
||||||
".zip": "application/zip",
|
|
||||||
".zoo": "application/octet-stream",
|
|
||||||
".zsh": "text/x-scriptzsh",
|
|
||||||
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
||||||
".docm": "application/vnd.ms-word.document.macroEnabled.12",
|
|
||||||
".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
|
||||||
".dotm": "application/vnd.ms-word.template.macroEnabled.12",
|
|
||||||
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12",
|
|
||||||
".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
|
||||||
".xltm": "application/vnd.ms-excel.template.macroEnabled.12",
|
|
||||||
".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
|
||||||
".xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
|
|
||||||
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
||||||
".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
|
||||||
".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
|
||||||
".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
|
||||||
".potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
|
|
||||||
".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12",
|
|
||||||
".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
|
||||||
".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
|
|
||||||
".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12",
|
|
||||||
".thmx": "application/vnd.ms-officetheme",
|
|
||||||
".onetoc": "application/onenote",
|
|
||||||
".onetoc2": "application/onenote",
|
|
||||||
".onetmp": "application/onenote",
|
|
||||||
".onepkg": "application/onenote",
|
|
||||||
".xpi": "application/x-xpinstall",
|
|
||||||
".wasm": "application/wasm",
|
|
||||||
".m4a": "audio/mp4",
|
|
||||||
".flac": "audio/x-flac",
|
|
||||||
".amr": "audio/amr",
|
|
||||||
".aac": "audio/aac",
|
|
||||||
".opus": "video/ogg",
|
|
||||||
".m4v": "video/mp4",
|
|
||||||
".mkv": "video/x-matroska",
|
|
||||||
".caf": "audio/x-caf",
|
|
||||||
".m3u8": "application/x-mpegURL",
|
|
||||||
".mpd": "application/dash+xml",
|
|
||||||
".webp": "image/webp",
|
|
||||||
".epub": "application/epub+zip",
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gochecknoinits
|
|
||||||
func init() {
|
|
||||||
for ext, typ := range types {
|
|
||||||
// skip errors
|
|
||||||
_ = mime.AddExtensionType(ext, typ)
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,8 +7,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MoveFile moves file from src to dst.
|
// MoveFile moves file from src to dst.
|
||||||
@ -19,12 +17,12 @@ func MoveFile(fs afero.Fs, src, dst string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// fallback
|
// fallback
|
||||||
err := Copy(fs, src, dst)
|
err := CopyFile(fs, src, dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = fs.Remove(dst)
|
_ = fs.Remove(dst)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fs.RemoveAll(src); err != nil {
|
if err := fs.Remove(src); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -42,13 +40,13 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
|||||||
|
|
||||||
// Makes the directory needed to create the dst
|
// Makes the directory needed to create the dst
|
||||||
// file.
|
// file.
|
||||||
err = fs.MkdirAll(filepath.Dir(dest), files.PermDir)
|
err = fs.MkdirAll(filepath.Dir(dest), 0666) //nolint:gomnd
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the destination file.
|
// Create the destination file.
|
||||||
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, files.PermFile)
|
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) //nolint:gomnd
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -98,7 +96,7 @@ func CommonPrefix(sep byte, paths ...string) string {
|
|||||||
// (e.g. /home/user1, /home/user1/foo, /home/user1/bar).
|
// (e.g. /home/user1, /home/user1/foo, /home/user1/bar).
|
||||||
// path.Clean will have cleaned off trailing / separators with
|
// path.Clean will have cleaned off trailing / separators with
|
||||||
// the exception of the root directory, "/" (in which case we
|
// the exception of the root directory, "/" (in which case we
|
||||||
// make it "//", but this will get fixed up to "/" below).
|
// make it "//", but this will get fixed up to "/" bellow).
|
||||||
c = append(c, sep)
|
c = append(c, sep)
|
||||||
|
|
||||||
// Ignore the first path since it's already in c
|
// Ignore the first path since it's already in c
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
# Ignore artifacts:
|
|
||||||
dist
|
|
||||||
pnpm-lock.yaml
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"trailingComma": "es5"
|
|
||||||
}
|
|
3
frontend/babel.config.js
Normal file
3
frontend/babel.config.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/app"],
|
||||||
|
};
|
4
frontend/dist/.gitignore
vendored
Normal file
4
frontend/dist/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
0
frontend/dist/.gitkeep
vendored
0
frontend/dist/.gitkeep
vendored
1
frontend/env.d.ts
vendored
1
frontend/env.d.ts
vendored
@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
@ -1,38 +0,0 @@
|
|||||||
import pluginVue from "eslint-plugin-vue";
|
|
||||||
import vueTsEslintConfig from "@vue/eslint-config-typescript";
|
|
||||||
import prettierConfig from "@vue/eslint-config-prettier";
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
name: "app/files-to-lint",
|
|
||||||
files: ["**/*.{ts,mts,tsx,vue}"],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "app/files-to-ignore",
|
|
||||||
ignores: ["**/dist/**", "**/dist-ssr/**", "**/coverage/**"],
|
|
||||||
},
|
|
||||||
|
|
||||||
...pluginVue.configs["flat/essential"],
|
|
||||||
...vueTsEslintConfig(),
|
|
||||||
prettierConfig,
|
|
||||||
|
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
// Note: you must disable the base rule as it can report incorrect errors
|
|
||||||
"no-unused-expressions": "off",
|
|
||||||
"@typescript-eslint/no-unused-expressions": "off",
|
|
||||||
// TODO: theres too many of these from before ts
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
// TODO: finish the ts conversion
|
|
||||||
"vue/block-lang": "off",
|
|
||||||
"vue/multi-word-component-names": "off",
|
|
||||||
"vue/no-mutating-props": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
shallowOnly: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
@ -1,192 +0,0 @@
|
|||||||
<!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"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<title>File Browser</title>
|
|
||||||
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="32x32"
|
|
||||||
href="/img/icons/favicon-32x32.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="16x16"
|
|
||||||
href="/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/iPadOS -->
|
|
||||||
<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="/img/icons/apple-touch-icon.png" />
|
|
||||||
|
|
||||||
<!-- Add to home screen for Windows -->
|
|
||||||
<meta
|
|
||||||
name="msapplication-TileImage"
|
|
||||||
content="/img/icons/mstile-144x144.png"
|
|
||||||
/>
|
|
||||||
<meta name="msapplication-TileColor" content="#2979ff" />
|
|
||||||
|
|
||||||
<!-- Inject Some Variables and generate the manifest json -->
|
|
||||||
<script>
|
|
||||||
// We can assign JSON directly
|
|
||||||
window.FileBrowser = {
|
|
||||||
AuthMethod: "json",
|
|
||||||
BaseURL: "",
|
|
||||||
CSS: false,
|
|
||||||
Color: "",
|
|
||||||
DisableExternal: false,
|
|
||||||
DisableUsedPercentage: false,
|
|
||||||
EnableExec: true,
|
|
||||||
EnableThumbs: true,
|
|
||||||
LoginPage: true,
|
|
||||||
Name: "",
|
|
||||||
NoAuth: false,
|
|
||||||
ReCaptcha: false,
|
|
||||||
ResizePreview: true,
|
|
||||||
Signup: false,
|
|
||||||
StaticURL: "",
|
|
||||||
Theme: "",
|
|
||||||
TusSettings: { chunkSize: 10485760, retryCount: 5 },
|
|
||||||
Version: "(untracked)",
|
|
||||||
};
|
|
||||||
// Global function to prepend static url
|
|
||||||
window.__prependStaticUrl = (url) => {
|
|
||||||
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
|
|
||||||
};
|
|
||||||
var dynamicManifest = {
|
|
||||||
name: window.FileBrowser.Name || "File Browser",
|
|
||||||
short_name: window.FileBrowser.Name || "File Browser",
|
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: window.__prependStaticUrl(
|
|
||||||
"/img/icons/android-chrome-192x192.png"
|
|
||||||
),
|
|
||||||
sizes: "192x192",
|
|
||||||
type: "image/png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: window.__prependStaticUrl(
|
|
||||||
"/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: window.FileBrowser.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: 0.1s ease opacity;
|
|
||||||
-webkit-transition: 0.1s ease opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading.done {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading .spinner {
|
|
||||||
width: 70px;
|
|
||||||
text-align: center;
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
-webkit-transform: translate(-50%, -50%);
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading .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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading .spinner .bounce1 {
|
|
||||||
-webkit-animation-delay: -0.32s;
|
|
||||||
animation-delay: -0.32s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading .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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-bouncedelay {
|
|
||||||
0%,
|
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
-webkit-transform: scale(0);
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
-webkit-transform: scale(1);
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
27899
frontend/package-lock.json
generated
Normal file
27899
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,77 +1,72 @@
|
|||||||
{
|
{
|
||||||
"name": "filebrowser-frontend",
|
"name": "filebrowser-frontend",
|
||||||
"version": "3.0.0",
|
"version": "2.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=22.0.0",
|
|
||||||
"pnpm": ">=9.0.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "pnpm run typecheck && vite build",
|
"build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean",
|
||||||
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
"lint": "npx vue-cli-service lint --no-fix --max-warnings=0",
|
||||||
"typecheck": "vue-tsc -p ./tsconfig.tsc.json --noEmit",
|
"fix": "npx vue-cli-service lint",
|
||||||
"lint": "eslint src/",
|
"watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean"
|
||||||
"lint:fix": "eslint --fix src/",
|
|
||||||
"format": "prettier --write .",
|
|
||||||
"test": "playwright test"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chenfengyuan/vue-number-input": "^2.0.1",
|
"ace-builds": "^1.4.7",
|
||||||
"@vueuse/core": "^12.5.0",
|
"clipboard": "^2.0.4",
|
||||||
"@vueuse/integrations": "^12.5.0",
|
"core-js": "^3.9.1",
|
||||||
"ace-builds": "^1.37.5",
|
"css-vars-ponyfill": "^2.4.3",
|
||||||
"core-js": "^3.40.0",
|
"js-base64": "^2.5.1",
|
||||||
"dayjs": "^1.11.10",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"epubjs": "^0.3.93",
|
"lodash.throttle": "^4.1.1",
|
||||||
"filesize": "^10.1.1",
|
"material-design-icons": "^3.0.1",
|
||||||
"js-base64": "^3.7.7",
|
"moment": "^2.24.0",
|
||||||
"jwt-decode": "^4.0.0",
|
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"marked": "^15.0.6",
|
|
||||||
"material-icons": "^1.13.13",
|
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"pinia": "^2.3.1",
|
"noty": "^3.2.0-beta",
|
||||||
"pretty-bytes": "^6.1.1",
|
"qrcode.vue": "^1.7.0",
|
||||||
"qrcode.vue": "^3.4.1",
|
|
||||||
"tus-js-client": "^4.3.1",
|
|
||||||
"utif": "^3.1.0",
|
"utif": "^3.1.0",
|
||||||
"video.js": "^8.21.0",
|
"vue": "^2.6.10",
|
||||||
"videojs-hotkeys": "^0.2.28",
|
"vue-i18n": "^8.15.3",
|
||||||
"videojs-mobile-ui": "^1.1.1",
|
"vue-lazyload": "^1.3.3",
|
||||||
"vue": "^3.4.21",
|
"vue-router": "^3.1.3",
|
||||||
"vue-final-modal": "^4.5.4",
|
"vuex": "^3.1.2",
|
||||||
"vue-i18n": "^11.1.2",
|
"vuex-router-sync": "^5.0.0",
|
||||||
"vue-lazyload": "^3.0.0",
|
"whatwg-fetch": "^3.6.2"
|
||||||
"vue-reader": "^1.2.17",
|
|
||||||
"vue-router": "^4.3.0",
|
|
||||||
"vue-toastification": "^2.0.0-rc.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/unplugin-vue-i18n": "^6.0.3",
|
"@vue/cli-plugin-babel": "^4.1.2",
|
||||||
"@playwright/test": "^1.50.0",
|
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||||
"@tsconfig/node22": "^22.0.0",
|
"@vue/cli-service": "^4.1.2",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
"@types/node": "^22.10.10",
|
"babel-eslint": "^10.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
"compression-webpack-plugin": "^6.0.3",
|
||||||
"@vitejs/plugin-legacy": "^6.0.0",
|
"eslint": "^6.7.2",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"eslint-plugin-prettier": "^3.3.1",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
"@vue/eslint-config-typescript": "^14.3.0",
|
"prettier": "^2.2.1",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"vue-template-compiler": "^2.6.10"
|
||||||
"autoprefixer": "^10.4.19",
|
|
||||||
"concurrently": "^9.1.2",
|
|
||||||
"eslint": "^9.19.0",
|
|
||||||
"eslint-plugin-prettier": "^5.2.3",
|
|
||||||
"eslint-plugin-vue": "^9.24.0",
|
|
||||||
"jsdom": "^26.0.0",
|
|
||||||
"postcss": "^8.5.1",
|
|
||||||
"prettier": "^3.4.2",
|
|
||||||
"terser": "^5.37.0",
|
|
||||||
"vite": "^6.1.6",
|
|
||||||
"vite-plugin-compression2": "^1.0.0",
|
|
||||||
"vue-tsc": "^2.2.0"
|
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0"
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/prettier"
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "babel-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postcss": {
|
||||||
|
"plugins": {
|
||||||
|
"autoprefixer": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not ie < 11"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
import { defineConfig, devices } from "@playwright/test";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read environment variables from file.
|
|
||||||
* https://github.com/motdotla/dotenv
|
|
||||||
*/
|
|
||||||
// require('dotenv').config();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
|
||||||
*/
|
|
||||||
export default defineConfig({
|
|
||||||
testDir: "./tests",
|
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
/* Retry on CI only */
|
|
||||||
retries: process.env.CI ? 2 : 0,
|
|
||||||
/* Opt out of parallel tests on CI. */
|
|
||||||
workers: process.env.CI ? 1 : undefined,
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: "html",
|
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
||||||
use: {
|
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
||||||
baseURL: "http://127.0.0.1:5173",
|
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: "on-first-retry",
|
|
||||||
|
|
||||||
/* Set default locale to English (US) */
|
|
||||||
locale: "en-US",
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: "chromium",
|
|
||||||
use: { ...devices["Desktop Chrome"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "firefox",
|
|
||||||
use: { ...devices["Desktop Firefox"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
// {
|
|
||||||
// name: "webkit",
|
|
||||||
// use: { ...devices["Desktop Safari"] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Chrome',
|
|
||||||
// use: { ...devices['Pixel 5'] },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Safari',
|
|
||||||
// use: { ...devices['iPhone 12'] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
/* Test against branded browsers. */
|
|
||||||
// {
|
|
||||||
// name: 'Microsoft Edge',
|
|
||||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Google Chrome',
|
|
||||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
|
||||||
webServer: {
|
|
||||||
command: "npm run dev",
|
|
||||||
url: "http://127.0.0.1:5173",
|
|
||||||
reuseExistingServer: !process.env.CI,
|
|
||||||
},
|
|
||||||
});
|
|
5426
frontend/pnpm-lock.yaml
generated
5426
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,101 +1,63 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, user-scalable=no"
|
|
||||||
/>
|
|
||||||
|
|
||||||
[{[ if .ReCaptcha -]}]
|
[{[ if .ReCaptcha -]}]
|
||||||
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
|
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
|
||||||
[{[ end ]}]
|
[{[ end ]}]
|
||||||
|
|
||||||
<title>
|
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
|
||||||
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]
|
|
||||||
</title>
|
|
||||||
|
|
||||||
<meta name="robots" content="noindex,nofollow" />
|
<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">
|
||||||
<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 -->
|
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||||
<link
|
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
|
||||||
rel="manifest"
|
<meta name="theme-color" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
|
||||||
id="manifestPlaceholder"
|
|
||||||
crossorigin="use-credentials"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="theme-color"
|
|
||||||
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<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-status-bar-style" content="black">
|
||||||
<meta name="apple-mobile-web-app-title" content="assets" />
|
<meta name="apple-mobile-web-app-title" content="assets">
|
||||||
<link
|
<link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png">
|
||||||
rel="apple-touch-icon"
|
|
||||||
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Add to home screen for Windows -->
|
<!-- Add to home screen for Windows -->
|
||||||
<meta
|
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png">
|
||||||
name="msapplication-TileImage"
|
<meta name="msapplication-TileColor" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
|
||||||
content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="msapplication-TileColor"
|
|
||||||
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Inject Some Variables and generate the manifest json -->
|
<!-- Inject Some Variables and generate the manifest json -->
|
||||||
<script>
|
<script>
|
||||||
// We can assign JSON directly
|
window.FileBrowser = JSON.parse('[{[ .Json ]}]');
|
||||||
window.FileBrowser = [{[ .Json ]}];
|
|
||||||
// Global function to prepend static url
|
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
||||||
window.__prependStaticUrl = (url) => {
|
|
||||||
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
|
|
||||||
};
|
|
||||||
var dynamicManifest = {
|
var dynamicManifest = {
|
||||||
name: window.FileBrowser.Name || "File Browser",
|
"name": window.FileBrowser.Name || 'File Browser',
|
||||||
short_name: window.FileBrowser.Name || "File Browser",
|
"short_name": window.FileBrowser.Name || 'File Browser',
|
||||||
icons: [
|
"icons": [
|
||||||
{
|
{
|
||||||
src: window.__prependStaticUrl("/img/icons/android-chrome-192x192.png"),
|
"src": fullStaticURL + "/img/icons/android-chrome-192x192.png",
|
||||||
sizes: "192x192",
|
"sizes": "192x192",
|
||||||
type: "image/png",
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: window.__prependStaticUrl("/img/icons/android-chrome-512x512.png"),
|
"src": fullStaticURL + "/img/icons/android-chrome-512x512.png",
|
||||||
sizes: "512x512",
|
"sizes": "512x512",
|
||||||
type: "image/png",
|
"type": "image/png"
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
start_url: window.location.origin + window.FileBrowser.BaseURL,
|
"start_url": window.location.origin + window.FileBrowser.BaseURL,
|
||||||
display: "standalone",
|
"display": "standalone",
|
||||||
background_color: "#ffffff",
|
"background_color": "#ffffff",
|
||||||
theme_color: window.FileBrowser.Color || "#455a64",
|
"theme_color": window.FileBrowser.Color || "#455a64"
|
||||||
};
|
}
|
||||||
|
|
||||||
const stringManifest = JSON.stringify(dynamicManifest);
|
const stringManifest = JSON.stringify(dynamicManifest);
|
||||||
const blob = new Blob([stringManifest], { type: "application/json" });
|
const blob = new Blob([stringManifest], {type: 'application/json'});
|
||||||
const manifestURL = URL.createObjectURL(blob);
|
const manifestURL = URL.createObjectURL(blob);
|
||||||
document
|
document.querySelector('#manifestPlaceholder').setAttribute('href', manifestURL);
|
||||||
.querySelector("#manifestPlaceholder")
|
|
||||||
.setAttribute("href", manifestURL);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -107,8 +69,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
transition: 0.1s ease opacity;
|
transition: .1s ease opacity;
|
||||||
-webkit-transition: 0.1s ease opacity;
|
-webkit-transition: .1s ease opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading.done {
|
#loading.done {
|
||||||
@ -146,26 +108,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes sk-bouncedelay {
|
@-webkit-keyframes sk-bouncedelay {
|
||||||
0%,
|
0%, 80%, 100% { -webkit-transform: scale(0) }
|
||||||
80%,
|
40% { -webkit-transform: scale(1.0) }
|
||||||
100% {
|
|
||||||
-webkit-transform: scale(0);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
-webkit-transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes sk-bouncedelay {
|
@keyframes sk-bouncedelay {
|
||||||
0%,
|
0%, 80%, 100% {
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
-webkit-transform: scale(0);
|
-webkit-transform: scale(0);
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
}
|
} 40% {
|
||||||
40% {
|
-webkit-transform: scale(1.0);
|
||||||
-webkit-transform: scale(1);
|
transform: scale(1.0);
|
||||||
transform: scale(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -181,8 +134,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
[{[ if .Theme -]}]
|
||||||
|
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css" />
|
||||||
|
[{[ end ]}]
|
||||||
[{[ if .CSS -]}]
|
[{[ if .CSS -]}]
|
||||||
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
||||||
[{[ end ]}]
|
[{[ end ]}]
|
||||||
|
211
frontend/public/themes/dark.css
Normal file
211
frontend/public/themes/dark.css
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
: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, main .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;
|
||||||
|
}
|
||||||
|
.breadcrumbs a:hover {
|
||||||
|
background-color: rgba(255, 255, 255, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard #nav ul li {
|
||||||
|
color: var(--textSecondary);
|
||||||
|
}
|
||||||
|
.dashboard #nav ul li:hover {
|
||||||
|
background: var(--surfaceSecondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h3,
|
||||||
|
.dashboard #nav,
|
||||||
|
.dashboard p label {
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
.card#share input,
|
||||||
|
.card#share 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 .wrapper,
|
||||||
|
.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 {
|
||||||
|
background: var(--surfacePrimary) !important;
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.share__box__element {
|
||||||
|
border-top-color: var(--divider);
|
||||||
|
}
|
@ -4,30 +4,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script>
|
||||||
import { ref, onMounted, watch } from "vue";
|
// eslint-disable-next-line no-undef
|
||||||
import { useI18n } from "vue-i18n";
|
__webpack_public_path__ = window.FileBrowser.StaticURL + "/";
|
||||||
import { setHtmlLocale } from "./i18n";
|
|
||||||
import { getMediaPreference, getTheme, setTheme } from "./utils/theme";
|
|
||||||
|
|
||||||
const { locale } = useI18n();
|
export default {
|
||||||
|
name: "app",
|
||||||
const userTheme = ref<UserTheme>(getTheme() || getMediaPreference());
|
mounted() {
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
setTheme(userTheme.value);
|
|
||||||
setHtmlLocale(locale.value);
|
|
||||||
// this might be null during HMR
|
|
||||||
const loading = document.getElementById("loading");
|
const loading = document.getElementById("loading");
|
||||||
loading?.classList.add("done");
|
loading.classList.add("done");
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
loading?.parentNode?.removeChild(loading);
|
loading.parentNode.removeChild(loading);
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
},
|
||||||
|
};
|
||||||
// handles ltr/rtl changes
|
|
||||||
watch(locale, (newValue) => {
|
|
||||||
newValue && setHtmlLocale(newValue);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import "./css/styles.css";
|
||||||
|
</style>
|
||||||
|
@ -1,22 +1,15 @@
|
|||||||
import { removePrefix } from "./utils";
|
import { removePrefix } from "./utils";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import store from "@/store";
|
||||||
|
|
||||||
const ssl = window.location.protocol === "https:";
|
const ssl = window.location.protocol === "https:";
|
||||||
const protocol = ssl ? "wss:" : "ws:";
|
const protocol = ssl ? "wss:" : "ws:";
|
||||||
|
|
||||||
export default function command(
|
export default function command(url, command, onmessage, onclose) {
|
||||||
url: string,
|
|
||||||
command: string,
|
|
||||||
onmessage: WebSocket["onmessage"],
|
|
||||||
onclose: WebSocket["onclose"]
|
|
||||||
) {
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
|
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${authStore.jwt}`;
|
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`;
|
||||||
|
|
||||||
const conn = new window.WebSocket(url);
|
let conn = new window.WebSocket(url);
|
||||||
conn.onopen = () => conn.send(command);
|
conn.onopen = () => conn.send(command);
|
||||||
conn.onmessage = onmessage;
|
conn.onmessage = onmessage;
|
||||||
conn.onclose = onclose;
|
conn.onclose = onclose;
|
156
frontend/src/api/files.js
Normal file
156
frontend/src/api/files.js
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
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) {
|
||||||
|
url += `algo=${format}&`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store.state.jwt) {
|
||||||
|
url += `auth=${store.state.jwt}&`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function post(url, content = "", overwrite = false, onupload) {
|
||||||
|
url = removePrefix(url);
|
||||||
|
|
||||||
|
let bufferContent;
|
||||||
|
if (
|
||||||
|
content instanceof Blob &&
|
||||||
|
!["http:", "https:"].includes(window.location.protocol)
|
||||||
|
) {
|
||||||
|
bufferContent = await new Response(content).arrayBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(bufferContent || content);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveCopy(items, copy = false, overwrite = false, rename = false) {
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
for (let item of items) {
|
||||||
|
const from = 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];
|
||||||
|
}
|
@ -1,219 +0,0 @@
|
|||||||
import { useAuthStore } from "@/stores/auth";
|
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
|
||||||
import { baseURL } from "@/utils/constants";
|
|
||||||
import { upload as postTus, useTus } from "./tus";
|
|
||||||
import { createURL, fetchURL, removePrefix } from "./utils";
|
|
||||||
|
|
||||||
export async function fetch(url: string) {
|
|
||||||
url = removePrefix(url);
|
|
||||||
|
|
||||||
const res = await fetchURL(`/api/resources${url}`, {});
|
|
||||||
|
|
||||||
const data = (await res.json()) as Resource;
|
|
||||||
data.url = `/files${url}`;
|
|
||||||
|
|
||||||
if (data.isDir) {
|
|
||||||
if (!data.url.endsWith("/")) data.url += "/";
|
|
||||||
// Perhaps change the any
|
|
||||||
data.items = data.items.map((item: any, index: any) => {
|
|
||||||
item.index = index;
|
|
||||||
item.url = `${data.url}${encodeURIComponent(item.name)}`;
|
|
||||||
|
|
||||||
if (item.isDir) {
|
|
||||||
item.url += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resourceAction(url: string, method: ApiMethod, content?: any) {
|
|
||||||
url = removePrefix(url);
|
|
||||||
|
|
||||||
const opts: ApiOpts = {
|
|
||||||
method,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (content) {
|
|
||||||
opts.body = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetchURL(`/api/resources${url}`, opts);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function remove(url: string) {
|
|
||||||
return resourceAction(url, "DELETE");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function put(url: string, content = "") {
|
|
||||||
return resourceAction(url, "PUT", content);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function download(format: any, ...files: string[]) {
|
|
||||||
let url = `${baseURL}/api/raw`;
|
|
||||||
|
|
||||||
if (files.length === 1) {
|
|
||||||
url += removePrefix(files[0]) + "?";
|
|
||||||
} else {
|
|
||||||
let arg = "";
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
arg += removePrefix(file) + ",";
|
|
||||||
}
|
|
||||||
|
|
||||||
arg = arg.substring(0, arg.length - 1);
|
|
||||||
arg = encodeURIComponent(arg);
|
|
||||||
url += `/?files=${arg}&`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format) {
|
|
||||||
url += `algo=${format}&`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
if (authStore.jwt) {
|
|
||||||
url += `auth=${authStore.jwt}&`;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.open(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function post(
|
|
||||||
url: string,
|
|
||||||
content: ApiContent = "",
|
|
||||||
overwrite = false,
|
|
||||||
onupload: any = () => {}
|
|
||||||
) {
|
|
||||||
// Use the pre-existing API if:
|
|
||||||
const useResourcesApi =
|
|
||||||
// a folder is being created
|
|
||||||
url.endsWith("/") ||
|
|
||||||
// We're not using http(s)
|
|
||||||
(content instanceof Blob &&
|
|
||||||
!["http:", "https:"].includes(window.location.protocol)) ||
|
|
||||||
// Tus is disabled / not applicable
|
|
||||||
!(await useTus(content));
|
|
||||||
return useResourcesApi
|
|
||||||
? postResources(url, content, overwrite, onupload)
|
|
||||||
: postTus(url, content, overwrite, onupload);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function postResources(
|
|
||||||
url: string,
|
|
||||||
content: ApiContent = "",
|
|
||||||
overwrite = false,
|
|
||||||
onupload: any
|
|
||||||
) {
|
|
||||||
url = removePrefix(url);
|
|
||||||
|
|
||||||
let bufferContent: ArrayBuffer;
|
|
||||||
if (
|
|
||||||
content instanceof Blob &&
|
|
||||||
!["http:", "https:"].includes(window.location.protocol)
|
|
||||||
) {
|
|
||||||
bufferContent = await new Response(content).arrayBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = new XMLHttpRequest();
|
|
||||||
request.open(
|
|
||||||
"POST",
|
|
||||||
`${baseURL}/api/resources${url}?override=${overwrite}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
request.setRequestHeader("X-Auth", authStore.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 = () => {
|
|
||||||
reject(new Error("001 Connection aborted"));
|
|
||||||
};
|
|
||||||
|
|
||||||
request.send(bufferContent || content);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveCopy(
|
|
||||||
items: any[],
|
|
||||||
copy = false,
|
|
||||||
overwrite = false,
|
|
||||||
rename = false
|
|
||||||
) {
|
|
||||||
const layoutStore = useLayoutStore();
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
const from = 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"));
|
|
||||||
}
|
|
||||||
layoutStore.closeHovers();
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function move(items: any[], overwrite = false, rename = false) {
|
|
||||||
return moveCopy(items, false, overwrite, rename);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function copy(items: any[], overwrite = false, rename = false) {
|
|
||||||
return moveCopy(items, true, overwrite, rename);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checksum(url: string, algo: ChecksumAlg) {
|
|
||||||
const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
|
|
||||||
return (await data.json()).checksums[algo];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDownloadURL(file: ResourceItem, inline: any) {
|
|
||||||
const params = {
|
|
||||||
...(inline && { inline: "true" }),
|
|
||||||
};
|
|
||||||
|
|
||||||
return createURL("api/raw" + file.path, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPreviewURL(file: ResourceItem, size: string) {
|
|
||||||
const params = {
|
|
||||||
inline: "true",
|
|
||||||
key: Date.parse(file.modified),
|
|
||||||
};
|
|
||||||
|
|
||||||
return createURL("api/preview/" + size + file.path, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSubtitlesURL(file: ResourceItem) {
|
|
||||||
const params = {
|
|
||||||
inline: "true",
|
|
||||||
};
|
|
||||||
|
|
||||||
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function usage(url: string) {
|
|
||||||
url = removePrefix(url);
|
|
||||||
|
|
||||||
const res = await fetchURL(`/api/usage${url}`, {});
|
|
||||||
|
|
||||||
return await res.json();
|
|
||||||
}
|
|
61
frontend/src/api/pub.js
Normal file
61
frontend/src/api/pub.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { fetchURL, removePrefix } from "./utils";
|
||||||
|
import { baseURL } from "@/utils/constants";
|
||||||
|
|
||||||
|
export async function fetch(url, password = "") {
|
||||||
|
url = removePrefix(url);
|
||||||
|
|
||||||
|
const res = await fetchURL(`/api/public/share${url}`, {
|
||||||
|
headers: { "X-SHARE-PASSWORD": password },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
let data = await res.json();
|
||||||
|
data.url = `/share${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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function download(format, hash, token, ...files) {
|
||||||
|
let url = `${baseURL}/api/public/dl/${hash}`;
|
||||||
|
|
||||||
|
if (files.length === 1) {
|
||||||
|
url += encodeURIComponent(files[0]) + "?";
|
||||||
|
} else {
|
||||||
|
let arg = "";
|
||||||
|
|
||||||
|
for (let file of files) {
|
||||||
|
arg += encodeURIComponent(file) + ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
arg = arg.substring(0, arg.length - 1);
|
||||||
|
arg = encodeURIComponent(arg);
|
||||||
|
url += `/?files=${arg}&`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format) {
|
||||||
|
url += `algo=${format}&`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
url += `token=${token}&`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open(url);
|
||||||
|
}
|
@ -1,75 +0,0 @@
|
|||||||
import { fetchURL, removePrefix, createURL } from "./utils";
|
|
||||||
import { baseURL } from "@/utils/constants";
|
|
||||||
|
|
||||||
export async function fetch(url: string, password: string = "") {
|
|
||||||
url = removePrefix(url);
|
|
||||||
|
|
||||||
const res = await fetchURL(
|
|
||||||
`/api/public/share${url}`,
|
|
||||||
{
|
|
||||||
headers: { "X-SHARE-PASSWORD": encodeURIComponent(password) },
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = (await res.json()) as Resource;
|
|
||||||
data.url = `/share${url}`;
|
|
||||||
|
|
||||||
if (data.isDir) {
|
|
||||||
if (!data.url.endsWith("/")) data.url += "/";
|
|
||||||
data.items = data.items.map((item: any, index: any) => {
|
|
||||||
item.index = index;
|
|
||||||
item.url = `${data.url}${encodeURIComponent(item.name)}`;
|
|
||||||
|
|
||||||
if (item.isDir) {
|
|
||||||
item.url += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function download(
|
|
||||||
format: DownloadFormat,
|
|
||||||
hash: string,
|
|
||||||
token: string,
|
|
||||||
...files: string[]
|
|
||||||
) {
|
|
||||||
let url = `${baseURL}/api/public/dl/${hash}`;
|
|
||||||
|
|
||||||
if (files.length === 1) {
|
|
||||||
url += encodeURIComponent(files[0]) + "?";
|
|
||||||
} else {
|
|
||||||
let arg = "";
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
arg += encodeURIComponent(file) + ",";
|
|
||||||
}
|
|
||||||
|
|
||||||
arg = arg.substring(0, arg.length - 1);
|
|
||||||
arg = encodeURIComponent(arg);
|
|
||||||
url += `/?files=${arg}&`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format) {
|
|
||||||
url += `algo=${format}&`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
url += `token=${token}&`;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.open(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDownloadURL(res: Resource, inline = false) {
|
|
||||||
const params = {
|
|
||||||
...(inline && { inline: "true" }),
|
|
||||||
...(res.token && { token: res.token }),
|
|
||||||
};
|
|
||||||
|
|
||||||
return createURL("api/public/dl/" + res.hash + res.path, params, false);
|
|
||||||
}
|
|
31
frontend/src/api/search.js
Normal file
31
frontend/src/api/search.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { fetchURL, removePrefix } from "./utils";
|
||||||
|
import url from "../utils/url";
|
||||||
|
|
||||||
|
export default async function search(base, query) {
|
||||||
|
base = removePrefix(base);
|
||||||
|
query = encodeURIComponent(query);
|
||||||
|
|
||||||
|
if (!base.endsWith("/")) {
|
||||||
|
base += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await fetchURL(`/api/search${base}?query=${query}`, {});
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
let data = await res.json();
|
||||||
|
|
||||||
|
data = data.map((item) => {
|
||||||
|
item.url = `/files${base}` + url.encodePath(item.path);
|
||||||
|
|
||||||
|
if (item.dir) {
|
||||||
|
item.url += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw Error(res.status);
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
import { fetchURL, removePrefix } from "./utils";
|
|
||||||
import url from "../utils/url";
|
|
||||||
|
|
||||||
export default async function search(base: string, query: string) {
|
|
||||||
base = removePrefix(base);
|
|
||||||
query = encodeURIComponent(query);
|
|
||||||
|
|
||||||
if (!base.endsWith("/")) {
|
|
||||||
base += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetchURL(`/api/search${base}?query=${query}`, {});
|
|
||||||
|
|
||||||
let data = await res.json();
|
|
||||||
|
|
||||||
data = data.map((item: UploadItem) => {
|
|
||||||
item.url = `/files${base}` + url.encodePath(item.path);
|
|
||||||
|
|
||||||
if (item.dir) {
|
|
||||||
item.url += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
16
frontend/src/api/settings.js
Normal file
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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
import { fetchURL, fetchJSON } from "./utils";
|
|
||||||
|
|
||||||
export function get() {
|
|
||||||
return fetchJSON<ISettings>(`/api/settings`, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function update(settings: ISettings) {
|
|
||||||
await fetchURL(`/api/settings`, {
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify(settings),
|
|
||||||
});
|
|
||||||
}
|
|
36
frontend/src/api/share.js
Normal file
36
frontend/src/api/share.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { fetchURL, fetchJSON, removePrefix } from "./utils";
|
||||||
|
|
||||||
|
export async function list() {
|
||||||
|
return fetchJSON("/api/shares");
|
||||||
|
}
|
||||||
|
|
||||||
|
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, password = "", expires = "", unit = "hours") {
|
||||||
|
url = removePrefix(url);
|
||||||
|
url = `/api/share${url}`;
|
||||||
|
if (expires !== "") {
|
||||||
|
url += `?expires=${expires}&unit=${unit}`;
|
||||||
|
}
|
||||||
|
let body = "{}";
|
||||||
|
if (password != "" || expires !== "" || unit !== "hours") {
|
||||||
|
body = JSON.stringify({ password: password, expires: expires, unit: unit });
|
||||||
|
}
|
||||||
|
return fetchJSON(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
}
|
@ -1,45 +0,0 @@
|
|||||||
import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils";
|
|
||||||
|
|
||||||
export async function list() {
|
|
||||||
return fetchJSON<Share[]>("/api/shares");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function get(url: string) {
|
|
||||||
url = removePrefix(url);
|
|
||||||
return fetchJSON<Share>(`/api/share${url}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function remove(hash: string) {
|
|
||||||
await fetchURL(`/api/share/${hash}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function create(
|
|
||||||
url: string,
|
|
||||||
password = "",
|
|
||||||
expires = "",
|
|
||||||
unit = "hours"
|
|
||||||
) {
|
|
||||||
url = removePrefix(url);
|
|
||||||
url = `/api/share${url}`;
|
|
||||||
if (expires !== "") {
|
|
||||||
url += `?expires=${expires}&unit=${unit}`;
|
|
||||||
}
|
|
||||||
let body = "{}";
|
|
||||||
if (password != "" || expires !== "" || unit !== "hours") {
|
|
||||||
body = JSON.stringify({
|
|
||||||
password: password,
|
|
||||||
expires: expires.toString(), // backend expects string not number
|
|
||||||
unit: unit,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return fetchJSON(url, {
|
|
||||||
method: "POST",
|
|
||||||
body: body,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getShareURL(share: Share) {
|
|
||||||
return createURL("share/" + share.hash, {}, false);
|
|
||||||
}
|
|
@ -1,213 +0,0 @@
|
|||||||
import * as tus from "tus-js-client";
|
|
||||||
import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants";
|
|
||||||
import { useAuthStore } from "@/stores/auth";
|
|
||||||
import { useUploadStore } from "@/stores/upload";
|
|
||||||
import { removePrefix } from "@/api/utils";
|
|
||||||
import { fetchURL } from "./utils";
|
|
||||||
|
|
||||||
const RETRY_BASE_DELAY = 1000;
|
|
||||||
const RETRY_MAX_DELAY = 20000;
|
|
||||||
const SPEED_UPDATE_INTERVAL = 1000;
|
|
||||||
const ALPHA = 0.2;
|
|
||||||
const ONE_MINUS_ALPHA = 1 - ALPHA;
|
|
||||||
const RECENT_SPEEDS_LIMIT = 5;
|
|
||||||
const MB_DIVISOR = 1024 * 1024;
|
|
||||||
const CURRENT_UPLOAD_LIST: CurrentUploadList = {};
|
|
||||||
|
|
||||||
export async function upload(
|
|
||||||
filePath: string,
|
|
||||||
content: ApiContent = "",
|
|
||||||
overwrite = false,
|
|
||||||
onupload: any
|
|
||||||
) {
|
|
||||||
if (!tusSettings) {
|
|
||||||
// Shouldn't happen as we check for tus support before calling this function
|
|
||||||
throw new Error("Tus.io settings are not defined");
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath = removePrefix(filePath);
|
|
||||||
const resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`;
|
|
||||||
|
|
||||||
await createUpload(resourcePath);
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
|
|
||||||
// Exit early because of typescript, tus content can't be a string
|
|
||||||
if (content === "") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return new Promise<void | string>((resolve, reject) => {
|
|
||||||
const upload = new tus.Upload(content, {
|
|
||||||
uploadUrl: `${baseURL}${resourcePath}`,
|
|
||||||
chunkSize: tusSettings.chunkSize,
|
|
||||||
retryDelays: computeRetryDelays(tusSettings),
|
|
||||||
parallelUploads: 1,
|
|
||||||
storeFingerprintForResuming: false,
|
|
||||||
headers: {
|
|
||||||
"X-Auth": authStore.jwt,
|
|
||||||
},
|
|
||||||
onError: function (error) {
|
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
|
||||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
|
||||||
}
|
|
||||||
delete CURRENT_UPLOAD_LIST[filePath];
|
|
||||||
reject(new Error(`Upload failed: ${error.message}`));
|
|
||||||
},
|
|
||||||
onProgress: function (bytesUploaded) {
|
|
||||||
const fileData = CURRENT_UPLOAD_LIST[filePath];
|
|
||||||
fileData.currentBytesUploaded = bytesUploaded;
|
|
||||||
|
|
||||||
if (!fileData.hasStarted) {
|
|
||||||
fileData.hasStarted = true;
|
|
||||||
fileData.lastProgressTimestamp = Date.now();
|
|
||||||
|
|
||||||
fileData.interval = window.setInterval(() => {
|
|
||||||
calcProgress(filePath);
|
|
||||||
}, SPEED_UPDATE_INTERVAL);
|
|
||||||
}
|
|
||||||
if (typeof onupload === "function") {
|
|
||||||
onupload({ loaded: bytesUploaded });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSuccess: function () {
|
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
|
||||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
|
||||||
}
|
|
||||||
delete CURRENT_UPLOAD_LIST[filePath];
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
CURRENT_UPLOAD_LIST[filePath] = {
|
|
||||||
upload: upload,
|
|
||||||
recentSpeeds: [],
|
|
||||||
initialBytesUploaded: 0,
|
|
||||||
currentBytesUploaded: 0,
|
|
||||||
currentAverageSpeed: 0,
|
|
||||||
lastProgressTimestamp: null,
|
|
||||||
sumOfRecentSpeeds: 0,
|
|
||||||
hasStarted: false,
|
|
||||||
interval: undefined,
|
|
||||||
};
|
|
||||||
upload.start();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createUpload(resourcePath: string) {
|
|
||||||
const headResp = await fetchURL(resourcePath, {
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
if (headResp.status !== 201) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to create an upload: ${headResp.status} ${headResp.statusText}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeRetryDelays(tusSettings: TusSettings): number[] | undefined {
|
|
||||||
if (!tusSettings.retryCount || tusSettings.retryCount < 1) {
|
|
||||||
// Disable retries altogether
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
// The tus client expects our retries as an array with computed backoffs
|
|
||||||
// E.g.: [0, 3000, 5000, 10000, 20000]
|
|
||||||
const retryDelays = [];
|
|
||||||
let delay = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < tusSettings.retryCount; i++) {
|
|
||||||
retryDelays.push(Math.min(delay, RETRY_MAX_DELAY));
|
|
||||||
delay =
|
|
||||||
delay === 0 ? RETRY_BASE_DELAY : Math.min(delay * 2, RETRY_MAX_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return retryDelays;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function useTus(content: ApiContent) {
|
|
||||||
return isTusSupported() && content instanceof Blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTusSupported() {
|
|
||||||
return tus.isSupported === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeETA(state: ETAState, speed?: number) {
|
|
||||||
if (state.speedMbyte === 0) {
|
|
||||||
return Infinity;
|
|
||||||
}
|
|
||||||
const totalSize = state.sizes.reduce(
|
|
||||||
(acc: number, size: number) => acc + size,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const uploadedSize = state.progress.reduce(
|
|
||||||
(acc: number, progress: Progress) => {
|
|
||||||
if (typeof progress === "number") {
|
|
||||||
return acc + progress;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const remainingSize = totalSize - uploadedSize;
|
|
||||||
const speedBytesPerSecond = (speed ?? state.speedMbyte) * 1024 * 1024;
|
|
||||||
return remainingSize / speedBytesPerSecond;
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeGlobalSpeedAndETA() {
|
|
||||||
const uploadStore = useUploadStore();
|
|
||||||
let totalSpeed = 0;
|
|
||||||
let totalCount = 0;
|
|
||||||
|
|
||||||
for (const filePath in CURRENT_UPLOAD_LIST) {
|
|
||||||
totalSpeed += CURRENT_UPLOAD_LIST[filePath].currentAverageSpeed;
|
|
||||||
totalCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalCount === 0) return { speed: 0, eta: Infinity };
|
|
||||||
|
|
||||||
const averageSpeed = totalSpeed / totalCount;
|
|
||||||
const averageETA = computeETA(uploadStore, averageSpeed);
|
|
||||||
|
|
||||||
return { speed: averageSpeed, eta: averageETA };
|
|
||||||
}
|
|
||||||
|
|
||||||
function calcProgress(filePath: string) {
|
|
||||||
const uploadStore = useUploadStore();
|
|
||||||
const fileData = CURRENT_UPLOAD_LIST[filePath];
|
|
||||||
|
|
||||||
const elapsedTime =
|
|
||||||
(Date.now() - (fileData.lastProgressTimestamp ?? 0)) / 1000;
|
|
||||||
const bytesSinceLastUpdate =
|
|
||||||
fileData.currentBytesUploaded - fileData.initialBytesUploaded;
|
|
||||||
const currentSpeed = bytesSinceLastUpdate / MB_DIVISOR / elapsedTime;
|
|
||||||
|
|
||||||
if (fileData.recentSpeeds.length >= RECENT_SPEEDS_LIMIT) {
|
|
||||||
fileData.sumOfRecentSpeeds -= fileData.recentSpeeds.shift() ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fileData.recentSpeeds.push(currentSpeed);
|
|
||||||
fileData.sumOfRecentSpeeds += currentSpeed;
|
|
||||||
|
|
||||||
const avgRecentSpeed =
|
|
||||||
fileData.sumOfRecentSpeeds / fileData.recentSpeeds.length;
|
|
||||||
fileData.currentAverageSpeed =
|
|
||||||
ALPHA * avgRecentSpeed + ONE_MINUS_ALPHA * fileData.currentAverageSpeed;
|
|
||||||
|
|
||||||
const { speed, eta } = computeGlobalSpeedAndETA();
|
|
||||||
uploadStore.setUploadSpeed(speed);
|
|
||||||
uploadStore.setETA(eta);
|
|
||||||
|
|
||||||
fileData.initialBytesUploaded = fileData.currentBytesUploaded;
|
|
||||||
fileData.lastProgressTimestamp = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function abortAllUploads() {
|
|
||||||
for (const filePath in CURRENT_UPLOAD_LIST) {
|
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
|
||||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
|
||||||
}
|
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].upload) {
|
|
||||||
CURRENT_UPLOAD_LIST[filePath].upload.abort(true);
|
|
||||||
}
|
|
||||||
delete CURRENT_UPLOAD_LIST[filePath];
|
|
||||||
}
|
|
||||||
}
|
|
51
frontend/src/api/users.js
Normal file
51
frontend/src/api/users.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user