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