Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c284de9d2c | ||
|
|
984ea7b569 | ||
|
|
fd7b70cf38 | ||
|
|
13e3b46718 | ||
|
|
d759ab0bd8 | ||
|
|
00323a8f37 | ||
|
|
420adea7e6 | ||
|
|
f576d38a7e | ||
|
|
9bdc67c207 | ||
|
|
f41585f039 | ||
|
|
89be0b1873 | ||
|
|
8c5dc7641e | ||
|
|
0a0cb8046f | ||
|
|
f89435c068 | ||
|
|
fb8d41eb9a | ||
|
|
0fadaccaa2 | ||
|
|
e24e1f1aba | ||
|
|
5de4099cba | ||
|
|
d01493106d | ||
|
|
2d9689dd6a | ||
|
|
c4c1cea230 | ||
|
|
ceb5e723f3 | ||
|
|
ebc7d2303d | ||
|
|
23c4e4565b | ||
|
|
17f1e08a58 | ||
|
|
ffc850454e | ||
|
|
13814e1119 | ||
|
|
4e9e312984 | ||
|
|
ce3b407c51 | ||
|
|
fb5d099f85 | ||
|
|
1ace579a55 | ||
|
|
ac7b49c148 | ||
|
|
9d44932dba | ||
|
|
0d973d3aad | ||
|
|
cacc0999e9 | ||
|
|
42d1b6f3ae | ||
|
|
bb10c3dfa9 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -97,7 +97,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Install Task
|
- name: Install Task
|
||||||
uses: go-task/setup-task@v1
|
uses: go-task/setup-task@v1
|
||||||
- run: task build-frontend
|
- run: task build:frontend
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
125
.golangci.yml
125
.golangci.yml
@@ -1,132 +1,9 @@
|
|||||||
version: "2"
|
version: "2"
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
# inverted configuration with `default: all` and `disable` is not scalable during updates of golangci-lint
|
default: standard
|
||||||
default: none
|
|
||||||
enable:
|
|
||||||
- bodyclose
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
|
||||||
- errcheck
|
|
||||||
- errorlint
|
|
||||||
- exhaustive
|
|
||||||
- funlen
|
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gochecknoinits
|
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
|
||||||
- godox
|
|
||||||
- goprintffuncname
|
|
||||||
- gosec
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- lll
|
|
||||||
- misspell
|
|
||||||
- mnd
|
|
||||||
- nakedret
|
|
||||||
- nolintlint
|
|
||||||
- prealloc
|
|
||||||
- revive
|
|
||||||
- rowserrcheck
|
|
||||||
- staticcheck
|
|
||||||
- testifylint
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
- 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:
|
exclusions:
|
||||||
generated: lax
|
|
||||||
presets:
|
presets:
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
- legacy
|
|
||||||
- std-error-handling
|
- 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:
|
|
||||||
enable:
|
|
||||||
- goimports
|
|
||||||
settings:
|
|
||||||
goimports:
|
|
||||||
local-prefixes:
|
|
||||||
- github.com/filebrowser/filebrowser
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
paths:
|
paths:
|
||||||
- frontend/
|
- frontend/
|
||||||
|
|||||||
70
CHANGELOG.md
70
CHANGELOG.md
@@ -2,8 +2,78 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.48.2](https://github.com/filebrowser/filebrowser/compare/v2.48.1...v2.48.2) (2025-11-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add transitionary support for FB_BASEURL ([984ea7b](https://github.com/filebrowser/filebrowser/commit/984ea7b569e3bd33b6f91ebdf63684a618d51e94))
|
||||||
|
|
||||||
|
|
||||||
|
### Refactorings
|
||||||
|
|
||||||
|
* rename python for clarification ([fd7b70c](https://github.com/filebrowser/filebrowser/commit/fd7b70cf38ac67c8c9ff79f2e7fde5e2ec45a1de))
|
||||||
|
|
||||||
|
## [2.48.1](https://github.com/filebrowser/filebrowser/compare/v2.48.0...v2.48.1) (2025-11-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* options should only override if set ([420adea](https://github.com/filebrowser/filebrowser/commit/420adea7e61a1c182cddd6fb2544a0752e5709f7))
|
||||||
|
|
||||||
|
## [2.48.0](https://github.com/filebrowser/filebrowser/compare/v2.47.0...v2.48.0) (2025-11-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* consistent flags and environment variables ([#5549](https://github.com/filebrowser/filebrowser/issues/5549)) ([0a0cb80](https://github.com/filebrowser/filebrowser/commit/0a0cb8046fce52f1ff926171b34bcdb7cd39aab3))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add tokenExpirationTime to `config init` and troubleshoot docs ([#5546](https://github.com/filebrowser/filebrowser/issues/5546)) ([8c5dc76](https://github.com/filebrowser/filebrowser/commit/8c5dc7641e6f8aadd9e5d5d3b25a2ad9f1ec9a1e))
|
||||||
|
* use all available flags in quick setup ([f41585f](https://github.com/filebrowser/filebrowser/commit/f41585f0392d65c08c01ab65b62d3eeb04c03b7d))
|
||||||
|
|
||||||
|
|
||||||
|
### Refactorings
|
||||||
|
|
||||||
|
* reuse logic for config init and set ([89be0b1](https://github.com/filebrowser/filebrowser/commit/89be0b1873527987dd2dddac746e93b8bc684d46))
|
||||||
|
|
||||||
|
## [2.47.0](https://github.com/filebrowser/filebrowser/compare/v2.46.1...v2.47.0) (2025-11-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add TUS settings to the command line ([#5556](https://github.com/filebrowser/filebrowser/issues/5556)) ([e24e1f1](https://github.com/filebrowser/filebrowser/commit/e24e1f1abae9e80add620c4ad65660ca1b575a49))
|
||||||
|
* remove importer of v1 config ([#5550](https://github.com/filebrowser/filebrowser/issues/5550)) ([ceb5e72](https://github.com/filebrowser/filebrowser/commit/ceb5e723f3ee2c966bb561a804015246450280ca))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* exit 0 when gracefully shutting down ([#5555](https://github.com/filebrowser/filebrowser/issues/5555)) ([5de4099](https://github.com/filebrowser/filebrowser/commit/5de4099cba2cf012d4a213c8eb29c412fc72c151))
|
||||||
|
|
||||||
|
## [2.46.1](https://github.com/filebrowser/filebrowser/compare/v2.46.0...v2.46.1) (2025-11-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* env key replacer and remove unused function ([#5547](https://github.com/filebrowser/filebrowser/issues/5547)) ([13814e1](https://github.com/filebrowser/filebrowser/commit/13814e11197ebd9101940883e3ca85998f86d442))
|
||||||
|
* remove duplicated 'hide-defaults' flag (is 'hideDefaults') ([#5548](https://github.com/filebrowser/filebrowser/issues/5548)) ([ffc8504](https://github.com/filebrowser/filebrowser/commit/ffc850454e4cb8f10b970511681d6c627340afc7))
|
||||||
|
|
||||||
|
## [2.46.0](https://github.com/filebrowser/filebrowser/compare/v2.45.3...v2.46.0) (2025-11-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add 'hide-dotfiles' as command line parameter ([#3802](https://github.com/filebrowser/filebrowser/issues/3802)) ([0d973d3](https://github.com/filebrowser/filebrowser/commit/0d973d3aad70ceb88950f2cd9c297fc76e7955b1))
|
||||||
|
* add context menu ([#3343](https://github.com/filebrowser/filebrowser/issues/3343)) ([1ace579](https://github.com/filebrowser/filebrowser/commit/1ace579a553486bb15af2d11f537414156606434))
|
||||||
|
* add option to hide the login button from public-facing pages ([#3922](https://github.com/filebrowser/filebrowser/issues/3922)) ([ac7b49c](https://github.com/filebrowser/filebrowser/commit/ac7b49c1484b4e27a1149310542ccd1e90659ee2))
|
||||||
|
* Updates for project File Browser ([#5544](https://github.com/filebrowser/filebrowser/issues/5544)) ([fb5d099](https://github.com/filebrowser/filebrowser/commit/fb5d099f8514516216f407be012d2e3f25de2441))
|
||||||
|
|
||||||
## [2.45.3](https://github.com/filebrowser/filebrowser/compare/v2.45.2...v2.45.3) (2025-11-13)
|
## [2.45.3](https://github.com/filebrowser/filebrowser/compare/v2.45.2...v2.45.3) (2025-11-13)
|
||||||
|
|
||||||
|
This is a test release to ensure the updated workflow works.
|
||||||
|
|
||||||
## [2.45.2](https://github.com/filebrowser/filebrowser/compare/v2.45.1...v2.45.2) (2025-11-13)
|
## [2.45.2](https://github.com/filebrowser/filebrowser/compare/v2.45.1...v2.45.2) (2025-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ task docs
|
|||||||
To start a local server on port `8000` to view the built documentation:
|
To start a local server on port `8000` to view the built documentation:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
task docs-serve
|
task docs:serve
|
||||||
```
|
```
|
||||||
|
|
||||||
## Release
|
## Release
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<img src="https://raw.githubusercontent.com/filebrowser/filebrowser/master/branding/banner.png" width="550"/>
|
<img src="https://raw.githubusercontent.com/filebrowser/filebrowser/master/branding/banner.png" width="550"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
|
[](https://github.com/filebrowser/filebrowser/actions/workflows/ci.yaml)
|
||||||
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser/v2)
|
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser/v2)
|
||||||
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ This project is a finished product which fulfills its goal: be a single binary w
|
|||||||
- It can take a while until someone gets back to you. Please be patient.
|
- It can take a while until someone gets back to you. Please be patient.
|
||||||
- [Issues](https://github.com/filebrowser/filebrowser/issues) are meant to track bugs. Unrelated issues will be converted into [discussions](https://github.com/filebrowser/filebrowser/discussions).
|
- [Issues](https://github.com/filebrowser/filebrowser/issues) are meant to track bugs. Unrelated issues will be converted into [discussions](https://github.com/filebrowser/filebrowser/discussions).
|
||||||
- No new features will be implemented by maintainers. Pull requests for new features will be reviewed on a case by case basis.
|
- No new features will be implemented by maintainers. Pull requests for new features will be reviewed on a case by case basis.
|
||||||
- The priority is triaging issues, addressing security issues, and fixing bug fixes.
|
- The priority is triaging issues, addressing security issues and reviewing pull requests meant to solve bugs.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
38
Taskfile.yml
38
Taskfile.yml
@@ -10,14 +10,14 @@ vars:
|
|||||||
-v ./CONTRIBUTING.md:/docs/docs/contributing.md
|
-v ./CONTRIBUTING.md:/docs/docs/contributing.md
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
build-frontend:
|
build:frontend:
|
||||||
desc: Build frontend assets
|
desc: Build frontend assets
|
||||||
dir: frontend
|
dir: frontend
|
||||||
cmds:
|
cmds:
|
||||||
- pnpm install --frozen-lockfile
|
- pnpm install --frozen-lockfile
|
||||||
- pnpm run build
|
- pnpm run build
|
||||||
|
|
||||||
build-backend:
|
build:backend:
|
||||||
desc: Build backend binary
|
desc: Build backend binary
|
||||||
cmds:
|
cmds:
|
||||||
- go build -ldflags='-s -w -X "github.com/filebrowser/filebrowser/v2/version.Version={{.VERSION}}" -X "github.com/filebrowser/filebrowser/v2/version.CommitSHA={{.GIT_COMMIT}}"' -o filebrowser .
|
- go build -ldflags='-s -w -X "github.com/filebrowser/filebrowser/v2/version.Version={{.VERSION}}" -X "github.com/filebrowser/filebrowser/v2/version.CommitSHA={{.GIT_COMMIT}}"' -o filebrowser .
|
||||||
@@ -30,16 +30,16 @@ tasks:
|
|||||||
build:
|
build:
|
||||||
desc: Build both frontend and backend
|
desc: Build both frontend and backend
|
||||||
cmds:
|
cmds:
|
||||||
- task: build-frontend
|
- task: build:frontend
|
||||||
- task: build-backend
|
- task: build:backend
|
||||||
|
|
||||||
release-make:
|
release:make:
|
||||||
internal: true
|
internal: true
|
||||||
prompt: Do you wish to proceed?
|
prompt: Do you wish to proceed?
|
||||||
cmds:
|
cmds:
|
||||||
- pnpm dlx commit-and-tag-version -s
|
- pnpm dlx commit-and-tag-version -s
|
||||||
|
|
||||||
release-dry-run:
|
release:dry-run:
|
||||||
internal: true
|
internal: true
|
||||||
cmds:
|
cmds:
|
||||||
- pnpm dlx commit-and-tag-version --dry-run --skip
|
- pnpm dlx commit-and-tag-version --dry-run --skip
|
||||||
@@ -47,10 +47,24 @@ tasks:
|
|||||||
release:
|
release:
|
||||||
desc: Create a new release
|
desc: Create a new release
|
||||||
cmds:
|
cmds:
|
||||||
- task: release-dry-run
|
- task: docs:cli:generate
|
||||||
- task: release-make
|
- git add www/docs/cli
|
||||||
|
- |
|
||||||
|
if [[ `git status www/docs/cli --porcelain` ]]; then
|
||||||
|
git commit -m 'chore(docs): update CLI documentation'
|
||||||
|
fi
|
||||||
|
- task: release:dry-run
|
||||||
|
- task: release:make
|
||||||
|
|
||||||
docs-image-make:
|
docs:cli:generate:
|
||||||
|
cmds:
|
||||||
|
- rm -rf www/docs/cli
|
||||||
|
- mkdir -p www/docs/cli
|
||||||
|
- go run . docs
|
||||||
|
generates:
|
||||||
|
- www/docs/cli
|
||||||
|
|
||||||
|
docs:docker:generate:
|
||||||
internal: true
|
internal: true
|
||||||
cmds:
|
cmds:
|
||||||
- docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
- docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
||||||
@@ -59,11 +73,11 @@ tasks:
|
|||||||
desc: Generate documentation
|
desc: Generate documentation
|
||||||
cmds:
|
cmds:
|
||||||
- rm -rf www/public
|
- rm -rf www/public
|
||||||
- task: docs-image-make
|
- task: docs:docker:generate
|
||||||
- docker run --rm {{.SITE_DOCKER_FLAGS}} filebrowser.site build -d "public"
|
- docker run --rm {{.SITE_DOCKER_FLAGS}} filebrowser.site build -d "public"
|
||||||
|
|
||||||
docs-serve:
|
docs:serve:
|
||||||
desc: Serve documentation
|
desc: Serve documentation
|
||||||
cmds:
|
cmds:
|
||||||
- task: docs-image-make
|
- task: docs:docker:generate
|
||||||
- docker run --rm -it -p 8000:8000 {{.SITE_DOCKER_FLAGS}} filebrowser.site
|
- docker run --rm -it -p 8000:8000 {{.SITE_DOCKER_FLAGS}} filebrowser.site
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func (a *HookAuth) RunCommand() (string, error) {
|
|||||||
command[i] = os.Expand(arg, envMapping)
|
command[i] = os.Expand(arg, envMapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
cmd := exec.Command(command[0], command[1:]...)
|
||||||
cmd.Env = append(os.Environ(), fmt.Sprintf("USERNAME=%s", a.Cred.Username))
|
cmd.Env = append(os.Environ(), fmt.Sprintf("USERNAME=%s", a.Cred.Username))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("PASSWORD=%s", a.Cred.Password))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("PASSWORD=%s", a.Cred.Password))
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (a JSONAuth) Auth(r *http.Request, usr users.Store, _ *settings.Settings, s
|
|||||||
|
|
||||||
// If ReCaptcha is enabled, check the code.
|
// If ReCaptcha is enabled, check the code.
|
||||||
if a.ReCaptcha != nil && a.ReCaptcha.Secret != "" {
|
if a.ReCaptcha != nil && a.ReCaptcha.Secret != "" {
|
||||||
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha) //nolint:govet
|
ok, err := a.ReCaptcha.Ok(cred.ReCaptcha)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
35
cmd/cmd_test.go
Normal file
35
cmd/cmd_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestEnvCollisions ensures that there are no collisions in the produced environment
|
||||||
|
// variable names for all commands and their flags.
|
||||||
|
func TestEnvCollisions(t *testing.T) {
|
||||||
|
testEnvCollisions(t, rootCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEnvCollisions(t *testing.T, cmd *cobra.Command) {
|
||||||
|
for _, cmd := range cmd.Commands() {
|
||||||
|
testEnvCollisions(t, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
replacements := generateEnvKeyReplacements(cmd)
|
||||||
|
envVariables := []string{}
|
||||||
|
|
||||||
|
for i := range replacements {
|
||||||
|
if i%2 != 0 {
|
||||||
|
envVariables = append(envVariables, replacements[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicates := lo.FindDuplicates(envVariables)
|
||||||
|
|
||||||
|
if len(duplicates) > 0 {
|
||||||
|
t.Errorf("Found duplicate environment variable keys for command %q: %v", cmd.Name(), duplicates)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,18 +15,18 @@ var cmdsAddCmd = &cobra.Command{
|
|||||||
Short: "Add a command to run on a specific event",
|
Short: "Add a command to run on a specific event",
|
||||||
Long: `Add a command to run on a specific event.`,
|
Long: `Add a command to run on a specific event.`,
|
||||||
Args: cobra.MinimumNArgs(2),
|
Args: cobra.MinimumNArgs(2),
|
||||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||||
s, err := d.store.Settings.Get()
|
s, err := st.Settings.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
command := strings.Join(args[1:], " ")
|
command := strings.Join(args[1:], " ")
|
||||||
s.Commands[args[0]] = append(s.Commands[args[0]], command)
|
s.Commands[args[0]] = append(s.Commands[args[0]], command)
|
||||||
err = d.store.Settings.Save(s)
|
err = st.Settings.Save(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printEvents(s.Commands)
|
printEvents(s.Commands)
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ var cmdsLsCmd = &cobra.Command{
|
|||||||
Short: "List all commands for each event",
|
Short: "List all commands for each event",
|
||||||
Long: `List all commands for each event.`,
|
Long: `List all commands for each event.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
RunE: withStore(func(cmd *cobra.Command, _ []string, st *store) error {
|
||||||
s, err := d.store.Settings.Get()
|
s, err := st.Settings.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
evt, err := getString(cmd.Flags(), "event")
|
|
||||||
|
evt, err := cmd.Flags().GetString("event")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -32,6 +33,7 @@ var cmdsLsCmd = &cobra.Command{
|
|||||||
show["after_"+evt] = s.Commands["after_"+evt]
|
show["after_"+evt] = s.Commands["after_"+evt]
|
||||||
printEvents(show)
|
printEvents(show)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ including 'index_end'.`,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||||
s, err := d.store.Settings.Get()
|
s, err := st.Settings.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -55,11 +55,11 @@ including 'index_end'.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...)
|
s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...)
|
||||||
err = d.store.Settings.Save(s)
|
err = st.Settings.Save(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printEvents(s.Commands)
|
printEvents(s.Commands)
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
166
cmd/config.go
166
cmd/config.go
@@ -30,11 +30,18 @@ var configCmd = &cobra.Command{
|
|||||||
func addConfigFlags(flags *pflag.FlagSet) {
|
func addConfigFlags(flags *pflag.FlagSet) {
|
||||||
addServerFlags(flags)
|
addServerFlags(flags)
|
||||||
addUserFlags(flags)
|
addUserFlags(flags)
|
||||||
|
|
||||||
flags.BoolP("signup", "s", false, "allow users to signup")
|
flags.BoolP("signup", "s", false, "allow users to signup")
|
||||||
flags.Bool("create-user-dir", false, "generate user's home directory automatically")
|
flags.Bool("hideLoginButton", false, "hide login button from public pages")
|
||||||
flags.Uint("minimum-password-length", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
|
flags.Bool("createUserDir", false, "generate user's home directory automatically")
|
||||||
|
flags.Uint("minimumPasswordLength", 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")
|
||||||
|
|
||||||
|
// NB: these are string so they can be presented as octal in the help text
|
||||||
|
// as that's the conventional representation for modes in Unix.
|
||||||
|
flags.String("fileMode", fmt.Sprintf("%O", settings.DefaultFileMode), "mode bits that new files are created with")
|
||||||
|
flags.String("dirMode", fmt.Sprintf("%O", settings.DefaultDirMode), "mode bits that new directories are created with")
|
||||||
|
|
||||||
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
||||||
flags.String("auth.header", "", "HTTP header for auth.method=proxy")
|
flags.String("auth.header", "", "HTTP header for auth.method=proxy")
|
||||||
flags.String("auth.command", "", "command for auth.method=hook")
|
flags.String("auth.command", "", "command for auth.method=hook")
|
||||||
@@ -49,14 +56,13 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
|||||||
flags.String("branding.files", "", "path to directory with images and custom styles")
|
flags.String("branding.files", "", "path to directory with images and custom styles")
|
||||||
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
||||||
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph")
|
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph")
|
||||||
// NB: these are string so they can be presented as octal in the help text
|
|
||||||
// as that's the conventional representation for modes in Unix.
|
flags.Uint64("tus.chunkSize", settings.DefaultTusChunkSize, "the tus chunk size")
|
||||||
flags.String("file-mode", fmt.Sprintf("%O", settings.DefaultFileMode), "Mode bits that new files are created with")
|
flags.Uint16("tus.retryCount", settings.DefaultTusRetryCount, "the tus retry count")
|
||||||
flags.String("dir-mode", fmt.Sprintf("%O", settings.DefaultDirMode), "Mode bits that new directories are created with")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, map[string]interface{}, error) {
|
func getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, map[string]interface{}, error) {
|
||||||
methodStr, err := getString(flags, "auth.method")
|
methodStr, err := flags.GetString("auth.method")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -87,7 +93,7 @@ func getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.Auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getProxyAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
func getProxyAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
||||||
header, err := getString(flags, "auth.header")
|
header, err := flags.GetString("auth.header")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -109,15 +115,17 @@ func getNoAuth() auth.Auther {
|
|||||||
|
|
||||||
func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
||||||
jsonAuth := &auth.JSONAuth{}
|
jsonAuth := &auth.JSONAuth{}
|
||||||
host, err := getString(flags, "recaptcha.host")
|
host, err := flags.GetString("recaptcha.host")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key, err := getString(flags, "recaptcha.key")
|
|
||||||
|
key, err := flags.GetString("recaptcha.key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
secret, err := getString(flags, "recaptcha.secret")
|
|
||||||
|
secret, err := flags.GetString("recaptcha.secret")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -145,11 +153,10 @@ func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (au
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getHookAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
func getHookAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
||||||
command, err := getString(flags, "auth.command")
|
command, err := flags.GetString("auth.command")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if command == "" {
|
if command == "" {
|
||||||
command = defaultAuther["command"].(string)
|
command = defaultAuther["command"].(string)
|
||||||
}
|
}
|
||||||
@@ -192,10 +199,12 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||||
|
|
||||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||||
|
fmt.Fprintf(w, "Hide Login Button:\t%t\n", set.HideLoginButton)
|
||||||
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, "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:")
|
||||||
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
||||||
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
|
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
|
||||||
@@ -203,6 +212,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Branding.DisableUsedPercentage)
|
fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Branding.DisableUsedPercentage)
|
||||||
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
|
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
|
||||||
fmt.Fprintf(w, "\tTheme:\t%s\n", set.Branding.Theme)
|
fmt.Fprintf(w, "\tTheme:\t%s\n", set.Branding.Theme)
|
||||||
|
|
||||||
fmt.Fprintln(w, "\nServer:")
|
fmt.Fprintln(w, "\nServer:")
|
||||||
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
|
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
|
||||||
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
|
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
|
||||||
@@ -212,9 +222,19 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
|
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
|
||||||
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
||||||
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
||||||
|
fmt.Fprintf(w, "\tToken Expiration Time:\t%s\n", ser.TokenExpirationTime)
|
||||||
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
|
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
|
||||||
|
fmt.Fprintf(w, "\tThumbnails Enabled:\t%t\n", ser.EnableThumbnails)
|
||||||
|
fmt.Fprintf(w, "\tResize Preview:\t%t\n", ser.ResizePreview)
|
||||||
|
fmt.Fprintf(w, "\tType Detection by Header:\t%t\n", ser.TypeDetectionByHeader)
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "\nTUS:")
|
||||||
|
fmt.Fprintf(w, "\tChunk size:\t%d\n", set.Tus.ChunkSize)
|
||||||
|
fmt.Fprintf(w, "\tRetry count:\t%d\n", set.Tus.RetryCount)
|
||||||
|
|
||||||
fmt.Fprintln(w, "\nDefaults:")
|
fmt.Fprintln(w, "\nDefaults:")
|
||||||
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
|
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
|
||||||
|
fmt.Fprintf(w, "\tHideDotfiles:\t%t\n", set.Defaults.HideDotfiles)
|
||||||
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
||||||
fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode)
|
fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode)
|
||||||
fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick)
|
fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick)
|
||||||
@@ -222,9 +242,11 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\tDirectory Creation Mode:\t%O\n", set.DirMode)
|
fmt.Fprintf(w, "\tDirectory Creation Mode:\t%O\n", set.DirMode)
|
||||||
fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " "))
|
fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " "))
|
||||||
fmt.Fprintf(w, "\tAce editor syntax highlighting theme:\t%s\n", set.Defaults.AceEditorTheme)
|
fmt.Fprintf(w, "\tAce editor syntax highlighting theme:\t%s\n", set.Defaults.AceEditorTheme)
|
||||||
|
|
||||||
fmt.Fprintf(w, "\tSorting:\n")
|
fmt.Fprintf(w, "\tSorting:\n")
|
||||||
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)
|
fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By)
|
||||||
fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc)
|
fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc)
|
||||||
|
|
||||||
fmt.Fprintf(w, "\tPermissions:\n")
|
fmt.Fprintf(w, "\tPermissions:\n")
|
||||||
fmt.Fprintf(w, "\t\tAdmin:\t%t\n", set.Defaults.Perm.Admin)
|
fmt.Fprintf(w, "\t\tAdmin:\t%t\n", set.Defaults.Perm.Admin)
|
||||||
fmt.Fprintf(w, "\t\tExecute:\t%t\n", set.Defaults.Perm.Execute)
|
fmt.Fprintf(w, "\t\tExecute:\t%t\n", set.Defaults.Perm.Execute)
|
||||||
@@ -234,6 +256,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\t\tDelete:\t%t\n", set.Defaults.Perm.Delete)
|
fmt.Fprintf(w, "\t\tDelete:\t%t\n", set.Defaults.Perm.Delete)
|
||||||
fmt.Fprintf(w, "\t\tShare:\t%t\n", set.Defaults.Perm.Share)
|
fmt.Fprintf(w, "\t\tShare:\t%t\n", set.Defaults.Perm.Share)
|
||||||
fmt.Fprintf(w, "\t\tDownload:\t%t\n", set.Defaults.Perm.Download)
|
fmt.Fprintf(w, "\t\tDownload:\t%t\n", set.Defaults.Perm.Download)
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
|
|
||||||
b, err := json.MarshalIndent(auther, "", " ")
|
b, err := json.MarshalIndent(auther, "", " ")
|
||||||
@@ -243,3 +266,118 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b))
|
fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSettings(flags *pflag.FlagSet, set *settings.Settings, ser *settings.Server, auther auth.Auther, all bool) (auth.Auther, error) {
|
||||||
|
errs := []error{}
|
||||||
|
hasAuth := false
|
||||||
|
|
||||||
|
visit := func(flag *pflag.Flag) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch flag.Name {
|
||||||
|
// Server flags from [addServerFlags]
|
||||||
|
case "address":
|
||||||
|
ser.Address, err = flags.GetString(flag.Name)
|
||||||
|
case "log":
|
||||||
|
ser.Log, err = flags.GetString(flag.Name)
|
||||||
|
case "port":
|
||||||
|
ser.Port, err = flags.GetString(flag.Name)
|
||||||
|
case "cert":
|
||||||
|
ser.TLSCert, err = flags.GetString(flag.Name)
|
||||||
|
case "key":
|
||||||
|
ser.TLSKey, err = flags.GetString(flag.Name)
|
||||||
|
case "root":
|
||||||
|
ser.Root, err = flags.GetString(flag.Name)
|
||||||
|
case "socket":
|
||||||
|
ser.Socket, err = flags.GetString(flag.Name)
|
||||||
|
case "baseURL":
|
||||||
|
ser.BaseURL, err = flags.GetString(flag.Name)
|
||||||
|
case "tokenExpirationTime":
|
||||||
|
ser.TokenExpirationTime, err = flags.GetString(flag.Name)
|
||||||
|
case "disableThumbnails":
|
||||||
|
ser.EnableThumbnails, err = flags.GetBool(flag.Name)
|
||||||
|
ser.EnableThumbnails = !ser.EnableThumbnails
|
||||||
|
case "disablePreviewResize":
|
||||||
|
ser.ResizePreview, err = flags.GetBool(flag.Name)
|
||||||
|
ser.ResizePreview = !ser.ResizePreview
|
||||||
|
case "disableExec":
|
||||||
|
ser.EnableExec, err = flags.GetBool(flag.Name)
|
||||||
|
ser.EnableExec = !ser.EnableExec
|
||||||
|
case "disableTypeDetectionByHeader":
|
||||||
|
ser.TypeDetectionByHeader, err = flags.GetBool(flag.Name)
|
||||||
|
ser.TypeDetectionByHeader = !ser.TypeDetectionByHeader
|
||||||
|
|
||||||
|
// Settings flags from [addConfigFlags]
|
||||||
|
case "signup":
|
||||||
|
set.Signup, err = flags.GetBool(flag.Name)
|
||||||
|
case "hideLoginButton":
|
||||||
|
set.HideLoginButton, err = flags.GetBool(flag.Name)
|
||||||
|
case "createUserDir":
|
||||||
|
set.CreateUserDir, err = flags.GetBool(flag.Name)
|
||||||
|
case "minimumPasswordLength":
|
||||||
|
set.MinimumPasswordLength, err = flags.GetUint(flag.Name)
|
||||||
|
case "shell":
|
||||||
|
var shell string
|
||||||
|
shell, err = flags.GetString(flag.Name)
|
||||||
|
if err == nil {
|
||||||
|
set.Shell = convertCmdStrToCmdArray(shell)
|
||||||
|
}
|
||||||
|
case "fileMode":
|
||||||
|
set.FileMode, err = getAndParseFileMode(flags, flag.Name)
|
||||||
|
case "dirMode":
|
||||||
|
set.DirMode, err = getAndParseFileMode(flags, flag.Name)
|
||||||
|
case "auth.method":
|
||||||
|
hasAuth = true
|
||||||
|
case "branding.name":
|
||||||
|
set.Branding.Name, err = flags.GetString(flag.Name)
|
||||||
|
case "branding.theme":
|
||||||
|
set.Branding.Theme, err = flags.GetString(flag.Name)
|
||||||
|
case "branding.color":
|
||||||
|
set.Branding.Color, err = flags.GetString(flag.Name)
|
||||||
|
case "branding.files":
|
||||||
|
set.Branding.Files, err = flags.GetString(flag.Name)
|
||||||
|
case "branding.disableExternal":
|
||||||
|
set.Branding.DisableExternal, err = flags.GetBool(flag.Name)
|
||||||
|
case "branding.disableUsedPercentage":
|
||||||
|
set.Branding.DisableUsedPercentage, err = flags.GetBool(flag.Name)
|
||||||
|
case "tus.chunkSize":
|
||||||
|
set.Tus.ChunkSize, err = flags.GetUint64(flag.Name)
|
||||||
|
case "tus.retryCount":
|
||||||
|
set.Tus.RetryCount, err = flags.GetUint16(flag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if all {
|
||||||
|
flags.VisitAll(visit)
|
||||||
|
} else {
|
||||||
|
flags.Visit(visit)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := nerrors.Join(errs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = getUserDefaults(flags, &set.Defaults, all)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if all {
|
||||||
|
set.AuthMethod, auther, err = getAuthentication(flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
set.AuthMethod, auther, err = getAuthentication(flags, hasAuth, set, auther)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return auther, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ var configCatCmd = &cobra.Command{
|
|||||||
Short: "Prints the configuration",
|
Short: "Prints the configuration",
|
||||||
Long: `Prints the configuration.`,
|
Long: `Prints the configuration.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: python(func(_ *cobra.Command, _ []string, d *pythonData) error {
|
RunE: withStore(func(_ *cobra.Command, _ []string, st *store) error {
|
||||||
set, err := d.store.Settings.Get()
|
set, err := st.Settings.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ser, err := d.store.Settings.GetServer()
|
ser, err := st.Settings.GetServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
auther, err := st.Auth.Get(set.AuthMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return printSettings(ser, set, auther)
|
return printSettings(ser, set, auther)
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,18 @@ var configExportCmd = &cobra.Command{
|
|||||||
json or yaml file. This exported configuration can be changed,
|
json or yaml file. This exported configuration can be changed,
|
||||||
and imported again with 'config import' command.`,
|
and imported again with 'config import' command.`,
|
||||||
Args: jsonYamlArg,
|
Args: jsonYamlArg,
|
||||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||||
settings, err := d.store.Settings.Get()
|
settings, err := st.Settings.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := d.store.Settings.GetServer()
|
server, err := st.Settings.GetServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
auther, err := d.store.Auth.Get(settings.AuthMethod)
|
auther, err := st.Auth.Get(settings.AuthMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -42,5 +42,5 @@ and imported again with 'config import' command.`,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ database.
|
|||||||
|
|
||||||
The path must be for a json or yaml file.`,
|
The path must be for a json or yaml file.`,
|
||||||
Args: jsonYamlArg,
|
Args: jsonYamlArg,
|
||||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||||
var key []byte
|
var key []byte
|
||||||
var err error
|
var err error
|
||||||
if d.hadDB {
|
if st.databaseExisted {
|
||||||
settings, settingErr := d.store.Settings.Get()
|
settings, settingErr := st.Settings.Get()
|
||||||
if settingErr != nil {
|
if settingErr != nil {
|
||||||
return settingErr
|
return settingErr
|
||||||
}
|
}
|
||||||
@@ -54,12 +54,12 @@ The path must be for a json or yaml file.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
file.Settings.Key = key
|
file.Settings.Key = key
|
||||||
err = d.store.Settings.Save(file.Settings)
|
err = st.Settings.Save(file.Settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Settings.SaveServer(file.Server)
|
err = st.Settings.SaveServer(file.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -98,13 +98,13 @@ The path must be for a json or yaml file.`,
|
|||||||
return autherErr
|
return autherErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Auth.Save(auther)
|
err = st.Auth.Save(auther)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return printSettings(file.Server, file.Settings, auther)
|
return printSettings(file.Server, file.Settings, auther)
|
||||||
}, pythonConfig{allowNoDB: true}),
|
}, storeOptions{allowsNoDatabase: true}),
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuther(sample auth.Auther, data interface{}) (interface{}, error) {
|
func getAuther(sample auth.Auther, data interface{}) (interface{}, error) {
|
||||||
|
|||||||
@@ -22,152 +22,31 @@ this options can be changed in the future with the command
|
|||||||
to the defaults when creating new users and you don't
|
to the defaults when creating new users and you don't
|
||||||
override the options.`,
|
override the options.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
RunE: withStore(func(cmd *cobra.Command, _ []string, st *store) error {
|
||||||
defaults := settings.UserDefaults{}
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
err := getUserDefaults(flags, &defaults, true)
|
|
||||||
if err != nil {
|
// Initialize config
|
||||||
return err
|
s := &settings.Settings{Key: generateKey()}
|
||||||
}
|
ser := &settings.Server{}
|
||||||
authMethod, auther, err := getAuthentication(flags)
|
|
||||||
|
// Fill config with options
|
||||||
|
auther, err := getSettings(flags, s, ser, nil, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
key := generateKey()
|
// Save updated config
|
||||||
|
err = st.Settings.Save(s)
|
||||||
signup, err := getBool(flags, "signup")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
createUserDir, err := getBool(flags, "create-user-dir")
|
err = st.Settings.SaveServer(ser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
minLength, err := getUint(flags, "minimum-password-length")
|
err = st.Auth.Save(auther)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
shell, err := getString(flags, "shell")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
brandingName, err := getString(flags, "branding.name")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
brandingDisableExternal, err := getBool(flags, "branding.disableExternal")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
brandingDisableUsedPercentage, err := getBool(flags, "branding.disableUsedPercentage")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
brandingTheme, err := getString(flags, "branding.theme")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
brandingFiles, err := getString(flags, "branding.files")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &settings.Settings{
|
|
||||||
Key: key,
|
|
||||||
Signup: signup,
|
|
||||||
CreateUserDir: createUserDir,
|
|
||||||
MinimumPasswordLength: minLength,
|
|
||||||
Shell: convertCmdStrToCmdArray(shell),
|
|
||||||
AuthMethod: authMethod,
|
|
||||||
Defaults: defaults,
|
|
||||||
Branding: settings.Branding{
|
|
||||||
Name: brandingName,
|
|
||||||
DisableExternal: brandingDisableExternal,
|
|
||||||
DisableUsedPercentage: brandingDisableUsedPercentage,
|
|
||||||
Theme: brandingTheme,
|
|
||||||
Files: brandingFiles,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
s.FileMode, err = getMode(flags, "file-mode")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.DirMode, err = getMode(flags, "dir-mode")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
address, err := getString(flags, "address")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
socket, err := getString(flags, "socket")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
root, err := getString(flags, "root")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
baseURL, err := getString(flags, "baseurl")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsKey, err := getString(flags, "key")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := getString(flags, "cert")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
port, err := getString(flags, "port")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log, err := getString(flags, "log")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ser := &settings.Server{
|
|
||||||
Address: address,
|
|
||||||
Socket: socket,
|
|
||||||
Root: root,
|
|
||||||
BaseURL: baseURL,
|
|
||||||
TLSKey: tlsKey,
|
|
||||||
TLSCert: cert,
|
|
||||||
Port: port,
|
|
||||||
Log: log,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.store.Settings.Save(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.store.Settings.SaveServer(ser)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.store.Auth.Save(auther)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -178,5 +57,5 @@ Now add your first user via 'filebrowser users add' and then you just
|
|||||||
need to call the main command to boot up the server.
|
need to call the main command to boot up the server.
|
||||||
`)
|
`)
|
||||||
return printSettings(ser, s, auther)
|
return printSettings(ser, s, auther)
|
||||||
}, pythonConfig{noDB: true}),
|
}, storeOptions{expectsNoDatabase: true}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -16,105 +15,47 @@ var configSetCmd = &cobra.Command{
|
|||||||
Long: `Updates the configuration. Set the flags for the options
|
Long: `Updates the configuration. Set the flags for the options
|
||||||
you want to change. Other options will remain unchanged.`,
|
you want to change. Other options will remain unchanged.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
RunE: withStore(func(cmd *cobra.Command, _ []string, st *store) error {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
set, err := d.store.Settings.Get()
|
|
||||||
|
// Read existing config
|
||||||
|
set, err := st.Settings.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ser, err := d.store.Settings.GetServer()
|
ser, err := st.Settings.GetServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAuth := false
|
auther, err := st.Auth.Get(set.AuthMethod)
|
||||||
flags.Visit(func(flag *pflag.Flag) {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch flag.Name {
|
|
||||||
case "baseurl":
|
|
||||||
ser.BaseURL, err = getString(flags, flag.Name)
|
|
||||||
case "root":
|
|
||||||
ser.Root, err = getString(flags, flag.Name)
|
|
||||||
case "socket":
|
|
||||||
ser.Socket, err = getString(flags, flag.Name)
|
|
||||||
case "cert":
|
|
||||||
ser.TLSCert, err = getString(flags, flag.Name)
|
|
||||||
case "key":
|
|
||||||
ser.TLSKey, err = getString(flags, flag.Name)
|
|
||||||
case "address":
|
|
||||||
ser.Address, err = getString(flags, flag.Name)
|
|
||||||
case "port":
|
|
||||||
ser.Port, err = getString(flags, flag.Name)
|
|
||||||
case "log":
|
|
||||||
ser.Log, err = getString(flags, flag.Name)
|
|
||||||
case "signup":
|
|
||||||
set.Signup, err = getBool(flags, flag.Name)
|
|
||||||
case "auth.method":
|
|
||||||
hasAuth = true
|
|
||||||
case "shell":
|
|
||||||
var shell string
|
|
||||||
shell, err = getString(flags, flag.Name)
|
|
||||||
set.Shell = convertCmdStrToCmdArray(shell)
|
|
||||||
case "create-user-dir":
|
|
||||||
set.CreateUserDir, err = getBool(flags, flag.Name)
|
|
||||||
case "minimum-password-length":
|
|
||||||
set.MinimumPasswordLength, err = getUint(flags, flag.Name)
|
|
||||||
case "branding.name":
|
|
||||||
set.Branding.Name, err = getString(flags, flag.Name)
|
|
||||||
case "branding.color":
|
|
||||||
set.Branding.Color, err = getString(flags, flag.Name)
|
|
||||||
case "branding.theme":
|
|
||||||
set.Branding.Theme, err = getString(flags, flag.Name)
|
|
||||||
case "branding.disableExternal":
|
|
||||||
set.Branding.DisableExternal, err = getBool(flags, flag.Name)
|
|
||||||
case "branding.disableUsedPercentage":
|
|
||||||
set.Branding.DisableUsedPercentage, err = getBool(flags, flag.Name)
|
|
||||||
case "branding.files":
|
|
||||||
set.Branding.Files, err = getString(flags, flag.Name)
|
|
||||||
case "file-mode":
|
|
||||||
set.FileMode, err = getMode(flags, flag.Name)
|
|
||||||
case "dir-mode":
|
|
||||||
set.DirMode, err = getMode(flags, flag.Name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = getUserDefaults(flags, &set.Defaults, false)
|
// Get updated config
|
||||||
|
auther, err = getSettings(flags, set, ser, auther, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the defaults
|
// Save updated config
|
||||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
err = st.Auth.Save(auther)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there are new flags for existing auth method
|
err = st.Settings.Save(set)
|
||||||
set.AuthMethod, auther, err = getAuthentication(flags, hasAuth, set, auther)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Auth.Save(auther)
|
err = st.Settings.SaveServer(ser)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.store.Settings.Save(set)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.store.Settings.SaveServer(ser)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return printSettings(ser, set, auther)
|
return printSettings(ser, set, auther)
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
162
cmd/docs.go
162
cmd/docs.go
@@ -3,36 +3,18 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path"
|
||||||
"sort"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/cobra/doc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(docsCmd)
|
rootCmd.AddCommand(docsCmd)
|
||||||
docsCmd.Flags().StringP("path", "p", "./docs", "path to save the docs")
|
docsCmd.Flags().String("out", "www/docs/cli", "directory to write the docs to")
|
||||||
}
|
|
||||||
|
|
||||||
func printToc(names []string) {
|
|
||||||
for i, name := range names {
|
|
||||||
name = strings.TrimSuffix(name, filepath.Ext(name))
|
|
||||||
name = strings.Replace(name, "-", " ", -1)
|
|
||||||
names[i] = name
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(names)
|
|
||||||
|
|
||||||
toc := ""
|
|
||||||
for _, name := range names {
|
|
||||||
toc += "* [" + name + "](cli/" + strings.Replace(name, " ", "-", -1) + ".md)\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(toc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var docsCmd = &cobra.Command{
|
var docsCmd = &cobra.Command{
|
||||||
@@ -40,115 +22,61 @@ var docsCmd = &cobra.Command{
|
|||||||
Hidden: true,
|
Hidden: true,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
dir, err := getString(cmd.Flags(), "path")
|
outputDir, err := cmd.Flags().GetString("out")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = generateDocs(rootCmd, dir)
|
tempDir, err := os.MkdirTemp(os.TempDir(), "filebrowser-docs-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
names := []string{}
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
err = filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
rootCmd.Root().DisableAutoGenTag = true
|
||||||
if err != nil || info.IsDir() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(info.Name(), "filebrowser") {
|
err = doc.GenMarkdownTreeCustom(cmd.Root(), tempDir, func(f string) string {
|
||||||
return nil
|
return ""
|
||||||
}
|
}, func(s string) string {
|
||||||
|
return s
|
||||||
names = append(names, info.Name())
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
printToc(names)
|
entries, err := os.ReadDir(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
headerRegex := regexp.MustCompile(`(?m)^(##)(.*)$`)
|
||||||
|
linkRegex := regexp.MustCompile(`\(filebrowser(.*)\.md\)`)
|
||||||
|
|
||||||
|
fmt.Println("Generated Documents:")
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
srcPath := path.Join(tempDir, entry.Name())
|
||||||
|
dstPath := path.Join(outputDir, strings.ReplaceAll(entry.Name(), "_", "-"))
|
||||||
|
|
||||||
|
data, err := os.ReadFile(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data = headerRegex.ReplaceAll(data, []byte("#$2"))
|
||||||
|
data = linkRegex.ReplaceAllFunc(data, func(b []byte) []byte {
|
||||||
|
return bytes.ReplaceAll(b, []byte("_"), []byte("-"))
|
||||||
|
})
|
||||||
|
data = bytes.ReplaceAll(data, []byte("## SEE ALSO"), []byte("## See Also"))
|
||||||
|
|
||||||
|
err = os.WriteFile(dstPath, data, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("- " + dstPath)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateDocs(cmd *cobra.Command, dir string) error {
|
|
||||||
for _, c := range cmd.Commands() {
|
|
||||||
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err := generateDocs(c, dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
|
|
||||||
filename := filepath.Join(dir, basename)
|
|
||||||
f, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
return generateMarkdown(cmd, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateMarkdown(cmd *cobra.Command, w io.Writer) error {
|
|
||||||
cmd.InitDefaultHelpCmd()
|
|
||||||
cmd.InitDefaultHelpFlag()
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
name := cmd.CommandPath()
|
|
||||||
|
|
||||||
short := cmd.Short
|
|
||||||
long := cmd.Long
|
|
||||||
if long == "" {
|
|
||||||
long = short
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteString("---\ndescription: " + short + "\n---\n\n")
|
|
||||||
buf.WriteString("# " + name + "\n\n")
|
|
||||||
buf.WriteString("## Synopsis\n\n")
|
|
||||||
buf.WriteString(long + "\n\n")
|
|
||||||
|
|
||||||
if cmd.Runnable() {
|
|
||||||
_, _ = fmt.Fprintf(buf, "```\n%s\n```\n\n", cmd.UseLine())
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.Example != "" {
|
|
||||||
buf.WriteString("## Examples\n\n")
|
|
||||||
_, _ = fmt.Fprintf(buf, "```\n%s\n```\n\n", cmd.Example)
|
|
||||||
}
|
|
||||||
|
|
||||||
printOptions(buf, cmd)
|
|
||||||
_, err := buf.WriteTo(w)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
|
|
||||||
_, _ = buf.WriteString("| Name | Shorthand | Usage |\n")
|
|
||||||
_, _ = buf.WriteString("|------|-----------|-------|\n")
|
|
||||||
|
|
||||||
fs.VisitAll(func(f *pflag.Flag) {
|
|
||||||
_, _ = buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func printOptions(buf *bytes.Buffer, cmd *cobra.Command) {
|
|
||||||
flags := cmd.NonInheritedFlags()
|
|
||||||
flags.SetOutput(buf)
|
|
||||||
if flags.HasAvailableFlags() {
|
|
||||||
buf.WriteString("## Options\n\n")
|
|
||||||
generateFlagsTable(flags, buf)
|
|
||||||
buf.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
parentFlags := cmd.InheritedFlags()
|
|
||||||
parentFlags.SetOutput(buf)
|
|
||||||
if parentFlags.HasAvailableFlags() {
|
|
||||||
buf.WriteString("### Inherited\n\n")
|
|
||||||
generateFlagsTable(parentFlags, buf)
|
|
||||||
buf.WriteString("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
378
cmd/root.go
378
cmd/root.go
@@ -13,20 +13,17 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
v "github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/auth"
|
"github.com/filebrowser/filebrowser/v2/auth"
|
||||||
"github.com/filebrowser/filebrowser/v2/diskcache"
|
"github.com/filebrowser/filebrowser/v2/diskcache"
|
||||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/frontend"
|
"github.com/filebrowser/filebrowser/v2/frontend"
|
||||||
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
fbhttp "github.com/filebrowser/filebrowser/v2/http"
|
||||||
"github.com/filebrowser/filebrowser/v2/img"
|
"github.com/filebrowser/filebrowser/v2/img"
|
||||||
@@ -36,28 +33,67 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
flagNamesMigrations = map[string]string{
|
||||||
|
"file-mode": "fileMode",
|
||||||
|
"dir-mode": "dirMode",
|
||||||
|
"hide-login-button": "hideLoginButton",
|
||||||
|
"create-user-dir": "createUserDir",
|
||||||
|
"minimum-password-length": "minimumPasswordLength",
|
||||||
|
"socket-perm": "socketPerm",
|
||||||
|
"disable-thumbnails": "disableThumbnails",
|
||||||
|
"disable-preview-resize": "disablePreviewResize",
|
||||||
|
"disable-exec": "disableExec",
|
||||||
|
"disable-type-detection-by-header": "disableTypeDetectionByHeader",
|
||||||
|
"img-processors": "imageProcessors",
|
||||||
|
"cache-dir": "cacheDir",
|
||||||
|
"token-expiration-time": "tokenExpirationTime",
|
||||||
|
"baseurl": "baseURL",
|
||||||
|
}
|
||||||
|
|
||||||
|
warnedFlags = map[string]bool{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(remove): remove after July 2026.
|
||||||
|
func migrateFlagNames(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||||
|
if newName, ok := flagNamesMigrations[name]; ok {
|
||||||
|
|
||||||
|
if !warnedFlags[name] {
|
||||||
|
warnedFlags[name] = true
|
||||||
|
log.Printf("DEPRECATION NOTICE: Flag --%s has been deprecated, use --%s instead\n", name, newName)
|
||||||
|
}
|
||||||
|
|
||||||
|
name = newName
|
||||||
|
}
|
||||||
|
|
||||||
|
return pflag.NormalizedName(name)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
|
||||||
rootCmd.SilenceUsage = true
|
rootCmd.SilenceUsage = true
|
||||||
|
rootCmd.SetGlobalNormalizationFunc(migrateFlagNames)
|
||||||
|
|
||||||
cobra.MousetrapHelpText = ""
|
cobra.MousetrapHelpText = ""
|
||||||
|
|
||||||
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
||||||
|
|
||||||
flags := rootCmd.Flags()
|
// Flags available across the whole program
|
||||||
persistent := rootCmd.PersistentFlags()
|
persistent := rootCmd.PersistentFlags()
|
||||||
|
persistent.StringP("config", "c", "", "config file path")
|
||||||
persistent.StringVarP(&cfgFile, "config", "c", "", "config file path")
|
|
||||||
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.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")
|
|
||||||
|
|
||||||
|
// Runtime flags for the root command
|
||||||
|
flags := rootCmd.Flags()
|
||||||
|
flags.Bool("noauth", false, "use the noauth auther when using quick setup")
|
||||||
|
flags.String("username", "admin", "username for the first user when using quick setup")
|
||||||
|
flags.String("password", "", "hashed password for the first user when using quick setup")
|
||||||
|
flags.Uint32("socketPerm", 0666, "unix socket file permissions")
|
||||||
|
flags.String("cacheDir", "", "file cache directory (disabled if empty)")
|
||||||
|
flags.Int("imageProcessors", 4, "image processors count")
|
||||||
addServerFlags(flags)
|
addServerFlags(flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addServerFlags adds server related flags to the given FlagSet. These flags are available
|
||||||
|
// in both the root command, config set and config init commands.
|
||||||
func addServerFlags(flags *pflag.FlagSet) {
|
func addServerFlags(flags *pflag.FlagSet) {
|
||||||
flags.StringP("address", "a", "127.0.0.1", "address to listen on")
|
flags.StringP("address", "a", "127.0.0.1", "address to listen on")
|
||||||
flags.StringP("log", "l", "stdout", "log output")
|
flags.StringP("log", "l", "stdout", "log output")
|
||||||
@@ -66,15 +102,12 @@ 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.StringP("baseURL", "b", "", "base url")
|
||||||
flags.StringP("baseurl", "b", "", "base url")
|
flags.String("tokenExpirationTime", "2h", "user session timeout")
|
||||||
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
flags.Bool("disableThumbnails", false, "disable image thumbnails")
|
||||||
flags.String("token-expiration-time", "2h", "user session timeout")
|
flags.Bool("disablePreviewResize", false, "disable resize of image previews")
|
||||||
flags.Int("img-processors", 4, "image processors count") //nolint:mnd
|
flags.Bool("disableExec", true, "disables Command Runner feature")
|
||||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
flags.Bool("disableTypeDetectionByHeader", false, "disables type detection by reading file headers")
|
||||||
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
|
||||||
flags.Bool("disable-exec", true, "disables Command Runner feature")
|
|
||||||
flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@@ -89,12 +122,14 @@ it. Don't worry: you don't need to setup a separate database server.
|
|||||||
We're using Bolt DB which is a single file database and all managed
|
We're using Bolt DB which is a single file database and all managed
|
||||||
by ourselves.
|
by ourselves.
|
||||||
|
|
||||||
For this specific command, all the flags you have available (except
|
For this command, all flags are available as environmental variables,
|
||||||
"config" for the configuration file), can be given either through
|
except for "--config", which specifies the configuration file to use.
|
||||||
environment variables or configuration files.
|
The environment variables are prefixed by "FB_" followed by the flag name in
|
||||||
|
UPPER_SNAKE_CASE. For example, the flag "--disablePreviewResize" is available
|
||||||
|
as FB_DISABLE_PREVIEW_RESIZE.
|
||||||
|
|
||||||
If you don't set "config", it will look for a configuration file called
|
If "--config" is not specified, File Browser will look for a configuration
|
||||||
.filebrowser.{json, toml, yaml, yml} in the following directories:
|
file named .filebrowser.{json, toml, yaml, yml} in the following directories:
|
||||||
|
|
||||||
- ./
|
- ./
|
||||||
- $HOME/
|
- $HOME/
|
||||||
@@ -102,52 +137,40 @@ If you don't set "config", it will look for a configuration file called
|
|||||||
|
|
||||||
The precedence of the configuration values are as follows:
|
The precedence of the configuration values are as follows:
|
||||||
|
|
||||||
- flags
|
- Flags
|
||||||
- environment variables
|
- Environment variables
|
||||||
- configuration file
|
- Configuration file
|
||||||
- database values
|
- Database values
|
||||||
- defaults
|
- Defaults
|
||||||
|
|
||||||
The environment variables are prefixed by "FB_" followed by the option
|
|
||||||
name in caps. So to set "database" via an env variable, you should
|
|
||||||
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 bootstrapped and a new
|
||||||
user created with the credentials from options "username" and "password".`,
|
user created with the credentials from options "username" and "password".`,
|
||||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
RunE: withViperAndStore(func(cmd *cobra.Command, _ []string, v *viper.Viper, st *store) error {
|
||||||
log.Println(cfgFile)
|
if !st.databaseExisted {
|
||||||
|
err := quickSetup(v, st.Storage)
|
||||||
if !d.hadDB {
|
|
||||||
err := quickSetup(cmd.Flags(), *d)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build img service
|
// build img service
|
||||||
workersCount, err := cmd.Flags().GetInt("img-processors")
|
imgWorkersCount := v.GetInt("imageProcessors")
|
||||||
if err != nil {
|
if imgWorkersCount < 1 {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if workersCount < 1 {
|
|
||||||
return errors.New("image resize workers count could not be < 1")
|
return errors.New("image resize workers count could not be < 1")
|
||||||
}
|
}
|
||||||
imgSvc := img.New(workersCount)
|
imageService := img.New(imgWorkersCount)
|
||||||
|
|
||||||
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
||||||
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
cacheDir := v.GetString("cacheDir")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if cacheDir != "" {
|
if cacheDir != "" {
|
||||||
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
|
if err := os.MkdirAll(cacheDir, 0700); err != nil {
|
||||||
return fmt.Errorf("can't make directory %s: %w", cacheDir, err)
|
return fmt.Errorf("can't make directory %s: %w", cacheDir, err)
|
||||||
}
|
}
|
||||||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := getRunParams(cmd.Flags(), d.store)
|
server, err := getServerSettings(v, st.Storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -169,16 +192,13 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
socketPerm := v.GetUint32("socketPerm")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case server.TLSKey != "" && server.TLSCert != "":
|
case server.TLSKey != "" && server.TLSCert != "":
|
||||||
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet
|
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -201,7 +221,7 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
|
handler, err := fbhttp.NewHandler(imageService, fileCache, st.Storage, server, assetsFs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -233,7 +253,7 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
sig := <-sigc
|
sig := <-sigc
|
||||||
log.Println("Got signal:", sig)
|
log.Println("Got signal:", sig)
|
||||||
|
|
||||||
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) //nolint:mnd
|
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer shutdownRelease()
|
defer shutdownRelease()
|
||||||
|
|
||||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||||
@@ -241,66 +261,78 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
}
|
}
|
||||||
log.Println("Graceful shutdown complete.")
|
log.Println("Graceful shutdown complete.")
|
||||||
|
|
||||||
switch sig {
|
return nil
|
||||||
case syscall.SIGHUP:
|
}, storeOptions{allowsNoDatabase: true}),
|
||||||
d.err = fbErrors.ErrSighup
|
|
||||||
case syscall.SIGINT:
|
|
||||||
d.err = fbErrors.ErrSigint
|
|
||||||
case syscall.SIGQUIT:
|
|
||||||
d.err = fbErrors.ErrSigquit
|
|
||||||
case syscall.SIGTERM:
|
|
||||||
d.err = fbErrors.ErrSigTerm
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.err
|
|
||||||
}, pythonConfig{allowNoDB: true}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
func getServerSettings(v *viper.Viper, st *storage.Storage) (*settings.Server, error) {
|
||||||
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server, error) {
|
|
||||||
server, err := st.Settings.GetServer()
|
server, err := st.Settings.GetServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "root"); set {
|
|
||||||
server.Root = val
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "baseurl"); set {
|
|
||||||
server.BaseURL = val
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "log"); set {
|
|
||||||
server.Log = val
|
|
||||||
}
|
|
||||||
|
|
||||||
isSocketSet := false
|
isSocketSet := false
|
||||||
isAddrSet := false
|
isAddrSet := false
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "address"); set {
|
if v.IsSet("address") {
|
||||||
server.Address = val
|
server.Address = v.GetString("address")
|
||||||
isAddrSet = isAddrSet || set
|
isAddrSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "port"); set {
|
if v.IsSet("log") {
|
||||||
server.Port = val
|
server.Log = v.GetString("log")
|
||||||
isAddrSet = isAddrSet || set
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "key"); set {
|
if v.IsSet("port") {
|
||||||
server.TLSKey = val
|
server.Port = v.GetString("port")
|
||||||
isAddrSet = isAddrSet || set
|
isAddrSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "cert"); set {
|
if v.IsSet("cert") {
|
||||||
server.TLSCert = val
|
server.TLSCert = v.GetString("cert")
|
||||||
isAddrSet = isAddrSet || set
|
isAddrSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "socket"); set {
|
if v.IsSet("key") {
|
||||||
server.Socket = val
|
server.TLSKey = v.GetString("key")
|
||||||
isSocketSet = isSocketSet || set
|
isAddrSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsSet("root") {
|
||||||
|
server.Root = v.GetString("root")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsSet("socket") {
|
||||||
|
server.Socket = v.GetString("socket")
|
||||||
|
isSocketSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsSet("baseURL") {
|
||||||
|
server.BaseURL = v.GetString("baseURL")
|
||||||
|
// TODO(remove): remove after July 2026.
|
||||||
|
} else if v := os.Getenv("FB_BASEURL"); v != "" {
|
||||||
|
log.Println("DEPRECATION NOTICE: Environment variable FB_BASEURL has been deprecated, use FB_BASE_URL instead")
|
||||||
|
server.BaseURL = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsSet("tokenExpirationTime") {
|
||||||
|
server.TokenExpirationTime = v.GetString("tokenExpirationTime")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsSet("disableThumbnails") {
|
||||||
|
server.EnableThumbnails = !v.GetBool("disableThumbnails")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsSet("disablePreviewResize") {
|
||||||
|
server.ResizePreview = !v.GetBool("disablePreviewResize")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsSet("disableTypeDetectionByHeader") {
|
||||||
|
server.TypeDetectionByHeader = !v.GetBool("disableTypeDetectionByHeader")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.IsSet("disableExec") {
|
||||||
|
server.EnableExec = !v.GetBool("disableExec")
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAddrSet && isSocketSet {
|
if isAddrSet && isSocketSet {
|
||||||
@@ -312,18 +344,6 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server,
|
|||||||
server.Socket = ""
|
server.Socket = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
disableThumbnails := getBoolParam(flags, "disable-thumbnails")
|
|
||||||
server.EnableThumbnails = !disableThumbnails
|
|
||||||
|
|
||||||
disablePreviewResize := getBoolParam(flags, "disable-preview-resize")
|
|
||||||
server.ResizePreview = !disablePreviewResize
|
|
||||||
|
|
||||||
disableTypeDetectionByHeader := getBoolParam(flags, "disable-type-detection-by-header")
|
|
||||||
server.TypeDetectionByHeader = !disableTypeDetectionByHeader
|
|
||||||
|
|
||||||
disableExec := getBoolParam(flags, "disable-exec")
|
|
||||||
server.EnableExec = !disableExec
|
|
||||||
|
|
||||||
if server.EnableExec {
|
if server.EnableExec {
|
||||||
log.Println("WARNING: Command Runner feature enabled!")
|
log.Println("WARNING: Command Runner feature enabled!")
|
||||||
log.Println("WARNING: This feature has known security vulnerabilities and should not")
|
log.Println("WARNING: This feature has known security vulnerabilities and should not")
|
||||||
@@ -331,71 +351,9 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server,
|
|||||||
log.Println("WARNING: read https://github.com/filebrowser/filebrowser/issues/5199")
|
log.Println("WARNING: read https://github.com/filebrowser/filebrowser/issues/5199")
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "token-expiration-time"); set {
|
|
||||||
server.TokenExpirationTime = val
|
|
||||||
}
|
|
||||||
|
|
||||||
return server, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBoolParamB 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 getBoolParamB(flags *pflag.FlagSet, key string) (value, ok 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)
|
|
||||||
|
|
||||||
// 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.GetString(key), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise use default value on flags.
|
|
||||||
return value, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStringParam(flags *pflag.FlagSet, key string) string {
|
|
||||||
val, _ := getStringParamB(flags, key)
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupLog(logMethod string) {
|
func setupLog(logMethod string) {
|
||||||
switch logMethod {
|
switch logMethod {
|
||||||
case "stdout":
|
case "stdout":
|
||||||
@@ -414,12 +372,13 @@ func setupLog(logMethod string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
func quickSetup(v *viper.Viper, s *storage.Storage) error {
|
||||||
log.Println("Performing quick setup")
|
log.Println("Performing quick setup")
|
||||||
|
|
||||||
set := &settings.Settings{
|
set := &settings.Settings{
|
||||||
Key: generateKey(),
|
Key: generateKey(),
|
||||||
Signup: false,
|
Signup: false,
|
||||||
|
HideLoginButton: true,
|
||||||
CreateUserDir: false,
|
CreateUserDir: false,
|
||||||
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
|
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
|
||||||
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
||||||
@@ -427,7 +386,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||||||
Scope: ".",
|
Scope: ".",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
SingleClick: false,
|
SingleClick: false,
|
||||||
AceEditorTheme: getStringParam(flags, "defaults.aceEditorTheme"),
|
AceEditorTheme: v.GetString("defaults.aceEditorTheme"),
|
||||||
Perm: users.Permissions{
|
Perm: users.Permissions{
|
||||||
Admin: false,
|
Admin: false,
|
||||||
Execute: true,
|
Execute: true,
|
||||||
@@ -451,39 +410,44 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if _, noauth := getStringParamB(flags, "noauth"); noauth {
|
if v.GetBool("noauth") {
|
||||||
set.AuthMethod = auth.MethodNoAuth
|
set.AuthMethod = auth.MethodNoAuth
|
||||||
err = d.store.Auth.Save(&auth.NoAuth{})
|
err = s.Auth.Save(&auth.NoAuth{})
|
||||||
} else {
|
} else {
|
||||||
set.AuthMethod = auth.MethodJSONAuth
|
set.AuthMethod = auth.MethodJSONAuth
|
||||||
err = d.store.Auth.Save(&auth.JSONAuth{})
|
err = s.Auth.Save(&auth.JSONAuth{})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Settings.Save(set)
|
err = s.Settings.Save(set)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ser := &settings.Server{
|
ser := &settings.Server{
|
||||||
BaseURL: getStringParam(flags, "baseurl"),
|
BaseURL: v.GetString("baseURL"),
|
||||||
Port: getStringParam(flags, "port"),
|
Port: v.GetString("port"),
|
||||||
Log: getStringParam(flags, "log"),
|
Log: v.GetString("log"),
|
||||||
TLSKey: getStringParam(flags, "key"),
|
TLSKey: v.GetString("key"),
|
||||||
TLSCert: getStringParam(flags, "cert"),
|
TLSCert: v.GetString("cert"),
|
||||||
Address: getStringParam(flags, "address"),
|
Address: v.GetString("address"),
|
||||||
Root: getStringParam(flags, "root"),
|
Root: v.GetString("root"),
|
||||||
|
TokenExpirationTime: v.GetString("tokenExpirationTime"),
|
||||||
|
EnableThumbnails: !v.GetBool("disableThumbnails"),
|
||||||
|
ResizePreview: !v.GetBool("disablePreviewResize"),
|
||||||
|
EnableExec: !v.GetBool("disableExec"),
|
||||||
|
TypeDetectionByHeader: !v.GetBool("disableTypeDetectionByHeader"),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Settings.SaveServer(ser)
|
err = s.Settings.SaveServer(ser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
username := getStringParam(flags, "username")
|
username := v.GetString("username")
|
||||||
password := getStringParam(flags, "password")
|
password := v.GetString("password")
|
||||||
|
|
||||||
if password == "" {
|
if password == "" {
|
||||||
var pwd string
|
var pwd string
|
||||||
@@ -514,35 +478,5 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||||||
set.Defaults.Apply(user)
|
set.Defaults.Apply(user)
|
||||||
user.Perm.Admin = true
|
user.Perm.Admin = true
|
||||||
|
|
||||||
return d.store.Users.Save(user)
|
return s.Users.Save(user)
|
||||||
}
|
|
||||||
|
|
||||||
func initConfig() {
|
|
||||||
if cfgFile == "" {
|
|
||||||
home, err := homedir.Dir()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
v.AddConfigPath(".")
|
|
||||||
v.AddConfigPath(home)
|
|
||||||
v.AddConfigPath("/etc/filebrowser/")
|
|
||||||
v.SetConfigName(".filebrowser")
|
|
||||||
} else {
|
|
||||||
v.SetConfigFile(cfgFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
v.SetEnvPrefix("FB")
|
|
||||||
v.AutomaticEnv()
|
|
||||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
||||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
|
||||||
|
|
||||||
if err := v.ReadInConfig(); err != nil {
|
|
||||||
var configParseError v.ConfigParseError
|
|
||||||
if errors.As(err, &configParseError) {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
cfgFile = "No config file used"
|
|
||||||
} else {
|
|
||||||
cfgFile = "Using config file: " + v.ConfigFileUsed()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ including 'index_end'.`,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||||
i, err := strconv.Atoi(args[0])
|
i, err := strconv.Atoi(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -55,14 +55,14 @@ including 'index_end'.`,
|
|||||||
|
|
||||||
user := func(u *users.User) error {
|
user := func(u *users.User) error {
|
||||||
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
|
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
|
||||||
return d.store.Users.Save(u)
|
return st.Users.Save(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
global := func(s *settings.Settings) error {
|
global := func(s *settings.Settings) error {
|
||||||
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
||||||
return d.store.Settings.Save(s)
|
return st.Settings.Save(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
return runRules(d.store, cmd, user, global)
|
return runRules(st.Storage, cmd, user, global)
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,11 +69,12 @@ func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getUserIdentifier(flags *pflag.FlagSet) (interface{}, error) {
|
func getUserIdentifier(flags *pflag.FlagSet) (interface{}, error) {
|
||||||
id, err := getUint(flags, "id")
|
id, err := flags.GetUint("id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
username, err := getString(flags, "username")
|
|
||||||
|
username, err := flags.GetString("username")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,19 @@ var rulesAddCmd = &cobra.Command{
|
|||||||
Short: "Add a global rule or user rule",
|
Short: "Add a global rule or user rule",
|
||||||
Long: `Add a global rule or user rule.`,
|
Long: `Add a global rule or user rule.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||||
allow, err := getBool(cmd.Flags(), "allow")
|
flags := cmd.Flags()
|
||||||
|
|
||||||
|
allow, err := flags.GetBool("allow")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
regex, err := getBool(cmd.Flags(), "regex")
|
|
||||||
|
regex, err := flags.GetBool("regex")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
exp := args[0]
|
exp := args[0]
|
||||||
|
|
||||||
if regex {
|
if regex {
|
||||||
@@ -49,14 +53,14 @@ var rulesAddCmd = &cobra.Command{
|
|||||||
|
|
||||||
user := func(u *users.User) error {
|
user := func(u *users.User) error {
|
||||||
u.Rules = append(u.Rules, rule)
|
u.Rules = append(u.Rules, rule)
|
||||||
return d.store.Users.Save(u)
|
return st.Users.Save(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
global := func(s *settings.Settings) error {
|
global := func(s *settings.Settings) error {
|
||||||
s.Rules = append(s.Rules, rule)
|
s.Rules = append(s.Rules, rule)
|
||||||
return d.store.Settings.Save(s)
|
return st.Settings.Save(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
return runRules(d.store, cmd, user, global)
|
return runRules(st.Storage, cmd, user, global)
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ var rulesLsCommand = &cobra.Command{
|
|||||||
Short: "List global rules or user specific rules",
|
Short: "List global rules or user specific rules",
|
||||||
Long: `List global rules or user specific rules.`,
|
Long: `List global rules or user specific rules.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error {
|
RunE: withStore(func(cmd *cobra.Command, _ []string, st *store) error {
|
||||||
return runRules(d.store, cmd, nil, nil)
|
return runRules(st.Storage, cmd, nil, nil)
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/storage/bolt/importer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(upgradeCmd)
|
|
||||||
|
|
||||||
upgradeCmd.Flags().String("old.database", "", "")
|
|
||||||
upgradeCmd.Flags().String("old.config", "", "")
|
|
||||||
_ = upgradeCmd.MarkFlagRequired("old.database")
|
|
||||||
}
|
|
||||||
|
|
||||||
var upgradeCmd = &cobra.Command{
|
|
||||||
Use: "upgrade",
|
|
||||||
Short: "Upgrades an old configuration",
|
|
||||||
Long: `Upgrades an old configuration. This command DOES NOT
|
|
||||||
import share links because they are incompatible with
|
|
||||||
this version.`,
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
||||||
flags := cmd.Flags()
|
|
||||||
oldDB, err := getString(flags, "old.database")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
oldConf, err := getString(flags, "old.config")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
db, err := getString(flags, "database")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return importer.Import(oldDB, oldConf, db)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
51
cmd/users.go
51
cmd/users.go
@@ -82,62 +82,64 @@ func addUserFlags(flags *pflag.FlagSet) {
|
|||||||
flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users")
|
flags.String("aceEditorTheme", "", "ace editor's syntax highlighting theme for users")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
|
func getAndParseViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
|
||||||
viewModeStr, err := getString(flags, "viewMode")
|
viewModeStr, err := flags.GetString("viewMode")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
viewMode := users.ViewMode(viewModeStr)
|
viewMode := users.ViewMode(viewModeStr)
|
||||||
if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode {
|
if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode {
|
||||||
return "", errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"")
|
return "", errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
return viewMode, nil
|
return viewMode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
|
||||||
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) error {
|
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) error {
|
||||||
var visitErr error
|
errs := []error{}
|
||||||
|
|
||||||
visit := func(flag *pflag.Flag) {
|
visit := func(flag *pflag.Flag) {
|
||||||
if visitErr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var err error
|
var err error
|
||||||
switch flag.Name {
|
switch flag.Name {
|
||||||
case "scope":
|
case "scope":
|
||||||
defaults.Scope, err = getString(flags, flag.Name)
|
defaults.Scope, err = flags.GetString(flag.Name)
|
||||||
case "locale":
|
case "locale":
|
||||||
defaults.Locale, err = getString(flags, flag.Name)
|
defaults.Locale, err = flags.GetString(flag.Name)
|
||||||
case "viewMode":
|
case "viewMode":
|
||||||
defaults.ViewMode, err = getViewMode(flags)
|
defaults.ViewMode, err = getAndParseViewMode(flags)
|
||||||
case "singleClick":
|
case "singleClick":
|
||||||
defaults.SingleClick, err = getBool(flags, flag.Name)
|
defaults.SingleClick, err = flags.GetBool(flag.Name)
|
||||||
case "aceEditorTheme":
|
case "aceEditorTheme":
|
||||||
defaults.AceEditorTheme, err = getString(flags, flag.Name)
|
defaults.AceEditorTheme, err = flags.GetString(flag.Name)
|
||||||
case "perm.admin":
|
case "perm.admin":
|
||||||
defaults.Perm.Admin, err = getBool(flags, flag.Name)
|
defaults.Perm.Admin, err = flags.GetBool(flag.Name)
|
||||||
case "perm.execute":
|
case "perm.execute":
|
||||||
defaults.Perm.Execute, err = getBool(flags, flag.Name)
|
defaults.Perm.Execute, err = flags.GetBool(flag.Name)
|
||||||
case "perm.create":
|
case "perm.create":
|
||||||
defaults.Perm.Create, err = getBool(flags, flag.Name)
|
defaults.Perm.Create, err = flags.GetBool(flag.Name)
|
||||||
case "perm.rename":
|
case "perm.rename":
|
||||||
defaults.Perm.Rename, err = getBool(flags, flag.Name)
|
defaults.Perm.Rename, err = flags.GetBool(flag.Name)
|
||||||
case "perm.modify":
|
case "perm.modify":
|
||||||
defaults.Perm.Modify, err = getBool(flags, flag.Name)
|
defaults.Perm.Modify, err = flags.GetBool(flag.Name)
|
||||||
case "perm.delete":
|
case "perm.delete":
|
||||||
defaults.Perm.Delete, err = getBool(flags, flag.Name)
|
defaults.Perm.Delete, err = flags.GetBool(flag.Name)
|
||||||
case "perm.share":
|
case "perm.share":
|
||||||
defaults.Perm.Share, err = getBool(flags, flag.Name)
|
defaults.Perm.Share, err = flags.GetBool(flag.Name)
|
||||||
case "perm.download":
|
case "perm.download":
|
||||||
defaults.Perm.Download, err = getBool(flags, flag.Name)
|
defaults.Perm.Download, err = flags.GetBool(flag.Name)
|
||||||
case "commands":
|
case "commands":
|
||||||
defaults.Commands, err = flags.GetStringSlice(flag.Name)
|
defaults.Commands, err = flags.GetStringSlice(flag.Name)
|
||||||
case "sorting.by":
|
case "sorting.by":
|
||||||
defaults.Sorting.By, err = getString(flags, flag.Name)
|
defaults.Sorting.By, err = flags.GetString(flag.Name)
|
||||||
case "sorting.asc":
|
case "sorting.asc":
|
||||||
defaults.Sorting.Asc, err = getBool(flags, flag.Name)
|
defaults.Sorting.Asc, err = flags.GetBool(flag.Name)
|
||||||
|
case "hideDotfiles":
|
||||||
|
defaults.HideDotfiles, err = flags.GetBool(flag.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
visitErr = err
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,5 +148,6 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
|
|||||||
} else {
|
} else {
|
||||||
flags.Visit(visit)
|
flags.Visit(visit)
|
||||||
}
|
}
|
||||||
return visitErr
|
|
||||||
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ var usersAddCmd = &cobra.Command{
|
|||||||
Short: "Create a new user",
|
Short: "Create a new user",
|
||||||
Long: `Create a new user and add it to the database.`,
|
Long: `Create a new user and add it to the database.`,
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||||
s, err := d.store.Settings.Get()
|
flags := cmd.Flags()
|
||||||
|
s, err := st.Settings.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
err = getUserDefaults(flags, &s.Defaults, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -31,39 +32,36 @@ var usersAddCmd = &cobra.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lockPassword, err := getBool(cmd.Flags(), "lockPassword")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dateFormat, err := getBool(cmd.Flags(), "dateFormat")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hideDotfiles, err := getBool(cmd.Flags(), "hideDotfiles")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &users.User{
|
user := &users.User{
|
||||||
Username: args[0],
|
Username: args[0],
|
||||||
Password: password,
|
Password: password,
|
||||||
LockPassword: lockPassword,
|
}
|
||||||
DateFormat: dateFormat,
|
|
||||||
HideDotfiles: hideDotfiles,
|
user.LockPassword, err = flags.GetBool("lockPassword")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.DateFormat, err = flags.GetBool("dateFormat")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.HideDotfiles, err = flags.GetBool("hideDotfiles")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Defaults.Apply(user)
|
s.Defaults.Apply(user)
|
||||||
|
|
||||||
servSettings, err := d.store.Settings.GetServer()
|
servSettings, err := st.Settings.GetServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// since getUserDefaults() polluted s.Defaults.Scope
|
// since getUserDefaults() polluted s.Defaults.Scope
|
||||||
// which makes the Scope not the one saved in the db
|
// which makes the Scope not the one saved in the db
|
||||||
// we need the right s.Defaults.Scope here
|
// we need the right s.Defaults.Scope here
|
||||||
s2, err := d.store.Settings.Get()
|
s2, err := st.Settings.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -74,11 +72,11 @@ var usersAddCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
user.Scope = userHome
|
user.Scope = userHome
|
||||||
|
|
||||||
err = d.store.Users.Save(user)
|
err = st.Users.Save(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printUsers([]*users.User{user})
|
printUsers([]*users.User{user})
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ var usersExportCmd = &cobra.Command{
|
|||||||
Long: `Export all users to a json or yaml file. Please indicate the
|
Long: `Export all users to a json or yaml file. Please indicate the
|
||||||
path to the file where you want to write the users.`,
|
path to the file where you want to write the users.`,
|
||||||
Args: jsonYamlArg,
|
Args: jsonYamlArg,
|
||||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||||
list, err := d.store.Users.Gets("")
|
list, err := st.Users.Gets("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -25,5 +25,5 @@ path to the file where you want to write the users.`,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ var usersLsCmd = &cobra.Command{
|
|||||||
RunE: findUsers,
|
RunE: findUsers,
|
||||||
}
|
}
|
||||||
|
|
||||||
var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
var findUsers = withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||||
var (
|
var (
|
||||||
list []*users.User
|
list []*users.User
|
||||||
user *users.User
|
user *users.User
|
||||||
@@ -36,14 +36,14 @@ var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) erro
|
|||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
username, id := parseUsernameOrID(args[0])
|
username, id := parseUsernameOrID(args[0])
|
||||||
if username != "" {
|
if username != "" {
|
||||||
user, err = d.store.Users.Get("", username)
|
user, err = st.Users.Get("", username)
|
||||||
} else {
|
} else {
|
||||||
user, err = d.store.Users.Get("", id)
|
user, err = st.Users.Get("", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
list = []*users.User{user}
|
list = []*users.User{user}
|
||||||
} else {
|
} else {
|
||||||
list, err = d.store.Users.Gets("")
|
list, err = st.Users.Gets("")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -51,4 +51,4 @@ var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) erro
|
|||||||
}
|
}
|
||||||
printUsers(list)
|
printUsers(list)
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{})
|
}, storeOptions{})
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ file. You can use this command to import new users to your
|
|||||||
installation. For that, just don't place their ID on the files
|
installation. For that, just don't place their ID on the files
|
||||||
list or set it to 0.`,
|
list or set it to 0.`,
|
||||||
Args: jsonYamlArg,
|
Args: jsonYamlArg,
|
||||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||||
|
flags := cmd.Flags()
|
||||||
fd, err := os.Open(args[0])
|
fd, err := os.Open(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -45,13 +46,13 @@ list or set it to 0.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replace, err := getBool(cmd.Flags(), "replace")
|
replace, err := flags.GetBool("replace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if replace {
|
if replace {
|
||||||
oldUsers, userImportErr := d.store.Users.Gets("")
|
oldUsers, userImportErr := st.Users.Gets("")
|
||||||
if userImportErr != nil {
|
if userImportErr != nil {
|
||||||
return userImportErr
|
return userImportErr
|
||||||
}
|
}
|
||||||
@@ -62,20 +63,20 @@ list or set it to 0.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range oldUsers {
|
for _, user := range oldUsers {
|
||||||
err = d.store.Users.Delete(user.ID)
|
err = st.Users.Delete(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
overwrite, err := getBool(cmd.Flags(), "overwrite")
|
overwrite, err := flags.GetBool("overwrite")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range list {
|
for _, user := range list {
|
||||||
onDB, err := d.store.Users.Get("", user.ID)
|
onDB, err := st.Users.Get("", user.ID)
|
||||||
|
|
||||||
// User exists in DB.
|
// User exists in DB.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -87,7 +88,7 @@ list or set it to 0.`,
|
|||||||
// with the new username. If there is, print an error and cancel the
|
// with the new username. If there is, print an error and cancel the
|
||||||
// operation
|
// operation
|
||||||
if user.Username != onDB.Username {
|
if user.Username != onDB.Username {
|
||||||
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
|
if conflictuous, err := st.Users.Get("", user.Username); err == nil {
|
||||||
return usernameConflictError(user.Username, conflictuous.ID, user.ID)
|
return usernameConflictError(user.Username, conflictuous.ID, user.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,13 +98,13 @@ list or set it to 0.`,
|
|||||||
user.ID = 0
|
user.ID = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Users.Save(user)
|
err = st.Users.Save(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
func usernameConflictError(username string, originalID, newID uint) error {
|
func usernameConflictError(username string, originalID, newID uint) error {
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ var usersRmCmd = &cobra.Command{
|
|||||||
Short: "Delete a user by username or id",
|
Short: "Delete a user by username or id",
|
||||||
Long: `Delete a user by username or id`,
|
Long: `Delete a user by username or id`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(_ *cobra.Command, args []string, st *store) error {
|
||||||
username, id := parseUsernameOrID(args[0])
|
username, id := parseUsernameOrID(args[0])
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if username != "" {
|
if username != "" {
|
||||||
err = d.store.Users.Delete(username)
|
err = st.Users.Delete(username)
|
||||||
} else {
|
} else {
|
||||||
err = d.store.Users.Delete(id)
|
err = st.Users.Delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -30,5 +30,5 @@ var usersRmCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
fmt.Println("user deleted successfully")
|
fmt.Println("user deleted successfully")
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,19 +21,20 @@ var usersUpdateCmd = &cobra.Command{
|
|||||||
Long: `Updates an existing user. Set the flags for the
|
Long: `Updates an existing user. Set the flags for the
|
||||||
options you want to change.`,
|
options you want to change.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
RunE: withStore(func(cmd *cobra.Command, args []string, st *store) error {
|
||||||
username, id := parseUsernameOrID(args[0])
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
password, err := getString(flags, "password")
|
username, id := parseUsernameOrID(args[0])
|
||||||
if err != nil {
|
password, err := flags.GetString("password")
|
||||||
return err
|
|
||||||
}
|
|
||||||
newUsername, err := getString(flags, "username")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := d.store.Settings.Get()
|
newUsername, err := flags.GetString("username")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := st.Settings.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -41,13 +42,11 @@ options you want to change.`,
|
|||||||
var (
|
var (
|
||||||
user *users.User
|
user *users.User
|
||||||
)
|
)
|
||||||
|
|
||||||
if id != 0 {
|
if id != 0 {
|
||||||
user, err = d.store.Users.Get("", id)
|
user, err = st.Users.Get("", id)
|
||||||
} else {
|
} else {
|
||||||
user, err = d.store.Users.Get("", username)
|
user, err = st.Users.Get("", username)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -61,10 +60,12 @@ options you want to change.`,
|
|||||||
Sorting: user.Sorting,
|
Sorting: user.Sorting,
|
||||||
Commands: user.Commands,
|
Commands: user.Commands,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = getUserDefaults(flags, &defaults, false)
|
err = getUserDefaults(flags, &defaults, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Scope = defaults.Scope
|
user.Scope = defaults.Scope
|
||||||
user.Locale = defaults.Locale
|
user.Locale = defaults.Locale
|
||||||
user.ViewMode = defaults.ViewMode
|
user.ViewMode = defaults.ViewMode
|
||||||
@@ -72,15 +73,17 @@ options you want to change.`,
|
|||||||
user.Perm = defaults.Perm
|
user.Perm = defaults.Perm
|
||||||
user.Commands = defaults.Commands
|
user.Commands = defaults.Commands
|
||||||
user.Sorting = defaults.Sorting
|
user.Sorting = defaults.Sorting
|
||||||
user.LockPassword, err = getBool(flags, "lockPassword")
|
user.LockPassword, err = flags.GetBool("lockPassword")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
user.DateFormat, err = getBool(flags, "dateFormat")
|
|
||||||
|
user.DateFormat, err = flags.GetBool("dateFormat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
user.HideDotfiles, err = getBool(flags, "hideDotfiles")
|
|
||||||
|
user.HideDotfiles, err = flags.GetBool("hideDotfiles")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -96,11 +99,11 @@ options you want to change.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Users.Update(user)
|
err = st.Users.Update(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printUsers([]*users.User{user})
|
printUsers([]*users.User{user})
|
||||||
return nil
|
return nil
|
||||||
}, pythonConfig{}),
|
}, storeOptions{}),
|
||||||
}
|
}
|
||||||
|
|||||||
183
cmd/utils.go
183
cmd/utils.go
@@ -12,8 +12,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/samber/lo"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/spf13/viper"
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
@@ -21,42 +24,22 @@ import (
|
|||||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dbPerms = 0640
|
const databasePermissions = 0640
|
||||||
|
|
||||||
func returnErr(err error) error {
|
func getAndParseFileMode(flags *pflag.FlagSet, name string) (fs.FileMode, error) {
|
||||||
if err != nil {
|
mode, err := flags.GetString(name)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getString(flags *pflag.FlagSet, flag string) (string, error) {
|
|
||||||
s, err := flags.GetString(flag)
|
|
||||||
return s, returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
|
|
||||||
s, err := getString(flags, flag)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
b, err := strconv.ParseUint(s, 0, 32)
|
|
||||||
|
b, err := strconv.ParseUint(mode, 0, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fs.FileMode(b), nil
|
return fs.FileMode(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBool(flags *pflag.FlagSet, flag string) (bool, error) {
|
|
||||||
b, err := flags.GetBool(flag)
|
|
||||||
return b, returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
|
|
||||||
b, err := flags.GetUint(flag)
|
|
||||||
return b, returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateKey() []byte {
|
func generateKey() []byte {
|
||||||
k, err := settings.GenerateKey()
|
k, err := settings.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -65,20 +48,6 @@ func generateKey() []byte {
|
|||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
type cobraFunc func(cmd *cobra.Command, args []string) error
|
|
||||||
type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error
|
|
||||||
|
|
||||||
type pythonConfig struct {
|
|
||||||
noDB bool
|
|
||||||
allowNoDB bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type pythonData struct {
|
|
||||||
hadDB bool
|
|
||||||
store *storage.Storage
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbExists(path string) (bool, error) {
|
func dbExists(path string) (bool, error) {
|
||||||
stat, err := os.Stat(path)
|
stat, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -89,7 +58,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 {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
@@ -99,40 +68,134 @@ func dbExists(path string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
// Generate the replacements for all environment variables. This allows to
|
||||||
|
// use FB_BRANDING_DISABLE_EXTERNAL environment variables, even when the
|
||||||
|
// option name is branding.disableExternal.
|
||||||
|
func generateEnvKeyReplacements(cmd *cobra.Command) []string {
|
||||||
|
replacements := []string{}
|
||||||
|
|
||||||
|
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||||
|
oldName := strings.ToUpper(f.Name)
|
||||||
|
newName := strings.ToUpper(lo.SnakeCase(f.Name))
|
||||||
|
replacements = append(replacements, oldName, newName)
|
||||||
|
})
|
||||||
|
|
||||||
|
return replacements
|
||||||
|
}
|
||||||
|
|
||||||
|
func initViper(cmd *cobra.Command) (*viper.Viper, error) {
|
||||||
|
v := viper.New()
|
||||||
|
|
||||||
|
// Get config file from flag
|
||||||
|
cfgFile, err := cmd.Flags().GetString("config")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration file
|
||||||
|
if cfgFile == "" {
|
||||||
|
home, err := homedir.Dir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v.AddConfigPath(".")
|
||||||
|
v.AddConfigPath(home)
|
||||||
|
v.AddConfigPath("/etc/filebrowser/")
|
||||||
|
v.SetConfigName(".filebrowser")
|
||||||
|
} else {
|
||||||
|
v.SetConfigFile(cfgFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment variables
|
||||||
|
v.SetEnvPrefix("FB")
|
||||||
|
v.AutomaticEnv()
|
||||||
|
v.SetEnvKeyReplacer(strings.NewReplacer(generateEnvKeyReplacements(cmd)...))
|
||||||
|
|
||||||
|
// Bind the flags
|
||||||
|
err = v.BindPFlags(cmd.Flags())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in configuration
|
||||||
|
if err := v.ReadInConfig(); err != nil {
|
||||||
|
if errors.Is(err, viper.ConfigParseError{}) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("No config file used")
|
||||||
|
} else {
|
||||||
|
log.Printf("Using config file: %s", v.ConfigFileUsed())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return Viper
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type store struct {
|
||||||
|
*storage.Storage
|
||||||
|
databaseExisted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type storeOptions struct {
|
||||||
|
expectsNoDatabase bool
|
||||||
|
allowsNoDatabase bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type cobraFunc func(cmd *cobra.Command, args []string) error
|
||||||
|
|
||||||
|
// withViperAndStore initializes Viper and the storage.Store and passes them to the callback function.
|
||||||
|
// This function should only be used by [withStore] and the root command. No other command should call
|
||||||
|
// this function directly.
|
||||||
|
func withViperAndStore(fn func(cmd *cobra.Command, args []string, v *viper.Viper, store *store) error, options storeOptions) cobraFunc {
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
return func(cmd *cobra.Command, args []string) error {
|
||||||
data := &pythonData{hadDB: true}
|
v, err := initViper(cmd)
|
||||||
|
|
||||||
path := getStringParam(cmd.Flags(), "database")
|
|
||||||
absPath, err := filepath.Abs(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path, err := filepath.Abs(v.GetString("database"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
exists, err := dbExists(path)
|
exists, err := dbExists(path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
} else if exists && cfg.noDB {
|
} else if exists && options.expectsNoDatabase {
|
||||||
log.Fatal(absPath + " already exists")
|
log.Fatal(path + " already exists")
|
||||||
} else if !exists && !cfg.noDB && !cfg.allowNoDB {
|
} else if !exists && !options.expectsNoDatabase && !options.allowsNoDatabase {
|
||||||
log.Fatal(absPath + " does not exist. Please run 'filebrowser config init' first.")
|
log.Fatal(path + " does not exist. Please run 'filebrowser config init' first.")
|
||||||
} else if !exists && !cfg.noDB {
|
} else if !exists && !options.expectsNoDatabase {
|
||||||
log.Println("Warning: filebrowser.db can't be found. Initialing in " + strings.TrimSuffix(absPath, "filebrowser.db"))
|
log.Println("WARNING: filebrowser.db can't be found. Initialing in " + strings.TrimSuffix(path, "filebrowser.db"))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Using database: " + absPath)
|
log.Println("Using database: " + path)
|
||||||
data.hadDB = exists
|
|
||||||
db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil))
|
db, err := storm.Open(path, storm.BoltOptions(databasePermissions, nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
data.store, err = bolt.NewStorage(db)
|
|
||||||
|
storage, err := bolt.NewStorage(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fn(cmd, args, data)
|
|
||||||
|
store := &store{
|
||||||
|
Storage: storage,
|
||||||
|
databaseExisted: exists,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return fn(cmd, args, v, store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withStore(fn func(cmd *cobra.Command, args []string, store *store) error, options storeOptions) cobraFunc {
|
||||||
|
return withViperAndStore(func(cmd *cobra.Command, args []string, v *viper.Viper, store *store) error {
|
||||||
|
return fn(cmd, args, store)
|
||||||
|
}, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshal(filename string, data interface{}) error {
|
func marshal(filename string, data interface{}) error {
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
rules: {
|
|
||||||
'body-leading-blank': [1, 'always'],
|
|
||||||
'body-max-line-length': [2, 'always', 100],
|
|
||||||
'footer-leading-blank': [1, 'always'],
|
|
||||||
'footer-max-line-length': [2, 'always', 100],
|
|
||||||
'header-max-length': [2, 'always', 100],
|
|
||||||
'scope-case': [2, 'always', 'lower-case'],
|
|
||||||
'subject-case': [
|
|
||||||
2,
|
|
||||||
'never',
|
|
||||||
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
|
|
||||||
],
|
|
||||||
'subject-full-stop': [2, 'never', '.'],
|
|
||||||
'type-case': [2, 'always', 'lower-case'],
|
|
||||||
'type-empty': [2, 'never'],
|
|
||||||
'type-enum': [
|
|
||||||
2,
|
|
||||||
'always',
|
|
||||||
[
|
|
||||||
'feat',
|
|
||||||
'fix',
|
|
||||||
'perf',
|
|
||||||
'revert',
|
|
||||||
'refactor',
|
|
||||||
'build',
|
|
||||||
'ci',
|
|
||||||
'test',
|
|
||||||
'chore',
|
|
||||||
'docs',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,7 @@ package diskcache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha1" //nolint:gosec
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -103,7 +103,7 @@ func (f *FileCache) getScopedLocks(key string) (lock sync.Locker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileCache) getFileName(key string) string {
|
func (f *FileCache) getFileName(key string) string {
|
||||||
hasher := sha1.New() //nolint:gosec
|
hasher := sha1.New()
|
||||||
_, _ = hasher.Write([]byte(key))
|
_, _ = hasher.Write([]byte(key))
|
||||||
hash := hex.EncodeToString(hasher.Sum(nil))
|
hash := hex.EncodeToString(hasher.Sum(nil))
|
||||||
return fmt.Sprintf("%s/%s/%s", hash[:1], hash[1:3], hash)
|
return fmt.Sprintf("%s/%s/%s", hash[:1], hash[1:3], hash)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func TestFileCache(t *testing.T) {
|
|||||||
require.False(t, exists)
|
require.False(t, exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath string, cache *FileCache, key, wantValue string) { //nolint:revive
|
func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath string, cache *FileCache, key, wantValue string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
// check actual file content
|
// check actual file content
|
||||||
b, err := afero.ReadFile(fs, fileFullPath)
|
b, err := afero.ReadFile(fs, fileFullPath)
|
||||||
|
|||||||
@@ -3,15 +3,6 @@ package errors
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ExitCodeSigTerm = 128 + int(syscall.SIGTERM)
|
|
||||||
ExitCodeSighup = 128 + int(syscall.SIGHUP)
|
|
||||||
ExitCodeSigint = 128 + int(syscall.SIGINT)
|
|
||||||
ExitCodeSigquit = 128 + int(syscall.SIGQUIT)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -31,10 +22,6 @@ var (
|
|||||||
ErrInvalidRequestParams = errors.New("invalid request params")
|
ErrInvalidRequestParams = errors.New("invalid request params")
|
||||||
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")
|
||||||
ErrSigTerm = errors.New("exit on signal: sigterm")
|
|
||||||
ErrSighup = errors.New("exit on signal: sighup")
|
|
||||||
ErrSigint = errors.New("exit on signal: sigint")
|
|
||||||
ErrSigquit = errors.New("exit on signal: sigquit")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrShortPassword struct {
|
type ErrShortPassword struct {
|
||||||
@@ -44,44 +31,3 @@ type ErrShortPassword struct {
|
|||||||
func (e ErrShortPassword) Error() string {
|
func (e ErrShortPassword) Error() string {
|
||||||
return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength)
|
return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExitCode returns the exit code for a given error.
|
|
||||||
func GetExitCode(err error) int {
|
|
||||||
if err == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
exitCodeMap := map[error]int{
|
|
||||||
ErrSigTerm: ExitCodeSigTerm,
|
|
||||||
ErrSighup: ExitCodeSighup,
|
|
||||||
ErrSigint: ExitCodeSigint,
|
|
||||||
ErrSigquit: ExitCodeSigquit,
|
|
||||||
}
|
|
||||||
|
|
||||||
for e, code := range exitCodeMap {
|
|
||||||
if errors.Is(err, e) {
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if exitErr, ok := err.(interface{ ExitCode() int }); ok {
|
|
||||||
return exitErr.ExitCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
var pathErr *os.PathError
|
|
||||||
if errors.As(err, &pathErr) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var syscallErr *os.SyscallError
|
|
||||||
if errors.As(err, &syscallErr) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var errno syscall.Errno
|
|
||||||
if errors.As(err, &errno) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package files
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5" //nolint:gosec
|
"crypto/md5"
|
||||||
"crypto/sha1" //nolint:gosec
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@@ -90,7 +90,7 @@ func NewFileInfo(opts *FileOptions) (*FileInfo, error) {
|
|||||||
|
|
||||||
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 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return file, nil
|
return file, nil
|
||||||
@@ -183,7 +183,6 @@ func (i *FileInfo) Checksum(algo string) error {
|
|||||||
|
|
||||||
var h hash.Hash
|
var h hash.Hash
|
||||||
|
|
||||||
//nolint:gosec
|
|
||||||
switch algo {
|
switch algo {
|
||||||
case "md5":
|
case "md5":
|
||||||
h = md5.New()
|
h = md5.New()
|
||||||
|
|||||||
@@ -600,7 +600,6 @@ var types = map[string]string{
|
|||||||
".epub": "application/epub+zip",
|
".epub": "application/epub+zip",
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gochecknoinits
|
|
||||||
func init() {
|
func init() {
|
||||||
for ext, typ := range types {
|
for ext, typ := range types {
|
||||||
// skip errors
|
// skip errors
|
||||||
|
|||||||
137
frontend/pnpm-lock.yaml
generated
137
frontend/pnpm-lock.yaml
generated
@@ -101,7 +101,7 @@ importers:
|
|||||||
version: 11.0.1(@vue/compiler-dom@3.5.24)(eslint@9.39.1)(rollup@4.52.5)(typescript@5.9.3)(vue-i18n@11.1.12(vue@3.5.24(typescript@5.9.3)))(vue@3.5.24(typescript@5.9.3))
|
version: 11.0.1(@vue/compiler-dom@3.5.24)(eslint@9.39.1)(rollup@4.52.5)(typescript@5.9.3)(vue-i18n@11.1.12(vue@3.5.24(typescript@5.9.3)))(vue@3.5.24(typescript@5.9.3))
|
||||||
'@tsconfig/node24':
|
'@tsconfig/node24':
|
||||||
specifier: ^24.0.2
|
specifier: ^24.0.2
|
||||||
version: 24.0.2
|
version: 24.0.3
|
||||||
'@types/lodash-es':
|
'@types/lodash-es':
|
||||||
specifier: ^4.17.12
|
specifier: ^4.17.12
|
||||||
version: 4.17.12
|
version: 4.17.12
|
||||||
@@ -110,7 +110,7 @@ importers:
|
|||||||
version: 24.10.1
|
version: 24.10.1
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
specifier: ^8.37.0
|
specifier: ^8.37.0
|
||||||
version: 8.46.4(@typescript-eslint/parser@8.37.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)
|
version: 8.47.0(@typescript-eslint/parser@8.37.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)
|
||||||
'@vitejs/plugin-legacy':
|
'@vitejs/plugin-legacy':
|
||||||
specifier: ^7.2.1
|
specifier: ^7.2.1
|
||||||
version: 7.2.1(terser@5.44.1)(vite@7.2.2(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))
|
version: 7.2.1(terser@5.44.1)(vite@7.2.2(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))
|
||||||
@@ -161,7 +161,7 @@ importers:
|
|||||||
version: 2.3.1(rollup@4.52.5)
|
version: 2.3.1(rollup@4.52.5)
|
||||||
vue-tsc:
|
vue-tsc:
|
||||||
specifier: ^3.1.3
|
specifier: ^3.1.3
|
||||||
version: 3.1.3(typescript@5.9.3)
|
version: 3.1.4(typescript@5.9.3)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -1087,8 +1087,8 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@tsconfig/node24@24.0.2':
|
'@tsconfig/node24@24.0.3':
|
||||||
resolution: {integrity: sha512-CNeOLUPI9PjbBc1DSIqb3GF/u+3kX/TDe9DKCzoI62mYI4dEDrMQ0r/9+SfYACP4UNMbiTlz7n8H7Rx/xTisQg==}
|
resolution: {integrity: sha512-vcERKtKQKHgzt/vfS3Gjasd8SUI2a0WZXpgJURdJsMySpS5+ctgbPfuLj2z/W+w4lAfTWxoN4upKfu2WzIRYnw==}
|
||||||
|
|
||||||
'@types/estree@1.0.8':
|
'@types/estree@1.0.8':
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
@@ -1123,11 +1123,11 @@ packages:
|
|||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <5.9.0'
|
typescript: '>=4.8.4 <5.9.0'
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.46.4':
|
'@typescript-eslint/eslint-plugin@8.47.0':
|
||||||
resolution: {integrity: sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==}
|
resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': ^8.46.4
|
'@typescript-eslint/parser': ^8.47.0
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
@@ -1150,6 +1150,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/project-service@8.47.0':
|
||||||
|
resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.37.0':
|
'@typescript-eslint/scope-manager@8.37.0':
|
||||||
resolution: {integrity: sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==}
|
resolution: {integrity: sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -1158,6 +1164,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==}
|
resolution: {integrity: sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/scope-manager@8.47.0':
|
||||||
|
resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.37.0':
|
'@typescript-eslint/tsconfig-utils@8.37.0':
|
||||||
resolution: {integrity: sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==}
|
resolution: {integrity: sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -1170,6 +1180,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/tsconfig-utils@8.47.0':
|
||||||
|
resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.37.0':
|
'@typescript-eslint/type-utils@8.37.0':
|
||||||
resolution: {integrity: sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==}
|
resolution: {integrity: sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -1177,8 +1193,8 @@ packages:
|
|||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <5.9.0'
|
typescript: '>=4.8.4 <5.9.0'
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.46.4':
|
'@typescript-eslint/type-utils@8.47.0':
|
||||||
resolution: {integrity: sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==}
|
resolution: {integrity: sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
@@ -1192,6 +1208,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==}
|
resolution: {integrity: sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/types@8.47.0':
|
||||||
|
resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.37.0':
|
'@typescript-eslint/typescript-estree@8.37.0':
|
||||||
resolution: {integrity: sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==}
|
resolution: {integrity: sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -1204,6 +1224,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/typescript-estree@8.47.0':
|
||||||
|
resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.37.0':
|
'@typescript-eslint/utils@8.37.0':
|
||||||
resolution: {integrity: sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==}
|
resolution: {integrity: sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -1211,8 +1237,8 @@ packages:
|
|||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <5.9.0'
|
typescript: '>=4.8.4 <5.9.0'
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.46.4':
|
'@typescript-eslint/utils@8.47.0':
|
||||||
resolution: {integrity: sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==}
|
resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
@@ -1226,6 +1252,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==}
|
resolution: {integrity: sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/visitor-keys@8.47.0':
|
||||||
|
resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@videojs/http-streaming@3.17.2':
|
'@videojs/http-streaming@3.17.2':
|
||||||
resolution: {integrity: sha512-VBQ3W4wnKnVKb/limLdtSD2rAd5cmHN70xoMf4OmuDd0t2kfJX04G+sfw6u2j8oOm2BXYM9E1f4acHruqKnM1g==}
|
resolution: {integrity: sha512-VBQ3W4wnKnVKb/limLdtSD2rAd5cmHN70xoMf4OmuDd0t2kfJX04G+sfw6u2j8oOm2BXYM9E1f4acHruqKnM1g==}
|
||||||
engines: {node: '>=8', npm: '>=5'}
|
engines: {node: '>=8', npm: '>=5'}
|
||||||
@@ -1303,8 +1333,8 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@vue/language-core@3.1.3':
|
'@vue/language-core@3.1.4':
|
||||||
resolution: {integrity: sha512-KpR1F/eGAG9D1RZ0/T6zWJs6dh/pRLfY5WupecyYKJ1fjVmDMgTPw9wXmKv2rBjo4zCJiOSiyB8BDP1OUwpMEA==}
|
resolution: {integrity: sha512-n/58wm8SkmoxMWkUNUH/PwoovWe4hmdyPJU2ouldr3EPi1MLoS7iDN46je8CsP95SnVBs2axInzRglPNKvqMcg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '*'
|
typescript: '*'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@@ -2484,8 +2514,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.0.2
|
vue: ^3.0.2
|
||||||
|
|
||||||
vue-tsc@3.1.3:
|
vue-tsc@3.1.4:
|
||||||
resolution: {integrity: sha512-StMNfZHwPIXQgY3KxPKM0Jsoc8b46mDV3Fn2UlHCBIwRJApjqrSwqeMYgWf0zpN+g857y74pv7GWuBm+UqQe1w==}
|
resolution: {integrity: sha512-GsRJxttj4WkmXW/zDwYPGMJAN3np/4jTzoDFQTpTsI5Vg/JKMWamBwamlmLihgSVHO66y9P7GX+uoliYxeI4Hw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=5.0.0'
|
typescript: '>=5.0.0'
|
||||||
@@ -3498,7 +3528,7 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc@4.52.5':
|
'@rollup/rollup-win32-x64-msvc@4.52.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tsconfig/node24@24.0.2': {}
|
'@tsconfig/node24@24.0.3': {}
|
||||||
|
|
||||||
'@types/estree@1.0.8': {}
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
@@ -3540,14 +3570,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.46.4(@typescript-eslint/parser@8.37.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)':
|
'@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.37.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@typescript-eslint/parser': 8.37.0(eslint@9.39.1)(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.37.0(eslint@9.39.1)(typescript@5.9.3)
|
||||||
'@typescript-eslint/scope-manager': 8.46.4
|
'@typescript-eslint/scope-manager': 8.47.0
|
||||||
'@typescript-eslint/type-utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3)
|
'@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.46.4
|
'@typescript-eslint/visitor-keys': 8.47.0
|
||||||
eslint: 9.39.1
|
eslint: 9.39.1
|
||||||
graphemer: 1.4.0
|
graphemer: 1.4.0
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
@@ -3587,6 +3617,15 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/project-service@8.47.0(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/types': 8.47.0
|
||||||
|
debug: 4.4.3
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.37.0':
|
'@typescript-eslint/scope-manager@8.37.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.37.0
|
'@typescript-eslint/types': 8.37.0
|
||||||
@@ -3597,6 +3636,11 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.46.4
|
'@typescript-eslint/types': 8.46.4
|
||||||
'@typescript-eslint/visitor-keys': 8.46.4
|
'@typescript-eslint/visitor-keys': 8.46.4
|
||||||
|
|
||||||
|
'@typescript-eslint/scope-manager@8.47.0':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.47.0
|
||||||
|
'@typescript-eslint/visitor-keys': 8.47.0
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.37.0(typescript@5.9.3)':
|
'@typescript-eslint/tsconfig-utils@8.37.0(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
@@ -3605,6 +3649,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
'@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.37.0(eslint@9.39.1)(typescript@5.9.3)':
|
'@typescript-eslint/type-utils@8.37.0(eslint@9.39.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.37.0
|
'@typescript-eslint/types': 8.37.0
|
||||||
@@ -3617,11 +3665,11 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)':
|
'@typescript-eslint/type-utils@8.47.0(eslint@9.39.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.46.4
|
'@typescript-eslint/types': 8.47.0
|
||||||
'@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.46.4(eslint@9.39.1)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 9.39.1
|
eslint: 9.39.1
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||||
@@ -3633,6 +3681,8 @@ snapshots:
|
|||||||
|
|
||||||
'@typescript-eslint/types@8.46.4': {}
|
'@typescript-eslint/types@8.46.4': {}
|
||||||
|
|
||||||
|
'@typescript-eslint/types@8.47.0': {}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.37.0(typescript@5.9.3)':
|
'@typescript-eslint/typescript-estree@8.37.0(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/project-service': 8.37.0(typescript@5.9.3)
|
'@typescript-eslint/project-service': 8.37.0(typescript@5.9.3)
|
||||||
@@ -3665,6 +3715,22 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/project-service': 8.47.0(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/types': 8.47.0
|
||||||
|
'@typescript-eslint/visitor-keys': 8.47.0
|
||||||
|
debug: 4.4.3
|
||||||
|
fast-glob: 3.3.3
|
||||||
|
is-glob: 4.0.3
|
||||||
|
minimatch: 9.0.5
|
||||||
|
semver: 7.7.3
|
||||||
|
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.37.0(eslint@9.39.1)(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.37.0(eslint@9.39.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1)
|
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1)
|
||||||
@@ -3676,12 +3742,12 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.46.4(eslint@9.39.1)(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.47.0(eslint@9.39.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1)
|
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1)
|
||||||
'@typescript-eslint/scope-manager': 8.46.4
|
'@typescript-eslint/scope-manager': 8.47.0
|
||||||
'@typescript-eslint/types': 8.46.4
|
'@typescript-eslint/types': 8.47.0
|
||||||
'@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3)
|
||||||
eslint: 9.39.1
|
eslint: 9.39.1
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -3697,6 +3763,11 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.46.4
|
'@typescript-eslint/types': 8.46.4
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
|
'@typescript-eslint/visitor-keys@8.47.0':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.47.0
|
||||||
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
'@videojs/http-streaming@3.17.2(video.js@8.23.4)':
|
'@videojs/http-streaming@3.17.2(video.js@8.23.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
@@ -3828,7 +3899,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@vue/language-core@3.1.3(typescript@5.9.3)':
|
'@vue/language-core@3.1.4(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@volar/language-core': 2.4.23
|
'@volar/language-core': 2.4.23
|
||||||
'@vue/compiler-dom': 3.5.24
|
'@vue/compiler-dom': 3.5.24
|
||||||
@@ -4966,10 +5037,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.24(typescript@5.9.3)
|
vue: 3.5.24(typescript@5.9.3)
|
||||||
|
|
||||||
vue-tsc@3.1.3(typescript@5.9.3):
|
vue-tsc@3.1.4(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@volar/typescript': 2.4.23
|
'@volar/typescript': 2.4.23
|
||||||
'@vue/language-core': 3.1.3(typescript@5.9.3)
|
'@vue/language-core': 3.1.4(typescript@5.9.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
vue@3.5.24(typescript@5.9.3):
|
vue@3.5.24(typescript@5.9.3):
|
||||||
|
|||||||
47
frontend/src/components/ContextMenu.vue
Normal file
47
frontend/src/components/ContextMenu.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="context-menu"
|
||||||
|
ref="contextMenu"
|
||||||
|
v-show="show"
|
||||||
|
:style="{
|
||||||
|
top: `${props.pos.y}px`,
|
||||||
|
left: `${left}px`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, computed, onUnmounted } from "vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(["hide"]);
|
||||||
|
const props = defineProps<{ show: boolean; pos: { x: number; y: number } }>();
|
||||||
|
const contextMenu = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const left = computed(() => {
|
||||||
|
return Math.min(
|
||||||
|
props.pos.x,
|
||||||
|
window.innerWidth - (contextMenu.value?.clientWidth ?? 0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hideContextMenu = () => {
|
||||||
|
emit("hide");
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
document.addEventListener("click", hideContextMenu);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener("click", hideContextMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener("click", hideContextMenu);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -63,6 +63,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<router-link
|
<router-link
|
||||||
|
v-if="!hideLoginButton"
|
||||||
class="action"
|
class="action"
|
||||||
to="/login"
|
to="/login"
|
||||||
:aria-label="$t('sidebar.login')"
|
:aria-label="$t('sidebar.login')"
|
||||||
@@ -124,6 +125,7 @@ import * as auth from "@/utils/auth";
|
|||||||
import {
|
import {
|
||||||
version,
|
version,
|
||||||
signup,
|
signup,
|
||||||
|
hideLoginButton,
|
||||||
disableExternal,
|
disableExternal,
|
||||||
disableUsedPercentage,
|
disableUsedPercentage,
|
||||||
noAuth,
|
noAuth,
|
||||||
@@ -153,6 +155,7 @@ export default {
|
|||||||
return this.currentPromptName === "sidebar";
|
return this.currentPromptName === "sidebar";
|
||||||
},
|
},
|
||||||
signup: () => signup,
|
signup: () => signup,
|
||||||
|
hideLoginButton: () => hideLoginButton,
|
||||||
version: () => version,
|
version: () => version,
|
||||||
disableExternal: () => disableExternal,
|
disableExternal: () => disableExternal,
|
||||||
disableUsedPercentage: () => disableUsedPercentage,
|
disableUsedPercentage: () => disableUsedPercentage,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
:aria-label="name"
|
:aria-label="name"
|
||||||
:aria-selected="isSelected"
|
:aria-selected="isSelected"
|
||||||
:data-ext="getExtension(name).toLowerCase()"
|
:data-ext="getExtension(name).toLowerCase()"
|
||||||
|
@contextmenu="contextMenu"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
@@ -239,6 +240,17 @@ const itemClick = (event: Event | KeyboardEvent) => {
|
|||||||
else click(event);
|
else click(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contextMenu = (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (
|
||||||
|
fileStore.selected.length === 0 ||
|
||||||
|
event.ctrlKey ||
|
||||||
|
fileStore.selected.indexOf(props.index) === -1
|
||||||
|
) {
|
||||||
|
click(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const click = (event: Event | KeyboardEvent) => {
|
const click = (event: Event | KeyboardEvent) => {
|
||||||
if (!singleClick.value && fileStore.selectedCount !== 0)
|
if (!singleClick.value && fileStore.selectedCount !== 0)
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
<p>{{ $t("prompts.moveMessage") }}</p>
|
||||||
<file-list
|
<file-list
|
||||||
ref="fileList"
|
ref="fileList"
|
||||||
@update:selected="(val) => (dest = val)"
|
@update:selected="(val) => (dest = val)"
|
||||||
|
|||||||
21
frontend/src/css/context-menu.css
Normal file
21
frontend/src/css/context-menu.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.context-menu {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
min-width: 180px;
|
||||||
|
max-width: 220px;
|
||||||
|
border: 1px solid var(--borderSecondary);
|
||||||
|
box-shadow: 0 2px 4px var(--borderPrimary);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu .action {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu .action .counter {
|
||||||
|
left: 1.75em;
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
@import "./mobile.css";
|
@import "./mobile.css";
|
||||||
@import "./epubReader.css";
|
@import "./epubReader.css";
|
||||||
@import "./mdPreview.css";
|
@import "./mdPreview.css";
|
||||||
|
@import "./context-menu.css";
|
||||||
|
|
||||||
/* For testing only
|
/* For testing only
|
||||||
:focus {
|
:focus {
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "إنشاء ملفات و مجلدات جديدة",
|
"allowNew": "إنشاء ملفات و مجلدات جديدة",
|
||||||
"allowPublish": "نشر مقالات و صفحات جديدة",
|
"allowPublish": "نشر مقالات و صفحات جديدة",
|
||||||
"allowSignup": "اسمح للمستخدمين بالاشتراك",
|
"allowSignup": "اسمح للمستخدمين بالاشتراك",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
|
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
|
||||||
"branding": "الشعار",
|
"branding": "الشعار",
|
||||||
"brandingDirectoryPath": "مسار مجلد الشعار",
|
"brandingDirectoryPath": "مسار مجلد الشعار",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Crear nous fitxers i carpetes",
|
"allowNew": "Crear nous fitxers i carpetes",
|
||||||
"allowPublish": "Publicar nous posts i pàgines",
|
"allowPublish": "Publicar nous posts i pàgines",
|
||||||
"allowSignup": "Permetre registre d'usuaris",
|
"allowSignup": "Permetre registre d'usuaris",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(deixar en blanc per evitar canvis)",
|
"avoidChanges": "(deixar en blanc per evitar canvis)",
|
||||||
"branding": "Marca",
|
"branding": "Marca",
|
||||||
"brandingDirectoryPath": "Ruta de la carpeta de personalització de marca",
|
"brandingDirectoryPath": "Ruta de la carpeta de personalització de marca",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Vytvářet nové soubory a adresáře",
|
"allowNew": "Vytvářet nové soubory a adresáře",
|
||||||
"allowPublish": "Publikovat nové příspěvky a stránky",
|
"allowPublish": "Publikovat nové příspěvky a stránky",
|
||||||
"allowSignup": "Povolit uživatelům registraci",
|
"allowSignup": "Povolit uživatelům registraci",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(ponechte prázdné pro zabránění změnám)",
|
"avoidChanges": "(ponechte prázdné pro zabránění změnám)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Cesta ke složce s brandingem",
|
"brandingDirectoryPath": "Cesta ke složce s brandingem",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Erstellen neuer Dateien und Ordner",
|
"allowNew": "Erstellen neuer Dateien und Ordner",
|
||||||
"allowPublish": "Veröffentlichen von neuen Beiträgen und Seiten",
|
"allowPublish": "Veröffentlichen von neuen Beiträgen und Seiten",
|
||||||
"allowSignup": "Erlaube Benutzern sich zu registrieren",
|
"allowSignup": "Erlaube Benutzern sich zu registrieren",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(leer lassen, um Änderungen zu vermeiden)",
|
"avoidChanges": "(leer lassen, um Änderungen zu vermeiden)",
|
||||||
"branding": "Design",
|
"branding": "Design",
|
||||||
"brandingDirectoryPath": "Designverzeichnispfad",
|
"brandingDirectoryPath": "Designverzeichnispfad",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Δημιουργία νέων αρχείων και φακέλων",
|
"allowNew": "Δημιουργία νέων αρχείων και φακέλων",
|
||||||
"allowPublish": "Δημοσίευση νέων αναρτήσεων και σελίδων",
|
"allowPublish": "Δημοσίευση νέων αναρτήσεων και σελίδων",
|
||||||
"allowSignup": "Να επιτρέπεται η εγγραφή νέων χρηστών",
|
"allowSignup": "Να επιτρέπεται η εγγραφή νέων χρηστών",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
|
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
|
||||||
"branding": "Εξατομίκευση",
|
"branding": "Εξατομίκευση",
|
||||||
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",
|
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Create new files and directories",
|
"allowNew": "Create new files and directories",
|
||||||
"allowPublish": "Publish new posts and pages",
|
"allowPublish": "Publish new posts and pages",
|
||||||
"allowSignup": "Allow users to signup",
|
"allowSignup": "Allow users to signup",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(leave blank to avoid changes)",
|
"avoidChanges": "(leave blank to avoid changes)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Branding directory path",
|
"brandingDirectoryPath": "Branding directory path",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Crear nuevos archivos y carpetas",
|
"allowNew": "Crear nuevos archivos y carpetas",
|
||||||
"allowPublish": "Publicar nuevos posts y páginas",
|
"allowPublish": "Publicar nuevos posts y páginas",
|
||||||
"allowSignup": "Permitir registro de usuarios",
|
"allowSignup": "Permitir registro de usuarios",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(dejar en blanco para evitar cambios)",
|
"avoidChanges": "(dejar en blanco para evitar cambios)",
|
||||||
"branding": "Marca",
|
"branding": "Marca",
|
||||||
"brandingDirectoryPath": "Ruta de la carpeta de personalizacion de marca",
|
"brandingDirectoryPath": "Ruta de la carpeta de personalizacion de marca",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "ایجاد فایلها و پوشه های جدید",
|
"allowNew": "ایجاد فایلها و پوشه های جدید",
|
||||||
"allowPublish": "انتشار پست ها و صفحات جدید",
|
"allowPublish": "انتشار پست ها و صفحات جدید",
|
||||||
"allowSignup": "اجاره دادن به کاربران برای ثبت نام",
|
"allowSignup": "اجاره دادن به کاربران برای ثبت نام",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(خالی بگذارید تا تغییر ایجاد نشود)",
|
"avoidChanges": "(خالی بگذارید تا تغییر ایجاد نشود)",
|
||||||
"branding": "برندسازی",
|
"branding": "برندسازی",
|
||||||
"brandingDirectoryPath": "مسیر پوشه برند",
|
"brandingDirectoryPath": "مسیر پوشه برند",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Créer de nouveaux fichiers et dossiers",
|
"allowNew": "Créer de nouveaux fichiers et dossiers",
|
||||||
"allowPublish": "Publier de nouveaux posts et pages",
|
"allowPublish": "Publier de nouveaux posts et pages",
|
||||||
"allowSignup": "Autoriser les utilisateur·ices à s'inscrire",
|
"allowSignup": "Autoriser les utilisateur·ices à s'inscrire",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
|
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
|
||||||
"branding": "Image de marque",
|
"branding": "Image de marque",
|
||||||
"brandingDirectoryPath": "Chemin du dossier d'image de marque",
|
"brandingDirectoryPath": "Chemin du dossier d'image de marque",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "יצירת קבצים ותיקיות חדשות",
|
"allowNew": "יצירת קבצים ותיקיות חדשות",
|
||||||
"allowPublish": "פרסום פוסטים ודפים חדשים",
|
"allowPublish": "פרסום פוסטים ודפים חדשים",
|
||||||
"allowSignup": "אפשר למשתמשים חדשים להירשם",
|
"allowSignup": "אפשר למשתמשים חדשים להירשם",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(השאר ריק כדי למנוע שינויים)",
|
"avoidChanges": "(השאר ריק כדי למנוע שינויים)",
|
||||||
"branding": "מיתוג",
|
"branding": "מיתוג",
|
||||||
"brandingDirectoryPath": "נתיב תיקיית מיתוג",
|
"brandingDirectoryPath": "נתיב תיקיית מיתוג",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Stvori nove datoteke i mape",
|
"allowNew": "Stvori nove datoteke i mape",
|
||||||
"allowPublish": "Objavi nove objave i stranice",
|
"allowPublish": "Objavi nove objave i stranice",
|
||||||
"allowSignup": "Dopusti registraciju korisnicima",
|
"allowSignup": "Dopusti registraciju korisnicima",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(ostavite prazno kako biste izbjegli promjene)",
|
"avoidChanges": "(ostavite prazno kako biste izbjegli promjene)",
|
||||||
"branding": "Brendiranje",
|
"branding": "Brendiranje",
|
||||||
"brandingDirectoryPath": "Put brendiranja",
|
"brandingDirectoryPath": "Put brendiranja",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Új fájlok és mappák létrehozása",
|
"allowNew": "Új fájlok és mappák létrehozása",
|
||||||
"allowPublish": "Új bejegyzések és oldalak létrehozása",
|
"allowPublish": "Új bejegyzések és oldalak létrehozása",
|
||||||
"allowSignup": "Felhasználók regisztrációjának engedélyezése",
|
"allowSignup": "Felhasználók regisztrációjának engedélyezése",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(üresen hagyva nincs változás)",
|
"avoidChanges": "(üresen hagyva nincs változás)",
|
||||||
"branding": "Márkázás",
|
"branding": "Márkázás",
|
||||||
"brandingDirectoryPath": "Márkázás mappaútvonala",
|
"brandingDirectoryPath": "Márkázás mappaútvonala",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Búa til ný skjöl og möppur",
|
"allowNew": "Búa til ný skjöl og möppur",
|
||||||
"allowPublish": "Gefa út nýjar færslur og síður",
|
"allowPublish": "Gefa út nýjar færslur og síður",
|
||||||
"allowSignup": "Leyfa nýjum notendum að skrá sig",
|
"allowSignup": "Leyfa nýjum notendum að skrá sig",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(engar breytingar ef ekkert er skrifað)",
|
"avoidChanges": "(engar breytingar ef ekkert er skrifað)",
|
||||||
"branding": "Útlit",
|
"branding": "Útlit",
|
||||||
"brandingDirectoryPath": "Mappa fyrir branding-skjöl",
|
"brandingDirectoryPath": "Mappa fyrir branding-skjöl",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Crea nuovi files o cartelle",
|
"allowNew": "Crea nuovi files o cartelle",
|
||||||
"allowPublish": "Pubblica nuovi post e pagine",
|
"allowPublish": "Pubblica nuovi post e pagine",
|
||||||
"allowSignup": "Permetti agli utenti di registrarsi",
|
"allowSignup": "Permetti agli utenti di registrarsi",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(lascia vuoto per evitare cambiamenti)",
|
"avoidChanges": "(lascia vuoto per evitare cambiamenti)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Directory del branding",
|
"brandingDirectoryPath": "Directory del branding",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "ファイルやフォルダーの新規作成",
|
"allowNew": "ファイルやフォルダーの新規作成",
|
||||||
"allowPublish": "新しい投稿やページの公開",
|
"allowPublish": "新しい投稿やページの公開",
|
||||||
"allowSignup": "ユーザーの新規登録を許可",
|
"allowSignup": "ユーザーの新規登録を許可",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(変更しない場合は空白のままにしてください)",
|
"avoidChanges": "(変更しない場合は空白のままにしてください)",
|
||||||
"branding": "ブランディング",
|
"branding": "ブランディング",
|
||||||
"brandingDirectoryPath": "ブランディングのディレクトリへのパス",
|
"brandingDirectoryPath": "ブランディングのディレクトリへのパス",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "파일/디렉토리 생성 허용",
|
"allowNew": "파일/디렉토리 생성 허용",
|
||||||
"allowPublish": "새 포스트/페이지 생성 허용",
|
"allowPublish": "새 포스트/페이지 생성 허용",
|
||||||
"allowSignup": "사용자 가입 허용",
|
"allowSignup": "사용자 가입 허용",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(수정하지 않으면 비워두세요)",
|
"avoidChanges": "(수정하지 않으면 비워두세요)",
|
||||||
"branding": "브랜딩",
|
"branding": "브랜딩",
|
||||||
"brandingDirectoryPath": "브랜드 디렉토리 경로",
|
"brandingDirectoryPath": "브랜드 디렉토리 경로",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Nieuwe bestanden of mappen aanmaken",
|
"allowNew": "Nieuwe bestanden of mappen aanmaken",
|
||||||
"allowPublish": "Publiceer nieuwe berichten en pagina's",
|
"allowPublish": "Publiceer nieuwe berichten en pagina's",
|
||||||
"allowSignup": "Sta gebruikers toe om zich te registreren",
|
"allowSignup": "Sta gebruikers toe om zich te registreren",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(laat leeg om wijzigingen te voorkomen)",
|
"avoidChanges": "(laat leeg om wijzigingen te voorkomen)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Branding directory path",
|
"brandingDirectoryPath": "Branding directory path",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Opprett nye filer og direktorater",
|
"allowNew": "Opprett nye filer og direktorater",
|
||||||
"allowPublish": "Publiser nye innlegg og sider",
|
"allowPublish": "Publiser nye innlegg og sider",
|
||||||
"allowSignup": "Tilat brukere å registrere seg",
|
"allowSignup": "Tilat brukere å registrere seg",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(la stå tomt for å unngå endringer)",
|
"avoidChanges": "(la stå tomt for å unngå endringer)",
|
||||||
"branding": "Merkevarebygging",
|
"branding": "Merkevarebygging",
|
||||||
"brandingDirectoryPath": "Bane for merkevarekatalog",
|
"brandingDirectoryPath": "Bane for merkevarekatalog",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Tworzenie nowych plików lub folderów",
|
"allowNew": "Tworzenie nowych plików lub folderów",
|
||||||
"allowPublish": "Tworzenie nowych wpisów i stron",
|
"allowPublish": "Tworzenie nowych wpisów i stron",
|
||||||
"allowSignup": "Pozwól użytkownikom na rejestrację",
|
"allowSignup": "Pozwól użytkownikom na rejestrację",
|
||||||
|
"hideLoginButton": "Ukryj przycisk logowania na stronach publicznych",
|
||||||
"avoidChanges": "(pozostaw puste, aby uniknąć zmian)",
|
"avoidChanges": "(pozostaw puste, aby uniknąć zmian)",
|
||||||
"branding": "Personalizacja",
|
"branding": "Personalizacja",
|
||||||
"brandingDirectoryPath": "Ścieżka do folderu personalizacji",
|
"brandingDirectoryPath": "Ścieżka do folderu personalizacji",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Criar novos arquivos e pastas",
|
"allowNew": "Criar novos arquivos e pastas",
|
||||||
"allowPublish": "Publicar novas páginas e conteúdos",
|
"allowPublish": "Publicar novas páginas e conteúdos",
|
||||||
"allowSignup": "Permitir cadastro de usuários",
|
"allowSignup": "Permitir cadastro de usuários",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(deixe em branco para manter)",
|
"avoidChanges": "(deixe em branco para manter)",
|
||||||
"branding": "Customização",
|
"branding": "Customização",
|
||||||
"brandingDirectoryPath": "Diretório de customização",
|
"brandingDirectoryPath": "Diretório de customização",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Criar novos ficheiros e pastas",
|
"allowNew": "Criar novos ficheiros e pastas",
|
||||||
"allowPublish": "Publicar novas páginas e conteúdos",
|
"allowPublish": "Publicar novas páginas e conteúdos",
|
||||||
"allowSignup": "Permitir que os utilizadores criem contas",
|
"allowSignup": "Permitir que os utilizadores criem contas",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(deixe em branco para manter)",
|
"avoidChanges": "(deixe em branco para manter)",
|
||||||
"branding": "Marca",
|
"branding": "Marca",
|
||||||
"brandingDirectoryPath": "Caminho da pasta de marca",
|
"brandingDirectoryPath": "Caminho da pasta de marca",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Crează noi fișiere sau directoare",
|
"allowNew": "Crează noi fișiere sau directoare",
|
||||||
"allowPublish": "Publică noi pagini și postări",
|
"allowPublish": "Publică noi pagini și postări",
|
||||||
"allowSignup": "Permite utilizatorilor să se înregistreze",
|
"allowSignup": "Permite utilizatorilor să se înregistreze",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(lasă gol pentru a nu schimba)",
|
"avoidChanges": "(lasă gol pentru a nu schimba)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Calea către directorul de branding",
|
"brandingDirectoryPath": "Calea către directorul de branding",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Создание новых файлов или каталогов",
|
"allowNew": "Создание новых файлов или каталогов",
|
||||||
"allowPublish": "Публикация новых записей и страниц",
|
"allowPublish": "Публикация новых записей и страниц",
|
||||||
"allowSignup": "Разрешить пользователям регистрироваться",
|
"allowSignup": "Разрешить пользователям регистрироваться",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(оставьте поле пустым, чтобы избежать изменений)",
|
"avoidChanges": "(оставьте поле пустым, чтобы избежать изменений)",
|
||||||
"branding": "Брендинг",
|
"branding": "Брендинг",
|
||||||
"brandingDirectoryPath": "Путь к каталогу брендов",
|
"brandingDirectoryPath": "Путь к каталогу брендов",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Vytvárať nové súbory a priečinky",
|
"allowNew": "Vytvárať nové súbory a priečinky",
|
||||||
"allowPublish": "Zverejňovať nové príspevky a stránky",
|
"allowPublish": "Zverejňovať nové príspevky a stránky",
|
||||||
"allowSignup": "Povoliť registráciu používateľov",
|
"allowSignup": "Povoliť registráciu používateľov",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(nechajte prázdne, aby sa nezmenilo)",
|
"avoidChanges": "(nechajte prázdne, aby sa nezmenilo)",
|
||||||
"branding": "Vlastný vzhľad",
|
"branding": "Vlastný vzhľad",
|
||||||
"brandingDirectoryPath": "Cesta k priečinku s vlastným vzhľadom",
|
"brandingDirectoryPath": "Cesta k priečinku s vlastným vzhľadom",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Skapa nya filer eller mappar",
|
"allowNew": "Skapa nya filer eller mappar",
|
||||||
"allowPublish": "Publicera nya inlägg och sidor",
|
"allowPublish": "Publicera nya inlägg och sidor",
|
||||||
"allowSignup": "Tillåt användare att registrera sig",
|
"allowSignup": "Tillåt användare att registrera sig",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(lämna blankt för att undvika ändringar)",
|
"avoidChanges": "(lämna blankt för att undvika ändringar)",
|
||||||
"branding": "Varumärke",
|
"branding": "Varumärke",
|
||||||
"brandingDirectoryPath": "Sökväg till varumärkes katalog",
|
"brandingDirectoryPath": "Sökväg till varumärkes katalog",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Yeni dosyalar ve dizinler oluşturun",
|
"allowNew": "Yeni dosyalar ve dizinler oluşturun",
|
||||||
"allowPublish": "Yeni linkler ve sayfaları yayınlayın",
|
"allowPublish": "Yeni linkler ve sayfaları yayınlayın",
|
||||||
"allowSignup": "Kullanıcıların kaydolmasına izin ver",
|
"allowSignup": "Kullanıcıların kaydolmasına izin ver",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
|
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
|
||||||
"branding": "Marka",
|
"branding": "Marka",
|
||||||
"brandingDirectoryPath": "Marka dizin yolu",
|
"brandingDirectoryPath": "Marka dizin yolu",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Створення нових файлів або каталогів",
|
"allowNew": "Створення нових файлів або каталогів",
|
||||||
"allowPublish": "Публікація нових записів та сторінок",
|
"allowPublish": "Публікація нових записів та сторінок",
|
||||||
"allowSignup": "Дозволити користувачам реєструватися",
|
"allowSignup": "Дозволити користувачам реєструватися",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
|
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
|
||||||
"branding": "Брендинг",
|
"branding": "Брендинг",
|
||||||
"brandingDirectoryPath": "Шлях до каталогу брендів",
|
"brandingDirectoryPath": "Шлях до каталогу брендів",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Tạo tệp và thư mục mới",
|
"allowNew": "Tạo tệp và thư mục mới",
|
||||||
"allowPublish": "Xuất bản bài viết và trang mới",
|
"allowPublish": "Xuất bản bài viết và trang mới",
|
||||||
"allowSignup": "Cho phép người dùng đăng ký",
|
"allowSignup": "Cho phép người dùng đăng ký",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(để trống để tránh thay đổi)",
|
"avoidChanges": "(để trống để tránh thay đổi)",
|
||||||
"branding": "Thương hiệu",
|
"branding": "Thương hiệu",
|
||||||
"brandingDirectoryPath": "Đường dẫn thư mục thương hiệu",
|
"brandingDirectoryPath": "Đường dẫn thư mục thương hiệu",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "创建新文件和文件夹",
|
"allowNew": "创建新文件和文件夹",
|
||||||
"allowPublish": "发布新的帖子与页面",
|
"allowPublish": "发布新的帖子与页面",
|
||||||
"allowSignup": "允许用户注册",
|
"allowSignup": "允许用户注册",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(留空以避免更改)",
|
"avoidChanges": "(留空以避免更改)",
|
||||||
"branding": "品牌",
|
"branding": "品牌",
|
||||||
"brandingDirectoryPath": "品牌信息文件夹路径",
|
"brandingDirectoryPath": "品牌信息文件夹路径",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "建立新檔案和目錄",
|
"allowNew": "建立新檔案和目錄",
|
||||||
"allowPublish": "發佈新的貼文與頁面",
|
"allowPublish": "發佈新的貼文與頁面",
|
||||||
"allowSignup": "允許使用者註冊",
|
"allowSignup": "允許使用者註冊",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(留空以避免更改)",
|
"avoidChanges": "(留空以避免更改)",
|
||||||
"branding": "品牌",
|
"branding": "品牌",
|
||||||
"brandingDirectoryPath": "品牌資訊資料夾路徑",
|
"brandingDirectoryPath": "品牌資訊資料夾路徑",
|
||||||
|
|||||||
1
frontend/src/types/settings.d.ts
vendored
1
frontend/src/types/settings.d.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
interface ISettings {
|
interface ISettings {
|
||||||
signup: boolean;
|
signup: boolean;
|
||||||
createUserDir: boolean;
|
createUserDir: boolean;
|
||||||
|
hideLoginButton: boolean;
|
||||||
minimumPasswordLength: number;
|
minimumPasswordLength: number;
|
||||||
userHomeBasePath: string;
|
userHomeBasePath: string;
|
||||||
defaults: SettingsDefaults;
|
defaults: SettingsDefaults;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const enableExec: boolean = window.FileBrowser.EnableExec;
|
|||||||
const tusSettings = window.FileBrowser.TusSettings;
|
const tusSettings = window.FileBrowser.TusSettings;
|
||||||
const origin = window.location.origin;
|
const origin = window.location.origin;
|
||||||
const tusEndpoint = `/api/tus`;
|
const tusEndpoint = `/api/tus`;
|
||||||
|
const hideLoginButton = window.FileBrowser.HideLoginButton;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
name,
|
name,
|
||||||
@@ -39,4 +40,5 @@ export {
|
|||||||
tusSettings,
|
tusSettings,
|
||||||
origin,
|
origin,
|
||||||
tusEndpoint,
|
tusEndpoint,
|
||||||
|
hideLoginButton,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -207,7 +207,10 @@
|
|||||||
<h2 v-if="fileStore.req?.numDirs ?? false">
|
<h2 v-if="fileStore.req?.numDirs ?? false">
|
||||||
{{ t("files.folders") }}
|
{{ t("files.folders") }}
|
||||||
</h2>
|
</h2>
|
||||||
<div v-if="fileStore.req?.numDirs ?? false">
|
<div
|
||||||
|
v-if="fileStore.req?.numDirs ?? false"
|
||||||
|
@contextmenu="showContextMenu"
|
||||||
|
>
|
||||||
<item
|
<item
|
||||||
v-for="item in dirs"
|
v-for="item in dirs"
|
||||||
:key="base64(item.name)"
|
:key="base64(item.name)"
|
||||||
@@ -223,8 +226,13 @@
|
|||||||
</item>
|
</item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 v-if="fileStore.req?.numFiles ?? false">{{ t("files.files") }}</h2>
|
<h2 v-if="fileStore.req?.numFiles ?? false">
|
||||||
<div v-if="fileStore.req?.numFiles ?? false">
|
{{ t("files.files") }}
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
v-if="fileStore.req?.numFiles ?? false"
|
||||||
|
@contextmenu="showContextMenu"
|
||||||
|
>
|
||||||
<item
|
<item
|
||||||
v-for="item in files"
|
v-for="item in files"
|
||||||
:key="base64(item.name)"
|
:key="base64(item.name)"
|
||||||
@@ -239,6 +247,53 @@
|
|||||||
>
|
>
|
||||||
</item>
|
</item>
|
||||||
</div>
|
</div>
|
||||||
|
<context-menu
|
||||||
|
:show="isContextMenuVisible"
|
||||||
|
:pos="contextMenuPos"
|
||||||
|
@hide="hideContextMenu"
|
||||||
|
>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.share"
|
||||||
|
icon="share"
|
||||||
|
:label="t('buttons.share')"
|
||||||
|
show="share"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.rename"
|
||||||
|
icon="mode_edit"
|
||||||
|
:label="t('buttons.rename')"
|
||||||
|
show="rename"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.copy"
|
||||||
|
id="copy-button"
|
||||||
|
icon="content_copy"
|
||||||
|
:label="t('buttons.copyFile')"
|
||||||
|
show="copy"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.move"
|
||||||
|
id="move-button"
|
||||||
|
icon="forward"
|
||||||
|
:label="t('buttons.moveFile')"
|
||||||
|
show="move"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.delete"
|
||||||
|
id="delete-button"
|
||||||
|
icon="delete"
|
||||||
|
:label="t('buttons.delete')"
|
||||||
|
show="delete"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.download"
|
||||||
|
icon="file_download"
|
||||||
|
:label="t('buttons.download')"
|
||||||
|
@action="download"
|
||||||
|
:counter="fileStore.selectedCount"
|
||||||
|
/>
|
||||||
|
<action icon="info" :label="t('buttons.info')" show="info" />
|
||||||
|
</context-menu>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
style="display: none"
|
style="display: none"
|
||||||
@@ -291,6 +346,7 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
|
|||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/header/Action.vue";
|
||||||
import Search from "@/components/Search.vue";
|
import Search from "@/components/Search.vue";
|
||||||
import Item from "@/components/files/ListingItem.vue";
|
import Item from "@/components/files/ListingItem.vue";
|
||||||
|
import ContextMenu from "@/components/ContextMenu.vue";
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
@@ -310,6 +366,8 @@ const columnWidth = ref<number>(280);
|
|||||||
const dragCounter = ref<number>(0);
|
const dragCounter = ref<number>(0);
|
||||||
const width = ref<number>(window.innerWidth);
|
const width = ref<number>(window.innerWidth);
|
||||||
const itemWeight = ref<number>(0);
|
const itemWeight = ref<number>(0);
|
||||||
|
const isContextMenuVisible = ref<boolean>(false);
|
||||||
|
const contextMenuPos = ref<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||||
|
|
||||||
const $showError = inject<IToastError>("$showError")!;
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
|
|
||||||
@@ -438,7 +496,7 @@ watch(req, () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Check the columns size for the first time.
|
// Check the columns size for the first time.
|
||||||
colunmsResize();
|
columnsResize();
|
||||||
|
|
||||||
// How much every listing item affects the window height
|
// How much every listing item affects the window height
|
||||||
setItemWeight();
|
setItemWeight();
|
||||||
@@ -642,7 +700,7 @@ const paste = (event: Event) => {
|
|||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
};
|
};
|
||||||
|
|
||||||
const colunmsResize = () => {
|
const columnsResize = () => {
|
||||||
// Update the columns size based on the window width.
|
// Update the columns size based on the window width.
|
||||||
const items_ = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
const items_ = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
||||||
if (items_ === null) return;
|
if (items_ === null) return;
|
||||||
@@ -850,7 +908,7 @@ const toggleMultipleSelection = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const windowsResize = throttle(() => {
|
const windowsResize = throttle(() => {
|
||||||
colunmsResize();
|
columnsResize();
|
||||||
width.value = window.innerWidth;
|
width.value = window.innerWidth;
|
||||||
|
|
||||||
// Listing element is not displayed
|
// Listing element is not displayed
|
||||||
@@ -977,4 +1035,17 @@ const revealPreviousItem = () => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showContextMenu = (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
isContextMenuVisible.value = true;
|
||||||
|
contextMenuPos.value = {
|
||||||
|
x: event.clientX + 8,
|
||||||
|
y: event.clientY + Math.floor(window.scrollY),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideContextMenu = () => {
|
||||||
|
isContextMenuVisible.value = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
{{ t("settings.createUserDir") }}
|
{{ t("settings.createUserDir") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input type="checkbox" v-model="settings.hideLoginButton" />
|
||||||
|
{{ t("settings.hideLoginButton") }}
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label class="small">{{ t("settings.userHomeBasePath") }}</label>
|
<label class="small">{{ t("settings.userHomeBasePath") }}</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
7
go.mod
7
go.mod
@@ -16,7 +16,7 @@ require (
|
|||||||
github.com/marusama/semaphore/v2 v2.5.0
|
github.com/marusama/semaphore/v2 v2.5.0
|
||||||
github.com/mholt/archives v0.1.5
|
github.com/mholt/archives v0.1.5
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/samber/lo v1.52.0
|
||||||
github.com/shirou/gopsutil/v4 v4.25.10
|
github.com/shirou/gopsutil/v4 v4.25.10
|
||||||
github.com/spf13/afero v1.15.0
|
github.com/spf13/afero v1.15.0
|
||||||
github.com/spf13/cobra v1.10.1
|
github.com/spf13/cobra v1.10.1
|
||||||
@@ -24,7 +24,6 @@ require (
|
|||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
||||||
go.etcd.io/bbolt v1.4.3
|
|
||||||
golang.org/x/crypto v0.44.0
|
golang.org/x/crypto v0.44.0
|
||||||
golang.org/x/image v0.33.0
|
golang.org/x/image v0.33.0
|
||||||
golang.org/x/text v0.31.0
|
golang.org/x/text v0.31.0
|
||||||
@@ -40,6 +39,7 @@ require (
|
|||||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||||
github.com/bodgit/windows v1.0.1 // indirect
|
github.com/bodgit/windows v1.0.1 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||||
@@ -58,9 +58,11 @@ require (
|
|||||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||||
github.com/minio/minlz v1.0.1 // indirect
|
github.com/minio/minlz v1.0.1 // indirect
|
||||||
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
|
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
@@ -68,6 +70,7 @@ require (
|
|||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
|
go.etcd.io/bbolt v1.4.3 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||||
golang.org/x/net v0.46.0 // indirect
|
golang.org/x/net v0.46.0 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -47,6 +47,7 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
|||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -196,10 +197,13 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
|||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||||
|
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA=
|
github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM=
|
github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM=
|
||||||
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ var (
|
|||||||
cmdNotAllowed = []byte("Command not allowed.")
|
cmdNotAllowed = []byte("Command not allowed.")
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:unparam
|
|
||||||
func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) {
|
func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) {
|
||||||
txt := http.StatusText(status)
|
txt := http.StatusText(status)
|
||||||
if err != nil || status >= 400 {
|
if err != nil || status >= 400 {
|
||||||
@@ -49,7 +48,7 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
|||||||
var raw string
|
var raw string
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, msg, err := conn.ReadMessage() //nolint:govet
|
_, msg, err := conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
@@ -63,7 +62,7 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
|||||||
|
|
||||||
// Fail fast
|
// Fail fast
|
||||||
if !d.server.EnableExec || !d.user.Perm.Execute {
|
if !d.server.EnableExec || !d.user.Perm.Execute {
|
||||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
|
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil {
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,21 +71,21 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
|||||||
|
|
||||||
command, name, err := runner.ParseCommand(d.settings, raw)
|
command, name, err := runner.ParseCommand(d.settings, raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:govet
|
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil {
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !slices.Contains(d.user.Commands, name) {
|
if !slices.Contains(d.user.Commands, name) {
|
||||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
|
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil {
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
cmd := exec.Command(command[0], command[1:]...)
|
||||||
cmd.Dir = d.user.FullPath(r.URL.Path)
|
cmd.Dir = d.user.FullPath(r.URL.Path)
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
|||||||
@@ -124,12 +124,12 @@ func createPreview(imgSvc ImgService, fileCache FileCache,
|
|||||||
options []img.Option
|
options []img.Option
|
||||||
)
|
)
|
||||||
|
|
||||||
switch {
|
switch previewSize {
|
||||||
case previewSize == PreviewSizeBig:
|
case PreviewSizeBig:
|
||||||
width = 1080
|
width = 1080
|
||||||
height = 1080
|
height = 1080
|
||||||
options = append(options, img.WithMode(img.ResizeModeFit), img.WithQuality(img.QualityMedium))
|
options = append(options, img.WithMode(img.ResizeModeFit), img.WithQuality(img.QualityMedium))
|
||||||
case previewSize == PreviewSizeThumb:
|
case PreviewSizeThumb:
|
||||||
width = 256
|
width = 256
|
||||||
height = 256
|
height = 256
|
||||||
options = append(options, img.WithMode(img.ResizeModeFill), img.WithQuality(img.QualityLow), img.WithFormat(img.FormatJpeg))
|
options = append(options, img.WithMode(img.ResizeModeFill), img.WithQuality(img.QualityLow), img.WithFormat(img.FormatJpeg))
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ var publicShareHandler = withHashFile(func(w http.ResponseWriter, r *http.Reques
|
|||||||
file := d.raw.(*files.FileInfo)
|
file := d.raw.(*files.FileInfo)
|
||||||
|
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
file.Listing.Sorting = files.Sorting{By: "name", Asc: false}
|
file.Sorting = files.Sorting{By: "name", Asc: false}
|
||||||
file.Listing.ApplySort()
|
file.ApplySort()
|
||||||
return renderJSON(w, r, file)
|
return renderJSON(w, r, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
func TestPublicShareHandlerAuthentication(t *testing.T) {
|
func TestPublicShareHandlerAuthentication(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const passwordBcrypt = "$2y$10$TFAmdCbyd/mEZDe5fUeZJu.MaJQXRTwdqb/IQV.eTn6dWrF58gCSe" //nolint:gosec
|
const passwordBcrypt = "$2y$10$TFAmdCbyd/mEZDe5fUeZJu.MaJQXRTwdqb/IQV.eTn6dWrF58gCSe"
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
share *share.Link
|
share *share.Link
|
||||||
req *http.Request
|
req *http.Request
|
||||||
@@ -70,7 +70,7 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
if err := db.Close(); err != nil { //nolint:govet
|
if err := db.Close(); err != nil {
|
||||||
t.Errorf("failed to close db: %v", err)
|
t.Errorf("failed to close db: %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func parseQueryFiles(r *http.Request, f *files.FileInfo, _ *users.User) ([]strin
|
|||||||
fileSlice = append(fileSlice, f.Path)
|
fileSlice = append(fileSlice, f.Path)
|
||||||
} else {
|
} else {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
name, err := url.QueryUnescape(strings.Replace(name, "+", "%2B", -1)) //nolint:govet
|
name, err := url.QueryUnescape(strings.ReplaceAll(name, "+", "%2B"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
|||||||
}
|
}
|
||||||
|
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
file.Listing.Sorting = d.user.Sorting
|
file.Sorting = d.user.Sorting
|
||||||
file.Listing.ApplySort()
|
file.ApplySort()
|
||||||
return renderJSON(w, r, file)
|
return renderJSON(w, r, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
type settingsData struct {
|
type settingsData struct {
|
||||||
Signup bool `json:"signup"`
|
Signup bool `json:"signup"`
|
||||||
|
HideLoginButton bool `json:"hideLoginButton"`
|
||||||
CreateUserDir bool `json:"createUserDir"`
|
CreateUserDir bool `json:"createUserDir"`
|
||||||
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
||||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||||
@@ -24,6 +25,7 @@ type settingsData struct {
|
|||||||
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
data := &settingsData{
|
data := &settingsData{
|
||||||
Signup: d.settings.Signup,
|
Signup: d.settings.Signup,
|
||||||
|
HideLoginButton: d.settings.HideLoginButton,
|
||||||
CreateUserDir: d.settings.CreateUserDir,
|
CreateUserDir: d.settings.CreateUserDir,
|
||||||
MinimumPasswordLength: d.settings.MinimumPasswordLength,
|
MinimumPasswordLength: d.settings.MinimumPasswordLength,
|
||||||
UserHomeBasePath: d.settings.UserHomeBasePath,
|
UserHomeBasePath: d.settings.UserHomeBasePath,
|
||||||
@@ -55,6 +57,7 @@ var settingsPutHandler = withAdmin(func(_ http.ResponseWriter, r *http.Request,
|
|||||||
d.settings.Tus = req.Tus
|
d.settings.Tus = req.Tus
|
||||||
d.settings.Shell = req.Shell
|
d.settings.Shell = req.Shell
|
||||||
d.settings.Commands = req.Commands
|
d.settings.Commands = req.Commands
|
||||||
|
d.settings.HideLoginButton = req.HideLoginButton
|
||||||
|
|
||||||
err = d.store.Settings.Save(d.settings)
|
err = d.store.Settings.Save(d.settings)
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request
|
|||||||
var expire int64 = 0
|
var expire int64 = 0
|
||||||
|
|
||||||
if body.Expires != "" {
|
if body.Expires != "" {
|
||||||
//nolint:govet
|
|
||||||
num, err := strconv.Atoi(body.Expires)
|
num, err := strconv.Atoi(body.Expires)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
|
|||||||
@@ -46,11 +46,12 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
|||||||
"ResizePreview": d.server.ResizePreview,
|
"ResizePreview": d.server.ResizePreview,
|
||||||
"EnableExec": d.server.EnableExec,
|
"EnableExec": d.server.EnableExec,
|
||||||
"TusSettings": d.settings.Tus,
|
"TusSettings": d.settings.Tus,
|
||||||
|
"HideLoginButton": d.settings.HideLoginButton,
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.settings.Branding.Files != "" {
|
if d.settings.Branding.Files != "" {
|
||||||
fPath := filepath.Join(d.settings.Branding.Files, "custom.css")
|
fPath := filepath.Join(d.settings.Branding.Files, "custom.css")
|
||||||
_, err := os.Stat(fPath) //nolint:govet
|
_, err := os.Stat(fPath)
|
||||||
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
log.Printf("couldn't load custom styles: %v", err)
|
log.Printf("couldn't load custom styles: %v", err)
|
||||||
@@ -62,7 +63,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.settings.AuthMethod == auth.MethodJSONAuth {
|
if d.settings.AuthMethod == auth.MethodJSONAuth {
|
||||||
raw, err := d.store.Auth.Get(d.settings.AuthMethod) //nolint:govet
|
raw, err := d.store.Auth.Get(d.settings.AuthMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, o
|
|||||||
case ResizeModeFill:
|
case ResizeModeFill:
|
||||||
img = imaging.Fill(img, width, height, imaging.Center, config.quality.resampleFilter())
|
img = imaging.Fill(img, width, height, imaging.Center, config.quality.resampleFilter())
|
||||||
case ResizeModeFit:
|
case ResizeModeFit:
|
||||||
fallthrough //nolint:gocritic
|
fallthrough
|
||||||
default:
|
default:
|
||||||
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
|
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
|
||||||
}
|
}
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -4,11 +4,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/cmd"
|
"github.com/filebrowser/filebrowser/v2/cmd"
|
||||||
"github.com/filebrowser/filebrowser/v2/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := cmd.Execute(); err != nil {
|
if err := cmd.Execute(); err != nil {
|
||||||
os.Exit(errors.GetExitCode(err))
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ func (r *Runner) exec(raw, evt, path, dst string, user *users.User) error {
|
|||||||
command[i] = os.Expand(arg, envMapping)
|
command[i] = os.Expand(arg, envMapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
|
cmd := exec.Command(command[0], command[1:]...)
|
||||||
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
|
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope)) //nolint:gocritic
|
cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", evt))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", evt))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", dst))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", dst))
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ func parseSearch(value string) *searchOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// removes the options from the value
|
// removes the options from the value
|
||||||
value = strings.Replace(value, "case:insensitive", "", -1)
|
value = strings.ReplaceAll(value, "case:insensitive", "")
|
||||||
value = strings.Replace(value, "case:sensitive", "", -1)
|
value = strings.ReplaceAll(value, "case:sensitive", "")
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
|
|
||||||
types := typeRegexp.FindAllStringSubmatch(value, -1)
|
types := typeRegexp.FindAllStringSubmatch(value, -1)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (s *Settings) MakeUserDir(username, userScope, serverRoot string) (string,
|
|||||||
func cleanUsername(s string) string {
|
func cleanUsername(s string) string {
|
||||||
// Remove any trailing space to avoid ending on -
|
// Remove any trailing space to avoid ending on -
|
||||||
s = strings.Trim(s, " ")
|
s = strings.Trim(s, " ")
|
||||||
s = strings.Replace(s, "..", "", -1)
|
s = strings.ReplaceAll(s, "..", "")
|
||||||
|
|
||||||
// Replace all characters which not in the list `0-9A-Za-z@_\-.` with a dash
|
// Replace all characters which not in the list `0-9A-Za-z@_\-.` with a dash
|
||||||
s = invalidFilenameChars.ReplaceAllString(s, "-")
|
s = invalidFilenameChars.ReplaceAllString(s, "-")
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type AuthMethod string
|
|||||||
type Settings struct {
|
type Settings struct {
|
||||||
Key []byte `json:"key"`
|
Key []byte `json:"key"`
|
||||||
Signup bool `json:"signup"`
|
Signup bool `json:"signup"`
|
||||||
|
HideLoginButton bool `json:"hideLoginButton"`
|
||||||
CreateUserDir bool `json:"createUserDir"`
|
CreateUserDir bool `json:"createUserDir"`
|
||||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||||
Defaults UserDefaults `json:"defaults"`
|
Defaults UserDefaults `json:"defaults"`
|
||||||
@@ -34,6 +35,7 @@ type Settings struct {
|
|||||||
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
||||||
FileMode fs.FileMode `json:"fileMode"`
|
FileMode fs.FileMode `json:"fileMode"`
|
||||||
DirMode fs.FileMode `json:"dirMode"`
|
DirMode fs.FileMode `json:"dirMode"`
|
||||||
|
HideDotfiles bool `json:"hideDotfiles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRules implements rules.Provider.
|
// GetRules implements rules.Provider.
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
package importer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/auth"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/storage"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
|
||||||
)
|
|
||||||
|
|
||||||
type oldDefs struct {
|
|
||||||
Commands []string `json:"commands" yaml:"commands" toml:"commands"`
|
|
||||||
Scope string `json:"scope" yaml:"scope" toml:"scope"`
|
|
||||||
ViewMode string `json:"viewMode" yaml:"viewMode" toml:"viewMode"`
|
|
||||||
Locale string `json:"locale" yaml:"locale" toml:"locale"`
|
|
||||||
AllowCommands bool `json:"allowCommands" yaml:"allowCommands" toml:"allowCommands"`
|
|
||||||
AllowEdit bool `json:"allowEdit" yaml:"allowEdit" toml:"allowEdit"`
|
|
||||||
AllowNew bool `json:"allowNew" yaml:"allowNew" toml:"allowNew"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type oldAuth struct {
|
|
||||||
Method string `json:"method" yaml:"method" toml:"method"` // default none proxy
|
|
||||||
Header string `json:"header" yaml:"header" toml:"header"`
|
|
||||||
Command string `json:"command" yaml:"command" toml:"command"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type oldConf struct {
|
|
||||||
Port string `json:"port" yaml:"port" toml:"port"`
|
|
||||||
BaseURL string `json:"baseURL" yaml:"baseURL" toml:"baseURL"`
|
|
||||||
Log string `json:"log" yaml:"log" toml:"log"`
|
|
||||||
Address string `json:"address" yaml:"address" toml:"address"`
|
|
||||||
Defaults oldDefs `json:"defaults" yaml:"defaults" toml:"defaults"`
|
|
||||||
ReCaptcha struct {
|
|
||||||
Key string `json:"key" yaml:"key" toml:"key"`
|
|
||||||
Secret string `json:"secret" yaml:"secret" toml:"secret"`
|
|
||||||
Host string `json:"host" yaml:"host" toml:"host"`
|
|
||||||
} `json:"recaptcha" yaml:"recaptcha" toml:"recaptcha"`
|
|
||||||
Auth oldAuth `json:"auth" yaml:"auth" toml:"auth"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaults = &oldConf{
|
|
||||||
Port: "0",
|
|
||||||
Log: "stdout",
|
|
||||||
Defaults: oldDefs{
|
|
||||||
Commands: []string{"git", "svn", "hg"},
|
|
||||||
ViewMode: string(users.MosaicViewMode),
|
|
||||||
AllowCommands: true,
|
|
||||||
AllowEdit: true,
|
|
||||||
AllowNew: true,
|
|
||||||
Locale: "en",
|
|
||||||
},
|
|
||||||
Auth: oldAuth{
|
|
||||||
Method: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConf(path string) (*oldConf, error) {
|
|
||||||
cfg := &oldConf{}
|
|
||||||
if path != "" {
|
|
||||||
ext := filepath.Ext(path)
|
|
||||||
|
|
||||||
fd, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer fd.Close()
|
|
||||||
|
|
||||||
switch ext {
|
|
||||||
case ".json":
|
|
||||||
err = json.NewDecoder(fd).Decode(cfg)
|
|
||||||
case ".toml":
|
|
||||||
err = toml.NewDecoder(fd).Decode(cfg)
|
|
||||||
case ".yaml", ".yml":
|
|
||||||
err = yaml.NewDecoder(fd).Decode(cfg)
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unsupported config extension " + ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cfg = defaults
|
|
||||||
path, err := filepath.Abs(".")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cfg.Defaults.Scope = path
|
|
||||||
}
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func importConf(db *storm.DB, path string, sto *storage.Storage) error {
|
|
||||||
cfg, err := readConf(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
commands := map[string][]string{}
|
|
||||||
err = db.Get("config", "commands", &commands)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := []byte{}
|
|
||||||
err = db.Get("config", "key", &key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &settings.Settings{
|
|
||||||
Key: key,
|
|
||||||
Signup: false,
|
|
||||||
Defaults: settings.UserDefaults{
|
|
||||||
Scope: cfg.Defaults.Scope,
|
|
||||||
Commands: cfg.Defaults.Commands,
|
|
||||||
ViewMode: users.ViewMode(cfg.Defaults.ViewMode),
|
|
||||||
Locale: cfg.Defaults.Locale,
|
|
||||||
Perm: users.Permissions{
|
|
||||||
Admin: false,
|
|
||||||
Execute: cfg.Defaults.AllowCommands,
|
|
||||||
Create: cfg.Defaults.AllowNew,
|
|
||||||
Rename: cfg.Defaults.AllowEdit,
|
|
||||||
Modify: cfg.Defaults.AllowEdit,
|
|
||||||
Delete: cfg.Defaults.AllowEdit,
|
|
||||||
Share: true,
|
|
||||||
Download: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
server := &settings.Server{
|
|
||||||
BaseURL: cfg.BaseURL,
|
|
||||||
Port: cfg.Port,
|
|
||||||
Address: cfg.Address,
|
|
||||||
Log: cfg.Log,
|
|
||||||
}
|
|
||||||
|
|
||||||
var auther auth.Auther
|
|
||||||
switch cfg.Auth.Method {
|
|
||||||
case "proxy":
|
|
||||||
auther = &auth.ProxyAuth{Header: cfg.Auth.Header}
|
|
||||||
s.AuthMethod = auth.MethodProxyAuth
|
|
||||||
case "hook":
|
|
||||||
auther = &auth.HookAuth{Command: cfg.Auth.Command}
|
|
||||||
s.AuthMethod = auth.MethodHookAuth
|
|
||||||
case "none":
|
|
||||||
auther = &auth.NoAuth{}
|
|
||||||
s.AuthMethod = auth.MethodNoAuth
|
|
||||||
default:
|
|
||||||
auther = &auth.JSONAuth{
|
|
||||||
ReCaptcha: &auth.ReCaptcha{
|
|
||||||
Host: cfg.ReCaptcha.Host,
|
|
||||||
Key: cfg.ReCaptcha.Key,
|
|
||||||
Secret: cfg.ReCaptcha.Secret,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
s.AuthMethod = auth.MethodJSONAuth
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sto.Auth.Save(auther)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sto.Settings.Save(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sto.Settings.SaveServer(server)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Configuration successfully imported.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package importer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/asdine/storm/v3"
|
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Import imports an old configuration to a newer database.
|
|
||||||
func Import(oldDBPath, oldConf, newDBPath string) error {
|
|
||||||
oldDB, err := storm.Open(oldDBPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer oldDB.Close()
|
|
||||||
|
|
||||||
newDB, err := storm.Open(newDBPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer newDB.Close()
|
|
||||||
|
|
||||||
sto, err := bolt.NewStorage(newDB)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = importUsers(oldDB, sto)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = importConf(oldDB, oldConf, sto)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
package importer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
|
||||||
bolt "go.etcd.io/bbolt"
|
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/rules"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/storage"
|
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
|
||||||
)
|
|
||||||
|
|
||||||
type oldUser struct {
|
|
||||||
ID int `storm:"id,increment"`
|
|
||||||
Admin bool `json:"admin"`
|
|
||||||
AllowCommands bool `json:"allowCommands"` // Execute commands
|
|
||||||
AllowEdit bool `json:"allowEdit"` // Edit/rename files
|
|
||||||
AllowNew bool `json:"allowNew"` // Create files and folders
|
|
||||||
AllowPublish bool `json:"allowPublish"` // Publish content (to use with static gen)
|
|
||||||
LockPassword bool `json:"lockPassword"`
|
|
||||||
Commands []string `json:"commands"`
|
|
||||||
Locale string `json:"locale"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Rules []*rules.Rule `json:"rules"`
|
|
||||||
Scope string `json:"filesystem"`
|
|
||||||
Username string `json:"username" storm:"index,unique"`
|
|
||||||
ViewMode string `json:"viewMode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func readOldUsers(db *storm.DB) ([]*oldUser, error) {
|
|
||||||
var oldUsers []*oldUser
|
|
||||||
err := db.Bolt.View(func(tx *bolt.Tx) error {
|
|
||||||
return tx.Bucket([]byte("User")).ForEach(func(_ []byte, v []byte) error {
|
|
||||||
if len(v) > 0 && string(v)[0] == '{' {
|
|
||||||
user := &oldUser{}
|
|
||||||
err := json.Unmarshal(v, user)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldUsers = append(oldUsers, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return oldUsers, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
|
|
||||||
list := []*users.User{}
|
|
||||||
|
|
||||||
for _, oldUser := range old {
|
|
||||||
user := &users.User{
|
|
||||||
Username: oldUser.Username,
|
|
||||||
Password: oldUser.Password,
|
|
||||||
Scope: oldUser.Scope,
|
|
||||||
Locale: oldUser.Locale,
|
|
||||||
LockPassword: oldUser.LockPassword,
|
|
||||||
ViewMode: users.ViewMode(oldUser.ViewMode),
|
|
||||||
Commands: oldUser.Commands,
|
|
||||||
Rules: []rules.Rule{},
|
|
||||||
Perm: users.Permissions{
|
|
||||||
Admin: oldUser.Admin,
|
|
||||||
Execute: oldUser.AllowCommands,
|
|
||||||
Create: oldUser.AllowNew,
|
|
||||||
Rename: oldUser.AllowEdit,
|
|
||||||
Modify: oldUser.AllowEdit,
|
|
||||||
Delete: oldUser.AllowEdit,
|
|
||||||
Share: true,
|
|
||||||
Download: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rule := range oldUser.Rules {
|
|
||||||
user.Rules = append(user.Rules, *rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := user.Clean("")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
list = append(list, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func importUsers(old *storm.DB, sto *storage.Storage) error {
|
|
||||||
oldUsers, err := readOldUsers(old)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newUsers, err := convertUsersToNew(oldUsers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range newUsers {
|
|
||||||
err = sto.Users.Save(user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%d users successfully imported into the new DB.\n", len(newUsers))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user