commit
db80387cf6
|
@ -8,7 +8,9 @@ All notable changes to this project will be documented in this file. See [standa
|
|||
- Works with new more advanced filebrowser.json
|
||||
- improved GUI
|
||||
- more unified coehisive look
|
||||
-
|
||||
- The shell is dead.
|
||||
- If you need to use the shell, exec into the docker container.
|
||||
- All configuration is done via filebrowser.yml
|
||||
|
||||
# v0.1.4
|
||||
- various UI fixes
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
|
||||
"port": 8080,
|
||||
"baseURL": "",
|
||||
"address": "",
|
||||
"log": "stdout",
|
||||
"database": "./database.db",
|
||||
"root": "/srv"
|
||||
|
||||
}
|
|
@ -15,9 +15,6 @@ import (
|
|||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
)
|
||||
|
||||
// MethodHookAuth is used to identify hook auth.
|
||||
const MethodHookAuth = "hook"
|
||||
|
||||
type hookCred struct {
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
|
|
|
@ -11,9 +11,6 @@ import (
|
|||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
)
|
||||
|
||||
// MethodJSONAuth is used to identify json auth.
|
||||
const MethodJSONAuth = "json"
|
||||
|
||||
type jsonCred struct {
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
|
|
|
@ -28,51 +28,28 @@ var configCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
func addConfigFlags(flags *pflag.FlagSet) {
|
||||
addServerFlags(flags)
|
||||
addUserFlags(flags)
|
||||
flags.BoolP("signup", "s", false, "allow users to signup")
|
||||
flags.String("shell", "", "shell command to which other commands should be appended")
|
||||
|
||||
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
||||
flags.String("auth.header", "", "HTTP header for auth.method=proxy")
|
||||
flags.String("auth.command", "", "command for auth.method=hook")
|
||||
|
||||
flags.String("recaptcha.host", "https://www.google.com", "use another host for ReCAPTCHA. recaptcha.net might be useful in China")
|
||||
flags.String("recaptcha.key", "", "ReCaptcha site key")
|
||||
flags.String("recaptcha.secret", "", "ReCaptcha secret")
|
||||
|
||||
flags.String("branding.name", "", "replace 'File Browser' by this name")
|
||||
flags.String("branding.color", "", "set the theme color")
|
||||
flags.String("branding.files", "", "path to directory with images and custom styles")
|
||||
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
|
||||
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph")
|
||||
flags.String("frontend.name", "", "replace 'File Browser' by this name")
|
||||
flags.String("frontend.color", "", "set the theme color")
|
||||
flags.String("frontend.files", "", "path to directory with images and custom styles")
|
||||
flags.Bool("frontend.disableExternal", false, "disable external links such as GitHub links")
|
||||
flags.Bool("frontend.disableUsedPercentage", false, "disable used disk percentage graph")
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (string, auth.Auther) {
|
||||
method := mustGetString(flags, "auth.method")
|
||||
|
||||
func getAuthentication() (string, auth.Auther) {
|
||||
method := settings.GlobalConfiguration.Auth.Method
|
||||
var defaultAuther map[string]interface{}
|
||||
if len(defaults) > 0 {
|
||||
if hasAuth := defaults[0]; hasAuth != true {
|
||||
for _, arg := range defaults {
|
||||
switch def := arg.(type) {
|
||||
case *settings.Settings:
|
||||
method = def.AuthMethod
|
||||
case auth.Auther:
|
||||
ms, err := json.Marshal(def)
|
||||
checkErr(err)
|
||||
err = json.Unmarshal(ms, &defaultAuther)
|
||||
checkErr(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var auther auth.Auther
|
||||
if method == auth.MethodProxyAuth {
|
||||
header := mustGetString(flags, "auth.header")
|
||||
|
||||
if method == "proxy" {
|
||||
header := settings.GlobalConfiguration.Auth.Header
|
||||
if header == "" {
|
||||
header = defaultAuther["header"].(string)
|
||||
}
|
||||
|
@ -84,15 +61,15 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (string, a
|
|||
auther = &auth.ProxyAuth{Header: header}
|
||||
}
|
||||
|
||||
if method == auth.MethodNoAuth {
|
||||
if method == "noauth" {
|
||||
auther = &auth.NoAuth{}
|
||||
}
|
||||
|
||||
if method == auth.MethodJSONAuth {
|
||||
if method == "password" {
|
||||
jsonAuth := &auth.JSONAuth{}
|
||||
host := mustGetString(flags, "recaptcha.host")
|
||||
key := mustGetString(flags, "recaptcha.key")
|
||||
secret := mustGetString(flags, "recaptcha.secret")
|
||||
host := settings.GlobalConfiguration.Auth.Recaptcha.Host
|
||||
key := settings.GlobalConfiguration.Auth.Recaptcha.Key
|
||||
secret := settings.GlobalConfiguration.Auth.Recaptcha.Secret
|
||||
|
||||
if key == "" {
|
||||
if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
|
||||
|
@ -116,8 +93,8 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (string, a
|
|||
auther = jsonAuth
|
||||
}
|
||||
|
||||
if method == auth.MethodHookAuth {
|
||||
command := mustGetString(flags, "auth.command")
|
||||
if method == "hook" {
|
||||
command := settings.GlobalConfiguration.Auth.Command
|
||||
|
||||
if command == "" {
|
||||
command = defaultAuther["command"].(string)
|
||||
|
@ -142,14 +119,14 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||
|
||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
||||
fmt.Fprintf(w, "Auth method:\t%s\n", set.AuthMethod)
|
||||
fmt.Fprintf(w, "Auth method:\t%s\n", set.Auth.Method)
|
||||
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
||||
fmt.Fprintln(w, "\nBranding:")
|
||||
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
||||
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files)
|
||||
fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Branding.DisableExternal)
|
||||
fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Branding.DisableUsedPercentage)
|
||||
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color)
|
||||
fmt.Fprintln(w, "\nFrontend:")
|
||||
fmt.Fprintf(w, "\tName:\t%s\n", set.Frontend.Name)
|
||||
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Frontend.Files)
|
||||
fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Frontend.DisableExternal)
|
||||
fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Frontend.DisableUsedPercentage)
|
||||
fmt.Fprintf(w, "\tColor:\t%s\n", set.Frontend.Color)
|
||||
fmt.Fprintln(w, "\nServer:")
|
||||
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
|
||||
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)
|
||||
|
|
|
@ -18,7 +18,7 @@ var configCatCmd = &cobra.Command{
|
|||
checkErr(err)
|
||||
ser, err := d.store.Settings.GetServer()
|
||||
checkErr(err)
|
||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||
auther, err := d.store.Auth.Get(set.Auth.Method)
|
||||
checkErr(err)
|
||||
printSettings(ser, set, auther)
|
||||
}, pythonConfig{}),
|
||||
|
|
|
@ -22,7 +22,7 @@ and imported again with 'config import' command.`,
|
|||
server, err := d.store.Settings.GetServer()
|
||||
checkErr(err)
|
||||
|
||||
auther, err := d.store.Auth.Get(settings.AuthMethod)
|
||||
auther, err := d.store.Auth.Get(settings.Auth.Method)
|
||||
checkErr(err)
|
||||
|
||||
data := &settingsFile{
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
@ -43,11 +44,10 @@ The path must be for a json or yaml file.`,
|
|||
} else {
|
||||
key = generateKey()
|
||||
}
|
||||
|
||||
file := settingsFile{}
|
||||
err := unmarshal(args[0], &file)
|
||||
checkErr(err)
|
||||
|
||||
log.Println(file.Settings)
|
||||
file.Settings.Key = key
|
||||
err = d.store.Settings.Save(file.Settings)
|
||||
checkErr(err)
|
||||
|
@ -61,16 +61,16 @@ The path must be for a json or yaml file.`,
|
|||
} else {
|
||||
rawAuther = file.Auther
|
||||
}
|
||||
|
||||
log.Println("config_import",file.Settings.Auth)
|
||||
var auther auth.Auther
|
||||
switch file.Settings.AuthMethod {
|
||||
case auth.MethodJSONAuth:
|
||||
switch file.Settings.Auth.Method {
|
||||
case "password":
|
||||
auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth)
|
||||
case auth.MethodNoAuth:
|
||||
case "noauth":
|
||||
auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth)
|
||||
case auth.MethodProxyAuth:
|
||||
case "proxy":
|
||||
auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth)
|
||||
case auth.MethodHookAuth:
|
||||
case "hook":
|
||||
auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth)
|
||||
default:
|
||||
checkErr(errors.New("invalid auth method"))
|
||||
|
|
|
@ -26,33 +26,9 @@ override the options.`,
|
|||
defaults := settings.UserDefaults{}
|
||||
flags := cmd.Flags()
|
||||
getUserDefaults(flags, &defaults, true)
|
||||
authMethod, auther := getAuthentication(flags)
|
||||
|
||||
s := &settings.Settings{
|
||||
Key: generateKey(),
|
||||
Signup: mustGetBool(flags, "signup"),
|
||||
Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
|
||||
AuthMethod: authMethod,
|
||||
Defaults: defaults,
|
||||
Branding: settings.Branding{
|
||||
Name: mustGetString(flags, "branding.name"),
|
||||
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
|
||||
DisableUsedPercentage: mustGetBool(flags, "branding.DisableUsedPercentage"),
|
||||
Files: mustGetString(flags, "branding.files"),
|
||||
},
|
||||
}
|
||||
|
||||
ser := &settings.Server{
|
||||
Address: mustGetString(flags, "address"),
|
||||
Socket: mustGetString(flags, "socket"),
|
||||
Root: mustGetString(flags, "root"),
|
||||
BaseURL: mustGetString(flags, "baseurl"),
|
||||
TLSKey: mustGetString(flags, "key"),
|
||||
TLSCert: mustGetString(flags, "cert"),
|
||||
Port: mustGetString(flags, "port"),
|
||||
Log: mustGetString(flags, "log"),
|
||||
}
|
||||
err := d.store.Settings.Save(s)
|
||||
_, auther := getAuthentication()
|
||||
ser := &settings.GlobalConfiguration.Server
|
||||
err := d.store.Settings.Save(&settings.GlobalConfiguration)
|
||||
checkErr(err)
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
checkErr(err)
|
||||
|
@ -64,6 +40,6 @@ Congratulations! You've set up your database to use with File Browser.
|
|||
Now add your first user via 'filebrowser users add' and then you just
|
||||
need to call the main command to boot up the server.
|
||||
`)
|
||||
printSettings(ser, s, auther)
|
||||
printSettings(ser, &settings.GlobalConfiguration, auther)
|
||||
}, pythonConfig{noDB: true}),
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ you want to change. Other options will remain unchanged.`,
|
|||
ser, err := d.store.Settings.GetServer()
|
||||
checkErr(err)
|
||||
|
||||
hasAuth := false
|
||||
flags.Visit(func(flag *pflag.Flag) {
|
||||
switch flag.Name {
|
||||
case "baseurl":
|
||||
|
@ -40,37 +39,30 @@ you want to change. Other options will remain unchanged.`,
|
|||
case "address":
|
||||
ser.Address = mustGetString(flags, flag.Name)
|
||||
case "port":
|
||||
ser.Port = mustGetString(flags, flag.Name)
|
||||
ser.Port = 8080
|
||||
case "log":
|
||||
ser.Log = mustGetString(flags, flag.Name)
|
||||
case "signup":
|
||||
set.Signup = mustGetBool(flags, flag.Name)
|
||||
case "auth.method":
|
||||
hasAuth = true
|
||||
case "shell":
|
||||
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
|
||||
case "branding.name":
|
||||
set.Branding.Name = mustGetString(flags, flag.Name)
|
||||
case "branding.color":
|
||||
set.Branding.Color = mustGetString(flags, flag.Name)
|
||||
case "branding.disableExternal":
|
||||
set.Branding.DisableExternal = mustGetBool(flags, flag.Name)
|
||||
case "branding.disableUsedPercentage":
|
||||
set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name)
|
||||
case "branding.files":
|
||||
set.Branding.Files = mustGetString(flags, flag.Name)
|
||||
case "frontend.name":
|
||||
set.Frontend.Name = mustGetString(flags, flag.Name)
|
||||
case "frontend.color":
|
||||
set.Frontend.Color = mustGetString(flags, flag.Name)
|
||||
case "frontend.disableExternal":
|
||||
set.Frontend.DisableExternal = mustGetBool(flags, flag.Name)
|
||||
case "frontend.disableUsedPercentage":
|
||||
set.Frontend.DisableUsedPercentage = mustGetBool(flags, flag.Name)
|
||||
case "frontend.files":
|
||||
set.Frontend.Files = mustGetString(flags, flag.Name)
|
||||
}
|
||||
})
|
||||
|
||||
getUserDefaults(flags, &set.Defaults, false)
|
||||
|
||||
// read the defaults
|
||||
auther, err := d.store.Auth.Get(set.AuthMethod)
|
||||
checkErr(err)
|
||||
|
||||
// check if there are new flags for existing auth method
|
||||
set.AuthMethod, auther = getAuthentication(flags, hasAuth, set, auther)
|
||||
|
||||
_, auther := getAuthentication()
|
||||
err = d.store.Auth.Save(auther)
|
||||
checkErr(err)
|
||||
err = d.store.Settings.Save(set)
|
||||
|
|
|
@ -2,7 +2,6 @@ package cmd
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
|
@ -10,7 +9,6 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
@ -57,55 +55,19 @@ func init() {
|
|||
flags.Bool("noauth", false, "use the noauth auther when using quick setup")
|
||||
flags.String("username", "admin", "username for the first user when using quick config")
|
||||
flags.String("password", "", "hashed password for the first user when using quick config (default \"admin\")")
|
||||
|
||||
addServerFlags(flags)
|
||||
}
|
||||
|
||||
func getEnvVariableAsUint32(key string) uint32 {
|
||||
valueStr := os.Getenv(key)
|
||||
value, err := strconv.ParseUint(valueStr, 10, 32)
|
||||
if err != nil {
|
||||
return 5 // default value every 5 minutes
|
||||
}
|
||||
return uint32(value)
|
||||
}
|
||||
|
||||
func addServerFlags(flags *pflag.FlagSet) {
|
||||
flags.StringP("address", "a", "127.0.0.1", "address to listen on")
|
||||
flags.StringP("log", "l", "stdout", "log output")
|
||||
flags.StringP("port", "p", "8080", "port to listen on")
|
||||
flags.StringP("cert", "t", "", "tls certificate")
|
||||
flags.StringP("key", "k", "", "tls key")
|
||||
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.Uint32("socket-perm", 0666, "unix socket file permissions") //nolint:gomnd
|
||||
flags.StringP("baseurl", "b", "", "base url")
|
||||
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
||||
flags.Int("img-processors", 4, "image processors count") //nolint:gomnd
|
||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||
flags.Bool("disable-preview-resize", true, "disable resize of image previews")
|
||||
flags.Bool("disable-exec", false, "disables Command Runner feature")
|
||||
flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "filebrowser",
|
||||
Short: "A stylish web-based file browser",
|
||||
Long: `File Browser CLI lets you create the database to use with File Browser,
|
||||
manage your users and all the configurations without acessing the
|
||||
web interface.
|
||||
|
||||
Long: `
|
||||
If you've never run File Browser, you'll need to have a database for
|
||||
it. Don't worry: you don't need to setup a separate database server.
|
||||
We're using Bolt DB which is a single file database and all managed
|
||||
by ourselves.
|
||||
|
||||
For this specific command, all the flags you have available (except
|
||||
"config" for the configuration file), can be given either through
|
||||
environment variables or configuration files.
|
||||
|
||||
If you don't set "config", it will look for a configuration file called
|
||||
.filebrowser.{json, toml, yaml, yml} in the following directories:
|
||||
filebrowser.{json, toml, yaml, yml} in the following directories:
|
||||
|
||||
- ./
|
||||
- $HOME/
|
||||
|
@ -119,31 +81,20 @@ The precedence of the configuration values are as follows:
|
|||
- database values
|
||||
- defaults
|
||||
|
||||
The environment variables are prefixed by "FB_" followed by the option
|
||||
name in caps. So to set "database" via an env variable, you should
|
||||
set FB_DATABASE.
|
||||
|
||||
Also, if the database path doesn't exist, File Browser will enter into
|
||||
the quick setup mode and a new database will be bootstraped and a new
|
||||
user created with the credentials from options "username" and "password".`,
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
log.Println(cfgFile)
|
||||
|
||||
serverConfig := settings.GlobalConfiguration.Server
|
||||
if !d.hadDB {
|
||||
quickSetup(cmd.Flags(), d)
|
||||
}
|
||||
|
||||
// build img service
|
||||
workersCount, err := cmd.Flags().GetInt("img-processors")
|
||||
checkErr(err)
|
||||
if workersCount < 1 {
|
||||
if serverConfig.NumImageProcessors < 1 {
|
||||
log.Fatal("Image resize workers count could not be < 1")
|
||||
}
|
||||
imgSvc := img.New(workersCount)
|
||||
|
||||
imgSvc := img.New(serverConfig.NumImageProcessors)
|
||||
var fileCache diskcache.Interface = diskcache.NewNoOp()
|
||||
cacheDir, err := cmd.Flags().GetString("cache-dir")
|
||||
checkErr(err)
|
||||
cacheDir := "/tmp"
|
||||
if cacheDir != "" {
|
||||
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd
|
||||
log.Fatalf("can't make directory %s: %s", cacheDir, err)
|
||||
|
@ -151,51 +102,38 @@ user created with the credentials from options "username" and "password".`,
|
|||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||
}
|
||||
// initialize indexing and schedule indexing ever n minutes (default 5)
|
||||
indexingInterval := getEnvVariableAsUint32("INDEXING_INTERVAL")
|
||||
go search.InitializeIndex(indexingInterval)
|
||||
|
||||
server := getRunParams(cmd.Flags(), d.store)
|
||||
setupLog(server.Log)
|
||||
|
||||
root, err := filepath.Abs(server.Root)
|
||||
go search.InitializeIndex(serverConfig.IndexingInterval)
|
||||
_, err := os.Stat(serverConfig.Root)
|
||||
checkErr(err)
|
||||
server.Root = root
|
||||
|
||||
adr := server.Address + ":" + server.Port
|
||||
|
||||
var listener net.Listener
|
||||
|
||||
address := serverConfig.Address + ":" + strconv.Itoa(serverConfig.Port)
|
||||
switch {
|
||||
case server.Socket != "":
|
||||
listener, err = net.Listen("unix", server.Socket)
|
||||
case serverConfig.Socket != "":
|
||||
listener, err = net.Listen("unix", serverConfig.Socket)
|
||||
checkErr(err)
|
||||
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
||||
checkErr(err)
|
||||
err = os.Chmod(server.Socket, os.FileMode(socketPerm))
|
||||
err = os.Chmod(serverConfig.Socket, os.FileMode(socketPerm))
|
||||
checkErr(err)
|
||||
case server.TLSKey != "" && server.TLSCert != "":
|
||||
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet
|
||||
case serverConfig.TLSKey != "" && serverConfig.TLSCert != "":
|
||||
cer, err := tls.LoadX509KeyPair(serverConfig.TLSCert, serverConfig.TLSKey) //nolint:govet
|
||||
checkErr(err)
|
||||
listener, err = tls.Listen("tcp", adr, &tls.Config{
|
||||
listener, err = tls.Listen("tcp", address, &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Certificates: []tls.Certificate{cer}},
|
||||
)
|
||||
checkErr(err)
|
||||
default:
|
||||
listener, err = net.Listen("tcp", adr)
|
||||
listener, err = net.Listen("tcp", address)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||
go cleanupHandler(listener, sigc)
|
||||
|
||||
assetsFs := dirFS{Dir: http.Dir("frontend/dist")}
|
||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs)
|
||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs)
|
||||
checkErr(err)
|
||||
|
||||
defer listener.Close()
|
||||
|
||||
log.Println("Listening on", listener.Addr().String())
|
||||
//nolint: gosec
|
||||
if err := http.Serve(listener, handler); err != nil {
|
||||
|
@ -215,68 +153,6 @@ func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfac
|
|||
func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
|
||||
server, err := st.Settings.GetServer()
|
||||
checkErr(err)
|
||||
|
||||
if val, set := getParamB(flags, "root"); set {
|
||||
server.Root = val
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "baseurl"); set {
|
||||
server.BaseURL = val
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "log"); set {
|
||||
server.Log = val
|
||||
}
|
||||
|
||||
isSocketSet := false
|
||||
isAddrSet := false
|
||||
|
||||
if val, set := getParamB(flags, "address"); set {
|
||||
server.Address = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "port"); set {
|
||||
server.Port = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "key"); set {
|
||||
server.TLSKey = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "cert"); set {
|
||||
server.TLSCert = val
|
||||
isAddrSet = isAddrSet || set
|
||||
}
|
||||
|
||||
if val, set := getParamB(flags, "socket"); set {
|
||||
server.Socket = val
|
||||
isSocketSet = isSocketSet || set
|
||||
}
|
||||
|
||||
if isAddrSet && isSocketSet {
|
||||
checkErr(errors.New("--socket flag cannot be used with --address, --port, --key nor --cert"))
|
||||
}
|
||||
|
||||
// Do not use saved Socket if address was manually set.
|
||||
if isAddrSet && server.Socket != "" {
|
||||
server.Socket = ""
|
||||
}
|
||||
|
||||
_, disableThumbnails := getParamB(flags, "disable-thumbnails")
|
||||
server.EnableThumbnails = !disableThumbnails
|
||||
|
||||
_, disablePreviewResize := getParamB(flags, "disable-preview-resize")
|
||||
server.ResizePreview = !disablePreviewResize
|
||||
|
||||
_, disableTypeDetectionByHeader := getParamB(flags, "disable-type-detection-by-header")
|
||||
server.TypeDetectionByHeader = !disableTypeDetectionByHeader
|
||||
|
||||
_, disableExec := getParamB(flags, "disable-exec")
|
||||
server.EnableExec = !disableExec
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
|
@ -348,32 +224,27 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||
Download: true,
|
||||
},
|
||||
},
|
||||
AuthMethod: "",
|
||||
Branding: settings.Branding{},
|
||||
Commands: nil,
|
||||
Shell: nil,
|
||||
Rules: nil,
|
||||
Frontend: settings.Frontend{},
|
||||
Commands: nil,
|
||||
Shell: nil,
|
||||
Rules: nil,
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, noauth := getParamB(flags, "noauth"); noauth {
|
||||
set.AuthMethod = auth.MethodNoAuth
|
||||
if settings.GlobalConfiguration.Auth.Method == "noauth" {
|
||||
set.Auth.Method = "noauth"
|
||||
err = d.store.Auth.Save(&auth.NoAuth{})
|
||||
} else {
|
||||
set.AuthMethod = auth.MethodJSONAuth
|
||||
set.Auth.Method = "password"
|
||||
err = d.store.Auth.Save(&auth.JSONAuth{})
|
||||
}
|
||||
|
||||
err = d.store.Settings.Save(set)
|
||||
checkErr(err)
|
||||
|
||||
ser := &settings.Server{
|
||||
BaseURL: getParam(flags, "baseurl"),
|
||||
Port: getParam(flags, "port"),
|
||||
Log: getParam(flags, "log"),
|
||||
TLSKey: getParam(flags, "key"),
|
||||
TLSCert: getParam(flags, "cert"),
|
||||
Address: getParam(flags, "address"),
|
||||
Root: getParam(flags, "root"),
|
||||
}
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
|
@ -408,7 +279,7 @@ func initConfig() {
|
|||
if cfgFile == "" {
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath("/etc/filebrowser/")
|
||||
v.SetConfigName(".filebrowser")
|
||||
v.SetConfigName("filebrowser")
|
||||
} else {
|
||||
v.SetConfigFile(cfgFile)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
server:
|
||||
indexingInterval: 5
|
||||
numImageProcessors: 2
|
||||
socket: ""
|
||||
tlsKey: ""
|
||||
tlsCert: ""
|
||||
enableThumbnails: true
|
||||
resizePreview: false
|
||||
typeDetectionByHeader: true
|
||||
port: 8080
|
||||
baseURL: "/"
|
||||
address: ""
|
||||
log: "stdout"
|
||||
database: "database.db"
|
||||
root: "/srv"
|
||||
auth:
|
||||
recaptcha:
|
||||
host: ""
|
||||
key: ""
|
||||
secret: ""
|
||||
header: ""
|
||||
method: noauth
|
||||
command: ""
|
||||
signup: false
|
||||
shell: ""
|
||||
frontend:
|
||||
name: ""
|
||||
disableExternal: false
|
||||
disableUsedPercentage: false
|
||||
files: ""
|
||||
theme: ""
|
||||
color: ""
|
||||
userDefaults:
|
||||
scope: ""
|
||||
locale: ""
|
||||
viewMode: ""
|
||||
singleClick: false
|
||||
sorting:
|
||||
by: ""
|
||||
asc: false
|
||||
perm:
|
||||
admin: false
|
||||
execute: false
|
||||
create: false
|
||||
rename: false
|
||||
modify: false
|
||||
delete: false
|
||||
share: false
|
||||
download: false
|
||||
commands: []
|
||||
hideDotfiles: false
|
||||
dateFormat: false
|
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
||||
github.com/goccy/go-yaml v1.11.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
|
@ -35,6 +36,7 @@ require (
|
|||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
|
@ -45,6 +47,8 @@ require (
|
|||
github.com/klauspost/compress v1.11.4 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||
|
@ -58,6 +62,7 @@ require (
|
|||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
@ -86,6 +86,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
|
@ -101,6 +103,11 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
|||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54=
|
||||
github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
|
@ -198,6 +205,7 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
|
@ -205,6 +213,12 @@ github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
|
|||
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
|
||||
github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=
|
||||
github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
|
@ -403,6 +417,7 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -427,6 +442,8 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -507,6 +524,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
|
|
|
@ -102,7 +102,7 @@ func withAdmin(fn handleFunc) handleFunc {
|
|||
}
|
||||
|
||||
var loginHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
auther, err := d.store.Auth.Get(d.settings.AuthMethod)
|
||||
auther, err := d.store.Auth.Get(d.settings.Auth.Method)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ type settingsData struct {
|
|||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
Defaults settings.UserDefaults `json:"defaults"`
|
||||
Rules []rules.Rule `json:"rules"`
|
||||
Branding settings.Branding `json:"branding"`
|
||||
Frontend settings.Frontend `json:"frontend"`
|
||||
Shell []string `json:"shell"`
|
||||
Commands map[string][]string `json:"commands"`
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request,
|
|||
UserHomeBasePath: d.settings.UserHomeBasePath,
|
||||
Defaults: d.settings.Defaults,
|
||||
Rules: d.settings.Rules,
|
||||
Branding: d.settings.Branding,
|
||||
Frontend: d.settings.Frontend,
|
||||
Shell: d.settings.Shell,
|
||||
Commands: d.settings.Commands,
|
||||
}
|
||||
|
@ -46,10 +46,9 @@ var settingsPutHandler = withAdmin(func(w http.ResponseWriter, r *http.Request,
|
|||
d.settings.UserHomeBasePath = req.UserHomeBasePath
|
||||
d.settings.Defaults = req.Defaults
|
||||
d.settings.Rules = req.Rules
|
||||
d.settings.Branding = req.Branding
|
||||
d.settings.Frontend = req.Frontend
|
||||
d.settings.Shell = req.Shell
|
||||
d.settings.Commands = req.Commands
|
||||
|
||||
err = d.store.Settings.Save(d.settings)
|
||||
return errToStatus(err), err
|
||||
})
|
||||
|
|
|
@ -21,33 +21,33 @@ import (
|
|||
func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys fs.FS, file, contentType string) (int, error) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
auther, err := d.store.Auth.Get(d.settings.AuthMethod)
|
||||
auther, err := d.store.Auth.Get(d.settings.Auth.Method)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Name": d.settings.Branding.Name,
|
||||
"DisableExternal": d.settings.Branding.DisableExternal,
|
||||
"DisableUsedPercentage": d.settings.Branding.DisableUsedPercentage,
|
||||
"Color": d.settings.Branding.Color,
|
||||
"Name": d.settings.Frontend.Name,
|
||||
"DisableExternal": d.settings.Frontend.DisableExternal,
|
||||
"DisableUsedPercentage": d.settings.Frontend.DisableUsedPercentage,
|
||||
"Color": d.settings.Frontend.Color,
|
||||
"BaseURL": d.server.BaseURL,
|
||||
"Version": version.Version,
|
||||
"StaticURL": path.Join(d.server.BaseURL, "/static"),
|
||||
"Signup": d.settings.Signup,
|
||||
"NoAuth": d.settings.AuthMethod == auth.MethodNoAuth,
|
||||
"AuthMethod": d.settings.AuthMethod,
|
||||
"NoAuth": d.settings.Auth.Method == "noauth",
|
||||
"AuthMethod": d.settings.Auth.Method,
|
||||
"LoginPage": auther.LoginPage(),
|
||||
"CSS": false,
|
||||
"ReCaptcha": false,
|
||||
"Theme": d.settings.Branding.Theme,
|
||||
"Theme": d.settings.Frontend.Theme,
|
||||
"EnableThumbs": d.server.EnableThumbnails,
|
||||
"ResizePreview": d.server.ResizePreview,
|
||||
"EnableExec": d.server.EnableExec,
|
||||
}
|
||||
|
||||
if d.settings.Branding.Files != "" {
|
||||
fPath := filepath.Join(d.settings.Branding.Files, "custom.css")
|
||||
if d.settings.Frontend.Files != "" {
|
||||
fPath := filepath.Join(d.settings.Frontend.Files, "custom.css")
|
||||
_, err := os.Stat(fPath) //nolint:govet
|
||||
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
|
@ -59,8 +59,8 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
|||
}
|
||||
}
|
||||
|
||||
if d.settings.AuthMethod == auth.MethodJSONAuth {
|
||||
raw, err := d.store.Auth.Get(d.settings.AuthMethod) //nolint:govet
|
||||
if d.settings.Auth.Method == "password" {
|
||||
raw, err := d.store.Auth.Get(d.settings.Auth.Method) //nolint:govet
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
@ -115,15 +115,15 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
|
|||
const maxAge = 86400 // 1 day
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%v", maxAge))
|
||||
|
||||
if d.settings.Branding.Files != "" {
|
||||
if d.settings.Frontend.Files != "" {
|
||||
if strings.HasPrefix(r.URL.Path, "img/") {
|
||||
fPath := filepath.Join(d.settings.Branding.Files, r.URL.Path)
|
||||
fPath := filepath.Join(d.settings.Frontend.Files, r.URL.Path)
|
||||
if _, err := os.Stat(fPath); err == nil {
|
||||
http.ServeFile(w, r, fPath)
|
||||
return 0, nil
|
||||
}
|
||||
} else if r.URL.Path == "custom.css" && d.settings.Branding.Files != "" {
|
||||
http.ServeFile(w, r, filepath.Join(d.settings.Branding.Files, "custom.css"))
|
||||
} else if r.URL.Path == "custom.css" && d.settings.Frontend.Files != "" {
|
||||
http.ServeFile(w, r, filepath.Join(d.settings.Frontend.Files, "custom.css"))
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
)
|
||||
|
||||
var GlobalConfiguration Settings
|
||||
|
||||
func init() {
|
||||
// Open and read the YAML file
|
||||
yamlFile, err := os.Open("filebrowser.yml")
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening YAML file: %v", err)
|
||||
}
|
||||
defer yamlFile.Close()
|
||||
|
||||
stat, err := yamlFile.Stat()
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting file information: %v", err)
|
||||
}
|
||||
|
||||
yamlData := make([]byte, stat.Size())
|
||||
_, err = yamlFile.Read(yamlData)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading YAML data: %v", err)
|
||||
}
|
||||
setDefaults()
|
||||
// Unmarshal the YAML data into the Settings struct
|
||||
err = yaml.Unmarshal(yamlData, &GlobalConfiguration)
|
||||
if err != nil {
|
||||
log.Fatalf("Error unmarshaling YAML data: %v", err)
|
||||
}
|
||||
// Now you have the Settings struct with values from the YAML file
|
||||
// You can access the values like: defaultSettings.Key, defaultSettings.Server.Port, etc.
|
||||
}
|
||||
|
||||
func setDefaults() {
|
||||
GlobalConfiguration = Settings{
|
||||
Signup: true,
|
||||
Server: Server{
|
||||
IndexingInterval: 5,
|
||||
Port: 8080,
|
||||
NumImageProcessors: 1,
|
||||
BaseURL: "",
|
||||
},
|
||||
Auth: Auth{
|
||||
Method: "password",
|
||||
Recaptcha: Recaptcha{
|
||||
Host: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -35,3 +35,7 @@ func GenerateKey() ([]byte, error) {
|
|||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func GetSettingsConfig(nameType string,Value string) string {
|
||||
return nameType + Value
|
||||
}
|
|
@ -29,21 +29,31 @@ type Settings struct {
|
|||
Shell []string `json:"shell"`
|
||||
Rules []rules.Rule `json:"rules"`
|
||||
Server Server `json:"server"`
|
||||
AuthMethod string `json:"authMethod"`
|
||||
Auth struct {
|
||||
Header string `json:"header"`
|
||||
Method string `json:"method"`
|
||||
Command string `json:"command"`
|
||||
Signup bool `json:"signup"`
|
||||
Shell string `json:"shell"`
|
||||
} `json:"auth"`
|
||||
Auth Auth `json:"auth"`
|
||||
|
||||
Branding Branding `json:"branding"`
|
||||
Frontend Frontend `json:"frontend"`
|
||||
|
||||
UserDefaults UserDefaults `json:"userDefaults"`
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
Recaptcha Recaptcha
|
||||
Header string `json:"header"`
|
||||
Method string `json:"method"`
|
||||
Command string `json:"command"`
|
||||
Signup bool `json:"signup"`
|
||||
Shell string `json:"shell"`
|
||||
}
|
||||
|
||||
type Recaptcha struct {
|
||||
Host string
|
||||
Key string
|
||||
Secret string
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
IndexingInterval uint32 `json:"indexingInterval"`
|
||||
NumImageProcessors int `json:"numImageProcessors"`
|
||||
Socket string `json:"socket"`
|
||||
TLSKey string `json:"tlsKey"`
|
||||
TLSCert string `json:"tlsCert"`
|
||||
|
@ -52,7 +62,7 @@ type Server struct {
|
|||
EnableExec bool `json:"enableExec"`
|
||||
TypeDetectionByHeader bool `json:"typeDetectionByHeader"`
|
||||
AuthHook string `json:"authHook"`
|
||||
Port string `json:"port"`
|
||||
Port int `json:"port"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Address string `json:"address"`
|
||||
Log string `json:"log"`
|
||||
|
@ -61,7 +71,7 @@ type Server struct {
|
|||
EnablePreviewResize bool `json:"disable-preview-resize"`
|
||||
}
|
||||
|
||||
type Branding struct {
|
||||
type Frontend struct {
|
||||
Name string `json:"name"`
|
||||
DisableExternal bool `json:"disableExternal"`
|
||||
DisableUsedPercentage bool `json:"disableUsedPercentage"`
|
||||
|
|
|
@ -2,7 +2,6 @@ package bolt
|
|||
|
||||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
)
|
||||
|
@ -13,20 +12,18 @@ type authBackend struct {
|
|||
|
||||
func (s authBackend) Get(t string) (auth.Auther, error) {
|
||||
var auther auth.Auther
|
||||
|
||||
switch t {
|
||||
case auth.MethodJSONAuth:
|
||||
case "password":
|
||||
auther = &auth.JSONAuth{}
|
||||
case auth.MethodProxyAuth:
|
||||
case "proxy":
|
||||
auther = &auth.ProxyAuth{}
|
||||
case auth.MethodHookAuth:
|
||||
case "hook":
|
||||
auther = &auth.HookAuth{}
|
||||
case auth.MethodNoAuth:
|
||||
case "noauth":
|
||||
auther = &auth.NoAuth{}
|
||||
default:
|
||||
return nil, errors.ErrInvalidAuthMethod
|
||||
}
|
||||
|
||||
return auther, get(s.db, "auther", auther)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package bolt
|
|||
|
||||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
)
|
||||
|
||||
|
@ -20,7 +19,10 @@ func (s settingsBackend) Save(set *settings.Settings) error {
|
|||
}
|
||||
|
||||
func (s settingsBackend) GetServer() (*settings.Server, error) {
|
||||
server := &settings.Server{}
|
||||
server := &settings.Server{
|
||||
Port: 8080,
|
||||
NumImageProcessors: 1,
|
||||
}
|
||||
return server, get(s.db, "server", server)
|
||||
}
|
||||
|
||||
|
|
|
@ -140,22 +140,22 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
|
|||
|
||||
server := &settings.Server{
|
||||
BaseURL: cfg.BaseURL,
|
||||
Port: cfg.Port,
|
||||
Port: 8080,
|
||||
Address: cfg.Address,
|
||||
Log: cfg.Log,
|
||||
}
|
||||
|
||||
fmt.Println("config.go", server)
|
||||
var auther auth.Auther
|
||||
switch cfg.Auth.Method {
|
||||
case "proxy":
|
||||
auther = &auth.ProxyAuth{Header: cfg.Auth.Header}
|
||||
s.AuthMethod = string(auth.MethodProxyAuth)
|
||||
s.Auth.Method = "proxy"
|
||||
case "hook":
|
||||
auther = &auth.HookAuth{Command: cfg.Auth.Command}
|
||||
s.AuthMethod = string(auth.MethodHookAuth)
|
||||
s.Auth.Method = "hoook"
|
||||
case "none":
|
||||
auther = &auth.NoAuth{}
|
||||
s.AuthMethod = string(auth.MethodNoAuth)
|
||||
s.Auth.Method = "noauth"
|
||||
default:
|
||||
auther = &auth.JSONAuth{
|
||||
ReCaptcha: &auth.ReCaptcha{
|
||||
|
@ -164,7 +164,7 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
|
|||
Secret: cfg.ReCaptcha.Secret,
|
||||
},
|
||||
}
|
||||
s.AuthMethod = string(auth.MethodJSONAuth)
|
||||
s.Auth.Method = "password"
|
||||
}
|
||||
|
||||
err = sto.Auth.Save(auther)
|
||||
|
|
|
@ -2,12 +2,14 @@ package importer
|
|||
|
||||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
"log"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/storage/bolt"
|
||||
)
|
||||
|
||||
// Import imports an old configuration to a newer database.
|
||||
func Import(oldDBPath, oldConf, newDBPath string) error {
|
||||
log.Println(oldDBPath, oldConf, newDBPath)
|
||||
oldDB, err := storm.Open(oldDBPath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -2,7 +2,7 @@ package version
|
|||
|
||||
var (
|
||||
// Version is the current File Browser version.
|
||||
Version = "(0.1.4)"
|
||||
Version = "(0.2.0)"
|
||||
// CommitSHA is the commmit sha.
|
||||
CommitSHA = "(unknown)"
|
||||
)
|
||||
|
|
|
@ -213,3 +213,7 @@ nav {
|
|||
background: var(--background);
|
||||
color: white
|
||||
}
|
||||
#result-desktop #result-list {
|
||||
background: #2a3137;
|
||||
|
||||
}
|
|
@ -211,7 +211,7 @@
|
|||
border-top: none;
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
background-color: white;
|
||||
background: white;
|
||||
max-height: 80vh;
|
||||
left: 50%;
|
||||
-webkit-transform: translateX(-50%);
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
@click="focus"
|
||||
class="shell"
|
||||
ref="scrollable"
|
||||
:class="{ ['shell--hidden']: !showShell }"
|
||||
>
|
||||
<div v-for="(c, index) in content" :key="index" class="shell__result">
|
||||
<div class="shell__prompt">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
</div>
|
||||
<pre class="shell__text">{{ c.text }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="shell__result" :class="{ 'shell__result--hidden': !canInput }">
|
||||
<div class="shell__prompt">
|
||||
<i class="material-icons">chevron_right</i>
|
||||
</div>
|
||||
<pre
|
||||
tabindex="0"
|
||||
ref="input"
|
||||
class="shell__text"
|
||||
contenteditable="true"
|
||||
@keydown.prevent.38="historyUp"
|
||||
@keydown.prevent.40="historyDown"
|
||||
@keypress.prevent.enter="submit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations, mapState, mapGetters } from "vuex";
|
||||
import { commands } from "@/api";
|
||||
|
||||
export default {
|
||||
name: "shell",
|
||||
computed: {
|
||||
...mapState(["user", "showShell"]),
|
||||
...mapGetters(["isFiles", "isLogged"]),
|
||||
path: function () {
|
||||
if (this.isFiles) {
|
||||
return this.$route.path;
|
||||
}
|
||||
|
||||
return "";
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
content: [],
|
||||
history: [],
|
||||
historyPos: 0,
|
||||
canInput: true,
|
||||
}),
|
||||
methods: {
|
||||
...mapMutations(["toggleShell"]),
|
||||
scroll: function () {
|
||||
this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight;
|
||||
},
|
||||
focus: function () {
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
historyUp() {
|
||||
if (this.historyPos > 0) {
|
||||
this.$refs.input.innerText = this.history[--this.historyPos];
|
||||
this.focus();
|
||||
}
|
||||
},
|
||||
historyDown() {
|
||||
if (this.historyPos >= 0 && this.historyPos < this.history.length - 1) {
|
||||
this.$refs.input.innerText = this.history[++this.historyPos];
|
||||
this.focus();
|
||||
} else {
|
||||
this.historyPos = this.history.length;
|
||||
this.$refs.input.innerText = "";
|
||||
}
|
||||
},
|
||||
submit: function (event) {
|
||||
const cmd = event.target.innerText.trim();
|
||||
|
||||
if (cmd === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd === "clear") {
|
||||
this.content = [];
|
||||
event.target.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd === "exit") {
|
||||
event.target.innerHTML = "";
|
||||
this.toggleShell();
|
||||
return;
|
||||
}
|
||||
|
||||
this.canInput = false;
|
||||
event.target.innerHTML = "";
|
||||
|
||||
let results = {
|
||||
text: `${cmd}\n\n`,
|
||||
};
|
||||
|
||||
this.history.push(cmd);
|
||||
this.historyPos = this.history.length;
|
||||
this.content.push(results);
|
||||
|
||||
commands(
|
||||
this.path,
|
||||
cmd,
|
||||
(event) => {
|
||||
results.text += `${event.data}\n`;
|
||||
this.scroll();
|
||||
},
|
||||
() => {
|
||||
results.text = results.text.trimEnd();
|
||||
this.canInput = true;
|
||||
this.$refs.input.focus();
|
||||
this.scroll();
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -1,24 +1,12 @@
|
|||
<template>
|
||||
<header>
|
||||
<action
|
||||
class="menu-button"
|
||||
icon="menu"
|
||||
:label="$t('buttons.toggleSidebar')"
|
||||
@action="toggleSidebar()"
|
||||
/>
|
||||
|
||||
<slot />
|
||||
|
||||
<div id="dropdown" :class="{ active: this.$store.state.show === 'more' }">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { logoURL } from "@/utils/constants";
|
||||
import Action from "@/components/header/Action";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
|
||||
export default {
|
||||
name: "header-bar",
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
@import "./_variables.css";
|
||||
@import "./_buttons.css";
|
||||
@import "./_inputs.css";
|
||||
@import "./_shell.css";
|
||||
@import "./_share.css";
|
||||
@import "./fonts.css";
|
||||
@import "./base.css";
|
||||
|
|
|
@ -3,16 +3,13 @@
|
|||
<div v-if="progress" class="progress">
|
||||
<div v-bind:style="{ width: this.progress + '%' }"></div>
|
||||
</div>
|
||||
<header-bar showMenu showLogo>
|
||||
<search />
|
||||
<template #actions>
|
||||
<action icon="grid_view" :label="$t('buttons.switchView')" @action="switchView" />
|
||||
</template>
|
||||
</header-bar>
|
||||
<editorBar v-if="getCurrentView === 'editor'"></editorBar>
|
||||
<listingBar v-else-if="getCurrentView === 'listing'"></listingBar>
|
||||
<previewBar v-else-if="getCurrentView === 'preview'"></previewBar>
|
||||
<defaultBar v-else></defaultBar>
|
||||
<sidebar></sidebar>
|
||||
<main>
|
||||
<router-view></router-view>
|
||||
<shell v-if="isExecEnabled && isLogged && user.perm.execute" />
|
||||
</main>
|
||||
<prompts></prompts>
|
||||
<upload-files></upload-files>
|
||||
|
@ -20,60 +17,64 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import editorBar from "./files/Editor.vue"
|
||||
import defaultBar from "./files/Default.vue"
|
||||
import listingBar from"./files/Listing.vue"
|
||||
import previewBar from "./files/Preview.vue"
|
||||
import Prompts from "@/components/prompts/Prompts";
|
||||
import Shell from "@/components/Shell";
|
||||
import Action from "@/components/header/Action";
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import Sidebar from "@/components/Sidebar.vue";
|
||||
import UploadFiles from "../components/prompts/UploadFiles";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
import Search from "@/components/Search";
|
||||
import Action from "@/components/header/Action";
|
||||
|
||||
export default {
|
||||
name: "layout",
|
||||
components: {
|
||||
defaultBar,
|
||||
editorBar,
|
||||
listingBar,
|
||||
previewBar,
|
||||
Action,
|
||||
HeaderBar,
|
||||
Search,
|
||||
Sidebar,
|
||||
Prompts,
|
||||
Shell,
|
||||
UploadFiles,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
showContexts: true,
|
||||
dragCounter: 0,
|
||||
width: window.innerWidth,
|
||||
itemWeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["isLogged", "progress"]),
|
||||
...mapState(["user"]),
|
||||
...mapState(["req", "user", "currentView"]),
|
||||
|
||||
isExecEnabled: () => enableExec,
|
||||
getCurrentView() {
|
||||
return this.currentView;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
getCurrentView: function () {
|
||||
console.log(this.currentView)
|
||||
},
|
||||
$route: function () {
|
||||
this.$store.commit("resetSelected");
|
||||
this.$store.commit("multiple", false);
|
||||
if (this.$store.state.show !== "success")
|
||||
this.$store.commit("closeHovers");
|
||||
if (this.$store.state.show !== "success") this.$store.commit("closeHovers");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
switchView: async function () {
|
||||
this.$store.commit("closeHovers");
|
||||
const modes = {
|
||||
list: "mosaic",
|
||||
mosaic: "mosaic gallery",
|
||||
"mosaic gallery": "list",
|
||||
};
|
||||
|
||||
const data = {
|
||||
id: this.user.id,
|
||||
viewMode: modes[this.user.viewMode] || "list",
|
||||
};
|
||||
//users.update(data, ["viewMode"]).catch(this.$showError);
|
||||
this.$store.commit("updateUser", data);
|
||||
|
||||
//this.setItemWeight();
|
||||
//this.fillWindow();
|
||||
getTitle() {
|
||||
let title = "Title"
|
||||
if (this.$route.path.startsWith('/settings/')){
|
||||
title = "Settings"
|
||||
}
|
||||
return title
|
||||
},
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
|
@ -0,0 +1,630 @@
|
|||
<template>
|
||||
<header-bar>
|
||||
<action
|
||||
class="menu-button"
|
||||
icon="menu"
|
||||
:label="$t('buttons.toggleSidebar')"
|
||||
@action="toggleSidebar()"
|
||||
/>
|
||||
<search />
|
||||
<action
|
||||
class="menu-button"
|
||||
icon="grid_view"
|
||||
:label="$t('buttons.switchView')"
|
||||
@action="switchView"
|
||||
/>
|
||||
</header-bar>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.flexbar {
|
||||
display:flex;
|
||||
flex-direction:block;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import { mapState, mapGetters, mapMutations } from "vuex";
|
||||
import { users, files as api } from "@/api";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||
import Action from "@/components/header/Action.vue";
|
||||
import * as upload from "@/utils/upload";
|
||||
import css from "@/utils/css";
|
||||
import throttle from "lodash.throttle";
|
||||
import Search from "@/components/Search.vue";
|
||||
|
||||
import Item from "@/components/files/ListingItem.vue";
|
||||
|
||||
export default {
|
||||
name: "listing",
|
||||
components: {
|
||||
HeaderBar,
|
||||
Action,
|
||||
Search,
|
||||
Item,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
showLimit: 50,
|
||||
columnWidth: 280,
|
||||
dragCounter: 0,
|
||||
width: window.innerWidth,
|
||||
itemWeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "selected", "user", "show", "multiple", "selected", "loading"]),
|
||||
...mapGetters(["selectedCount"]),
|
||||
nameSorted() {
|
||||
return this.req.sorting.by === "name";
|
||||
},
|
||||
sizeSorted() {
|
||||
return this.req.sorting.by === "size";
|
||||
},
|
||||
modifiedSorted() {
|
||||
return this.req.sorting.by === "modified";
|
||||
},
|
||||
ascOrdered() {
|
||||
return this.req.sorting.asc;
|
||||
},
|
||||
items() {
|
||||
const dirs = [];
|
||||
const files = [];
|
||||
|
||||
this.req.items.forEach((item) => {
|
||||
if (item.isDir) {
|
||||
dirs.push(item);
|
||||
} else {
|
||||
files.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return { dirs, files };
|
||||
},
|
||||
dirs() {
|
||||
return this.items.dirs.slice(0, this.showLimit);
|
||||
},
|
||||
files() {
|
||||
let showLimit = this.showLimit - this.items.dirs.length;
|
||||
|
||||
if (showLimit < 0) showLimit = 0;
|
||||
|
||||
return this.items.files.slice(0, showLimit);
|
||||
},
|
||||
nameIcon() {
|
||||
if (this.nameSorted && !this.ascOrdered) {
|
||||
return "arrow_upward";
|
||||
}
|
||||
|
||||
return "arrow_downward";
|
||||
},
|
||||
sizeIcon() {
|
||||
if (this.sizeSorted && this.ascOrdered) {
|
||||
return "arrow_downward";
|
||||
}
|
||||
|
||||
return "arrow_upward";
|
||||
},
|
||||
modifiedIcon() {
|
||||
if (this.modifiedSorted && this.ascOrdered) {
|
||||
return "arrow_downward";
|
||||
}
|
||||
|
||||
return "arrow_upward";
|
||||
},
|
||||
viewIcon() {
|
||||
const icons = {
|
||||
list: "view_module",
|
||||
mosaic: "grid_view",
|
||||
"mosaic gallery": "view_list",
|
||||
};
|
||||
return icons[this.user.viewMode];
|
||||
},
|
||||
headerButtons() {
|
||||
return {
|
||||
select: this.selectedCount > 0,
|
||||
upload: this.user.perm.create && this.selectedCount > 0,
|
||||
download: this.user.perm.download && this.selectedCount > 0,
|
||||
delete: this.selectedCount > 0 && this.user.perm.delete,
|
||||
rename: this.selectedCount === 1 && this.user.perm.rename,
|
||||
share: this.selectedCount === 1 && this.user.perm.share,
|
||||
move: this.selectedCount > 0 && this.user.perm.rename,
|
||||
copy: this.selectedCount > 0 && this.user.perm.create,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
req: function () {
|
||||
// Reset the show value
|
||||
this.showLimit = 50;
|
||||
|
||||
// Ensures that the listing is displayed
|
||||
Vue.nextTick(() => {
|
||||
// How much every listing item affects the window height
|
||||
this.setItemWeight();
|
||||
|
||||
// Fill and fit the window with listing items
|
||||
this.fillWindow(true);
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
// Check the columns size for the first time.
|
||||
this.colunmsResize();
|
||||
|
||||
// How much every listing item affects the window height
|
||||
this.setItemWeight();
|
||||
|
||||
// Fill and fit the window with listing items
|
||||
this.fillWindow(true);
|
||||
|
||||
// Add the needed event listeners to the window and document.
|
||||
window.addEventListener("keydown", this.keyEvent);
|
||||
window.addEventListener("scroll", this.scrollEvent);
|
||||
window.addEventListener("resize", this.windowsResize);
|
||||
|
||||
if (!this.user.perm.create) return;
|
||||
document.addEventListener("dragover", this.preventDefault);
|
||||
document.addEventListener("dragenter", this.dragEnter);
|
||||
document.addEventListener("dragleave", this.dragLeave);
|
||||
document.addEventListener("drop", this.drop);
|
||||
},
|
||||
beforeDestroy() {
|
||||
// Remove event listeners before destroying this page.
|
||||
window.removeEventListener("keydown", this.keyEvent);
|
||||
window.removeEventListener("scroll", this.scrollEvent);
|
||||
window.removeEventListener("resize", this.windowsResize);
|
||||
|
||||
if (this.user && !this.user.perm.create) return;
|
||||
document.removeEventListener("dragover", this.preventDefault);
|
||||
document.removeEventListener("dragenter", this.dragEnter);
|
||||
document.removeEventListener("dragleave", this.dragLeave);
|
||||
document.removeEventListener("drop", this.drop);
|
||||
},
|
||||
methods: {
|
||||
action: function () {
|
||||
if (this.show) {
|
||||
this.$store.commit("showHover", this.show);
|
||||
}
|
||||
|
||||
this.$emit("action");
|
||||
},
|
||||
toggleSidebar() {
|
||||
if (this.$store.state.show == "sidebar") {
|
||||
this.$store.commit("closeHovers");
|
||||
} else {
|
||||
this.$store.commit("showHover", "sidebar");
|
||||
}
|
||||
},
|
||||
...mapMutations(["updateUser", "addSelected"]),
|
||||
base64: function (name) {
|
||||
return window.btoa(unescape(encodeURIComponent(name)));
|
||||
},
|
||||
keyEvent(event) {
|
||||
// No prompts are shown
|
||||
if (this.show !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
// Reset files selection.
|
||||
this.$store.commit("resetSelected");
|
||||
}
|
||||
|
||||
// Del!
|
||||
if (event.keyCode === 46) {
|
||||
if (!this.user.perm.delete || this.selectedCount == 0) return;
|
||||
|
||||
// Show delete prompt.
|
||||
this.$store.commit("showHover", "delete");
|
||||
}
|
||||
|
||||
// F2!
|
||||
if (event.keyCode === 113) {
|
||||
if (!this.user.perm.rename || this.selectedCount !== 1) return;
|
||||
|
||||
// Show rename prompt.
|
||||
this.$store.commit("showHover", "rename");
|
||||
}
|
||||
|
||||
// Ctrl is pressed
|
||||
if (!event.ctrlKey && !event.metaKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = String.fromCharCode(event.which).toLowerCase();
|
||||
|
||||
switch (key) {
|
||||
case "f":
|
||||
event.preventDefault();
|
||||
this.$store.commit("showHover", "search");
|
||||
break;
|
||||
case "c":
|
||||
case "x":
|
||||
this.copyCut(event, key);
|
||||
break;
|
||||
case "v":
|
||||
this.paste(event);
|
||||
break;
|
||||
case "a":
|
||||
event.preventDefault();
|
||||
for (let file of this.items.files) {
|
||||
if (this.$store.state.selected.indexOf(file.index) === -1) {
|
||||
this.addSelected(file.index);
|
||||
}
|
||||
}
|
||||
for (let dir of this.items.dirs) {
|
||||
if (this.$store.state.selected.indexOf(dir.index) === -1) {
|
||||
this.addSelected(dir.index);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "s":
|
||||
event.preventDefault();
|
||||
document.getElementById("download-button").click();
|
||||
break;
|
||||
}
|
||||
},
|
||||
switchView: async function () {
|
||||
this.$store.commit("closeHovers");
|
||||
const modes = {
|
||||
list: "mosaic",
|
||||
mosaic: "mosaic gallery",
|
||||
"mosaic gallery": "list",
|
||||
};
|
||||
|
||||
const data = {
|
||||
id: this.user.id,
|
||||
viewMode: modes[this.user.viewMode] || "list",
|
||||
};
|
||||
//users.update(data, ["viewMode"]).catch(this.$showError);
|
||||
this.$store.commit("updateUser", data);
|
||||
|
||||
//this.setItemWeight();
|
||||
//this.fillWindow();
|
||||
},
|
||||
preventDefault(event) {
|
||||
// Wrapper around prevent default.
|
||||
event.preventDefault();
|
||||
},
|
||||
copyCut(event, key) {
|
||||
if (event.target.tagName.toLowerCase() === "input") {
|
||||
return;
|
||||
}
|
||||
|
||||
let items = [];
|
||||
|
||||
for (let i of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[i].url,
|
||||
name: this.req.items[i].name,
|
||||
});
|
||||
}
|
||||
|
||||
if (items.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.commit("updateClipboard", {
|
||||
key: key,
|
||||
items: items,
|
||||
path: this.$route.path,
|
||||
});
|
||||
},
|
||||
paste(event) {
|
||||
if (event.target.tagName.toLowerCase() === "input") {
|
||||
return;
|
||||
}
|
||||
|
||||
let items = [];
|
||||
|
||||
for (let item of this.$store.state.clipboard.items) {
|
||||
const from = item.from.endsWith("/") ? item.from.slice(0, -1) : item.from;
|
||||
const to = this.$route.path + encodeURIComponent(item.name);
|
||||
items.push({ from, to, name: item.name });
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let action = (overwrite, rename) => {
|
||||
api
|
||||
.copy(items, overwrite, rename)
|
||||
.then(() => {
|
||||
this.$store.commit("setReload", true);
|
||||
})
|
||||
.catch(this.$showError);
|
||||
};
|
||||
|
||||
if (this.$store.state.clipboard.key === "x") {
|
||||
action = (overwrite, rename) => {
|
||||
api
|
||||
.move(items, overwrite, rename)
|
||||
.then(() => {
|
||||
this.$store.commit("resetClipboard");
|
||||
this.$store.commit("setReload", true);
|
||||
})
|
||||
.catch(this.$showError);
|
||||
};
|
||||
}
|
||||
|
||||
if (this.$store.state.clipboard.path == this.$route.path) {
|
||||
action(false, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let conflict = upload.checkConflict(items, this.req.items);
|
||||
|
||||
let overwrite = false;
|
||||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
action(overwrite, rename);
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
action(overwrite, rename);
|
||||
},
|
||||
colunmsResize() {
|
||||
// Update the columns size based on the window width.
|
||||
let columns = Math.floor(
|
||||
document.querySelector("main").offsetWidth / this.columnWidth
|
||||
);
|
||||
let items = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
||||
if (columns === 0) columns = 1;
|
||||
items.style.width = `calc(${100 / columns}% - 1em)`;
|
||||
},
|
||||
scrollEvent: throttle(function () {
|
||||
const totalItems = this.req.numDirs + this.req.numFiles;
|
||||
|
||||
// All items are displayed
|
||||
if (this.showLimit >= totalItems) return;
|
||||
|
||||
const currentPos = window.innerHeight + window.scrollY;
|
||||
|
||||
// Trigger at the 75% of the window height
|
||||
const triggerPos = document.body.offsetHeight - window.innerHeight * 0.25;
|
||||
|
||||
if (currentPos > triggerPos) {
|
||||
// Quantity of items needed to fill 2x of the window height
|
||||
const showQuantity = Math.ceil((window.innerHeight * 2) / this.itemWeight);
|
||||
|
||||
// Increase the number of displayed items
|
||||
this.showLimit += showQuantity;
|
||||
}
|
||||
}, 100),
|
||||
dragEnter() {
|
||||
this.dragCounter++;
|
||||
|
||||
// When the user starts dragging an item, put every
|
||||
// file on the listing with 50% opacity.
|
||||
let items = document.getElementsByClassName("item");
|
||||
|
||||
Array.from(items).forEach((file) => {
|
||||
file.style.opacity = 0.5;
|
||||
});
|
||||
},
|
||||
dragLeave() {
|
||||
this.dragCounter--;
|
||||
|
||||
if (this.dragCounter == 0) {
|
||||
this.resetOpacity();
|
||||
}
|
||||
},
|
||||
drop: async function (event) {
|
||||
event.preventDefault();
|
||||
this.dragCounter = 0;
|
||||
this.resetOpacity();
|
||||
|
||||
let dt = event.dataTransfer;
|
||||
let el = event.target;
|
||||
|
||||
if (dt.files.length <= 0) return;
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (el !== null && !el.classList.contains("item")) {
|
||||
el = el.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
let files = await upload.scanFiles(dt);
|
||||
let items = this.req.items;
|
||||
let path = this.$route.path.endsWith("/")
|
||||
? this.$route.path
|
||||
: this.$route.path + "/";
|
||||
|
||||
if (el !== null && el.classList.contains("item") && el.dataset.dir === "true") {
|
||||
// Get url from ListingItem instance
|
||||
path = el.__vue__.url;
|
||||
|
||||
try {
|
||||
items = (await api.fetch(path)).items;
|
||||
} catch (error) {
|
||||
this.$showError(error);
|
||||
}
|
||||
}
|
||||
|
||||
let conflict = upload.checkConflict(files, items);
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace",
|
||||
confirm: (event) => {
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
upload.handleFiles(files, path, true);
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
upload.handleFiles(files, path);
|
||||
},
|
||||
uploadInput(event) {
|
||||
this.$store.commit("closeHovers");
|
||||
|
||||
let files = event.currentTarget.files;
|
||||
let folder_upload =
|
||||
files[0].webkitRelativePath !== undefined && files[0].webkitRelativePath !== "";
|
||||
|
||||
if (folder_upload) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let file = files[i];
|
||||
files[i].fullPath = file.webkitRelativePath;
|
||||
}
|
||||
}
|
||||
|
||||
let path = this.$route.path.endsWith("/")
|
||||
? this.$route.path
|
||||
: this.$route.path + "/";
|
||||
let conflict = upload.checkConflict(files, this.req.items);
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace",
|
||||
confirm: (event) => {
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
upload.handleFiles(files, path, true);
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
upload.handleFiles(files, path);
|
||||
},
|
||||
resetOpacity() {
|
||||
let items = document.getElementsByClassName("item");
|
||||
|
||||
Array.from(items).forEach((file) => {
|
||||
file.style.opacity = 1;
|
||||
});
|
||||
},
|
||||
async sort(by) {
|
||||
let asc = false;
|
||||
|
||||
if (by === "name") {
|
||||
if (this.nameIcon === "arrow_upward") {
|
||||
asc = true;
|
||||
}
|
||||
} else if (by === "size") {
|
||||
if (this.sizeIcon === "arrow_upward") {
|
||||
asc = true;
|
||||
}
|
||||
} else if (by === "modified") {
|
||||
if (this.modifiedIcon === "arrow_upward") {
|
||||
asc = true;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await users.update({ id: this.user.id, sorting: { by, asc } }, ["sorting"]);
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
}
|
||||
|
||||
this.$store.commit("setReload", true);
|
||||
},
|
||||
openSearch() {
|
||||
this.$store.commit("showHover", "search");
|
||||
},
|
||||
toggleMultipleSelection() {
|
||||
this.$store.commit("multiple", !this.multiple);
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
windowsResize: throttle(function () {
|
||||
this.colunmsResize();
|
||||
this.width = window.innerWidth;
|
||||
|
||||
// Listing element is not displayed
|
||||
if (this.$refs.listing == null) return;
|
||||
|
||||
// How much every listing item affects the window height
|
||||
this.setItemWeight();
|
||||
|
||||
// Fill but not fit the window
|
||||
this.fillWindow();
|
||||
}, 100),
|
||||
download() {
|
||||
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||
api.download(null, this.req.items[this.selected[0]].url);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "download",
|
||||
confirm: (format) => {
|
||||
this.$store.commit("closeHovers");
|
||||
let files = [];
|
||||
if (this.selectedCount > 0) {
|
||||
for (let i of this.selected) {
|
||||
files.push(this.req.items[i].url);
|
||||
}
|
||||
} else {
|
||||
files.push(this.$route.path);
|
||||
}
|
||||
|
||||
api.download(format, ...files);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
upload: function () {
|
||||
if (
|
||||
typeof window.DataTransferItem !== "undefined" &&
|
||||
typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined"
|
||||
) {
|
||||
this.$store.commit("showHover", "upload");
|
||||
} else {
|
||||
document.getElementById("upload-input").click();
|
||||
}
|
||||
},
|
||||
setItemWeight() {
|
||||
// Listing element is not displayed
|
||||
if (this.$refs.listing == null) return;
|
||||
|
||||
let itemQuantity = this.req.numDirs + this.req.numFiles;
|
||||
if (itemQuantity > this.showLimit) itemQuantity = this.showLimit;
|
||||
|
||||
// How much every listing item affects the window height
|
||||
this.itemWeight = this.$refs.listing.offsetHeight / itemQuantity;
|
||||
},
|
||||
fillWindow(fit = false) {
|
||||
const totalItems = this.req.numDirs + this.req.numFiles;
|
||||
|
||||
// More items are displayed than the total
|
||||
if (this.showLimit >= totalItems && !fit) return;
|
||||
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
// Quantity of items needed to fill 2x of the window height
|
||||
const showQuantity = Math.ceil((windowHeight + windowHeight * 2) / this.itemWeight);
|
||||
|
||||
// Less items to display than current
|
||||
if (this.showLimit > showQuantity && !fit) return;
|
||||
|
||||
// Set the number of displayed items
|
||||
this.showLimit = showQuantity > totalItems ? totalItems : showQuantity;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -301,7 +301,6 @@ export default {
|
|||
select: this.selectedCount > 0,
|
||||
upload: this.user.perm.create && this.selectedCount > 0,
|
||||
download: this.user.perm.download && this.selectedCount > 0,
|
||||
shell: this.user.perm.execute && enableExec,
|
||||
delete: this.selectedCount > 0 && this.user.perm.delete,
|
||||
rename: this.selectedCount === 1 && this.user.perm.rename,
|
||||
share: this.selectedCount === 1 && this.user.perm.share,
|
||||
|
|
Loading…
Reference in New Issue