Compare commits
12
Commits
main
...
v0.6.8-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ffc1c1ae6 | ||
|
|
d3e49b2803 | ||
|
|
5be5646c7c | ||
|
|
19e6a836a5 | ||
|
|
a435fa20bf | ||
|
|
3ddedea6ef | ||
|
|
3d612c61a4 | ||
|
|
938527301b | ||
|
|
79a0b3def9 | ||
|
|
9757cd58b6 | ||
|
|
31304ef9a6 | ||
|
|
cf4abf63c6 |
No files matched your search
@@ -30,42 +30,3 @@ jobs:
|
||||
context: .
|
||||
file: ./_docker/Dockerfile.playwright-general
|
||||
push: false
|
||||
push_latest_to_registry:
|
||||
needs: [ test_playwright ]
|
||||
name: Push latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Find latest beta tag
|
||||
run: |
|
||||
echo "LATEST_TAG=$(git describe --tags $(git rev-list --tags="*-beta*" --max-count=1))" >> $GITHUB_ENV
|
||||
echo "latest tag is $LATEST_TAG"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
build-args: |
|
||||
VERSION=${{ env.LATEST_TAG }}
|
||||
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
file: ./_docker/Dockerfile
|
||||
push: true
|
||||
tags: 'gtstef/filebrowser:latest'
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -24,20 +24,30 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{github.actor}}
|
||||
password: ${{ secrets.PAT }}
|
||||
- name: Extract metadata (tags, labels) for Docker and GHCR
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: modify version names
|
||||
images: |
|
||||
gtstef/filebrowser
|
||||
ghcr.io/gtsteffaniak/filebrowser
|
||||
- name: Modify version names
|
||||
id: modify-json
|
||||
run: |
|
||||
JSON="${{ steps.meta.outputs.tags }}"
|
||||
# Use jq to remove 'v' from the version field
|
||||
JSON=$(echo "$JSON" | sed 's/filebrowser:dev-v/filebrowser:/')
|
||||
JSON=$(echo "$JSON" | sed -E 's/(filebrowser:[0-9]+\.[0-9]+\.[0-9]+)/\1-dev/g')
|
||||
JSON="$JSON,gtstef/filebrowser:dev"
|
||||
echo "cleaned_tag=$JSON" >> $GITHUB_OUTPUT
|
||||
TAGS="${{ steps.meta.outputs.tags }}"
|
||||
# Apply modifications to both docker.io and ghcr.io tags
|
||||
CLEANED_TAGS=$(echo "$TAGS" | sed 's/filebrowser:dev-v/filebrowser:/g' | sed -E 's/(filebrowser:[0-9]+\.[0-9]+\.[0-9]+)/\1-dev/g')
|
||||
# Add dev tag to both registries
|
||||
CLEANED_TAGS="$CLEANED_TAGS,gtstef/filebrowser:dev,ghcr.io/gtsteffaniak/filebrowser:dev"
|
||||
echo "cleaned_tag<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CLEANED_TAGS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
@@ -49,4 +59,4 @@ jobs:
|
||||
file: ./_docker/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.modify-json.outputs.cleaned_tag }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
+30
-11
@@ -45,26 +45,45 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{github.actor}}
|
||||
password: ${{ secrets.PAT }}
|
||||
- name: Extract metadata (tags, labels) for Docker and GHCR
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
images: |
|
||||
gtstef/filebrowser
|
||||
ghcr.io/gtsteffaniak/filebrowser
|
||||
- name: Modify tags (strip 'v' and add 'beta'/'stable' if needed)
|
||||
id: modify-json
|
||||
run: |
|
||||
JSON='${{ steps.meta.outputs.json }}' # Assuming the JSON output is stored in steps.meta.outputs.json
|
||||
# Process the JSON array using jq
|
||||
JSON='${{ steps.meta.outputs.json }}'
|
||||
MODIFIED_JSON=$(echo "$JSON" | jq '
|
||||
.tags |= map(sub(":v"; ":")) |
|
||||
if any(.tags[]; test("beta")) then .tags += ["gtstef/filebrowser:beta"] else . end |
|
||||
if any(.tags[]; test("stable")) then .tags += ["gtstef/filebrowser:stable"] else . end
|
||||
.tags |= map(sub(":v"; ":")) |
|
||||
if any(.tags[]; test("beta")) then
|
||||
.tags += [
|
||||
"gtstef/filebrowser:beta",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:beta",
|
||||
"gtstef/filebrowser:latest",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:latest"
|
||||
]
|
||||
else . end |
|
||||
if any(.tags[]; test("stable")) then
|
||||
.tags += [
|
||||
"gtstef/filebrowser:stable",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:stable",
|
||||
"gtstef/filebrowser:latest",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:latest"
|
||||
]
|
||||
else . end
|
||||
')
|
||||
# Convert JSON to a comma-separated string for Docker tags
|
||||
TAGS_CSV=$(echo "$MODIFIED_JSON" | jq -r '.tags | join(",")')
|
||||
echo "$TAGS_CSV" > modified_tags.txt # Save for debugging
|
||||
echo "$TAGS_CSV" > modified_tags.txt # Optional debug output
|
||||
echo "cleaned_tag=$TAGS_CSV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
@@ -76,4 +95,4 @@ jobs:
|
||||
file: ./_docker/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.modify-json.outputs.cleaned_tag }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -2,6 +2,25 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
||||
|
||||
## v0.6.8-beta
|
||||
|
||||
**New Features**
|
||||
- environment variables are available for certain secrets.
|
||||
- see wiki https://github.com/gtsteffaniak/filebrowser/wiki/Environment-Variables
|
||||
- thanks @aaronkyriesenbach https://github.com/gtsteffaniak/filebrowser/pull/511
|
||||
|
||||
**Notes**:
|
||||
- config validation (see https://github.com/gtsteffaniak/filebrowser/wiki/Full-Config-Example)
|
||||
- fails when config file contains unknown fields (helps spot typos)
|
||||
- some light value validation on certain fields
|
||||
- removed recaptcha -- was disabled and not used before.
|
||||
- moved `recaptcha` and `signup` configs to `auth.methods.password`
|
||||
- also pushes to ghcr `ghcr.io/gtsteffaniak/filebrowser` https://github.com/gtsteffaniak/filebrowser/issues/470
|
||||
|
||||
**BugFixes**:
|
||||
- fix scope reset on restart https://github.com/gtsteffaniak/filebrowser/issues/515
|
||||
- Clicking empty space to deselect https://github.com/gtsteffaniak/filebrowser/issues/492
|
||||
|
||||
## v0.6.7-beta
|
||||
|
||||
**Notes**:
|
||||
|
||||
@@ -6,7 +6,6 @@ server:
|
||||
sources:
|
||||
- path: "../frontend/tests/playwright-files"
|
||||
auth:
|
||||
signup: false
|
||||
methods:
|
||||
noauth: true
|
||||
frontend:
|
||||
|
||||
@@ -18,7 +18,6 @@ frontend:
|
||||
title: "Playwright"
|
||||
|
||||
auth:
|
||||
signup: false
|
||||
methods:
|
||||
proxy:
|
||||
enabled: true
|
||||
|
||||
+24
-13
@@ -3,11 +3,14 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/fileutils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
var createBackup = []bool{}
|
||||
|
||||
func validateUserInfo() {
|
||||
// update source info for users if names/sources/paths might have changed
|
||||
usersList, err := store.Users.Gets()
|
||||
@@ -15,23 +18,37 @@ func validateUserInfo() {
|
||||
logger.Fatal(fmt.Sprintf("could not load users: %v", err))
|
||||
}
|
||||
for _, user := range usersList {
|
||||
updateUser := false
|
||||
if user.Username == "publicUser" {
|
||||
settings.ApplyUserDefaults(user)
|
||||
updateUser = true
|
||||
}
|
||||
if updateUserScopes(user) {
|
||||
updateUser = true
|
||||
}
|
||||
if updateUser {
|
||||
if len(createBackup) == 1 {
|
||||
logger.Warning("Incompatible user settings detected, creating backup of database before converting.")
|
||||
err = fileutils.CopyFile(settings.Config.Server.Database, fmt.Sprintf("%s.bak", settings.Config.Server.Database))
|
||||
if err != nil {
|
||||
logger.Fatal(fmt.Sprintf("Unable to create automatic backup of database due to error: %v", err))
|
||||
}
|
||||
}
|
||||
err := store.Users.Save(user, false)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("could not update user: %v", err))
|
||||
}
|
||||
}
|
||||
updateUserScopes(user)
|
||||
}
|
||||
}
|
||||
|
||||
func updateUserScopes(user *users.User) {
|
||||
func updateUserScopes(user *users.User) bool {
|
||||
updateUser := false
|
||||
newScopes := []users.SourceScope{}
|
||||
for _, source := range settings.Config.Server.SourceMap {
|
||||
scopePath, err := settings.GetScopeFromSourcePath(user.Scopes, source.Path)
|
||||
if !user.Perm.Admin {
|
||||
// apply default scope if it doesn't exist
|
||||
if !user.Perm.Admin && scopePath == "" {
|
||||
scopePath = source.Config.DefaultUserScope
|
||||
}
|
||||
if scopePath == "" {
|
||||
@@ -49,12 +66,11 @@ func updateUserScopes(user *users.User) {
|
||||
} else {
|
||||
newScopes = append(newScopes, users.SourceScope{Scope: scopePath, Name: source.Path}) // backend name is path
|
||||
}
|
||||
user.Scopes = newScopes
|
||||
}
|
||||
|
||||
// maintain backwards compatibility, update user scope from scopes
|
||||
if len(user.Scopes) == 0 {
|
||||
user.Scopes = []users.SourceScope{
|
||||
if len(newScopes) == 0 {
|
||||
newScopes = []users.SourceScope{
|
||||
{
|
||||
Scope: settings.Config.Server.DefaultSource.Config.DefaultUserScope,
|
||||
Name: settings.Config.Server.DefaultSource.Path, // backend name is path
|
||||
@@ -62,11 +78,6 @@ func updateUserScopes(user *users.User) {
|
||||
}
|
||||
updateUser = true
|
||||
}
|
||||
if !updateUser {
|
||||
return
|
||||
}
|
||||
err := store.Users.Save(user, false)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("could not update user: %v", err))
|
||||
}
|
||||
user.Scopes = newScopes
|
||||
return updateUser
|
||||
}
|
||||
@@ -5,12 +5,9 @@ server:
|
||||
- levels: "info|debug|warning|error"
|
||||
sources:
|
||||
- path: "/srv"
|
||||
auth:
|
||||
signup: false
|
||||
userDefaults:
|
||||
darkMode: true
|
||||
disableSettings: false
|
||||
scope: "."
|
||||
singleClick: false
|
||||
permissions:
|
||||
admin: false
|
||||
|
||||
+12
-14
@@ -312,32 +312,30 @@ func WriteFile(opts FileOptions, in io.Reader) error {
|
||||
return idx.RefreshFileInfo(opts)
|
||||
}
|
||||
|
||||
// resolveSymlinks resolves symlinks in the given path
|
||||
func resolveSymlinks(path string) (string, bool, error) {
|
||||
for {
|
||||
// Get the file info using os.Lstat to handle symlinks
|
||||
const maxSymlinks = 25
|
||||
for i := 0; i < maxSymlinks; i++ {
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return path, false, fmt.Errorf("could not stat path: %s, %v", path, err)
|
||||
}
|
||||
|
||||
// Check if the path is a symlink
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
// Read the symlink target
|
||||
target, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return path, false, fmt.Errorf("could not read symlink: %s, %v", path, err)
|
||||
}
|
||||
|
||||
// Resolve the symlink's target relative to its directory
|
||||
// This ensures the resolved path is absolute and correctly calculated
|
||||
path = filepath.Join(filepath.Dir(path), target)
|
||||
} else {
|
||||
// Not a symlink, so return the resolved path and whether it's a directory
|
||||
isDir := info.IsDir()
|
||||
return path, isDir, nil
|
||||
path = filepath.Clean(filepath.Join(filepath.Dir(path), target))
|
||||
if !filepath.IsAbs(path) {
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return path, false, fmt.Errorf("could not resolve absolute path: %s, %v", path, err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
return path, info.IsDir(), nil
|
||||
}
|
||||
return path, false, fmt.Errorf("too many symlink resolutions for path: %s", path)
|
||||
}
|
||||
|
||||
// addContent reads and sets content based on the file type.
|
||||
|
||||
+13
-8
@@ -8,7 +8,8 @@ require (
|
||||
github.com/asdine/storm/v3 v3.2.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||
github.com/goccy/go-yaml v1.16.0
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/goccy/go-yaml v1.17.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
@@ -16,9 +17,9 @@ require (
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/swaggo/http-swagger v1.3.4
|
||||
github.com/swaggo/swag v1.16.4
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/image v0.25.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/image v0.26.0
|
||||
golang.org/x/sys v0.32.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -26,24 +27,28 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/golang/geo v0.0.0-20250321002858-2bb09a976f49 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang/geo v0.0.0-20250410091149-9ff723126794 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/swaggo/files v1.0.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/tools v0.32.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
+30
-18
@@ -27,6 +27,8 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LV
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
@@ -44,15 +46,23 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/goccy/go-yaml v1.16.0 h1:d7m1G7A0t+logajVtklHfDYJs2Et9g3gHwdBNNFou0w=
|
||||
github.com/goccy/go-yaml v1.16.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
|
||||
github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20250321002858-2bb09a976f49 h1:JJ32MDIC/hIQ/Fq8Ej9Hgh1s3D82FYNMt54WYViOI7Y=
|
||||
github.com/golang/geo v0.0.0-20250321002858-2bb09a976f49/go.mod h1:J+F9/3Ofc8ysEOY2/cNjxTMl2eB1gvPIywEHUplPgDA=
|
||||
github.com/golang/geo v0.0.0-20250410091149-9ff723126794 h1:sYWK0P7YCim7xkB7IqMX6k4YtrcqLVSdCZqmMnqhBAI=
|
||||
github.com/golang/geo v0.0.0-20250410091149-9ff723126794/go.mod h1:J+F9/3Ofc8ysEOY2/cNjxTMl2eB1gvPIywEHUplPgDA=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
@@ -73,6 +83,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -104,11 +116,11 @@ go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
|
||||
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
@@ -123,12 +135,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -143,8 +155,8 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -153,13 +165,13 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
|
||||
@@ -124,7 +124,7 @@ type signupBody struct {
|
||||
// @Failure 500 {object} map[string]string "Internal server error"
|
||||
// @Router /api/signup [post]
|
||||
func signupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !settings.Config.Auth.Signup {
|
||||
if !settings.Config.Auth.Methods.PasswordAuth.Signup {
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
@@ -222,7 +222,7 @@ func makeSignedTokenAPI(user *users.User, name string, duration time.Duration, p
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
|
||||
tokenString, err := token.SignedString(config.Auth.Key)
|
||||
tokenString, err := token.SignedString([]byte(config.Auth.Key))
|
||||
if err != nil {
|
||||
return claim, err
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func withUserHelper(fn handleFunc) handleFunc {
|
||||
return fn(w, r, data)
|
||||
}
|
||||
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
||||
return config.Auth.Key, nil
|
||||
return []byte(config.Auth.Key), nil
|
||||
}
|
||||
tokenString, err := extractToken(r)
|
||||
if err != nil {
|
||||
|
||||
@@ -209,7 +209,7 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
|
||||
handler := withHashFileHelper(publicShareHandler)
|
||||
if err := store.Settings.Save(&settings.Settings{
|
||||
Auth: settings.Auth{
|
||||
Key: []byte("key"),
|
||||
Key: "key",
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("failed to save settings: %v", err)
|
||||
|
||||
+2
-2
@@ -230,8 +230,8 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
|
||||
sizeInMB := estimatedSize / 1024 / 1024
|
||||
// if larger than 100 megabytes, log it
|
||||
if sizeInMB > 100 {
|
||||
// if larger than 500 megabytes, log it
|
||||
if sizeInMB > 500 {
|
||||
logger.Debug(fmt.Sprintf("User %v is downloading large (%d MB) file: %v", d.user.Username, sizeInMB, fileName))
|
||||
}
|
||||
// Stream file to response
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param property query string false "Property to retrieve: `userDefaults`, `frontend`, `auth`, `server`, `sources`"
|
||||
// @Success 200 {object} settingsData "System settings data"
|
||||
// @Success 200 {object} settings.Settings "System settings data"
|
||||
// @Router /api/settings [get]
|
||||
func settingsGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
property := r.URL.Query().Get("property")
|
||||
|
||||
+1
-25
@@ -8,7 +8,6 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/version"
|
||||
)
|
||||
@@ -48,42 +47,19 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, file, contentT
|
||||
"Version": version.Version,
|
||||
"CommitSHA": version.CommitSHA,
|
||||
"StaticURL": config.Server.BaseURL + "static",
|
||||
"Signup": settings.Config.Auth.Signup,
|
||||
"Signup": settings.Config.Auth.Methods.PasswordAuth.Signup,
|
||||
"NoAuth": config.Auth.Methods.NoAuth,
|
||||
"PasswordAuth": config.Auth.Methods.PasswordAuth,
|
||||
"LoginPage": auther.LoginPage(),
|
||||
"CSS": false,
|
||||
"ReCaptcha": false,
|
||||
"EnableThumbs": config.Server.EnableThumbnails,
|
||||
"ResizePreview": config.Server.ResizePreview,
|
||||
"EnableExec": config.Server.EnableExec,
|
||||
"ReCaptchaHost": config.Auth.Recaptcha.Host,
|
||||
"ExternalLinks": config.Frontend.ExternalLinks,
|
||||
"ExternalUrl": strings.TrimSuffix(config.Server.ExternalUrl, "/"),
|
||||
"OnlyOfficeUrl": settings.Config.Integrations.OnlyOffice.Url,
|
||||
"SourceCount": len(config.Server.Sources),
|
||||
}
|
||||
|
||||
if config.Auth.Methods.PasswordAuth.Enabled {
|
||||
raw, err := store.Auth.Get("password") //nolint:govet
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
auther, ok := raw.(*auth.JSONAuth)
|
||||
if !ok {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if auther.ReCaptcha != nil {
|
||||
data["ReCaptcha"] = auther.ReCaptcha.Key != "" && auther.ReCaptcha.Secret != ""
|
||||
data["ReCaptchaHost"] = auther.ReCaptcha.Host
|
||||
data["ReCaptchaKey"] = auther.ReCaptcha.Key
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
@@ -134,7 +134,7 @@ func Error(msg string) {
|
||||
if len(loggers) > 0 {
|
||||
Log(levels.ERROR, msg, true, false, RED)
|
||||
} else {
|
||||
log.Println("[ERROR] ", msg)
|
||||
log.Println("[ERROR]", msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
-13
@@ -8,6 +8,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
@@ -21,6 +22,7 @@ func Initialize(configFile string) {
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
}
|
||||
loadEnvConfig()
|
||||
setupLogging()
|
||||
setupAuth()
|
||||
setupSources()
|
||||
@@ -123,9 +125,6 @@ func setupBaseURL() {
|
||||
}
|
||||
|
||||
func setupAuth() {
|
||||
if Config.Auth.Method != "" {
|
||||
logger.Warning("The `auth.method` setting is deprecated and will be removed in a future version. Please use `auth.methods` instead.")
|
||||
}
|
||||
if Config.Auth.Methods.PasswordAuth.Enabled {
|
||||
Config.Auth.AuthMethods = append(Config.Auth.AuthMethods, "Password")
|
||||
}
|
||||
@@ -176,29 +175,60 @@ func loadConfigWithDefaults(configFile string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
yamlData := make([]byte, stat.Size())
|
||||
_, err = yamlFile.Read(yamlData)
|
||||
if err != nil && configFile != "config.yaml" {
|
||||
return fmt.Errorf("could not load specified config file: " + err.Error())
|
||||
return fmt.Errorf("could not load specified config file: %v", err.Error())
|
||||
}
|
||||
if err != nil {
|
||||
logger.Warning(fmt.Sprintf("Could not load config file '%v', using default settings: %v", configFile, err))
|
||||
}
|
||||
err = ValidateConfig(yamlData)
|
||||
if err != nil {
|
||||
errmsg := "the provided config file failed validation. "
|
||||
errmsg += "If you are seeing this on a config that worked previeously, "
|
||||
errmsg += "this is because v0.6.8 requires a fully validated config to run. "
|
||||
errmsg += "Please review your config for typos and invalid keys which are no longer supported. "
|
||||
errmsg += "visit https://github.com/gtsteffaniak/filebrowser/wiki/Full-Config-Example for more information."
|
||||
logger.Error(errmsg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ValidateConfig(yamlData []byte) error {
|
||||
var err error
|
||||
Config = setDefaults()
|
||||
err = yaml.Unmarshal(yamlData, &Config)
|
||||
err = yaml.NewDecoder(strings.NewReader(string(yamlData)), yaml.DisallowUnknownField()).Decode(&Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshaling YAML data: %v", err)
|
||||
}
|
||||
|
||||
validate := validator.New()
|
||||
err = validate.Struct(Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not validate %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadEnvConfig() {
|
||||
adminPassword, ok := os.LookupEnv("FILEBROWSER_ADMIN_PASSWORD")
|
||||
if ok {
|
||||
logger.Info("Using admin password from FILEBROWSER_ADMIN_PASSWORD environment variable")
|
||||
Config.Auth.AdminPassword = adminPassword
|
||||
}
|
||||
officeSecret, ok := os.LookupEnv("FILEBROWSER_ONLYOFFICE_SECRET")
|
||||
if ok {
|
||||
logger.Info("Using OnlyOffice secret from FILEBROWSER_ONLYOFFICE_SECRET environment variable")
|
||||
Config.Integrations.OnlyOffice.Secret = officeSecret
|
||||
}
|
||||
}
|
||||
|
||||
func setDefaults() Settings {
|
||||
return Settings{
|
||||
Server: Server{
|
||||
EnableThumbnails: true,
|
||||
ResizePreview: false,
|
||||
EnableExec: false,
|
||||
Port: 80,
|
||||
NumImageProcessors: 4,
|
||||
BaseURL: "",
|
||||
@@ -212,13 +242,11 @@ func setDefaults() Settings {
|
||||
AdminUsername: "admin",
|
||||
AdminPassword: "admin",
|
||||
TokenExpirationHours: 2,
|
||||
Signup: false,
|
||||
Recaptcha: Recaptcha{
|
||||
Host: "",
|
||||
},
|
||||
Methods: LoginMethods{
|
||||
PasswordAuth: PasswordAuthConfig{
|
||||
Enabled: true,
|
||||
MinLength: 5,
|
||||
Signup: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -258,10 +286,12 @@ func ConvertToBackendScopes(scopes []users.SourceScope) ([]users.SourceScope, er
|
||||
if !strings.HasPrefix(scope.Scope, "/") {
|
||||
scope.Scope = "/" + scope.Scope
|
||||
}
|
||||
if !strings.HasSuffix(scope.Scope, "/") {
|
||||
scope.Scope = scope.Scope + "/"
|
||||
}
|
||||
// first check if its already a path name and keep it
|
||||
source, ok := Config.Server.SourceMap[scope.Name]
|
||||
if ok {
|
||||
|
||||
newScopes = append(newScopes, users.SourceScope{
|
||||
Name: source.Path, // backend name is path
|
||||
Scope: scope.Scope,
|
||||
@@ -283,7 +313,7 @@ func ConvertToBackendScopes(scopes []users.SourceScope) ([]users.SourceScope, er
|
||||
}
|
||||
|
||||
func ConvertToFrontendScopes(scopes []users.SourceScope) []users.SourceScope {
|
||||
newScopes := make([]users.SourceScope, 0, len(scopes)) // Preserve original order
|
||||
newScopes := []users.SourceScope{}
|
||||
for _, scope := range scopes {
|
||||
if source, ok := Config.Server.SourceMap[scope.Name]; ok {
|
||||
// Replace scope.Name with source.Path while keeping the same Scope value
|
||||
|
||||
@@ -2,7 +2,10 @@ package settings
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestInitialize(t *testing.T) {
|
||||
@@ -37,3 +40,169 @@ func Test_setDefaults(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigLoadChanged(t *testing.T) {
|
||||
defaultConfig := setDefaults()
|
||||
err := loadConfigWithDefaults("./validConfig.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("error loading config file: %v", err)
|
||||
}
|
||||
// Use go-cmp to compare the two structs
|
||||
if diff := cmp.Diff(defaultConfig, Config); diff == "" {
|
||||
t.Errorf("No change when there should have been (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigLoadSpecificValues(t *testing.T) {
|
||||
defaultConfig := setDefaults()
|
||||
err := loadConfigWithDefaults("./validConfig.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("error loading config file: %v", err)
|
||||
}
|
||||
testCases := []struct {
|
||||
fieldName string
|
||||
globalVal interface{}
|
||||
newVal interface{}
|
||||
}{
|
||||
{"Server.Database", Config.Server.Database, defaultConfig.Server.Database},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
if tc.globalVal == tc.newVal {
|
||||
t.Errorf("Differences should have been found:\nConfig.%s: %v \nSetConfig: %v \n", tc.fieldName, tc.globalVal, tc.newVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidConfig(t *testing.T) {
|
||||
configFile := "./invalidConfig.yaml"
|
||||
err := loadConfigWithDefaults(configFile)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error loading config file %s, got nil", configFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidationVarious(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
configFile []byte
|
||||
searchErr string
|
||||
}{
|
||||
{
|
||||
name: "valid minimal config",
|
||||
configFile: []byte(`
|
||||
server:
|
||||
port: 8080
|
||||
sources:
|
||||
- path: "/data"
|
||||
auth:
|
||||
tokenExpirationHours: 24
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
signup: false
|
||||
key: ""
|
||||
adminUsername: "admin"
|
||||
adminPassword: "password"
|
||||
frontend:
|
||||
name: "MyApp"
|
||||
userDefaults:
|
||||
stickySidebar: false
|
||||
darkMode: false
|
||||
lockPassword: false
|
||||
locale: "en"
|
||||
viewMode: "grid"
|
||||
gallerySize: 5
|
||||
singleClick: false
|
||||
showHidden: false
|
||||
dateFormat: false
|
||||
themeColor: "#ffffff"
|
||||
quickDownload: false
|
||||
integrations:
|
||||
office:
|
||||
url: "https://office.example.com"
|
||||
secret: "secret-key"
|
||||
`),
|
||||
searchErr: "",
|
||||
},
|
||||
{
|
||||
name: "missing required server.sources",
|
||||
configFile: []byte(`
|
||||
server:
|
||||
port: 8080
|
||||
`),
|
||||
searchErr: "Field validation for 'Sources' failed on the 'required'",
|
||||
},
|
||||
{
|
||||
name: "missing onlyoffice.secret",
|
||||
configFile: []byte(`
|
||||
server:
|
||||
port: 8080
|
||||
sources:
|
||||
- path: "/data"
|
||||
integrations:
|
||||
office:
|
||||
url: "https://office.example.com"
|
||||
`),
|
||||
searchErr: "Field validation for 'Secret' failed on the 'required'",
|
||||
},
|
||||
{
|
||||
name: "typo: Server with lowercase s",
|
||||
configFile: []byte(`
|
||||
serverx:
|
||||
port: 8080
|
||||
sources:
|
||||
- path: "/data"
|
||||
`),
|
||||
searchErr: "unknown field \"serverx\"",
|
||||
},
|
||||
{
|
||||
name: "typo in auth.methods.password",
|
||||
configFile: []byte(`
|
||||
server:
|
||||
port: 8080
|
||||
sources:
|
||||
- path: "/data"
|
||||
auth:
|
||||
tokenExpirationHours: 24
|
||||
methods:
|
||||
passwrd:
|
||||
enabled: true
|
||||
signup: false
|
||||
key: ""
|
||||
adminUsername: "admin"
|
||||
adminPassword: "password"
|
||||
`),
|
||||
searchErr: "unknown field \"passwrd\"",
|
||||
},
|
||||
{
|
||||
name: "invalid userDefaults",
|
||||
configFile: []byte(`
|
||||
server:
|
||||
sources:
|
||||
- path: "/data"
|
||||
userDefaults:
|
||||
permissions:
|
||||
download: false
|
||||
`),
|
||||
searchErr: "unknown field \"download\"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := ValidateConfig(tc.configFile)
|
||||
if !(err == nil && tc.searchErr == "") {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.searchErr) {
|
||||
t.Fatalf("testcase %v could not find error: '%v', got: %v", tc.name, tc.searchErr, err)
|
||||
}
|
||||
}
|
||||
if err != nil && tc.searchErr == "" {
|
||||
t.Fatalf("testcase %v should not have failed but got: %v", tc.name, err)
|
||||
}
|
||||
if err == nil && tc.searchErr != "" {
|
||||
t.Fatalf("testcase %v should have failed but got: %v", tc.name, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -1,40 +0,0 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestConfigLoadChanged(t *testing.T) {
|
||||
defaultConfig := setDefaults()
|
||||
err := loadConfigWithDefaults("./testingConfig.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("error loading config file: %v", err)
|
||||
}
|
||||
// Use go-cmp to compare the two structs
|
||||
if diff := cmp.Diff(defaultConfig, Config); diff == "" {
|
||||
t.Errorf("No change when there should have been (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigLoadSpecificValues(t *testing.T) {
|
||||
defaultConfig := setDefaults()
|
||||
err := loadConfigWithDefaults("./testingConfig.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("error loading config file: %v", err)
|
||||
}
|
||||
testCases := []struct {
|
||||
fieldName string
|
||||
globalVal interface{}
|
||||
newVal interface{}
|
||||
}{
|
||||
{"Server.Database", Config.Server.Database, defaultConfig.Server.Database},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
if tc.globalVal == tc.newVal {
|
||||
t.Errorf("Differences should have been found:\nConfig.%s: %v \nSetConfig: %v \n", tc.fieldName, tc.globalVal, tc.newVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-30
@@ -23,25 +23,30 @@ type Settings struct {
|
||||
|
||||
type Auth struct {
|
||||
TokenExpirationHours int `json:"tokenExpirationHours"`
|
||||
Recaptcha Recaptcha `json:"recaptcha"`
|
||||
Methods LoginMethods `json:"methods"`
|
||||
Signup bool `json:"signup"`
|
||||
Method string `json:"method"`
|
||||
Key []byte `json:"key"`
|
||||
Key string `json:"key"`
|
||||
AdminUsername string `json:"adminUsername"`
|
||||
AdminPassword string `json:"adminPassword"`
|
||||
AuthMethods []string
|
||||
AuthMethods []string `json:"-"`
|
||||
}
|
||||
|
||||
type LoginMethods struct {
|
||||
ProxyAuth ProxyAuthConfig `json:"proxy"`
|
||||
NoAuth bool `json:"noauth"`
|
||||
PasswordAuth PasswordAuthConfig `json:"password"`
|
||||
ProxyAuth ProxyAuthConfig `json:"proxy" validate:"omitempty"`
|
||||
NoAuth bool `json:"noauth" validate:"omitempty"`
|
||||
PasswordAuth PasswordAuthConfig `json:"password" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type PasswordAuthConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
MinLength int `json:"minLength"`
|
||||
Enabled bool `json:"enabled"`
|
||||
MinLength int `json:"minLength" validate:"omitempty,min=5"`
|
||||
Signup bool `json:"signup" validate:"omitempty"`
|
||||
Recaptcha Recaptcha `json:"recaptcha" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type Recaptcha struct {
|
||||
Host string `json:"host" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
Secret string `json:"secret" validate:"required"`
|
||||
}
|
||||
|
||||
type ProxyAuthConfig struct {
|
||||
@@ -50,12 +55,6 @@ type ProxyAuthConfig struct {
|
||||
Header string `json:"header"`
|
||||
}
|
||||
|
||||
type Recaptcha struct {
|
||||
Host string `json:"host"`
|
||||
Key string `json:"key"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
NumImageProcessors int `json:"numImageProcessors"`
|
||||
Socket string `json:"socket"`
|
||||
@@ -63,32 +62,30 @@ type Server struct {
|
||||
TLSCert string `json:"tlsCert"`
|
||||
EnableThumbnails bool `json:"enableThumbnails"`
|
||||
ResizePreview bool `json:"resizePreview"`
|
||||
EnableExec bool `json:"enableExec"`
|
||||
AuthHook string `json:"authHook"`
|
||||
Port int `json:"port"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Logging []LogConfig `json:"logging"`
|
||||
Database string `json:"database"`
|
||||
Sources []Source `json:"sources"`
|
||||
Sources []Source `json:"sources" validate:"required,dive"`
|
||||
ExternalUrl string `json:"externalUrl"`
|
||||
InternalUrl string `json:"internalUrl"` // used by integrations
|
||||
CacheDir string `json:"cacheDir"`
|
||||
MaxArchiveSizeGB int64 `json:"maxArchiveSize"`
|
||||
// not exposed to config
|
||||
SourceMap map[string]Source `json:"-"` // uses realpath as key
|
||||
NameToSource map[string]Source `json:"-"` // uses name as key
|
||||
DefaultSource Source `json:"-"`
|
||||
SourceMap map[string]Source `json:"-" validate:"omitempty"` // uses realpath as key
|
||||
NameToSource map[string]Source `json:"-" validate:"omitempty"` // uses name as key
|
||||
DefaultSource Source `json:"-" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type Integrations struct {
|
||||
OnlyOffice OnlyOffice `json:"office"`
|
||||
OnlyOffice OnlyOffice `json:"office" validate:"omitempty"`
|
||||
}
|
||||
|
||||
// onlyoffice secret is stored in the local.json file
|
||||
// docker exec <containerID> /var/www/onlyoffice/documentserver/npm/json -f /etc/onlyoffice/documentserver/local.json 'services.CoAuthoring.secret.session.string'
|
||||
type OnlyOffice struct {
|
||||
Url string `json:"url"`
|
||||
Secret string `json:"secret"`
|
||||
Url string `json:"url" validate:"required"`
|
||||
Secret string `json:"secret" validate:"required"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
@@ -100,8 +97,8 @@ type LogConfig struct {
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
Path string `json:"path"` // can be relative, filesystem path
|
||||
Name string `json:"name"` // display name
|
||||
Path string `json:"path" validate:"required"` // file system path. (Can be relative)
|
||||
Name string `json:"name"` // display name
|
||||
Config SourceConfig `json:"config"`
|
||||
}
|
||||
|
||||
@@ -133,9 +130,9 @@ type Frontend struct {
|
||||
}
|
||||
|
||||
type ExternalLink struct {
|
||||
Text string `json:"text"`
|
||||
Text string `json:"text" validate:"required"`
|
||||
Title string `json:"title"`
|
||||
Url string `json:"url"`
|
||||
Url string `json:"url" validate:"required"`
|
||||
}
|
||||
|
||||
// UserDefaults is a type that holds the default values
|
||||
@@ -145,7 +142,6 @@ type UserDefaults struct {
|
||||
DarkMode bool `json:"darkMode"`
|
||||
LockPassword bool `json:"lockPassword"`
|
||||
DisableSettings bool `json:"disableSettings,omitempty"`
|
||||
Scope string `json:"scope"` // deprecated
|
||||
Locale string `json:"locale"`
|
||||
ViewMode string `json:"viewMode"`
|
||||
GallerySize int `json:"gallerySize"`
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
server:
|
||||
numImageProcessors: 4
|
||||
socket: ""
|
||||
tlsKey: ""
|
||||
tlsCert: ""
|
||||
enableThumbnails: false
|
||||
resizePreview: true
|
||||
port: 80
|
||||
baseURL: "/"
|
||||
database: "mydb.db"
|
||||
sources:
|
||||
- path: "."
|
||||
integrations:
|
||||
office:
|
||||
url: "http://localhost:9052"
|
||||
secret: 6H1H7GzUk2alRkNxAPav0W02g8JS5oUd
|
||||
auth:
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
frontend:
|
||||
name: ""
|
||||
disableUsedPercentage: true
|
||||
userDefaults:
|
||||
locale: ""
|
||||
viewMode: ""
|
||||
darkMode: true
|
||||
disableSettings: false
|
||||
singleClick: true
|
||||
permissions:
|
||||
admin: true
|
||||
modify: true
|
||||
share: true
|
||||
api: true
|
||||
dateFormat: false
|
||||
@@ -88,14 +88,6 @@ func (st usersBackend) Update(user *users.User, actorIsAdmin bool, fields ...str
|
||||
return fmt.Errorf("no fields to update")
|
||||
}
|
||||
|
||||
// converting scopes to map of paths intead of names (names can change)
|
||||
if slices.Contains(fields, "scopes") || slices.Contains(fields, "Scopes") {
|
||||
adjustedScopes, err := settings.ConvertToBackendScopes(user.Scopes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.Scopes = adjustedScopes
|
||||
}
|
||||
// Use reflection to access struct fields
|
||||
userFields := reflect.ValueOf(user).Elem() // Get struct value
|
||||
|
||||
|
||||
+289
-25
@@ -913,7 +913,7 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "System settings data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.settingsData"
|
||||
"$ref": "#/definitions/settings.Settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1689,26 +1689,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.settingsData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createUserDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaults": {
|
||||
"$ref": "#/definitions/settings.UserDefaults"
|
||||
},
|
||||
"frontend": {
|
||||
"$ref": "#/definitions/settings.Frontend"
|
||||
},
|
||||
"signup": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"userHomeBasePath": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.signupBody": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1720,8 +1700,38 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Auth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"$ref": "#/definitions/settings.LoginMethods"
|
||||
},
|
||||
"signup": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tokenExpirationHours": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.ExternalLink": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
@@ -1754,6 +1764,264 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.IndexFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fileEndsWith": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Integrations": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"office": {
|
||||
"$ref": "#/definitions/settings.OnlyOffice"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.LogConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"apiLevels": {
|
||||
"type": "string"
|
||||
},
|
||||
"json": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"levels": {
|
||||
"type": "string"
|
||||
},
|
||||
"noColors": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"output": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.LoginMethods": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"noauth": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"password": {
|
||||
"$ref": "#/definitions/settings.PasswordAuthConfig"
|
||||
},
|
||||
"proxy": {
|
||||
"$ref": "#/definitions/settings.ProxyAuthConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.OnlyOffice": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"secret",
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"secret": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.PasswordAuthConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"minLength": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.ProxyAuthConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"createUser": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"header": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Server": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"sources"
|
||||
],
|
||||
"properties": {
|
||||
"baseURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"cacheDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"enableThumbnails": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"externalUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"internalUrl": {
|
||||
"description": "used by integrations",
|
||||
"type": "string"
|
||||
},
|
||||
"logging": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/settings.LogConfig"
|
||||
}
|
||||
},
|
||||
"maxArchiveSize": {
|
||||
"type": "integer"
|
||||
},
|
||||
"numImageProcessors": {
|
||||
"type": "integer"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"resizePreview": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"socket": {
|
||||
"type": "string"
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/settings.Source"
|
||||
}
|
||||
},
|
||||
"tlsCert": {
|
||||
"type": "string"
|
||||
},
|
||||
"tlsKey": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auth": {
|
||||
"$ref": "#/definitions/settings.Auth"
|
||||
},
|
||||
"frontend": {
|
||||
"$ref": "#/definitions/settings.Frontend"
|
||||
},
|
||||
"integrations": {
|
||||
"$ref": "#/definitions/settings.Integrations"
|
||||
},
|
||||
"server": {
|
||||
"$ref": "#/definitions/settings.Server"
|
||||
},
|
||||
"userDefaults": {
|
||||
"$ref": "#/definitions/settings.UserDefaults"
|
||||
},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/settings.UserDefaults"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Source": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"config": {
|
||||
"$ref": "#/definitions/settings.SourceConfig"
|
||||
},
|
||||
"name": {
|
||||
"description": "display name",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "file system path. (Can be relative)",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.SourceConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createUserDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultUserScope": {
|
||||
"description": "default \"\" should match folders under path",
|
||||
"type": "string"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"exclude": {
|
||||
"$ref": "#/definitions/settings.IndexFilter"
|
||||
},
|
||||
"ignoreHidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ignoreZeroSizeFolders": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"include": {
|
||||
"$ref": "#/definitions/settings.IndexFilter"
|
||||
},
|
||||
"indexingInterval": {
|
||||
"type": "integer"
|
||||
},
|
||||
"maxWatchers": {
|
||||
"type": "integer"
|
||||
},
|
||||
"neverWatchPaths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.UserDefaults": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1784,10 +2052,6 @@ const docTemplate = `{
|
||||
"quickDownload": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"scope": {
|
||||
"description": "deprecated",
|
||||
"type": "string"
|
||||
},
|
||||
"showHidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -902,7 +902,7 @@
|
||||
"200": {
|
||||
"description": "System settings data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.settingsData"
|
||||
"$ref": "#/definitions/settings.Settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1678,26 +1678,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.settingsData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createUserDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaults": {
|
||||
"$ref": "#/definitions/settings.UserDefaults"
|
||||
},
|
||||
"frontend": {
|
||||
"$ref": "#/definitions/settings.Frontend"
|
||||
},
|
||||
"signup": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"userHomeBasePath": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.signupBody": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1709,8 +1689,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Auth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"$ref": "#/definitions/settings.LoginMethods"
|
||||
},
|
||||
"signup": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tokenExpirationHours": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.ExternalLink": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"text",
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
@@ -1743,6 +1753,264 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.IndexFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fileEndsWith": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Integrations": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"office": {
|
||||
"$ref": "#/definitions/settings.OnlyOffice"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.LogConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"apiLevels": {
|
||||
"type": "string"
|
||||
},
|
||||
"json": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"levels": {
|
||||
"type": "string"
|
||||
},
|
||||
"noColors": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"output": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.LoginMethods": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"noauth": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"password": {
|
||||
"$ref": "#/definitions/settings.PasswordAuthConfig"
|
||||
},
|
||||
"proxy": {
|
||||
"$ref": "#/definitions/settings.ProxyAuthConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.OnlyOffice": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"secret",
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"secret": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.PasswordAuthConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"minLength": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.ProxyAuthConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"createUser": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"header": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Server": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"sources"
|
||||
],
|
||||
"properties": {
|
||||
"baseURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"cacheDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"enableThumbnails": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"externalUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"internalUrl": {
|
||||
"description": "used by integrations",
|
||||
"type": "string"
|
||||
},
|
||||
"logging": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/settings.LogConfig"
|
||||
}
|
||||
},
|
||||
"maxArchiveSize": {
|
||||
"type": "integer"
|
||||
},
|
||||
"numImageProcessors": {
|
||||
"type": "integer"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"resizePreview": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"socket": {
|
||||
"type": "string"
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/settings.Source"
|
||||
}
|
||||
},
|
||||
"tlsCert": {
|
||||
"type": "string"
|
||||
},
|
||||
"tlsKey": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auth": {
|
||||
"$ref": "#/definitions/settings.Auth"
|
||||
},
|
||||
"frontend": {
|
||||
"$ref": "#/definitions/settings.Frontend"
|
||||
},
|
||||
"integrations": {
|
||||
"$ref": "#/definitions/settings.Integrations"
|
||||
},
|
||||
"server": {
|
||||
"$ref": "#/definitions/settings.Server"
|
||||
},
|
||||
"userDefaults": {
|
||||
"$ref": "#/definitions/settings.UserDefaults"
|
||||
},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/settings.UserDefaults"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.Source": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"config": {
|
||||
"$ref": "#/definitions/settings.SourceConfig"
|
||||
},
|
||||
"name": {
|
||||
"description": "display name",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "file system path. (Can be relative)",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.SourceConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createUserDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultUserScope": {
|
||||
"description": "default \"\" should match folders under path",
|
||||
"type": "string"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"exclude": {
|
||||
"$ref": "#/definitions/settings.IndexFilter"
|
||||
},
|
||||
"ignoreHidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ignoreZeroSizeFolders": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"include": {
|
||||
"$ref": "#/definitions/settings.IndexFilter"
|
||||
},
|
||||
"indexingInterval": {
|
||||
"type": "integer"
|
||||
},
|
||||
"maxWatchers": {
|
||||
"type": "integer"
|
||||
},
|
||||
"neverWatchPaths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.UserDefaults": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1773,10 +2041,6 @@
|
||||
"quickDownload": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"scope": {
|
||||
"description": "deprecated",
|
||||
"type": "string"
|
||||
},
|
||||
"showHidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -86,19 +86,6 @@ definitions:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
http.settingsData:
|
||||
properties:
|
||||
createUserDir:
|
||||
type: boolean
|
||||
defaults:
|
||||
$ref: '#/definitions/settings.UserDefaults'
|
||||
frontend:
|
||||
$ref: '#/definitions/settings.Frontend'
|
||||
signup:
|
||||
type: boolean
|
||||
userHomeBasePath:
|
||||
type: string
|
||||
type: object
|
||||
http.signupBody:
|
||||
properties:
|
||||
password:
|
||||
@@ -106,6 +93,23 @@ definitions:
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
settings.Auth:
|
||||
properties:
|
||||
adminPassword:
|
||||
type: string
|
||||
adminUsername:
|
||||
type: string
|
||||
key:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
methods:
|
||||
$ref: '#/definitions/settings.LoginMethods'
|
||||
signup:
|
||||
type: boolean
|
||||
tokenExpirationHours:
|
||||
type: integer
|
||||
type: object
|
||||
settings.ExternalLink:
|
||||
properties:
|
||||
text:
|
||||
@@ -114,6 +118,9 @@ definitions:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- text
|
||||
- url
|
||||
type: object
|
||||
settings.Frontend:
|
||||
properties:
|
||||
@@ -128,6 +135,176 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
settings.IndexFilter:
|
||||
properties:
|
||||
fileEndsWith:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
files:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
folders:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
settings.Integrations:
|
||||
properties:
|
||||
office:
|
||||
$ref: '#/definitions/settings.OnlyOffice'
|
||||
type: object
|
||||
settings.LogConfig:
|
||||
properties:
|
||||
apiLevels:
|
||||
type: string
|
||||
json:
|
||||
type: boolean
|
||||
levels:
|
||||
type: string
|
||||
noColors:
|
||||
type: boolean
|
||||
output:
|
||||
type: string
|
||||
type: object
|
||||
settings.LoginMethods:
|
||||
properties:
|
||||
noauth:
|
||||
type: boolean
|
||||
password:
|
||||
$ref: '#/definitions/settings.PasswordAuthConfig'
|
||||
proxy:
|
||||
$ref: '#/definitions/settings.ProxyAuthConfig'
|
||||
type: object
|
||||
settings.OnlyOffice:
|
||||
properties:
|
||||
secret:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- secret
|
||||
- url
|
||||
type: object
|
||||
settings.PasswordAuthConfig:
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
minLength:
|
||||
type: integer
|
||||
required:
|
||||
- enabled
|
||||
type: object
|
||||
settings.ProxyAuthConfig:
|
||||
properties:
|
||||
createUser:
|
||||
type: boolean
|
||||
enabled:
|
||||
type: boolean
|
||||
header:
|
||||
type: string
|
||||
required:
|
||||
- enabled
|
||||
type: object
|
||||
settings.Server:
|
||||
properties:
|
||||
baseURL:
|
||||
type: string
|
||||
cacheDir:
|
||||
type: string
|
||||
database:
|
||||
type: string
|
||||
enableThumbnails:
|
||||
type: boolean
|
||||
externalUrl:
|
||||
type: string
|
||||
internalUrl:
|
||||
description: used by integrations
|
||||
type: string
|
||||
logging:
|
||||
items:
|
||||
$ref: '#/definitions/settings.LogConfig'
|
||||
type: array
|
||||
maxArchiveSize:
|
||||
type: integer
|
||||
numImageProcessors:
|
||||
type: integer
|
||||
port:
|
||||
type: integer
|
||||
resizePreview:
|
||||
type: boolean
|
||||
socket:
|
||||
type: string
|
||||
sources:
|
||||
items:
|
||||
$ref: '#/definitions/settings.Source'
|
||||
type: array
|
||||
tlsCert:
|
||||
type: string
|
||||
tlsKey:
|
||||
type: string
|
||||
required:
|
||||
- sources
|
||||
type: object
|
||||
settings.Settings:
|
||||
properties:
|
||||
auth:
|
||||
$ref: '#/definitions/settings.Auth'
|
||||
frontend:
|
||||
$ref: '#/definitions/settings.Frontend'
|
||||
integrations:
|
||||
$ref: '#/definitions/settings.Integrations'
|
||||
server:
|
||||
$ref: '#/definitions/settings.Server'
|
||||
userDefaults:
|
||||
$ref: '#/definitions/settings.UserDefaults'
|
||||
users:
|
||||
items:
|
||||
$ref: '#/definitions/settings.UserDefaults'
|
||||
type: array
|
||||
type: object
|
||||
settings.Source:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/definitions/settings.SourceConfig'
|
||||
name:
|
||||
description: display name
|
||||
type: string
|
||||
path:
|
||||
description: file system path. (Can be relative)
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
settings.SourceConfig:
|
||||
properties:
|
||||
createUserDir:
|
||||
type: boolean
|
||||
defaultEnabled:
|
||||
type: boolean
|
||||
defaultUserScope:
|
||||
description: default "" should match folders under path
|
||||
type: string
|
||||
disabled:
|
||||
type: boolean
|
||||
exclude:
|
||||
$ref: '#/definitions/settings.IndexFilter'
|
||||
ignoreHidden:
|
||||
type: boolean
|
||||
ignoreZeroSizeFolders:
|
||||
type: boolean
|
||||
include:
|
||||
$ref: '#/definitions/settings.IndexFilter'
|
||||
indexingInterval:
|
||||
type: integer
|
||||
maxWatchers:
|
||||
type: integer
|
||||
neverWatchPaths:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
settings.UserDefaults:
|
||||
properties:
|
||||
darkMode:
|
||||
@@ -148,9 +325,6 @@ definitions:
|
||||
$ref: '#/definitions/users.Permissions'
|
||||
quickDownload:
|
||||
type: boolean
|
||||
scope:
|
||||
description: deprecated
|
||||
type: string
|
||||
showHidden:
|
||||
type: boolean
|
||||
singleClick:
|
||||
@@ -892,7 +1066,7 @@ paths:
|
||||
"200":
|
||||
description: System settings data
|
||||
schema:
|
||||
$ref: '#/definitions/http.settingsData'
|
||||
$ref: '#/definitions/settings.Settings'
|
||||
summary: Get system settings
|
||||
tags:
|
||||
- Settings
|
||||
|
||||
@@ -21,13 +21,13 @@ func CheckErr(source string, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateKey() []byte {
|
||||
func GenerateKey() string {
|
||||
b := make([]byte, 64)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
return b
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// CapitalizeFirst returns the input string with the first letter capitalized.
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
{{ if .ReCaptcha }}
|
||||
<script src="{{ .ReCaptchaHost }}/recaptcha/api.js?render=explicit" data-vite-ignore></script>
|
||||
{{ end }}
|
||||
|
||||
<title>{{ if .Name }}{{ .Name }}{{ else }}FileBrowser Quantum{{ end }}</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="256x256" href="{{ .StaticURL }}/img/icons/favicon-256x256.png">
|
||||
|
||||
@@ -5,8 +5,8 @@ const disableUsedPercentage = window.FileBrowser.DisableUsedPercentage;
|
||||
const baseURL = window.FileBrowser.BaseURL;
|
||||
const staticURL = window.FileBrowser.StaticURL;
|
||||
const darkMode = window.FileBrowser.darkMode;
|
||||
const recaptcha = window.FileBrowser.ReCaptcha;
|
||||
const recaptchaKey = window.FileBrowser.ReCaptchaKey;
|
||||
const recaptcha = false;
|
||||
const recaptchaKey = "";
|
||||
const signup = window.FileBrowser.Signup;
|
||||
const version = window.FileBrowser.Version;
|
||||
const commitSHA = window.FileBrowser.CommitSHA;
|
||||
|
||||
@@ -919,7 +919,7 @@ export default {
|
||||
// if control or shift is pressed, do not clear the selection
|
||||
if (this.ctrKeyPressed || event.shiftKey) return;
|
||||
const sameAsBefore = state.selected == this.lastSelected;
|
||||
if (sameAsBefore && !state.multiple && getters.currentPromptName == null) {
|
||||
if (sameAsBefore && !state.multiple && getters.currentPromptName() == null) {
|
||||
mutations.resetSelected();
|
||||
}
|
||||
this.lastSelected = state.selected;
|
||||
|
||||
@@ -39,6 +39,30 @@ test("open from search", async({ page, checkForErrors, context }) => {
|
||||
checkForErrors();
|
||||
})
|
||||
|
||||
test("open nested file in /files dir from search", async({ page, checkForErrors, context }) => {
|
||||
await page.goto("/files/");
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
|
||||
await page.locator('#search').click()
|
||||
await page.locator('#main-input').fill('graham');
|
||||
await expect(page.locator('#result-list')).toHaveCount(1);
|
||||
await page.locator('li[aria-label="graham.xlsx"]').click();
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - graham.xlsx");
|
||||
await expect(page.locator('#previewer')).toContainText('Preview is not available for this file.');
|
||||
checkForErrors();
|
||||
})
|
||||
|
||||
test("open markdown file from search", async({ page, checkForErrors, context }) => {
|
||||
await page.goto("/files/");
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
|
||||
await page.locator('#search').click()
|
||||
await page.locator('#main-input').fill('for testing');
|
||||
await expect(page.locator('#result-list')).toHaveCount(1);
|
||||
await page.locator('li[aria-label="for testing.md"]').click();
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - for testing.md");
|
||||
await expect(page.locator('#markedown-viewer')).toContainText('this is a test');
|
||||
checkForErrors();
|
||||
})
|
||||
|
||||
test("2x copy from listing to new folder", async({ page, checkForErrors, context }) => {
|
||||
await page.goto("/files/");
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
|
||||
|
||||
Whitespace-only changes.
@@ -7,6 +7,8 @@ setup:
|
||||
fi
|
||||
echo "installing swagger needed to generate backend api docs..."
|
||||
go install github.com/swaggo/swag/cmd/swag@latest
|
||||
echo "installing golangci-lint needed to lint backend code..."
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
echo "installing npm requirements for frontend..."
|
||||
cd frontend && npm i
|
||||
|
||||
|
||||
Reference in new issue
Block a user