Compare commits
No commits in common. "master" and "v2.31.0" have entirely different histories.
@ -1,7 +1,5 @@
|
|||||||
.venv
|
*
|
||||||
dist
|
!docker/*
|
||||||
.idea
|
!healthcheck.sh
|
||||||
frontend/node_modules
|
!docker_config.json
|
||||||
frontend/dist
|
!filebrowser
|
||||||
filebrowser.db
|
|
||||||
docs/index.md
|
|
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
@ -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. -->
|
53
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,53 +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.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: |
|
|
||||||
A clear and concise description of what the issue is about. What are you trying to do?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: What did you expect to happen?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: What actually happened?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- 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?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- 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
@ -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
@ -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. -->
|
27
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,16 +1,19 @@
|
|||||||
## 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 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/).
|
||||||
|
|
||||||
## 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).
|
||||||
|
- [ ] 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).
|
|
||||||
|
31
.github/workflows/main.yaml
vendored
@ -3,25 +3,20 @@ name: main
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "master"
|
- 'master'
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- 'v*'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# linters
|
# linters
|
||||||
lint-frontend:
|
lint-frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
package_json_file: "frontend/package.json"
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "22.x"
|
node-version: '18'
|
||||||
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
|
||||||
@ -37,19 +32,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run: echo "done"
|
- run: echo "done"
|
||||||
|
|
||||||
# tests
|
# tests
|
||||||
test-frontend:
|
test-frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
package_json_file: "frontend/package.json"
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "22.x"
|
node-version: '18'
|
||||||
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
|
||||||
@ -65,7 +55,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run: echo "done"
|
- run: echo "done"
|
||||||
|
|
||||||
# release
|
# release
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [lint, test]
|
needs: [lint, test]
|
||||||
@ -77,14 +67,9 @@ jobs:
|
|||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.23.0
|
go-version: 1.23.0
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
package_json_file: "frontend/package.json"
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "22.x"
|
node-version: '18'
|
||||||
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
|
||||||
|
20
.github/workflows/site-pr.yml
vendored
@ -1,20 +0,0 @@
|
|||||||
name: Build Site
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'www'
|
|
||||||
- '*.md'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Build site
|
|
||||||
run: make site
|
|
32
.github/workflows/site-publish.yml
vendored
@ -1,32 +0,0 @@
|
|||||||
name: Build and Deploy Site
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
deployments: write
|
|
||||||
pull-requests: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Build site
|
|
||||||
run: make site
|
|
||||||
|
|
||||||
- name: Deploy to Cloudflare Pages
|
|
||||||
uses: cloudflare/wrangler-action@v3
|
|
||||||
with:
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
command: pages deploy www/public --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
|
||||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
|
24
.github/workflows/stale.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v5
|
||||||
|
with:
|
||||||
|
stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||||
|
close-pr-message: 'This PR was closed because it has been stalled for 5 days with no activity.'
|
||||||
|
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||||
|
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
|
||||||
|
days-before-stale: 30
|
||||||
|
days-before-close: 5
|
||||||
|
exempt-issue-labels: 'feature ☘,enhancement ⚙,bug 🐞'
|
||||||
|
exempt-pr-labels: 'need-help,wip'
|
||||||
|
operations-per-run: 100
|
1
.gitignore
vendored
@ -6,7 +6,6 @@ rice-box.go
|
|||||||
/filebrowser
|
/filebrowser
|
||||||
/filebrowser.exe
|
/filebrowser.exe
|
||||||
/dist
|
/dist
|
||||||
.venv
|
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
187
.golangci.yml
@ -1,132 +1,121 @@
|
|||||||
version: "2"
|
linters-settings:
|
||||||
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
exhaustive:
|
||||||
|
default-signifies-exhaustive: false
|
||||||
|
funlen:
|
||||||
|
lines: 100
|
||||||
|
statements: 50
|
||||||
|
goconst:
|
||||||
|
min-len: 2
|
||||||
|
min-occurrences: 2
|
||||||
|
gocritic:
|
||||||
|
enabled-tags:
|
||||||
|
- diagnostic
|
||||||
|
- experimental
|
||||||
|
- opinionated
|
||||||
|
- performance
|
||||||
|
- style
|
||||||
|
disabled-checks:
|
||||||
|
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||||
|
- ifElseChain
|
||||||
|
- octalLiteral
|
||||||
|
- whyNoLint
|
||||||
|
- wrapperFunc
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 15
|
||||||
|
goimports:
|
||||||
|
local-prefixes: github.com/filebrowser/filebrowser
|
||||||
|
gomnd:
|
||||||
|
# don't include the "operation" and "assign"
|
||||||
|
checks:
|
||||||
|
- argument
|
||||||
|
- case
|
||||||
|
- condition
|
||||||
|
- return
|
||||||
|
ignored-numbers:
|
||||||
|
- '0'
|
||||||
|
- '1'
|
||||||
|
- '2'
|
||||||
|
- '3'
|
||||||
|
ignored-functions:
|
||||||
|
- strings.SplitN
|
||||||
|
govet:
|
||||||
|
enable:
|
||||||
|
- nilness
|
||||||
|
- shadow
|
||||||
|
lll:
|
||||||
|
line-length: 140
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
nolintlint:
|
||||||
|
allow-unused: false # report any unused nolint directives
|
||||||
|
require-explanation: false # require an explanation for nolint directives
|
||||||
|
require-specific: true # require nolint directives to be specific about which linter is being skipped
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
# inverted configuration with `default: all` and `disable` is not scalable during updates of golangci-lint
|
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||||
default: none
|
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
|
||||||
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- dogsled
|
- dogsled
|
||||||
- dupl
|
- dupl
|
||||||
- errcheck
|
- errcheck
|
||||||
- errorlint
|
- errorlint
|
||||||
|
- exportloopref
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- funlen
|
- funlen
|
||||||
- gocheckcompilerdirectives
|
- gocheckcompilerdirectives
|
||||||
- gochecknoinits
|
- gochecknoinits
|
||||||
|
- goconst
|
||||||
- gocritic
|
- gocritic
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- godox
|
- godox
|
||||||
|
- goimports
|
||||||
|
- gomnd
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
|
- gosimple
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- lll
|
- lll
|
||||||
- misspell
|
- misspell
|
||||||
- mnd
|
|
||||||
- nakedret
|
- nakedret
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- prealloc
|
- prealloc
|
||||||
- revive
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
|
- stylecheck
|
||||||
- testifylint
|
- testifylint
|
||||||
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- unused
|
- unused
|
||||||
- whitespace
|
- whitespace
|
||||||
settings:
|
|
||||||
dupl:
|
|
||||||
threshold: 100
|
|
||||||
exhaustive:
|
|
||||||
default-signifies-exhaustive: false
|
|
||||||
funlen:
|
|
||||||
lines: 100
|
|
||||||
statements: 50
|
|
||||||
gocritic:
|
|
||||||
disabled-checks:
|
|
||||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
|
||||||
- ifElseChain
|
|
||||||
- octalLiteral
|
|
||||||
- whyNoLint
|
|
||||||
- wrapperFunc
|
|
||||||
enabled-tags:
|
|
||||||
- diagnostic
|
|
||||||
- experimental
|
|
||||||
- opinionated
|
|
||||||
- performance
|
|
||||||
- style
|
|
||||||
gocyclo:
|
|
||||||
min-complexity: 15
|
|
||||||
govet:
|
|
||||||
enable:
|
|
||||||
- nilness
|
|
||||||
- shadow
|
|
||||||
lll:
|
|
||||||
line-length: 140
|
|
||||||
misspell:
|
|
||||||
locale: US
|
|
||||||
mnd:
|
|
||||||
# don't include the "operation" and "assign"
|
|
||||||
checks:
|
|
||||||
- argument
|
|
||||||
- case
|
|
||||||
- condition
|
|
||||||
- return
|
|
||||||
ignored-numbers:
|
|
||||||
- "0"
|
|
||||||
- "1"
|
|
||||||
- "2"
|
|
||||||
- "3"
|
|
||||||
- "0666"
|
|
||||||
- "0700"
|
|
||||||
- "0700"
|
|
||||||
ignored-functions:
|
|
||||||
- strings.SplitN
|
|
||||||
- make
|
|
||||||
nolintlint:
|
|
||||||
allow-unused: false # report any unused nolint directives
|
|
||||||
require-explanation: false # require an explanation for nolint directives
|
|
||||||
require-specific: true # require nolint directives to be specific about which linter is being skipped
|
|
||||||
staticcheck:
|
|
||||||
checks:
|
|
||||||
- "all"
|
|
||||||
- "-QF*"
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
presets:
|
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
- legacy
|
|
||||||
- std-error-handling
|
|
||||||
rules:
|
|
||||||
- linters:
|
|
||||||
- gochecknoinits
|
|
||||||
path: cmd/.*.go
|
|
||||||
- linters:
|
|
||||||
- dupl
|
|
||||||
- funlen
|
|
||||||
- gochecknoinits
|
|
||||||
- gocyclo
|
|
||||||
- lll
|
|
||||||
- scopelint
|
|
||||||
path: .*_test.go
|
|
||||||
- linters:
|
|
||||||
- misspell
|
|
||||||
text: "[aA]uther"
|
|
||||||
- linters:
|
|
||||||
- mnd
|
|
||||||
text: strconv.Parse
|
|
||||||
paths:
|
|
||||||
- frontend/
|
|
||||||
|
|
||||||
formatters:
|
issues:
|
||||||
enable:
|
exclude-dirs:
|
||||||
- goimports
|
- frontend/
|
||||||
settings:
|
exclude-rules:
|
||||||
goimports:
|
- path: cmd/.*.go
|
||||||
local-prefixes:
|
linters:
|
||||||
- github.com/filebrowser/filebrowser
|
- gochecknoinits
|
||||||
exclusions:
|
- path: .*_test.go
|
||||||
generated: lax
|
linters:
|
||||||
paths:
|
- lll
|
||||||
- frontend/
|
- gochecknoinits
|
||||||
|
- gocyclo
|
||||||
|
- funlen
|
||||||
|
- dupl
|
||||||
|
- scopelint
|
||||||
|
- text: "Auther"
|
||||||
|
linters:
|
||||||
|
- misspell
|
||||||
|
- text: "strconv.Parse"
|
||||||
|
linters:
|
||||||
|
- gomnd
|
||||||
|
|
||||||
|
run:
|
||||||
|
timeout: 5m
|
@ -19,30 +19,31 @@ builds:
|
|||||||
- freebsd
|
- freebsd
|
||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- "386"
|
- 386
|
||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
- riscv64
|
- riscv64
|
||||||
goarm:
|
goarm:
|
||||||
- "5"
|
- 5
|
||||||
- "6"
|
- 6
|
||||||
- "7"
|
- 7
|
||||||
ignore:
|
ignore:
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
goarch: "386"
|
goarch: 386
|
||||||
- goos: freebsd
|
- goos: freebsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
|
-
|
||||||
formats: ["tar.gz"]
|
name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
|
||||||
|
format: tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
formats: ["zip"]
|
format: zip
|
||||||
|
|
||||||
dockers:
|
dockers:
|
||||||
# Alpine docker images
|
-
|
||||||
- dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
use: buildx
|
use: buildx
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--pull"
|
- "--pull"
|
||||||
@ -58,8 +59,10 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
|
- "filebrowser/filebrowser:{{ .Tag }}-amd64"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
|
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker
|
- docker_config.json
|
||||||
- dockerfile: Dockerfile
|
- healthcheck.sh
|
||||||
|
-
|
||||||
|
dockerfile: Dockerfile
|
||||||
use: buildx
|
use: buildx
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--pull"
|
- "--pull"
|
||||||
@ -75,8 +78,10 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
|
- "filebrowser/filebrowser:{{ .Tag }}-arm64"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
|
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker
|
- docker_config.json
|
||||||
- dockerfile: Dockerfile
|
- healthcheck.sh
|
||||||
|
-
|
||||||
|
dockerfile: Dockerfile
|
||||||
use: buildx
|
use: buildx
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--pull"
|
- "--pull"
|
||||||
@ -88,13 +93,15 @@ dockers:
|
|||||||
- "--platform=linux/arm/v6"
|
- "--platform=linux/arm/v6"
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: "6"
|
goarm: '6'
|
||||||
image_templates:
|
image_templates:
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
|
- "filebrowser/filebrowser:{{ .Tag }}-armv6"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-armv6"
|
- "filebrowser/filebrowser:v{{ .Major }}-armv6"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker
|
- docker_config.json
|
||||||
- dockerfile: Dockerfile
|
- healthcheck.sh
|
||||||
|
-
|
||||||
|
dockerfile: Dockerfile
|
||||||
use: buildx
|
use: buildx
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--pull"
|
- "--pull"
|
||||||
@ -106,15 +113,16 @@ dockers:
|
|||||||
- "--platform=linux/arm/v7"
|
- "--platform=linux/arm/v7"
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: "7"
|
goarm: '7'
|
||||||
image_templates:
|
image_templates:
|
||||||
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
|
- "filebrowser/filebrowser:{{ .Tag }}-armv7"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
|
- "filebrowser/filebrowser:v{{ .Major }}-armv7"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker
|
- docker_config.json
|
||||||
|
- healthcheck.sh
|
||||||
## s6-overlay docker images
|
## s6 based docker images
|
||||||
- dockerfile: Dockerfile.s6
|
-
|
||||||
|
dockerfile: Dockerfile.s6
|
||||||
use: buildx
|
use: buildx
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--pull"
|
- "--pull"
|
||||||
@ -130,8 +138,9 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:{{ .Tag }}-amd64-s6"
|
- "filebrowser/filebrowser:{{ .Tag }}-amd64-s6"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-amd64-s6"
|
- "filebrowser/filebrowser:v{{ .Major }}-amd64-s6"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker
|
- docker/root
|
||||||
- dockerfile: Dockerfile.s6
|
-
|
||||||
|
dockerfile: Dockerfile.s6.aarch64
|
||||||
use: buildx
|
use: buildx
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--pull"
|
- "--pull"
|
||||||
@ -147,8 +156,7 @@ dockers:
|
|||||||
- "filebrowser/filebrowser:{{ .Tag }}-arm64-s6"
|
- "filebrowser/filebrowser:{{ .Tag }}-arm64-s6"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-arm64-s6"
|
- "filebrowser/filebrowser:v{{ .Major }}-arm64-s6"
|
||||||
extra_files:
|
extra_files:
|
||||||
- docker
|
- docker/root
|
||||||
|
|
||||||
docker_manifests:
|
docker_manifests:
|
||||||
- name_template: "filebrowser/filebrowser:latest"
|
- name_template: "filebrowser/filebrowser:latest"
|
||||||
image_templates:
|
image_templates:
|
||||||
@ -165,7 +173,7 @@ docker_manifests:
|
|||||||
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
|
- "filebrowser/filebrowser:v{{ .Major }}-amd64"
|
||||||
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
|
- "filebrowser/filebrowser:v{{ .Major }}-arm64"
|
||||||
- "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"
|
||||||
@ -178,20 +186,15 @@ docker_manifests:
|
|||||||
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"
|
||||||
|
brews:
|
||||||
homebrew_casks:
|
|
||||||
- name: filebrowser
|
- name: filebrowser
|
||||||
repository:
|
repository:
|
||||||
owner: filebrowser
|
owner: filebrowser
|
||||||
name: homebrew-tap
|
name: homebrew-tap
|
||||||
|
directory: 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
|
||||||
hooks:
|
license: "MIT"
|
||||||
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
@ -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, cz-CS: cz_cs
|
||||||
|
|
||||||
|
[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
|
285
CHANGELOG.md
@ -2,289 +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.37.0](https://github.com/filebrowser/filebrowser/compare/v2.36.3...v2.37.0) (2025-07-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Translate frontend/src/i18n/en.json in zh_CN ([65bbf44](https://github.com/filebrowser/filebrowser/commit/65bbf44e3c0bff83e64193d46e9d6ad302952276))
|
|
||||||
* Translate frontend/src/i18n/en.json in zh_TW ([b28952c](https://github.com/filebrowser/filebrowser/commit/b28952cb2582bd4eb44e91d0676e2803c458cf31))
|
|
||||||
* Translate frontend/src/i18n/en.json in zh_TW ([1e96fd9](https://github.com/filebrowser/filebrowser/commit/1e96fd9035d5185dc80970a2826ccb573b5f000e))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* long file name overlap ([fcb248a](https://github.com/filebrowser/filebrowser/commit/fcb248a5feb7b7404ca5923aae17f6d3f8d3cc96))
|
|
||||||
* preview PDF is correctly displayed ([bf73e4d](https://github.com/filebrowser/filebrowser/commit/bf73e4dea3b27c01c8f6e60fb2048e1a2122a70e))
|
|
||||||
* Upload progress size calculation ([e423395](https://github.com/filebrowser/filebrowser/commit/e423395ef0bcd106ddc7d460c055b95b5208415e))
|
|
||||||
|
|
||||||
### [2.36.3](https://github.com/filebrowser/filebrowser/compare/v2.36.2...v2.36.3) (2025-07-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* log error if branding file exists but cannot be loaded ([3645b57](https://github.com/filebrowser/filebrowser/commit/3645b578cddb9fc8f25a00e0153fb600ad1b9266))
|
|
||||||
|
|
||||||
### [2.36.2](https://github.com/filebrowser/filebrowser/compare/v2.36.1...v2.36.2) (2025-07-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* lookup directory name if blank when downloading shared directory ([046d619](https://github.com/filebrowser/filebrowser/commit/046d6193c57b4df0e3dc583b6518b43d29d302c9))
|
|
||||||
|
|
||||||
### [2.36.1](https://github.com/filebrowser/filebrowser/compare/v2.36.0...v2.36.1) (2025-07-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* remove associated shares when deleting file/folder ([e99e0b3](https://github.com/filebrowser/filebrowser/commit/e99e0b3028e1c8a50e1744bb07ecc8e809bdb8e6))
|
|
||||||
|
|
||||||
## [2.36.0](https://github.com/filebrowser/filebrowser/compare/v2.35.0...v2.36.0) (2025-07-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* update icons, remove deprecated Microsoft Tiles ([04166e8](https://github.com/filebrowser/filebrowser/commit/04166e81e52d38b1f66ba3313ccb1291c239eea2))
|
|
||||||
|
|
||||||
## [2.35.0](https://github.com/filebrowser/filebrowser/compare/v2.34.2...v2.35.0) (2025-06-30)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Long press selects item in single click mode ([8d75220](https://github.com/filebrowser/filebrowser/commit/8d7522049ced83f28f0933b55772c32e3ad04627))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* shell value must be joined by blank space ([4403cd3](https://github.com/filebrowser/filebrowser/commit/4403cd35720dbda5a8bb1013b92582accf3317bc))
|
|
||||||
* update documentation links ([38d0366](https://github.com/filebrowser/filebrowser/commit/38d0366acf88352b5a9a97c45837b0f865efae0b))
|
|
||||||
|
|
||||||
### [2.34.2](https://github.com/filebrowser/filebrowser/compare/v2.34.1...v2.34.2) (2025-06-29)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* mitigate unprotected shares ([2b5d6cb](https://github.com/filebrowser/filebrowser/commit/2b5d6cbb996a61a769acc56af0acc12eec2d8d8f))
|
|
||||||
|
|
||||||
### [2.34.1](https://github.com/filebrowser/filebrowser/compare/v2.34.0...v2.34.1) (2025-06-29)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* exclude to-be-moved folder from move dialog ([#5235](https://github.com/filebrowser/filebrowser/issues/5235)) ([7354eb6](https://github.com/filebrowser/filebrowser/commit/7354eb6cf966244141277c2808988855c004f908))
|
|
||||||
* passthrough the minimum password length ([#5236](https://github.com/filebrowser/filebrowser/issues/5236)) ([bf37f88](https://github.com/filebrowser/filebrowser/commit/bf37f88c32222ad9c186482bb97338a9c9b4a93c))
|
|
||||||
|
|
||||||
## [2.34.0](https://github.com/filebrowser/filebrowser/compare/v2.33.10...v2.34.0) (2025-06-29)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Translate frontend/src/i18n/en.json in fa ([0acd69c](https://github.com/filebrowser/filebrowser/commit/0acd69c537ce2909ff62c4bb6980982524ece221))
|
|
||||||
* Translate frontend/src/i18n/en.json in fa ([#5233](https://github.com/filebrowser/filebrowser/issues/5233)) ([09f679f](https://github.com/filebrowser/filebrowser/commit/09f679fae43398f5b87d21acc9d974d4d053392f))
|
|
||||||
* update translations for project File Browser ([#5226](https://github.com/filebrowser/filebrowser/issues/5226)) ([a5ea2a2](https://github.com/filebrowser/filebrowser/commit/a5ea2a266bef619d1c4322266d1aa7d397d2c856))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* abort ongoing requests when changing pages ([#3927](https://github.com/filebrowser/filebrowser/issues/3927)) ([93c4b2e](https://github.com/filebrowser/filebrowser/commit/93c4b2e03c5176da01a7e00a03c03ffcce279bc8))
|
|
||||||
* add configurable minimum password length ([#5225](https://github.com/filebrowser/filebrowser/issues/5225)) ([464b644](https://github.com/filebrowser/filebrowser/commit/464b644adf22a2178414a6f1e4fa286276de81d2))
|
|
||||||
* do not expose the name of the root directory ([#5224](https://github.com/filebrowser/filebrowser/issues/5224)) ([0892559](https://github.com/filebrowser/filebrowser/commit/089255997a653c284cd4249990b58bed00086c61))
|
|
||||||
* Graceful shutdown ([8230eb7](https://github.com/filebrowser/filebrowser/commit/8230eb7ab51ccbd00b03f5b9d6964fa4aae331d4))
|
|
||||||
|
|
||||||
|
|
||||||
### Reverts
|
|
||||||
|
|
||||||
* Revert "docs: change cloudflare environment (#5231)" (#5232) ([9e273cd](https://github.com/filebrowser/filebrowser/commit/9e273cd9475d57b9500034e8b341ff8b620bcab8)), closes [#5231](https://github.com/filebrowser/filebrowser/issues/5231) [#5232](https://github.com/filebrowser/filebrowser/issues/5232)
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* add an arm64 target for the site image ([#5229](https://github.com/filebrowser/filebrowser/issues/5229)) ([f5e531c](https://github.com/filebrowser/filebrowser/commit/f5e531c8ae0b9b18717e184856ace0ce19beef82))
|
|
||||||
* bump golangci-lint to 2.1.6 ([1d494ff](https://github.com/filebrowser/filebrowser/commit/1d494ff3159ef939cfb4980ccde6f27df3e738b5))
|
|
||||||
* **deps:** bump brace-expansion from 1.1.11 to 1.1.12 in /tools ([#5228](https://github.com/filebrowser/filebrowser/issues/5228)) ([5a07291](https://github.com/filebrowser/filebrowser/commit/5a072913062a6b2b0e5c74a02ca7710218ed3e5e))
|
|
||||||
* **deps:** bump github.com/go-viper/mapstructure/v2 ([f32f273](https://github.com/filebrowser/filebrowser/commit/f32f27383d1fafa074f038cc873bd37b7f20ee27))
|
|
||||||
* **deps:** bump github.com/go-viper/mapstructure/v2 in /tools ([5331969](https://github.com/filebrowser/filebrowser/commit/5331969163f5ae1fd2389f665059fc9e4a98db15))
|
|
||||||
* publish docs to cloudflare pages ([#5230](https://github.com/filebrowser/filebrowser/issues/5230)) ([8861933](https://github.com/filebrowser/filebrowser/commit/8861933cf845b104e072f35e5f37d7c26097c9dc))
|
|
||||||
|
|
||||||
### [2.33.10](https://github.com/filebrowser/filebrowser/compare/v2.33.9...v2.33.10) (2025-06-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* correctly check if command is allowed when using shell ([4d830f7](https://github.com/filebrowser/filebrowser/commit/4d830f707fc4314741fd431e70c2ce50cd5a3108))
|
|
||||||
* correctly split shell ([f84a6db](https://github.com/filebrowser/filebrowser/commit/f84a6db680b6df1c7c8f06f1816f7e4c9e963668))
|
|
||||||
* ignore linting error ([e735491](https://github.com/filebrowser/filebrowser/commit/e735491c57b12c3b19dd2e4b570723df78f4eb44))
|
|
||||||
|
|
||||||
### [2.33.9](https://github.com/filebrowser/filebrowser/compare/v2.33.8...v2.33.9) (2025-06-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* check exact match on command allow list ([e2e1e49](https://github.com/filebrowser/filebrowser/commit/e2e1e4913085cca8917e0f69171dc28d3c6af1b6))
|
|
||||||
* remove auth token from /api/command ([d5b39a1](https://github.com/filebrowser/filebrowser/commit/d5b39a14fd3fc0d1c364116b41289484df7c27b2))
|
|
||||||
* remove unused import ([c232d41](https://github.com/filebrowser/filebrowser/commit/c232d41f903d3026ec290bbe819b6c59a933048e))
|
|
||||||
|
|
||||||
### [2.33.8](https://github.com/filebrowser/filebrowser/compare/v2.33.7...v2.33.8) (2025-06-25)
|
|
||||||
|
|
||||||
### [2.33.7](https://github.com/filebrowser/filebrowser/compare/v2.33.6...v2.33.7) (2025-06-25)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* correctly parse negative boolean flags ([221451a](https://github.com/filebrowser/filebrowser/commit/221451a5179c8f139819a315b80d0ecb0e7220c3))
|
|
||||||
* linting issues ([4bfbf33](https://github.com/filebrowser/filebrowser/commit/4bfbf332499fc8aea5f6df6aae1efa0de918d1ae))
|
|
||||||
* linting issues ([e74c958](https://github.com/filebrowser/filebrowser/commit/e74c95886226c0ee429af1860eed21dd1f8601aa))
|
|
||||||
|
|
||||||
### [2.33.6](https://github.com/filebrowser/filebrowser/compare/v2.33.5...v2.33.6) (2025-06-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* remove incorrect default for password flag ([23bd8f6](https://github.com/filebrowser/filebrowser/commit/23bd8f67155081d707d4799393d3b1e2bebeaa34))
|
|
||||||
|
|
||||||
### [2.33.5](https://github.com/filebrowser/filebrowser/compare/v2.33.4...v2.33.5) (2025-06-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* update languages for project File Browser ([#5190](https://github.com/filebrowser/filebrowser/issues/5190)) ([f330764](https://github.com/filebrowser/filebrowser/commit/f33076462a133935ca97fb6c7345303fe350e167))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* actually register the czech language ([#5189](https://github.com/filebrowser/filebrowser/issues/5189)) ([0268506](https://github.com/filebrowser/filebrowser/commit/0268506f80d33d2d31e38055e12530241d27a11b))
|
|
||||||
|
|
||||||
### [2.33.4](https://github.com/filebrowser/filebrowser/compare/v2.33.3...v2.33.4) (2025-06-22)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* translation updates for project File Browser ([#5179](https://github.com/filebrowser/filebrowser/issues/5179)) ([f714e71](https://github.com/filebrowser/filebrowser/commit/f714e71a356c2301f394d651c9b6c467440508e3))
|
|
||||||
|
|
||||||
### [2.33.3](https://github.com/filebrowser/filebrowser/compare/v2.33.2...v2.33.3) (2025-06-22)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* keep command behavior in Dockerfile ([7c0c782](https://github.com/filebrowser/filebrowser/commit/7c0c7820efbbed2f0499353cc76ecb85d00ff7c3))
|
|
||||||
* update search hotkey in help prompt ([#5178](https://github.com/filebrowser/filebrowser/issues/5178)) ([2741616](https://github.com/filebrowser/filebrowser/commit/2741616473636d40b7e9f14c9906ada08d328c3c))
|
|
||||||
|
|
||||||
### [2.33.2](https://github.com/filebrowser/filebrowser/compare/v2.33.1...v2.33.2) (2025-06-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* create user dir on signup ([0ca8059](https://github.com/filebrowser/filebrowser/commit/0ca8059d8dea4fe079146471ce4f24acc96021f2))
|
|
||||||
|
|
||||||
### [2.33.1](https://github.com/filebrowser/filebrowser/compare/v2.33.0...v2.33.1) (2025-06-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* downloadUrl of file preview ([#3728](https://github.com/filebrowser/filebrowser/issues/3728)) ([8a14018](https://github.com/filebrowser/filebrowser/commit/8a14018861fe581672bbd27cdc3ae5691f70a108))
|
|
||||||
* remove auth query parameter from download and preview links ([cbb7124](https://github.com/filebrowser/filebrowser/commit/cbb712484d3bdabc033acaf3b696ef4f5865813d))
|
|
||||||
* search uses ctrl+shift+f instead of hijacking browser's ctrl+f ([#4638](https://github.com/filebrowser/filebrowser/issues/4638)) ([a02b297](https://github.com/filebrowser/filebrowser/commit/a02b2972ebde2a58806ad1377bad46e748b63166))
|
|
||||||
|
|
||||||
## [2.33.0](https://github.com/filebrowser/filebrowser/compare/v2.32.3...v2.33.0) (2025-06-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* improved docker image volumes and permissions ([#5160](https://github.com/filebrowser/filebrowser/issues/5160)) ([2e26393](https://github.com/filebrowser/filebrowser/commit/2e26393a022df0eaa9e08727407aba8b997aa728))
|
|
||||||
|
|
||||||
### [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)
|
## [2.31.0](https://github.com/filebrowser/filebrowser/compare/v2.30.0...v2.31.0) (2024-08-29)
|
||||||
|
|
||||||
|
|
||||||
@ -370,7 +87,7 @@ All notable changes to this project will be documented in this file. See [standa
|
|||||||
* close editor when click escape key ([#2947](https://github.com/filebrowser/filebrowser/issues/2947)) ([70c8261](https://github.com/filebrowser/filebrowser/commit/70c826133b8578b8712e6db8f762a15a076cd9a9))
|
* 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))
|
* 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))
|
* 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))
|
* freezing the list in the backgroud 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))
|
* 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))
|
* 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))
|
||||||
|
|
||||||
|
@ -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,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`.
|
|
||||||
|
|
41
Dockerfile
@ -1,32 +1,19 @@
|
|||||||
FROM alpine:3.22
|
FROM alpine:latest
|
||||||
|
RUN apk --update add ca-certificates \
|
||||||
|
mailcap \
|
||||||
|
curl \
|
||||||
|
jq
|
||||||
|
|
||||||
RUN apk update && \
|
COPY healthcheck.sh /healthcheck.sh
|
||||||
apk --no-cache add ca-certificates mailcap curl jq tini
|
RUN chmod +x /healthcheck.sh # Make the script executable
|
||||||
|
|
||||||
# Make user and create necessary directories
|
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
||||||
ENV UID=1000
|
CMD /healthcheck.sh || exit 1
|
||||||
ENV GID=1000
|
|
||||||
|
|
||||||
RUN addgroup -g $GID user && \
|
|
||||||
adduser -D -u $UID -G user user && \
|
|
||||||
mkdir -p /config /database /srv && \
|
|
||||||
chown -R user:user /config /database /srv
|
|
||||||
|
|
||||||
# Copy files and set permissions
|
|
||||||
COPY filebrowser /bin/filebrowser
|
|
||||||
COPY docker/common/ /
|
|
||||||
COPY docker/alpine/ /
|
|
||||||
|
|
||||||
RUN chown -R user:user /bin/filebrowser /defaults healthcheck.sh init.sh
|
|
||||||
|
|
||||||
# Define healthcheck script
|
|
||||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh
|
|
||||||
|
|
||||||
# Set the user, volumes and exposed ports
|
|
||||||
USER user
|
|
||||||
|
|
||||||
VOLUME /srv /config /database
|
|
||||||
|
|
||||||
|
VOLUME /srv
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
ENTRYPOINT [ "tini", "--", "/init.sh", "filebrowser", "--config", "/config/settings.json" ]
|
COPY docker_config.json /.filebrowser.json
|
||||||
|
COPY filebrowser /filebrowser
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/filebrowser" ]
|
@ -1,23 +1,16 @@
|
|||||||
FROM ghcr.io/linuxserver/baseimage-alpine:3.22
|
FROM ghcr.io/linuxserver/baseimage-alpine:3.17
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk --update add ca-certificates \
|
||||||
apk --no-cache add ca-certificates mailcap curl jq
|
mailcap \
|
||||||
|
curl
|
||||||
|
|
||||||
# Make user and create necessary directories
|
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s \
|
||||||
RUN mkdir -p /config /database /srv && \
|
CMD curl -f http://localhost/health || exit 1
|
||||||
chown -R abc:abc /config /database /srv
|
|
||||||
|
|
||||||
# Copy files and set permissions
|
# copy local files
|
||||||
COPY filebrowser /bin/filebrowser
|
COPY docker/root/ /
|
||||||
COPY docker/common/ /
|
COPY filebrowser /usr/bin/filebrowser
|
||||||
COPY docker/s6/ /
|
|
||||||
|
|
||||||
RUN chown -R abc:abc /bin/filebrowser /defaults healthcheck.sh
|
# ports and volumes
|
||||||
|
|
||||||
# Define healthcheck script
|
|
||||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh
|
|
||||||
|
|
||||||
# Set the volumes and exposed ports
|
|
||||||
VOLUME /srv /config /database
|
VOLUME /srv /config /database
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
16
Dockerfile.s6.aarch64
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM ghcr.io/linuxserver/baseimage-alpine:arm64v8-3.17
|
||||||
|
|
||||||
|
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
|
16
Dockerfile.s6.armhf
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM ghcr.io/linuxserver/baseimage-alpine:arm32v7-3.17
|
||||||
|
|
||||||
|
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
|
2
LICENSE
@ -187,7 +187,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2018 File Browser Contributors
|
Copyright 2018 File Browser contributors
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
24
Makefile
@ -3,14 +3,6 @@ include tools.mk
|
|||||||
|
|
||||||
LDFLAGS += -X "$(MODULE)/version.Version=$(VERSION)" -X "$(MODULE)/version.CommitSHA=$(VERSION_HASH)"
|
LDFLAGS += -X "$(MODULE)/version.Version=$(VERSION)" -X "$(MODULE)/version.CommitSHA=$(VERSION_HASH)"
|
||||||
|
|
||||||
SITE_DOCKER_FLAGS = \
|
|
||||||
-v $(CURDIR)/www:/docs \
|
|
||||||
-v $(CURDIR)/LICENSE:/docs/docs/LICENSE \
|
|
||||||
-v $(CURDIR)/SECURITY.md:/docs/docs/security.md \
|
|
||||||
-v $(CURDIR)/CHANGELOG.md:/docs/docs/changelog.md \
|
|
||||||
-v $(CURDIR)/CODE-OF-CONDUCT.md:/docs/docs/code-of-conduct.md \
|
|
||||||
-v $(CURDIR)/CONTRIBUTING.md:/docs/docs/contributing.md
|
|
||||||
|
|
||||||
## Build:
|
## Build:
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
@ -18,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
|
||||||
@ -29,7 +21,6 @@ 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
|
||||||
@ -40,7 +31,7 @@ lint: lint-frontend lint-backend ## 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
|
||||||
@ -61,17 +52,6 @@ clean: clean-tools ## Clean
|
|||||||
bump-version: $(standard-version) ## Bump app version
|
bump-version: $(standard-version) ## Bump app version
|
||||||
$Q ./scripts/bump_version.sh
|
$Q ./scripts/bump_version.sh
|
||||||
|
|
||||||
.PHONY: site
|
|
||||||
site: ## Build site
|
|
||||||
@rm -rf www/public
|
|
||||||
docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
|
||||||
docker run --rm $(SITE_DOCKER_FLAGS) filebrowser.site build -d "public"
|
|
||||||
|
|
||||||
.PHONY: site-serve
|
|
||||||
site-serve: ## Serve site for development
|
|
||||||
docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
|
||||||
docker run --rm -it -p 8000:8000 $(SITE_DOCKER_FLAGS) filebrowser.site
|
|
||||||
|
|
||||||
## Help:
|
## Help:
|
||||||
help: ## Show this help
|
help: ## Show this help
|
||||||
@echo ''
|
@echo ''
|
||||||
|
49
README.md
@ -2,37 +2,38 @@
|
|||||||
<img src="https://raw.githubusercontent.com/filebrowser/logo/master/banner.png" width="550"/>
|
<img src="https://raw.githubusercontent.com/filebrowser/logo/master/banner.png" width="550"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
[](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
|
[](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
|
||||||
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
|
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
|
||||||
[](http://godoc.org/github.com/filebrowser/filebrowser)
|
[](http://godoc.org/github.com/filebrowser/filebrowser)
|
||||||
[](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)
|
||||||
|
|
||||||
File Browser provides a file managing interface within a specified directory and it can be used to upload, delete, preview and edit your files. It is a **create-your-own-cloud**-kind of software where you can just install it on your server, direct it to a path and access your files through a nice web interface.
|
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.
|
||||||
|
|
||||||
## Documentation
|
## Demo
|
||||||
|
|
||||||
Documentation on how to install, configure, and contribute to this project is hosted at [filebrowser.org](https://filebrowser.org).
|
url: https://demo.filebrowser.org/
|
||||||
|
|
||||||
## Project Status
|
credentials: `demo`/`demo`
|
||||||
|
|
||||||
> [!WARNING]
|
## Features
|
||||||
>
|
|
||||||
> 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
|
Please refer to our docs at [https://filebrowser.org/features](https://filebrowser.org/features)
|
||||||
[discussions]: https://github.com/filebrowser/filebrowser/discussions
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
For installation instructions please refer to our docs at [https://filebrowser.org/installation](https://filebrowser.org/installation).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
Contributions are always welcome. To start contributing to this project, read our [guidelines](CONTRIBUTING.md) first.
|
If you're interested in contributing to this project, our docs are best places to start [https://filebrowser.org/contributing](https://filebrowser.org/contributing).
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[Apache License 2.0](LICENSE) © File Browser Contributors
|
|
||||||
|
@ -12,9 +12,7 @@ currently being supported with security updates.
|
|||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Vulnerabilities with critical impact should be reported on the [Security](https://github.com/filebrowser/filebrowser/security) page of this repository, which is a private way of communicating vulnerabilities to maintainers. This project is in maintenance-only mode and it can take a while until someone gets back to you.
|
Vulnerabilities should be reported to filebrowser@googlegroups.com - which is a private, maintainer-only group. Maintainers will attempt to respond to/confirm reports within 2-3 days, but if you believe your report to be "critical" to user safety and security, please note as such in the subject. We have tens of thousands of users using our software, and take security vulnerabilities seriously.
|
||||||
|
|
||||||
If it is not a critical vulnerability, please open an issue and we will categorize it as a security issue. By giving visibility, we can get more help from the community at fixing such issues.
|
|
||||||
|
|
||||||
When reporting an issue, where possible, please provide at least:
|
When reporting an issue, where possible, please provide at least:
|
||||||
|
|
||||||
@ -23,4 +21,6 @@ When reporting an issue, where possible, please provide at least:
|
|||||||
* Steps to reproduce
|
* Steps to reproduce
|
||||||
* Your recommended remediation(s), if any.
|
* Your recommended remediation(s), if any.
|
||||||
|
|
||||||
The File Browser team is a volunteer-only effort, and may reach back out for clarification.
|
The FileBrowser team is a volunteer-only effort, and may reach back out for clarification.
|
||||||
|
|
||||||
|
> Note: Please do not open public issues for security issues, as GitHub does not provide facility for private issues, and deleting the issue makes it hard to triage/respond back to the reporter.
|
||||||
|
@ -150,7 +150,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if u == nil {
|
if u == nil {
|
||||||
pass, err := users.ValidateAndHashPwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
|
pass, err := users.HashPwd(a.Cred.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -186,7 +186,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
|
|||||||
|
|
||||||
// update the password when it doesn't match the current
|
// update the password when it doesn't match the current
|
||||||
if p {
|
if p {
|
||||||
pass, err := users.ValidateAndHashPwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
|
pass, err := users.HashPwd(a.Cred.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
@ -18,50 +19,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, usr users.Store, _ *settings.Settings, srv *settings.Server) (*users.User, error) {
|
||||||
username := r.Header.Get(a.Header)
|
username := r.Header.Get(a.Header)
|
||||||
user, err := usr.Get(srv.Root, username)
|
user, err := usr.Get(srv.Root, username)
|
||||||
if errors.Is(err, fbErrors.ErrNotExist) {
|
if errors.Is(err, fbErrors.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 randomPasswordLength = settings.DefaultMinimumPasswordLength + 10
|
|
||||||
pwd, err := users.RandomPwd(randomPasswordLength)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var hashedRandomPassword string
|
|
||||||
hashedRandomPassword, err = users.ValidateAndHashPwd(pwd, setting.MinimumPasswordLength)
|
|
||||||
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
|
||||||
|
@ -32,7 +32,6 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
|||||||
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.Bool("create-user-dir", false, "generate user's home directory automatically")
|
||||||
flags.Uint("minimum-password-length", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
|
|
||||||
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")
|
||||||
@ -145,7 +144,6 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
|
|
||||||
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)
|
||||||
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
|
||||||
fmt.Fprintf(w, "Auth method:\t%s\n", set.AuthMethod)
|
fmt.Fprintf(w, "Auth method:\t%s\n", set.AuthMethod)
|
||||||
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
||||||
fmt.Fprintln(w, "\nBranding:")
|
fmt.Fprintln(w, "\nBranding:")
|
||||||
|
@ -56,7 +56,7 @@ The path must be for a json or yaml file.`,
|
|||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
var rawAuther interface{}
|
var rawAuther interface{}
|
||||||
if filepath.Ext(args[0]) != ".json" {
|
if filepath.Ext(args[0]) != ".json" { //nolint:goconst
|
||||||
rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{}))
|
rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{}))
|
||||||
} else {
|
} else {
|
||||||
rawAuther = file.Auther
|
rawAuther = file.Auther
|
||||||
|
@ -29,13 +29,12 @@ override the options.`,
|
|||||||
authMethod, auther := getAuthentication(flags)
|
authMethod, auther := getAuthentication(flags)
|
||||||
|
|
||||||
s := &settings.Settings{
|
s := &settings.Settings{
|
||||||
Key: generateKey(),
|
Key: generateKey(),
|
||||||
Signup: mustGetBool(flags, "signup"),
|
Signup: mustGetBool(flags, "signup"),
|
||||||
CreateUserDir: mustGetBool(flags, "create-user-dir"),
|
CreateUserDir: mustGetBool(flags, "create-user-dir"),
|
||||||
MinimumPasswordLength: mustGetUint(flags, "minimum-password-length"),
|
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"),
|
||||||
|
@ -51,8 +51,6 @@ you want to change. Other options will remain unchanged.`,
|
|||||||
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
|
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
|
||||||
case "create-user-dir":
|
case "create-user-dir":
|
||||||
set.CreateUserDir = mustGetBool(flags, flag.Name)
|
set.CreateUserDir = mustGetBool(flags, flag.Name)
|
||||||
case "minimum-password-length":
|
|
||||||
set.MinimumPasswordLength = mustGetUint(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":
|
||||||
|
161
cmd/root.go
@ -1,7 +1,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
@ -14,7 +13,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
@ -50,7 +48,7 @@ func init() {
|
|||||||
persistent.StringP("database", "d", "./filebrowser.db", "database path")
|
persistent.StringP("database", "d", "./filebrowser.db", "database path")
|
||||||
flags.Bool("noauth", false, "use the noauth auther when using quick setup")
|
flags.Bool("noauth", false, "use the noauth auther when using quick setup")
|
||||||
flags.String("username", "admin", "username for the first user when using quick config")
|
flags.String("username", "admin", "username for the first user when using quick config")
|
||||||
flags.String("password", "", "hashed password for the first user when using quick config")
|
flags.String("password", "", "hashed password for the first user when using quick config (default \"admin\")")
|
||||||
|
|
||||||
addServerFlags(flags)
|
addServerFlags(flags)
|
||||||
}
|
}
|
||||||
@ -63,14 +61,14 @@ func addServerFlags(flags *pflag.FlagSet) {
|
|||||||
flags.StringP("key", "k", "", "tls key")
|
flags.StringP("key", "k", "", "tls key")
|
||||||
flags.StringP("root", "r", ".", "root to prepend to relative paths")
|
flags.StringP("root", "r", ".", "root to prepend to relative paths")
|
||||||
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
|
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
|
||||||
flags.Uint32("socket-perm", 0666, "unix socket file permissions")
|
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.String("token-expiration-time", "2h", "user session timeout")
|
||||||
flags.Int("img-processors", 4, "image processors count") //nolint:mnd
|
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")
|
||||||
flags.Bool("disable-exec", true, "disables Command Runner feature")
|
flags.Bool("disable-exec", false, "disables Command Runner feature")
|
||||||
flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers")
|
flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +76,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
|
||||||
@ -110,7 +108,7 @@ 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, _ []string, d pythonData) {
|
||||||
log.Println(cfgFile)
|
log.Println(cfgFile)
|
||||||
@ -131,7 +129,7 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
if cacheDir != "" {
|
if cacheDir != "" {
|
||||||
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
|
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd
|
||||||
log.Fatalf("can't make directory %s: %s", cacheDir, err)
|
log.Fatalf("can't make directory %s: %s", cacheDir, err)
|
||||||
}
|
}
|
||||||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||||
@ -169,6 +167,10 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sigc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go cleanupHandler(listener, sigc)
|
||||||
|
|
||||||
assetsFs, err := fs.Sub(frontend.Assets(), "dist")
|
assetsFs, err := fs.Sub(frontend.Assets(), "dist")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -180,74 +182,61 @@ 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())
|
||||||
srv := &http.Server{
|
//nolint: gosec
|
||||||
Handler: handler,
|
if err := http.Serve(listener, handler); err != nil {
|
||||||
ReadHeaderTimeout: 60 * time.Second,
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
if err := srv.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
log.Fatalf("HTTP server error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Stopped serving new connections.")
|
|
||||||
}()
|
|
||||||
|
|
||||||
sigc := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
|
||||||
<-sigc
|
|
||||||
|
|
||||||
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) //nolint:mnd
|
|
||||||
defer shutdownRelease()
|
|
||||||
|
|
||||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
|
||||||
log.Fatalf("HTTP shutdown error: %v", err)
|
|
||||||
}
|
|
||||||
log.Println("Graceful shutdown complete.")
|
|
||||||
}, pythonConfig{allowNoDB: true}),
|
}, pythonConfig{allowNoDB: true}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfacer
|
||||||
|
sig := <-c
|
||||||
|
log.Printf("Caught signal %s: shutting down.", sig)
|
||||||
|
listener.Close()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
||||||
server, err := st.Settings.GetServer()
|
server, err := st.Settings.GetServer()
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "root"); set {
|
if val, set := getParamB(flags, "root"); set {
|
||||||
server.Root = val
|
server.Root = val
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "baseurl"); set {
|
if val, set := getParamB(flags, "baseurl"); set {
|
||||||
server.BaseURL = val
|
server.BaseURL = val
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "log"); set {
|
if val, set := getParamB(flags, "log"); set {
|
||||||
server.Log = val
|
server.Log = val
|
||||||
}
|
}
|
||||||
|
|
||||||
isSocketSet := false
|
isSocketSet := false
|
||||||
isAddrSet := false
|
isAddrSet := false
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "address"); set {
|
if val, set := getParamB(flags, "address"); set {
|
||||||
server.Address = val
|
server.Address = val
|
||||||
isAddrSet = isAddrSet || set
|
isAddrSet = isAddrSet || set
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "port"); set {
|
if val, set := getParamB(flags, "port"); set {
|
||||||
server.Port = val
|
server.Port = val
|
||||||
isAddrSet = isAddrSet || set
|
isAddrSet = isAddrSet || set
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "key"); set {
|
if val, set := getParamB(flags, "key"); set {
|
||||||
server.TLSKey = val
|
server.TLSKey = val
|
||||||
isAddrSet = isAddrSet || set
|
isAddrSet = isAddrSet || set
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "cert"); set {
|
if val, set := getParamB(flags, "cert"); set {
|
||||||
server.TLSCert = val
|
server.TLSCert = val
|
||||||
isAddrSet = isAddrSet || set
|
isAddrSet = isAddrSet || set
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "socket"); set {
|
if val, set := getParamB(flags, "socket"); set {
|
||||||
server.Socket = val
|
server.Socket = val
|
||||||
isSocketSet = isSocketSet || set
|
isSocketSet = isSocketSet || set
|
||||||
}
|
}
|
||||||
@ -261,69 +250,33 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
|||||||
server.Socket = ""
|
server.Socket = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
disableThumbnails := getBoolParam(flags, "disable-thumbnails")
|
_, disableThumbnails := getParamB(flags, "disable-thumbnails")
|
||||||
server.EnableThumbnails = !disableThumbnails
|
server.EnableThumbnails = !disableThumbnails
|
||||||
|
|
||||||
disablePreviewResize := getBoolParam(flags, "disable-preview-resize")
|
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
|
||||||
server.ResizePreview = !disablePreviewResize
|
server.ResizePreview = !disablePreviewResize
|
||||||
|
|
||||||
disableTypeDetectionByHeader := getBoolParam(flags, "disable-type-detection-by-header")
|
_, disableTypeDetectionByHeader := getParamB(flags, "disable-type-detection-by-header")
|
||||||
server.TypeDetectionByHeader = !disableTypeDetectionByHeader
|
server.TypeDetectionByHeader = !disableTypeDetectionByHeader
|
||||||
|
|
||||||
disableExec := getBoolParam(flags, "disable-exec")
|
_, disableExec := getParamB(flags, "disable-exec")
|
||||||
server.EnableExec = !disableExec
|
server.EnableExec = !disableExec
|
||||||
|
|
||||||
if server.EnableExec {
|
if val, set := getParamB(flags, "token-expiration-time"); set {
|
||||||
log.Println("WARNING: Command Runner feature enabled!")
|
|
||||||
log.Println("WARNING: This feature has known security vulnerabilities and should not")
|
|
||||||
log.Println("WARNING: you fully understand the risks involved. For more information")
|
|
||||||
log.Println("WARNING: read https://github.com/filebrowser/filebrowser/issues/5199")
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "token-expiration-time"); set {
|
|
||||||
server.TokenExpirationTime = val
|
server.TokenExpirationTime = val
|
||||||
}
|
}
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBoolParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
// getParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
||||||
//
|
//
|
||||||
// NOTE: we could simply bind the flags to viper and use IsSet.
|
// NOTE: we could simply bind the flags to viper and use IsSet.
|
||||||
// Although there is a bug on Viper that always returns true on IsSet
|
// Although there is a bug on Viper that always returns true on IsSet
|
||||||
// if a flag is binded. Our alternative way is to manually check
|
// if a flag is binded. Our alternative way is to manually check
|
||||||
// the flag and then the value from env/config/gotten by viper.
|
// the flag and then the value from env/config/gotten by viper.
|
||||||
// https://github.com/spf13/viper/pull/331
|
// https://github.com/spf13/viper/pull/331
|
||||||
func getBoolParamB(flags *pflag.FlagSet, key string) (value, ok bool) {
|
func getParamB(flags *pflag.FlagSet, key string) (string, bool) {
|
||||||
value, _ = flags.GetBool(key)
|
|
||||||
|
|
||||||
// If set on Flags, use it.
|
|
||||||
if flags.Changed(key) {
|
|
||||||
return value, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If set through viper (env, config), return it.
|
|
||||||
if v.IsSet(key) {
|
|
||||||
return v.GetBool(key), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise use default value on flags.
|
|
||||||
return value, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBoolParam(flags *pflag.FlagSet, key string) bool {
|
|
||||||
val, _ := getBoolParamB(flags, key)
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// getStringParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
|
||||||
//
|
|
||||||
// NOTE: we could simply bind the flags to viper and use IsSet.
|
|
||||||
// Although there is a bug on Viper that always returns true on IsSet
|
|
||||||
// if a flag is binded. Our alternative way is to manually check
|
|
||||||
// the flag and then the value from env/config/gotten by viper.
|
|
||||||
// https://github.com/spf13/viper/pull/331
|
|
||||||
func getStringParamB(flags *pflag.FlagSet, key string) (string, bool) {
|
|
||||||
value, _ := flags.GetString(key)
|
value, _ := flags.GetString(key)
|
||||||
|
|
||||||
// If set on Flags, use it.
|
// If set on Flags, use it.
|
||||||
@ -340,8 +293,8 @@ func getStringParamB(flags *pflag.FlagSet, key string) (string, bool) {
|
|||||||
return value, false
|
return value, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStringParam(flags *pflag.FlagSet, key string) string {
|
func getParam(flags *pflag.FlagSet, key string) string {
|
||||||
val, _ := getStringParamB(flags, key)
|
val, _ := getParamB(flags, key)
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,11 +318,10 @@ func setupLog(logMethod string) {
|
|||||||
|
|
||||||
func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
||||||
set := &settings.Settings{
|
set := &settings.Settings{
|
||||||
Key: generateKey(),
|
Key: generateKey(),
|
||||||
Signup: false,
|
Signup: false,
|
||||||
CreateUserDir: false,
|
CreateUserDir: false,
|
||||||
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
|
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
||||||
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
|
||||||
Defaults: settings.UserDefaults{
|
Defaults: settings.UserDefaults{
|
||||||
Scope: ".",
|
Scope: ".",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
@ -397,7 +349,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if _, noauth := getStringParamB(flags, "noauth"); noauth {
|
if _, noauth := getParamB(flags, "noauth"); noauth {
|
||||||
set.AuthMethod = auth.MethodNoAuth
|
set.AuthMethod = auth.MethodNoAuth
|
||||||
err = d.store.Auth.Save(&auth.NoAuth{})
|
err = d.store.Auth.Save(&auth.NoAuth{})
|
||||||
} else {
|
} else {
|
||||||
@ -410,29 +362,23 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
ser := &settings.Server{
|
ser := &settings.Server{
|
||||||
BaseURL: getStringParam(flags, "baseurl"),
|
BaseURL: getParam(flags, "baseurl"),
|
||||||
Port: getStringParam(flags, "port"),
|
Port: getParam(flags, "port"),
|
||||||
Log: getStringParam(flags, "log"),
|
Log: getParam(flags, "log"),
|
||||||
TLSKey: getStringParam(flags, "key"),
|
TLSKey: getParam(flags, "key"),
|
||||||
TLSCert: getStringParam(flags, "cert"),
|
TLSCert: getParam(flags, "cert"),
|
||||||
Address: getStringParam(flags, "address"),
|
Address: getParam(flags, "address"),
|
||||||
Root: getStringParam(flags, "root"),
|
Root: getParam(flags, "root"),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Settings.SaveServer(ser)
|
err = d.store.Settings.SaveServer(ser)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
username := getStringParam(flags, "username")
|
username := getParam(flags, "username")
|
||||||
password := getStringParam(flags, "password")
|
password := getParam(flags, "password")
|
||||||
|
|
||||||
if password == "" {
|
if password == "" {
|
||||||
var pwd string
|
password, err = users.HashPwd("admin")
|
||||||
pwd, err = users.RandomPwd(set.MinimumPasswordLength)
|
|
||||||
checkErr(err)
|
|
||||||
|
|
||||||
log.Println("Randomly generated password for user 'admin':", pwd)
|
|
||||||
|
|
||||||
password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength)
|
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,7 +414,6 @@ func initConfig() {
|
|||||||
v.SetEnvPrefix("FB")
|
v.SetEnvPrefix("FB")
|
||||||
v.AutomaticEnv()
|
v.AutomaticEnv()
|
||||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
|
||||||
|
|
||||||
if err := v.ReadInConfig(); err != nil {
|
if err := v.ReadInConfig(); err != nil {
|
||||||
var configParseError v.ConfigParseError
|
var configParseError v.ConfigParseError
|
||||||
|
@ -25,7 +25,7 @@ this version.`,
|
|||||||
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")
|
||||||
err := importer.Import(oldDB, oldConf, getStringParam(flags, "database"))
|
err := importer.Import(oldDB, oldConf, getParam(flags, "database"))
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ var usersAddCmd = &cobra.Command{
|
|||||||
checkErr(err)
|
checkErr(err)
|
||||||
getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
||||||
|
|
||||||
password, err := users.ValidateAndHashPwd(args[1], s.MinimumPasswordLength)
|
password, err := users.HashPwd(args[1])
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
user := &users.User{
|
user := &users.User{
|
||||||
|
@ -27,10 +27,8 @@ options you want to change.`,
|
|||||||
password := mustGetString(flags, "password")
|
password := mustGetString(flags, "password")
|
||||||
newUsername := mustGetString(flags, "username")
|
newUsername := mustGetString(flags, "username")
|
||||||
|
|
||||||
s, err := d.store.Settings.Get()
|
|
||||||
checkErr(err)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
err error
|
||||||
user *users.User
|
user *users.User
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,7 +64,7 @@ options you want to change.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if password != "" {
|
if password != "" {
|
||||||
user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength)
|
user.Password, err = users.HashPwd(password)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
cmd/utils.go
@ -14,7 +14,6 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/storage"
|
"github.com/filebrowser/filebrowser/v2/storage"
|
||||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||||
@ -73,7 +72,7 @@ func dbExists(path string) (bool, error) {
|
|||||||
d := filepath.Dir(path)
|
d := filepath.Dir(path)
|
||||||
_, err = os.Stat(d)
|
_, err = os.Stat(d)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet
|
if err := os.MkdirAll(d, 0700); err != nil { //nolint:govet,gomnd
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -87,7 +86,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
|||||||
return func(cmd *cobra.Command, args []string) {
|
return func(cmd *cobra.Command, args []string) {
|
||||||
data := pythonData{hadDB: true}
|
data := pythonData{hadDB: true}
|
||||||
|
|
||||||
path := getStringParam(cmd.Flags(), "database")
|
path := getParam(cmd.Flags(), "database")
|
||||||
absPath, err := filepath.Abs(path)
|
absPath, err := filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -106,7 +105,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
|||||||
|
|
||||||
log.Println("Using database: " + absPath)
|
log.Println("Using database: " + absPath)
|
||||||
data.hadDB = exists
|
data.hadDB = exists
|
||||||
db, err := storm.Open(path, storm.BoltOptions(files.PermFile, nil))
|
db, err := storm.Open(path)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
data.store, err = bolt.NewStorage(db)
|
data.store, err = bolt.NewStorage(db)
|
||||||
@ -125,7 +124,7 @@ func marshal(filename string, data interface{}) error {
|
|||||||
encoder := json.NewEncoder(fd)
|
encoder := json.NewEncoder(fd)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
return encoder.Encode(data)
|
return encoder.Encode(data)
|
||||||
case ".yml", ".yaml":
|
case ".yml", ".yaml": //nolint:goconst
|
||||||
encoder := yaml.NewEncoder(fd)
|
encoder := yaml.NewEncoder(fd)
|
||||||
return encoder.Encode(data)
|
return encoder.Encode(data)
|
||||||
default:
|
default:
|
||||||
@ -189,7 +188,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
|
||||||
|
@ -37,11 +37,11 @@ func (f *FileCache) Store(_ context.Context, key string, value []byte) error {
|
|||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
fileName := f.getFileName(key)
|
fileName := f.getFileName(key)
|
||||||
if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil {
|
if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil { //nolint:gomnd
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil {
|
if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil { //nolint:gomnd
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Ensure configuration exists
|
|
||||||
if [ ! -f "/config/settings.json" ]; then
|
|
||||||
cp -a /defaults/settings.json /config/settings.json
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$@"
|
|
@ -1,9 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
PORT=${FB_PORT:-$(jq -r .port /config/settings.json)}
|
|
||||||
ADDRESS=${FB_ADDRESS:-$(jq -r .address /config/settings.json)}
|
|
||||||
ADDRESS=${ADDRESS:-localhost}
|
|
||||||
|
|
||||||
curl -f http://$ADDRESS:$PORT/health || exit 1
|
|
5
docker/s6/custom-cont-init.d/20-config → docker/root/etc/cont-init.d/20-config
Executable file → Normal file
@ -1,6 +1,9 @@
|
|||||||
#!/usr/bin/with-contenv bash
|
#!/usr/bin/with-contenv bash
|
||||||
|
|
||||||
# Ensure configuration exists
|
# make folders
|
||||||
|
mkdir -p /database
|
||||||
|
|
||||||
|
# copy config
|
||||||
if [ ! -f "/config/settings.json" ]; then
|
if [ ! -f "/config/settings.json" ]; then
|
||||||
cp -a /defaults/settings.json /config/settings.json
|
cp -a /defaults/settings.json /config/settings.json
|
||||||
fi
|
fi
|
3
docker/root/etc/services.d/filebrowser/run
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
|
||||||
|
exec s6-setuidgid abc filebrowser -c /config/settings.json -d /database/filebrowser.db;
|
@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/with-contenv bash
|
|
||||||
|
|
||||||
exec s6-setuidgid abc filebrowser -c /config/settings.json;
|
|
@ -3,6 +3,6 @@
|
|||||||
"baseURL": "",
|
"baseURL": "",
|
||||||
"address": "",
|
"address": "",
|
||||||
"log": "stdout",
|
"log": "stdout",
|
||||||
"database": "/database/filebrowser.db",
|
"database": "/database.db",
|
||||||
"root": "/srv"
|
"root": "/srv"
|
||||||
}
|
}
|
@ -1,16 +1,12 @@
|
|||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import "errors"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrEmptyKey = errors.New("empty key")
|
ErrEmptyKey = errors.New("empty key")
|
||||||
ErrExist = errors.New("the resource already exists")
|
ErrExist = errors.New("the resource already exists")
|
||||||
ErrNotExist = errors.New("the resource does not exist")
|
ErrNotExist = errors.New("the resource does not exist")
|
||||||
ErrEmptyPassword = errors.New("password is empty")
|
ErrEmptyPassword = errors.New("password is empty")
|
||||||
ErrEasyPassword = errors.New("password is too easy")
|
|
||||||
ErrEmptyUsername = errors.New("username is empty")
|
ErrEmptyUsername = errors.New("username is empty")
|
||||||
ErrEmptyRequest = errors.New("empty request")
|
ErrEmptyRequest = errors.New("empty request")
|
||||||
ErrScopeIsRelative = errors.New("scope is a relative path")
|
ErrScopeIsRelative = errors.New("scope is a relative path")
|
||||||
@ -23,11 +19,3 @@ var (
|
|||||||
ErrSourceIsParent = errors.New("source is parent")
|
ErrSourceIsParent = errors.New("source is parent")
|
||||||
ErrRootUserDeletion = errors.New("user with id 1 can't be deleted")
|
ErrRootUserDeletion = errors.New("user with id 1 can't be deleted")
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrShortPassword struct {
|
|
||||||
MinimumLength uint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ErrShortPassword) Error() string {
|
|
||||||
return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength)
|
|
||||||
}
|
|
||||||
|
@ -27,8 +27,8 @@ import (
|
|||||||
"github.com/filebrowser/filebrowser/v2/rules"
|
"github.com/filebrowser/filebrowser/v2/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PermFile = 0640
|
const PermFile = 0644
|
||||||
const PermDir = 0750
|
const PermDir = 0755
|
||||||
|
|
||||||
var (
|
var (
|
||||||
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
||||||
@ -86,11 +86,6 @@ func NewFileInfo(opts *FileOptions) (*FileInfo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not expose the name of root directory.
|
|
||||||
if file.Path == "/" {
|
|
||||||
file.Name = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Expand {
|
if opts.Expand {
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
|
if err := file.readListing(opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
|
||||||
@ -222,6 +217,7 @@ func (i *FileInfo) RealPath() string {
|
|||||||
return i.Path
|
return i.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:goconst
|
||||||
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"
|
||||||
@ -318,7 +314,7 @@ func (i *FileInfo) readFirstBytes() []byte {
|
|||||||
}
|
}
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
buffer := make([]byte, 512)
|
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 && !errors.Is(err, io.EOF) {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
@ -16,6 +16,8 @@ type Listing struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplySort applies the sort order using .Order and .Sort
|
// ApplySort applies the sort order using .Order and .Sort
|
||||||
|
//
|
||||||
|
//nolint:goconst
|
||||||
func (l Listing) ApplySort() {
|
func (l Listing) ApplySort() {
|
||||||
// Check '.Order' to know how to sort
|
// Check '.Order' to know how to sort
|
||||||
if !l.Sorting.Asc {
|
if !l.Sorting.Asc {
|
||||||
|
@ -28,7 +28,7 @@ const (
|
|||||||
ContentTextHeaderValue = "text/plain"
|
ContentTextHeaderValue = "text/plain"
|
||||||
// ContentXMLHeaderValue header value for XML data.
|
// ContentXMLHeaderValue header value for XML data.
|
||||||
ContentXMLHeaderValue = "text/xml"
|
ContentXMLHeaderValue = "text/xml"
|
||||||
// ContentXMLUnreadableHeaderValue obsolete header value for XML.
|
// ContentXMLUnreadableHeaderValue obselete header value for XML.
|
||||||
ContentXMLUnreadableHeaderValue = "application/xml"
|
ContentXMLUnreadableHeaderValue = "application/xml"
|
||||||
// ContentMarkdownHeaderValue custom key/content type, the real is the text/html.
|
// ContentMarkdownHeaderValue custom key/content type, the real is the text/html.
|
||||||
ContentMarkdownHeaderValue = "text/markdown"
|
ContentMarkdownHeaderValue = "text/markdown"
|
||||||
|
@ -98,7 +98,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
|
||||||
|
27
frontend/.eslintrc.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/eslint-config-typescript",
|
||||||
|
"@vue/eslint-config-prettier"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"vue/multi-word-component-names": "off",
|
||||||
|
"vue/no-mutating-props": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"shallowOnly": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
// no-undef is already included in
|
||||||
|
// @vue/eslint-config-typescript
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,2 @@
|
|||||||
# Ignore artifacts:
|
# Ignore artifacts:
|
||||||
dist
|
dist
|
||||||
pnpm-lock.yaml
|
|
@ -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,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
@ -10,10 +10,18 @@
|
|||||||
|
|
||||||
<title>File Browser</title>
|
<title>File Browser</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg" />
|
<link
|
||||||
<link rel="shortcut icon" href="/img/icons/favicon.ico" />
|
rel="icon"
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/img/icons/apple-touch-icon.png" />
|
type="image/png"
|
||||||
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
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 -->
|
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||||
<link
|
<link
|
||||||
@ -23,6 +31,19 @@
|
|||||||
/>
|
/>
|
||||||
<meta name="theme-color" content="#2979ff" />
|
<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 -->
|
<!-- Inject Some Variables and generate the manifest json -->
|
||||||
<script>
|
<script>
|
||||||
// We can assign JSON directly
|
// We can assign JSON directly
|
||||||
|
10
frontend/jsconfig.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
7962
frontend/package-lock.json
generated
Normal file
@ -4,75 +4,70 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.0.0",
|
"npm": ">=7.0.0",
|
||||||
"pnpm": ">=9.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "pnpm run typecheck && vite build",
|
"build": "npm run typecheck && vite build",
|
||||||
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
||||||
"typecheck": "vue-tsc -p ./tsconfig.tsc.json --noEmit",
|
"typecheck": "vue-tsc -p ./tsconfig.json --noEmit",
|
||||||
"lint": "eslint src/",
|
"lint": "npm run typecheck && eslint --ext .vue,.ts src/",
|
||||||
"lint:fix": "eslint --fix src/",
|
"lint:fix": "eslint --ext .vue,.ts --fix src/",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"test": "playwright test"
|
"test": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chenfengyuan/vue-number-input": "^2.0.1",
|
"@chenfengyuan/vue-number-input": "^2.0.1",
|
||||||
"@vueuse/core": "^12.5.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@vueuse/integrations": "^12.5.0",
|
"@vueuse/integrations": "^10.9.0",
|
||||||
"ace-builds": "^1.37.5",
|
"ace-builds": "^1.32.9",
|
||||||
"core-js": "^3.40.0",
|
"core-js": "^3.36.1",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dompurify": "^3.2.6",
|
|
||||||
"epubjs": "^0.3.93",
|
|
||||||
"filesize": "^10.1.1",
|
"filesize": "^10.1.1",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^15.0.6",
|
"material-icons": "^1.13.12",
|
||||||
"material-icons": "^1.13.13",
|
"marked": "^14.1.0",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^2.1.7",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"qrcode.vue": "^3.4.1",
|
"qrcode.vue": "^3.4.1",
|
||||||
"tus-js-client": "^4.3.1",
|
"tus-js-client": "^4.1.0",
|
||||||
"utif": "^3.1.0",
|
"utif": "^3.1.0",
|
||||||
"video.js": "^8.21.0",
|
"video.js": "^8.10.0",
|
||||||
"videojs-hotkeys": "^0.2.28",
|
"videojs-hotkeys": "^0.2.28",
|
||||||
"videojs-mobile-ui": "^1.1.1",
|
"videojs-mobile-ui": "^1.1.1",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-final-modal": "^4.5.4",
|
"vue-final-modal": "^4.5.4",
|
||||||
"vue-i18n": "^11.1.2",
|
"vue-i18n": "^9.10.2",
|
||||||
"vue-lazyload": "^3.0.0",
|
"vue-lazyload": "^3.0.0",
|
||||||
"vue-reader": "^1.2.17",
|
"vue-reader": "^1.2.14",
|
||||||
"vue-router": "^4.3.0",
|
"vue-router": "^4.3.0",
|
||||||
"vue-toastification": "^2.0.0-rc.5"
|
"vue-toastification": "^2.0.0-rc.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/unplugin-vue-i18n": "^6.0.3",
|
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||||
"@playwright/test": "^1.50.0",
|
"@playwright/test": "^1.42.1",
|
||||||
"@tsconfig/node22": "^22.0.0",
|
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^22.10.10",
|
"@types/node": "^20.12.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||||
"@vitejs/plugin-legacy": "^6.0.0",
|
"@vitejs/plugin-legacy": "^5.3.2",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
"@vue/eslint-config-typescript": "^14.3.0",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^8.2.2",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-prettier": "^5.2.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-vue": "^9.24.0",
|
"eslint-plugin-vue": "^9.24.0",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.2.5",
|
||||||
"terser": "^5.37.0",
|
"terser": "^5.30.0",
|
||||||
"vite": "^6.1.6",
|
"vite": "^5.2.7",
|
||||||
"vite-plugin-compression2": "^1.0.0",
|
"vite-plugin-compression2": "^1.0.0",
|
||||||
"vue-tsc": "^2.2.0"
|
"vue-tsc": "^2.0.7"
|
||||||
},
|
}
|
||||||
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0"
|
|
||||||
}
|
}
|
||||||
|
5442
frontend/pnpm-lock.yaml
generated
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 7.2 KiB |
9
frontend/public/img/icons/browserconfig.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#455a64</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
BIN
frontend/public/img/icons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 843 B |
BIN
frontend/public/img/icons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 36 KiB |
BIN
frontend/public/img/icons/mstile-144x144.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
frontend/public/img/icons/mstile-150x150.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
frontend/public/img/icons/mstile-310x150.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
frontend/public/img/icons/mstile-310x310.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
frontend/public/img/icons/mstile-70x70.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
42
frontend/public/img/icons/safari-pinned-tab.svg
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M3245 6989 c-522 -39 -1042 -197 -1480 -449 -849 -488 -1459 -1308
|
||||||
|
-1673 -2250 -177 -776 -89 -1582 250 -2301 368 -778 1052 -1418 1857 -1739
|
||||||
|
903 -359 1927 -325 2812 92 778 368 1418 1052 1739 1857 359 903 325 1927 -92
|
||||||
|
2812 -296 627 -806 1175 -1423 1529 -587 338 -1308 500 -1990 449z m555 -580
|
||||||
|
c519 -51 1018 -245 1446 -565 788 -588 1229 -1526 1174 -2496 -16 -277 -58
|
||||||
|
-500 -145 -763 -144 -440 -378 -819 -710 -1150 -452 -452 -1005 -730 -1655
|
||||||
|
-832 -91 -14 -175 -18 -405 -18 -304 0 -369 6 -595 51 -1105 223 -1999 1092
|
||||||
|
-2259 2197 -52 221 -73 412 -73 667 0 397 64 732 204 1080 304 752 886 1334
|
||||||
|
1638 1638 431 174 895 238 1380 191z"/>
|
||||||
|
<path d="M2670 5215 c0 -13 -44 -15 -335 -15 -352 0 -383 -3 -399 -45 -3 -9
|
||||||
|
-6 -758 -6 -1663 0 -1168 -3 -1643 -11 -1632 -8 11 -9 8 -4 -15 3 -16 17 -41
|
||||||
|
31 -55 l24 -25 1530 0 1530 0 24 25 c14 14 26 36 27 50 1 14 1 711 1 1550 l-2
|
||||||
|
1526 -228 142 -229 142 -136 0 -137 0 0 -600 0 -600 -705 0 -705 0 0 615 0
|
||||||
|
615 -135 0 c-113 0 -135 -2 -135 -15z m-264 -190 c57 -29 89 -71 103 -137 35
|
||||||
|
-154 -98 -282 -258 -247 -55 12 -122 62 -148 113 -36 69 -12 186 49 243 62 58
|
||||||
|
170 70 254 28z m2316 -1702 c17 -15 18 -49 18 -670 l0 -653 -1245 0 -1245 0 0
|
||||||
|
654 c0 582 2 656 16 670 14 14 139 16 1226 16 1113 0 1213 -1 1230 -17z
|
||||||
|
m-2602 -1363 c40 -40 13 -100 -43 -100 -60 0 -88 59 -47 100 11 11 31 20 45
|
||||||
|
20 14 0 34 -9 45 -20z m2840 0 c41 -41 11 -100 -52 -100 -35 0 -58 24 -58 60
|
||||||
|
0 54 71 79 110 40z"/>
|
||||||
|
<path d="M2431 3091 c-7 -13 -7 -23 2 -35 11 -15 97 -16 1067 -14 l1055 3 0
|
||||||
|
30 0 30 -1057 3 c-1023 2 -1058 1 -1067 -17z"/>
|
||||||
|
<path d="M2436 2675 c-19 -19 -11 -41 17 -49 41 -11 2067 -7 2088 4 23 13 25
|
||||||
|
46 3 54 -9 3 -483 6 -1054 6 -919 0 -1040 -2 -1054 -15z"/>
|
||||||
|
<path d="M2447 2273 c-14 -4 -17 -13 -15 -36 l3 -32 1049 -3 c767 -1 1052 1
|
||||||
|
1062 9 20 16 17 47 -5 59 -20 10 -2055 13 -2094 3z"/>
|
||||||
|
<path d="M3822 5027 c-21 -23 -22 -30 -22 -293 0 -258 1 -271 20 -292 27 -29
|
||||||
|
56 -35 140 -30 56 3 75 8 93 26 22 22 22 26 22 298 l0 276 -24 19 c-19 16 -40
|
||||||
|
19 -115 19 -84 0 -95 -2 -114 -23z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -18,10 +18,18 @@
|
|||||||
|
|
||||||
<meta name="robots" content="noindex,nofollow" />
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="[{[ .StaticURL ]}]/img/icons/favicon.svg" />
|
<link
|
||||||
<link rel="shortcut icon" href="[{[ .StaticURL ]}]/img/icons/favicon.ico" />
|
rel="icon"
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png" />
|
type="image/png"
|
||||||
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
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
|
||||||
@ -34,6 +42,25 @@
|
|||||||
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
|
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 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="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Add to home screen for Windows -->
|
||||||
|
<meta
|
||||||
|
name="msapplication-TileImage"
|
||||||
|
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
|
// We can assign JSON directly
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { baseURL } from "@/utils/constants";
|
|
||||||
import { removePrefix } from "./utils";
|
import { removePrefix } from "./utils";
|
||||||
|
import { baseURL } from "@/utils/constants";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
const ssl = window.location.protocol === "https:";
|
const ssl = window.location.protocol === "https:";
|
||||||
const protocol = ssl ? "wss:" : "ws:";
|
const protocol = ssl ? "wss:" : "ws:";
|
||||||
@ -10,8 +11,10 @@ export default function command(
|
|||||||
onmessage: WebSocket["onmessage"],
|
onmessage: WebSocket["onmessage"],
|
||||||
onclose: WebSocket["onclose"]
|
onclose: WebSocket["onclose"]
|
||||||
) {
|
) {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}`;
|
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${authStore.jwt}`;
|
||||||
|
|
||||||
const conn = new window.WebSocket(url);
|
const conn = new window.WebSocket(url);
|
||||||
conn.onopen = () => conn.send(command);
|
conn.onopen = () => conn.send(command);
|
||||||
|
@ -1,23 +1,14 @@
|
|||||||
import { useAuthStore } from "@/stores/auth";
|
import { createURL, fetchURL, removePrefix } from "./utils";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { upload as postTus, useTus } from "./tus";
|
import { upload as postTus, useTus } from "./tus";
|
||||||
import { createURL, fetchURL, removePrefix, StatusError } from "./utils";
|
|
||||||
|
|
||||||
export async function fetch(url: string, signal?: AbortSignal) {
|
export async function fetch(url: string) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
const res = await fetchURL(`/api/resources${url}`, { signal });
|
|
||||||
|
|
||||||
let data: Resource;
|
const res = await fetchURL(`/api/resources${url}`, {});
|
||||||
try {
|
|
||||||
data = (await res.json()) as Resource;
|
const data = (await res.json()) as Resource;
|
||||||
} catch (e) {
|
|
||||||
// Check if the error is an intentional cancellation
|
|
||||||
if (e instanceof Error && e.name === "AbortError") {
|
|
||||||
throw new StatusError("000 No connection", 0, true);
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
data.url = `/files${url}`;
|
data.url = `/files${url}`;
|
||||||
|
|
||||||
if (data.isDir) {
|
if (data.isDir) {
|
||||||
@ -83,6 +74,11 @@ export function download(format: any, ...files: string[]) {
|
|||||||
url += `algo=${format}&`;
|
url += `algo=${format}&`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
if (authStore.jwt) {
|
||||||
|
url += `auth=${authStore.jwt}&`;
|
||||||
|
}
|
||||||
|
|
||||||
window.open(url);
|
window.open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +156,6 @@ function moveCopy(
|
|||||||
overwrite = false,
|
overwrite = false,
|
||||||
rename = false
|
rename = false
|
||||||
) {
|
) {
|
||||||
const layoutStore = useLayoutStore();
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
@ -171,7 +166,7 @@ function moveCopy(
|
|||||||
}&destination=${to}&override=${overwrite}&rename=${rename}`;
|
}&destination=${to}&override=${overwrite}&rename=${rename}`;
|
||||||
promises.push(resourceAction(url, "PATCH"));
|
promises.push(resourceAction(url, "PATCH"));
|
||||||
}
|
}
|
||||||
layoutStore.closeHovers();
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,18 +208,10 @@ export function getSubtitlesURL(file: ResourceItem) {
|
|||||||
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
|
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function usage(url: string, signal: AbortSignal) {
|
export async function usage(url: string) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
const res = await fetchURL(`/api/usage${url}`, { signal });
|
const res = await fetchURL(`/api/usage${url}`, {});
|
||||||
|
|
||||||
try {
|
return await res.json();
|
||||||
return await res.json();
|
|
||||||
} catch (e) {
|
|
||||||
// Check if the error is an intentional cancellation
|
|
||||||
if (e instanceof Error && e.name == "AbortError") {
|
|
||||||
throw new StatusError("000 No connection", 0, true);
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -71,5 +71,5 @@ export function getDownloadURL(res: Resource, inline = false) {
|
|||||||
...(res.token && { token: res.token }),
|
...(res.token && { token: res.token }),
|
||||||
};
|
};
|
||||||
|
|
||||||
return createURL("api/public/dl/" + res.hash + res.path, params);
|
return createURL("api/public/dl/" + res.hash + res.path, params, false);
|
||||||
}
|
}
|
||||||
|
@ -41,5 +41,5 @@ export async function create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getShareURL(share: Share) {
|
export function getShareURL(share: Share) {
|
||||||
return createURL("share/" + share.hash, {});
|
return createURL("share/" + share.hash, {}, false);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export async function create(user: IUser) {
|
|||||||
throw new StatusError(await res.text(), res.status);
|
throw new StatusError(await res.text(), res.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(user: Partial<IUser>, which = ["all"]) {
|
export async function update(user: IUser, which = ["all"]) {
|
||||||
await fetchURL(`/api/users/${user.id}`, {
|
await fetchURL(`/api/users/${user.id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -6,8 +6,7 @@ import { encodePath } from "@/utils/url";
|
|||||||
export class StatusError extends Error {
|
export class StatusError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
message: any,
|
message: any,
|
||||||
public status?: number,
|
public status?: number
|
||||||
public is_canceled?: boolean
|
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = "StatusError";
|
this.name = "StatusError";
|
||||||
@ -34,11 +33,7 @@ export async function fetchURL(
|
|||||||
},
|
},
|
||||||
...rest,
|
...rest,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Check if the error is an intentional cancellation
|
|
||||||
if (e instanceof Error && e.name === "AbortError") {
|
|
||||||
throw new StatusError("000 No connection", 0, true);
|
|
||||||
}
|
|
||||||
throw new StatusError("000 No connection", 0);
|
throw new StatusError("000 No connection", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,13 +76,23 @@ export function removePrefix(url: string): string {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createURL(endpoint: string, searchParams = {}): string {
|
export function createURL(endpoint: string, params = {}, auth = true): string {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
let prefix = baseURL;
|
let prefix = baseURL;
|
||||||
if (!prefix.endsWith("/")) {
|
if (!prefix.endsWith("/")) {
|
||||||
prefix = prefix + "/";
|
prefix = prefix + "/";
|
||||||
}
|
}
|
||||||
const url = new URL(prefix + encodePath(endpoint), origin);
|
const url = new URL(prefix + encodePath(endpoint), origin);
|
||||||
url.search = new URLSearchParams(searchParams).toString();
|
|
||||||
|
const searchParams: SearchParams = {
|
||||||
|
...(auth && { auth: authStore.jwt }),
|
||||||
|
...params,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key in searchParams) {
|
||||||
|
url.searchParams.set(key, searchParams[key]);
|
||||||
|
}
|
||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const items = computed(() => {
|
const items = computed(() => {
|
||||||
const relativePath = route.path.replace(props.base, "");
|
const relativePath = route.path.replace(props.base, "");
|
||||||
const parts = relativePath.split("/");
|
let parts = relativePath.split("/");
|
||||||
|
|
||||||
if (parts[0] === "") {
|
if (parts[0] === "") {
|
||||||
parts.shift();
|
parts.shift();
|
||||||
@ -44,7 +44,7 @@ const items = computed(() => {
|
|||||||
parts.pop();
|
parts.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
const breadcrumbs: BreadCrumb[] = [];
|
let breadcrumbs: BreadCrumb[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
for (let i = 0; i < parts.length; i++) {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
|
@ -46,7 +46,7 @@ https://raw.githubusercontent.com/dzwillia/vue-simple-progress/master/src/compon
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
// We're leaving this untouched as you can read in the beginning
|
// We're leaving this untouched as you can read in the beginning
|
||||||
const isNumber = function (n) {
|
var isNumber = function (n) {
|
||||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pct() {
|
pct() {
|
||||||
let pct = (this.val / this.max) * 100;
|
var pct = (this.val / this.max) * 100;
|
||||||
pct = pct.toFixed(2);
|
pct = pct.toFixed(2);
|
||||||
return Math.min(pct, this.max);
|
return Math.min(pct, this.max);
|
||||||
},
|
},
|
||||||
@ -160,7 +160,7 @@ export default {
|
|||||||
return isNumber(this.fontSize) ? this.fontSize : 13;
|
return isNumber(this.fontSize) ? this.fontSize : 13;
|
||||||
},
|
},
|
||||||
progress_style() {
|
progress_style() {
|
||||||
const style = {
|
var style = {
|
||||||
background: this.bgColor,
|
background: this.bgColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ export default {
|
|||||||
return style;
|
return style;
|
||||||
},
|
},
|
||||||
bar_style() {
|
bar_style() {
|
||||||
const style = {
|
var style = {
|
||||||
background: this.barColor,
|
background: this.barColor,
|
||||||
width: this.pct + "%",
|
width: this.pct + "%",
|
||||||
height: this.size_px + "px",
|
height: this.size_px + "px",
|
||||||
@ -198,7 +198,7 @@ export default {
|
|||||||
return style;
|
return style;
|
||||||
},
|
},
|
||||||
text_style() {
|
text_style() {
|
||||||
const style = {
|
var style = {
|
||||||
color: this.textFgColor,
|
color: this.textFgColor,
|
||||||
"font-size": this.text_font_size + "px",
|
"font-size": this.text_font_size + "px",
|
||||||
"text-align": this.textAlign,
|
"text-align": this.textAlign,
|
||||||
|
@ -50,7 +50,7 @@ import { useFileStore } from "@/stores/file";
|
|||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import { commands } from "@/api";
|
import { commands } from "@/api";
|
||||||
import { throttle } from "lodash-es";
|
import { throttle } from "lodash";
|
||||||
import { theme } from "@/utils/constants";
|
import { theme } from "@/utils/constants";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -163,7 +163,7 @@ export default {
|
|||||||
this.canInput = false;
|
this.canInput = false;
|
||||||
event.target.innerHTML = "";
|
event.target.innerHTML = "";
|
||||||
|
|
||||||
const results = {
|
let results = {
|
||||||
text: `${cmd}\n\n`,
|
text: `${cmd}\n\n`,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -180,7 +180,7 @@ export default {
|
|||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
results.text = results.text
|
results.text = results.text
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
.replace(/\u001b\[[0-9;]+m/g, "") // Filter ANSI color for now
|
.replace(/\u001b\[[0-9;]+m/g, "") // Filter ANSI color for now
|
||||||
.trimEnd();
|
.trimEnd();
|
||||||
this.canInput = true;
|
this.canInput = true;
|
||||||
|
@ -101,7 +101,7 @@
|
|||||||
href="https://github.com/filebrowser/filebrowser"
|
href="https://github.com/filebrowser/filebrowser"
|
||||||
>File Browser</a
|
>File Browser</a
|
||||||
>
|
>
|
||||||
<span> {{ " " }} {{ version }}</span>
|
<span> {{ version }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<a @click="help">{{ $t("sidebar.help") }}</a>
|
<a @click="help">{{ $t("sidebar.help") }}</a>
|
||||||
@ -129,7 +129,6 @@ import {
|
|||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import ProgressBar from "@/components/ProgressBar.vue";
|
import ProgressBar from "@/components/ProgressBar.vue";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import { StatusError } from "@/api/utils.js";
|
|
||||||
|
|
||||||
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
||||||
|
|
||||||
@ -137,7 +136,7 @@ export default {
|
|||||||
name: "sidebar",
|
name: "sidebar",
|
||||||
setup() {
|
setup() {
|
||||||
const usage = reactive(USAGE_DEFAULT);
|
const usage = reactive(USAGE_DEFAULT);
|
||||||
return { usage, usageAbortController: new AbortController() };
|
return { usage };
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
@ -158,11 +157,8 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
|
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
|
||||||
abortOngoingFetchUsage() {
|
|
||||||
this.usageAbortController.abort();
|
|
||||||
},
|
|
||||||
async fetchUsage() {
|
async fetchUsage() {
|
||||||
const path = this.$route.path.endsWith("/")
|
let path = this.$route.path.endsWith("/")
|
||||||
? this.$route.path
|
? this.$route.path
|
||||||
: this.$route.path + "/";
|
: this.$route.path + "/";
|
||||||
let usageStats = USAGE_DEFAULT;
|
let usageStats = USAGE_DEFAULT;
|
||||||
@ -170,18 +166,13 @@ export default {
|
|||||||
return Object.assign(this.usage, usageStats);
|
return Object.assign(this.usage, usageStats);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.abortOngoingFetchUsage();
|
let usage = await api.usage(path);
|
||||||
this.usageAbortController = new AbortController();
|
|
||||||
const usage = await api.usage(path, this.usageAbortController.signal);
|
|
||||||
usageStats = {
|
usageStats = {
|
||||||
used: prettyBytes(usage.used, { binary: true }),
|
used: prettyBytes(usage.used, { binary: true }),
|
||||||
total: prettyBytes(usage.total, { binary: true }),
|
total: prettyBytes(usage.total, { binary: true }),
|
||||||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof StatusError && error.is_canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$showError(error);
|
this.$showError(error);
|
||||||
}
|
}
|
||||||
return Object.assign(this.usage, usageStats);
|
return Object.assign(this.usage, usageStats);
|
||||||
@ -200,17 +191,9 @@ export default {
|
|||||||
logout: auth.logout,
|
logout: auth.logout,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route: {
|
isFiles(newValue) {
|
||||||
handler(to) {
|
newValue && this.fetchUsage();
|
||||||
if (to.path.includes("/files")) {
|
|
||||||
this.fetchUsage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
immediate: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
unmounted() {
|
|
||||||
this.abortOngoingFetchUsage();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -14,15 +14,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { throttle } from "lodash-es";
|
import throttle from "lodash/throttle";
|
||||||
import UTIF from "utif";
|
import UTIF from "utif";
|
||||||
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
src: string;
|
src: string;
|
||||||
moveDisabledTime?: number;
|
moveDisabledTime: number;
|
||||||
classList?: any[];
|
classList: any[];
|
||||||
zoomStep?: number;
|
zoomStep: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<IProps>(), {
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
@ -102,11 +102,10 @@ const decodeUTIF = () => {
|
|||||||
if (document?.location?.pathname === undefined) {
|
if (document?.location?.pathname === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const suff =
|
let suff = document.location.pathname.split(".")?.pop()?.toLowerCase() ?? "";
|
||||||
document.location.pathname.split(".")?.pop()?.toLowerCase() ?? "";
|
|
||||||
|
|
||||||
if (sufs.indexOf(suff) == -1) return false;
|
if (sufs.indexOf(suff) == -1) return false;
|
||||||
const xhr = new XMLHttpRequest();
|
let xhr = new XMLHttpRequest();
|
||||||
UTIF._xhrs.push(xhr);
|
UTIF._xhrs.push(xhr);
|
||||||
UTIF._imgs.push(imgex.value);
|
UTIF._imgs.push(imgex.value);
|
||||||
xhr.open("GET", props.src);
|
xhr.open("GET", props.src);
|
||||||
@ -231,7 +230,7 @@ const touchMove = (event: TouchEvent) => {
|
|||||||
if (imgex.value === null) {
|
if (imgex.value === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const step = imgex.value.width / 5;
|
let step = imgex.value.width / 5;
|
||||||
if (event.targetTouches.length === 2) {
|
if (event.targetTouches.length === 2) {
|
||||||
moveDisabled.value = true;
|
moveDisabled.value = true;
|
||||||
if (disabledTimer.value) clearTimeout(disabledTimer.value);
|
if (disabledTimer.value) clearTimeout(disabledTimer.value);
|
||||||
@ -240,9 +239,9 @@ const touchMove = (event: TouchEvent) => {
|
|||||||
props.moveDisabledTime
|
props.moveDisabledTime
|
||||||
);
|
);
|
||||||
|
|
||||||
const p1 = event.targetTouches[0];
|
let p1 = event.targetTouches[0];
|
||||||
const p2 = event.targetTouches[1];
|
let p2 = event.targetTouches[1];
|
||||||
const touchDistance = Math.sqrt(
|
let touchDistance = Math.sqrt(
|
||||||
Math.pow(p2.pageX - p1.pageX, 2) + Math.pow(p2.pageY - p1.pageY, 2)
|
Math.pow(p2.pageX - p1.pageX, 2) + Math.pow(p2.pageY - p1.pageY, 2)
|
||||||
);
|
);
|
||||||
if (!lastTouchDistance.value) {
|
if (!lastTouchDistance.value) {
|
||||||
@ -254,8 +253,8 @@ const touchMove = (event: TouchEvent) => {
|
|||||||
setZoom();
|
setZoom();
|
||||||
} else if (event.targetTouches.length === 1) {
|
} else if (event.targetTouches.length === 1) {
|
||||||
if (moveDisabled.value) return;
|
if (moveDisabled.value) return;
|
||||||
const x = event.targetTouches[0].pageX - (lastX.value ?? 0);
|
let x = event.targetTouches[0].pageX - (lastX.value ?? 0);
|
||||||
const y = event.targetTouches[0].pageY - (lastY.value ?? 0);
|
let y = event.targetTouches[0].pageY - (lastY.value ?? 0);
|
||||||
if (Math.abs(x) >= step && Math.abs(y) >= step) return;
|
if (Math.abs(x) >= step && Math.abs(y) >= step) return;
|
||||||
lastX.value = event.targetTouches[0].pageX;
|
lastX.value = event.targetTouches[0].pageX;
|
||||||
lastY.value = event.targetTouches[0].pageY;
|
lastY.value = event.targetTouches[0].pageY;
|
||||||
@ -269,8 +268,8 @@ const doMove = (x: number, y: number) => {
|
|||||||
}
|
}
|
||||||
const style = imgex.value.style;
|
const style = imgex.value.style;
|
||||||
|
|
||||||
const posX = pxStringToNumber(style.left) + x;
|
let posX = pxStringToNumber(style.left) + x;
|
||||||
const posY = pxStringToNumber(style.top) + y;
|
let posY = pxStringToNumber(style.top) + y;
|
||||||
|
|
||||||
style.left = posX + "px";
|
style.left = posX + "px";
|
||||||
style.top = posY + "px";
|
style.top = posY + "px";
|
||||||
|
@ -8,13 +8,6 @@
|
|||||||
@dragover="dragOver"
|
@dragover="dragOver"
|
||||||
@drop="drop"
|
@drop="drop"
|
||||||
@click="itemClick"
|
@click="itemClick"
|
||||||
@mousedown="handleMouseDown"
|
|
||||||
@mouseup="handleMouseUp"
|
|
||||||
@mouseleave="handleMouseLeave"
|
|
||||||
@touchstart="handleTouchStart"
|
|
||||||
@touchend="handleTouchEnd"
|
|
||||||
@touchcancel="handleTouchCancel"
|
|
||||||
@touchmove="handleTouchMove"
|
|
||||||
:data-dir="isDir"
|
:data-dir="isDir"
|
||||||
:data-type="type"
|
:data-type="type"
|
||||||
:aria-label="name"
|
:aria-label="name"
|
||||||
@ -57,12 +50,6 @@ import { useRouter } from "vue-router";
|
|||||||
|
|
||||||
const touches = ref<number>(0);
|
const touches = ref<number>(0);
|
||||||
|
|
||||||
const longPressTimer = ref<number | null>(null);
|
|
||||||
const longPressTriggered = ref<boolean>(false);
|
|
||||||
const longPressDelay = ref<number>(500);
|
|
||||||
const startPosition = ref<{ x: number; y: number } | null>(null);
|
|
||||||
const moveThreshold = ref<number>(10);
|
|
||||||
|
|
||||||
const $showError = inject<IToastError>("$showError")!;
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -95,7 +82,7 @@ const isDraggable = computed(
|
|||||||
const canDrop = computed(() => {
|
const canDrop = computed(() => {
|
||||||
if (!props.isDir || props.readOnly) return false;
|
if (!props.isDir || props.readOnly) return false;
|
||||||
|
|
||||||
for (const i of fileStore.selected) {
|
for (let i of fileStore.selected) {
|
||||||
if (fileStore.req?.items[i].url === props.url) {
|
if (fileStore.req?.items[i].url === props.url) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -169,9 +156,9 @@ const drop = async (event: Event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const items: any[] = [];
|
let items: any[] = [];
|
||||||
|
|
||||||
for (const i of fileStore.selected) {
|
for (let i of fileStore.selected) {
|
||||||
if (fileStore.req) {
|
if (fileStore.req) {
|
||||||
items.push({
|
items.push({
|
||||||
from: fileStore.req?.items[i].url,
|
from: fileStore.req?.items[i].url,
|
||||||
@ -185,10 +172,10 @@ const drop = async (event: Event) => {
|
|||||||
if (el === null) {
|
if (el === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const path = el.__vue__.url;
|
let path = el.__vue__.url;
|
||||||
const baseItems = (await api.fetch(path)).items;
|
let baseItems = (await api.fetch(path)).items;
|
||||||
|
|
||||||
const action = (overwrite: boolean, rename: boolean) => {
|
let action = (overwrite: boolean, rename: boolean) => {
|
||||||
api
|
api
|
||||||
.move(items, overwrite, rename)
|
.move(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -197,7 +184,7 @@ const drop = async (event: Event) => {
|
|||||||
.catch($showError);
|
.catch($showError);
|
||||||
};
|
};
|
||||||
|
|
||||||
const conflict = upload.checkConflict(items, baseItems);
|
let conflict = upload.checkConflict(items, baseItems);
|
||||||
|
|
||||||
let overwrite = false;
|
let overwrite = false;
|
||||||
let rename = false;
|
let rename = false;
|
||||||
@ -222,12 +209,6 @@ const drop = async (event: Event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const itemClick = (event: Event | KeyboardEvent) => {
|
const itemClick = (event: Event | KeyboardEvent) => {
|
||||||
// If long press was triggered, prevent normal click behavior
|
|
||||||
if (longPressTriggered.value) {
|
|
||||||
longPressTriggered.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
singleClick.value &&
|
singleClick.value &&
|
||||||
!(event as KeyboardEvent).ctrlKey &&
|
!(event as KeyboardEvent).ctrlKey &&
|
||||||
@ -300,76 +281,4 @@ const getExtension = (fileName: string): string => {
|
|||||||
}
|
}
|
||||||
return fileName.substring(lastDotIndex);
|
return fileName.substring(lastDotIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Long-press helper functions
|
|
||||||
const startLongPress = (clientX: number, clientY: number) => {
|
|
||||||
startPosition.value = { x: clientX, y: clientY };
|
|
||||||
longPressTimer.value = window.setTimeout(() => {
|
|
||||||
handleLongPress();
|
|
||||||
}, longPressDelay.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelLongPress = () => {
|
|
||||||
if (longPressTimer.value !== null) {
|
|
||||||
window.clearTimeout(longPressTimer.value);
|
|
||||||
longPressTimer.value = null;
|
|
||||||
}
|
|
||||||
startPosition.value = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLongPress = () => {
|
|
||||||
if (singleClick.value) {
|
|
||||||
longPressTriggered.value = true;
|
|
||||||
click(new Event("longpress"));
|
|
||||||
}
|
|
||||||
cancelLongPress();
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkMovement = (clientX: number, clientY: number): boolean => {
|
|
||||||
if (!startPosition.value) return false;
|
|
||||||
|
|
||||||
const deltaX = Math.abs(clientX - startPosition.value.x);
|
|
||||||
const deltaY = Math.abs(clientY - startPosition.value.y);
|
|
||||||
|
|
||||||
return deltaX > moveThreshold.value || deltaY > moveThreshold.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Event handlers
|
|
||||||
const handleMouseDown = (event: MouseEvent) => {
|
|
||||||
if (event.button === 0) {
|
|
||||||
startLongPress(event.clientX, event.clientY);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
|
||||||
cancelLongPress();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
cancelLongPress();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTouchStart = (event: TouchEvent) => {
|
|
||||||
if (event.touches.length === 1) {
|
|
||||||
const touch = event.touches[0];
|
|
||||||
startLongPress(touch.clientX, touch.clientY);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTouchEnd = () => {
|
|
||||||
cancelLongPress();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTouchCancel = () => {
|
|
||||||
cancelLongPress();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTouchMove = (event: TouchEvent) => {
|
|
||||||
if (event.touches.length === 1 && startPosition.value) {
|
|
||||||
const touch = event.touches[0];
|
|
||||||
if (checkMovement(touch.clientX, touch.clientY)) {
|
|
||||||
cancelLongPress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -62,8 +62,7 @@ const initVideoPlayer = async () => {
|
|||||||
const languagePack = await (
|
const languagePack = await (
|
||||||
languageImports[lang] || languageImports.en
|
languageImports[lang] || languageImports.en
|
||||||
)?.();
|
)?.();
|
||||||
const code = languageImports[lang] ? lang : "en";
|
videojs.addLanguage("videoPlayerLocal", languagePack.default);
|
||||||
videojs.addLanguage(code, languagePack.default);
|
|
||||||
sourceType.value = "";
|
sourceType.value = "";
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -71,19 +70,14 @@ const initVideoPlayer = async () => {
|
|||||||
|
|
||||||
const srcOpt = { sources: { src: props.source, type: sourceType.value } };
|
const srcOpt = { sources: { src: props.source, type: sourceType.value } };
|
||||||
//Supporting localized language display.
|
//Supporting localized language display.
|
||||||
const langOpt = { language: code };
|
const langOpt = { language: "videoPlayerLocal" };
|
||||||
// support for playback at different speeds.
|
// support for playback at different speeds.
|
||||||
const playbackRatesOpt = { playbackRates: [0.5, 1, 1.5, 2, 2.5, 3] };
|
const playbackRatesOpt = { playbackRates: [0.5, 1, 1.5, 2, 2.5, 3] };
|
||||||
const options = getOptions(
|
let options = getOptions(props.options, langOpt, srcOpt, playbackRatesOpt);
|
||||||
props.options,
|
|
||||||
langOpt,
|
|
||||||
srcOpt,
|
|
||||||
playbackRatesOpt
|
|
||||||
);
|
|
||||||
player.value = videojs(videoPlayer.value!, options, () => {});
|
player.value = videojs(videoPlayer.value!, options, () => {});
|
||||||
|
|
||||||
// TODO: need to test on mobile
|
// TODO: need to test on mobile
|
||||||
// @ts-expect-error no ts definition for mobileUi
|
// @ts-ignore
|
||||||
player.value!.mobileUi();
|
player.value!.mobileUi();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error initializing video player:", error);
|
console.error("Error initializing video player:", error);
|
||||||
@ -126,7 +120,7 @@ const subLabel = (subUrl: string) => {
|
|||||||
let url: URL;
|
let url: URL;
|
||||||
try {
|
try {
|
||||||
url = new URL(subUrl);
|
url = new URL(subUrl);
|
||||||
} catch {
|
} catch (_) {
|
||||||
// treat it as a relative url
|
// treat it as a relative url
|
||||||
// we only need this for filename
|
// we only need this for filename
|
||||||
url = new URL(subUrl, window.location.origin);
|
url = new URL(subUrl, window.location.origin);
|
||||||
|
@ -82,10 +82,10 @@ export default {
|
|||||||
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
||||||
copy: async function (event) {
|
copy: async function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const items = [];
|
let items = [];
|
||||||
|
|
||||||
// Create a new promise for each file.
|
// Create a new promise for each file.
|
||||||
for (const item of this.selected) {
|
for (let item of this.selected) {
|
||||||
items.push({
|
items.push({
|
||||||
from: this.req.items[item].url,
|
from: this.req.items[item].url,
|
||||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||||
@ -93,7 +93,7 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = async (overwrite, rename) => {
|
let action = async (overwrite, rename) => {
|
||||||
buttons.loading("copy");
|
buttons.loading("copy");
|
||||||
|
|
||||||
await api
|
await api
|
||||||
@ -122,8 +122,8 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dstItems = (await api.fetch(this.dest)).items;
|
let dstItems = (await api.fetch(this.dest)).items;
|
||||||
const conflict = upload.checkConflict(items, dstItems);
|
let conflict = upload.checkConflict(items, dstItems);
|
||||||
|
|
||||||
let overwrite = false;
|
let overwrite = false;
|
||||||
let rename = false;
|
let rename = false;
|
||||||
|
@ -74,8 +74,8 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = [];
|
let promises = [];
|
||||||
for (const index of this.selected) {
|
for (let index of this.selected) {
|
||||||
promises.push(api.remove(this.req.items[index].url));
|
promises.push(api.remove(this.req.items[index].url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ export default {
|
|||||||
submit: async function () {
|
submit: async function () {
|
||||||
this.updateRequest(null);
|
this.updateRequest(null);
|
||||||
|
|
||||||
const uri = url.removeLastDir(this.$route.path) + "/";
|
let uri = url.removeLastDir(this.$route.path) + "/";
|
||||||
this.$router.push({ path: uri });
|
this.$router.push({ path: uri });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -31,16 +31,9 @@ import { useFileStore } from "@/stores/file";
|
|||||||
|
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files } from "@/api";
|
import { files } from "@/api";
|
||||||
import { StatusError } from "@/api/utils.js";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "file-list",
|
name: "file-list",
|
||||||
props: {
|
|
||||||
exclude: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
items: [],
|
items: [],
|
||||||
@ -50,7 +43,6 @@ export default {
|
|||||||
},
|
},
|
||||||
selected: null,
|
selected: null,
|
||||||
current: window.location.pathname,
|
current: window.location.pathname,
|
||||||
nextAbortController: new AbortController(),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: ["$showError"],
|
inject: ["$showError"],
|
||||||
@ -64,13 +56,7 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.fillOptions(this.req);
|
this.fillOptions(this.req);
|
||||||
},
|
},
|
||||||
unmounted() {
|
|
||||||
this.abortOngoingNext();
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
abortOngoingNext() {
|
|
||||||
this.nextAbortController.abort();
|
|
||||||
},
|
|
||||||
fillOptions(req) {
|
fillOptions(req) {
|
||||||
// Sets the current path and resets
|
// Sets the current path and resets
|
||||||
// the current items.
|
// the current items.
|
||||||
@ -94,9 +80,8 @@ export default {
|
|||||||
|
|
||||||
// Otherwise we add every directory to the
|
// Otherwise we add every directory to the
|
||||||
// move options.
|
// move options.
|
||||||
for (const item of req.items) {
|
for (let item of req.items) {
|
||||||
if (!item.isDir) continue;
|
if (!item.isDir) continue;
|
||||||
if (this.exclude?.includes(item.url)) continue;
|
|
||||||
|
|
||||||
this.items.push({
|
this.items.push({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
@ -108,21 +93,12 @@ export default {
|
|||||||
// Retrieves the URL of the directory the user
|
// Retrieves the URL of the directory the user
|
||||||
// just clicked in and fill the options with its
|
// just clicked in and fill the options with its
|
||||||
// content.
|
// content.
|
||||||
const uri = event.currentTarget.dataset.url;
|
let uri = event.currentTarget.dataset.url;
|
||||||
this.abortOngoingNext();
|
|
||||||
this.nextAbortController = new AbortController();
|
files.fetch(uri).then(this.fillOptions).catch(this.$showError);
|
||||||
files
|
|
||||||
.fetch(uri, this.nextAbortController.signal)
|
|
||||||
.then(this.fillOptions)
|
|
||||||
.catch((e) => {
|
|
||||||
if (e instanceof StatusError && e.is_canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$showError(e);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
touchstart(event) {
|
touchstart(event) {
|
||||||
const url = event.currentTarget.dataset.url;
|
let url = event.currentTarget.dataset.url;
|
||||||
|
|
||||||
// In 300 milliseconds, we shall reset the count.
|
// In 300 milliseconds, we shall reset the count.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<li><strong>DEL</strong> - {{ $t("help.del") }}</li>
|
<li><strong>DEL</strong> - {{ $t("help.del") }}</li>
|
||||||
<li><strong>ESC</strong> - {{ $t("help.esc") }}</li>
|
<li><strong>ESC</strong> - {{ $t("help.esc") }}</li>
|
||||||
<li><strong>CTRL + S</strong> - {{ $t("help.ctrl.s") }}</li>
|
<li><strong>CTRL + S</strong> - {{ $t("help.ctrl.s") }}</li>
|
||||||
<li><strong>CTRL + SHIFT + F</strong> - {{ $t("help.ctrl.f") }}</li>
|
<li><strong>CTRL + F</strong> - {{ $t("help.ctrl.f") }}</li>
|
||||||
<li><strong>CTRL + Click</strong> - {{ $t("help.ctrl.click") }}</li>
|
<li><strong>CTRL + Click</strong> - {{ $t("help.ctrl.click") }}</li>
|
||||||
<li><strong>Click</strong> - {{ $t("help.click") }}</li>
|
<li><strong>Click</strong> - {{ $t("help.click") }}</li>
|
||||||
<li><strong>Double click</strong> - {{ $t("help.doubleClick") }}</li>
|
<li><strong>Double click</strong> - {{ $t("help.doubleClick") }}</li>
|
||||||
|
@ -124,7 +124,7 @@ export default {
|
|||||||
|
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
|
||||||
for (const selected of this.selected) {
|
for (let selected of this.selected) {
|
||||||
sum += this.req.items[selected].size;
|
sum += this.req.items[selected].size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
<file-list
|
<file-list
|
||||||
ref="fileList"
|
ref="fileList"
|
||||||
@update:selected="(val) => (dest = val)"
|
@update:selected="(val) => (dest = val)"
|
||||||
:exclude="excludedFolders"
|
|
||||||
tabindex="1"
|
tabindex="1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -77,19 +76,14 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState(useFileStore, ["req", "selected"]),
|
...mapState(useFileStore, ["req", "selected"]),
|
||||||
...mapState(useAuthStore, ["user"]),
|
...mapState(useAuthStore, ["user"]),
|
||||||
excludedFolders() {
|
|
||||||
return this.selected
|
|
||||||
.filter((idx) => this.req.items[idx].isDir)
|
|
||||||
.map((idx) => this.req.items[idx].url);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
||||||
move: async function (event) {
|
move: async function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const items = [];
|
let items = [];
|
||||||
|
|
||||||
for (const item of this.selected) {
|
for (let item of this.selected) {
|
||||||
items.push({
|
items.push({
|
||||||
from: this.req.items[item].url,
|
from: this.req.items[item].url,
|
||||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||||
@ -97,7 +91,7 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = async (overwrite, rename) => {
|
let action = async (overwrite, rename) => {
|
||||||
buttons.loading("move");
|
buttons.loading("move");
|
||||||
|
|
||||||
await api
|
await api
|
||||||
@ -112,8 +106,8 @@ export default {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const dstItems = (await api.fetch(this.dest)).items;
|
let dstItems = (await api.fetch(this.dest)).items;
|
||||||
const conflict = upload.checkConflict(items, dstItems);
|
let conflict = upload.checkConflict(items, dstItems);
|
||||||
|
|
||||||
let overwrite = false;
|
let overwrite = false;
|
||||||
let rename = false;
|
let rename = false;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
import { ModalsContainer, useModal } from "vue-final-modal";
|
import { ModalsContainer, useModal } from "vue-final-modal";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
@ -30,6 +30,8 @@ const layoutStore = useLayoutStore();
|
|||||||
|
|
||||||
const { currentPromptName } = storeToRefs(layoutStore);
|
const { currentPromptName } = storeToRefs(layoutStore);
|
||||||
|
|
||||||
|
const closeModal = ref<() => Promise<string>>();
|
||||||
|
|
||||||
const components = new Map<string, any>([
|
const components = new Map<string, any>([
|
||||||
["info", Info],
|
["info", Info],
|
||||||
["help", Help],
|
["help", Help],
|
||||||
@ -50,6 +52,11 @@ const components = new Map<string, any>([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
watch(currentPromptName, (newValue) => {
|
watch(currentPromptName, (newValue) => {
|
||||||
|
if (closeModal.value) {
|
||||||
|
closeModal.value();
|
||||||
|
closeModal.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const modal = components.get(newValue!);
|
const modal = components.get(newValue!);
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
|
|
||||||
@ -60,7 +67,7 @@ watch(currentPromptName, (newValue) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
layoutStore.setCloseOnPrompt(close, newValue!);
|
closeModal.value = close;
|
||||||
open();
|
open();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,6 +32,16 @@
|
|||||||
<i class="material-icons">content_paste</i>
|
<i class="material-icons">content_paste</i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="small" v-if="hasDownloadLink()">
|
||||||
|
<button
|
||||||
|
class="action copy-clipboard"
|
||||||
|
:aria-label="$t('buttons.copyDownloadLinkToClipboard')"
|
||||||
|
:title="$t('buttons.copyDownloadLinkToClipboard')"
|
||||||
|
@click="copyToClipboard(buildDownloadLink(link))"
|
||||||
|
>
|
||||||
|
<i class="material-icons">content_paste_go</i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
<td class="small">
|
<td class="small">
|
||||||
<button
|
<button
|
||||||
class="action"
|
class="action"
|
||||||
@ -132,7 +142,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState } from "pinia";
|
import { mapActions, mapState } from "pinia";
|
||||||
import { useFileStore } from "@/stores/file";
|
import { useFileStore } from "@/stores/file";
|
||||||
import { share as api } from "@/api";
|
import { share as api, pub as pub_api } from "@/api";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
import { copy } from "@/utils/clipboard";
|
import { copy } from "@/utils/clipboard";
|
||||||
@ -186,23 +196,13 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
copyToClipboard: function (text) {
|
copyToClipboard: function (text) {
|
||||||
copy({ text }).then(
|
copy(text).then(
|
||||||
() => {
|
() => {
|
||||||
// clipboard successfully set
|
// clipboard successfully set
|
||||||
this.$showSuccess(this.$t("success.linkCopied"));
|
this.$showSuccess(this.$t("success.linkCopied"));
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
// clipboard write failed
|
// clipboard write failed
|
||||||
copy({ text }, { permission: true }).then(
|
|
||||||
() => {
|
|
||||||
// clipboard successfully set
|
|
||||||
this.$showSuccess(this.$t("success.linkCopied"));
|
|
||||||
},
|
|
||||||
(e) => {
|
|
||||||
// clipboard write failed
|
|
||||||
this.$showError(e);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -247,6 +247,14 @@ export default {
|
|||||||
buildLink(share) {
|
buildLink(share) {
|
||||||
return api.getShareURL(share);
|
return api.getShareURL(share);
|
||||||
},
|
},
|
||||||
|
hasDownloadLink() {
|
||||||
|
return (
|
||||||
|
this.selected.length === 1 && !this.req.items[this.selected[0]].isDir
|
||||||
|
);
|
||||||
|
},
|
||||||
|
buildDownloadLink(share) {
|
||||||
|
return pub_api.getDownloadURL(share);
|
||||||
|
},
|
||||||
sort() {
|
sort() {
|
||||||
this.links = this.links.sort((a, b) => {
|
this.links = this.links.sort((a, b) => {
|
||||||
if (a.expire === 0) return -1;
|
if (a.expire === 0) return -1;
|
||||||
|
@ -48,10 +48,12 @@ const layoutStore = useLayoutStore();
|
|||||||
|
|
||||||
// TODO: this is a copy of the same function in FileListing.vue
|
// TODO: this is a copy of the same function in FileListing.vue
|
||||||
const uploadInput = (event: Event) => {
|
const uploadInput = (event: Event) => {
|
||||||
const files = (event.currentTarget as HTMLInputElement)?.files;
|
layoutStore.closeHovers();
|
||||||
|
|
||||||
|
let files = (event.currentTarget as HTMLInputElement)?.files;
|
||||||
if (files === null) return;
|
if (files === null) return;
|
||||||
|
|
||||||
const folder_upload = !!files[0].webkitRelativePath;
|
let folder_upload = !!files[0].webkitRelativePath;
|
||||||
|
|
||||||
const uploadFiles: UploadList = [];
|
const uploadFiles: UploadList = [];
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
@ -66,8 +68,8 @@ const uploadInput = (event: Event) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = route.path.endsWith("/") ? route.path : route.path + "/";
|
let path = route.path.endsWith("/") ? route.path : route.path + "/";
|
||||||
const conflict = upload.checkConflict(uploadFiles, fileStore.req!.items);
|
let conflict = upload.checkConflict(uploadFiles, fileStore.req!.items);
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
layoutStore.showHover({
|
layoutStore.showHover({
|
||||||
|
@ -13,13 +13,12 @@ export default {
|
|||||||
name: "languages",
|
name: "languages",
|
||||||
props: ["locale"],
|
props: ["locale"],
|
||||||
data() {
|
data() {
|
||||||
const dataObj = {};
|
let dataObj = {};
|
||||||
const locales = {
|
const locales = {
|
||||||
he: "עברית",
|
he: "עברית",
|
||||||
hu: "Magyar",
|
hu: "Magyar",
|
||||||
ar: "العربية",
|
ar: "العربية",
|
||||||
ca: "Català",
|
ca: "Català",
|
||||||
cs: "Čeština",
|
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
el: "Ελληνικά",
|
el: "Ελληνικά",
|
||||||
en: "English",
|
en: "English",
|
||||||
@ -39,7 +38,6 @@ export default {
|
|||||||
"sv-se": "Swedish (Sweden)",
|
"sv-se": "Swedish (Sweden)",
|
||||||
tr: "Türkçe",
|
tr: "Türkçe",
|
||||||
uk: "Українська",
|
uk: "Українська",
|
||||||
vi: "Tiếng Việt",
|
|
||||||
"zh-cn": "中文 (简体)",
|
"zh-cn": "中文 (简体)",
|
||||||
"zh-tw": "中文 (繁體)",
|
"zh-tw": "中文 (繁體)",
|
||||||
};
|
};
|
||||||
|
@ -39,7 +39,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
remove(event, index) {
|
remove(event, index) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const rules = [...this.rules];
|
let rules = [...this.rules];
|
||||||
rules.splice(index, 1);
|
rules.splice(index, 1);
|
||||||
this.$emit("update:rules", [...rules]);
|
this.$emit("update:rules", [...rules]);
|
||||||
},
|
},
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SelectHTMLAttributes } from "vue";
|
import { SelectHTMLAttributes } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@ -17,6 +17,7 @@ defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
(e: "update:theme", val: string | null): void;
|
(e: "update:theme", val: string | null): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ watch(createUserDirData, () => {
|
|||||||
if (props.user?.scope) {
|
if (props.user?.scope) {
|
||||||
props.user.scope = createUserDirData.value
|
props.user.scope = createUserDirData.value
|
||||||
? ""
|
? ""
|
||||||
: (originalUserScope.value ?? "");
|
: originalUserScope.value ?? "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -63,8 +63,8 @@
|
|||||||
local("Roboto"),
|
local("Roboto"),
|
||||||
local("Roboto-Regular"),
|
local("Roboto-Regular"),
|
||||||
url(../assets/fonts/roboto/normal-latin-ext.woff2) format("woff2");
|
url(../assets/fonts/roboto/normal-latin-ext.woff2) format("woff2");
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF,
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F,
|
||||||
U+2C60-2C7F, U+A720-A7FF;
|
U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@ -142,8 +142,8 @@
|
|||||||
local("Roboto Medium"),
|
local("Roboto Medium"),
|
||||||
local("Roboto-Medium"),
|
local("Roboto-Medium"),
|
||||||
url(../assets/fonts/roboto/medium-latin-ext.woff2) format("woff2");
|
url(../assets/fonts/roboto/medium-latin-ext.woff2) format("woff2");
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF,
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F,
|
||||||
U+2C60-2C7F, U+A720-A7FF;
|
U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@ -221,8 +221,8 @@
|
|||||||
local("Roboto Bold"),
|
local("Roboto Bold"),
|
||||||
local("Roboto-Bold"),
|
local("Roboto-Bold"),
|
||||||
url(../assets/fonts/roboto/bold-latin-ext.woff2) format("woff2");
|
url(../assets/fonts/roboto/bold-latin-ext.woff2) format("woff2");
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF,
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F,
|
||||||
U+2C60-2C7F, U+A720-A7FF;
|
U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
@ -223,8 +223,8 @@ html[dir="rtl"] #listing {
|
|||||||
border-bottom: 1px solid var(--borderPrimary);
|
border-bottom: 1px solid var(--borderPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.list .item.header > div {
|
#listing.list .item.header > div:first-child {
|
||||||
width: 100%;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.list .item.header .name {
|
#listing.list .item.header .name {
|
||||||
@ -235,6 +235,10 @@ html[dir="rtl"] #listing {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#listing.list .item.header > div:first-child {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#listing.list .name {
|
#listing.list .name {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
@ -242,6 +246,10 @@ html[dir="rtl"] #listing {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#listing.list .item.header .name {
|
||||||
|
margin-right: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
#listing.list .header span {
|
#listing.list .header span {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|