Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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:
|
||||||
|
|||||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -2,6 +2,33 @@
|
|||||||
|
|
||||||
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.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":
|
||||||
|
|||||||
@@ -58,11 +58,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 +145,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 +242,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',
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
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)
|
||||||
|
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
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -51,14 +58,17 @@
|
|||||||
</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 () {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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