Merge pull request #17 from gtsteffaniak/another

Another
This commit is contained in:
Graham Steffaniak 2023-09-01 21:14:06 -05:00 committed by GitHub
commit db80387cf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 947 additions and 503 deletions

View File

@ -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 - Works with new more advanced filebrowser.json
- improved GUI - improved GUI
- more unified coehisive look - 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 # v0.1.4
- various UI fixes - various UI fixes

View File

@ -1,10 +0,0 @@
{
"port": 8080,
"baseURL": "",
"address": "",
"log": "stdout",
"database": "./database.db",
"root": "/srv"
}

View File

@ -15,9 +15,6 @@ import (
"github.com/gtsteffaniak/filebrowser/users" "github.com/gtsteffaniak/filebrowser/users"
) )
// MethodHookAuth is used to identify hook auth.
const MethodHookAuth = "hook"
type hookCred struct { type hookCred struct {
Password string `json:"password"` Password string `json:"password"`
Username string `json:"username"` Username string `json:"username"`

View File

@ -11,9 +11,6 @@ import (
"github.com/gtsteffaniak/filebrowser/users" "github.com/gtsteffaniak/filebrowser/users"
) )
// MethodJSONAuth is used to identify json auth.
const MethodJSONAuth = "json"
type jsonCred struct { type jsonCred struct {
Password string `json:"password"` Password string `json:"password"`
Username string `json:"username"` Username string `json:"username"`

View File

@ -28,51 +28,28 @@ var configCmd = &cobra.Command{
} }
func addConfigFlags(flags *pflag.FlagSet) { func addConfigFlags(flags *pflag.FlagSet) {
addServerFlags(flags)
addUserFlags(flags) addUserFlags(flags)
flags.BoolP("signup", "s", false, "allow users to signup") flags.BoolP("signup", "s", false, "allow users to signup")
flags.String("shell", "", "shell command to which other commands should be appended") 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.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.key", "", "ReCaptcha site key")
flags.String("recaptcha.secret", "", "ReCaptcha secret") flags.String("recaptcha.secret", "", "ReCaptcha secret")
flags.String("branding.name", "", "replace 'File Browser' by this name") flags.String("frontend.name", "", "replace 'File Browser' by this name")
flags.String("branding.color", "", "set the theme color") flags.String("frontend.color", "", "set the theme color")
flags.String("branding.files", "", "path to directory with images and custom styles") flags.String("frontend.files", "", "path to directory with images and custom styles")
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links") flags.Bool("frontend.disableExternal", false, "disable external links such as GitHub links")
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph") flags.Bool("frontend.disableUsedPercentage", false, "disable used disk percentage graph")
} }
//nolint:gocyclo //nolint:gocyclo
func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (string, auth.Auther) { func getAuthentication() (string, auth.Auther) {
method := mustGetString(flags, "auth.method") method := settings.GlobalConfiguration.Auth.Method
var defaultAuther map[string]interface{} 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 var auther auth.Auther
if method == auth.MethodProxyAuth { if method == "proxy" {
header := mustGetString(flags, "auth.header") header := settings.GlobalConfiguration.Auth.Header
if header == "" { if header == "" {
header = defaultAuther["header"].(string) header = defaultAuther["header"].(string)
} }
@ -84,15 +61,15 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (string, a
auther = &auth.ProxyAuth{Header: header} auther = &auth.ProxyAuth{Header: header}
} }
if method == auth.MethodNoAuth { if method == "noauth" {
auther = &auth.NoAuth{} auther = &auth.NoAuth{}
} }
if method == auth.MethodJSONAuth { if method == "password" {
jsonAuth := &auth.JSONAuth{} jsonAuth := &auth.JSONAuth{}
host := mustGetString(flags, "recaptcha.host") host := settings.GlobalConfiguration.Auth.Recaptcha.Host
key := mustGetString(flags, "recaptcha.key") key := settings.GlobalConfiguration.Auth.Recaptcha.Key
secret := mustGetString(flags, "recaptcha.secret") secret := settings.GlobalConfiguration.Auth.Recaptcha.Secret
if key == "" { if key == "" {
if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok { if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok {
@ -116,8 +93,8 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (string, a
auther = jsonAuth auther = jsonAuth
} }
if method == auth.MethodHookAuth { if method == "hook" {
command := mustGetString(flags, "auth.command") command := settings.GlobalConfiguration.Auth.Command
if command == "" { if command == "" {
command = defaultAuther["command"].(string) 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, "Sign up:\t%t\n", set.Signup)
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir) 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.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
fmt.Fprintln(w, "\nBranding:") fmt.Fprintln(w, "\nFrontend:")
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name) fmt.Fprintf(w, "\tName:\t%s\n", set.Frontend.Name)
fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Branding.Files) fmt.Fprintf(w, "\tFiles override:\t%s\n", set.Frontend.Files)
fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Branding.DisableExternal) fmt.Fprintf(w, "\tDisable external links:\t%t\n", set.Frontend.DisableExternal)
fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Branding.DisableUsedPercentage) fmt.Fprintf(w, "\tDisable used disk percentage graph:\t%t\n", set.Frontend.DisableUsedPercentage)
fmt.Fprintf(w, "\tColor:\t%s\n", set.Branding.Color) fmt.Fprintf(w, "\tColor:\t%s\n", set.Frontend.Color)
fmt.Fprintln(w, "\nServer:") fmt.Fprintln(w, "\nServer:")
fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log) fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log)
fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port) fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port)

View File

@ -18,7 +18,7 @@ var configCatCmd = &cobra.Command{
checkErr(err) checkErr(err)
ser, err := d.store.Settings.GetServer() ser, err := d.store.Settings.GetServer()
checkErr(err) checkErr(err)
auther, err := d.store.Auth.Get(set.AuthMethod) auther, err := d.store.Auth.Get(set.Auth.Method)
checkErr(err) checkErr(err)
printSettings(ser, set, auther) printSettings(ser, set, auther)
}, pythonConfig{}), }, pythonConfig{}),

View File

@ -22,7 +22,7 @@ and imported again with 'config import' command.`,
server, err := d.store.Settings.GetServer() server, err := d.store.Settings.GetServer()
checkErr(err) checkErr(err)
auther, err := d.store.Auth.Get(settings.AuthMethod) auther, err := d.store.Auth.Get(settings.Auth.Method)
checkErr(err) checkErr(err)
data := &settingsFile{ data := &settingsFile{

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"path/filepath" "path/filepath"
"reflect" "reflect"
"log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -43,11 +44,10 @@ The path must be for a json or yaml file.`,
} else { } else {
key = generateKey() key = generateKey()
} }
file := settingsFile{} file := settingsFile{}
err := unmarshal(args[0], &file) err := unmarshal(args[0], &file)
checkErr(err) checkErr(err)
log.Println(file.Settings)
file.Settings.Key = key file.Settings.Key = key
err = d.store.Settings.Save(file.Settings) err = d.store.Settings.Save(file.Settings)
checkErr(err) checkErr(err)
@ -61,16 +61,16 @@ The path must be for a json or yaml file.`,
} else { } else {
rawAuther = file.Auther rawAuther = file.Auther
} }
log.Println("config_import",file.Settings.Auth)
var auther auth.Auther var auther auth.Auther
switch file.Settings.AuthMethod { switch file.Settings.Auth.Method {
case auth.MethodJSONAuth: case "password":
auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth) auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth)
case auth.MethodNoAuth: case "noauth":
auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth) auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth)
case auth.MethodProxyAuth: case "proxy":
auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth) auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth)
case auth.MethodHookAuth: case "hook":
auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth) auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth)
default: default:
checkErr(errors.New("invalid auth method")) checkErr(errors.New("invalid auth method"))

View File

@ -26,33 +26,9 @@ override the options.`,
defaults := settings.UserDefaults{} defaults := settings.UserDefaults{}
flags := cmd.Flags() flags := cmd.Flags()
getUserDefaults(flags, &defaults, true) getUserDefaults(flags, &defaults, true)
authMethod, auther := getAuthentication(flags) _, auther := getAuthentication()
ser := &settings.GlobalConfiguration.Server
s := &settings.Settings{ err := d.store.Settings.Save(&settings.GlobalConfiguration)
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)
checkErr(err) checkErr(err)
err = d.store.Settings.SaveServer(ser) err = d.store.Settings.SaveServer(ser)
checkErr(err) 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 Now add your first user via 'filebrowser users add' and then you just
need to call the main command to boot up the server. need to call the main command to boot up the server.
`) `)
printSettings(ser, s, auther) printSettings(ser, &settings.GlobalConfiguration, auther)
}, pythonConfig{noDB: true}), }, pythonConfig{noDB: true}),
} }

View File

@ -24,7 +24,6 @@ you want to change. Other options will remain unchanged.`,
ser, err := d.store.Settings.GetServer() ser, err := d.store.Settings.GetServer()
checkErr(err) checkErr(err)
hasAuth := false
flags.Visit(func(flag *pflag.Flag) { flags.Visit(func(flag *pflag.Flag) {
switch flag.Name { switch flag.Name {
case "baseurl": case "baseurl":
@ -40,37 +39,30 @@ you want to change. Other options will remain unchanged.`,
case "address": case "address":
ser.Address = mustGetString(flags, flag.Name) ser.Address = mustGetString(flags, flag.Name)
case "port": case "port":
ser.Port = mustGetString(flags, flag.Name) ser.Port = 8080
case "log": case "log":
ser.Log = mustGetString(flags, flag.Name) ser.Log = mustGetString(flags, flag.Name)
case "signup": case "signup":
set.Signup = mustGetBool(flags, flag.Name) set.Signup = mustGetBool(flags, flag.Name)
case "auth.method":
hasAuth = true
case "shell": case "shell":
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name)) set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
case "branding.name": case "frontend.name":
set.Branding.Name = mustGetString(flags, flag.Name) set.Frontend.Name = mustGetString(flags, flag.Name)
case "branding.color": case "frontend.color":
set.Branding.Color = mustGetString(flags, flag.Name) set.Frontend.Color = mustGetString(flags, flag.Name)
case "branding.disableExternal": case "frontend.disableExternal":
set.Branding.DisableExternal = mustGetBool(flags, flag.Name) set.Frontend.DisableExternal = mustGetBool(flags, flag.Name)
case "branding.disableUsedPercentage": case "frontend.disableUsedPercentage":
set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name) set.Frontend.DisableUsedPercentage = mustGetBool(flags, flag.Name)
case "branding.files": case "frontend.files":
set.Branding.Files = mustGetString(flags, flag.Name) set.Frontend.Files = mustGetString(flags, flag.Name)
} }
}) })
getUserDefaults(flags, &set.Defaults, false) getUserDefaults(flags, &set.Defaults, false)
// read the defaults // read the defaults
auther, err := d.store.Auth.Get(set.AuthMethod) _, auther := getAuthentication()
checkErr(err)
// check if there are new flags for existing auth method
set.AuthMethod, auther = getAuthentication(flags, hasAuth, set, auther)
err = d.store.Auth.Save(auther) err = d.store.Auth.Save(auther)
checkErr(err) checkErr(err)
err = d.store.Settings.Save(set) err = d.store.Settings.Save(set)

View File

@ -2,7 +2,6 @@ package cmd
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"io" "io"
"io/fs" "io/fs"
"log" "log"
@ -10,7 +9,6 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@ -57,55 +55,19 @@ func init() {
flags.Bool("noauth", false, "use the noauth auther when using quick setup") 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("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\")") 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{ var rootCmd = &cobra.Command{
Use: "filebrowser", Use: "filebrowser",
Short: "A stylish web-based file browser", Short: "A stylish web-based file browser",
Long: `File Browser CLI lets you create the database to use with File Browser, Long: `
manage your users and all the configurations without acessing the
web interface.
If you've never run File Browser, you'll need to have a database for 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. 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 We're using Bolt DB which is a single file database and all managed
by ourselves. 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 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/ - $HOME/
@ -119,31 +81,20 @@ The precedence of the configuration values are as follows:
- database values - database values
- defaults - 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 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 the quick setup mode and a new database will be bootstraped and a new
user created with the credentials from options "username" and "password".`, user created with the credentials from options "username" and "password".`,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) { Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
log.Println(cfgFile) serverConfig := settings.GlobalConfiguration.Server
if !d.hadDB { if !d.hadDB {
quickSetup(cmd.Flags(), d) quickSetup(cmd.Flags(), d)
} }
if serverConfig.NumImageProcessors < 1 {
// build img service
workersCount, err := cmd.Flags().GetInt("img-processors")
checkErr(err)
if workersCount < 1 {
log.Fatal("Image resize workers count could not be < 1") log.Fatal("Image resize workers count could not be < 1")
} }
imgSvc := img.New(workersCount) imgSvc := img.New(serverConfig.NumImageProcessors)
var fileCache diskcache.Interface = diskcache.NewNoOp() var fileCache diskcache.Interface = diskcache.NewNoOp()
cacheDir, err := cmd.Flags().GetString("cache-dir") cacheDir := "/tmp"
checkErr(err)
if cacheDir != "" { if cacheDir != "" {
if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd
log.Fatalf("can't make directory %s: %s", cacheDir, err) 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) fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
} }
// initialize indexing and schedule indexing ever n minutes (default 5) // initialize indexing and schedule indexing ever n minutes (default 5)
indexingInterval := getEnvVariableAsUint32("INDEXING_INTERVAL") go search.InitializeIndex(serverConfig.IndexingInterval)
go search.InitializeIndex(indexingInterval) _, err := os.Stat(serverConfig.Root)
server := getRunParams(cmd.Flags(), d.store)
setupLog(server.Log)
root, err := filepath.Abs(server.Root)
checkErr(err) checkErr(err)
server.Root = root
adr := server.Address + ":" + server.Port
var listener net.Listener var listener net.Listener
address := serverConfig.Address + ":" + strconv.Itoa(serverConfig.Port)
switch { switch {
case server.Socket != "": case serverConfig.Socket != "":
listener, err = net.Listen("unix", server.Socket) listener, err = net.Listen("unix", serverConfig.Socket)
checkErr(err) checkErr(err)
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
checkErr(err) checkErr(err)
err = os.Chmod(server.Socket, os.FileMode(socketPerm)) err = os.Chmod(serverConfig.Socket, os.FileMode(socketPerm))
checkErr(err) checkErr(err)
case server.TLSKey != "" && server.TLSCert != "": case serverConfig.TLSKey != "" && serverConfig.TLSCert != "":
cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet cer, err := tls.LoadX509KeyPair(serverConfig.TLSCert, serverConfig.TLSKey) //nolint:govet
checkErr(err) checkErr(err)
listener, err = tls.Listen("tcp", adr, &tls.Config{ listener, err = tls.Listen("tcp", address, &tls.Config{
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{cer}}, Certificates: []tls.Certificate{cer}},
) )
checkErr(err) checkErr(err)
default: default:
listener, err = net.Listen("tcp", adr) listener, err = net.Listen("tcp", address)
checkErr(err) checkErr(err)
} }
sigc := make(chan os.Signal, 1) sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
go cleanupHandler(listener, sigc) go cleanupHandler(listener, sigc)
assetsFs := dirFS{Dir: http.Dir("frontend/dist")} 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) checkErr(err)
defer listener.Close() defer listener.Close()
log.Println("Listening on", listener.Addr().String()) log.Println("Listening on", listener.Addr().String())
//nolint: gosec //nolint: gosec
if err := http.Serve(listener, handler); err != nil { 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 { func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
server, err := st.Settings.GetServer() server, err := st.Settings.GetServer()
checkErr(err) 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 return server
} }
@ -348,32 +224,27 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
Download: true, Download: true,
}, },
}, },
AuthMethod: "", Frontend: settings.Frontend{},
Branding: settings.Branding{}, Commands: nil,
Commands: nil, Shell: nil,
Shell: nil, Rules: nil,
Rules: nil,
} }
var err error var err error
if _, noauth := getParamB(flags, "noauth"); noauth { if settings.GlobalConfiguration.Auth.Method == "noauth" {
set.AuthMethod = auth.MethodNoAuth set.Auth.Method = "noauth"
err = d.store.Auth.Save(&auth.NoAuth{}) err = d.store.Auth.Save(&auth.NoAuth{})
} else { } else {
set.AuthMethod = auth.MethodJSONAuth set.Auth.Method = "password"
err = d.store.Auth.Save(&auth.JSONAuth{}) err = d.store.Auth.Save(&auth.JSONAuth{})
} }
err = d.store.Settings.Save(set) err = d.store.Settings.Save(set)
checkErr(err) checkErr(err)
ser := &settings.Server{ ser := &settings.Server{
BaseURL: getParam(flags, "baseurl"), BaseURL: getParam(flags, "baseurl"),
Port: getParam(flags, "port"),
Log: getParam(flags, "log"), Log: getParam(flags, "log"),
TLSKey: getParam(flags, "key"), TLSKey: getParam(flags, "key"),
TLSCert: getParam(flags, "cert"), TLSCert: getParam(flags, "cert"),
Address: getParam(flags, "address"),
Root: getParam(flags, "root"), Root: getParam(flags, "root"),
} }
err = d.store.Settings.SaveServer(ser) err = d.store.Settings.SaveServer(ser)
@ -408,7 +279,7 @@ func initConfig() {
if cfgFile == "" { if cfgFile == "" {
v.AddConfigPath(".") v.AddConfigPath(".")
v.AddConfigPath("/etc/filebrowser/") v.AddConfigPath("/etc/filebrowser/")
v.SetConfigName(".filebrowser") v.SetConfigName("filebrowser")
} else { } else {
v.SetConfigFile(cfgFile) v.SetConfigFile(cfgFile)
} }

52
backend/filebrowser.yml Normal file
View File

@ -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

View File

@ -7,6 +7,7 @@ require (
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/dsoprea/go-exif/v3 v3.0.1 github.com/dsoprea/go-exif/v3 v3.0.1
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 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/golang-jwt/jwt/v4 v4.5.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.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/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // 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/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/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect github.com/go-errors/errors v1.4.2 // indirect
github.com/go-ole/go-ole v1.2.6 // 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/compress v1.11.4 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect github.com/klauspost/pgzip v1.2.5 // indirect
github.com/magiconair/properties v1.8.7 // 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/mitchellh/mapstructure v1.5.0 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect github.com/nwaples/rardecode v1.1.0 // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect github.com/pierrec/lz4/v4 v4.1.2 // indirect
@ -58,6 +62,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/net v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.11.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/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@ -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.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 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/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 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= 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-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 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-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 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 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= 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/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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 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/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 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 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/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 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=
github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ= 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 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 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-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-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-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-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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/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-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-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-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-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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/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-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-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-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.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.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=

View File

@ -102,7 +102,7 @@ func withAdmin(fn handleFunc) handleFunc {
} }
var loginHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { 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 { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }

View File

@ -14,7 +14,7 @@ type settingsData struct {
UserHomeBasePath string `json:"userHomeBasePath"` UserHomeBasePath string `json:"userHomeBasePath"`
Defaults settings.UserDefaults `json:"defaults"` Defaults settings.UserDefaults `json:"defaults"`
Rules []rules.Rule `json:"rules"` Rules []rules.Rule `json:"rules"`
Branding settings.Branding `json:"branding"` Frontend settings.Frontend `json:"frontend"`
Shell []string `json:"shell"` Shell []string `json:"shell"`
Commands map[string][]string `json:"commands"` Commands map[string][]string `json:"commands"`
} }
@ -26,7 +26,7 @@ var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request,
UserHomeBasePath: d.settings.UserHomeBasePath, UserHomeBasePath: d.settings.UserHomeBasePath,
Defaults: d.settings.Defaults, Defaults: d.settings.Defaults,
Rules: d.settings.Rules, Rules: d.settings.Rules,
Branding: d.settings.Branding, Frontend: d.settings.Frontend,
Shell: d.settings.Shell, Shell: d.settings.Shell,
Commands: d.settings.Commands, 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.UserHomeBasePath = req.UserHomeBasePath
d.settings.Defaults = req.Defaults d.settings.Defaults = req.Defaults
d.settings.Rules = req.Rules d.settings.Rules = req.Rules
d.settings.Branding = req.Branding d.settings.Frontend = req.Frontend
d.settings.Shell = req.Shell d.settings.Shell = req.Shell
d.settings.Commands = req.Commands d.settings.Commands = req.Commands
err = d.store.Settings.Save(d.settings) err = d.store.Settings.Save(d.settings)
return errToStatus(err), err return errToStatus(err), err
}) })

View File

@ -21,33 +21,33 @@ import (
func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys fs.FS, file, contentType string) (int, error) { func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys fs.FS, file, contentType string) (int, error) {
w.Header().Set("Content-Type", contentType) 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 { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
data := map[string]interface{}{ data := map[string]interface{}{
"Name": d.settings.Branding.Name, "Name": d.settings.Frontend.Name,
"DisableExternal": d.settings.Branding.DisableExternal, "DisableExternal": d.settings.Frontend.DisableExternal,
"DisableUsedPercentage": d.settings.Branding.DisableUsedPercentage, "DisableUsedPercentage": d.settings.Frontend.DisableUsedPercentage,
"Color": d.settings.Branding.Color, "Color": d.settings.Frontend.Color,
"BaseURL": d.server.BaseURL, "BaseURL": d.server.BaseURL,
"Version": version.Version, "Version": version.Version,
"StaticURL": path.Join(d.server.BaseURL, "/static"), "StaticURL": path.Join(d.server.BaseURL, "/static"),
"Signup": d.settings.Signup, "Signup": d.settings.Signup,
"NoAuth": d.settings.AuthMethod == auth.MethodNoAuth, "NoAuth": d.settings.Auth.Method == "noauth",
"AuthMethod": d.settings.AuthMethod, "AuthMethod": d.settings.Auth.Method,
"LoginPage": auther.LoginPage(), "LoginPage": auther.LoginPage(),
"CSS": false, "CSS": false,
"ReCaptcha": false, "ReCaptcha": false,
"Theme": d.settings.Branding.Theme, "Theme": d.settings.Frontend.Theme,
"EnableThumbs": d.server.EnableThumbnails, "EnableThumbs": d.server.EnableThumbnails,
"ResizePreview": d.server.ResizePreview, "ResizePreview": d.server.ResizePreview,
"EnableExec": d.server.EnableExec, "EnableExec": d.server.EnableExec,
} }
if d.settings.Branding.Files != "" { if d.settings.Frontend.Files != "" {
fPath := filepath.Join(d.settings.Branding.Files, "custom.css") fPath := filepath.Join(d.settings.Frontend.Files, "custom.css")
_, err := os.Stat(fPath) //nolint:govet _, err := os.Stat(fPath) //nolint:govet
if err != nil && !os.IsNotExist(err) { 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 { if d.settings.Auth.Method == "password" {
raw, err := d.store.Auth.Get(d.settings.AuthMethod) //nolint:govet raw, err := d.store.Auth.Get(d.settings.Auth.Method) //nolint:govet
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
@ -115,15 +115,15 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
const maxAge = 86400 // 1 day const maxAge = 86400 // 1 day
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%v", maxAge)) 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/") { 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 { if _, err := os.Stat(fPath); err == nil {
http.ServeFile(w, r, fPath) http.ServeFile(w, r, fPath)
return 0, nil return 0, nil
} }
} else if r.URL.Path == "custom.css" && d.settings.Branding.Files != "" { } else if r.URL.Path == "custom.css" && d.settings.Frontend.Files != "" {
http.ServeFile(w, r, filepath.Join(d.settings.Branding.Files, "custom.css")) http.ServeFile(w, r, filepath.Join(d.settings.Frontend.Files, "custom.css"))
return 0, nil return 0, nil
} }
} }

View File

View File

@ -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: "",
},
},
}
}

View File

@ -35,3 +35,7 @@ func GenerateKey() ([]byte, error) {
return b, nil return b, nil
} }
func GetSettingsConfig(nameType string,Value string) string {
return nameType + Value
}

View File

@ -29,21 +29,31 @@ type Settings struct {
Shell []string `json:"shell"` Shell []string `json:"shell"`
Rules []rules.Rule `json:"rules"` Rules []rules.Rule `json:"rules"`
Server Server `json:"server"` Server Server `json:"server"`
AuthMethod string `json:"authMethod"` Auth Auth `json:"auth"`
Auth struct {
Header string `json:"header"`
Method string `json:"method"`
Command string `json:"command"`
Signup bool `json:"signup"`
Shell string `json:"shell"`
} `json:"auth"`
Branding Branding `json:"branding"` Frontend Frontend `json:"frontend"`
UserDefaults UserDefaults `json:"userDefaults"` 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 { type Server struct {
IndexingInterval uint32 `json:"indexingInterval"`
NumImageProcessors int `json:"numImageProcessors"`
Socket string `json:"socket"` Socket string `json:"socket"`
TLSKey string `json:"tlsKey"` TLSKey string `json:"tlsKey"`
TLSCert string `json:"tlsCert"` TLSCert string `json:"tlsCert"`
@ -52,7 +62,7 @@ type Server struct {
EnableExec bool `json:"enableExec"` EnableExec bool `json:"enableExec"`
TypeDetectionByHeader bool `json:"typeDetectionByHeader"` TypeDetectionByHeader bool `json:"typeDetectionByHeader"`
AuthHook string `json:"authHook"` AuthHook string `json:"authHook"`
Port string `json:"port"` Port int `json:"port"`
BaseURL string `json:"baseURL"` BaseURL string `json:"baseURL"`
Address string `json:"address"` Address string `json:"address"`
Log string `json:"log"` Log string `json:"log"`
@ -61,7 +71,7 @@ type Server struct {
EnablePreviewResize bool `json:"disable-preview-resize"` EnablePreviewResize bool `json:"disable-preview-resize"`
} }
type Branding struct { type Frontend struct {
Name string `json:"name"` Name string `json:"name"`
DisableExternal bool `json:"disableExternal"` DisableExternal bool `json:"disableExternal"`
DisableUsedPercentage bool `json:"disableUsedPercentage"` DisableUsedPercentage bool `json:"disableUsedPercentage"`

View File

@ -2,7 +2,6 @@ package bolt
import ( import (
"github.com/asdine/storm/v3" "github.com/asdine/storm/v3"
"github.com/gtsteffaniak/filebrowser/auth" "github.com/gtsteffaniak/filebrowser/auth"
"github.com/gtsteffaniak/filebrowser/errors" "github.com/gtsteffaniak/filebrowser/errors"
) )
@ -13,20 +12,18 @@ type authBackend struct {
func (s authBackend) Get(t string) (auth.Auther, error) { func (s authBackend) Get(t string) (auth.Auther, error) {
var auther auth.Auther var auther auth.Auther
switch t { switch t {
case auth.MethodJSONAuth: case "password":
auther = &auth.JSONAuth{} auther = &auth.JSONAuth{}
case auth.MethodProxyAuth: case "proxy":
auther = &auth.ProxyAuth{} auther = &auth.ProxyAuth{}
case auth.MethodHookAuth: case "hook":
auther = &auth.HookAuth{} auther = &auth.HookAuth{}
case auth.MethodNoAuth: case "noauth":
auther = &auth.NoAuth{} auther = &auth.NoAuth{}
default: default:
return nil, errors.ErrInvalidAuthMethod return nil, errors.ErrInvalidAuthMethod
} }
return auther, get(s.db, "auther", auther) return auther, get(s.db, "auther", auther)
} }

View File

@ -2,7 +2,6 @@ package bolt
import ( import (
"github.com/asdine/storm/v3" "github.com/asdine/storm/v3"
"github.com/gtsteffaniak/filebrowser/settings" "github.com/gtsteffaniak/filebrowser/settings"
) )
@ -20,7 +19,10 @@ func (s settingsBackend) Save(set *settings.Settings) error {
} }
func (s settingsBackend) GetServer() (*settings.Server, 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) return server, get(s.db, "server", server)
} }

View File

@ -140,22 +140,22 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
server := &settings.Server{ server := &settings.Server{
BaseURL: cfg.BaseURL, BaseURL: cfg.BaseURL,
Port: cfg.Port, Port: 8080,
Address: cfg.Address, Address: cfg.Address,
Log: cfg.Log, Log: cfg.Log,
} }
fmt.Println("config.go", server)
var auther auth.Auther var auther auth.Auther
switch cfg.Auth.Method { switch cfg.Auth.Method {
case "proxy": case "proxy":
auther = &auth.ProxyAuth{Header: cfg.Auth.Header} auther = &auth.ProxyAuth{Header: cfg.Auth.Header}
s.AuthMethod = string(auth.MethodProxyAuth) s.Auth.Method = "proxy"
case "hook": case "hook":
auther = &auth.HookAuth{Command: cfg.Auth.Command} auther = &auth.HookAuth{Command: cfg.Auth.Command}
s.AuthMethod = string(auth.MethodHookAuth) s.Auth.Method = "hoook"
case "none": case "none":
auther = &auth.NoAuth{} auther = &auth.NoAuth{}
s.AuthMethod = string(auth.MethodNoAuth) s.Auth.Method = "noauth"
default: default:
auther = &auth.JSONAuth{ auther = &auth.JSONAuth{
ReCaptcha: &auth.ReCaptcha{ ReCaptcha: &auth.ReCaptcha{
@ -164,7 +164,7 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
Secret: cfg.ReCaptcha.Secret, Secret: cfg.ReCaptcha.Secret,
}, },
} }
s.AuthMethod = string(auth.MethodJSONAuth) s.Auth.Method = "password"
} }
err = sto.Auth.Save(auther) err = sto.Auth.Save(auther)

View File

@ -2,12 +2,14 @@ package importer
import ( import (
"github.com/asdine/storm/v3" "github.com/asdine/storm/v3"
"log"
"github.com/gtsteffaniak/filebrowser/storage/bolt" "github.com/gtsteffaniak/filebrowser/storage/bolt"
) )
// Import imports an old configuration to a newer database. // Import imports an old configuration to a newer database.
func Import(oldDBPath, oldConf, newDBPath string) error { func Import(oldDBPath, oldConf, newDBPath string) error {
log.Println(oldDBPath, oldConf, newDBPath)
oldDB, err := storm.Open(oldDBPath) oldDB, err := storm.Open(oldDBPath)
if err != nil { if err != nil {
return err return err

View File

@ -2,7 +2,7 @@ package version
var ( var (
// Version is the current File Browser version. // Version is the current File Browser version.
Version = "(0.1.4)" Version = "(0.2.0)"
// CommitSHA is the commmit sha. // CommitSHA is the commmit sha.
CommitSHA = "(unknown)" CommitSHA = "(unknown)"
) )

View File

@ -213,3 +213,7 @@ nav {
background: var(--background); background: var(--background);
color: white color: white
} }
#result-desktop #result-list {
background: #2a3137;
}

View File

@ -211,7 +211,7 @@
border-top: none; border-top: none;
border-top-left-radius: 0px; border-top-left-radius: 0px;
border-top-right-radius: 0px; border-top-right-radius: 0px;
background-color: white; background: white;
max-height: 80vh; max-height: 80vh;
left: 50%; left: 50%;
-webkit-transform: translateX(-50%); -webkit-transform: translateX(-50%);

View File

@ -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>

View File

@ -1,24 +1,12 @@
<template> <template>
<header> <header>
<action
class="menu-button"
icon="menu"
:label="$t('buttons.toggleSidebar')"
@action="toggleSidebar()"
/>
<slot /> <slot />
<div id="dropdown" :class="{ active: this.$store.state.show === 'more' }">
<slot name="actions" />
</div>
</header> </header>
</template> </template>
<script> <script>
import { logoURL } from "@/utils/constants"; import { logoURL } from "@/utils/constants";
import Action from "@/components/header/Action"; import Action from "@/components/header/Action.vue";
export default { export default {
name: "header-bar", name: "header-bar",

View File

@ -4,7 +4,6 @@
@import "./_variables.css"; @import "./_variables.css";
@import "./_buttons.css"; @import "./_buttons.css";
@import "./_inputs.css"; @import "./_inputs.css";
@import "./_shell.css";
@import "./_share.css"; @import "./_share.css";
@import "./fonts.css"; @import "./fonts.css";
@import "./base.css"; @import "./base.css";

View File

@ -3,16 +3,13 @@
<div v-if="progress" class="progress"> <div v-if="progress" class="progress">
<div v-bind:style="{ width: this.progress + '%' }"></div> <div v-bind:style="{ width: this.progress + '%' }"></div>
</div> </div>
<header-bar showMenu showLogo> <editorBar v-if="getCurrentView === 'editor'"></editorBar>
<search /> <listingBar v-else-if="getCurrentView === 'listing'"></listingBar>
<template #actions> <previewBar v-else-if="getCurrentView === 'preview'"></previewBar>
<action icon="grid_view" :label="$t('buttons.switchView')" @action="switchView" /> <defaultBar v-else></defaultBar>
</template>
</header-bar>
<sidebar></sidebar> <sidebar></sidebar>
<main> <main>
<router-view></router-view> <router-view></router-view>
<shell v-if="isExecEnabled && isLogged && user.perm.execute" />
</main> </main>
<prompts></prompts> <prompts></prompts>
<upload-files></upload-files> <upload-files></upload-files>
@ -20,60 +17,64 @@
</template> </template>
<script> <script>
import { mapState, mapGetters } from "vuex"; import editorBar from "./files/Editor.vue"
import Sidebar from "@/components/Sidebar"; 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 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 UploadFiles from "../components/prompts/UploadFiles";
import { enableExec } from "@/utils/constants"; import { enableExec } from "@/utils/constants";
import HeaderBar from "@/components/header/HeaderBar";
import Search from "@/components/Search";
import Action from "@/components/header/Action";
export default { export default {
name: "layout", name: "layout",
components: { components: {
defaultBar,
editorBar,
listingBar,
previewBar,
Action, Action,
HeaderBar,
Search,
Sidebar, Sidebar,
Prompts, Prompts,
Shell,
UploadFiles, UploadFiles,
}, },
data: function () {
return {
showContexts: true,
dragCounter: 0,
width: window.innerWidth,
itemWeight: 0,
};
},
computed: { computed: {
...mapGetters(["isLogged", "progress"]), ...mapGetters(["isLogged", "progress"]),
...mapState(["user"]), ...mapState(["req", "user", "currentView"]),
isExecEnabled: () => enableExec, isExecEnabled: () => enableExec,
getCurrentView() {
return this.currentView;
},
}, },
watch: { watch: {
getCurrentView: function () {
console.log(this.currentView)
},
$route: function () { $route: function () {
this.$store.commit("resetSelected"); this.$store.commit("resetSelected");
this.$store.commit("multiple", false); this.$store.commit("multiple", false);
if (this.$store.state.show !== "success") if (this.$store.state.show !== "success") this.$store.commit("closeHovers");
this.$store.commit("closeHovers");
}, },
}, },
methods: { methods: {
switchView: async function () { getTitle() {
this.$store.commit("closeHovers"); let title = "Title"
const modes = { if (this.$route.path.startsWith('/settings/')){
list: "mosaic", title = "Settings"
mosaic: "mosaic gallery", }
"mosaic gallery": "list", return title
};
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();
}, },
} },
}; };
</script> </script>

View File

@ -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>

View File

@ -301,7 +301,6 @@ export default {
select: this.selectedCount > 0, select: this.selectedCount > 0,
upload: this.user.perm.create && this.selectedCount > 0, upload: this.user.perm.create && this.selectedCount > 0,
download: this.user.perm.download && 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, delete: this.selectedCount > 0 && this.user.perm.delete,
rename: this.selectedCount === 1 && this.user.perm.rename, rename: this.selectedCount === 1 && this.user.perm.rename,
share: this.selectedCount === 1 && this.user.perm.share, share: this.selectedCount === 1 && this.user.perm.share,