Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f858398ab | ||
|
|
0ac80e8387 | ||
|
|
0dca0b92d1 | ||
|
|
c9b36ba32e | ||
|
|
f2c4e78381 | ||
|
|
05bff54b71 | ||
|
|
2bd163d92a | ||
|
|
5e27ba5c8c | ||
|
|
5aaeb3b76d | ||
|
|
36fb9f562a | ||
|
|
ad99bf1801 | ||
|
|
4c2a094255 | ||
|
|
97693cc611 | ||
|
|
c6d4fcd08f | ||
|
|
dd7b9ddd85 | ||
|
|
26d62e4117 | ||
|
|
babd7783af | ||
|
|
1529e796df | ||
|
|
d4b904b92b | ||
|
|
12d4177823 | ||
|
|
8142b32f38 | ||
|
|
c5abbb4e1c | ||
|
|
65ac73414f | ||
|
|
ede4213c8e | ||
|
|
b60d291490 | ||
|
|
b9ede79888 | ||
|
|
3d2cb838d1 | ||
|
|
778734419d |
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- '*'
|
- '*'
|
||||||
test:
|
test:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.14.6
|
- image: circleci/golang:1.15.2
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
command: go test ./...
|
command: go test ./...
|
||||||
build-go:
|
build-go:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.14.6
|
- image: circleci/golang:1.15.2
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: '~/project'
|
at: '~/project'
|
||||||
@@ -49,12 +49,12 @@ jobs:
|
|||||||
- '*'
|
- '*'
|
||||||
release:
|
release:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.14.6
|
- image: circleci/golang:1.15.2
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: '~/project'
|
at: '~/project'
|
||||||
- setup_remote_docker
|
- setup_remote_docker
|
||||||
- run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
|
- run: echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
|
||||||
- run: curl -sL https://git.io/goreleaser | bash
|
- run: curl -sL https://git.io/goreleaser | bash
|
||||||
- run: docker logout
|
- run: docker logout
|
||||||
workflows:
|
workflows:
|
||||||
|
|||||||
42
CHANGELOG.md
42
CHANGELOG.md
@@ -2,6 +2,48 @@
|
|||||||
|
|
||||||
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.9.0](https://github.com/filebrowser/filebrowser/compare/v2.8.0...v2.9.0) (2020-10-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* support WKWebview custom protocol ([#1113](https://github.com/filebrowser/filebrowser/issues/1113)) ([0ac80e8](https://github.com/filebrowser/filebrowser/commit/0ac80e8387a69924284259bde448af2813d84ed1))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow start from Windows explorer ([f2c4e78](https://github.com/filebrowser/filebrowser/commit/f2c4e78381610879eda5316d38a999c89df6c14a))
|
||||||
|
* file upload missing path slash ([5e27ba5](https://github.com/filebrowser/filebrowser/commit/5e27ba5c8c1be603c6ae7fec8de48e3532dea1f7))
|
||||||
|
* preview case sensitive file extension ([05bff54](https://github.com/filebrowser/filebrowser/commit/05bff54b71543fd232f1089c40504d0cbfd106be))
|
||||||
|
* search missing path slash ([2bd163d](https://github.com/filebrowser/filebrowser/commit/2bd163d92a856d65c8d4615e37898470c1edf2f4))
|
||||||
|
|
||||||
|
## [2.8.0](https://github.com/filebrowser/filebrowser/compare/v2.7.0...v2.8.0) (2020-10-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add disable exec flag ([#1090](https://github.com/filebrowser/filebrowser/issues/1090)) ([97693cc](https://github.com/filebrowser/filebrowser/commit/97693cc6117ce1c956baede91de5dd48b904e175))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* empty commands setting ([c6d4fcd](https://github.com/filebrowser/filebrowser/commit/c6d4fcd08f5f1531c2cef514dc86019e23e7289f))
|
||||||
|
* file upload path encoding ([babd778](https://github.com/filebrowser/filebrowser/commit/babd7783afe85b790e1c558375d7b5013b2d366f))
|
||||||
|
* fix empty command name ([#1106](https://github.com/filebrowser/filebrowser/issues/1106)) ([36fb9f5](https://github.com/filebrowser/filebrowser/commit/36fb9f562a2c005ca4390fdebde0b4690201dff9))
|
||||||
|
* fix panic when accessing nonexistent .js file in static path ([#1105](https://github.com/filebrowser/filebrowser/issues/1105)) ([ad99bf1](https://github.com/filebrowser/filebrowser/commit/ad99bf180197e0e6d82231a86457585de16366a8))
|
||||||
|
* preview key shortcut conflict ([dd7b9dd](https://github.com/filebrowser/filebrowser/commit/dd7b9ddd8546361060ef99e838a691b2fc6c495a))
|
||||||
|
* search results absolute url ([26d62e4](https://github.com/filebrowser/filebrowser/commit/26d62e411716a5eb9a5a703e47484cfb3fbf3bd0))
|
||||||
|
|
||||||
|
## [2.7.0](https://github.com/filebrowser/filebrowser/compare/v2.6.2...v2.7.0) (2020-09-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add --socket-perm flag to control unix socket file permissions (closes [#1060](https://github.com/filebrowser/filebrowser/issues/1060)) ([65ac734](https://github.com/filebrowser/filebrowser/commit/65ac73414fadc4686c94803a93ff319e8f7ce9d1))
|
||||||
|
* preview mobile dropdown ([7787344](https://github.com/filebrowser/filebrowser/commit/778734419de314d4cb64d07109bbab73f8e2e42a))
|
||||||
|
* preview size button ([3d2cb83](https://github.com/filebrowser/filebrowser/commit/3d2cb838d111ee61047599f49e76de80c821f341))
|
||||||
|
* put selected files in the root of the archive (closes [#1065](https://github.com/filebrowser/filebrowser/issues/1065)) ([8142b32](https://github.com/filebrowser/filebrowser/commit/8142b32f3865eccd3331328e0d087f805d186ed5))
|
||||||
|
|
||||||
### [2.6.2](https://github.com/filebrowser/filebrowser/compare/v2.6.1...v2.6.2) (2020-08-05)
|
### [2.6.2](https://github.com/filebrowser/filebrowser/compare/v2.6.1...v2.6.2) (2020-08-05)
|
||||||
|
|
||||||
### [2.6.1](https://github.com/filebrowser/filebrowser/compare/v2.6.0...v2.6.1) (2020-07-28)
|
### [2.6.1](https://github.com/filebrowser/filebrowser/compare/v2.6.0...v2.6.1) (2020-07-28)
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
|
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
|
||||||
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
|
||||||
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
|
||||||
|
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
|
||||||
fmt.Fprintln(w, "\nDefaults:")
|
fmt.Fprintln(w, "\nDefaults:")
|
||||||
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
|
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
|
||||||
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ override the options.`,
|
|||||||
s := &settings.Settings{
|
s := &settings.Settings{
|
||||||
Key: generateKey(),
|
Key: generateKey(),
|
||||||
Signup: mustGetBool(flags, "signup"),
|
Signup: mustGetBool(flags, "signup"),
|
||||||
Shell: strings.Split(strings.TrimSpace(mustGetString(flags, "shell")), " "),
|
Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
|
||||||
AuthMethod: authMethod,
|
AuthMethod: authMethod,
|
||||||
Defaults: defaults,
|
Defaults: defaults,
|
||||||
Branding: settings.Branding{
|
Branding: settings.Branding{
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
@@ -50,7 +48,7 @@ you want to change. Other options will remain unchanged.`,
|
|||||||
case "auth.method":
|
case "auth.method":
|
||||||
hasAuth = true
|
hasAuth = true
|
||||||
case "shell":
|
case "shell":
|
||||||
set.Shell = strings.Split(strings.TrimSpace(mustGetString(flags, flag.Name)), " ")
|
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
|
||||||
case "branding.name":
|
case "branding.name":
|
||||||
set.Branding.Name = mustGetString(flags, flag.Name)
|
set.Branding.Name = mustGetString(flags, flag.Name)
|
||||||
case "branding.disableExternal":
|
case "branding.disableExternal":
|
||||||
|
|||||||
10
cmd/root.go
10
cmd/root.go
@@ -35,6 +35,7 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
|
cobra.MousetrapHelpText = ""
|
||||||
|
|
||||||
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
||||||
|
|
||||||
@@ -58,11 +59,13 @@ func addServerFlags(flags *pflag.FlagSet) {
|
|||||||
flags.StringP("key", "k", "", "tls key")
|
flags.StringP("key", "k", "", "tls key")
|
||||||
flags.StringP("root", "r", ".", "root to prepend to relative paths")
|
flags.StringP("root", "r", ".", "root to prepend to relative paths")
|
||||||
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
|
flags.String("socket", "", "socket to listen to (cannot be used with address, port, cert nor key flags)")
|
||||||
|
flags.Uint32("socket-perm", 0666, "unix socket file permissions")
|
||||||
flags.StringP("baseurl", "b", "", "base url")
|
flags.StringP("baseurl", "b", "", "base url")
|
||||||
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
||||||
flags.Int("img-processors", 4, "image processors count")
|
flags.Int("img-processors", 4, "image processors count")
|
||||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||||
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
||||||
|
flags.Bool("disable-exec", false, "disables Command Runner feature")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@@ -143,6 +146,10 @@ user created with the credentials from options "username" and "password".`,
|
|||||||
case server.Socket != "":
|
case server.Socket != "":
|
||||||
listener, err = net.Listen("unix", server.Socket)
|
listener, err = net.Listen("unix", server.Socket)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
||||||
|
checkErr(err)
|
||||||
|
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
||||||
|
checkErr(err)
|
||||||
case server.TLSKey != "" && server.TLSCert != "":
|
case server.TLSKey != "" && server.TLSCert != "":
|
||||||
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:shadow
|
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:shadow
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
@@ -236,6 +243,9 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
|||||||
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
|
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
|
||||||
server.ResizePreview = !disablePreviewResize
|
server.ResizePreview = !disablePreviewResize
|
||||||
|
|
||||||
|
_, disableExec := getParamB(flags, "disable-exec")
|
||||||
|
server.EnableExec = !disableExec
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
cmd/utils.go
13
cmd/utils.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -178,3 +179,15 @@ func cleanUpMapValue(v interface{}) interface{} {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertCmdStrToCmdArray checks if cmd string is blank (whitespace included)
|
||||||
|
// then returns empty string array, else returns the splitted word array of cmd.
|
||||||
|
// This is to ensure the result will never be []string{""}
|
||||||
|
func convertCmdStrToCmdArray(cmd string) []string {
|
||||||
|
var cmdArray []string
|
||||||
|
trimmedCmdStr := strings.TrimSpace(cmd)
|
||||||
|
if trimmedCmdStr != "" {
|
||||||
|
cmdArray = strings.Split(trimmedCmdStr, " ")
|
||||||
|
}
|
||||||
|
return cmdArray
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package fileutils
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
@@ -50,3 +51,59 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommonPrefix returns common directory path of provided files
|
||||||
|
func CommonPrefix(sep byte, paths ...string) string {
|
||||||
|
// Handle special cases.
|
||||||
|
switch len(paths) {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
case 1:
|
||||||
|
return path.Clean(paths[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note, we treat string as []byte, not []rune as is often
|
||||||
|
// done in Go. (And sep as byte, not rune). This is because
|
||||||
|
// most/all supported OS' treat paths as string of non-zero
|
||||||
|
// bytes. A filename may be displayed as a sequence of Unicode
|
||||||
|
// runes (typically encoded as UTF-8) but paths are
|
||||||
|
// not required to be valid UTF-8 or in any normalized form
|
||||||
|
// (e.g. "é" (U+00C9) and "é" (U+0065,U+0301) are different
|
||||||
|
// file names.
|
||||||
|
c := []byte(path.Clean(paths[0]))
|
||||||
|
|
||||||
|
// We add a trailing sep to handle the case where the
|
||||||
|
// common prefix directory is included in the path list
|
||||||
|
// (e.g. /home/user1, /home/user1/foo, /home/user1/bar).
|
||||||
|
// path.Clean will have cleaned off trailing / separators with
|
||||||
|
// the exception of the root directory, "/" (in which case we
|
||||||
|
// make it "//", but this will get fixed up to "/" bellow).
|
||||||
|
c = append(c, sep)
|
||||||
|
|
||||||
|
// Ignore the first path since it's already in c
|
||||||
|
for _, v := range paths[1:] {
|
||||||
|
// Clean up each path before testing it
|
||||||
|
v = path.Clean(v) + string(sep)
|
||||||
|
|
||||||
|
// Find the first non-common byte and truncate c
|
||||||
|
if len(v) < len(c) {
|
||||||
|
c = c[:len(v)]
|
||||||
|
}
|
||||||
|
for i := 0; i < len(c); i++ {
|
||||||
|
if v[i] != c[i] {
|
||||||
|
c = c[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing non-separator characters and the final separator
|
||||||
|
for i := len(c) - 1; i >= 0; i-- {
|
||||||
|
if c[i] == sep {
|
||||||
|
c = c[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|||||||
46
fileutils/file_test.go
Normal file
46
fileutils/file_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package fileutils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCommonPrefix(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
paths []string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
"same lvl": {
|
||||||
|
paths: []string{
|
||||||
|
"/home/user/file1",
|
||||||
|
"/home/user/file2",
|
||||||
|
},
|
||||||
|
want: "/home/user",
|
||||||
|
},
|
||||||
|
"sub folder": {
|
||||||
|
paths: []string{
|
||||||
|
"/home/user/folder",
|
||||||
|
"/home/user/folder/file",
|
||||||
|
},
|
||||||
|
want: "/home/user/folder",
|
||||||
|
},
|
||||||
|
"relative path": {
|
||||||
|
paths: []string{
|
||||||
|
"/home/user/folder",
|
||||||
|
"/home/user/folder/../folder2",
|
||||||
|
},
|
||||||
|
want: "/home/user",
|
||||||
|
},
|
||||||
|
"no common path": {
|
||||||
|
paths: []string{
|
||||||
|
"/home/user/folder",
|
||||||
|
"/etc/file",
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tt := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
if got := CommonPrefix('/', tt.paths...); got != tt.want {
|
||||||
|
t.Errorf("CommonPrefix() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,24 +13,25 @@
|
|||||||
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
|
||||||
|
|
||||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||||
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
|
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
|
||||||
<meta name="theme-color" content="#2979ff">
|
<meta name="theme-color" content="#2979ff">
|
||||||
|
|
||||||
<!-- Add to home screen for Safari on iOS -->
|
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="apple-mobile-web-app-title" content="assets">
|
<meta name="apple-mobile-web-app-title" content="assets">
|
||||||
<link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon-152x152.png">
|
<link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png">
|
||||||
|
|
||||||
<!-- Add to home screen for Windows -->
|
<!-- Add to home screen for Windows -->
|
||||||
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/msapplication-icon-144x144.png">
|
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png">
|
||||||
<meta name="msapplication-TileColor" content="#2979ff">
|
<meta name="msapplication-TileColor" content="#2979ff">
|
||||||
|
|
||||||
<!-- Inject Some Variables and generate the manifest json -->
|
<!-- Inject Some Variables and generate the manifest json -->
|
||||||
<script>
|
<script>
|
||||||
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
|
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
|
||||||
|
|
||||||
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
|
||||||
var dynamicManifest = {
|
var dynamicManifest = {
|
||||||
"name": window.FileBrowser.Name || 'File Browser',
|
"name": window.FileBrowser.Name || 'File Browser',
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ export function download (format, ...files) {
|
|||||||
export async function post (url, content = '', overwrite = false, onupload) {
|
export async function post (url, content = '', overwrite = false, onupload) {
|
||||||
url = removePrefix(url)
|
url = removePrefix(url)
|
||||||
|
|
||||||
|
let bufferContent
|
||||||
|
if (content instanceof Blob && !['http:', 'https:'].includes(window.location.protocol)) {
|
||||||
|
bufferContent = await new Response(content).arrayBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new XMLHttpRequest()
|
let request = new XMLHttpRequest()
|
||||||
request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true)
|
request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true)
|
||||||
@@ -108,7 +113,7 @@ export async function post (url, content = '', overwrite = false, onupload) {
|
|||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.send(content)
|
request.send(bufferContent || content)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,31 @@
|
|||||||
import { fetchJSON, removePrefix } from './utils'
|
import { fetchURL, removePrefix } from './utils'
|
||||||
|
import url from '../utils/url'
|
||||||
|
|
||||||
export default async function search (url, query) {
|
export default async function search (base, query) {
|
||||||
url = removePrefix(url)
|
base = removePrefix(base)
|
||||||
query = encodeURIComponent(query)
|
query = encodeURIComponent(query)
|
||||||
|
|
||||||
return fetchJSON(`/api/search${url}?query=${query}`, {})
|
if (!base.endsWith('/')) {
|
||||||
}
|
base += '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await fetchURL(`/api/search${base}?query=${query}`, {})
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
let data = await res.json()
|
||||||
|
|
||||||
|
data = data.map((item) => {
|
||||||
|
item.url = `/files${base}` + url.encodePath(item.path)
|
||||||
|
|
||||||
|
if (item.dir) {
|
||||||
|
item.url += '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
throw Error(res.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<header v-if="!isEditor">
|
<header v-if="!isEditor && !isPreview">
|
||||||
<div>
|
<div>
|
||||||
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
|
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
|
||||||
<i class="material-icons">menu</i>
|
<i class="material-icons">menu</i>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<delete-button v-show="showDeleteButton"></delete-button>
|
<delete-button v-show="showDeleteButton"></delete-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<shell-button v-show="user.perm.execute" />
|
<shell-button v-if="isExecEnabled && user.perm.execute" />
|
||||||
<switch-button v-show="isListing"></switch-button>
|
<switch-button v-show="isListing"></switch-button>
|
||||||
<download-button v-show="showDownloadButton"></download-button>
|
<download-button v-show="showDownloadButton"></download-button>
|
||||||
<upload-button v-show="showUpload"></upload-button>
|
<upload-button v-show="showUpload"></upload-button>
|
||||||
@@ -68,7 +68,7 @@ import CopyButton from './buttons/Copy'
|
|||||||
import ShareButton from './buttons/Share'
|
import ShareButton from './buttons/Share'
|
||||||
import ShellButton from './buttons/Shell'
|
import ShellButton from './buttons/Shell'
|
||||||
import {mapGetters, mapState} from 'vuex'
|
import {mapGetters, mapState} from 'vuex'
|
||||||
import { logoURL } from '@/utils/constants'
|
import { logoURL, enableExec } from '@/utils/constants'
|
||||||
import * as api from '@/api'
|
import * as api from '@/api'
|
||||||
import buttons from '@/utils/buttons'
|
import buttons from '@/utils/buttons'
|
||||||
|
|
||||||
@@ -108,6 +108,7 @@ export default {
|
|||||||
'selectedCount',
|
'selectedCount',
|
||||||
'isFiles',
|
'isFiles',
|
||||||
'isEditor',
|
'isEditor',
|
||||||
|
'isPreview',
|
||||||
'isListing',
|
'isListing',
|
||||||
'isLogged'
|
'isLogged'
|
||||||
]),
|
]),
|
||||||
@@ -119,6 +120,7 @@ export default {
|
|||||||
'multiple'
|
'multiple'
|
||||||
]),
|
]),
|
||||||
logoURL: () => logoURL,
|
logoURL: () => logoURL,
|
||||||
|
isExecEnabled: () => enableExec,
|
||||||
isMobile () {
|
isMobile () {
|
||||||
return this.width <= 736
|
return this.width <= 736
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<ul v-show="results.length > 0">
|
<ul v-show="results.length > 0">
|
||||||
<li v-for="(s,k) in filteredResults" :key="k">
|
<li v-for="(s,k) in filteredResults" :key="k">
|
||||||
<router-link @click.native="close" :to="'./' + s.path">
|
<router-link @click.native="close" :to="s.url">
|
||||||
<i v-if="s.dir" class="material-icons">folder</i>
|
<i v-if="s.dir" class="material-icons">folder</i>
|
||||||
<i v-else class="material-icons">insert_drive_file</i>
|
<i v-else class="material-icons">insert_drive_file</i>
|
||||||
<span>./{{ s.path }}</span>
|
<span>./{{ s.path }}</span>
|
||||||
@@ -183,8 +183,12 @@ export default {
|
|||||||
|
|
||||||
this.ongoing = true
|
this.ongoing = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.results = await search(path, this.value)
|
||||||
|
} catch (error) {
|
||||||
|
this.$showError(error)
|
||||||
|
}
|
||||||
|
|
||||||
this.results = await search(path, this.value)
|
|
||||||
this.ongoing = false
|
this.ongoing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
frontend/src/components/buttons/PreviewSize.vue
Normal file
22
frontend/src/components/buttons/PreviewSize.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="$emit('change-size')">
|
||||||
|
<i class="material-icons">{{ this.icon }}</i>
|
||||||
|
<span>{{ $t('buttons.info') }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'preview-size-button',
|
||||||
|
props: [ 'size' ],
|
||||||
|
computed: {
|
||||||
|
icon () {
|
||||||
|
if (this.size) {
|
||||||
|
return 'photo_size_select_large'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'hd'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -77,6 +77,13 @@ export default {
|
|||||||
window.removeEventListener('resize', this.onResize)
|
window.removeEventListener('resize', this.onResize)
|
||||||
document.removeEventListener('mouseup', this.onMouseUp)
|
document.removeEventListener('mouseup', this.onMouseUp)
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
src: function () {
|
||||||
|
this.scale = 1
|
||||||
|
this.setZoom()
|
||||||
|
this.setCenter()
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onLoad() {
|
onLoad() {
|
||||||
let img = this.$refs.imgex
|
let img = this.$refs.imgex
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let files = await upload.scanFiles(dt)
|
let files = await upload.scanFiles(dt)
|
||||||
let path = this.$route.path + base
|
let path = this.$route.path.endsWith('/') ? this.$route.path + base : this.$route.path + '/' + base
|
||||||
let items = this.req.items
|
let items = this.req.items
|
||||||
|
|
||||||
if (base !== '') {
|
if (base !== '') {
|
||||||
@@ -409,7 +409,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = this.$route.path
|
let path = this.$route.path.endsWith('/') ? this.$route.path : this.$route.path + '/'
|
||||||
let conflict = upload.checkConflict(files, this.req.items)
|
let conflict = upload.checkConflict(files, this.req.items)
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
|
|||||||
@@ -9,10 +9,17 @@
|
|||||||
<span>{{ this.name }}</span>
|
<span>{{ this.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<rename-button :disabled="loading" v-if="user.perm.rename"></rename-button>
|
<preview-size-button v-if="isResizeEnabled && this.req.type === 'image'" @change-size="toggleSize" v-bind:size="fullSize" :disabled="loading"></preview-size-button>
|
||||||
<delete-button :disabled="loading" v-if="user.perm.delete"></delete-button>
|
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
|
||||||
<download-button :disabled="loading" v-if="user.perm.download"></download-button>
|
<i class="material-icons">more_vert</i>
|
||||||
<info-button :disabled="loading"></info-button>
|
</button>
|
||||||
|
|
||||||
|
<div id="dropdown" :class="{ active : showMore }">
|
||||||
|
<rename-button :disabled="loading" v-if="user.perm.rename"></rename-button>
|
||||||
|
<delete-button :disabled="loading" v-if="user.perm.delete"></delete-button>
|
||||||
|
<download-button :disabled="loading" v-if="user.perm.download"></download-button>
|
||||||
|
<info-button :disabled="loading"></info-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="loading" v-if="loading">
|
<div class="loading" v-if="loading">
|
||||||
@@ -45,20 +52,23 @@
|
|||||||
but don't worry, you can <a :href="download">download it</a>
|
but don't worry, you can <a :href="download">download it</a>
|
||||||
and watch it with your favorite video player!
|
and watch it with your favorite video player!
|
||||||
</video>
|
</video>
|
||||||
<object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw"></object>
|
<object v-else-if="req.extension.toLowerCase() == '.pdf'" class="pdf" :data="raw"></object>
|
||||||
<a v-else-if="req.type == 'blob'" :href="download">
|
<a v-else-if="req.type == 'blob'" :href="download">
|
||||||
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2>
|
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<div v-show="showMore" @click="resetPrompts" class="overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import url from '@/utils/url'
|
import url from '@/utils/url'
|
||||||
import { baseURL } from '@/utils/constants'
|
import { baseURL, resizePreview } from '@/utils/constants'
|
||||||
import { files as api } from '@/api'
|
import { files as api } from '@/api'
|
||||||
|
import PreviewSizeButton from '@/components/buttons/PreviewSize'
|
||||||
import InfoButton from '@/components/buttons/Info'
|
import InfoButton from '@/components/buttons/Info'
|
||||||
import DeleteButton from '@/components/buttons/Delete'
|
import DeleteButton from '@/components/buttons/Delete'
|
||||||
import RenameButton from '@/components/buttons/Rename'
|
import RenameButton from '@/components/buttons/Rename'
|
||||||
@@ -75,6 +85,7 @@ const mediaTypes = [
|
|||||||
export default {
|
export default {
|
||||||
name: 'preview',
|
name: 'preview',
|
||||||
components: {
|
components: {
|
||||||
|
PreviewSizeButton,
|
||||||
InfoButton,
|
InfoButton,
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
RenameButton,
|
RenameButton,
|
||||||
@@ -87,11 +98,12 @@ export default {
|
|||||||
nextLink: '',
|
nextLink: '',
|
||||||
listing: null,
|
listing: null,
|
||||||
name: '',
|
name: '',
|
||||||
subtitles: []
|
subtitles: [],
|
||||||
|
fullSize: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['req', 'user', 'oldReq', 'jwt', 'loading']),
|
...mapState(['req', 'user', 'oldReq', 'jwt', 'loading', 'show']),
|
||||||
hasPrevious () {
|
hasPrevious () {
|
||||||
return (this.previousLink !== '')
|
return (this.previousLink !== '')
|
||||||
},
|
},
|
||||||
@@ -102,13 +114,19 @@ export default {
|
|||||||
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
|
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
|
||||||
},
|
},
|
||||||
previewUrl () {
|
previewUrl () {
|
||||||
if (this.req.type === 'image') {
|
if (this.req.type === 'image' && !this.fullSize) {
|
||||||
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}`
|
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}`
|
||||||
}
|
}
|
||||||
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
|
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
|
||||||
},
|
},
|
||||||
raw () {
|
raw () {
|
||||||
return `${this.previewUrl}&inline=true`
|
return `${this.previewUrl}&inline=true`
|
||||||
|
},
|
||||||
|
showMore () {
|
||||||
|
return this.$store.state.show === 'more'
|
||||||
|
},
|
||||||
|
isResizeEnabled () {
|
||||||
|
return resizePreview
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -141,6 +159,10 @@ export default {
|
|||||||
key (event) {
|
key (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (this.show !== null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (event.which === 13 || event.which === 39) { // right arrow
|
if (event.which === 13 || event.which === 39) { // right arrow
|
||||||
if (this.hasNext) this.next()
|
if (this.hasNext) this.next()
|
||||||
} else if (event.which === 37) { // left arrow
|
} else if (event.which === 37) { // left arrow
|
||||||
@@ -189,6 +211,15 @@ export default {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
openMore () {
|
||||||
|
this.$store.commit('showHover', 'more')
|
||||||
|
},
|
||||||
|
resetPrompts () {
|
||||||
|
this.$store.commit('closeHovers')
|
||||||
|
},
|
||||||
|
toggleSize () {
|
||||||
|
this.fullSize = !this.fullSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ export default {
|
|||||||
return this.commands.join(' ')
|
return this.commands.join(' ')
|
||||||
},
|
},
|
||||||
set (value) {
|
set (value) {
|
||||||
this.$emit('update:commands', value.split(' '))
|
if (value !== '') {
|
||||||
|
this.$emit('update:commands', value.split(' '))
|
||||||
|
} else {
|
||||||
|
this.$emit('update:commands', [])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p>
|
<p v-if="isExecEnabled"><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p>
|
||||||
<p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p>
|
<p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { enableExec } from '@/utils/constants'
|
||||||
export default {
|
export default {
|
||||||
name: 'permissions',
|
name: 'permissions',
|
||||||
props: ['perm'],
|
props: ['perm'],
|
||||||
@@ -33,7 +34,8 @@ export default {
|
|||||||
|
|
||||||
this.perm.admin = value
|
this.perm.admin = value
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
isExecEnabled: () => enableExec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<permissions :perm.sync="user.perm" />
|
<permissions :perm.sync="user.perm" />
|
||||||
<commands :commands.sync="user.commands" />
|
<commands v-if="isExecEnabled" :commands.sync="user.commands" />
|
||||||
|
|
||||||
<div v-if="!isDefault">
|
<div v-if="!isDefault">
|
||||||
<h3>{{ $t('settings.rules') }}</h3>
|
<h3>{{ $t('settings.rules') }}</h3>
|
||||||
@@ -40,6 +40,7 @@ import Languages from './Languages'
|
|||||||
import Rules from './Rules'
|
import Rules from './Rules'
|
||||||
import Permissions from './Permissions'
|
import Permissions from './Permissions'
|
||||||
import Commands from './Commands'
|
import Commands from './Commands'
|
||||||
|
import { enableExec } from '@/utils/constants'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'user',
|
name: 'user',
|
||||||
@@ -53,7 +54,8 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
passwordPlaceholder () {
|
passwordPlaceholder () {
|
||||||
return this.isNew ? '' : this.$t('settings.avoidChanges')
|
return this.isNew ? '' : this.$t('settings.avoidChanges')
|
||||||
}
|
},
|
||||||
|
isExecEnabled: () => enableExec
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'user.perm.admin': function () {
|
'user.perm.admin': function () {
|
||||||
|
|||||||
@@ -179,7 +179,7 @@
|
|||||||
"userDefaults": "User default settings",
|
"userDefaults": "User default settings",
|
||||||
"defaultUserDescription": "This are the default settings for new users.",
|
"defaultUserDescription": "This are the default settings for new users.",
|
||||||
"executeOnShell": "Execute on shell",
|
"executeOnShell": "Execute on shell",
|
||||||
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
|
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
|
||||||
"perm": {
|
"perm": {
|
||||||
"create": "Create files and directories",
|
"create": "Create files and directories",
|
||||||
"delete": "Delete files and directories",
|
"delete": "Delete files and directories",
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
"home": "Accueil",
|
"home": "Accueil",
|
||||||
"lastModified": "Dernière modification",
|
"lastModified": "Dernière modification",
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
"lonely": "Il semble qu'il n'y ai rien par ici...",
|
"lonely": "Il semble qu'il n'y ait rien par ici...",
|
||||||
"metadata": "Metadonnées",
|
"metadata": "Metadonnées",
|
||||||
"multipleSelectionEnabled": "Sélection multiple activée",
|
"multipleSelectionEnabled": "Sélection multiple activée",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
|
|||||||
@@ -90,8 +90,8 @@
|
|||||||
"copy": "复制",
|
"copy": "复制",
|
||||||
"copyMessage": "请选择欲复制至的目录:",
|
"copyMessage": "请选择欲复制至的目录:",
|
||||||
"currentlyNavigating": "当前目录:",
|
"currentlyNavigating": "当前目录:",
|
||||||
"deleteMessageMultiple": "你确定要删除这 {count} 个文件吗?",
|
"deleteMessageMultiple": "你确定要删除这 {count} 个文件吗?",
|
||||||
"deleteMessageSingle": "你确定要删除这个文件/文件夹吗?",
|
"deleteMessageSingle": "你确定要删除这个文件/文件夹吗?",
|
||||||
"deleteTitle": "删除文件",
|
"deleteTitle": "删除文件",
|
||||||
"displayName": "名称:",
|
"displayName": "名称:",
|
||||||
"download": "下载文件",
|
"download": "下载文件",
|
||||||
@@ -112,31 +112,38 @@
|
|||||||
"replaceMessage": "您尝试上传的文件中有一个与现有文件的名称存在冲突。是否替换现有的同名文件?",
|
"replaceMessage": "您尝试上传的文件中有一个与现有文件的名称存在冲突。是否替换现有的同名文件?",
|
||||||
"rename": "重命名",
|
"rename": "重命名",
|
||||||
"renameMessage": "请输入新名称,旧名称为:",
|
"renameMessage": "请输入新名称,旧名称为:",
|
||||||
"show": "揭示",
|
"show": "点击以显示",
|
||||||
"size": "大小",
|
"size": "大小",
|
||||||
"schedule": "计划",
|
"schedule": "计划",
|
||||||
"scheduleMessage": "请选择发布这篇帖子的日期。",
|
"scheduleMessage": "请选择发布这篇帖子的日期。",
|
||||||
"newArchetype": "创建一个基于原型的新帖子。您的文件将会创建在内容文件夹中。"
|
"newArchetype": "创建一个基于原型的新帖子。您的文件将会创建在内容文件夹中。",
|
||||||
|
"upload": "上传",
|
||||||
|
"uploadMessage": "选择上传项。"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"instanceName": "Instance name",
|
"themes": {
|
||||||
|
"title": "主题",
|
||||||
|
"light": "浅色",
|
||||||
|
"dark": "深色"
|
||||||
|
},
|
||||||
|
"instanceName": "实例名称",
|
||||||
"brandingDirectoryPath": "品牌信息文件夹路径",
|
"brandingDirectoryPath": "品牌信息文件夹路径",
|
||||||
"documentation": "帮助文档",
|
"documentation": "帮助文档",
|
||||||
"branding": "品牌",
|
"branding": "品牌",
|
||||||
"disableExternalLinks": "禁止外部链接(帮助文档除外)",
|
"disableExternalLinks": "禁止外部链接(帮助文档除外)",
|
||||||
"brandingHelp": "您可以通过改变名称,更换商标,加入自定义样式,甚至禁用外部链接来自定义File Browser的外观和给人的感觉。\n想获得更多信息,请查看 {0} 。",
|
"brandingHelp": "您可以通过改变实例名称,更换Logo,加入自定义样式,甚至禁用到Github的外部链接来自定义File Browser的外观和给人的感觉。\n想获得更多信息,请查看 {0} 。",
|
||||||
"admin": "管理员",
|
"admin": "管理员",
|
||||||
"administrator": "管理员",
|
"administrator": "管理员",
|
||||||
"allowCommands": "执行命令(Linux 代码)",
|
"allowCommands": "执行命令(shell 命令)",
|
||||||
"allowEdit": "编辑、重命名或删除文件/目录",
|
"allowEdit": "编辑、重命名或删除文件/目录",
|
||||||
"allowNew": "创建新文件和目录",
|
"allowNew": "创建新文件和目录",
|
||||||
"allowPublish": "发布新的帖子与页面",
|
"allowPublish": "发布新的帖子与页面",
|
||||||
"avoidChanges": "(留空以避免更改)",
|
"avoidChanges": "(留空以避免更改)",
|
||||||
"changePassword": "更改密码",
|
"changePassword": "更改密码",
|
||||||
"commandRunner": "命令执行器",
|
"commandRunner": "命令执行器",
|
||||||
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
|
"commandRunnerHelp": "在这里你可以设置在下面的事件中执行的命令。每行必须写一条命令。可以在命令中使用环境变量 {0} 和 {1}。关于此功能和可用环境变量的更多信息,请阅读{2}.",
|
||||||
"commandsUpdated": "命令已更新!",
|
"commandsUpdated": "命令已更新!",
|
||||||
"customStylesheet": "自定义样式表",
|
"customStylesheet": "自定义样式表(CSS)",
|
||||||
"examples": "例子",
|
"examples": "例子",
|
||||||
"globalSettings": "全局设置",
|
"globalSettings": "全局设置",
|
||||||
"language": "语言",
|
"language": "语言",
|
||||||
@@ -149,15 +156,15 @@
|
|||||||
"permissions": "权限",
|
"permissions": "权限",
|
||||||
"permissionsHelp": "您可以将该用户设置为管理员,也可以单独选择各项权限。如果选择了“管理员”,则其他的选项会被自动勾上,同时该用户可以管理其他用户。",
|
"permissionsHelp": "您可以将该用户设置为管理员,也可以单独选择各项权限。如果选择了“管理员”,则其他的选项会被自动勾上,同时该用户可以管理其他用户。",
|
||||||
"profileSettings": "个人设置",
|
"profileSettings": "个人设置",
|
||||||
"ruleExample1": "阻止用户访问所有文件夹下任何以 . 开头的文件(隐藏文件, 例如: .git, .gitignore)。",
|
"ruleExample1": "阻止用户访问所有文件夹下任何以 . 开头的文件(隐藏文件, 例如: .git, .gitignore)。",
|
||||||
"ruleExample2": "阻止用户访问其目录范围的根目录下名为 Caddyfile 的文件。",
|
"ruleExample2": "阻止用户访问其目录范围的根目录下名为 Caddyfile 的文件。",
|
||||||
"rules": "规则",
|
"rules": "规则",
|
||||||
"rulesHelp": "您可以为该用户制定一组黑名单或白名单式的规则,被屏蔽的文件将不会显示在列表中,用户也无权限访问,支持相对于目录范围的路径。",
|
"rulesHelp": "您可以为该用户制定一组黑名单或白名单式的规则,被屏蔽的文件将不会显示在列表中,用户也无权限访问,支持相对于目录范围的路径。",
|
||||||
"scope": "目录范围",
|
"scope": "目录范围",
|
||||||
"settingsUpdated": "设置已更新!",
|
"settingsUpdated": "设置已更新!",
|
||||||
"user": "用户",
|
"user": "用户",
|
||||||
"userCommands": "用户命令(Linux 代码)",
|
"userCommands": "用户命令(shell 命令)",
|
||||||
"userCommandsHelp": "指定该用户可以执行的命令(Linux 代码),用空格分隔。例如:",
|
"userCommandsHelp": "指定该用户可以执行的命令(shell 代码),用空格分隔。例如:",
|
||||||
"userCreated": "用户已创建!",
|
"userCreated": "用户已创建!",
|
||||||
"userDeleted": "用户已删除!",
|
"userDeleted": "用户已删除!",
|
||||||
"userManagement": "用户管理",
|
"userManagement": "用户管理",
|
||||||
@@ -167,12 +174,12 @@
|
|||||||
"allowSignup": "允许用户注册",
|
"allowSignup": "允许用户注册",
|
||||||
"createUserDir": "在添加新用户的同时自动创建用户的个人目录",
|
"createUserDir": "在添加新用户的同时自动创建用户的个人目录",
|
||||||
"insertRegex": "插入正则表达式",
|
"insertRegex": "插入正则表达式",
|
||||||
"insertPath": "Insert the path",
|
"insertPath": "插入路径",
|
||||||
"userUpdated": "用户已更新!",
|
"userUpdated": "用户已更新!",
|
||||||
"userDefaults": "用户默认设置",
|
"userDefaults": "用户默认设置",
|
||||||
"defaultUserDescription": "这些是新用户的默认设置",
|
"defaultUserDescription": "这些是新用户的默认设置。",
|
||||||
"executeOnShell": "在Shell中执行",
|
"executeOnShell": "在Shell中执行",
|
||||||
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
|
"executeOnShellDescription": "默认情况下,File Browser通过直接调用命令的二进制包来执行命令,如果想在shell中执行(如Bash、PowerShell),你可以在这里定义所使用的shell和参数。如果设置了这个选项,所执行的命令会作为参数追加在后面。本选项对用户命令和事件钩子都生效。",
|
||||||
"perm": {
|
"perm": {
|
||||||
"create": "创建文件和文件夹",
|
"create": "创建文件和文件夹",
|
||||||
"delete": "删除文件和文件夹",
|
"delete": "删除文件和文件夹",
|
||||||
@@ -209,18 +216,22 @@
|
|||||||
"languages": {
|
"languages": {
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português(Brasil)",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文(简体)",
|
||||||
"zhTW": "中文 (繁體)",
|
"zhTW": "中文(繁體)",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"ko": "한국어"
|
"ko": "한국어",
|
||||||
|
"nlBE": "Dutch(Belgium)",
|
||||||
|
"ro": "Romanian",
|
||||||
|
"svSE": "Swedish(Sweden)"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"unit": "时间单位",
|
"unit": "时间单位",
|
||||||
|
|||||||
@@ -116,15 +116,22 @@
|
|||||||
"size": "大小",
|
"size": "大小",
|
||||||
"schedule": "計畫",
|
"schedule": "計畫",
|
||||||
"scheduleMessage": "請選擇發佈這篇貼文的日期。",
|
"scheduleMessage": "請選擇發佈這篇貼文的日期。",
|
||||||
"newArchetype": "建立一個基於原型的新貼文。您的檔案將會建立在內容資料夾中。"
|
"newArchetype": "建立一個基於原型的新貼文。您的檔案將會建立在內容資料夾中。",
|
||||||
|
"upload": "上傳",
|
||||||
|
"uploadMessage": "選擇上傳項。"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"instanceName": "Instance name",
|
"themes": {
|
||||||
"brandingDirectoryPath": "Branding directory path",
|
"title": "主題",
|
||||||
"documentation": "documentation",
|
"light": "淺色",
|
||||||
"branding": "Branding",
|
"dark": "深色"
|
||||||
"disableExternalLinks": "Disable external links (except documentation)",
|
},
|
||||||
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
"instanceName": "例項名稱",
|
||||||
|
"brandingDirectoryPath": "品牌資訊資料夾路徑",
|
||||||
|
"documentation": "幫助文件",
|
||||||
|
"branding": "品牌",
|
||||||
|
"disableExternalLinks": "禁止外部連結(幫助文件除外)",
|
||||||
|
"brandingHelp": "您可以通過改變例項名稱,更換Logo,加入自定義樣式,甚至禁用到Github的外部連結來自定義File Browser的外觀和給人的感覺。\n想獲得更多資訊,請檢視 {0} 。",
|
||||||
"admin": "管理員",
|
"admin": "管理員",
|
||||||
"administrator": "管理員",
|
"administrator": "管理員",
|
||||||
"allowCommands": "執行命令",
|
"allowCommands": "執行命令",
|
||||||
@@ -133,8 +140,8 @@
|
|||||||
"allowPublish": "發佈新的貼文與頁面",
|
"allowPublish": "發佈新的貼文與頁面",
|
||||||
"avoidChanges": "(留空以避免更改)",
|
"avoidChanges": "(留空以避免更改)",
|
||||||
"changePassword": "更改密碼",
|
"changePassword": "更改密碼",
|
||||||
"commandRunner": "Command runner",
|
"commandRunner": "命令執行器",
|
||||||
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
|
"commandRunnerHelp": "在這裡你可以設定在下面的事件中執行的命令。每行必須寫一條命令。可以在命令中使用環境變數 {0} 和 {1}。關於此功能和可用環境變數的更多資訊,請閱讀{2}.",
|
||||||
"commandsUpdated": "命令已更新!",
|
"commandsUpdated": "命令已更新!",
|
||||||
"customStylesheet": "自定義樣式表",
|
"customStylesheet": "自定義樣式表",
|
||||||
"examples": "範例",
|
"examples": "範例",
|
||||||
@@ -163,22 +170,22 @@
|
|||||||
"userManagement": "使用者管理",
|
"userManagement": "使用者管理",
|
||||||
"username": "使用者名稱",
|
"username": "使用者名稱",
|
||||||
"users": "使用者",
|
"users": "使用者",
|
||||||
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
|
"globalRules": "這是全局允許與禁止規則。它們作用於所有使用者。您可以給每個使用者定義單獨的特殊規則來覆蓋全局規則。",
|
||||||
"allowSignup": "Allow users to signup",
|
"allowSignup": "允許使用者註冊",
|
||||||
"createUserDir": "Auto create user home dir while adding new user",
|
"createUserDir": "在新增新使用者的同時自動建立使用者的個人目錄",
|
||||||
"insertRegex": "Insert regex expression",
|
"insertRegex": "插入正規表示式",
|
||||||
"insertPath": "Insert the path",
|
"insertPath": "插入路徑",
|
||||||
"userUpdated": "使用者已更新!",
|
"userUpdated": "使用者已更新!",
|
||||||
"userDefaults": "User default settings",
|
"userDefaults": "使用者預設選項",
|
||||||
"defaultUserDescription": "This are the default settings for new users.",
|
"defaultUserDescription": "這些是新使用者的預設設定。",
|
||||||
"executeOnShell": "Execute on shell",
|
"executeOnShell": "在Shell中執行",
|
||||||
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
|
"executeOnShellDescription": "預設情況下,File Browser通過直接呼叫命令的二進位制包來執行命令,如果想在shell中執行(如Bash、PowerShell),你可以在這裡定義所使用的shell和參數。如果設定了這個選項,所執行的命令會作為參數追加在後面。本選項對使用者命令和事件鉤子都生效。",
|
||||||
"perm": {
|
"perm": {
|
||||||
"create": "建立檔案和資料夾",
|
"create": "建立檔案和資料夾",
|
||||||
"delete": "刪除檔案和資料夾",
|
"delete": "刪除檔案和資料夾",
|
||||||
"download": "下載",
|
"download": "下載",
|
||||||
"modify": "編輯檔案",
|
"modify": "編輯檔案",
|
||||||
"execute": "Execute commands",
|
"execute": "執行命令",
|
||||||
"rename": "重命名或移動檔案/資料夾",
|
"rename": "重命名或移動檔案/資料夾",
|
||||||
"share": "分享檔案"
|
"share": "分享檔案"
|
||||||
}
|
}
|
||||||
@@ -203,12 +210,13 @@
|
|||||||
"types": "類型",
|
"types": "類型",
|
||||||
"video": "影片",
|
"video": "影片",
|
||||||
"search": "搜尋...",
|
"search": "搜尋...",
|
||||||
"typeToSearch": "Type to search...",
|
"typeToSearch": "輸入以搜尋...",
|
||||||
"pressToSearch": "Press enter to search..."
|
"pressToSearch": "按確認鍵搜尋..."
|
||||||
},
|
},
|
||||||
"languages": {
|
"languages": {
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
@@ -220,7 +228,10 @@
|
|||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"ko": "한국어"
|
"ko": "한국어",
|
||||||
|
"nlBE": "Dutch(Belgium)",
|
||||||
|
"ro": "Romanian",
|
||||||
|
"svSE": "Swedish(Sweden)"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"unit": "時間單位",
|
"unit": "時間單位",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const getters = {
|
|||||||
isFiles: state => !state.loading && state.route.name === 'Files',
|
isFiles: state => !state.loading && state.route.name === 'Files',
|
||||||
isListing: (state, getters) => getters.isFiles && state.req.isDir,
|
isListing: (state, getters) => getters.isFiles && state.req.isDir,
|
||||||
isEditor: (state, getters) => getters.isFiles && (state.req.type === 'text' || state.req.type === 'textImmutable'),
|
isEditor: (state, getters) => getters.isFiles && (state.req.type === 'text' || state.req.type === 'textImmutable'),
|
||||||
|
isPreview: state => state.previewMode,
|
||||||
selectedCount: state => state.selected.length,
|
selectedCount: state => state.selected.length,
|
||||||
progress : state => {
|
progress : state => {
|
||||||
if (state.upload.progress.length == 0) {
|
if (state.upload.progress.length == 0) {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const authMethod = window.FileBrowser.AuthMethod
|
|||||||
const loginPage = window.FileBrowser.LoginPage
|
const loginPage = window.FileBrowser.LoginPage
|
||||||
const theme = window.FileBrowser.Theme
|
const theme = window.FileBrowser.Theme
|
||||||
const enableThumbs = window.FileBrowser.EnableThumbs
|
const enableThumbs = window.FileBrowser.EnableThumbs
|
||||||
|
const resizePreview = window.FileBrowser.ResizePreview
|
||||||
|
const enableExec = window.FileBrowser.EnableExec
|
||||||
|
|
||||||
export {
|
export {
|
||||||
name,
|
name,
|
||||||
@@ -26,5 +28,7 @@ export {
|
|||||||
authMethod,
|
authMethod,
|
||||||
loginPage,
|
loginPage,
|
||||||
theme,
|
theme,
|
||||||
enableThumbs
|
enableThumbs,
|
||||||
|
resizePreview,
|
||||||
|
enableExec
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,31 +96,25 @@ export function scanFiles(dt) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleFiles(files, path, overwrite = false) {
|
export function handleFiles(files, base, overwrite = false) {
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
let id = store.state.upload.id
|
||||||
|
let path = base
|
||||||
let file = files[i]
|
let file = files[i]
|
||||||
|
|
||||||
let filename = (file.fullPath !== undefined) ? file.fullPath : file.name
|
if (file.fullPath !== undefined) {
|
||||||
let filenameEncoded = url.encodeRFC5987ValueChars(filename)
|
path += url.encodePath(file.fullPath)
|
||||||
|
} else {
|
||||||
let id = store.state.upload.id
|
path += url.encodeRFC5987ValueChars(file.name)
|
||||||
|
}
|
||||||
let itemPath = path + filenameEncoded
|
|
||||||
|
|
||||||
if (file.isDir) {
|
if (file.isDir) {
|
||||||
itemPath = path
|
path += '/'
|
||||||
let folders = file.fullPath.split("/")
|
|
||||||
|
|
||||||
for (let i = 0; i < folders.length; i++) {
|
|
||||||
let folder = folders[i]
|
|
||||||
let folderEncoded = encodeURIComponent(folder)
|
|
||||||
itemPath += folderEncoded + "/"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = {
|
const item = {
|
||||||
id,
|
id,
|
||||||
path: itemPath,
|
path,
|
||||||
file,
|
file,
|
||||||
overwrite
|
overwrite
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<sidebar></sidebar>
|
<sidebar></sidebar>
|
||||||
<main>
|
<main>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
<shell v-if="isLogged && user.perm.execute" />
|
<shell v-if="isExecEnabled && isLogged && user.perm.execute" />
|
||||||
</main>
|
</main>
|
||||||
<prompts></prompts>
|
<prompts></prompts>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,6 +19,7 @@ import Sidebar from '@/components/Sidebar'
|
|||||||
import Prompts from '@/components/prompts/Prompts'
|
import Prompts from '@/components/prompts/Prompts'
|
||||||
import SiteHeader from '@/components/Header'
|
import SiteHeader from '@/components/Header'
|
||||||
import Shell from '@/components/Shell'
|
import Shell from '@/components/Shell'
|
||||||
|
import { enableExec } from '@/utils/constants'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
@@ -30,7 +31,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters([ 'isLogged', 'progress' ]),
|
...mapGetters([ 'isLogged', 'progress' ]),
|
||||||
...mapState([ 'user' ])
|
...mapState([ 'user' ]),
|
||||||
|
isExecEnabled: () => enableExec
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'$route': function () {
|
'$route': function () {
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
<p class="small">{{ $t('settings.globalRules') }}</p>
|
<p class="small">{{ $t('settings.globalRules') }}</p>
|
||||||
<rules :rules.sync="settings.rules" />
|
<rules :rules.sync="settings.rules" />
|
||||||
|
|
||||||
<h3>{{ $t('settings.executeOnShell') }}</h3>
|
<div v-if="isExecEnabled">
|
||||||
<p class="small">{{ $t('settings.executeOnShellDescription') }}</p>
|
<h3>{{ $t('settings.executeOnShell') }}</h3>
|
||||||
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" />
|
<p class="small">{{ $t('settings.executeOnShellDescription') }}</p>
|
||||||
|
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>{{ $t('settings.branding') }}</h3>
|
<h3>{{ $t('settings.branding') }}</h3>
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form class="card" @submit.prevent="save">
|
<form v-if="isExecEnabled" class="card" @submit.prevent="save">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t('settings.commandRunner') }}</h2>
|
<h2>{{ $t('settings.commandRunner') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,6 +106,7 @@ import { settings as api } from '@/api'
|
|||||||
import UserForm from '@/components/settings/UserForm'
|
import UserForm from '@/components/settings/UserForm'
|
||||||
import Rules from '@/components/settings/Rules'
|
import Rules from '@/components/settings/Rules'
|
||||||
import Themes from '@/components/settings/Themes'
|
import Themes from '@/components/settings/Themes'
|
||||||
|
import { enableExec } from '@/utils/constants'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
@@ -119,7 +122,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([ 'user' ])
|
...mapState([ 'user' ]),
|
||||||
|
isExecEnabled: () => enableExec
|
||||||
},
|
},
|
||||||
async created () {
|
async created () {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !d.user.CanExecute(strings.Split(raw, " ")[0]) {
|
if !d.server.EnableExec || !d.user.CanExecute(strings.Split(raw, " ")[0]) {
|
||||||
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:shadow
|
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:shadow
|
||||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func handle(fn handleFunc, prefix string, store *storage.Storage, server *settin
|
|||||||
}
|
}
|
||||||
|
|
||||||
status, err := fn(w, r, &data{
|
status, err := fn(w, r, &data{
|
||||||
Runner: &runner.Runner{Settings: settings},
|
Runner: &runner.Runner{Enabled: server.EnableExec, Settings: settings},
|
||||||
store: store,
|
store: store,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
server: server,
|
server: server,
|
||||||
|
|||||||
13
http/raw.go
13
http/raw.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/mholt/archiver"
|
"github.com/mholt/archiver"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/v2/files"
|
"github.com/filebrowser/filebrowser/v2/files"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/fileutils"
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data)
|
|||||||
return rawDirHandler(w, r, d, file)
|
return rawDirHandler(w, r, d, file)
|
||||||
})
|
})
|
||||||
|
|
||||||
func addFile(ar archiver.Writer, d *data, path string) error {
|
func addFile(ar archiver.Writer, d *data, path, commonPath string) error {
|
||||||
// Checks are always done with paths with "/" as path separator.
|
// Checks are always done with paths with "/" as path separator.
|
||||||
path = strings.Replace(path, "\\", "/", -1)
|
path = strings.Replace(path, "\\", "/", -1)
|
||||||
if !d.Check(path) {
|
if !d.Check(path) {
|
||||||
@@ -115,10 +116,12 @@ func addFile(ar archiver.Writer, d *data, path string) error {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
|
filename := strings.TrimPrefix(path, commonPath)
|
||||||
|
filename = strings.TrimPrefix(filename, "/")
|
||||||
err = ar.Write(archiver.File{
|
err = ar.Write(archiver.File{
|
||||||
FileInfo: archiver.FileInfo{
|
FileInfo: archiver.FileInfo{
|
||||||
FileInfo: info,
|
FileInfo: info,
|
||||||
CustomName: strings.TrimPrefix(path, "/"),
|
CustomName: filename,
|
||||||
},
|
},
|
||||||
ReadCloser: file,
|
ReadCloser: file,
|
||||||
})
|
})
|
||||||
@@ -133,7 +136,7 @@ func addFile(ar archiver.Writer, d *data, path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
err = addFile(ar, d, filepath.Join(path, name))
|
err = addFile(ar, d, filepath.Join(path, name), commonPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -167,8 +170,10 @@ func rawDirHandler(w http.ResponseWriter, r *http.Request, d *data, file *files.
|
|||||||
}
|
}
|
||||||
defer ar.Close()
|
defer ar.Close()
|
||||||
|
|
||||||
|
commonDir := fileutils.CommonPrefix('/', filenames...)
|
||||||
|
|
||||||
for _, fname := range filenames {
|
for _, fname := range filenames {
|
||||||
err = addFile(ar, d, fname)
|
err = addFile(ar, d, fname, commonDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, box *
|
|||||||
"ReCaptcha": false,
|
"ReCaptcha": false,
|
||||||
"Theme": d.settings.Branding.Theme,
|
"Theme": d.settings.Branding.Theme,
|
||||||
"EnableThumbs": d.server.EnableThumbnails,
|
"EnableThumbs": d.server.EnableThumbnails,
|
||||||
|
"ResizePreview": d.server.ResizePreview,
|
||||||
|
"EnableExec": d.server.EnableExec,
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.settings.Branding.Files != "" {
|
if d.settings.Branding.Files != "" {
|
||||||
@@ -77,7 +79,14 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, box *
|
|||||||
|
|
||||||
data["Json"] = string(b)
|
data["Json"] = string(b)
|
||||||
|
|
||||||
index := template.Must(template.New("index").Delims("[{[", "]}]").Parse(box.MustString(file)))
|
fileContents, err := box.String(file)
|
||||||
|
if err != nil {
|
||||||
|
if err == os.ErrNotExist {
|
||||||
|
return http.StatusNotFound, err
|
||||||
|
}
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
index := template.Must(template.New("index").Delims("[{[", "]}]").Parse(fileContents))
|
||||||
err = index.Execute(w, data)
|
err = index.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
// Runner is a commands runner.
|
// Runner is a commands runner.
|
||||||
type Runner struct {
|
type Runner struct {
|
||||||
|
Enabled bool
|
||||||
*settings.Settings
|
*settings.Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,11 +22,13 @@ func (r *Runner) RunHook(fn func() error, evt, path, dst string, user *users.Use
|
|||||||
path = user.FullPath(path)
|
path = user.FullPath(path)
|
||||||
dst = user.FullPath(dst)
|
dst = user.FullPath(dst)
|
||||||
|
|
||||||
if val, ok := r.Commands["before_"+evt]; ok {
|
if r.Enabled {
|
||||||
for _, command := range val {
|
if val, ok := r.Commands["before_"+evt]; ok {
|
||||||
err := r.exec(command, "before_"+evt, path, dst, user)
|
for _, command := range val {
|
||||||
if err != nil {
|
err := r.exec(command, "before_"+evt, path, dst, user)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,11 +38,13 @@ func (r *Runner) RunHook(fn func() error, evt, path, dst string, user *users.Use
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := r.Commands["after_"+evt]; ok {
|
if r.Enabled {
|
||||||
for _, command := range val {
|
if val, ok := r.Commands["after_"+evt]; ok {
|
||||||
err := r.exec(command, "after_"+evt, path, dst, user)
|
for _, command := range val {
|
||||||
if err != nil {
|
err := r.exec(command, "after_"+evt, path, dst, user)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ func Search(fs afero.Fs, scope, query string, checker rules.Checker, found func(
|
|||||||
if len(search.Terms) > 0 {
|
if len(search.Terms) > 0 {
|
||||||
for _, term := range search.Terms {
|
for _, term := range search.Terms {
|
||||||
if strings.Contains(path, term) {
|
if strings.Contains(path, term) {
|
||||||
return found(strings.TrimPrefix(originalPath, scope), f)
|
originalPath = strings.TrimPrefix(originalPath, scope)
|
||||||
|
originalPath = strings.TrimPrefix(originalPath, "/")
|
||||||
|
return found(originalPath, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type Server struct {
|
|||||||
Log string `json:"log"`
|
Log string `json:"log"`
|
||||||
EnableThumbnails bool `json:"enableThumbnails"`
|
EnableThumbnails bool `json:"enableThumbnails"`
|
||||||
ResizePreview bool `json:"resizePreview"`
|
ResizePreview bool `json:"resizePreview"`
|
||||||
|
EnableExec bool `json:"enableExec"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean cleans any variables that might need cleaning.
|
// Clean cleans any variables that might need cleaning.
|
||||||
|
|||||||
Reference in New Issue
Block a user