mirror of
https://github.com/filebrowser/filebrowser.git
synced 2025-08-29 10:30:28 +00:00
Compare commits
No commits in common. "master" and "v2.40.0" have entirely different histories.
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,6 +1,6 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: Report a bug in FileBrowser.
|
description: Report a bug in FileBrowser.
|
||||||
labels: [bug, 'waiting: triage']
|
labels: [bug, triage]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
|
6
.github/workflows/main.yaml
vendored
6
.github/workflows/main.yaml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
go-version: 1.23.0
|
||||||
- run: make lint-backend
|
- run: make lint-backend
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -57,7 +57,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
go-version: 1.23.0
|
||||||
- run: make test-backend
|
- run: make test-backend
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -76,7 +76,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: 1.23.0
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
package_json_file: "frontend/package.json"
|
package_json_file: "frontend/package.json"
|
||||||
|
99
CHANGELOG.md
99
CHANGELOG.md
@ -2,105 +2,6 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
### [2.42.5](https://github.com/filebrowser/filebrowser/compare/v2.42.4...v2.42.5) (2025-08-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* "new folder" button not working in the move and copy popup ([#5368](https://github.com/filebrowser/filebrowser/issues/5368)) ([3107ae4](https://github.com/filebrowser/filebrowser/commit/3107ae41475ae9383c3af414d25a133e549f8087))
|
|
||||||
|
|
||||||
### [2.42.4](https://github.com/filebrowser/filebrowser/compare/v2.42.3...v2.42.4) (2025-08-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add libcap to Dockerfile.s6 ([342b239](https://github.com/filebrowser/filebrowser/commit/342b239ac6f4af2453d5f7aa27f7f0093024dd72))
|
|
||||||
|
|
||||||
### [2.42.3](https://github.com/filebrowser/filebrowser/compare/v2.42.2...v2.42.3) (2025-08-09)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add missing CLI flags for user management ([#5351](https://github.com/filebrowser/filebrowser/issues/5351)) ([cd51a59](https://github.com/filebrowser/filebrowser/commit/cd51a59e72c72560fce7bcc9b12aaf02646b699c))
|
|
||||||
|
|
||||||
### [2.42.2](https://github.com/filebrowser/filebrowser/compare/v2.42.1...v2.42.2) (2025-08-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* show file upload errors ([06e8713](https://github.com/filebrowser/filebrowser/commit/06e8713fa55065d38f02499d3e8d39fc86926cab))
|
|
||||||
|
|
||||||
|
|
||||||
### Refactorings
|
|
||||||
|
|
||||||
* upload progress calculation ([#5350](https://github.com/filebrowser/filebrowser/issues/5350)) ([c14cf86](https://github.com/filebrowser/filebrowser/commit/c14cf86f8304e01d804e01a7eef5ea093627ef37))
|
|
||||||
|
|
||||||
### [2.42.1](https://github.com/filebrowser/filebrowser/compare/v2.42.0...v2.42.1) (2025-07-31)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Translate frontend/src/i18n/en.json in sk ([14ee054](https://github.com/filebrowser/filebrowser/commit/14ee0543599f2ec73b7f5d2dbd8415f47fe592aa))
|
|
||||||
* Translate frontend/src/i18n/en.json in vi ([75baf7c](https://github.com/filebrowser/filebrowser/commit/75baf7ce337671a1045f897ba4a19967a31b1aec))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* directory mode on config init ([4ff6347](https://github.com/filebrowser/filebrowser/commit/4ff634715543b65878943273dff70f340167900b))
|
|
||||||
|
|
||||||
## [2.42.0](https://github.com/filebrowser/filebrowser/compare/v2.41.0...v2.42.0) (2025-07-27)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add Norwegian support ([#5332](https://github.com/filebrowser/filebrowser/issues/5332)) ([25e47c3](https://github.com/filebrowser/filebrowser/commit/25e47c3ce8b35b820b5370a4b8bfdf682bd5ae0b))
|
|
||||||
* select item on file list after navigating back ([#5329](https://github.com/filebrowser/filebrowser/issues/5329)) ([cbeec6d](https://github.com/filebrowser/filebrowser/commit/cbeec6d225691723c4750d7f84122ebb14d662bf))
|
|
||||||
* Translate frontend/src/i18n/en.json in no ([5eb3bf4](https://github.com/filebrowser/filebrowser/commit/5eb3bf40586c2ffc32f4834b5dd59f0eb719c1f7))
|
|
||||||
* Translate frontend/src/i18n/en.json in sk ([07dfdce](https://github.com/filebrowser/filebrowser/commit/07dfdce8e4c371f4ca7480f3cef0bd66ff5c9abb))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* norsk loading ([619f683](https://github.com/filebrowser/filebrowser/commit/619f6837b0d1ec6c654d30f4ecedd6696874721f))
|
|
||||||
|
|
||||||
|
|
||||||
### Reverts
|
|
||||||
|
|
||||||
* Revert "chore(release): 2.42.0" ([d778c19](https://github.com/filebrowser/filebrowser/commit/d778c192ae02c5e73781f7632e3b7276c5811e17))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* bump go version to 1.23.11 ([c7a5c7e](https://github.com/filebrowser/filebrowser/commit/c7a5c7efee2b2bede89ec90bafd1af61c39519ff))
|
|
||||||
* bump to go 1.24 ([c1b0207](https://github.com/filebrowser/filebrowser/commit/c1b0207800b4bb52c8dd459c1d69ce0f785473b6))
|
|
||||||
|
|
||||||
## [2.41.0](https://github.com/filebrowser/filebrowser/compare/v2.40.2...v2.41.0) (2025-07-22)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Allow file and directory creation modes to be configured ([21ad653](https://github.com/filebrowser/filebrowser/commit/21ad653b7eb246c0e95ccdc131f8d59267de7818)), closes [#5316](https://github.com/filebrowser/filebrowser/issues/5316) [#5200](https://github.com/filebrowser/filebrowser/issues/5200)
|
|
||||||
* better error handling for sys kill signals ([1582b8b](https://github.com/filebrowser/filebrowser/commit/1582b8b2cd1c62fa93e60ca9b4e740e940b02e84))
|
|
||||||
|
|
||||||
### [2.40.2](https://github.com/filebrowser/filebrowser/compare/v2.40.1...v2.40.2) (2025-07-17)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Location header on TUS endpoint ([#5302](https://github.com/filebrowser/filebrowser/issues/5302)) ([607f570](https://github.com/filebrowser/filebrowser/commit/607f5708a2484428ab837781a5ef26b8cc3194f4))
|
|
||||||
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
* **deps:** bump vue-i18n from 11.1.9 to 11.1.10 in /frontend ([d61110e](https://github.com/filebrowser/filebrowser/commit/d61110e4d7155a5849557adf3b75dc0191f17e80))
|
|
||||||
|
|
||||||
### [2.40.1](https://github.com/filebrowser/filebrowser/compare/v2.40.0...v2.40.1) (2025-07-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* print correct user on setup ([88f1442](https://github.com/filebrowser/filebrowser/commit/88f144293267260fd4d823e3259783309b1a57b3))
|
|
||||||
|
|
||||||
## [2.40.0](https://github.com/filebrowser/filebrowser/compare/v2.39.0...v2.40.0) (2025-07-13)
|
## [2.40.0](https://github.com/filebrowser/filebrowser/compare/v2.39.0...v2.40.0) (2025-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
FROM ghcr.io/linuxserver/baseimage-alpine:3.22
|
FROM ghcr.io/linuxserver/baseimage-alpine:3.22
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk --no-cache add ca-certificates mailcap jq libcap
|
apk --no-cache add ca-certificates mailcap jq
|
||||||
|
|
||||||
# Make user and create necessary directories
|
# Make user and create necessary directories
|
||||||
RUN mkdir -p /config /database /srv && \
|
RUN mkdir -p /config /database /srv && \
|
||||||
@ -12,8 +12,7 @@ COPY filebrowser /bin/filebrowser
|
|||||||
COPY docker/common/ /
|
COPY docker/common/ /
|
||||||
COPY docker/s6/ /
|
COPY docker/s6/ /
|
||||||
|
|
||||||
RUN chown -R abc:abc /bin/filebrowser /defaults healthcheck.sh && \
|
RUN chown -R abc:abc /bin/filebrowser /defaults healthcheck.sh
|
||||||
setcap 'cap_net_bind_service=+ep' /bin/filebrowser
|
|
||||||
|
|
||||||
# Define healthcheck script
|
# Define healthcheck script
|
||||||
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh
|
HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh
|
||||||
|
10
cmd/cmd.go
10
cmd/cmd.go
@ -1,6 +1,12 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
// Execute executes the commands.
|
// Execute executes the commands.
|
||||||
func Execute() error {
|
func Execute() {
|
||||||
return rootCmd.Execute()
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,18 +15,13 @@ 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 {
|
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||||
s, err := d.store.Settings.Get()
|
s, err := d.store.Settings.Get()
|
||||||
if err != nil {
|
checkErr(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 = d.store.Settings.Save(s)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
printEvents(s.Commands)
|
printEvents(s.Commands)
|
||||||
return nil
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -14,15 +14,10 @@ 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 {
|
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||||
s, err := d.store.Settings.Get()
|
s, err := d.store.Settings.Get()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
evt := mustGetString(cmd.Flags(), "event")
|
||||||
}
|
|
||||||
evt, err := getString(cmd.Flags(), "event")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if evt == "" {
|
if evt == "" {
|
||||||
printEvents(s.Commands)
|
printEvents(s.Commands)
|
||||||
@ -32,6 +27,5 @@ var cmdsLsCmd = &cobra.Command{
|
|||||||
show["after_"+evt] = s.Commands["after_"+evt]
|
show["after_"+evt] = s.Commands["after_"+evt]
|
||||||
printEvents(show)
|
printEvents(show)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -35,31 +35,22 @@ including 'index_end'.`,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||||
s, err := d.store.Settings.Get()
|
s, err := d.store.Settings.Get()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
evt := args[0]
|
evt := args[0]
|
||||||
|
|
||||||
i, err := strconv.Atoi(args[1])
|
i, err := strconv.Atoi(args[1])
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
f := i
|
f := i
|
||||||
if len(args) == 3 {
|
if len(args) == 3 {
|
||||||
f, err = strconv.Atoi(args[2])
|
f, err = strconv.Atoi(args[2])
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = d.store.Settings.Save(s)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
printEvents(s.Commands)
|
printEvents(s.Commands)
|
||||||
return nil
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
105
cmd/config.go
105
cmd/config.go
@ -49,18 +49,11 @@ 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.String("file-mode", fmt.Sprintf("%O", settings.DefaultFileMode), "Mode bits that new files are created with")
|
|
||||||
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) {
|
//nolint:gocyclo
|
||||||
methodStr, err := getString(flags, "auth.method")
|
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther) {
|
||||||
if err != nil {
|
method := settings.AuthMethod(mustGetString(flags, "auth.method"))
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
method := settings.AuthMethod(methodStr)
|
|
||||||
|
|
||||||
var defaultAuther map[string]interface{}
|
var defaultAuther map[string]interface{}
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
@ -71,56 +64,38 @@ func getAuthMethod(flags *pflag.FlagSet, defaults ...interface{}) (settings.Auth
|
|||||||
method = def.AuthMethod
|
method = def.AuthMethod
|
||||||
case auth.Auther:
|
case auth.Auther:
|
||||||
ms, err := json.Marshal(def)
|
ms, err := json.Marshal(def)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(ms, &defaultAuther)
|
err = json.Unmarshal(ms, &defaultAuther)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return method, defaultAuther, nil
|
var auther auth.Auther
|
||||||
}
|
if method == auth.MethodProxyAuth {
|
||||||
|
header := mustGetString(flags, "auth.header")
|
||||||
func getProxyAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
|
||||||
header, err := getString(flags, "auth.header")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if header == "" {
|
if header == "" {
|
||||||
header = defaultAuther["header"].(string)
|
header = defaultAuther["header"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if header == "" {
|
if header == "" {
|
||||||
return nil, nerrors.New("you must set the flag 'auth.header' for method 'proxy'")
|
checkErr(nerrors.New("you must set the flag 'auth.header' for method 'proxy'"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &auth.ProxyAuth{Header: header}, nil
|
auther = &auth.ProxyAuth{Header: header}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNoAuth() auth.Auther {
|
if method == auth.MethodNoAuth {
|
||||||
return &auth.NoAuth{}
|
auther = &auth.NoAuth{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
if method == auth.MethodJSONAuth {
|
||||||
jsonAuth := &auth.JSONAuth{}
|
jsonAuth := &auth.JSONAuth{}
|
||||||
host, err := getString(flags, "recaptcha.host")
|
host := mustGetString(flags, "recaptcha.host")
|
||||||
if err != nil {
|
key := mustGetString(flags, "recaptcha.key")
|
||||||
return nil, err
|
secret := mustGetString(flags, "recaptcha.secret")
|
||||||
}
|
|
||||||
key, err := getString(flags, "recaptcha.key")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
secret, err := getString(flags, "recaptcha.secret")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if key == "" {
|
if key == "" {
|
||||||
if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
|
if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
|
||||||
@ -141,54 +116,31 @@ func getJSONAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (au
|
|||||||
Secret: secret,
|
Secret: secret,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jsonAuth, nil
|
auther = jsonAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHookAuth(flags *pflag.FlagSet, defaultAuther map[string]interface{}) (auth.Auther, error) {
|
if method == auth.MethodHookAuth {
|
||||||
command, err := getString(flags, "auth.command")
|
command := mustGetString(flags, "auth.command")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if command == "" {
|
if command == "" {
|
||||||
command = defaultAuther["command"].(string)
|
command = defaultAuther["command"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if command == "" {
|
if command == "" {
|
||||||
return nil, nerrors.New("you must set the flag 'auth.command' for method 'hook'")
|
checkErr(nerrors.New("you must set the flag 'auth.command' for method 'hook'"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &auth.HookAuth{Command: command}, nil
|
auther = &auth.HookAuth{Command: command}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.AuthMethod, auth.Auther, error) {
|
if auther == nil {
|
||||||
method, defaultAuther, err := getAuthMethod(flags, defaults...)
|
panic(errors.ErrInvalidAuthMethod)
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var auther auth.Auther
|
return method, auther
|
||||||
switch method {
|
|
||||||
case auth.MethodProxyAuth:
|
|
||||||
auther, err = getProxyAuth(flags, defaultAuther)
|
|
||||||
case auth.MethodNoAuth:
|
|
||||||
auther = getNoAuth()
|
|
||||||
case auth.MethodJSONAuth:
|
|
||||||
auther, err = getJSONAuth(flags, defaultAuther)
|
|
||||||
case auth.MethodHookAuth:
|
|
||||||
auther, err = getHookAuth(flags, defaultAuther)
|
|
||||||
default:
|
|
||||||
return "", nil, errors.ErrInvalidAuthMethod
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) {
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return method, auther, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) error {
|
|
||||||
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)
|
||||||
@ -218,8 +170,6 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
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)
|
||||||
fmt.Fprintf(w, "\tFile Creation Mode:\t%O\n", set.FileMode)
|
|
||||||
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, "\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)
|
||||||
@ -236,9 +186,6 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
w.Flush()
|
w.Flush()
|
||||||
|
|
||||||
b, err := json.MarshalIndent(auther, "", " ")
|
b, err := json.MarshalIndent(auther, "", " ")
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -13,19 +13,13 @@ 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 {
|
Run: python(func(_ *cobra.Command, _ []string, d pythonData) {
|
||||||
set, err := d.store.Settings.Get()
|
set, err := d.store.Settings.Get()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
ser, err := d.store.Settings.GetServer()
|
ser, err := d.store.Settings.GetServer()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
printSettings(ser, set, auther)
|
||||||
}
|
|
||||||
return printSettings(ser, set, auther)
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -15,21 +15,15 @@ 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 {
|
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||||
settings, err := d.store.Settings.Get()
|
settings, err := d.store.Settings.Get()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := d.store.Settings.GetServer()
|
server, err := d.store.Settings.GetServer()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
auther, err := d.store.Auth.Get(settings.AuthMethod)
|
auther, err := d.store.Auth.Get(settings.AuthMethod)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := &settingsFile{
|
data := &settingsFile{
|
||||||
Settings: settings,
|
Settings: settings,
|
||||||
@ -38,9 +32,6 @@ and imported again with 'config import' command.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = marshal(args[0], data)
|
err = marshal(args[0], data)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -34,35 +34,26 @@ 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 {
|
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||||
var key []byte
|
var key []byte
|
||||||
var err error
|
|
||||||
if d.hadDB {
|
if d.hadDB {
|
||||||
settings, settingErr := d.store.Settings.Get()
|
settings, err := d.store.Settings.Get()
|
||||||
if settingErr != nil {
|
checkErr(err)
|
||||||
return settingErr
|
|
||||||
}
|
|
||||||
key = settings.Key
|
key = settings.Key
|
||||||
} else {
|
} else {
|
||||||
key = generateKey()
|
key = generateKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
file := settingsFile{}
|
file := settingsFile{}
|
||||||
err = unmarshal(args[0], &file)
|
err := unmarshal(args[0], &file)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file.Settings.Key = key
|
file.Settings.Key = key
|
||||||
err = d.store.Settings.Save(file.Settings)
|
err = d.store.Settings.Save(file.Settings)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.store.Settings.SaveServer(file.Server)
|
err = d.store.Settings.SaveServer(file.Server)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawAuther interface{}
|
var rawAuther interface{}
|
||||||
if filepath.Ext(args[0]) != ".json" {
|
if filepath.Ext(args[0]) != ".json" {
|
||||||
@ -72,51 +63,32 @@ The path must be for a json or yaml file.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var auther auth.Auther
|
var auther auth.Auther
|
||||||
var autherErr error
|
|
||||||
switch file.Settings.AuthMethod {
|
switch file.Settings.AuthMethod {
|
||||||
case auth.MethodJSONAuth:
|
case auth.MethodJSONAuth:
|
||||||
var a interface{}
|
auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth)
|
||||||
a, autherErr = getAuther(auth.JSONAuth{}, rawAuther)
|
|
||||||
auther = a.(*auth.JSONAuth)
|
|
||||||
case auth.MethodNoAuth:
|
case auth.MethodNoAuth:
|
||||||
var a interface{}
|
auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth)
|
||||||
a, autherErr = getAuther(auth.NoAuth{}, rawAuther)
|
|
||||||
auther = a.(*auth.NoAuth)
|
|
||||||
case auth.MethodProxyAuth:
|
case auth.MethodProxyAuth:
|
||||||
var a interface{}
|
auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth)
|
||||||
a, autherErr = getAuther(auth.ProxyAuth{}, rawAuther)
|
|
||||||
auther = a.(*auth.ProxyAuth)
|
|
||||||
case auth.MethodHookAuth:
|
case auth.MethodHookAuth:
|
||||||
var a interface{}
|
auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth)
|
||||||
a, autherErr = getAuther(&auth.HookAuth{}, rawAuther)
|
|
||||||
auther = a.(*auth.HookAuth)
|
|
||||||
default:
|
default:
|
||||||
return errors.New("invalid auth method")
|
checkErr(errors.New("invalid auth method"))
|
||||||
}
|
|
||||||
|
|
||||||
if autherErr != nil {
|
|
||||||
return autherErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Auth.Save(auther)
|
err = d.store.Auth.Save(auther)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return printSettings(file.Server, file.Settings, auther)
|
printSettings(file.Server, file.Settings, auther)
|
||||||
}, pythonConfig{allowNoDB: true}),
|
}, pythonConfig{allowNoDB: true}),
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuther(sample auth.Auther, data interface{}) (interface{}, error) {
|
func getAuther(sample auth.Auther, data interface{}) interface{} {
|
||||||
authType := reflect.TypeOf(sample)
|
authType := reflect.TypeOf(sample)
|
||||||
auther := reflect.New(authType).Interface()
|
auther := reflect.New(authType).Interface()
|
||||||
bytes, err := json.Marshal(data)
|
bytes, err := json.Marshal(data)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(bytes, &auther)
|
err = json.Unmarshal(bytes, &auther)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return nil, err
|
return auther
|
||||||
}
|
|
||||||
return auther, nil
|
|
||||||
}
|
}
|
||||||
|
@ -22,161 +22,52 @@ 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 {
|
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||||
defaults := settings.UserDefaults{}
|
defaults := settings.UserDefaults{}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
err := getUserDefaults(flags, &defaults, true)
|
getUserDefaults(flags, &defaults, true)
|
||||||
if err != nil {
|
authMethod, auther := getAuthentication(flags)
|
||||||
return err
|
|
||||||
}
|
|
||||||
authMethod, auther, err := getAuthentication(flags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := generateKey()
|
|
||||||
|
|
||||||
signup, err := getBool(flags, "signup")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
createUserDir, err := getBool(flags, "create-user-dir")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
minLength, err := getUint(flags, "minimum-password-length")
|
|
||||||
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{
|
s := &settings.Settings{
|
||||||
Key: key,
|
Key: generateKey(),
|
||||||
Signup: signup,
|
Signup: mustGetBool(flags, "signup"),
|
||||||
CreateUserDir: createUserDir,
|
CreateUserDir: mustGetBool(flags, "create-user-dir"),
|
||||||
MinimumPasswordLength: minLength,
|
MinimumPasswordLength: mustGetUint(flags, "minimum-password-length"),
|
||||||
Shell: convertCmdStrToCmdArray(shell),
|
Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
|
||||||
AuthMethod: authMethod,
|
AuthMethod: authMethod,
|
||||||
Defaults: defaults,
|
Defaults: defaults,
|
||||||
Branding: settings.Branding{
|
Branding: settings.Branding{
|
||||||
Name: brandingName,
|
Name: mustGetString(flags, "branding.name"),
|
||||||
DisableExternal: brandingDisableExternal,
|
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
|
||||||
DisableUsedPercentage: brandingDisableUsedPercentage,
|
DisableUsedPercentage: mustGetBool(flags, "branding.disableUsedPercentage"),
|
||||||
Theme: brandingTheme,
|
Theme: mustGetString(flags, "branding.theme"),
|
||||||
Files: brandingFiles,
|
Files: mustGetString(flags, "branding.files"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
ser := &settings.Server{
|
||||||
Address: address,
|
Address: mustGetString(flags, "address"),
|
||||||
Socket: socket,
|
Socket: mustGetString(flags, "socket"),
|
||||||
Root: root,
|
Root: mustGetString(flags, "root"),
|
||||||
BaseURL: baseURL,
|
BaseURL: mustGetString(flags, "baseurl"),
|
||||||
TLSKey: tlsKey,
|
TLSKey: mustGetString(flags, "key"),
|
||||||
TLSCert: cert,
|
TLSCert: mustGetString(flags, "cert"),
|
||||||
Port: port,
|
Port: mustGetString(flags, "port"),
|
||||||
Log: log,
|
Log: mustGetString(flags, "log"),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Settings.Save(s)
|
err := d.store.Settings.Save(s)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.store.Settings.SaveServer(ser)
|
err = d.store.Settings.SaveServer(ser)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.store.Auth.Save(auther)
|
err = d.store.Auth.Save(auther)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf(`
|
fmt.Printf(`
|
||||||
Congratulations! You've set up your database to use with File Browser.
|
Congratulations! You've set up your database to use with File Browser.
|
||||||
Now add your first user via 'filebrowser users add' and then you just
|
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)
|
printSettings(ser, s, auther)
|
||||||
}, pythonConfig{noDB: true}),
|
}, pythonConfig{noDB: true}),
|
||||||
}
|
}
|
||||||
|
@ -16,105 +16,73 @@ 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 {
|
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
set, err := d.store.Settings.Get()
|
set, err := d.store.Settings.Get()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ser, err := d.store.Settings.GetServer()
|
ser, err := d.store.Settings.GetServer()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hasAuth := false
|
hasAuth := false
|
||||||
flags.Visit(func(flag *pflag.Flag) {
|
flags.Visit(func(flag *pflag.Flag) {
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch flag.Name {
|
switch flag.Name {
|
||||||
case "baseurl":
|
case "baseurl":
|
||||||
ser.BaseURL, err = getString(flags, flag.Name)
|
ser.BaseURL = mustGetString(flags, flag.Name)
|
||||||
case "root":
|
case "root":
|
||||||
ser.Root, err = getString(flags, flag.Name)
|
ser.Root = mustGetString(flags, flag.Name)
|
||||||
case "socket":
|
case "socket":
|
||||||
ser.Socket, err = getString(flags, flag.Name)
|
ser.Socket = mustGetString(flags, flag.Name)
|
||||||
case "cert":
|
case "cert":
|
||||||
ser.TLSCert, err = getString(flags, flag.Name)
|
ser.TLSCert = mustGetString(flags, flag.Name)
|
||||||
case "key":
|
case "key":
|
||||||
ser.TLSKey, err = getString(flags, flag.Name)
|
ser.TLSKey = mustGetString(flags, flag.Name)
|
||||||
case "address":
|
case "address":
|
||||||
ser.Address, err = getString(flags, flag.Name)
|
ser.Address = mustGetString(flags, flag.Name)
|
||||||
case "port":
|
case "port":
|
||||||
ser.Port, err = getString(flags, flag.Name)
|
ser.Port = mustGetString(flags, flag.Name)
|
||||||
case "log":
|
case "log":
|
||||||
ser.Log, err = getString(flags, flag.Name)
|
ser.Log = mustGetString(flags, flag.Name)
|
||||||
case "signup":
|
case "signup":
|
||||||
set.Signup, err = getBool(flags, flag.Name)
|
set.Signup = mustGetBool(flags, flag.Name)
|
||||||
case "auth.method":
|
case "auth.method":
|
||||||
hasAuth = true
|
hasAuth = true
|
||||||
case "shell":
|
case "shell":
|
||||||
var shell string
|
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
|
||||||
shell, err = getString(flags, flag.Name)
|
|
||||||
set.Shell = convertCmdStrToCmdArray(shell)
|
|
||||||
case "create-user-dir":
|
case "create-user-dir":
|
||||||
set.CreateUserDir, err = getBool(flags, flag.Name)
|
set.CreateUserDir = mustGetBool(flags, flag.Name)
|
||||||
case "minimum-password-length":
|
case "minimum-password-length":
|
||||||
set.MinimumPasswordLength, err = getUint(flags, flag.Name)
|
set.MinimumPasswordLength = mustGetUint(flags, flag.Name)
|
||||||
case "branding.name":
|
case "branding.name":
|
||||||
set.Branding.Name, err = getString(flags, flag.Name)
|
set.Branding.Name = mustGetString(flags, flag.Name)
|
||||||
case "branding.color":
|
case "branding.color":
|
||||||
set.Branding.Color, err = getString(flags, flag.Name)
|
set.Branding.Color = mustGetString(flags, flag.Name)
|
||||||
case "branding.theme":
|
case "branding.theme":
|
||||||
set.Branding.Theme, err = getString(flags, flag.Name)
|
set.Branding.Theme = mustGetString(flags, flag.Name)
|
||||||
case "branding.disableExternal":
|
case "branding.disableExternal":
|
||||||
set.Branding.DisableExternal, err = getBool(flags, flag.Name)
|
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
|
||||||
case "branding.disableUsedPercentage":
|
case "branding.disableUsedPercentage":
|
||||||
set.Branding.DisableUsedPercentage, err = getBool(flags, flag.Name)
|
set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name)
|
||||||
case "branding.files":
|
case "branding.files":
|
||||||
set.Branding.Files, err = getString(flags, flag.Name)
|
set.Branding.Files = mustGetString(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 {
|
getUserDefaults(flags, &set.Defaults, false)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = getUserDefaults(flags, &set.Defaults, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// read the defaults
|
// read the defaults
|
||||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if there are new flags for existing auth method
|
// check if there are new flags for existing auth method
|
||||||
set.AuthMethod, auther, err = getAuthentication(flags, hasAuth, set, auther)
|
set.AuthMethod, auther = getAuthentication(flags, hasAuth, set, auther)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.store.Auth.Save(auther)
|
err = d.store.Auth.Save(auther)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.store.Settings.Save(set)
|
err = d.store.Settings.Save(set)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = d.store.Settings.SaveServer(ser)
|
err = d.store.Settings.SaveServer(ser)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
printSettings(ser, set, auther)
|
||||||
}
|
|
||||||
|
|
||||||
return printSettings(ser, set, auther)
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
37
cmd/docs.go
37
cmd/docs.go
@ -39,19 +39,12 @@ var docsCmd = &cobra.Command{
|
|||||||
Use: "docs",
|
Use: "docs",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
Run: func(cmd *cobra.Command, _ []string) {
|
||||||
dir, err := getString(cmd.Flags(), "path")
|
dir := mustGetString(cmd.Flags(), "path")
|
||||||
if err != nil {
|
generateDocs(rootCmd, dir)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = generateDocs(rootCmd, dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
names := []string{}
|
names := []string{}
|
||||||
|
|
||||||
err = filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
err := filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
||||||
if err != nil || info.IsDir() {
|
if err != nil || info.IsDir() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -63,38 +56,30 @@ var docsCmd = &cobra.Command{
|
|||||||
names = append(names, info.Name())
|
names = append(names, info.Name())
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
checkErr(err)
|
||||||
printToc(names)
|
printToc(names)
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateDocs(cmd *cobra.Command, dir string) error {
|
func generateDocs(cmd *cobra.Command, dir string) {
|
||||||
for _, c := range cmd.Commands() {
|
for _, c := range cmd.Commands() {
|
||||||
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
|
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err := generateDocs(c, dir)
|
generateDocs(c, dir)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
|
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
|
||||||
filename := filepath.Join(dir, basename)
|
filename := filepath.Join(dir, basename)
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
return generateMarkdown(cmd, f)
|
generateMarkdown(cmd, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateMarkdown(cmd *cobra.Command, w io.Writer) error {
|
func generateMarkdown(cmd *cobra.Command, w io.Writer) {
|
||||||
cmd.InitDefaultHelpCmd()
|
cmd.InitDefaultHelpCmd()
|
||||||
cmd.InitDefaultHelpFlag()
|
cmd.InitDefaultHelpFlag()
|
||||||
|
|
||||||
@ -123,7 +108,7 @@ func generateMarkdown(cmd *cobra.Command, w io.Writer) error {
|
|||||||
|
|
||||||
printOptions(buf, cmd)
|
printOptions(buf, cmd)
|
||||||
_, err := buf.WriteTo(w)
|
_, err := buf.WriteTo(w)
|
||||||
return err
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
|
func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) {
|
||||||
|
@ -17,12 +17,9 @@ var hashCmd = &cobra.Command{
|
|||||||
Short: "Hashes a password",
|
Short: "Hashes a password",
|
||||||
Long: `Hashes a password using bcrypt algorithm.`,
|
Long: `Hashes a password using bcrypt algorithm.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(_ *cobra.Command, args []string) error {
|
Run: func(_ *cobra.Command, args []string) {
|
||||||
pwd, err := users.HashPwd(args[0])
|
pwd, err := users.HashPwd(args[0])
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(pwd)
|
fmt.Println(pwd)
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
129
cmd/root.go
129
cmd/root.go
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
@ -26,7 +25,6 @@ import (
|
|||||||
|
|
||||||
"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"
|
||||||
@ -41,7 +39,6 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
rootCmd.SilenceUsage = true
|
|
||||||
cobra.MousetrapHelpText = ""
|
cobra.MousetrapHelpText = ""
|
||||||
|
|
||||||
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
||||||
@ -115,48 +112,36 @@ 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 {
|
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||||
log.Println(cfgFile)
|
log.Println(cfgFile)
|
||||||
|
|
||||||
if !d.hadDB {
|
if !d.hadDB {
|
||||||
err := quickSetup(cmd.Flags(), *d)
|
quickSetup(cmd.Flags(), d)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// build img service
|
// build img service
|
||||||
workersCount, err := cmd.Flags().GetInt("img-processors")
|
workersCount, err := cmd.Flags().GetInt("img-processors")
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
if workersCount < 1 {
|
if workersCount < 1 {
|
||||||
return errors.New("image resize workers count could not be < 1")
|
log.Fatal("Image resize workers count could not be < 1")
|
||||||
}
|
}
|
||||||
imgSvc := img.New(workersCount)
|
imgSvc := img.New(workersCount)
|
||||||
|
|
||||||
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
||||||
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
if cacheDir != "" {
|
if cacheDir != "" {
|
||||||
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
|
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet
|
||||||
return fmt.Errorf("can't make directory %s: %w", cacheDir, err)
|
log.Fatalf("can't make directory %s: %s", cacheDir, err)
|
||||||
}
|
}
|
||||||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := getRunParams(cmd.Flags(), d.store)
|
server := getRunParams(cmd.Flags(), d.store)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setupLog(server.Log)
|
setupLog(server.Log)
|
||||||
|
|
||||||
root, err := filepath.Abs(server.Root)
|
root, err := filepath.Abs(server.Root)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
server.Root = root
|
server.Root = root
|
||||||
|
|
||||||
adr := server.Address + ":" + server.Port
|
adr := server.Address + ":" + server.Port
|
||||||
@ -166,34 +151,22 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
switch {
|
switch {
|
||||||
case server.Socket != "":
|
case server.Socket != "":
|
||||||
listener, err = net.Listen("unix", server.Socket)
|
listener, err = net.Listen("unix", server.Socket)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
||||||
if err != nil {
|
checkErr(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) //nolint:govet
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
listener, err = tls.Listen("tcp", adr, &tls.Config{
|
listener, err = tls.Listen("tcp", adr, &tls.Config{
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
Certificates: []tls.Certificate{cer}},
|
Certificates: []tls.Certificate{cer}},
|
||||||
)
|
)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
listener, err = net.Listen("tcp", adr)
|
listener, err = net.Listen("tcp", adr)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assetsFs, err := fs.Sub(frontend.Assets(), "dist")
|
assetsFs, err := fs.Sub(frontend.Assets(), "dist")
|
||||||
@ -202,9 +175,7 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
|
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
@ -223,15 +194,8 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
sigc := make(chan os.Signal, 1)
|
sigc := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigc,
|
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||||
os.Interrupt,
|
<-sigc
|
||||||
syscall.SIGHUP,
|
|
||||||
syscall.SIGINT,
|
|
||||||
syscall.SIGTERM,
|
|
||||||
syscall.SIGQUIT,
|
|
||||||
)
|
|
||||||
sig := <-sigc
|
|
||||||
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) //nolint:mnd
|
||||||
defer shutdownRelease()
|
defer shutdownRelease()
|
||||||
@ -240,28 +204,13 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
log.Fatalf("HTTP shutdown error: %v", err)
|
log.Fatalf("HTTP shutdown error: %v", err)
|
||||||
}
|
}
|
||||||
log.Println("Graceful shutdown complete.")
|
log.Println("Graceful shutdown complete.")
|
||||||
|
|
||||||
switch sig {
|
|
||||||
case syscall.SIGHUP:
|
|
||||||
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}),
|
}, pythonConfig{allowNoDB: true}),
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server, error) {
|
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
||||||
server, err := st.Settings.GetServer()
|
server, err := st.Settings.GetServer()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, set := getStringParamB(flags, "root"); set {
|
if val, set := getStringParamB(flags, "root"); set {
|
||||||
server.Root = val
|
server.Root = val
|
||||||
@ -304,7 +253,7 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isAddrSet && isSocketSet {
|
if isAddrSet && isSocketSet {
|
||||||
return nil, errors.New("--socket flag cannot be used with --address, --port, --key nor --cert")
|
checkErr(errors.New("--socket flag cannot be used with --address, --port, --key nor --cert"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not use saved Socket if address was manually set.
|
// Do not use saved Socket if address was manually set.
|
||||||
@ -335,7 +284,7 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) (*settings.Server,
|
|||||||
server.TokenExpirationTime = val
|
server.TokenExpirationTime = val
|
||||||
}
|
}
|
||||||
|
|
||||||
return server, nil
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBoolParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
// getBoolParamB returns a parameter as a string and a boolean to tell if it is different from the default
|
||||||
@ -414,9 +363,7 @@ func setupLog(logMethod string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
||||||
log.Println("Performing quick setup")
|
|
||||||
|
|
||||||
set := &settings.Settings{
|
set := &settings.Settings{
|
||||||
Key: generateKey(),
|
Key: generateKey(),
|
||||||
Signup: false,
|
Signup: false,
|
||||||
@ -457,14 +404,10 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||||||
set.AuthMethod = auth.MethodJSONAuth
|
set.AuthMethod = auth.MethodJSONAuth
|
||||||
err = d.store.Auth.Save(&auth.JSONAuth{})
|
err = d.store.Auth.Save(&auth.JSONAuth{})
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
checkErr(err)
|
||||||
err = d.store.Settings.Save(set)
|
err = d.store.Settings.Save(set)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ser := &settings.Server{
|
ser := &settings.Server{
|
||||||
BaseURL: getStringParam(flags, "baseurl"),
|
BaseURL: getStringParam(flags, "baseurl"),
|
||||||
@ -477,9 +420,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Settings.SaveServer(ser)
|
err = d.store.Settings.SaveServer(ser)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
username := getStringParam(flags, "username")
|
username := getStringParam(flags, "username")
|
||||||
password := getStringParam(flags, "password")
|
password := getStringParam(flags, "password")
|
||||||
@ -487,17 +428,12 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||||||
if password == "" {
|
if password == "" {
|
||||||
var pwd string
|
var pwd string
|
||||||
pwd, err = users.RandomPwd(set.MinimumPasswordLength)
|
pwd, err = users.RandomPwd(set.MinimumPasswordLength)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
log.Println("Randomly generated password for user 'admin':", pwd)
|
||||||
|
|
||||||
log.Printf("User '%s' initialized with randomly generated password: %s\n", username, pwd)
|
|
||||||
password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength)
|
password, err = users.ValidateAndHashPwd(pwd, set.MinimumPasswordLength)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("User '%s' initialize wth user-provided password\n", username)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
@ -513,15 +449,14 @@ 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)
|
err = d.store.Users.Save(user)
|
||||||
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConfig() {
|
func initConfig() {
|
||||||
if cfgFile == "" {
|
if cfgFile == "" {
|
||||||
home, err := homedir.Dir()
|
home, err := homedir.Dir()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
v.AddConfigPath(".")
|
v.AddConfigPath(".")
|
||||||
v.AddConfigPath(home)
|
v.AddConfigPath(home)
|
||||||
v.AddConfigPath("/etc/filebrowser/")
|
v.AddConfigPath("/etc/filebrowser/")
|
||||||
|
@ -40,29 +40,27 @@ including 'index_end'.`,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
i, err := strconv.Atoi(args[0])
|
i, err := strconv.Atoi(args[0])
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
f := i
|
f := i
|
||||||
if len(args) == 2 {
|
if len(args) == 2 {
|
||||||
f, err = strconv.Atoi(args[1])
|
f, err = strconv.Atoi(args[1])
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user := func(u *users.User) error {
|
user := func(u *users.User) {
|
||||||
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)
|
err := d.store.Users.Save(u)
|
||||||
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
global := func(s *settings.Settings) error {
|
global := func(s *settings.Settings) {
|
||||||
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)
|
err := d.store.Settings.Save(s)
|
||||||
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return runRules(d.store, cmd, user, global)
|
runRules(d.store, cmd, user, global)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
49
cmd/rules.go
49
cmd/rules.go
@ -29,62 +29,41 @@ rules.`,
|
|||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User) error, globalFn func(*settings.Settings) error) error {
|
func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User), globalFn func(*settings.Settings)) {
|
||||||
id, err := getUserIdentifier(cmd.Flags())
|
id := getUserIdentifier(cmd.Flags())
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if id != nil {
|
if id != nil {
|
||||||
var user *users.User
|
user, err := st.Users.Get("", id)
|
||||||
user, err = st.Users.Get("", id)
|
checkErr(err)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if usersFn != nil {
|
if usersFn != nil {
|
||||||
err = usersFn(user)
|
usersFn(user)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printRules(user.Rules, id)
|
printRules(user.Rules, id)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := st.Settings.Get()
|
s, err := st.Settings.Get()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if globalFn != nil {
|
if globalFn != nil {
|
||||||
err = globalFn(s)
|
globalFn(s)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printRules(s.Rules, id)
|
printRules(s.Rules, id)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserIdentifier(flags *pflag.FlagSet) (interface{}, error) {
|
func getUserIdentifier(flags *pflag.FlagSet) interface{} {
|
||||||
id, err := getUint(flags, "id")
|
id := mustGetUint(flags, "id")
|
||||||
if err != nil {
|
username := mustGetString(flags, "username")
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
username, err := getString(flags, "username")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if id != 0 {
|
if id != 0 {
|
||||||
return id, nil
|
return id
|
||||||
} else if username != "" {
|
} else if username != "" {
|
||||||
return username, nil
|
return username
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printRules(rulez []rules.Rule, id interface{}) {
|
func printRules(rulez []rules.Rule, id interface{}) {
|
||||||
|
@ -21,15 +21,9 @@ 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 {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
allow, err := getBool(cmd.Flags(), "allow")
|
allow := mustGetBool(cmd.Flags(), "allow")
|
||||||
if err != nil {
|
regex := mustGetBool(cmd.Flags(), "regex")
|
||||||
return err
|
|
||||||
}
|
|
||||||
regex, err := getBool(cmd.Flags(), "regex")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
exp := args[0]
|
exp := args[0]
|
||||||
|
|
||||||
if regex {
|
if regex {
|
||||||
@ -47,16 +41,18 @@ var rulesAddCmd = &cobra.Command{
|
|||||||
rule.Path = exp
|
rule.Path = exp
|
||||||
}
|
}
|
||||||
|
|
||||||
user := func(u *users.User) error {
|
user := func(u *users.User) {
|
||||||
u.Rules = append(u.Rules, rule)
|
u.Rules = append(u.Rules, rule)
|
||||||
return d.store.Users.Save(u)
|
err := d.store.Users.Save(u)
|
||||||
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
global := func(s *settings.Settings) error {
|
global := func(s *settings.Settings) {
|
||||||
s.Rules = append(s.Rules, rule)
|
s.Rules = append(s.Rules, rule)
|
||||||
return d.store.Settings.Save(s)
|
err := d.store.Settings.Save(s)
|
||||||
|
checkErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return runRules(d.store, cmd, user, global)
|
runRules(d.store, cmd, user, global)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
Run: python(func(cmd *cobra.Command, _ []string, d pythonData) {
|
||||||
return runRules(d.store, cmd, nil, nil)
|
runRules(d.store, cmd, nil, nil)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -21,20 +21,11 @@ var upgradeCmd = &cobra.Command{
|
|||||||
import share links because they are incompatible with
|
import share links because they are incompatible with
|
||||||
this version.`,
|
this version.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
Run: func(cmd *cobra.Command, _ []string) {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
oldDB, err := getString(flags, "old.database")
|
oldDB := mustGetString(flags, "old.database")
|
||||||
if err != nil {
|
oldConf := mustGetString(flags, "old.config")
|
||||||
return err
|
err := importer.Import(oldDB, oldConf, getStringParam(flags, "database"))
|
||||||
}
|
checkErr(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)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
57
cmd/users.go
57
cmd/users.go
@ -77,64 +77,52 @@ func addUserFlags(flags *pflag.FlagSet) {
|
|||||||
flags.String("locale", "en", "locale for users")
|
flags.String("locale", "en", "locale for users")
|
||||||
flags.String("viewMode", string(users.ListViewMode), "view mode for users")
|
flags.String("viewMode", string(users.ListViewMode), "view mode for users")
|
||||||
flags.Bool("singleClick", false, "use single clicks only")
|
flags.Bool("singleClick", false, "use single clicks only")
|
||||||
flags.Bool("dateFormat", false, "use date format (true for absolute time, false for relative)")
|
|
||||||
flags.Bool("hideDotfiles", false, "hide dotfiles")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getViewMode(flags *pflag.FlagSet) (users.ViewMode, error) {
|
func getViewMode(flags *pflag.FlagSet) users.ViewMode {
|
||||||
viewModeStr, err := getString(flags, "viewMode")
|
viewMode := users.ViewMode(mustGetString(flags, "viewMode"))
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
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) + "\"")
|
checkErr(errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\""))
|
||||||
}
|
}
|
||||||
return viewMode, nil
|
return viewMode
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) error {
|
func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all bool) {
|
||||||
var visitErr error
|
|
||||||
visit := func(flag *pflag.Flag) {
|
visit := func(flag *pflag.Flag) {
|
||||||
if visitErr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
switch flag.Name {
|
switch flag.Name {
|
||||||
case "scope":
|
case "scope":
|
||||||
defaults.Scope, err = getString(flags, flag.Name)
|
defaults.Scope = mustGetString(flags, flag.Name)
|
||||||
case "locale":
|
case "locale":
|
||||||
defaults.Locale, err = getString(flags, flag.Name)
|
defaults.Locale = mustGetString(flags, flag.Name)
|
||||||
case "viewMode":
|
case "viewMode":
|
||||||
defaults.ViewMode, err = getViewMode(flags)
|
defaults.ViewMode = getViewMode(flags)
|
||||||
case "singleClick":
|
case "singleClick":
|
||||||
defaults.SingleClick, err = getBool(flags, flag.Name)
|
defaults.SingleClick = mustGetBool(flags, flag.Name)
|
||||||
case "perm.admin":
|
case "perm.admin":
|
||||||
defaults.Perm.Admin, err = getBool(flags, flag.Name)
|
defaults.Perm.Admin = mustGetBool(flags, flag.Name)
|
||||||
case "perm.execute":
|
case "perm.execute":
|
||||||
defaults.Perm.Execute, err = getBool(flags, flag.Name)
|
defaults.Perm.Execute = mustGetBool(flags, flag.Name)
|
||||||
case "perm.create":
|
case "perm.create":
|
||||||
defaults.Perm.Create, err = getBool(flags, flag.Name)
|
defaults.Perm.Create = mustGetBool(flags, flag.Name)
|
||||||
case "perm.rename":
|
case "perm.rename":
|
||||||
defaults.Perm.Rename, err = getBool(flags, flag.Name)
|
defaults.Perm.Rename = mustGetBool(flags, flag.Name)
|
||||||
case "perm.modify":
|
case "perm.modify":
|
||||||
defaults.Perm.Modify, err = getBool(flags, flag.Name)
|
defaults.Perm.Modify = mustGetBool(flags, flag.Name)
|
||||||
case "perm.delete":
|
case "perm.delete":
|
||||||
defaults.Perm.Delete, err = getBool(flags, flag.Name)
|
defaults.Perm.Delete = mustGetBool(flags, flag.Name)
|
||||||
case "perm.share":
|
case "perm.share":
|
||||||
defaults.Perm.Share, err = getBool(flags, flag.Name)
|
defaults.Perm.Share = mustGetBool(flags, flag.Name)
|
||||||
case "perm.download":
|
case "perm.download":
|
||||||
defaults.Perm.Download, err = getBool(flags, flag.Name)
|
defaults.Perm.Download = mustGetBool(flags, flag.Name)
|
||||||
case "commands":
|
case "commands":
|
||||||
defaults.Commands, err = flags.GetStringSlice(flag.Name)
|
commands, err := flags.GetStringSlice(flag.Name)
|
||||||
|
checkErr(err)
|
||||||
|
defaults.Commands = commands
|
||||||
case "sorting.by":
|
case "sorting.by":
|
||||||
defaults.Sorting.By, err = getString(flags, flag.Name)
|
defaults.Sorting.By = mustGetString(flags, flag.Name)
|
||||||
case "sorting.asc":
|
case "sorting.asc":
|
||||||
defaults.Sorting.Asc, err = getBool(flags, flag.Name)
|
defaults.Sorting.Asc = mustGetBool(flags, flag.Name)
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
visitErr = err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,5 +131,4 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
|
|||||||
} else {
|
} else {
|
||||||
flags.Visit(visit)
|
flags.Visit(visit)
|
||||||
}
|
}
|
||||||
return visitErr
|
|
||||||
}
|
}
|
||||||
|
@ -16,69 +16,36 @@ 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 {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
s, err := d.store.Settings.Get()
|
s, err := d.store.Settings.Get()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
||||||
}
|
|
||||||
err = getUserDefaults(cmd.Flags(), &s.Defaults, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
password, err := users.ValidateAndHashPwd(args[1], s.MinimumPasswordLength)
|
password, err := users.ValidateAndHashPwd(args[1], s.MinimumPasswordLength)
|
||||||
if err != nil {
|
checkErr(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,
|
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"),
|
||||||
DateFormat: dateFormat,
|
|
||||||
HideDotfiles: hideDotfiles,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Defaults.Apply(user)
|
s.Defaults.Apply(user)
|
||||||
|
|
||||||
servSettings, err := d.store.Settings.GetServer()
|
servSettings, err := d.store.Settings.GetServer()
|
||||||
if err != nil {
|
checkErr(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 := d.store.Settings.Get()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
|
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
user.Scope = userHome
|
user.Scope = userHome
|
||||||
|
|
||||||
err = d.store.Users.Save(user)
|
err = d.store.Users.Save(user)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
printUsers([]*users.User{user})
|
printUsers([]*users.User{user})
|
||||||
return nil
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -14,16 +14,11 @@ 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 {
|
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||||
list, err := d.store.Users.Gets("")
|
list, err := d.store.Users.Gets("")
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = marshal(args[0], list)
|
err = marshal(args[0], list)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -16,17 +16,17 @@ var usersFindCmd = &cobra.Command{
|
|||||||
Short: "Find a user by username or id",
|
Short: "Find a user by username or id",
|
||||||
Long: `Find a user by username or id. If no flag is set, all users will be printed.`,
|
Long: `Find a user by username or id. If no flag is set, all users will be printed.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: findUsers,
|
Run: findUsers,
|
||||||
}
|
}
|
||||||
|
|
||||||
var usersLsCmd = &cobra.Command{
|
var usersLsCmd = &cobra.Command{
|
||||||
Use: "ls",
|
Use: "ls",
|
||||||
Short: "List all users.",
|
Short: "List all users.",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: findUsers,
|
Run: findUsers,
|
||||||
}
|
}
|
||||||
|
|
||||||
var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
var findUsers = python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||||
var (
|
var (
|
||||||
list []*users.User
|
list []*users.User
|
||||||
user *users.User
|
user *users.User
|
||||||
@ -46,9 +46,6 @@ var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) erro
|
|||||||
list, err = d.store.Users.Gets("")
|
list, err = d.store.Users.Gets("")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
printUsers(list)
|
printUsers(list)
|
||||||
return nil
|
|
||||||
}, pythonConfig{})
|
}, pythonConfig{})
|
||||||
|
@ -25,54 +25,34 @@ 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 {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
fd, err := os.Open(args[0])
|
fd, err := os.Open(args[0])
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
list := []*users.User{}
|
list := []*users.User{}
|
||||||
err = unmarshal(args[0], &list)
|
err = unmarshal(args[0], &list)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range list {
|
for _, user := range list {
|
||||||
err = user.Clean("")
|
err = user.Clean("")
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
replace, err := getBool(cmd.Flags(), "replace")
|
if mustGetBool(cmd.Flags(), "replace") {
|
||||||
if err != nil {
|
oldUsers, err := d.store.Users.Gets("")
|
||||||
return err
|
checkErr(err)
|
||||||
}
|
|
||||||
|
|
||||||
if replace {
|
|
||||||
oldUsers, userImportErr := d.store.Users.Gets("")
|
|
||||||
if userImportErr != nil {
|
|
||||||
return userImportErr
|
|
||||||
}
|
|
||||||
|
|
||||||
err = marshal("users.backup.json", list)
|
err = marshal("users.backup.json", list)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range oldUsers {
|
for _, user := range oldUsers {
|
||||||
err = d.store.Users.Delete(user.ID)
|
err = d.store.Users.Delete(user.ID)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
overwrite, err := getBool(cmd.Flags(), "overwrite")
|
overwrite := mustGetBool(cmd.Flags(), "overwrite")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range list {
|
for _, user := range list {
|
||||||
onDB, err := d.store.Users.Get("", user.ID)
|
onDB, err := d.store.Users.Get("", user.ID)
|
||||||
@ -80,7 +60,7 @@ list or set it to 0.`,
|
|||||||
// User exists in DB.
|
// User exists in DB.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !overwrite {
|
if !overwrite {
|
||||||
return errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered")
|
checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the usernames mismatch, check if there is another one in the DB
|
// If the usernames mismatch, check if there is another one in the DB
|
||||||
@ -88,7 +68,7 @@ list or set it to 0.`,
|
|||||||
// 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 := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
|
||||||
return usernameConflictError(user.Username, conflictuous.ID, user.ID)
|
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -98,11 +78,8 @@ list or set it to 0.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Users.Save(user)
|
err = d.store.Users.Save(user)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ var usersRmCmd = &cobra.Command{
|
|||||||
Short: "Delete a user by username or id",
|
Short: "Delete a user by username or id",
|
||||||
Long: `Delete a user by username or id`,
|
Long: `Delete a user by username or id`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error {
|
Run: python(func(_ *cobra.Command, args []string, d pythonData) {
|
||||||
username, id := parseUsernameOrID(args[0])
|
username, id := parseUsernameOrID(args[0])
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -25,10 +25,7 @@ var usersRmCmd = &cobra.Command{
|
|||||||
err = d.store.Users.Delete(id)
|
err = d.store.Users.Delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println("user deleted successfully")
|
fmt.Println("user deleted successfully")
|
||||||
return nil
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
@ -21,22 +21,14 @@ 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 {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
username, id := parseUsernameOrID(args[0])
|
username, id := parseUsernameOrID(args[0])
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
password, err := getString(flags, "password")
|
password := mustGetString(flags, "password")
|
||||||
if err != nil {
|
newUsername := mustGetString(flags, "username")
|
||||||
return err
|
|
||||||
}
|
|
||||||
newUsername, err := getString(flags, "username")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := d.store.Settings.Get()
|
s, err := d.store.Settings.Get()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
user *users.User
|
user *users.User
|
||||||
@ -48,9 +40,7 @@ options you want to change.`,
|
|||||||
user, err = d.store.Users.Get("", username)
|
user, err = d.store.Users.Get("", username)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults := settings.UserDefaults{
|
defaults := settings.UserDefaults{
|
||||||
Scope: user.Scope,
|
Scope: user.Scope,
|
||||||
@ -61,10 +51,7 @@ options you want to change.`,
|
|||||||
Sorting: user.Sorting,
|
Sorting: user.Sorting,
|
||||||
Commands: user.Commands,
|
Commands: user.Commands,
|
||||||
}
|
}
|
||||||
err = getUserDefaults(flags, &defaults, false)
|
getUserDefaults(flags, &defaults, false)
|
||||||
if err != nil {
|
|
||||||
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,18 +59,7 @@ 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 = mustGetBool(flags, "lockPassword")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
user.DateFormat, err = getBool(flags, "dateFormat")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
user.HideDotfiles, err = getBool(flags, "hideDotfiles")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if newUsername != "" {
|
if newUsername != "" {
|
||||||
user.Username = newUsername
|
user.Username = newUsername
|
||||||
@ -91,16 +67,11 @@ options you want to change.`,
|
|||||||
|
|
||||||
if password != "" {
|
if password != "" {
|
||||||
user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength)
|
user.Password, err = users.ValidateAndHashPwd(password, s.MinimumPasswordLength)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Users.Update(user)
|
err = d.store.Users.Update(user)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
printUsers([]*users.User{user})
|
printUsers([]*users.User{user})
|
||||||
return nil
|
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
70
cmd/utils.go
70
cmd/utils.go
@ -4,11 +4,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
@ -16,57 +14,44 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/files"
|
||||||
"github.com/filebrowser/filebrowser/v2/settings"
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/storage"
|
"github.com/filebrowser/filebrowser/v2/storage"
|
||||||
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
"github.com/filebrowser/filebrowser/v2/storage/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dbPerms = 0640
|
func checkErr(err error) {
|
||||||
|
|
||||||
func returnErr(err error) error {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getString(flags *pflag.FlagSet, flag string) (string, error) {
|
func mustGetString(flags *pflag.FlagSet, flag string) string {
|
||||||
s, err := flags.GetString(flag)
|
s, err := flags.GetString(flag)
|
||||||
return s, returnErr(err)
|
checkErr(err)
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
|
func mustGetBool(flags *pflag.FlagSet, flag string) bool {
|
||||||
s, err := getString(flags, flag)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
b, err := strconv.ParseUint(s, 0, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return fs.FileMode(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBool(flags *pflag.FlagSet, flag string) (bool, error) {
|
|
||||||
b, err := flags.GetBool(flag)
|
b, err := flags.GetBool(flag)
|
||||||
return b, returnErr(err)
|
checkErr(err)
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
|
func mustGetUint(flags *pflag.FlagSet, flag string) uint {
|
||||||
b, err := flags.GetUint(flag)
|
b, err := flags.GetUint(flag)
|
||||||
return b, returnErr(err)
|
checkErr(err)
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateKey() []byte {
|
func generateKey() []byte {
|
||||||
k, err := settings.GenerateKey()
|
k, err := settings.GenerateKey()
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
type cobraFunc func(cmd *cobra.Command, args []string) error
|
type cobraFunc func(cmd *cobra.Command, args []string)
|
||||||
type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error
|
type pythonFunc func(cmd *cobra.Command, args []string, data pythonData)
|
||||||
|
|
||||||
type pythonConfig struct {
|
type pythonConfig struct {
|
||||||
noDB bool
|
noDB bool
|
||||||
@ -76,7 +61,6 @@ type pythonConfig struct {
|
|||||||
type pythonData struct {
|
type pythonData struct {
|
||||||
hadDB bool
|
hadDB bool
|
||||||
store *storage.Storage
|
store *storage.Storage
|
||||||
err error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dbExists(path string) (bool, error) {
|
func dbExists(path string) (bool, error) {
|
||||||
@ -100,8 +84,8 @@ func dbExists(path string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
return func(cmd *cobra.Command, args []string) {
|
||||||
data := &pythonData{hadDB: true}
|
data := pythonData{hadDB: true}
|
||||||
|
|
||||||
path := getStringParam(cmd.Flags(), "database")
|
path := getStringParam(cmd.Flags(), "database")
|
||||||
absPath, err := filepath.Abs(path)
|
absPath, err := filepath.Abs(path)
|
||||||
@ -122,24 +106,18 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
|||||||
|
|
||||||
log.Println("Using database: " + absPath)
|
log.Println("Using database: " + absPath)
|
||||||
data.hadDB = exists
|
data.hadDB = exists
|
||||||
db, err := storm.Open(path, storm.BoltOptions(dbPerms, nil))
|
db, err := storm.Open(path, storm.BoltOptions(files.PermFile, nil))
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
data.store, err = bolt.NewStorage(db)
|
data.store, err = bolt.NewStorage(db)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
fn(cmd, args, data)
|
||||||
}
|
|
||||||
return fn(cmd, args, data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshal(filename string, data interface{}) error {
|
func marshal(filename string, data interface{}) error {
|
||||||
fd, err := os.Create(filename)
|
fd, err := os.Create(filename)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
switch ext := filepath.Ext(filename); ext {
|
switch ext := filepath.Ext(filename); ext {
|
||||||
@ -157,9 +135,7 @@ func marshal(filename string, data interface{}) error {
|
|||||||
|
|
||||||
func unmarshal(filename string, data interface{}) error {
|
func unmarshal(filename string, data interface{}) error {
|
||||||
fd, err := os.Open(filename)
|
fd, err := os.Open(filename)
|
||||||
if err != nil {
|
checkErr(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
switch ext := filepath.Ext(filename); ext {
|
switch ext := filepath.Ext(filename); ext {
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -27,6 +27,9 @@ import (
|
|||||||
"github.com/filebrowser/filebrowser/v2/rules"
|
"github.com/filebrowser/filebrowser/v2/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const PermFile = 0640
|
||||||
|
const PermDir = 0750
|
||||||
|
|
||||||
var (
|
var (
|
||||||
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
||||||
reSubExts = regexp.MustCompile("(?i)(.vtt|.srt|.ass|.ssa)$")
|
reSubExts = regexp.MustCompile("(?i)(.vtt|.srt|.ass|.ssa)$")
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package fileutils
|
package fileutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
@ -9,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Copy copies a file or folder from one place to another.
|
// Copy copies a file or folder from one place to another.
|
||||||
func Copy(afs afero.Fs, src, dst string, fileMode, dirMode fs.FileMode) error {
|
func Copy(fs afero.Fs, src, dst string) error {
|
||||||
if src = path.Clean("/" + src); src == "" {
|
if src = path.Clean("/" + src); src == "" {
|
||||||
return os.ErrNotExist
|
return os.ErrNotExist
|
||||||
}
|
}
|
||||||
@ -27,14 +26,14 @@ func Copy(afs afero.Fs, src, dst string, fileMode, dirMode fs.FileMode) error {
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := afs.Stat(src)
|
info, err := fs.Stat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return CopyDir(afs, src, dst, fileMode, dirMode)
|
return CopyDir(fs, src, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
return CopyFile(afs, src, dst, fileMode, dirMode)
|
return CopyFile(fs, src, dst)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package fileutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
@ -10,20 +9,20 @@ import (
|
|||||||
// CopyDir copies a directory from source to dest and all
|
// CopyDir copies a directory from source to dest and all
|
||||||
// of its sub-directories. It doesn't stop if it finds an error
|
// of its sub-directories. It doesn't stop if it finds an error
|
||||||
// during the copy. Returns an error if any.
|
// during the copy. Returns an error if any.
|
||||||
func CopyDir(afs afero.Fs, source, dest string, fileMode, dirMode fs.FileMode) error {
|
func CopyDir(fs afero.Fs, source, dest string) error {
|
||||||
// Get properties of source.
|
// Get properties of source.
|
||||||
srcinfo, err := afs.Stat(source)
|
srcinfo, err := fs.Stat(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the destination directory.
|
// Create the destination directory.
|
||||||
err = afs.MkdirAll(dest, srcinfo.Mode())
|
err = fs.MkdirAll(dest, srcinfo.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, _ := afs.Open(source)
|
dir, _ := fs.Open(source)
|
||||||
obs, err := dir.Readdir(-1)
|
obs, err := dir.Readdir(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -37,13 +36,13 @@ func CopyDir(afs afero.Fs, source, dest string, fileMode, dirMode fs.FileMode) e
|
|||||||
|
|
||||||
if obj.IsDir() {
|
if obj.IsDir() {
|
||||||
// Create sub-directories, recursively.
|
// Create sub-directories, recursively.
|
||||||
err = CopyDir(afs, fsource, fdest, fileMode, dirMode)
|
err = CopyDir(fs, fsource, fdest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Perform the file copy.
|
// Perform the file copy.
|
||||||
err = CopyFile(afs, fsource, fdest, fileMode, dirMode)
|
err = CopyFile(fs, fsource, fdest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
@ -2,28 +2,29 @@ package fileutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/v2/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MoveFile moves file from src to dst.
|
// MoveFile moves file from src to dst.
|
||||||
// By default the rename filesystem system call is used. If src and dst point to different volumes
|
// By default the rename filesystem system call is used. If src and dst point to different volumes
|
||||||
// the file copy is used as a fallback
|
// the file copy is used as a fallback
|
||||||
func MoveFile(afs afero.Fs, src, dst string, fileMode, dirMode fs.FileMode) error {
|
func MoveFile(fs afero.Fs, src, dst string) error {
|
||||||
if afs.Rename(src, dst) == nil {
|
if fs.Rename(src, dst) == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// fallback
|
// fallback
|
||||||
err := Copy(afs, src, dst, fileMode, dirMode)
|
err := Copy(fs, src, dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = afs.Remove(dst)
|
_ = fs.Remove(dst)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := afs.RemoveAll(src); err != nil {
|
if err := fs.RemoveAll(src); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -31,9 +32,9 @@ func MoveFile(afs afero.Fs, src, dst string, fileMode, dirMode fs.FileMode) erro
|
|||||||
|
|
||||||
// CopyFile copies a file from source to dest and returns
|
// CopyFile copies a file from source to dest and returns
|
||||||
// an error if any.
|
// an error if any.
|
||||||
func CopyFile(afs afero.Fs, source, dest string, fileMode, dirMode fs.FileMode) error {
|
func CopyFile(fs afero.Fs, source, dest string) error {
|
||||||
// Open the source file.
|
// Open the source file.
|
||||||
src, err := afs.Open(source)
|
src, err := fs.Open(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -41,13 +42,13 @@ func CopyFile(afs afero.Fs, source, dest string, fileMode, dirMode fs.FileMode)
|
|||||||
|
|
||||||
// Makes the directory needed to create the dst
|
// Makes the directory needed to create the dst
|
||||||
// file.
|
// file.
|
||||||
err = afs.MkdirAll(filepath.Dir(dest), dirMode)
|
err = fs.MkdirAll(filepath.Dir(dest), files.PermDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the destination file.
|
// Create the destination file.
|
||||||
dst, err := afs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
|
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, files.PermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -60,11 +61,11 @@ func CopyFile(afs afero.Fs, source, dest string, fileMode, dirMode fs.FileMode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy the mode
|
// Copy the mode
|
||||||
info, err := afs.Stat(source)
|
info, err := fs.Stat(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = afs.Chmod(dest, info.Mode())
|
err = fs.Chmod(dest, info.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
import pluginVue from "eslint-plugin-vue";
|
import pluginVue from "eslint-plugin-vue";
|
||||||
import {
|
import vueTsEslintConfig from "@vue/eslint-config-typescript";
|
||||||
defineConfigWithVueTs,
|
|
||||||
vueTsConfigs,
|
|
||||||
} from "@vue/eslint-config-typescript";
|
|
||||||
import prettierConfig from "@vue/eslint-config-prettier";
|
import prettierConfig from "@vue/eslint-config-prettier";
|
||||||
|
|
||||||
export default defineConfigWithVueTs(
|
export default [
|
||||||
{
|
{
|
||||||
name: "app/files-to-lint",
|
name: "app/files-to-lint",
|
||||||
files: ["**/*.{ts,mts,tsx,vue}"],
|
files: ["**/*.{ts,mts,tsx,vue}"],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "app/files-to-ignore",
|
name: "app/files-to-ignore",
|
||||||
ignores: ["**/dist/**", "**/dist-ssr/**", "**/coverage/**"],
|
ignores: ["**/dist/**", "**/dist-ssr/**", "**/coverage/**"],
|
||||||
},
|
},
|
||||||
pluginVue.configs["flat/essential"],
|
|
||||||
vueTsConfigs.recommended,
|
...pluginVue.configs["flat/essential"],
|
||||||
|
...vueTsEslintConfig(),
|
||||||
prettierConfig,
|
prettierConfig,
|
||||||
|
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
// Note: you must disable the base rule as it can report incorrect errors
|
// Note: you must disable the base rule as it can report incorrect errors
|
||||||
|
"no-unused-expressions": "off",
|
||||||
"@typescript-eslint/no-unused-expressions": "off",
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
// TODO: theres too many of these from before ts
|
// TODO: theres too many of these from before ts
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
@ -33,5 +34,5 @@ export default defineConfigWithVueTs(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
];
|
||||||
|
@ -21,9 +21,9 @@
|
|||||||
"@chenfengyuan/vue-number-input": "^2.0.1",
|
"@chenfengyuan/vue-number-input": "^2.0.1",
|
||||||
"@vueuse/core": "^12.5.0",
|
"@vueuse/core": "^12.5.0",
|
||||||
"@vueuse/integrations": "^12.5.0",
|
"@vueuse/integrations": "^12.5.0",
|
||||||
"ace-builds": "^1.43.2",
|
"ace-builds": "^1.37.5",
|
||||||
"core-js": "^3.44.0",
|
"core-js": "^3.40.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.10",
|
||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.2.6",
|
||||||
"epubjs": "^0.3.93",
|
"epubjs": "^0.3.93",
|
||||||
"filesize": "^10.1.1",
|
"filesize": "^10.1.1",
|
||||||
@ -31,46 +31,45 @@
|
|||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^15.0.6",
|
"marked": "^15.0.6",
|
||||||
"material-icons": "^1.13.14",
|
"material-icons": "^1.13.13",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^2.3.1",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"qrcode.vue": "^3.6.0",
|
"qrcode.vue": "^3.4.1",
|
||||||
"tus-js-client": "^4.3.1",
|
"tus-js-client": "^4.3.1",
|
||||||
"utif": "^3.1.0",
|
"utif": "^3.1.0",
|
||||||
"video.js": "^8.23.3",
|
"video.js": "^8.21.0",
|
||||||
"videojs-hotkeys": "^0.2.28",
|
"videojs-hotkeys": "^0.2.28",
|
||||||
"videojs-mobile-ui": "^1.1.1",
|
"videojs-mobile-ui": "^1.1.1",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.4.21",
|
||||||
"vue-final-modal": "^4.5.5",
|
"vue-final-modal": "^4.5.4",
|
||||||
"vue-i18n": "^11.1.10",
|
"vue-i18n": "^11.1.2",
|
||||||
"vue-lazyload": "^3.0.0",
|
"vue-lazyload": "^3.0.0",
|
||||||
"vue-reader": "^1.2.17",
|
"vue-reader": "^1.2.17",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.3.0",
|
||||||
"vue-toastification": "^2.0.0-rc.5"
|
"vue-toastification": "^2.0.0-rc.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
"@intlify/unplugin-vue-i18n": "^6.0.3",
|
||||||
"@playwright/test": "^1.54.1",
|
"@playwright/test": "^1.50.0",
|
||||||
"@tsconfig/node22": "^22.0.2",
|
"@tsconfig/node22": "^22.0.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^22.10.10",
|
"@types/node": "^22.10.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
||||||
"@vitejs/plugin-legacy": "^6.0.0",
|
"@vitejs/plugin-legacy": "^6.0.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.3.0",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.19",
|
||||||
"concurrently": "^9.2.0",
|
"concurrently": "^9.1.2",
|
||||||
"eslint": "^9.31.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"eslint-plugin-prettier": "^5.5.1",
|
|
||||||
"eslint-plugin-vue": "^9.24.0",
|
"eslint-plugin-vue": "^9.24.0",
|
||||||
"jsdom": "^26.1.0",
|
"jsdom": "^26.0.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.4.2",
|
||||||
"terser": "^5.43.1",
|
"terser": "^5.37.0",
|
||||||
"vite": "^6.1.6",
|
"vite": "^6.1.6",
|
||||||
"vite-plugin-compression2": "^1.0.0",
|
"vite-plugin-compression2": "^1.0.0",
|
||||||
"vue-tsc": "^2.2.0"
|
"vue-tsc": "^2.2.0"
|
||||||
|
1082
frontend/pnpm-lock.yaml
generated
1082
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ export default async function search(base: string, query: string) {
|
|||||||
|
|
||||||
let data = await res.json();
|
let data = await res.json();
|
||||||
|
|
||||||
data = data.map((item: ResourceItem & { dir: boolean }) => {
|
data = data.map((item: UploadItem) => {
|
||||||
item.url = `/files${base}` + url.encodePath(item.path);
|
item.url = `/files${base}` + url.encodePath(item.path);
|
||||||
|
|
||||||
if (item.dir) {
|
if (item.dir) {
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import * as tus from "tus-js-client";
|
import * as tus from "tus-js-client";
|
||||||
import { baseURL, tusEndpoint, tusSettings, origin } from "@/utils/constants";
|
import { baseURL, tusEndpoint, tusSettings, origin } from "@/utils/constants";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { useUploadStore } from "@/stores/upload";
|
||||||
import { removePrefix } from "@/api/utils";
|
import { removePrefix } from "@/api/utils";
|
||||||
|
|
||||||
const RETRY_BASE_DELAY = 1000;
|
const RETRY_BASE_DELAY = 1000;
|
||||||
const RETRY_MAX_DELAY = 20000;
|
const RETRY_MAX_DELAY = 20000;
|
||||||
const CURRENT_UPLOAD_LIST: { [key: string]: tus.Upload } = {};
|
const SPEED_UPDATE_INTERVAL = 1000;
|
||||||
|
const ALPHA = 0.2;
|
||||||
|
const ONE_MINUS_ALPHA = 1 - ALPHA;
|
||||||
|
const RECENT_SPEEDS_LIMIT = 5;
|
||||||
|
const MB_DIVISOR = 1024 * 1024;
|
||||||
|
const CURRENT_UPLOAD_LIST: CurrentUploadList = {};
|
||||||
|
|
||||||
export async function upload(
|
export async function upload(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
@ -49,35 +55,48 @@ export async function upload(
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
onError: function (error: Error | tus.DetailedError) {
|
onError: function (error) {
|
||||||
delete CURRENT_UPLOAD_LIST[filePath];
|
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||||
|
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||||
if (error.message === "Upload aborted") {
|
|
||||||
return reject(error);
|
|
||||||
}
|
}
|
||||||
|
delete CURRENT_UPLOAD_LIST[filePath];
|
||||||
const message =
|
reject(new Error(`Upload failed: ${error.message}`));
|
||||||
error instanceof tus.DetailedError
|
|
||||||
? error.originalResponse === null
|
|
||||||
? "000 No connection"
|
|
||||||
: error.originalResponse.getBody()
|
|
||||||
: "Upload failed";
|
|
||||||
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
reject(new Error(message));
|
|
||||||
},
|
},
|
||||||
onProgress: function (bytesUploaded) {
|
onProgress: function (bytesUploaded) {
|
||||||
|
const fileData = CURRENT_UPLOAD_LIST[filePath];
|
||||||
|
fileData.currentBytesUploaded = bytesUploaded;
|
||||||
|
|
||||||
|
if (!fileData.hasStarted) {
|
||||||
|
fileData.hasStarted = true;
|
||||||
|
fileData.lastProgressTimestamp = Date.now();
|
||||||
|
|
||||||
|
fileData.interval = window.setInterval(() => {
|
||||||
|
calcProgress(filePath);
|
||||||
|
}, SPEED_UPDATE_INTERVAL);
|
||||||
|
}
|
||||||
if (typeof onupload === "function") {
|
if (typeof onupload === "function") {
|
||||||
onupload({ loaded: bytesUploaded });
|
onupload({ loaded: bytesUploaded });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSuccess: function () {
|
onSuccess: function () {
|
||||||
|
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||||
|
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||||
|
}
|
||||||
delete CURRENT_UPLOAD_LIST[filePath];
|
delete CURRENT_UPLOAD_LIST[filePath];
|
||||||
resolve();
|
resolve();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
CURRENT_UPLOAD_LIST[filePath] = upload;
|
CURRENT_UPLOAD_LIST[filePath] = {
|
||||||
|
upload: upload,
|
||||||
|
recentSpeeds: [],
|
||||||
|
initialBytesUploaded: 0,
|
||||||
|
currentBytesUploaded: 0,
|
||||||
|
currentAverageSpeed: 0,
|
||||||
|
lastProgressTimestamp: null,
|
||||||
|
sumOfRecentSpeeds: 0,
|
||||||
|
hasStarted: false,
|
||||||
|
interval: undefined,
|
||||||
|
};
|
||||||
upload.start();
|
upload.start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -109,11 +128,76 @@ function isTusSupported() {
|
|||||||
return tus.isSupported === true;
|
return tus.isSupported === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeETA(speed?: number) {
|
||||||
|
const state = useUploadStore();
|
||||||
|
if (state.speedMbyte === 0) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
const totalSize = state.sizes.reduce(
|
||||||
|
(acc: number, size: number) => acc + size,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const uploadedSize = state.progress.reduce((a, b) => a + b, 0);
|
||||||
|
const remainingSize = totalSize - uploadedSize;
|
||||||
|
const speedBytesPerSecond = (speed ?? state.speedMbyte) * 1024 * 1024;
|
||||||
|
return remainingSize / speedBytesPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeGlobalSpeedAndETA() {
|
||||||
|
let totalSpeed = 0;
|
||||||
|
let totalCount = 0;
|
||||||
|
|
||||||
|
for (const filePath in CURRENT_UPLOAD_LIST) {
|
||||||
|
totalSpeed += CURRENT_UPLOAD_LIST[filePath].currentAverageSpeed;
|
||||||
|
totalCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalCount === 0) return { speed: 0, eta: Infinity };
|
||||||
|
|
||||||
|
const averageSpeed = totalSpeed / totalCount;
|
||||||
|
const averageETA = computeETA(averageSpeed);
|
||||||
|
|
||||||
|
return { speed: averageSpeed, eta: averageETA };
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcProgress(filePath: string) {
|
||||||
|
const uploadStore = useUploadStore();
|
||||||
|
const fileData = CURRENT_UPLOAD_LIST[filePath];
|
||||||
|
|
||||||
|
const elapsedTime =
|
||||||
|
(Date.now() - (fileData.lastProgressTimestamp ?? 0)) / 1000;
|
||||||
|
const bytesSinceLastUpdate =
|
||||||
|
fileData.currentBytesUploaded - fileData.initialBytesUploaded;
|
||||||
|
const currentSpeed = bytesSinceLastUpdate / MB_DIVISOR / elapsedTime;
|
||||||
|
|
||||||
|
if (fileData.recentSpeeds.length >= RECENT_SPEEDS_LIMIT) {
|
||||||
|
fileData.sumOfRecentSpeeds -= fileData.recentSpeeds.shift() ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileData.recentSpeeds.push(currentSpeed);
|
||||||
|
fileData.sumOfRecentSpeeds += currentSpeed;
|
||||||
|
|
||||||
|
const avgRecentSpeed =
|
||||||
|
fileData.sumOfRecentSpeeds / fileData.recentSpeeds.length;
|
||||||
|
fileData.currentAverageSpeed =
|
||||||
|
ALPHA * avgRecentSpeed + ONE_MINUS_ALPHA * fileData.currentAverageSpeed;
|
||||||
|
|
||||||
|
const { speed, eta } = computeGlobalSpeedAndETA();
|
||||||
|
uploadStore.setUploadSpeed(speed);
|
||||||
|
uploadStore.setETA(eta);
|
||||||
|
|
||||||
|
fileData.initialBytesUploaded = fileData.currentBytesUploaded;
|
||||||
|
fileData.lastProgressTimestamp = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
export function abortAllUploads() {
|
export function abortAllUploads() {
|
||||||
for (const filePath in CURRENT_UPLOAD_LIST) {
|
for (const filePath in CURRENT_UPLOAD_LIST) {
|
||||||
if (CURRENT_UPLOAD_LIST[filePath]) {
|
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||||
CURRENT_UPLOAD_LIST[filePath].abort(true);
|
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||||
CURRENT_UPLOAD_LIST[filePath].options!.onError!(
|
}
|
||||||
|
if (CURRENT_UPLOAD_LIST[filePath].upload) {
|
||||||
|
CURRENT_UPLOAD_LIST[filePath].upload.abort(true);
|
||||||
|
CURRENT_UPLOAD_LIST[filePath].upload.options!.onError!(
|
||||||
new Error("Upload aborted")
|
new Error("Upload aborted")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -192,8 +192,7 @@ export default {
|
|||||||
style["position"] = "absolute";
|
style["position"] = "absolute";
|
||||||
style["top"] = "0";
|
style["top"] = "0";
|
||||||
style["height"] = "100%";
|
style["height"] = "100%";
|
||||||
((style["min-height"] = this.size_px + "px"),
|
(style["min-height"] = this.size_px + "px"), (style["z-index"] = "-1");
|
||||||
(style["z-index"] = "-1"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return style;
|
return style;
|
||||||
|
@ -132,6 +132,7 @@ import {
|
|||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import ProgressBar from "@/components/ProgressBar.vue";
|
import ProgressBar from "@/components/ProgressBar.vue";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
|
import { StatusError } from "@/api/utils.js";
|
||||||
|
|
||||||
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
||||||
|
|
||||||
@ -180,9 +181,13 @@ export default {
|
|||||||
total: prettyBytes(usage.total, { binary: true }),
|
total: prettyBytes(usage.total, { binary: true }),
|
||||||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||||
};
|
};
|
||||||
} finally {
|
} catch (error) {
|
||||||
return Object.assign(this.usage, usageStats);
|
if (error instanceof StatusError && error.is_canceled) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this.$showError(error);
|
||||||
|
}
|
||||||
|
return Object.assign(this.usage, usageStats);
|
||||||
},
|
},
|
||||||
toRoot() {
|
toRoot() {
|
||||||
this.$router.push({ path: "/files" });
|
this.$router.push({ path: "/files" });
|
||||||
|
@ -62,7 +62,6 @@ import FileList from "./FileList.vue";
|
|||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
import { removePrefix } from "@/api/utils";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "copy",
|
name: "copy",
|
||||||
@ -77,7 +76,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState(useFileStore, ["req", "selected"]),
|
...mapState(useFileStore, ["req", "selected"]),
|
||||||
...mapState(useAuthStore, ["user"]),
|
...mapState(useAuthStore, ["user"]),
|
||||||
...mapWritableState(useFileStore, ["reload", "preselect"]),
|
...mapWritableState(useFileStore, ["reload"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
||||||
@ -101,7 +100,6 @@ export default {
|
|||||||
.copy(items, overwrite, rename)
|
.copy(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
buttons.success("copy");
|
buttons.success("copy");
|
||||||
this.preselect = removePrefix(items[0].to);
|
|
||||||
|
|
||||||
if (this.$route.path === this.dest) {
|
if (this.$route.path === this.dest) {
|
||||||
this.reload = true;
|
this.reload = true;
|
||||||
|
@ -48,15 +48,16 @@ export default {
|
|||||||
"selectedCount",
|
"selectedCount",
|
||||||
"req",
|
"req",
|
||||||
"selected",
|
"selected",
|
||||||
|
"currentPrompt",
|
||||||
]),
|
]),
|
||||||
...mapState(useLayoutStore, ["currentPrompt"]),
|
...mapWritableState(useFileStore, ["reload"]),
|
||||||
...mapWritableState(useFileStore, ["reload", "preselect"]),
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
submit: async function () {
|
submit: async function () {
|
||||||
buttons.loading("delete");
|
buttons.loading("delete");
|
||||||
|
|
||||||
|
window.sessionStorage.setItem("modified", "true");
|
||||||
try {
|
try {
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
await api.remove(this.$route.path);
|
await api.remove(this.$route.path);
|
||||||
@ -80,12 +81,6 @@ export default {
|
|||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
|
|
||||||
const nearbyItem =
|
|
||||||
this.req.items[Math.max(0, Math.min(this.selected) - 1)];
|
|
||||||
|
|
||||||
this.preselect = nearbyItem?.path;
|
|
||||||
|
|
||||||
this.reload = true;
|
this.reload = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
buttons.done("delete");
|
buttons.done("delete");
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="focus-prompt"
|
id="focus-prompt"
|
||||||
@click="currentPrompt.confirm"
|
@click="submit"
|
||||||
class="button button--flat button--red"
|
class="button button--flat button--red"
|
||||||
:aria-label="$t('buttons.discardChanges')"
|
:aria-label="$t('buttons.discardChanges')"
|
||||||
:title="$t('buttons.discardChanges')"
|
:title="$t('buttons.discardChanges')"
|
||||||
@ -30,16 +30,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapActions } from "pinia";
|
import { mapActions } from "pinia";
|
||||||
|
import url from "@/utils/url";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "discardEditorChanges",
|
name: "discardEditorChanges",
|
||||||
computed: {
|
|
||||||
...mapState(useLayoutStore, ["currentPrompt"]),
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
|
...mapActions(useFileStore, ["updateRequest"]),
|
||||||
|
submit: async function () {
|
||||||
|
this.updateRequest(null);
|
||||||
|
|
||||||
|
const uri = url.removeLastDir(this.$route.path) + "/";
|
||||||
|
this.$router.push({ path: uri });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -25,10 +25,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapActions } from "pinia";
|
import { mapState } from "pinia";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { useFileStore } from "@/stores/file";
|
import { useFileStore } from "@/stores/file";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
|
||||||
|
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files } from "@/api";
|
import { files } from "@/api";
|
||||||
@ -69,7 +68,6 @@ export default {
|
|||||||
this.abortOngoingNext();
|
this.abortOngoingNext();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["showHover"]),
|
|
||||||
abortOngoingNext() {
|
abortOngoingNext() {
|
||||||
this.nextAbortController.abort();
|
this.nextAbortController.abort();
|
||||||
},
|
},
|
||||||
@ -165,7 +163,7 @@ export default {
|
|||||||
this.$emit("update:selected", this.selected);
|
this.$emit("update:selected", this.selected);
|
||||||
},
|
},
|
||||||
createDir: async function () {
|
createDir: async function () {
|
||||||
this.showHover({
|
this.$store.commit("showHover", {
|
||||||
prompt: "newDir",
|
prompt: "newDir",
|
||||||
action: null,
|
action: null,
|
||||||
confirm: null,
|
confirm: null,
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
import { mapActions, mapState } from "pinia";
|
||||||
import { useFileStore } from "@/stores/file";
|
import { useFileStore } from "@/stores/file";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
@ -63,7 +63,6 @@ import FileList from "./FileList.vue";
|
|||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
import { removePrefix } from "@/api/utils";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "move",
|
name: "move",
|
||||||
@ -78,7 +77,6 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState(useFileStore, ["req", "selected"]),
|
...mapState(useFileStore, ["req", "selected"]),
|
||||||
...mapState(useAuthStore, ["user"]),
|
...mapState(useAuthStore, ["user"]),
|
||||||
...mapWritableState(useFileStore, ["preselect"]),
|
|
||||||
excludedFolders() {
|
excludedFolders() {
|
||||||
return this.selected
|
return this.selected
|
||||||
.filter((idx) => this.req.items[idx].isDir)
|
.filter((idx) => this.req.items[idx].isDir)
|
||||||
@ -106,7 +104,6 @@ export default {
|
|||||||
.move(items, overwrite, rename)
|
.move(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
buttons.success("move");
|
buttons.success("move");
|
||||||
this.preselect = removePrefix(items[0].to);
|
|
||||||
this.$router.push({ path: this.dest });
|
this.$router.push({ path: this.dest });
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
@ -46,7 +46,6 @@ import { useFileStore } from "@/stores/file";
|
|||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import { removePrefix } from "@/api/utils";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "rename",
|
name: "rename",
|
||||||
@ -66,7 +65,7 @@ export default {
|
|||||||
"selectedCount",
|
"selectedCount",
|
||||||
"isListing",
|
"isListing",
|
||||||
]),
|
]),
|
||||||
...mapWritableState(useFileStore, ["reload", "preselect"]),
|
...mapWritableState(useFileStore, ["reload"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
@ -98,6 +97,7 @@ export default {
|
|||||||
newLink =
|
newLink =
|
||||||
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
||||||
|
|
||||||
|
window.sessionStorage.setItem("modified", "true");
|
||||||
try {
|
try {
|
||||||
await api.move([{ from: oldLink, to: newLink }]);
|
await api.move([{ from: oldLink, to: newLink }]);
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
@ -105,8 +105,6 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.preselect = removePrefix(newLink);
|
|
||||||
|
|
||||||
this.reload = true;
|
this.reload = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
this.$showError(e);
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="uploadStore.activeUploads.size > 0"
|
v-if="filesInUploadCount > 0"
|
||||||
class="upload-files"
|
class="upload-files"
|
||||||
v-bind:class="{ closed: !open }"
|
v-bind:class="{ closed: !open }"
|
||||||
>
|
>
|
||||||
<div class="card floating">
|
<div class="card floating">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>
|
<h2>{{ $t("prompts.uploadFiles", { files: filesInUploadCount }) }}</h2>
|
||||||
{{
|
|
||||||
$t("prompts.uploadFiles", {
|
|
||||||
files: uploadStore.pendingUploadCount,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</h2>
|
|
||||||
<div class="upload-info">
|
<div class="upload-info">
|
||||||
<div class="upload-speed">{{ speedMbytes }}/s</div>
|
<div class="upload-speed">{{ uploadSpeed.toFixed(2) }} MB/s</div>
|
||||||
<div class="upload-eta">{{ formattedETA }} remaining</div>
|
<div class="upload-eta">{{ formattedETA }} remaining</div>
|
||||||
<div class="upload-percentage">{{ sentPercent }}% Completed</div>
|
<div class="upload-percentage">
|
||||||
|
{{ getProgressDecimal }}% Completed
|
||||||
|
</div>
|
||||||
<div class="upload-fraction">
|
<div class="upload-fraction">
|
||||||
{{ sentMbytes }} /
|
{{ getTotalProgressBytes }} / {{ getTotalSize }}
|
||||||
{{ totalMbytes }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -45,21 +40,17 @@
|
|||||||
<div class="card-content file-icons">
|
<div class="card-content file-icons">
|
||||||
<div
|
<div
|
||||||
class="file"
|
class="file"
|
||||||
v-for="upload in uploadStore.activeUploads"
|
v-for="file in filesInUpload"
|
||||||
:key="upload.path"
|
:key="file.id"
|
||||||
:data-dir="upload.type === 'dir'"
|
:data-dir="file.isDir"
|
||||||
:data-type="upload.type"
|
:data-type="file.type"
|
||||||
:aria-label="upload.name"
|
:aria-label="file.name"
|
||||||
>
|
>
|
||||||
<div class="file-name">
|
<div class="file-name">
|
||||||
<i class="material-icons"></i> {{ upload.name }}
|
<i class="material-icons"></i> {{ file.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="file-progress">
|
<div class="file-progress">
|
||||||
<div
|
<div v-bind:style="{ width: file.progress + '%' }"></div>
|
||||||
v-bind:style="{
|
|
||||||
width: (upload.sentBytes / upload.totalBytes) * 100 + '%',
|
|
||||||
}"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,105 +58,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script>
|
||||||
import { useFileStore } from "@/stores/file";
|
import { mapState, mapWritableState, mapActions } from "pinia";
|
||||||
import { useUploadStore } from "@/stores/upload";
|
import { useUploadStore } from "@/stores/upload";
|
||||||
import { storeToRefs } from "pinia";
|
import { useFileStore } from "@/stores/file";
|
||||||
import { computed, ref, watch } from "vue";
|
import { abortAllUploads } from "@/api/tus";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import { useI18n } from "vue-i18n";
|
|
||||||
import { partial } from "filesize";
|
|
||||||
|
|
||||||
const { t } = useI18n({});
|
export default {
|
||||||
|
name: "uploadFiles",
|
||||||
const open = ref<boolean>(false);
|
data: function () {
|
||||||
const speed = ref<number>(0);
|
return {
|
||||||
const eta = ref<number>(Infinity);
|
open: false,
|
||||||
|
|
||||||
const fileStore = useFileStore();
|
|
||||||
const uploadStore = useUploadStore();
|
|
||||||
|
|
||||||
const { sentBytes, totalBytes } = storeToRefs(uploadStore);
|
|
||||||
|
|
||||||
const byteToMbyte = partial({ exponent: 2 });
|
|
||||||
|
|
||||||
const sentPercent = computed(() =>
|
|
||||||
((uploadStore.sentBytes / uploadStore.totalBytes) * 100).toFixed(2)
|
|
||||||
);
|
|
||||||
|
|
||||||
const sentMbytes = computed(() => byteToMbyte(uploadStore.sentBytes));
|
|
||||||
const totalMbytes = computed(() => byteToMbyte(uploadStore.totalBytes));
|
|
||||||
const speedMbytes = computed(() => byteToMbyte(speed.value));
|
|
||||||
|
|
||||||
let lastSpeedUpdate: number = 0;
|
|
||||||
let recentSpeeds: number[] = [];
|
|
||||||
|
|
||||||
const calculateSpeed = (sentBytes: number, oldSentBytes: number) => {
|
|
||||||
// Reset the state when the uploads batch is complete
|
|
||||||
if (sentBytes === 0) {
|
|
||||||
lastSpeedUpdate = 0;
|
|
||||||
recentSpeeds = [];
|
|
||||||
|
|
||||||
eta.value = Infinity;
|
|
||||||
speed.value = 0;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const elapsedTime = (Date.now() - (lastSpeedUpdate ?? 0)) / 1000;
|
|
||||||
const bytesSinceLastUpdate = sentBytes - oldSentBytes;
|
|
||||||
const currentSpeed = bytesSinceLastUpdate / elapsedTime;
|
|
||||||
|
|
||||||
recentSpeeds.push(currentSpeed);
|
|
||||||
if (recentSpeeds.length > 5) {
|
|
||||||
recentSpeeds.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
const recentSpeedsAverage =
|
|
||||||
recentSpeeds.reduce((acc, curr) => acc + curr) / recentSpeeds.length;
|
|
||||||
|
|
||||||
// Use the current speed for the first update to avoid smoothing lag
|
|
||||||
if (recentSpeeds.length === 1) {
|
|
||||||
speed.value = currentSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
speed.value = recentSpeedsAverage * 0.2 + speed.value * 0.8;
|
|
||||||
|
|
||||||
lastSpeedUpdate = Date.now();
|
|
||||||
|
|
||||||
calculateEta();
|
|
||||||
};
|
};
|
||||||
|
},
|
||||||
const calculateEta = () => {
|
computed: {
|
||||||
if (speed.value === 0) {
|
...mapState(useUploadStore, [
|
||||||
eta.value = Infinity;
|
"filesInUpload",
|
||||||
|
"filesInUploadCount",
|
||||||
return Infinity;
|
"uploadSpeed",
|
||||||
}
|
"getETA",
|
||||||
|
"getProgress",
|
||||||
const remainingSize = uploadStore.totalBytes - uploadStore.sentBytes;
|
"getProgressDecimal",
|
||||||
const speedBytesPerSecond = speed.value;
|
"getTotalProgressBytes",
|
||||||
|
"getTotalSize",
|
||||||
eta.value = remainingSize / speedBytesPerSecond;
|
]),
|
||||||
};
|
...mapWritableState(useFileStore, ["reload"]),
|
||||||
|
formattedETA() {
|
||||||
watch(sentBytes, calculateSpeed);
|
if (!this.getETA || this.getETA === Infinity) {
|
||||||
|
|
||||||
watch(totalBytes, (totalBytes, oldTotalBytes) => {
|
|
||||||
if (oldTotalBytes !== 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark the start time of a new upload batch
|
|
||||||
lastSpeedUpdate = Date.now();
|
|
||||||
});
|
|
||||||
|
|
||||||
const formattedETA = computed(() => {
|
|
||||||
if (!eta.value || eta.value === Infinity) {
|
|
||||||
return "--:--:--";
|
return "--:--:--";
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalSeconds = eta.value;
|
let totalSeconds = this.getETA;
|
||||||
const hours = Math.floor(totalSeconds / 3600);
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
totalSeconds %= 3600;
|
totalSeconds %= 3600;
|
||||||
const minutes = Math.floor(totalSeconds / 60);
|
const minutes = Math.floor(totalSeconds / 60);
|
||||||
@ -174,19 +98,23 @@ const formattedETA = computed(() => {
|
|||||||
return `${hours.toString().padStart(2, "0")}:${minutes
|
return `${hours.toString().padStart(2, "0")}:${minutes
|
||||||
.toString()
|
.toString()
|
||||||
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
||||||
});
|
},
|
||||||
|
},
|
||||||
const toggle = () => {
|
methods: {
|
||||||
open.value = !open.value;
|
...mapActions(useUploadStore, ["reset"]), // Mapping reset action from upload store
|
||||||
};
|
toggle: function () {
|
||||||
|
this.open = !this.open;
|
||||||
const abortAll = () => {
|
},
|
||||||
if (confirm(t("upload.abortUpload"))) {
|
abortAll() {
|
||||||
|
if (confirm(this.$t("upload.abortUpload"))) {
|
||||||
|
abortAllUploads();
|
||||||
buttons.done("upload");
|
buttons.done("upload");
|
||||||
open.value = false;
|
this.open = false;
|
||||||
uploadStore.abort();
|
this.reset(); // Resetting the upload store state
|
||||||
fileStore.reload = true; // Trigger reload in the file store
|
this.reload = true; // Trigger reload in the file store
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ export default {
|
|||||||
ja: "日本語",
|
ja: "日本語",
|
||||||
ko: "한국어",
|
ko: "한국어",
|
||||||
"nl-be": "Dutch (Belgium)",
|
"nl-be": "Dutch (Belgium)",
|
||||||
no: "Norsk",
|
|
||||||
pl: "Polski",
|
pl: "Polski",
|
||||||
"pt-br": "Português",
|
"pt-br": "Português",
|
||||||
pt: "Português (Brasil)",
|
pt: "Português (Brasil)",
|
||||||
|
@ -96,9 +96,6 @@ main {
|
|||||||
height: 3em;
|
height: 3em;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
border-bottom: 1px solid var(--divider);
|
border-bottom: 1px solid var(--divider);
|
||||||
position: sticky;
|
|
||||||
z-index: 1000;
|
|
||||||
top: 4em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs span,
|
.breadcrumbs span,
|
||||||
|
@ -329,7 +329,6 @@ main .spinner .bounce2 {
|
|||||||
#editor-container {
|
#editor-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
@ -352,8 +351,6 @@ main .spinner .bounce2 {
|
|||||||
#editor-container .breadcrumbs {
|
#editor-container .breadcrumbs {
|
||||||
height: 2.3em;
|
height: 2.3em;
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** RTL - flip and position arrow of path ***/
|
/*** RTL - flip and position arrow of path ***/
|
||||||
|
@ -77,14 +77,14 @@
|
|||||||
"noPreview": "L'aperçu n'est pas disponible pour ce fichier."
|
"noPreview": "L'aperçu n'est pas disponible pour ce fichier."
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"click": "Sélectionner un fichier ou dossier",
|
"click": "Sélectionner un élément",
|
||||||
"ctrl": {
|
"ctrl": {
|
||||||
"click": "Sélectionner plusieurs fichiers ou dossiers",
|
"click": "Sélectionner plusieurs éléments",
|
||||||
"f": "Ouvrir l'invité de recherche",
|
"f": "Ouvrir l'invité de recherche",
|
||||||
"s": "Enregistrer un fichier ou télécharger le dossier actuel"
|
"s": "Télécharger l'élément actuel"
|
||||||
},
|
},
|
||||||
"del": "Supprimer les éléments sélectionnés",
|
"del": "Supprimer les éléments sélectionnés",
|
||||||
"doubleClick": "Ouvrir un fichier ou dossier",
|
"doubleClick": "Ouvrir un élément",
|
||||||
"esc": "Désélectionner et/ou fermer la boîte de dialogue",
|
"esc": "Désélectionner et/ou fermer la boîte de dialogue",
|
||||||
"f1": "Ouvrir l'aide",
|
"f1": "Ouvrir l'aide",
|
||||||
"f2": "Renommer le fichier",
|
"f2": "Renommer le fichier",
|
||||||
@ -98,8 +98,8 @@
|
|||||||
"passwordsDontMatch": "Les mots de passe ne concordent pas",
|
"passwordsDontMatch": "Les mots de passe ne concordent pas",
|
||||||
"signup": "S'inscrire",
|
"signup": "S'inscrire",
|
||||||
"submit": "Se connecter",
|
"submit": "Se connecter",
|
||||||
"username": "Utilisateur·ice",
|
"username": "Utilisateur",
|
||||||
"usernameTaken": "Le nom d'utilisateur·ice est déjà pris",
|
"usernameTaken": "Le nom d'utilisateur est déjà pris",
|
||||||
"wrongCredentials": "Identifiants incorrects !"
|
"wrongCredentials": "Identifiants incorrects !"
|
||||||
},
|
},
|
||||||
"permanent": "Permanent",
|
"permanent": "Permanent",
|
||||||
@ -110,7 +110,7 @@
|
|||||||
"deleteMessageMultiple": "Êtes-vous sûr de vouloir supprimer ces {count} élément(s) ?",
|
"deleteMessageMultiple": "Êtes-vous sûr de vouloir supprimer ces {count} élément(s) ?",
|
||||||
"deleteMessageSingle": "Êtes-vous sûr de vouloir supprimer cet élément ?",
|
"deleteMessageSingle": "Êtes-vous sûr de vouloir supprimer cet élément ?",
|
||||||
"deleteMessageShare": "Êtes-vous sûr de vouloir supprimer ce partage ({path}) ?",
|
"deleteMessageShare": "Êtes-vous sûr de vouloir supprimer ce partage ({path}) ?",
|
||||||
"deleteUser": "Êtes-vous sûr de vouloir supprimer cet·te utilisateur·ice ?",
|
"deleteUser": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?",
|
||||||
"deleteTitle": "Supprimer",
|
"deleteTitle": "Supprimer",
|
||||||
"displayName": "Nom :",
|
"displayName": "Nom :",
|
||||||
"download": "Télécharger",
|
"download": "Télécharger",
|
||||||
@ -120,7 +120,7 @@
|
|||||||
"filesSelected": "{count} éléments sélectionnés",
|
"filesSelected": "{count} éléments sélectionnés",
|
||||||
"lastModified": "Dernière modification",
|
"lastModified": "Dernière modification",
|
||||||
"move": "Déplacer",
|
"move": "Déplacer",
|
||||||
"moveMessage": "Choisissez un nouveau dossier principal pour vos fichier(s)/dossier(s) :",
|
"moveMessage": "Choisissez l'emplacement où déplacer la sélection :",
|
||||||
"newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu.",
|
"newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu.",
|
||||||
"newDir": "Nouveau dossier",
|
"newDir": "Nouveau dossier",
|
||||||
"newDirMessage": "Nom du nouveau dossier :",
|
"newDirMessage": "Nom du nouveau dossier :",
|
||||||
@ -155,12 +155,12 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrateur·ice",
|
"administrator": "Administrateur",
|
||||||
"allowCommands": "Exécuter des commandes",
|
"allowCommands": "Exécuter des commandes",
|
||||||
"allowEdit": "Éditer, renommer et supprimer des fichiers ou des dossiers",
|
"allowEdit": "Éditer, renommer et supprimer des fichiers ou des dossiers",
|
||||||
"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 utilisateurs à s'inscrire",
|
||||||
"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",
|
||||||
@ -169,17 +169,17 @@
|
|||||||
"commandRunner": "Exécuteur de commandes",
|
"commandRunner": "Exécuteur de commandes",
|
||||||
"commandRunnerHelp": "Ici, vous pouvez définir les commandes qui seront exécutées lors des événements nommés précédemments. Vous devez en écrire une par ligne. Les variables d'environnement {0} et {1} seront disponibles, {0} étant relatif à {1}. Pour plus d'informations sur cette fonctionnalité et les variables d'environnement disponibles, veuillez lire la {2}.",
|
"commandRunnerHelp": "Ici, vous pouvez définir les commandes qui seront exécutées lors des événements nommés précédemments. Vous devez en écrire une par ligne. Les variables d'environnement {0} et {1} seront disponibles, {0} étant relatif à {1}. Pour plus d'informations sur cette fonctionnalité et les variables d'environnement disponibles, veuillez lire la {2}.",
|
||||||
"commandsUpdated": "Commandes mises à jour !",
|
"commandsUpdated": "Commandes mises à jour !",
|
||||||
"createUserDir": "Créer automatiquement un dossier pour l'utilisateur·ice",
|
"createUserDir": "Créer automatiquement un dossier pour l'utilisateur",
|
||||||
"minimumPasswordLength": "Taille minimale du mot de passe",
|
"minimumPasswordLength": "Minimum password length",
|
||||||
"tusUploads": "Uploads segmentés",
|
"tusUploads": "Uploads segmentés",
|
||||||
"tusUploadsHelp": "File Browser prend en charge les uploads segmentés afin de permettre une gestion efficace, fiable et reprenable sur des réseaux instables.",
|
"tusUploadsHelp": "File Browser prend en charge les uploads segmentés afin de permettre une gestion efficace, fiable et reprenable sur des réseaux instables.",
|
||||||
"tusUploadsChunkSize": "Taille maximale autorisée par segment (les uploads directs seront utilisés pour les fichiers plus petits). Vous pouvez entrer un entier en octets ou une chaîne telle que 10MB, 1GB, etc.",
|
"tusUploadsChunkSize": "Taille maximale autorisée par segment (les uploads directs seront utilisés pour les fichiers plus petits). Vous pouvez entrer un entier en octets ou une chaîne telle que 10MB, 1GB, etc.",
|
||||||
"tusUploadsRetryCount": "Nombre de tentatives en cas d'échec d'un segment.",
|
"tusUploadsRetryCount": "Nombre de tentatives en cas d'échec d'un segment.",
|
||||||
"userHomeBasePath": "Chemin de base pour les dossiers personnels des utilisateur·ices",
|
"userHomeBasePath": "Chemin de base pour les répertoires personnels des utilisateurs",
|
||||||
"userScopeGenerationPlaceholder": "Le périmètre sera généré automatiquement",
|
"userScopeGenerationPlaceholder": "Le périmètre sera généré automatiquement",
|
||||||
"createUserHomeDirectory": "Créer le dossier personnel de l'utilisateur·ice",
|
"createUserHomeDirectory": "Créer le répertoire personnel de l'utilisateur",
|
||||||
"customStylesheet": "Feuille de style personnalisée",
|
"customStylesheet": "Feuille de style personnalisée",
|
||||||
"defaultUserDescription": "Paramètres par défaut pour les nouveaux utilisateur·ices.",
|
"defaultUserDescription": "Paramètres par défaut pour les nouveaux utilisateurs.",
|
||||||
"disableExternalLinks": "Désactiver les liens externes (sauf la documentation)",
|
"disableExternalLinks": "Désactiver les liens externes (sauf la documentation)",
|
||||||
"disableUsedDiskPercentage": "Désactiver le graphique de pourcentage d'utilisation du disque",
|
"disableUsedDiskPercentage": "Désactiver le graphique de pourcentage d'utilisation du disque",
|
||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
@ -188,12 +188,12 @@
|
|||||||
"executeOnShellDescription": "Par défaut, File Browser exécute les commandes en appelant directement leurs binaires. Si vous voulez les exécuter sur un shell à la place (comme Bash ou PowerShell), vous pouvez le définir ici avec les arguments et les drapeaux requis. S'il est défini, la commande que vous exécutez sera ajoutée en tant qu'argument. Cela s'applique à la fois aux commandes utilisateur et aux crochets d'événements.",
|
"executeOnShellDescription": "Par défaut, File Browser exécute les commandes en appelant directement leurs binaires. Si vous voulez les exécuter sur un shell à la place (comme Bash ou PowerShell), vous pouvez le définir ici avec les arguments et les drapeaux requis. S'il est défini, la commande que vous exécutez sera ajoutée en tant qu'argument. Cela s'applique à la fois aux commandes utilisateur et aux crochets d'événements.",
|
||||||
"globalRules": "Il s'agit d'un ensemble global de règles d'autorisation et d'interdiction. Elles s'appliquent à tous les utilisateurs. Vous pouvez définir des règles spécifiques sur les paramètres de chaque utilisateur pour remplacer celles-ci.",
|
"globalRules": "Il s'agit d'un ensemble global de règles d'autorisation et d'interdiction. Elles s'appliquent à tous les utilisateurs. Vous pouvez définir des règles spécifiques sur les paramètres de chaque utilisateur pour remplacer celles-ci.",
|
||||||
"globalSettings": "Paramètres globaux",
|
"globalSettings": "Paramètres globaux",
|
||||||
"hideDotfiles": "Cacher les fichiers de configuration commançant par un point",
|
"hideDotfiles": "Cacher les fichiers de configuration utilisateur (dotfiles)",
|
||||||
"insertPath": "Insérer le chemin",
|
"insertPath": "Insérer le chemin",
|
||||||
"insertRegex": "Insérer une expression régulière",
|
"insertRegex": "Insérer une expression régulière",
|
||||||
"instanceName": "Nom de l'instance",
|
"instanceName": "Nom de l'instance",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"lockPassword": "Empêcher l'utilisateur·ice de changer son mot de passe",
|
"lockPassword": "Empêcher l'utilisateur de changer son mot de passe",
|
||||||
"newPassword": "Votre nouveau mot de passe",
|
"newPassword": "Votre nouveau mot de passe",
|
||||||
"newPasswordConfirm": "Confirmation du nouveau mot de passe",
|
"newPasswordConfirm": "Confirmation du nouveau mot de passe",
|
||||||
"newUser": "Nouvel utilisateur",
|
"newUser": "Nouvel utilisateur",
|
||||||
@ -210,13 +210,13 @@
|
|||||||
"share": "Partager des fichiers"
|
"share": "Partager des fichiers"
|
||||||
},
|
},
|
||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
"permissionsHelp": "Vous pouvez définir l'utilisateur·ice comme étant administrateur·ice ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur·ice\", toutes les autres options seront automatiquement activées. La gestion des utilisateur·ices est un privilège que seul l'administrateur·ice possède.\n",
|
"permissionsHelp": "Vous pouvez définir l'utilisateur comme étant un administrateur ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur\", toutes les autres options seront automatiquement activées. La gestion des utilisateurs est un privilège que seul l'administrateur possède.\n",
|
||||||
"profileSettings": "Paramètres du profil",
|
"profileSettings": "Paramètres du profil",
|
||||||
"ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers.\n",
|
"ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers",
|
||||||
"ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur·ice.",
|
"ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur",
|
||||||
"rules": "Règles",
|
"rules": "Règles",
|
||||||
"rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur·ice. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur·ice. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur·ice.\n",
|
"rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur.\n",
|
||||||
"scope": "Portée du dossier utilisateur·ice",
|
"scope": "Portée du dossier utilisateur",
|
||||||
"setDateFormat": "Définir le format de la date",
|
"setDateFormat": "Définir le format de la date",
|
||||||
"settingsUpdated": "Les paramètres ont été mis à jour !",
|
"settingsUpdated": "Les paramètres ont été mis à jour !",
|
||||||
"shareDuration": "Durée du partage",
|
"shareDuration": "Durée du partage",
|
||||||
@ -224,21 +224,21 @@
|
|||||||
"shareDeleted": "Partage supprimé !",
|
"shareDeleted": "Partage supprimé !",
|
||||||
"singleClick": "Utiliser un simple clic pour ouvrir les fichiers et les dossiers",
|
"singleClick": "Utiliser un simple clic pour ouvrir les fichiers et les dossiers",
|
||||||
"themes": {
|
"themes": {
|
||||||
"default": "Par défaut du système",
|
"default": "System default",
|
||||||
"dark": "Sombre",
|
"dark": "Sombre",
|
||||||
"light": "Clair",
|
"light": "Clair",
|
||||||
"title": "Thème"
|
"title": "Thème"
|
||||||
},
|
},
|
||||||
"user": "Utilisateur·ice",
|
"user": "Utilisateur",
|
||||||
"userCommands": "Commandes",
|
"userCommands": "Commandes",
|
||||||
"userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur·ice. Exemple :\n",
|
"userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur. Exemple :\n",
|
||||||
"userCreated": "Utilisateur·ice créé !",
|
"userCreated": "Utilisateur créé !",
|
||||||
"userDefaults": "Paramètres par défaut de l'utilisateur.ice",
|
"userDefaults": "Paramètres par défaut de l'utilisateur",
|
||||||
"userDeleted": "Utilisateur·ice supprimé !",
|
"userDeleted": "Utilisateur supprimé !",
|
||||||
"userManagement": "Gestion des utilisateur·ices",
|
"userManagement": "Gestion des utilisateurs",
|
||||||
"userUpdated": "Utilisateur·ice mis à jour !",
|
"userUpdated": "Utilisateur mis à jour !",
|
||||||
"username": "Nom d'utilisateur·ice",
|
"username": "Nom d'utilisateur",
|
||||||
"users": "Utilisateur·ices"
|
"users": "Utilisateurs"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"help": "Aide",
|
"help": "Aide",
|
||||||
|
@ -27,7 +27,6 @@ import("dayjs/locale/vi");
|
|||||||
import("dayjs/locale/zh-cn");
|
import("dayjs/locale/zh-cn");
|
||||||
import("dayjs/locale/zh-tw");
|
import("dayjs/locale/zh-tw");
|
||||||
import("dayjs/locale/cs");
|
import("dayjs/locale/cs");
|
||||||
import("dayjs/locale/nb");
|
|
||||||
|
|
||||||
// All i18n resources specified in the plugin `include` option can be loaded
|
// All i18n resources specified in the plugin `include` option can be loaded
|
||||||
// at once using the import syntax
|
// at once using the import syntax
|
||||||
@ -102,6 +101,7 @@ export function detectLocale() {
|
|||||||
case /^tr\b/.test(locale):
|
case /^tr\b/.test(locale):
|
||||||
locale = "tr";
|
locale = "tr";
|
||||||
break;
|
break;
|
||||||
|
// ua wasnt a valid locale for ukraine
|
||||||
case /^uk\b/.test(locale):
|
case /^uk\b/.test(locale):
|
||||||
locale = "uk";
|
locale = "uk";
|
||||||
break;
|
break;
|
||||||
@ -115,10 +115,6 @@ export function detectLocale() {
|
|||||||
case /^nl-be\b/.test(locale):
|
case /^nl-be\b/.test(locale):
|
||||||
locale = "nl-be";
|
locale = "nl-be";
|
||||||
break;
|
break;
|
||||||
case /^nb\b/.test(locale):
|
|
||||||
case /^no\b/.test(locale):
|
|
||||||
locale = "no";
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
locale = "en";
|
locale = "en";
|
||||||
}
|
}
|
||||||
|
@ -1,266 +0,0 @@
|
|||||||
{
|
|
||||||
"buttons": {
|
|
||||||
"cancel": "Avbryt",
|
|
||||||
"clear": "Fjern",
|
|
||||||
"close": "Lukk",
|
|
||||||
"continue": "Fortsett",
|
|
||||||
"copy": "Kopier",
|
|
||||||
"copyFile": "Fortsett",
|
|
||||||
"copyToClipboard": "Kopier til utklippstavlen",
|
|
||||||
"copyDownloadLinkToClipboard": "Kopier nedlastingslenken til utklippstavlen",
|
|
||||||
"create": "Opprett",
|
|
||||||
"delete": "Slett",
|
|
||||||
"download": "Nedlast",
|
|
||||||
"file": "Fil",
|
|
||||||
"folder": "Mappe",
|
|
||||||
"fullScreen": "Skru på fullskjerm",
|
|
||||||
"hideDotfiles": "Skjul punktfiler",
|
|
||||||
"info": "Info",
|
|
||||||
"more": "Meir",
|
|
||||||
"move": "Flytt",
|
|
||||||
"moveFile": "Flytt Fil",
|
|
||||||
"new": "Ny",
|
|
||||||
"next": "Neste",
|
|
||||||
"ok": "Ok",
|
|
||||||
"permalink": "Få permanent link",
|
|
||||||
"previous": "Tidligere",
|
|
||||||
"preview": "Forhåndsvisning",
|
|
||||||
"publish": "Publiser",
|
|
||||||
"rename": "Gi nytt navn",
|
|
||||||
"replace": "Bytt ut\n ",
|
|
||||||
"reportIssue": "Rapporter problem",
|
|
||||||
"save": "Lagre",
|
|
||||||
"schedule": "Planlegg ",
|
|
||||||
"search": "Søk",
|
|
||||||
"select": "Velg",
|
|
||||||
"selectMultiple": "Velg Fleire",
|
|
||||||
"share": "Del",
|
|
||||||
"shell": "Skru på shell",
|
|
||||||
"submit": "Send",
|
|
||||||
"switchView": "Skift visning",
|
|
||||||
"toggleSidebar": "Skru på sidebar",
|
|
||||||
"update": "Opptater",
|
|
||||||
"upload": "Last opp",
|
|
||||||
"openFile": "Open file",
|
|
||||||
"discardChanges": "Slett"
|
|
||||||
},
|
|
||||||
"download": {
|
|
||||||
"downloadFile": "Nedlast filen",
|
|
||||||
"downloadFolder": "Nedlast mappen",
|
|
||||||
"downloadSelected": "Nedlast merket"
|
|
||||||
},
|
|
||||||
"upload": {
|
|
||||||
"abortUpload": "Er du sikker på at du ønsker å avbryte?"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"forbidden": "Du har ikkje tilgang til denne filen.",
|
|
||||||
"internal": "Noko gikk virkelig galt.",
|
|
||||||
"notFound": "Denne lokasjonen kan ikkje bli nådd.",
|
|
||||||
"connection": "Denne serveren kan ikkje nås."
|
|
||||||
},
|
|
||||||
"files": {
|
|
||||||
"body": "Kropp",
|
|
||||||
"closePreview": "Lukk forhandsvisning",
|
|
||||||
"files": "Filer",
|
|
||||||
"folders": "Mappe",
|
|
||||||
"home": "Hjem",
|
|
||||||
"lastModified": "Sist endret",
|
|
||||||
"loading": "Laster....",
|
|
||||||
"lonely": "Det føltes ensomt her...",
|
|
||||||
"metadata": "Metadata",
|
|
||||||
"multipleSelectionEnabled": "Fleire seksjoner på",
|
|
||||||
"name": "Navn",
|
|
||||||
"size": "Størrelse",
|
|
||||||
"sortByLastModified": "Sorter etter sist endret",
|
|
||||||
"sortByName": "Sorter etter navn",
|
|
||||||
"sortBySize": "Sorter etter størrelse",
|
|
||||||
"noPreview": "Forhåndsvisning er ikkje tilgjengeleg for denne filen."
|
|
||||||
},
|
|
||||||
"help": {
|
|
||||||
"click": "velg fil eller katalog",
|
|
||||||
"ctrl": {
|
|
||||||
"click": "velg flere filer eller mapper",
|
|
||||||
"f": "opner søk",
|
|
||||||
"s": "lagr en fil eller last ned direktoratet der du er"
|
|
||||||
},
|
|
||||||
"del": "slett markert filer",
|
|
||||||
"doubleClick": "open en fil eller direktorat",
|
|
||||||
"esc": "visk av seleksjon og/eller lukk dette varselet",
|
|
||||||
"f1": "denne informasjonen",
|
|
||||||
"f2": "gi nytt navn til denne filen",
|
|
||||||
"help": "Hjelp"
|
|
||||||
},
|
|
||||||
"login": {
|
|
||||||
"createAnAccount": "Opprett ein konto",
|
|
||||||
"loginInstead": "Du har allerede ein konto",
|
|
||||||
"password": "Passord",
|
|
||||||
"passwordConfirm": "Passordbekreftelse",
|
|
||||||
"passwordsDontMatch": "Passordene samsvarer ikkje",
|
|
||||||
"signup": "Registrer deg",
|
|
||||||
"submit": "Logg inn",
|
|
||||||
"username": "Brukernavn",
|
|
||||||
"usernameTaken": "Brukernavn er allerede i bruk",
|
|
||||||
"wrongCredentials": "Feil legitimasjon"
|
|
||||||
},
|
|
||||||
"permanent": "Permanent",
|
|
||||||
"prompts": {
|
|
||||||
"copy": "Kopiere",
|
|
||||||
"copyMessage": "Velg hvor du vil kopiere filene dine:",
|
|
||||||
"currentlyNavigating": "Navigerer nå på:",
|
|
||||||
"deleteMessageMultiple": "Er du sikker på at du vil slette {count} fil(er)?",
|
|
||||||
"deleteMessageSingle": "Er du sikker på at du vil slette denne filen/mappen?",
|
|
||||||
"deleteMessageShare": "Er du sikker på at du vil slette denne delingen ({path})?",
|
|
||||||
"deleteUser": "Er du sikker at du vil slette denne brukeren?",
|
|
||||||
"deleteTitle": "Slett filer",
|
|
||||||
"displayName": "Vis Navn:",
|
|
||||||
"download": "Last ned filer",
|
|
||||||
"downloadMessage": "Velg kva format du ønsker å laste ned.",
|
|
||||||
"error": "Noko gikk galt.",
|
|
||||||
"fileInfo": "Fil informasjon",
|
|
||||||
"filesSelected": "{count} filer valgt.",
|
|
||||||
"lastModified": "Sist endret",
|
|
||||||
"move": "Flytt",
|
|
||||||
"moveMessage": "Velg nytt hjem for filen(e)/mappen(e)din:",
|
|
||||||
"newArchetype": "Opprett et nytt innlegg basert på en arketype. Filen din opprettes i innholdsmappen.",
|
|
||||||
"newDir": "Nytt Direktorat",
|
|
||||||
"newDirMessage": "Navn gi ditt nye direktorat",
|
|
||||||
"newFile": "Ny fil",
|
|
||||||
"newFileMessage": "Navn gi ditt nye fil",
|
|
||||||
"numberDirs": "Nummer av direktorat",
|
|
||||||
"numberFiles": "Nummer av filer",
|
|
||||||
"rename": "Gi nytt navn",
|
|
||||||
"renameMessage": "Sett inn nytt navn for",
|
|
||||||
"replace": "Bytt ut",
|
|
||||||
"replaceMessage": "En av filene du prøver å laste opp har et motstridende navn. Vil du hoppe over denne filen og fortsette opplastingen eller erstatte den eksisterende?\n",
|
|
||||||
"schedule": "Planlegg",
|
|
||||||
"scheduleMessage": "Velg en dato og et klokkeslett for å planlegge publiseringen av dette innlegget.",
|
|
||||||
"show": "Vis",
|
|
||||||
"size": "Størrelse",
|
|
||||||
"upload": "Last opp",
|
|
||||||
"uploadFiles": "Laster opp {filer} filer...",
|
|
||||||
"uploadMessage": "Velg et alternativ for opplasting.",
|
|
||||||
"optionalPassword": "Valgfritt passord",
|
|
||||||
"resolution": "Oppløysning",
|
|
||||||
"discardEditorChanges": "Er du sikker på at du vil forkaste endringene du har gjort?"
|
|
||||||
},
|
|
||||||
"search": {
|
|
||||||
"images": "Bilde",
|
|
||||||
"music": "Musikk",
|
|
||||||
"pdf": "PDF",
|
|
||||||
"pressToSearch": "Trykk enter for å søke...",
|
|
||||||
"search": "Søk...",
|
|
||||||
"typeToSearch": "Trykk for å søke...",
|
|
||||||
"types": "Typer",
|
|
||||||
"video": "Video"
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"admin": "Admin",
|
|
||||||
"administrator": "Administrator",
|
|
||||||
"allowCommands": "Utfør kommandoer",
|
|
||||||
"allowEdit": "Rediger, gi nytt navn til og slett filer eller mapper",
|
|
||||||
"allowNew": "Opprett nye filer og direktorater",
|
|
||||||
"allowPublish": "Publiser nye innlegg og sider",
|
|
||||||
"allowSignup": "Tilat brukere å registrere seg",
|
|
||||||
"avoidChanges": "(la stå tomt for å unngå endringer)",
|
|
||||||
"branding": "Merkevarebygging",
|
|
||||||
"brandingDirectoryPath": "Bane for merkevarekatalog",
|
|
||||||
"brandingHelp": "Du kan tilpasse hvordan Filleser-instansen din ser ut og føles ved å endre navnet, erstatte logoen, legge til egendefinerte stiler og til og med deaktivere eksterne lenker til GitHub.\n\nFor mer informasjon om tilpasset merkevarebygging, se {0}.",
|
|
||||||
"changePassword": "Skift Passord",
|
|
||||||
"commandRunner": "Kommandoløper",
|
|
||||||
"commandRunnerHelp": "Her kan du angi kommandoer som skal utføres i de navngitte hendelsene. Du må skrive én per linje. Miljøvariablene {0} og {1} vil være tilgjengelige, siden de er {0} relative til {1}. For mer informasjon om denne funksjonen og de tilgjengelige miljøvariablene, vennligst les {2}.",
|
|
||||||
"commandsUpdated": "Komando opptatert!",
|
|
||||||
"createUserDir": "Opprett brukerens hjemmappe automatisk når du legger til en ny bruker",
|
|
||||||
"minimumPasswordLength": "Minimum passord lengde",
|
|
||||||
"tusUploads": "Klumpede opplastinger",
|
|
||||||
"tusUploadsHelp": "Filleseren støtter opplasting av delte filer, noe som gjør det mulig å lage effektive, pålitelige, gjenopptakbare og delte filer, selv på upålitelige nettverk.",
|
|
||||||
"tusUploadsChunkSize": "Angir maksimal størrelse på en forespørsel (direkte opplastinger vil bli brukt for mindre opplastinger). Du kan legge inn et heltall som angir bytestørrelsen, eller en streng som 10 MB, 1 GB osv.",
|
|
||||||
"tusUploadsRetryCount": "Antall nye forsøk som skal utføres hvis en del ikke lastes opp.",
|
|
||||||
"userHomeBasePath": "Basissti for brukerens hjemmekataloger",
|
|
||||||
"userScopeGenerationPlaceholder": "Omfanget vil bli generert automatisk",
|
|
||||||
"createUserHomeDirectory": "Opprett bruker hjemme direktorat",
|
|
||||||
"customStylesheet": "Egendefinert stilark",
|
|
||||||
"defaultUserDescription": "Dette er standardinnstillingene for nye brukere.",
|
|
||||||
"disableExternalLinks": "Deaktiver eksterne lenker (unntatt dokumentasjon)",
|
|
||||||
"disableUsedDiskPercentage": "Deaktiver grafen for prosentandelen brukt disk",
|
|
||||||
"documentation": "dokumentasjon",
|
|
||||||
"examples": "Eksempel",
|
|
||||||
"executeOnShell": "Kjør på skall",
|
|
||||||
"executeOnShellDescription": "Som standard kjører Filleseren kommandoene ved å kalle binærfilene direkte. Hvis du heller ønsker å kjøre dem på et skall (som Bash eller PowerShell), kan du definere det her med de nødvendige argumentene og flaggene. Hvis dette er angitt, vil kommandoen du kjører bli lagt til som et argument. Dette gjelder både brukerkommandoer og hendelseshooker.",
|
|
||||||
"globalRules": "Dette er et globalt sett med regler for tillatelse og forbud. De gjelder for alle brukere. Du kan definere spesifikke regler for hver brukers innstillinger for å overstyre disse.",
|
|
||||||
"globalSettings": "Globale Innstillinger",
|
|
||||||
"hideDotfiles": "Skjul punktfiler",
|
|
||||||
"insertPath": "Sett inn banen",
|
|
||||||
"insertRegex": "sett inn regex-uttrykk",
|
|
||||||
"instanceName": "Forekomstnavn",
|
|
||||||
"language": "Språk",
|
|
||||||
"lockPassword": "Hindre brukeren i å endre passordet",
|
|
||||||
"newPassword": "Sett ditt nye passord",
|
|
||||||
"newPasswordConfirm": "Bekreft ditt nye passord",
|
|
||||||
"newUser": "Ny bruker",
|
|
||||||
"password": "Passord",
|
|
||||||
"passwordUpdated": "Passord opptatert!",
|
|
||||||
"path": "Veg",
|
|
||||||
"perm": {
|
|
||||||
"create": "Opprett filer og direktorater",
|
|
||||||
"delete": "Slett filer og direktorater",
|
|
||||||
"download": "Nedlast",
|
|
||||||
"execute": "Utfør kommandoer",
|
|
||||||
"modify": "Endre filer",
|
|
||||||
"rename": "Gi nytt navn eller flytt filer og direktorater",
|
|
||||||
"share": "Del filer"
|
|
||||||
},
|
|
||||||
"permissions": "Tilaterser",
|
|
||||||
"permissionsHelp": "Du kan angi brukeren som administrator eller velge tillatelsene individuelt. Hvis du velger «Administrator», vil alle de andre alternativene bli automatisk avkrysset. Administrasjon av brukere er fortsatt et privilegium for en administrator.\n",
|
|
||||||
"profileSettings": "Profil Innstilinger",
|
|
||||||
"ruleExample1": "forhindrer tilgang til noen dotfiler (som .git, .gitignore) i alle mapper.\n",
|
|
||||||
"ruleExample2": "blokkerer tilgangen til filen med navnet Caddyfile på roten av omfanget.",
|
|
||||||
"rules": "Regler",
|
|
||||||
"rulesHelp": "Her kan du definere et sett med tillatelses- og forbudsregler for denne spesifikke brukeren. De blokkerte filene vil ikke vises i listene, og de vil ikke være tilgjengelige for brukeren. Vi støtter regex og stier i forhold til brukerens omfang.",
|
|
||||||
"scope": "Omfang",
|
|
||||||
"setDateFormat": "Sett eksakt dato format",
|
|
||||||
"settingsUpdated": "Innstilinger opptatert!",
|
|
||||||
"shareDuration": "Del tidsbruk",
|
|
||||||
"shareManagement": "Del Ledelse",
|
|
||||||
"shareDeleted": "Delte ting slettet!",
|
|
||||||
"singleClick": "Bruk enkeltklikk for å åpne filer og mapper",
|
|
||||||
"themes": {
|
|
||||||
"default": "Systemstandard",
|
|
||||||
"dark": "Mørk",
|
|
||||||
"light": "Lyst",
|
|
||||||
"title": "Tema"
|
|
||||||
},
|
|
||||||
"user": "Bruker",
|
|
||||||
"userCommands": "Kommando",
|
|
||||||
"userCommandsHelp": "En mellomromsseparert liste med tilgjengelige kommandoer for denne brukeren. Eksempel:\n",
|
|
||||||
"userCreated": "Bruker opprettet!",
|
|
||||||
"userDefaults": "Bruker systemstandard instillinger",
|
|
||||||
"userDeleted": "Bruker slettet!",
|
|
||||||
"userManagement": "Brukeradministrasjon",
|
|
||||||
"userUpdated": "Bruker opprettet!",
|
|
||||||
"username": "Brukernavn",
|
|
||||||
"users": "Bruker"
|
|
||||||
},
|
|
||||||
"sidebar": {
|
|
||||||
"help": "Hjelp",
|
|
||||||
"hugoNew": "Hugo Ny",
|
|
||||||
"login": "Logg inn",
|
|
||||||
"logout": "Logg Ut",
|
|
||||||
"myFiles": "Mine filer",
|
|
||||||
"newFile": "Ny fil",
|
|
||||||
"newFolder": "Ny mappe",
|
|
||||||
"preview": "Forhåndsvis",
|
|
||||||
"settings": "Innstillinger",
|
|
||||||
"signup": "Registrer deg",
|
|
||||||
"siteSettings": "Side innstillinger"
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
"linkCopied": "Link koppiert!"
|
|
||||||
},
|
|
||||||
"time": {
|
|
||||||
"days": "Dager",
|
|
||||||
"hours": "Timer",
|
|
||||||
"minutes": "Minutt",
|
|
||||||
"seconds": "Sekunder",
|
|
||||||
"unit": "Time format"
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,17 +3,17 @@
|
|||||||
"cancel": "Zrušiť",
|
"cancel": "Zrušiť",
|
||||||
"clear": "Zrušiť výber",
|
"clear": "Zrušiť výber",
|
||||||
"close": "Zavrieť",
|
"close": "Zavrieť",
|
||||||
"continue": "Pokračovať",
|
"continue": "Continue",
|
||||||
"copy": "Kopírovať",
|
"copy": "Kopírovať",
|
||||||
"copyFile": "Kopírovať súbor",
|
"copyFile": "Kopírovať súbor",
|
||||||
"copyToClipboard": "Kopírovať do schránky",
|
"copyToClipboard": "Kopírovať do schránky",
|
||||||
"copyDownloadLinkToClipboard": "Kopírovať odkaz na stiahnutie do schránky",
|
"copyDownloadLinkToClipboard": "Copy download link to clipboard",
|
||||||
"create": "Vytvoriť",
|
"create": "Vytvoriť",
|
||||||
"delete": "Odstrániť",
|
"delete": "Odstrániť",
|
||||||
"download": "Stiahnuť",
|
"download": "Stiahnuť",
|
||||||
"file": "Súbor",
|
"file": "Súbor",
|
||||||
"folder": "Priečinok",
|
"folder": "Priečinok",
|
||||||
"fullScreen": "Prepnúť na celú obrazovku",
|
"fullScreen": "Toggle full screen",
|
||||||
"hideDotfiles": "Skryť súbory začínajúce bodkou",
|
"hideDotfiles": "Skryť súbory začínajúce bodkou",
|
||||||
"info": "Info",
|
"info": "Info",
|
||||||
"more": "Viac",
|
"more": "Viac",
|
||||||
@ -24,7 +24,7 @@
|
|||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"permalink": "Získať trvalý odkaz",
|
"permalink": "Získať trvalý odkaz",
|
||||||
"previous": "Predošlé",
|
"previous": "Predošlé",
|
||||||
"preview": "Náhľad",
|
"preview": "Preview",
|
||||||
"publish": "Zverejniť",
|
"publish": "Zverejniť",
|
||||||
"rename": "Premenovať",
|
"rename": "Premenovať",
|
||||||
"replace": "Nahradiť",
|
"replace": "Nahradiť",
|
||||||
@ -42,7 +42,7 @@
|
|||||||
"update": "Aktualizovať",
|
"update": "Aktualizovať",
|
||||||
"upload": "Nahrať",
|
"upload": "Nahrať",
|
||||||
"openFile": "Otvoriť súbor",
|
"openFile": "Otvoriť súbor",
|
||||||
"discardChanges": "Zahodiť"
|
"discardChanges": "Discard"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Stiahnuť súbor",
|
"downloadFile": "Stiahnuť súbor",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"downloadSelected": "Stiahnuť vybraté"
|
"downloadSelected": "Stiahnuť vybraté"
|
||||||
},
|
},
|
||||||
"upload": {
|
"upload": {
|
||||||
"abortUpload": "Naozaj chcete prerušiť?"
|
"abortUpload": "Are you sure you wish to abort?"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"forbidden": "You don't have permissions to access this.",
|
"forbidden": "You don't have permissions to access this.",
|
||||||
@ -110,7 +110,7 @@
|
|||||||
"deleteMessageMultiple": "Naozaj chcete odstrániť {count} súbor(ov)?",
|
"deleteMessageMultiple": "Naozaj chcete odstrániť {count} súbor(ov)?",
|
||||||
"deleteMessageSingle": "Naozaj chcete odstrániť tento súbor/priečinok?",
|
"deleteMessageSingle": "Naozaj chcete odstrániť tento súbor/priečinok?",
|
||||||
"deleteMessageShare": "Naozaj chcete odstrániť toto zdieľanie({path})?",
|
"deleteMessageShare": "Naozaj chcete odstrániť toto zdieľanie({path})?",
|
||||||
"deleteUser": "Naozaj chcete odstrániť tohto používateľa?",
|
"deleteUser": "Are you sure you want to delete this user?",
|
||||||
"deleteTitle": "Odstránenie súborov",
|
"deleteTitle": "Odstránenie súborov",
|
||||||
"displayName": "Zobrazený názov:",
|
"displayName": "Zobrazený názov:",
|
||||||
"download": "Stiahnuť súbory",
|
"download": "Stiahnuť súbory",
|
||||||
@ -137,11 +137,11 @@
|
|||||||
"show": "Zobraziť",
|
"show": "Zobraziť",
|
||||||
"size": "Veľkosť",
|
"size": "Veľkosť",
|
||||||
"upload": "Nahrať",
|
"upload": "Nahrať",
|
||||||
"uploadFiles": "Nahráva sa {files} súborov...",
|
"uploadFiles": "Uploading {files} files...",
|
||||||
"uploadMessage": "Zvoľte možnosť nahrávania.",
|
"uploadMessage": "Zvoľte možnosť nahrávania.",
|
||||||
"optionalPassword": "Voliteľné heslo",
|
"optionalPassword": "Voliteľné heslo",
|
||||||
"resolution": "Rozlíšenie",
|
"resolution": "Resolution",
|
||||||
"discardEditorChanges": "Naozaj chcete zahodiť vykonané zmeny?"
|
"discardEditorChanges": "Are you sure you wish to discard the changes you've made?"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"images": "Obrázky",
|
"images": "Obrázky",
|
||||||
@ -170,14 +170,14 @@
|
|||||||
"commandRunnerHelp": "Sem môžete nastaviť príkazy, ktoré sa vykonajú pri určitých udalostiach. Musíte písať jeden na riadok. Premenné prostredia {0} a {1} sú k dispozícii, s tým že {0} relatívne k {1}. Viac informácií o tejto funkcionalite a dostupných premenných prostredia nájdete na {2}.",
|
"commandRunnerHelp": "Sem môžete nastaviť príkazy, ktoré sa vykonajú pri určitých udalostiach. Musíte písať jeden na riadok. Premenné prostredia {0} a {1} sú k dispozícii, s tým že {0} relatívne k {1}. Viac informácií o tejto funkcionalite a dostupných premenných prostredia nájdete na {2}.",
|
||||||
"commandsUpdated": "Príkazy upravené!",
|
"commandsUpdated": "Príkazy upravené!",
|
||||||
"createUserDir": "Automaticky vytvoriť domovský priečinok pri pridaní používateľa",
|
"createUserDir": "Automaticky vytvoriť domovský priečinok pri pridaní používateľa",
|
||||||
"minimumPasswordLength": "Minimálna dĺžka hesla",
|
"minimumPasswordLength": "Minimum password length",
|
||||||
"tusUploads": "Nahrávanie po častiach",
|
"tusUploads": "Chunked Uploads",
|
||||||
"tusUploadsHelp": "Prehliadač súborov podporuje nahrávanie súborov po častiach, čo umožňuje vytváranie efektívnych, spoľahlivých, obnoviteľných a po častiach nahrávaných súborov aj v prípade nespoľahlivých sietí.",
|
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
|
||||||
"tusUploadsChunkSize": "Označuje maximálnu veľkosť požiadavky (pre menšie nahratia sa použijú priame nahratia). Môžete zadať celé číslo označujúce veľkosť v bajtoch alebo reťazec ako 10 MB, 1 GB atď.",
|
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
|
||||||
"tusUploadsRetryCount": "Počet opakovaných pokusov, ktoré sa majú vykonať, ak sa nepodarí nahrať časť súboru.",
|
"tusUploadsRetryCount": "Number of retries to perform if a chunk fails to upload.",
|
||||||
"userHomeBasePath": "Východisková cesta pre domáce adresáre používateľov",
|
"userHomeBasePath": "Base path for user home directories",
|
||||||
"userScopeGenerationPlaceholder": "Rozsah bude automaticky generovaný",
|
"userScopeGenerationPlaceholder": "The scope will be auto generated",
|
||||||
"createUserHomeDirectory": "Vytvoriť domovský adresár používateľa",
|
"createUserHomeDirectory": "Create user home directory",
|
||||||
"customStylesheet": "Vlastný Stylesheet",
|
"customStylesheet": "Vlastný Stylesheet",
|
||||||
"defaultUserDescription": "Toto sú predvolané nastavenia nového používateľa.",
|
"defaultUserDescription": "Toto sú predvolané nastavenia nového používateľa.",
|
||||||
"disableExternalLinks": "Vypnúť externé odkazy (okrem dokumentácie)",
|
"disableExternalLinks": "Vypnúť externé odkazy (okrem dokumentácie)",
|
||||||
@ -217,14 +217,14 @@
|
|||||||
"rules": "Pravidlá",
|
"rules": "Pravidlá",
|
||||||
"rulesHelp": "Tu môžete definovať pravidlá pre konkrétneho používateľa. Blokované súbory používateľ nebude vidieť a ani nebude k nim mať prístup. Podporujeme regex a cesty relatívne k používateľovi.\n",
|
"rulesHelp": "Tu môžete definovať pravidlá pre konkrétneho používateľa. Blokované súbory používateľ nebude vidieť a ani nebude k nim mať prístup. Podporujeme regex a cesty relatívne k používateľovi.\n",
|
||||||
"scope": "Scope",
|
"scope": "Scope",
|
||||||
"setDateFormat": "Nastaviť presný formát dátumu",
|
"setDateFormat": "Set exact date format",
|
||||||
"settingsUpdated": "Nastavenia upravené!",
|
"settingsUpdated": "Nastavenia upravené!",
|
||||||
"shareDuration": "Trvanie zdieľania",
|
"shareDuration": "Trvanie zdieľania",
|
||||||
"shareManagement": "Správa zdieľania",
|
"shareManagement": "Správa zdieľania",
|
||||||
"shareDeleted": "Zdieľanie odstránené!",
|
"shareDeleted": "Zdieľanie odstránené!",
|
||||||
"singleClick": "Používať jeden klik na otváranie súborov a priečinkov",
|
"singleClick": "Používať jeden klik na otváranie súborov a priečinkov",
|
||||||
"themes": {
|
"themes": {
|
||||||
"default": "Predvolené nastavenie systému",
|
"default": "System default",
|
||||||
"dark": "Tmavá",
|
"dark": "Tmavá",
|
||||||
"light": "Svetlá",
|
"light": "Svetlá",
|
||||||
"title": "Téma"
|
"title": "Téma"
|
||||||
|
@ -170,7 +170,7 @@
|
|||||||
"commandRunnerHelp": "Tại đây, bạn có thể thiết lập các lệnh được thực thi trong các sự kiện đã định. Bạn phải viết một lệnh trên mỗi dòng. Các biến môi trường {0} và {1} sẽ có sẵn, trong đó {0} tương đối với {1}. Để biết thêm thông tin về tính năng này và các biến môi trường có sẵn, vui lòng đọc {2}.",
|
"commandRunnerHelp": "Tại đây, bạn có thể thiết lập các lệnh được thực thi trong các sự kiện đã định. Bạn phải viết một lệnh trên mỗi dòng. Các biến môi trường {0} và {1} sẽ có sẵn, trong đó {0} tương đối với {1}. Để biết thêm thông tin về tính năng này và các biến môi trường có sẵn, vui lòng đọc {2}.",
|
||||||
"commandsUpdated": "Lệnh đã được cập nhật!",
|
"commandsUpdated": "Lệnh đã được cập nhật!",
|
||||||
"createUserDir": "Tự động tạo thư mục chính của người dùng khi thêm người dùng mới",
|
"createUserDir": "Tự động tạo thư mục chính của người dùng khi thêm người dùng mới",
|
||||||
"minimumPasswordLength": "Độ dài mật khẩu tối thiểu",
|
"minimumPasswordLength": "Minimum password length",
|
||||||
"tusUploads": "Tải lên theo phân đoạn",
|
"tusUploads": "Tải lên theo phân đoạn",
|
||||||
"tusUploadsHelp": "File Browser hỗ trợ tải lên tệp theo phân đoạn, giúp việc tải lên trở nên hiệu quả, đáng tin cậy, có thể tiếp tục và phù hợp với mạng không ổn định.",
|
"tusUploadsHelp": "File Browser hỗ trợ tải lên tệp theo phân đoạn, giúp việc tải lên trở nên hiệu quả, đáng tin cậy, có thể tiếp tục và phù hợp với mạng không ổn định.",
|
||||||
"tusUploadsChunkSize": "Kích thước tối đa của một yêu cầu (tải lên trực tiếp sẽ được sử dụng cho các tệp nhỏ hơn). Bạn có thể nhập một số nguyên biểu thị kích thước theo byte hoặc một chuỗi như 10MB, 1GB, v.v.",
|
"tusUploadsChunkSize": "Kích thước tối đa của một yêu cầu (tải lên trực tiếp sẽ được sử dụng cho các tệp nhỏ hơn). Bạn có thể nhập một số nguyên biểu thị kích thước theo byte hoặc một chuỗi như 10MB, 1GB, v.v.",
|
||||||
|
@ -9,7 +9,6 @@ export const useFileStore = defineStore("file", {
|
|||||||
selected: number[];
|
selected: number[];
|
||||||
multiple: boolean;
|
multiple: boolean;
|
||||||
isFiles: boolean;
|
isFiles: boolean;
|
||||||
preselect: string | null;
|
|
||||||
} => ({
|
} => ({
|
||||||
req: null,
|
req: null,
|
||||||
oldReq: null,
|
oldReq: null,
|
||||||
@ -17,7 +16,6 @@ export const useFileStore = defineStore("file", {
|
|||||||
selected: [],
|
selected: [],
|
||||||
multiple: false,
|
multiple: false,
|
||||||
isFiles: false,
|
isFiles: false,
|
||||||
preselect: null,
|
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
selectedCount: (state) => state.selected.length,
|
selectedCount: (state) => state.selected.length,
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { useFileStore } from "./file";
|
import { useFileStore } from "./file";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
|
import { throttle } from "lodash-es";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import { computed, inject, markRaw, ref } from "vue";
|
|
||||||
import * as tus from "@/api/tus";
|
|
||||||
|
|
||||||
// TODO: make this into a user setting
|
// TODO: make this into a user setting
|
||||||
const UPLOADS_LIMIT = 5;
|
const UPLOADS_LIMIT = 5;
|
||||||
@ -14,167 +13,208 @@ const beforeUnload = (event: Event) => {
|
|||||||
// event.returnValue = "";
|
// event.returnValue = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUploadStore = defineStore("upload", () => {
|
// Utility function to format bytes into a readable string
|
||||||
const $showError = inject<IToastError>("$showError")!;
|
function formatSize(bytes: number): string {
|
||||||
|
if (bytes === 0) return "0.00 Bytes";
|
||||||
|
|
||||||
let progressInterval: number | null = null;
|
const k = 1024;
|
||||||
|
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
//
|
// Return the rounded size with two decimal places
|
||||||
// STATE
|
return (bytes / k ** i).toFixed(2) + " " + sizes[i];
|
||||||
//
|
}
|
||||||
|
|
||||||
const allUploads = ref<Upload[]>([]);
|
export const useUploadStore = defineStore("upload", {
|
||||||
const activeUploads = ref<Set<Upload>>(new Set());
|
// convert to a function
|
||||||
const lastUpload = ref<number>(-1);
|
state: (): {
|
||||||
const totalBytes = ref<number>(0);
|
id: number;
|
||||||
const sentBytes = ref<number>(0);
|
sizes: number[];
|
||||||
|
progress: number[];
|
||||||
|
queue: UploadItem[];
|
||||||
|
uploads: Uploads;
|
||||||
|
speedMbyte: number;
|
||||||
|
eta: number;
|
||||||
|
error: Error | null;
|
||||||
|
} => ({
|
||||||
|
id: 0,
|
||||||
|
sizes: [],
|
||||||
|
progress: [],
|
||||||
|
queue: [],
|
||||||
|
uploads: {},
|
||||||
|
speedMbyte: 0,
|
||||||
|
eta: 0,
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
// user and jwt getter removed, no longer needed
|
||||||
|
getProgress: (state) => {
|
||||||
|
if (state.progress.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
const totalSize = state.sizes.reduce((a, b) => a + b, 0);
|
||||||
// ACTIONS
|
const sum = state.progress.reduce((a, b) => a + b, 0);
|
||||||
//
|
return Math.ceil((sum / totalSize) * 100);
|
||||||
|
},
|
||||||
|
getProgressDecimal: (state) => {
|
||||||
|
if (state.progress.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const upload = (
|
const totalSize = state.sizes.reduce((a, b) => a + b, 0);
|
||||||
path: string,
|
const sum = state.progress.reduce((a, b) => a + b, 0);
|
||||||
name: string,
|
return ((sum / totalSize) * 100).toFixed(2);
|
||||||
file: File | null,
|
},
|
||||||
overwrite: boolean,
|
getTotalProgressBytes: (state) => {
|
||||||
type: ResourceType
|
if (state.progress.length === 0 || state.sizes.length === 0) {
|
||||||
) => {
|
return "0 Bytes";
|
||||||
if (!hasActiveUploads() && !hasPendingUploads()) {
|
}
|
||||||
|
const sum = state.progress.reduce((a, b) => a + b, 0);
|
||||||
|
return formatSize(sum);
|
||||||
|
},
|
||||||
|
getTotalSize: (state) => {
|
||||||
|
if (state.sizes.length === 0) {
|
||||||
|
return "0 Bytes";
|
||||||
|
}
|
||||||
|
const totalSize = state.sizes.reduce((a, b) => a + b, 0);
|
||||||
|
return formatSize(totalSize);
|
||||||
|
},
|
||||||
|
filesInUploadCount: (state) => {
|
||||||
|
return Object.keys(state.uploads).length + state.queue.length;
|
||||||
|
},
|
||||||
|
filesInUpload: (state) => {
|
||||||
|
const files = [];
|
||||||
|
|
||||||
|
for (const index in state.uploads) {
|
||||||
|
const upload = state.uploads[index];
|
||||||
|
const id = upload.id;
|
||||||
|
const type = upload.type;
|
||||||
|
const name = upload.file.name;
|
||||||
|
const size = state.sizes[id];
|
||||||
|
const isDir = upload.file.isDir;
|
||||||
|
const progress = isDir
|
||||||
|
? 100
|
||||||
|
: Math.ceil((state.progress[id] / size) * 100);
|
||||||
|
|
||||||
|
files.push({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
progress,
|
||||||
|
type,
|
||||||
|
isDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.sort((a, b) => a.progress - b.progress);
|
||||||
|
},
|
||||||
|
uploadSpeed: (state) => {
|
||||||
|
return state.speedMbyte;
|
||||||
|
},
|
||||||
|
getETA: (state) => state.eta,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// no context as first argument, use `this` instead
|
||||||
|
setProgress({ id, loaded }: { id: number; loaded: number }) {
|
||||||
|
this.progress[id] = loaded;
|
||||||
|
},
|
||||||
|
setError(error: Error) {
|
||||||
|
this.error = error;
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.id = 0;
|
||||||
|
this.sizes = [];
|
||||||
|
this.progress = [];
|
||||||
|
this.queue = [];
|
||||||
|
this.uploads = {};
|
||||||
|
this.speedMbyte = 0;
|
||||||
|
this.eta = 0;
|
||||||
|
this.error = null;
|
||||||
|
},
|
||||||
|
addJob(item: UploadItem) {
|
||||||
|
this.queue.push(item);
|
||||||
|
this.sizes[this.id] = item.file.size;
|
||||||
|
this.id++;
|
||||||
|
},
|
||||||
|
moveJob() {
|
||||||
|
const item = this.queue[0];
|
||||||
|
this.queue.shift();
|
||||||
|
this.uploads[item.id] = item;
|
||||||
|
},
|
||||||
|
removeJob(id: number) {
|
||||||
|
delete this.uploads[id];
|
||||||
|
},
|
||||||
|
upload(item: UploadItem) {
|
||||||
|
const uploadsCount = Object.keys(this.uploads).length;
|
||||||
|
|
||||||
|
const isQueueEmpty = this.queue.length == 0;
|
||||||
|
const isUploadsEmpty = uploadsCount == 0;
|
||||||
|
|
||||||
|
if (isQueueEmpty && isUploadsEmpty) {
|
||||||
window.addEventListener("beforeunload", beforeUnload);
|
window.addEventListener("beforeunload", beforeUnload);
|
||||||
buttons.loading("upload");
|
buttons.loading("upload");
|
||||||
}
|
}
|
||||||
|
|
||||||
const upload: Upload = {
|
this.addJob(item);
|
||||||
path,
|
this.processUploads();
|
||||||
name,
|
},
|
||||||
file,
|
finishUpload(item: UploadItem) {
|
||||||
overwrite,
|
this.setProgress({ id: item.id, loaded: item.file.size });
|
||||||
type,
|
this.removeJob(item.id);
|
||||||
totalBytes: file?.size || 1,
|
this.processUploads();
|
||||||
sentBytes: 0,
|
},
|
||||||
// Stores rapidly changing sent bytes value without causing component re-renders
|
async processUploads() {
|
||||||
rawProgress: markRaw({
|
const uploadsCount = Object.keys(this.uploads).length;
|
||||||
sentBytes: 0,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
totalBytes.value += upload.totalBytes;
|
const isBelowLimit = uploadsCount < UPLOADS_LIMIT;
|
||||||
allUploads.value.push(upload);
|
const isQueueEmpty = this.queue.length == 0;
|
||||||
|
const isUploadsEmpty = uploadsCount == 0;
|
||||||
|
|
||||||
processUploads();
|
const isFinished = isQueueEmpty && isUploadsEmpty;
|
||||||
};
|
const canProcess = isBelowLimit && !isQueueEmpty;
|
||||||
|
|
||||||
const abort = () => {
|
if (isFinished) {
|
||||||
// Resets the state by preventing the processing of the remaning uploads
|
|
||||||
lastUpload.value = Infinity;
|
|
||||||
tus.abortAllUploads();
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// GETTERS
|
|
||||||
//
|
|
||||||
|
|
||||||
const pendingUploadCount = computed(
|
|
||||||
() =>
|
|
||||||
allUploads.value.length -
|
|
||||||
(lastUpload.value + 1) +
|
|
||||||
activeUploads.value.size
|
|
||||||
);
|
|
||||||
|
|
||||||
//
|
|
||||||
// PRIVATE FUNCTIONS
|
|
||||||
//
|
|
||||||
|
|
||||||
const hasActiveUploads = () => activeUploads.value.size > 0;
|
|
||||||
|
|
||||||
const hasPendingUploads = () =>
|
|
||||||
allUploads.value.length > lastUpload.value + 1;
|
|
||||||
|
|
||||||
const isActiveUploadsOnLimit = () => activeUploads.value.size < UPLOADS_LIMIT;
|
|
||||||
|
|
||||||
const processUploads = async () => {
|
|
||||||
if (!hasActiveUploads() && !hasPendingUploads()) {
|
|
||||||
const fileStore = useFileStore();
|
const fileStore = useFileStore();
|
||||||
window.removeEventListener("beforeunload", beforeUnload);
|
window.removeEventListener("beforeunload", beforeUnload);
|
||||||
buttons.success("upload");
|
buttons.success("upload");
|
||||||
reset();
|
this.reset();
|
||||||
fileStore.reload = true;
|
fileStore.reload = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActiveUploadsOnLimit() && hasPendingUploads()) {
|
if (canProcess) {
|
||||||
if (!hasActiveUploads()) {
|
const item = this.queue[0];
|
||||||
// Update the state in a fixed time interval
|
this.moveJob();
|
||||||
progressInterval = window.setInterval(syncState, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const upload = nextUpload();
|
if (item.file.isDir) {
|
||||||
|
await api.post(item.path).catch(this.setError);
|
||||||
if (upload.type === "dir") {
|
|
||||||
await api.post(upload.path).catch($showError);
|
|
||||||
} else {
|
} else {
|
||||||
const onUpload = (event: ProgressEvent) => {
|
const onUpload = throttle(
|
||||||
upload.rawProgress.sentBytes = event.loaded;
|
(event: ProgressEvent) =>
|
||||||
};
|
this.setProgress({
|
||||||
|
id: item.id,
|
||||||
|
loaded: event.loaded,
|
||||||
|
}),
|
||||||
|
100,
|
||||||
|
{ leading: true, trailing: false }
|
||||||
|
);
|
||||||
|
|
||||||
await api
|
await api
|
||||||
.post(upload.path, upload.file!, upload.overwrite, onUpload)
|
.post(item.path, item.file.file as File, item.overwrite, onUpload)
|
||||||
.catch((err) => err.message !== "Upload aborted" && $showError(err));
|
.catch(this.setError);
|
||||||
}
|
}
|
||||||
|
|
||||||
finishUpload(upload);
|
this.finishUpload(item);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
setUploadSpeed(value: number) {
|
||||||
const nextUpload = (): Upload => {
|
this.speedMbyte = value;
|
||||||
lastUpload.value++;
|
},
|
||||||
|
setETA(value: number) {
|
||||||
const upload = allUploads.value[lastUpload.value];
|
this.eta = value;
|
||||||
activeUploads.value.add(upload);
|
},
|
||||||
|
// easily reset state using `$reset`
|
||||||
return upload;
|
clearUpload() {
|
||||||
};
|
this.$reset();
|
||||||
|
},
|
||||||
const finishUpload = (upload: Upload) => {
|
},
|
||||||
sentBytes.value += upload.totalBytes - upload.sentBytes;
|
|
||||||
upload.sentBytes = upload.totalBytes;
|
|
||||||
upload.file = null;
|
|
||||||
|
|
||||||
activeUploads.value.delete(upload);
|
|
||||||
processUploads();
|
|
||||||
};
|
|
||||||
|
|
||||||
const syncState = () => {
|
|
||||||
for (const upload of activeUploads.value) {
|
|
||||||
sentBytes.value += upload.rawProgress.sentBytes - upload.sentBytes;
|
|
||||||
upload.sentBytes = upload.rawProgress.sentBytes;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const reset = () => {
|
|
||||||
if (progressInterval !== null) {
|
|
||||||
clearInterval(progressInterval);
|
|
||||||
progressInterval = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
allUploads.value = [];
|
|
||||||
activeUploads.value = new Set();
|
|
||||||
lastUpload.value = -1;
|
|
||||||
totalBytes.value = 0;
|
|
||||||
sentBytes.value = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
// STATE
|
|
||||||
activeUploads,
|
|
||||||
totalBytes,
|
|
||||||
sentBytes,
|
|
||||||
|
|
||||||
// ACTIONS
|
|
||||||
upload,
|
|
||||||
abort,
|
|
||||||
|
|
||||||
// GETTERS
|
|
||||||
pendingUploadCount,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
1
frontend/src/types/file.d.ts
vendored
1
frontend/src/types/file.d.ts
vendored
@ -29,7 +29,6 @@ interface ResourceItem extends ResourceBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ResourceType =
|
type ResourceType =
|
||||||
| "dir"
|
|
||||||
| "video"
|
| "video"
|
||||||
| "audio"
|
| "audio"
|
||||||
| "image"
|
| "image"
|
||||||
|
43
frontend/src/types/upload.d.ts
vendored
43
frontend/src/types/upload.d.ts
vendored
@ -1,15 +1,22 @@
|
|||||||
type Upload = {
|
interface Uploads {
|
||||||
|
[key: number]: Upload;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Upload {
|
||||||
|
id: number;
|
||||||
|
file: UploadEntry;
|
||||||
|
type?: ResourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UploadItem {
|
||||||
|
id: number;
|
||||||
|
url?: string;
|
||||||
path: string;
|
path: string;
|
||||||
name: string;
|
file: UploadEntry;
|
||||||
file: File | null;
|
dir?: boolean;
|
||||||
type: ResourceType;
|
overwrite?: boolean;
|
||||||
overwrite: boolean;
|
type?: ResourceType;
|
||||||
totalBytes: number;
|
}
|
||||||
sentBytes: number;
|
|
||||||
rawProgress: {
|
|
||||||
sentBytes: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
interface UploadEntry {
|
interface UploadEntry {
|
||||||
name: string;
|
name: string;
|
||||||
@ -20,3 +27,17 @@ interface UploadEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UploadList = UploadEntry[];
|
type UploadList = UploadEntry[];
|
||||||
|
|
||||||
|
type CurrentUploadList = {
|
||||||
|
[key: string]: {
|
||||||
|
upload: import("tus-js-client").Upload;
|
||||||
|
recentSpeeds: number[];
|
||||||
|
initialBytesUploaded: number;
|
||||||
|
currentBytesUploaded: number;
|
||||||
|
currentAverageSpeed: number;
|
||||||
|
lastProgressTimestamp: number | null;
|
||||||
|
sumOfRecentSpeeds: number;
|
||||||
|
hasStarted: boolean;
|
||||||
|
interval: number | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -132,6 +132,7 @@ export function handleFiles(
|
|||||||
layoutStore.closeHovers();
|
layoutStore.closeHovers();
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
const id = uploadStore.id;
|
||||||
let path = base;
|
let path = base;
|
||||||
|
|
||||||
if (file.fullPath !== undefined) {
|
if (file.fullPath !== undefined) {
|
||||||
@ -144,8 +145,14 @@ export function handleFiles(
|
|||||||
path += "/";
|
path += "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = file.isDir ? "dir" : detectType((file.file as File).type);
|
const item: UploadItem = {
|
||||||
|
id,
|
||||||
|
path,
|
||||||
|
file,
|
||||||
|
overwrite,
|
||||||
|
...(!file.isDir && { type: detectType((file.file as File).type) }),
|
||||||
|
};
|
||||||
|
|
||||||
uploadStore.upload(path, file.name, file.file ?? null, overwrite, type);
|
uploadStore.upload(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<header-bar
|
<header-bar
|
||||||
v-if="error || fileStore.req?.type === undefined"
|
v-if="error || fileStore.req?.type === null"
|
||||||
showMenu
|
showMenu
|
||||||
showLogo
|
showLogo
|
||||||
/>
|
/>
|
||||||
@ -9,7 +9,7 @@
|
|||||||
<breadcrumbs base="/files" />
|
<breadcrumbs base="/files" />
|
||||||
<errors v-if="error" :errorCode="error.status" />
|
<errors v-if="error" :errorCode="error.status" />
|
||||||
<component v-else-if="currentView" :is="currentView"></component>
|
<component v-else-if="currentView" :is="currentView"></component>
|
||||||
<div v-else>
|
<div v-else-if="currentView !== null">
|
||||||
<h2 class="message delayed">
|
<h2 class="message delayed">
|
||||||
<div class="spinner">
|
<div class="spinner">
|
||||||
<div class="bounce1"></div>
|
<div class="bounce1"></div>
|
||||||
@ -36,6 +36,7 @@ import { files as api } from "@/api";
|
|||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { useFileStore } from "@/stores/file";
|
import { useFileStore } from "@/stores/file";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
import { useUploadStore } from "@/stores/upload";
|
||||||
|
|
||||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||||
@ -51,8 +52,10 @@ const Preview = defineAsyncComponent(() => import("@/views/files/Preview.vue"));
|
|||||||
|
|
||||||
const layoutStore = useLayoutStore();
|
const layoutStore = useLayoutStore();
|
||||||
const fileStore = useFileStore();
|
const fileStore = useFileStore();
|
||||||
|
const uploadStore = useUploadStore();
|
||||||
|
|
||||||
const { reload } = storeToRefs(fileStore);
|
const { reload } = storeToRefs(fileStore);
|
||||||
|
const { error: uploadError } = storeToRefs(uploadStore);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
@ -99,41 +102,26 @@ onUnmounted(() => {
|
|||||||
fetchDataController.abort();
|
fetchDataController.abort();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(route, () => {
|
watch(route, (to, from) => {
|
||||||
|
if (from.path.endsWith("/")) {
|
||||||
|
window.sessionStorage.setItem(
|
||||||
|
"listFrozen",
|
||||||
|
(!to.path.endsWith("/")).toString()
|
||||||
|
);
|
||||||
|
} else if (to.path.endsWith("/")) {
|
||||||
|
fileStore.updateRequest(null);
|
||||||
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
});
|
});
|
||||||
watch(reload, (newValue) => {
|
watch(reload, (newValue) => {
|
||||||
newValue && fetchData();
|
newValue && fetchData();
|
||||||
});
|
});
|
||||||
|
watch(uploadError, (newValue) => {
|
||||||
|
newValue && layoutStore.showError();
|
||||||
|
});
|
||||||
|
|
||||||
// Define functions
|
// Define functions
|
||||||
|
|
||||||
const applyPreSelection = () => {
|
|
||||||
const preselect = fileStore.preselect;
|
|
||||||
fileStore.preselect = null;
|
|
||||||
|
|
||||||
if (!fileStore.req?.isDir || fileStore.oldReq === null) return;
|
|
||||||
|
|
||||||
let index = -1;
|
|
||||||
if (preselect) {
|
|
||||||
// Find item with the specified path
|
|
||||||
index = fileStore.req.items.findIndex((item) => item.path === preselect);
|
|
||||||
} else if (fileStore.oldReq.path.startsWith(fileStore.req.path)) {
|
|
||||||
// Get immediate child folder of the previous path
|
|
||||||
const name = fileStore.oldReq.path
|
|
||||||
.substring(fileStore.req.path.length)
|
|
||||||
.split("/")
|
|
||||||
.shift();
|
|
||||||
|
|
||||||
index = fileStore.req.items.findIndex(
|
|
||||||
(val) => val.path == fileStore.req!.path + name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index === -1) return;
|
|
||||||
fileStore.selected.push(index);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
// Reset view information.
|
// Reset view information.
|
||||||
fileStore.reload = false;
|
fileStore.reload = false;
|
||||||
@ -142,7 +130,12 @@ const fetchData = async () => {
|
|||||||
layoutStore.closeHovers();
|
layoutStore.closeHovers();
|
||||||
|
|
||||||
// Set loading to true and reset the error.
|
// Set loading to true and reset the error.
|
||||||
|
if (
|
||||||
|
window.sessionStorage.getItem("listFrozen") !== "true" &&
|
||||||
|
window.sessionStorage.getItem("modified") !== "true"
|
||||||
|
) {
|
||||||
layoutStore.loading = true;
|
layoutStore.loading = true;
|
||||||
|
}
|
||||||
error.value = null;
|
error.value = null;
|
||||||
|
|
||||||
let url = route.path;
|
let url = route.path;
|
||||||
@ -156,9 +149,6 @@ const fetchData = async () => {
|
|||||||
fileStore.updateRequest(res);
|
fileStore.updateRequest(res);
|
||||||
document.title = `${res.name || t("sidebar.myFiles")} - ${t("files.files")} - ${name}`;
|
document.title = `${res.name || t("sidebar.myFiles")} - ${t("files.files")} - ${name}`;
|
||||||
layoutStore.loading = false;
|
layoutStore.loading = false;
|
||||||
|
|
||||||
// Selects the post-reload target item or the previously visited child folder
|
|
||||||
applyPreSelection();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof StatusError && err.is_canceled) {
|
if (err instanceof StatusError && err.is_canceled) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="uploadStore.totalBytes" class="progress">
|
<div v-if="uploadStore.getProgress" class="progress">
|
||||||
<div
|
<div v-bind:style="{ width: uploadStore.getProgress + '%' }"></div>
|
||||||
v-bind:style="{
|
|
||||||
width: sentPercent + '%',
|
|
||||||
}"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
<sidebar></sidebar>
|
<sidebar></sidebar>
|
||||||
<main>
|
<main>
|
||||||
@ -31,7 +27,7 @@ import Prompts from "@/components/prompts/Prompts.vue";
|
|||||||
import Shell from "@/components/Shell.vue";
|
import Shell from "@/components/Shell.vue";
|
||||||
import UploadFiles from "@/components/prompts/UploadFiles.vue";
|
import UploadFiles from "@/components/prompts/UploadFiles.vue";
|
||||||
import { enableExec } from "@/utils/constants";
|
import { enableExec } from "@/utils/constants";
|
||||||
import { computed, watch } from "vue";
|
import { watch } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
const layoutStore = useLayoutStore();
|
const layoutStore = useLayoutStore();
|
||||||
@ -40,10 +36,6 @@ const fileStore = useFileStore();
|
|||||||
const uploadStore = useUploadStore();
|
const uploadStore = useUploadStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const sentPercent = computed(() =>
|
|
||||||
((uploadStore.sentBytes / uploadStore.totalBytes) * 100).toFixed(2)
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(route, () => {
|
watch(route, () => {
|
||||||
fileStore.selected = [];
|
fileStore.selected = [];
|
||||||
fileStore.multiple = false;
|
fileStore.multiple = false;
|
||||||
|
@ -32,25 +32,17 @@
|
|||||||
/>
|
/>
|
||||||
</header-bar>
|
</header-bar>
|
||||||
|
|
||||||
<!-- preview container -->
|
|
||||||
<div class="loading delayed" v-if="layoutStore.loading">
|
|
||||||
<div class="spinner">
|
|
||||||
<div class="bounce1"></div>
|
|
||||||
<div class="bounce2"></div>
|
|
||||||
<div class="bounce3"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<Breadcrumbs base="/files" noLink />
|
<Breadcrumbs base="/files" noLink />
|
||||||
|
|
||||||
|
<!-- preview container -->
|
||||||
<div
|
<div
|
||||||
v-show="isPreview && isMarkdownFile"
|
v-show="isPreview && isMarkdownFile"
|
||||||
id="preview-container"
|
id="preview-container"
|
||||||
class="md_preview"
|
class="md_preview"
|
||||||
v-html="previewContent"
|
v-html="previewContent"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<form v-show="!isPreview || !isMarkdownFile" id="editor"></form>
|
<form v-show="!isPreview || !isMarkdownFile" id="editor"></form>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -154,19 +146,12 @@ onBeforeUnmount(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeRouteUpdate((to, from, next) => {
|
onBeforeRouteUpdate((to, from, next) => {
|
||||||
if (editor.value?.session.getUndoManager().isClean()) {
|
if (!editor.value?.session.getUndoManager().isClean()) {
|
||||||
|
layoutStore.showHover("discardEditorChanges");
|
||||||
|
next(false);
|
||||||
|
} else {
|
||||||
next();
|
next();
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutStore.showHover({
|
|
||||||
prompt: "discardEditorChanges",
|
|
||||||
confirm: (event: Event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const keyEvent = (event: KeyboardEvent) => {
|
const keyEvent = (event: KeyboardEvent) => {
|
||||||
@ -231,6 +216,13 @@ const decreaseFontSize = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
|
if (!editor.value?.session.getUndoManager().isClean()) {
|
||||||
|
layoutStore.showHover("discardEditorChanges");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStore.updateRequest(null);
|
||||||
|
|
||||||
const uri = url.removeLastDir(route.path) + "/";
|
const uri = url.removeLastDir(route.path) + "/";
|
||||||
router.push({ path: uri });
|
router.push({ path: uri });
|
||||||
};
|
};
|
||||||
|
@ -303,7 +303,6 @@ import {
|
|||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { removePrefix } from "@/api/utils";
|
|
||||||
|
|
||||||
const showLimit = ref<number>(50);
|
const showLimit = ref<number>(50);
|
||||||
const columnWidth = ref<number>(280);
|
const columnWidth = ref<number>(280);
|
||||||
@ -421,6 +420,10 @@ const isMobile = computed(() => {
|
|||||||
|
|
||||||
watch(req, () => {
|
watch(req, () => {
|
||||||
// Reset the show value
|
// Reset the show value
|
||||||
|
if (
|
||||||
|
window.sessionStorage.getItem("listFrozen") !== "true" &&
|
||||||
|
window.sessionStorage.getItem("modified") !== "true"
|
||||||
|
) {
|
||||||
showLimit.value = 50;
|
showLimit.value = 50;
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@ -428,12 +431,14 @@ watch(req, () => {
|
|||||||
// How much every listing item affects the window height
|
// How much every listing item affects the window height
|
||||||
setItemWeight();
|
setItemWeight();
|
||||||
|
|
||||||
// Scroll to the item opened previously
|
|
||||||
if (!revealPreviousItem()) {
|
|
||||||
// Fill and fit the window with listing items
|
// Fill and fit the window with listing items
|
||||||
fillWindow(true);
|
fillWindow(true);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
if (req.value?.isDir) {
|
||||||
|
window.sessionStorage.setItem("listFrozen", "false");
|
||||||
|
window.sessionStorage.setItem("modified", "false");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -443,11 +448,8 @@ onMounted(() => {
|
|||||||
// How much every listing item affects the window height
|
// How much every listing item affects the window height
|
||||||
setItemWeight();
|
setItemWeight();
|
||||||
|
|
||||||
// Scroll to the item opened previously
|
|
||||||
if (!revealPreviousItem()) {
|
|
||||||
// Fill and fit the window with listing items
|
// Fill and fit the window with listing items
|
||||||
fillWindow(true);
|
fillWindow(true);
|
||||||
}
|
|
||||||
|
|
||||||
// Add the needed event listeners to the window and document.
|
// Add the needed event listeners to the window and document.
|
||||||
window.addEventListener("keydown", keyEvent);
|
window.addEventListener("keydown", keyEvent);
|
||||||
@ -587,13 +589,10 @@ const paste = (event: Event) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const preselect = removePrefix(route.path) + items[0].name;
|
|
||||||
|
|
||||||
let action = (overwrite: boolean, rename: boolean) => {
|
let action = (overwrite: boolean, rename: boolean) => {
|
||||||
api
|
api
|
||||||
.copy(items, overwrite, rename)
|
.copy(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
fileStore.preselect = preselect;
|
|
||||||
fileStore.reload = true;
|
fileStore.reload = true;
|
||||||
})
|
})
|
||||||
.catch($showError);
|
.catch($showError);
|
||||||
@ -605,7 +604,6 @@ const paste = (event: Event) => {
|
|||||||
.move(items, overwrite, rename)
|
.move(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
clipboardStore.resetClipboard();
|
clipboardStore.resetClipboard();
|
||||||
fileStore.preselect = preselect;
|
|
||||||
fileStore.reload = true;
|
fileStore.reload = true;
|
||||||
})
|
})
|
||||||
.catch($showError);
|
.catch($showError);
|
||||||
@ -733,8 +731,6 @@ const drop = async (event: DragEvent) => {
|
|||||||
|
|
||||||
const conflict = upload.checkConflict(files, items);
|
const conflict = upload.checkConflict(files, items);
|
||||||
|
|
||||||
const preselect = removePrefix(path) + (files[0].fullPath || files[0].name);
|
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
layoutStore.showHover({
|
layoutStore.showHover({
|
||||||
prompt: "replace",
|
prompt: "replace",
|
||||||
@ -742,13 +738,11 @@ const drop = async (event: DragEvent) => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
layoutStore.closeHovers();
|
layoutStore.closeHovers();
|
||||||
upload.handleFiles(files, path, false);
|
upload.handleFiles(files, path, false);
|
||||||
fileStore.preselect = preselect;
|
|
||||||
},
|
},
|
||||||
confirm: (event: Event) => {
|
confirm: (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
layoutStore.closeHovers();
|
layoutStore.closeHovers();
|
||||||
upload.handleFiles(files, path, true);
|
upload.handleFiles(files, path, true);
|
||||||
fileStore.preselect = preselect;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -756,7 +750,6 @@ const drop = async (event: DragEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
upload.handleFiles(files, path);
|
upload.handleFiles(files, path);
|
||||||
fileStore.preselect = preselect;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadInput = (event: Event) => {
|
const uploadInput = (event: Event) => {
|
||||||
@ -960,21 +953,4 @@ const fillWindow = (fit = false) => {
|
|||||||
// Set the number of displayed items
|
// Set the number of displayed items
|
||||||
showLimit.value = showQuantity > totalItems ? totalItems : showQuantity;
|
showLimit.value = showQuantity > totalItems ? totalItems : showQuantity;
|
||||||
};
|
};
|
||||||
|
|
||||||
const revealPreviousItem = () => {
|
|
||||||
if (!fileStore.req || !fileStore.oldReq) return;
|
|
||||||
|
|
||||||
const index = fileStore.selected[0];
|
|
||||||
if (index === undefined) return;
|
|
||||||
|
|
||||||
showLimit.value =
|
|
||||||
index + Math.ceil((window.innerHeight * 2) / itemWeight.value);
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
const items = document.querySelectorAll("#listing .item");
|
|
||||||
items[index].scrollIntoView({ block: "center" });
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -301,8 +301,10 @@ watch(route, () => {
|
|||||||
// Specify hooks
|
// Specify hooks
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
window.addEventListener("keydown", key);
|
window.addEventListener("keydown", key);
|
||||||
listing.value = fileStore.oldReq?.items ?? null;
|
if (fileStore.oldReq) {
|
||||||
|
listing.value = fileStore.oldReq.items;
|
||||||
updatePreview();
|
updatePreview();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => window.removeEventListener("keydown", key));
|
onBeforeUnmount(() => window.removeEventListener("keydown", key));
|
||||||
@ -315,16 +317,11 @@ const deleteFile = () => {
|
|||||||
if (listing.value === null) {
|
if (listing.value === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
listing.value = listing.value.filter((item) => item.name !== name.value);
|
||||||
const index = listing.value.findIndex((item) => item.name == name.value);
|
|
||||||
listing.value.splice(index, 1);
|
|
||||||
|
|
||||||
if (hasNext.value) {
|
if (hasNext.value) {
|
||||||
next();
|
next();
|
||||||
} else if (!hasPrevious.value && !hasNext.value) {
|
} else if (!hasPrevious.value && !hasNext.value) {
|
||||||
const nearbyItem = listing.value[Math.max(0, index - 1)];
|
|
||||||
fileStore.preselect = nearbyItem?.path;
|
|
||||||
|
|
||||||
close();
|
close();
|
||||||
} else {
|
} else {
|
||||||
prev();
|
prev();
|
||||||
@ -430,6 +427,8 @@ const toggleNavigation = throttle(function () {
|
|||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
|
fileStore.updateRequest(null);
|
||||||
|
|
||||||
const uri = url.removeLastDir(route.path) + "/";
|
const uri = url.removeLastDir(route.path) + "/";
|
||||||
router.push({ path: uri });
|
router.push({ path: uri });
|
||||||
};
|
};
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
"exclude": [
|
"exclude": [
|
||||||
"src/components/Shell.vue",
|
"src/components/Shell.vue",
|
||||||
"src/components/prompts/Copy.vue",
|
"src/components/prompts/Copy.vue",
|
||||||
"src/components/prompts/Move.vue",
|
|
||||||
"src/components/prompts/Delete.vue",
|
"src/components/prompts/Delete.vue",
|
||||||
"src/components/prompts/FileList.vue",
|
"src/components/prompts/FileList.vue",
|
||||||
"src/components/prompts/Rename.vue",
|
"src/components/prompts/Rename.vue",
|
||||||
"src/components/prompts/Share.vue"
|
"src/components/prompts/Share.vue",
|
||||||
|
"src/components/prompts/UploadFiles.vue"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
38
go.mod
38
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/filebrowser/filebrowser/v2
|
module github.com/filebrowser/filebrowser/v2
|
||||||
|
|
||||||
go 1.24
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/asdine/storm/v3 v3.2.1
|
github.com/asdine/storm/v3 v3.2.1
|
||||||
@ -11,7 +11,6 @@ require (
|
|||||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/jellydator/ttlcache/v3 v3.4.0
|
|
||||||
github.com/maruel/natural v1.1.1
|
github.com/maruel/natural v1.1.1
|
||||||
github.com/marusama/semaphore/v2 v2.5.0
|
github.com/marusama/semaphore/v2 v2.5.0
|
||||||
github.com/mholt/archives v0.1.3
|
github.com/mholt/archives v0.1.3
|
||||||
@ -24,21 +23,21 @@ require (
|
|||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
|
||||||
go.etcd.io/bbolt v1.4.2
|
go.etcd.io/bbolt v1.4.1
|
||||||
golang.org/x/crypto v0.40.0
|
golang.org/x/crypto v0.39.0
|
||||||
golang.org/x/image v0.29.0
|
golang.org/x/image v0.28.0
|
||||||
golang.org/x/text v0.27.0
|
golang.org/x/text v0.26.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
github.com/STARRY-S/zip v0.2.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
|
||||||
github.com/asticode/go-astikit v0.56.0 // indirect
|
github.com/asticode/go-astikit v0.55.0 // indirect
|
||||||
github.com/asticode/go-astits v1.13.0 // indirect
|
github.com/asticode/go-astits v1.13.0 // indirect
|
||||||
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.0 // indirect
|
||||||
github.com/bodgit/windows v1.0.1 // indirect
|
github.com/bodgit/windows v1.0.1 // 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
|
||||||
@ -47,21 +46,24 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/go-errors/errors v1.5.1 // indirect
|
github.com/go-errors/errors v1.5.1 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
||||||
github.com/golang/geo v0.0.0-20250707181242-c5087ca84cf4 // indirect
|
github.com/golang/geo v0.0.0-20250606134707-e8fe6a72b492 // indirect
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jellydator/ttlcache/v3 v3.4.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
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.0 // indirect
|
||||||
github.com/nwaples/rardecode/v2 v2.1.1 // indirect
|
github.com/nwaples/rardecode/v2 v2.1.0 // 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/sagikazarmark/locafero v0.9.0 // indirect
|
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||||
github.com/sorairolake/lzip-go v0.3.7 // indirect
|
github.com/sorairolake/lzip-go v0.3.5 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/cast v1.9.2 // indirect
|
github.com/spf13/cast v1.9.2 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
@ -69,9 +71,9 @@ require (
|
|||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||||
golang.org/x/net v0.42.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.15.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
82
go.sum
82
go.sum
@ -19,18 +19,18 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
|
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
|
||||||
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||||
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
|
||||||
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
|
||||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
|
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
|
||||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
||||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ=
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
|
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
|
||||||
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
|
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
|
||||||
github.com/asticode/go-astikit v0.20.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
github.com/asticode/go-astikit v0.20.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
||||||
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
||||||
github.com/asticode/go-astikit v0.56.0 h1:DmD2p7YnvxiPdF0h+dRmos3bsejNEXbycENsY5JfBqw=
|
github.com/asticode/go-astikit v0.55.0 h1:jdR6djfjAF2SwtFu1hzwkenCRejzOZUREsr3xPAPHeg=
|
||||||
github.com/asticode/go-astikit v0.56.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE=
|
github.com/asticode/go-astikit v0.55.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE=
|
||||||
github.com/asticode/go-astisub v0.34.0 h1:owKNj0A9pc7YVW/rNy2MJZ1mf0L8DTdklZVfyZDhTWI=
|
github.com/asticode/go-astisub v0.34.0 h1:owKNj0A9pc7YVW/rNy2MJZ1mf0L8DTdklZVfyZDhTWI=
|
||||||
github.com/asticode/go-astisub v0.34.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8=
|
github.com/asticode/go-astisub v0.34.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8=
|
||||||
github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ=
|
github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ=
|
||||||
@ -38,8 +38,8 @@ github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwf
|
|||||||
github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
|
github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
|
||||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||||
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
|
github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
|
||||||
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
|
github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
|
||||||
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||||
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
@ -93,15 +93,15 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
|||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
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-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-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-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-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
github.com/golang/geo v0.0.0-20250707181242-c5087ca84cf4 h1:vCeHcs8N7MOccOOsOVIy1xcYu+kBkA4J5urTgigww7c=
|
github.com/golang/geo v0.0.0-20250606134707-e8fe6a72b492 h1:8mHyM6CCmj/DQAhHXJVTgdkg/6hAH71N7qGEF+t4Bzg=
|
||||||
github.com/golang/geo v0.0.0-20250707181242-c5087ca84cf4/go.mod h1:AN0OjM34c3PbjAsX+QNma1nYtJtRxl+s9MZNV7S+efw=
|
github.com/golang/geo v0.0.0-20250606134707-e8fe6a72b492/go.mod h1:Vaw7L5b+xa3Rj4/pRtrQkymn3lSBRB/NAEdbF9YEVLA=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@ -113,9 +113,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
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.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
@ -139,6 +138,11 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
|||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
@ -175,12 +179,12 @@ github.com/mholt/archives v0.1.3 h1:aEAaOtNra78G+TvV5ohmXrJOAzf++dIlYeDW3N9q458=
|
|||||||
github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU=
|
github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU=
|
||||||
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||||
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||||
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
|
github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ=
|
||||||
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/nwaples/rardecode/v2 v2.1.1 h1:OJaYalXdliBUXPmC8CZGQ7oZDxzX1/5mQmgn0/GASew=
|
github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U=
|
||||||
github.com/nwaples/rardecode/v2 v2.1.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||||
@ -193,16 +197,16 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
|
|||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
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.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
github.com/sorairolake/lzip-go v0.3.7 h1:vP2uiD/NoklLyzYMdgOWkZME0ulkSfVTTE4MNRKCwNs=
|
github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
|
||||||
github.com/sorairolake/lzip-go v0.3.7/go.mod h1:THOHr0FlNVCw2eOIEE9shFJAG1QxQg/pf2XUPAmNIqg=
|
github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||||
@ -218,8 +222,6 @@ github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqj
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
@ -242,14 +244,12 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
|||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
|
go.etcd.io/bbolt v1.4.1 h1:5mOV+HWjIPLEAlUGMsveaUvK2+byZMFOzojoi7bh7uI=
|
||||||
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
|
go.etcd.io/bbolt v1.4.1/go.mod h1:c8zu2BnXWTu2XM4XcICtbGSl9cFwsXtcf9zLt2OncM8=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
||||||
@ -260,8 +260,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -273,8 +273,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
|||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
|
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
||||||
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
|
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -313,8 +313,8 @@ 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-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.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.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -327,8 +327,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -354,8 +354,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.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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@ -366,8 +366,8 @@ 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.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.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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -432,8 +432,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
|||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
|
||||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -106,7 +105,7 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
|||||||
|
|
||||||
// Directories creation on POST.
|
// Directories creation on POST.
|
||||||
if strings.HasSuffix(r.URL.Path, "/") {
|
if strings.HasSuffix(r.URL.Path, "/") {
|
||||||
err := d.user.Fs.MkdirAll(r.URL.Path, d.settings.DirMode)
|
err := d.user.Fs.MkdirAll(r.URL.Path, files.PermDir)
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +134,7 @@ func resourcePostHandler(fileCache FileCache) handleFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = d.RunHook(func() error {
|
err = d.RunHook(func() error {
|
||||||
info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body, d.settings.FileMode, d.settings.DirMode)
|
info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
return writeErr
|
return writeErr
|
||||||
}
|
}
|
||||||
@ -172,7 +171,7 @@ var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = d.RunHook(func() error {
|
err = d.RunHook(func() error {
|
||||||
info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body, d.settings.FileMode, d.settings.DirMode)
|
info, writeErr := writeFile(d.user.Fs, r.URL.Path, r.Body)
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
return writeErr
|
return writeErr
|
||||||
}
|
}
|
||||||
@ -244,14 +243,14 @@ func checkParent(src, dst string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addVersionSuffix(source string, afs afero.Fs) string {
|
func addVersionSuffix(source string, fs afero.Fs) string {
|
||||||
counter := 1
|
counter := 1
|
||||||
dir, name := path.Split(source)
|
dir, name := path.Split(source)
|
||||||
ext := filepath.Ext(name)
|
ext := filepath.Ext(name)
|
||||||
base := strings.TrimSuffix(name, ext)
|
base := strings.TrimSuffix(name, ext)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if _, err := afs.Stat(source); err != nil {
|
if _, err := fs.Stat(source); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
renamed := fmt.Sprintf("%s(%d)%s", base, counter, ext)
|
renamed := fmt.Sprintf("%s(%d)%s", base, counter, ext)
|
||||||
@ -262,14 +261,14 @@ func addVersionSuffix(source string, afs afero.Fs) string {
|
|||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFile(afs afero.Fs, dst string, in io.Reader, fileMode, dirMode fs.FileMode) (os.FileInfo, error) {
|
func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) {
|
||||||
dir, _ := path.Split(dst)
|
dir, _ := path.Split(dst)
|
||||||
err := afs.MkdirAll(dir, dirMode)
|
err := fs.MkdirAll(dir, files.PermDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := afs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
|
file, err := fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, files.PermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -307,7 +306,7 @@ func patchAction(ctx context.Context, action, src, dst string, d *data, fileCach
|
|||||||
return fbErrors.ErrPermissionDenied
|
return fbErrors.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileutils.Copy(d.user.Fs, src, dst, d.settings.FileMode, d.settings.DirMode)
|
return fileutils.Copy(d.user.Fs, src, dst)
|
||||||
case "rename":
|
case "rename":
|
||||||
if !d.user.Perm.Rename {
|
if !d.user.Perm.Rename {
|
||||||
return fbErrors.ErrPermissionDenied
|
return fbErrors.ErrPermissionDenied
|
||||||
@ -333,7 +332,7 @@ func patchAction(ctx context.Context, action, src, dst string, d *data, fileCach
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileutils.MoveFile(d.user.Fs, src, dst, d.settings.FileMode, d.settings.DirMode)
|
return fileutils.MoveFile(d.user.Fs, src, dst)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported action %s: %w", action, fbErrors.ErrInvalidRequestParams)
|
return fmt.Errorf("unsupported action %s: %w", action, fbErrors.ErrInvalidRequestParams)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -92,7 +91,7 @@ func tusPostHandler() handleFunc {
|
|||||||
case errors.Is(err, afero.ErrFileNotFound):
|
case errors.Is(err, afero.ErrFileNotFound):
|
||||||
dirPath := filepath.Dir(r.URL.Path)
|
dirPath := filepath.Dir(r.URL.Path)
|
||||||
if _, statErr := d.user.Fs.Stat(dirPath); os.IsNotExist(statErr) {
|
if _, statErr := d.user.Fs.Stat(dirPath); os.IsNotExist(statErr) {
|
||||||
if mkdirErr := d.user.Fs.MkdirAll(dirPath, d.settings.DirMode); mkdirErr != nil {
|
if mkdirErr := d.user.Fs.MkdirAll(dirPath, files.PermDir); mkdirErr != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,7 +120,7 @@ func tusPostHandler() handleFunc {
|
|||||||
fileFlags |= os.O_TRUNC
|
fileFlags |= os.O_TRUNC
|
||||||
}
|
}
|
||||||
|
|
||||||
openFile, err := d.user.Fs.OpenFile(r.URL.Path, fileFlags, d.settings.FileMode)
|
openFile, err := d.user.Fs.OpenFile(r.URL.Path, fileFlags, files.PermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
@ -148,12 +147,9 @@ func tusPostHandler() handleFunc {
|
|||||||
// Enables the user to utilize the PATCH endpoint for uploading file data
|
// Enables the user to utilize the PATCH endpoint for uploading file data
|
||||||
registerUpload(file.RealPath(), uploadLength)
|
registerUpload(file.RealPath(), uploadLength)
|
||||||
|
|
||||||
path, err := url.JoinPath("/", d.server.BaseURL, "/api/tus", r.URL.Path)
|
// Signal the frontend to reuse the current request URL
|
||||||
if err != nil {
|
w.Header().Set("Location", "")
|
||||||
return http.StatusBadRequest, fmt.Errorf("invalid path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Location", path)
|
|
||||||
return http.StatusCreated, nil
|
return http.StatusCreated, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -239,7 +235,7 @@ func tusPatchHandler() handleFunc {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
openFile, err := d.user.Fs.OpenFile(r.URL.Path, os.O_WRONLY|os.O_APPEND, d.settings.FileMode)
|
openFile, err := d.user.Fs.OpenFile(r.URL.Path, os.O_WRONLY|os.O_APPEND, files.PermFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, fmt.Errorf("could not open file: %w", err)
|
return http.StatusInternalServerError, fmt.Errorf("could not open file: %w", err)
|
||||||
}
|
}
|
||||||
|
7
main.go
7
main.go
@ -1,14 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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 {
|
cmd.Execute()
|
||||||
os.Exit(errors.GetExitCode(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"io/fs"
|
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -12,8 +11,6 @@ import (
|
|||||||
|
|
||||||
const DefaultUsersHomeBasePath = "/users"
|
const DefaultUsersHomeBasePath = "/users"
|
||||||
const DefaultMinimumPasswordLength = 12
|
const DefaultMinimumPasswordLength = 12
|
||||||
const DefaultFileMode = 0640
|
|
||||||
const DefaultDirMode = 0750
|
|
||||||
|
|
||||||
// AuthMethod describes an authentication method.
|
// AuthMethod describes an authentication method.
|
||||||
type AuthMethod string
|
type AuthMethod string
|
||||||
@ -32,8 +29,6 @@ type Settings struct {
|
|||||||
Shell []string `json:"shell"`
|
Shell []string `json:"shell"`
|
||||||
Rules []rules.Rule `json:"rules"`
|
Rules []rules.Rule `json:"rules"`
|
||||||
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
||||||
FileMode fs.FileMode `json:"fileMode"`
|
|
||||||
DirMode fs.FileMode `json:"dirMode"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRules implements rules.Provider.
|
// GetRules implements rules.Provider.
|
||||||
|
@ -42,12 +42,6 @@ func (s *Storage) Get() (*Settings, error) {
|
|||||||
RetryCount: DefaultTusRetryCount,
|
RetryCount: DefaultTusRetryCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if set.FileMode == 0 {
|
|
||||||
set.FileMode = DefaultFileMode
|
|
||||||
}
|
|
||||||
if set.DirMode == 0 {
|
|
||||||
set.DirMode = DefaultDirMode
|
|
||||||
}
|
|
||||||
return set, nil
|
return set, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module github.com/filebrowser/filebrowser/v2/tools
|
module github.com/filebrowser/filebrowser/v2/tools
|
||||||
|
|
||||||
go 1.24
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golangci/golangci-lint/v2 v2.1.6
|
github.com/golangci/golangci-lint/v2 v2.1.6
|
||||||
@ -69,7 +69,7 @@ require (
|
|||||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
github.com/go-toolsmith/astp v1.1.0 // indirect
|
||||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
||||||
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
|
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
|
||||||
github.com/gobwas/glob v0.2.3 // indirect
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
github.com/gofrs/flock v0.12.1 // indirect
|
github.com/gofrs/flock v0.12.1 // indirect
|
||||||
|
@ -207,8 +207,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi
|
|||||||
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
|
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
|
||||||
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
|
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
|
||||||
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
|
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
|
github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
|
||||||
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
Most of the configuration can be understood through the command line interface documentation. To access it, you need to install File Browser and run `filebrowser --help`. In this page, we cover some specific, more complex, topics.
|
Most of the configuration can be understood through our Command Line Interface documentation. Although there are some specific topics that we want to cover on this section.
|
||||||
|
|
||||||
## Custom Branding
|
## Custom Branding
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user