14
									
								
								CHANGELOG.md
								
								
								
								
							
							
						
						|  | @ -2,6 +2,20 @@ | ||||||
| 
 | 
 | ||||||
| All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | # v0.2.0 | ||||||
|  | 
 | ||||||
|  |  - improved UI | ||||||
|  |    - more unified coehisive look | ||||||
|  |    - Adjusted header bar look and icon behavior | ||||||
|  |  - The shell is dead. | ||||||
|  |    - If you need to use custom commands, exec into the docker container. | ||||||
|  |  - The json config file is dead. | ||||||
|  |    - All configuration is done via advanced `filebrowser.yaml` | ||||||
|  |    - The only flag that is allowed is flag to specify config file. | ||||||
|  |  - Removed old code to migrate database versions | ||||||
|  |  - Removed all unused cmd code | ||||||
|  | 
 | ||||||
| # v0.1.4 | # v0.1.4 | ||||||
|  - various UI fixes |  - various UI fixes | ||||||
|    - Added download button back to toolbar |    - Added download button back to toolbar | ||||||
|  |  | ||||||
|  | @ -17,7 +17,7 @@ RUN apk --no-cache add \ | ||||||
| VOLUME /srv | VOLUME /srv | ||||||
| EXPOSE 8080 | EXPOSE 8080 | ||||||
| WORKDIR / | WORKDIR / | ||||||
| COPY --from=base /app/.filebrowser.json /.filebrowser.json | COPY --from=base /app/filebrowser.yaml /filebrowser.yaml | ||||||
| COPY --from=base /app/filebrowser /filebrowser | COPY --from=base /app/filebrowser /filebrowser | ||||||
| COPY --from=nbuild /app/dist/ /frontend/dist/ | COPY --from=nbuild /app/dist/ /frontend/dist/ | ||||||
| ENTRYPOINT [ "./filebrowser" ] | ENTRYPOINT [ "./filebrowser" ] | ||||||
							
								
								
									
										40
									
								
								README.md
								
								
								
								
							
							
						
						|  | @ -1,6 +1,10 @@ | ||||||
| ## Filebrowser | ## Filebrowser | ||||||
| 
 | 
 | ||||||
| **Note: Intended to be used in docker only.** | > **NOTE** | ||||||
|  | Intended for docker use only | ||||||
|  | 
 | ||||||
|  | > **Warning** | ||||||
|  | Starting with v0.2.0, *ALL* configuration is done via `filebrowser.yaml` configuration file. `.filebrowser.json` and any flags other than `-c` and `-config` during execution WILL NO LONGER WORK. This is by design, in order to use the v0.2.0 You can mount your directory and initialize a new DB with a new default `filebrowser.yaml` which you can tweak and use in the future. Or you can copy and paste the default startup `filebrowser.yaml` below. | ||||||
| 
 | 
 | ||||||
| This fork makes the following significant changes to filebrowser for origin: | This fork makes the following significant changes to filebrowser for origin: | ||||||
| 
 | 
 | ||||||
|  | @ -9,7 +13,7 @@ This fork makes the following significant changes to filebrowser for origin: | ||||||
|     - [x] Realtime results as you type |     - [x] Realtime results as you type | ||||||
|     - [x] Works with file type filter |     - [x] Works with file type filter | ||||||
|     - [x] better desktop search view |     - [x] better desktop search view | ||||||
|  1. [ ] Preview enhancements |  1. [x] Preview enhancements | ||||||
|     - Preview default view is constrained to files subwindow, |     - Preview default view is constrained to files subwindow, | ||||||
|     which can be toggled to fullscreen. |     which can be toggled to fullscreen. | ||||||
|  1. [x] Improved and simplified GUI |  1. [x] Improved and simplified GUI | ||||||
|  | @ -20,11 +24,8 @@ This fork makes the following significant changes to filebrowser for origin: | ||||||
|     - [x] Uses latest npm and node version |     - [x] Uses latest npm and node version | ||||||
|     - [x] Removes deprecated npm packages |     - [x] Removes deprecated npm packages | ||||||
|     - [x] Updates golang dependencies |     - [x] Updates golang dependencies | ||||||
|     - [ ] Remove all unnecessary packages, replaces with generic functions. |     - [x] Remove all unnecessary packages, replaces with generic functions. | ||||||
|  1. [ ] Moved all configurations to filebrowser.json. |  1. [x] **IMPORTANT** Moved all configurations to `filebrowser.yaml`. no more flags or binary operations to db | ||||||
|   no more flags or binary operations to db |  | ||||||
|  1. [ ] File browsing uses index first for better performance |  | ||||||
|     - file details shown only when toggled or needed |  | ||||||
| 
 | 
 | ||||||
| ## About | ## About | ||||||
| 
 | 
 | ||||||
|  | @ -44,14 +45,14 @@ Once this is fully complete, the only updates to th | ||||||
| 
 | 
 | ||||||
| ## Look | ## Look | ||||||
| 
 | 
 | ||||||
| General UI desktop (dark mode): | This is how desktop search looks in 0.2.0 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| [General UI mobile (dark mode)](https://github.com/gtsteffaniak/filebrowser/assets/42989099/634d3ba6-7ac0-425b-8a83-419743e92fec) |  | ||||||
| 
 | 
 | ||||||
|  | However [mobile search](https://github.com/gtsteffaniak/filebrowser/assets/42989099/37e8f03b-4f5a-4689-aa6c-5cd858a858e9) still appears very similar to filebrowser/filebrowsers original implementation. | ||||||
| 
 | 
 | ||||||
| [However, mobile search still appears very similar to filebrowser/filebrowsers original implementation](https://github.com/gtsteffaniak/filebrowser/assets/42989099/e179b821-f4e2-4568-b895-4e00de371637) | [Mobile web also looks similar](https://github.com/gtsteffaniak/filebrowser/assets/42989099/b04d3c1f-154b-45ba-927c-2112926ad3a9) | ||||||
| 
 | 
 | ||||||
| ## Performance | ## Performance | ||||||
| 
 | 
 | ||||||
|  | @ -124,14 +125,9 @@ volumes: | ||||||
| 
 | 
 | ||||||
| ## Configuration | ## Configuration | ||||||
| 
 | 
 | ||||||
| Note: still a WIP migrating configuration to json. | All configuration is now done via a single configuration file: `filebrowser.yaml`, here is an example [configuration file](./backend/filebrowser.yaml). | ||||||
|  | ### background | ||||||
| 
 | 
 | ||||||
| All configuration is now done via the filebrowser.json config file. | The original project filebrowser/filebrowser used multiple different ways to configure the server. | ||||||
| This was chosen because it works best with a docker first use case. | This was confusing and difficult to work with from a user and from a developer's perspective. | ||||||
| 
 | So I completely redesigned the program to use one single human-readable config file. | ||||||
| Previously the primary way to configure filebrowser was via flags. |  | ||||||
| But this quickly became cumbersome if you had many configurations to make |  | ||||||
| 
 |  | ||||||
| The other method to configure was via `filebrowser config` commands which |  | ||||||
| would write configurations to a db if it existed already. |  | ||||||
| When considering |  | ||||||
|  |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| { |  | ||||||
| 
 |  | ||||||
|   "port": 8080, |  | ||||||
|   "baseURL": "", |  | ||||||
|   "address": "", |  | ||||||
|   "log": "stdout", |  | ||||||
|   "database": "./database.db", |  | ||||||
|   "root": "/srv" |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -3,14 +3,13 @@ package auth | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gtsteffaniak/filebrowser/settings" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/users" | 	"github.com/gtsteffaniak/filebrowser/users" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Auther is the authentication interface.
 | // Auther is the authentication interface.
 | ||||||
| type Auther interface { | type Auther interface { | ||||||
| 	// Auth is called to authenticate a request.
 | 	// Auth is called to authenticate a request.
 | ||||||
| 	Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error) | 	Auth(r *http.Request, usr users.Store) (*users.User, error) | ||||||
| 	// LoginPage indicates if this auther needs a login page.
 | 	// LoginPage indicates if this auther needs a login page.
 | ||||||
| 	LoginPage() bool | 	LoginPage() bool | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -15,9 +15,6 @@ import ( | ||||||
| 	"github.com/gtsteffaniak/filebrowser/users" | 	"github.com/gtsteffaniak/filebrowser/users" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // MethodHookAuth is used to identify hook auth.
 |  | ||||||
| const MethodHookAuth = "hook" |  | ||||||
| 
 |  | ||||||
| type hookCred struct { | type hookCred struct { | ||||||
| 	Password string `json:"password"` | 	Password string `json:"password"` | ||||||
| 	Username string `json:"username"` | 	Username string `json:"username"` | ||||||
|  | @ -34,7 +31,7 @@ type HookAuth struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Auth authenticates the user via a json in content body.
 | // Auth authenticates the user via a json in content body.
 | ||||||
| func (a *HookAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error) { | func (a *HookAuth) Auth(r *http.Request, usr users.Store) (*users.User, error) { | ||||||
| 	var cred hookCred | 	var cred hookCred | ||||||
| 
 | 
 | ||||||
| 	if r.Body == nil { | 	if r.Body == nil { | ||||||
|  | @ -47,8 +44,8 @@ func (a *HookAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	a.Users = usr | 	a.Users = usr | ||||||
| 	a.Settings = stg | 	a.Settings = &settings.GlobalConfiguration | ||||||
| 	a.Server = srv | 	a.Server = &settings.GlobalConfiguration.Server | ||||||
| 	a.Cred = cred | 	a.Cred = cred | ||||||
| 
 | 
 | ||||||
| 	action, err := a.RunCommand() | 	action, err := a.RunCommand() | ||||||
|  | @ -153,19 +150,18 @@ func (a *HookAuth) SaveUser() (*users.User, error) { | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		// create user with the provided credentials
 | 		// create user with the provided credentials
 | ||||||
| 		d := &users.User{ | 		d := &users.User{ | ||||||
| 			Username:     a.Cred.Username, | 			Username:     a.Cred.Username, | ||||||
| 			Password:     pass, | 			Password:     pass, | ||||||
| 			Scope:        a.Settings.Defaults.Scope, | 			Scope:        a.Settings.UserDefaults.Scope, | ||||||
| 			Locale:       a.Settings.Defaults.Locale, | 			Locale:       a.Settings.UserDefaults.Locale, | ||||||
| 			ViewMode:     a.Settings.Defaults.ViewMode, | 			ViewMode:     a.Settings.UserDefaults.ViewMode, | ||||||
| 			SingleClick:  a.Settings.Defaults.SingleClick, | 			SingleClick:  a.Settings.UserDefaults.SingleClick, | ||||||
| 			Sorting:      a.Settings.Defaults.Sorting, | 			Sorting:      a.Settings.UserDefaults.Sorting, | ||||||
| 			Perm:         a.Settings.Defaults.Perm, | 			Perm:         a.Settings.UserDefaults.Perm, | ||||||
| 			Commands:     a.Settings.Defaults.Commands, | 			Commands:     a.Settings.UserDefaults.Commands, | ||||||
| 			HideDotfiles: a.Settings.Defaults.HideDotfiles, | 			HideDotfiles: a.Settings.UserDefaults.HideDotfiles, | ||||||
| 		} | 		} | ||||||
| 		u = a.GetUser(d) | 		u = a.GetUser(d) | ||||||
| 
 | 
 | ||||||
|  | @ -222,7 +218,7 @@ func (a *HookAuth) GetUser(d *users.User) *users.User { | ||||||
| 		Password:    d.Password, | 		Password:    d.Password, | ||||||
| 		Scope:       a.Fields.GetString("user.scope", d.Scope), | 		Scope:       a.Fields.GetString("user.scope", d.Scope), | ||||||
| 		Locale:      a.Fields.GetString("user.locale", d.Locale), | 		Locale:      a.Fields.GetString("user.locale", d.Locale), | ||||||
| 		ViewMode:    users.ViewMode(a.Fields.GetString("user.viewMode", string(d.ViewMode))), | 		ViewMode:    d.ViewMode, | ||||||
| 		SingleClick: a.Fields.GetBoolean("user.singleClick", d.SingleClick), | 		SingleClick: a.Fields.GetBoolean("user.singleClick", d.SingleClick), | ||||||
| 		Sorting: files.Sorting{ | 		Sorting: files.Sorting{ | ||||||
| 			Asc: a.Fields.GetBoolean("user.sorting.asc", d.Sorting.Asc), | 			Asc: a.Fields.GetBoolean("user.sorting.asc", d.Sorting.Asc), | ||||||
|  |  | ||||||
|  | @ -11,9 +11,6 @@ import ( | ||||||
| 	"github.com/gtsteffaniak/filebrowser/users" | 	"github.com/gtsteffaniak/filebrowser/users" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // MethodJSONAuth is used to identify json auth.
 |  | ||||||
| const MethodJSONAuth = "json" |  | ||||||
| 
 |  | ||||||
| type jsonCred struct { | type jsonCred struct { | ||||||
| 	Password  string `json:"password"` | 	Password  string `json:"password"` | ||||||
| 	Username  string `json:"username"` | 	Username  string `json:"username"` | ||||||
|  | @ -26,7 +23,8 @@ type JSONAuth struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Auth authenticates the user via a json in content body.
 | // Auth authenticates the user via a json in content body.
 | ||||||
| func (a JSONAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error) { | func (a JSONAuth) Auth(r *http.Request, usr users.Store) (*users.User, error) { | ||||||
|  | 	config := &settings.GlobalConfiguration | ||||||
| 	var cred jsonCred | 	var cred jsonCred | ||||||
| 
 | 
 | ||||||
| 	if r.Body == nil { | 	if r.Body == nil { | ||||||
|  | @ -51,7 +49,7 @@ func (a JSONAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	u, err := usr.Get(srv.Root, cred.Username) | 	u, err := usr.Get(config.Server.Root, cred.Username) | ||||||
| 	if err != nil || !users.CheckPwd(cred.Password, u.Password) { | 	if err != nil || !users.CheckPwd(cred.Password, u.Password) { | ||||||
| 		return nil, os.ErrPermission | 		return nil, os.ErrPermission | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -14,8 +14,8 @@ const MethodNoAuth = "noauth" | ||||||
| type NoAuth struct{} | type NoAuth struct{} | ||||||
| 
 | 
 | ||||||
| // Auth uses authenticates user 1.
 | // Auth uses authenticates user 1.
 | ||||||
| func (a NoAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error) { | func (a NoAuth) Auth(r *http.Request, usr users.Store) (*users.User, error) { | ||||||
| 	return usr.Get(srv.Root, uint(1)) | 	return usr.Get(settings.GlobalConfiguration.Server.Root, uint(1)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LoginPage tells that no auth doesn't require a login page.
 | // LoginPage tells that no auth doesn't require a login page.
 | ||||||
|  |  | ||||||
|  | @ -4,8 +4,9 @@ import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gtsteffaniak/filebrowser/errors" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/settings" | 	"github.com/gtsteffaniak/filebrowser/settings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gtsteffaniak/filebrowser/errors" | ||||||
| 	"github.com/gtsteffaniak/filebrowser/users" | 	"github.com/gtsteffaniak/filebrowser/users" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -18,9 +19,9 @@ type ProxyAuth struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Auth authenticates the user via an HTTP header.
 | // Auth authenticates the user via an HTTP header.
 | ||||||
| func (a ProxyAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error) { | func (a ProxyAuth) Auth(r *http.Request, usr users.Store) (*users.User, error) { | ||||||
| 	username := r.Header.Get(a.Header) | 	username := r.Header.Get(a.Header) | ||||||
| 	user, err := usr.Get(srv.Root, username) | 	user, err := usr.Get(settings.GlobalConfiguration.Server.Root, username) | ||||||
| 	if err == errors.ErrNotExist { | 	if err == errors.ErrNotExist { | ||||||
| 		return nil, os.ErrPermission | 		return nil, os.ErrPermission | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | 
 | ||||||
|  |  == Running benchmark ==  | ||||||
|  | /usr/local/go/bin/go | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/auth	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/cmd	[no test files] | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/diskcache	0.004s | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/errors	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/files	[no test files] | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/fileutils	0.004s | ||||||
|  | 2023/09/02 19:15:20 h: 401  <nil> | ||||||
|  | 2023/09/02 19:15:20 h: 401  <nil> | ||||||
|  | 2023/09/02 19:15:20 h: 401  <nil> | ||||||
|  | 2023/09/02 19:15:20 h: 401  <nil> | ||||||
|  | 2023/09/02 19:15:20 h: 401  <nil> | ||||||
|  | 2023/09/02 19:15:20 h: 401  <nil> | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/http	0.094s | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/img	0.122s | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/rules	0.002s | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/runner	0.004s | ||||||
|  | goos: linux | ||||||
|  | goarch: amd64 | ||||||
|  | pkg: github.com/gtsteffaniak/filebrowser/search | ||||||
|  | cpu: 11th Gen Intel(R) Core(TM) i5-11320H @ 3.20GHz | ||||||
|  | BenchmarkSearchAllIndexes-8   	      10	   5176084 ns/op	 2743632 B/op	   42785 allocs/op | ||||||
|  | BenchmarkFillIndex-8          	      10	   3263308 ns/op	   18485 B/op	     453 allocs/op | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/search	0.109s | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/settings	0.004s | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/share	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/storage	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/storage/bolt	[no test files] | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/users	0.004s | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/version	[no test files] | ||||||
|  | @ -1,12 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"log" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Execute executes the commands.
 |  | ||||||
| func Execute() { |  | ||||||
| 	if err := rootCmd.Execute(); err != nil { |  | ||||||
| 		log.Fatal(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,26 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 
 |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	rootCmd.AddCommand(cmdsCmd) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var cmdsCmd = &cobra.Command{ |  | ||||||
| 	Use:   "cmds", |  | ||||||
| 	Short: "Command runner management utility", |  | ||||||
| 	Long:  `Command runner management utility.`, |  | ||||||
| 	Args:  cobra.NoArgs, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func printEvents(m map[string][]string) { |  | ||||||
| 	for evt, cmds := range m { |  | ||||||
| 		for i, cmd := range cmds { |  | ||||||
| 			fmt.Printf("%s(%d): %s\n", evt, i, cmd) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,27 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"strings" |  | ||||||
| 
 |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	cmdsCmd.AddCommand(cmdsAddCmd) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var cmdsAddCmd = &cobra.Command{ |  | ||||||
| 	Use:   "add <event> <command>", |  | ||||||
| 	Short: "Add a command to run on a specific event", |  | ||||||
| 	Long:  `Add a command to run on a specific event.`, |  | ||||||
| 	Args:  cobra.MinimumNArgs(2), //nolint:gomnd
 |  | ||||||
| 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { |  | ||||||
| 		s, err := d.store.Settings.Get() |  | ||||||
| 		checkErr(err) |  | ||||||
| 		command := strings.Join(args[1:], " ") |  | ||||||
| 		s.Commands[args[0]] = append(s.Commands[args[0]], command) |  | ||||||
| 		err = d.store.Settings.Save(s) |  | ||||||
| 		checkErr(err) |  | ||||||
| 		printEvents(s.Commands) |  | ||||||
| 	}, pythonConfig{}), |  | ||||||
| } |  | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	cmdsCmd.AddCommand(cmdsLsCmd) |  | ||||||
| 	cmdsLsCmd.Flags().StringP("event", "e", "", "event name, without 'before' or 'after'") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var cmdsLsCmd = &cobra.Command{ |  | ||||||
| 	Use:   "ls", |  | ||||||
| 	Short: "List all commands for each event", |  | ||||||
| 	Long:  `List all commands for each event.`, |  | ||||||
| 	Args:  cobra.NoArgs, |  | ||||||
| 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { |  | ||||||
| 		s, err := d.store.Settings.Get() |  | ||||||
| 		checkErr(err) |  | ||||||
| 		evt := mustGetString(cmd.Flags(), "event") |  | ||||||
| 
 |  | ||||||
| 		if evt == "" { |  | ||||||
| 			printEvents(s.Commands) |  | ||||||
| 		} else { |  | ||||||
| 			show := map[string][]string{} |  | ||||||
| 			show["before_"+evt] = s.Commands["before_"+evt] |  | ||||||
| 			show["after_"+evt] = s.Commands["after_"+evt] |  | ||||||
| 			printEvents(show) |  | ||||||
| 		} |  | ||||||
| 	}, pythonConfig{}), |  | ||||||
| } |  | ||||||
|  | @ -1,56 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"strconv" |  | ||||||
| 
 |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	cmdsCmd.AddCommand(cmdsRmCmd) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var cmdsRmCmd = &cobra.Command{ |  | ||||||
| 	Use:   "rm <event> <index> [index_end]", |  | ||||||
| 	Short: "Removes a command from an event hooker", |  | ||||||
| 	Long: `Removes a command from an event hooker. The provided index |  | ||||||
| is the same that's printed when you run 'cmds ls'. Note |  | ||||||
| that after each removal/addition, the index of the |  | ||||||
| commands change. So be careful when removing them after each |  | ||||||
| other. |  | ||||||
| 
 |  | ||||||
| You can also specify an optional parameter (index_end) so |  | ||||||
| you can remove all commands from 'index' to 'index_end', |  | ||||||
| including 'index_end'.`, |  | ||||||
| 	Args: func(cmd *cobra.Command, args []string) error { |  | ||||||
| 		if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { //nolint:gomnd
 |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, arg := range args[1:] { |  | ||||||
| 			if _, err := strconv.Atoi(arg); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return nil |  | ||||||
| 	}, |  | ||||||
| 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { |  | ||||||
| 		s, err := d.store.Settings.Get() |  | ||||||
| 		checkErr(err) |  | ||||||
| 		evt := args[0] |  | ||||||
| 
 |  | ||||||
| 		i, err := strconv.Atoi(args[1]) |  | ||||||
| 		checkErr(err) |  | ||||||
| 		f := i |  | ||||||
| 		if len(args) == 3 { //nolint:gomnd
 |  | ||||||
| 			f, err = strconv.Atoi(args[2]) |  | ||||||
| 			checkErr(err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...) |  | ||||||
| 		err = d.store.Settings.Save(s) |  | ||||||
| 		checkErr(err) |  | ||||||
| 		printEvents(s.Commands) |  | ||||||
| 	}, pythonConfig{}), |  | ||||||
| } |  | ||||||
|  | @ -1,186 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	nerrors "errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"os" |  | ||||||
| 	"strings" |  | ||||||
| 	"text/tabwriter" |  | ||||||
| 
 |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| 	"github.com/spf13/pflag" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/auth" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/errors" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/settings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	rootCmd.AddCommand(configCmd) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var configCmd = &cobra.Command{ |  | ||||||
| 	Use:   "config", |  | ||||||
| 	Short: "Configuration management utility", |  | ||||||
| 	Long:  `Configuration management utility.`, |  | ||||||
| 	Args:  cobra.NoArgs, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| //nolint:gocyclo
 |  | ||||||
| func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (string, auth.Auther) { |  | ||||||
| 	method := mustGetString(flags, "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 header == "" { |  | ||||||
| 			header = defaultAuther["header"].(string) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if header == "" { |  | ||||||
| 			checkErr(nerrors.New("you must set the flag 'auth.header' for method 'proxy'")) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		auther = &auth.ProxyAuth{Header: header} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if method == auth.MethodNoAuth { |  | ||||||
| 		auther = &auth.NoAuth{} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if method == auth.MethodJSONAuth { |  | ||||||
| 		jsonAuth := &auth.JSONAuth{} |  | ||||||
| 		host := mustGetString(flags, "recaptcha.host") |  | ||||||
| 		key := mustGetString(flags, "recaptcha.key") |  | ||||||
| 		secret := mustGetString(flags, "recaptcha.secret") |  | ||||||
| 
 |  | ||||||
| 		if key == "" { |  | ||||||
| 			if kmap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok { |  | ||||||
| 				key = kmap["key"].(string) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if secret == "" { |  | ||||||
| 			if smap, ok := defaultAuther["recaptcha"].(map[string]interface{}); ok { |  | ||||||
| 				secret = smap["secret"].(string) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if key != "" && secret != "" { |  | ||||||
| 			jsonAuth.ReCaptcha = &auth.ReCaptcha{ |  | ||||||
| 				Host:   host, |  | ||||||
| 				Key:    key, |  | ||||||
| 				Secret: secret, |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		auther = jsonAuth |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if method == auth.MethodHookAuth { |  | ||||||
| 		command := mustGetString(flags, "auth.command") |  | ||||||
| 
 |  | ||||||
| 		if command == "" { |  | ||||||
| 			command = defaultAuther["command"].(string) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if command == "" { |  | ||||||
| 			checkErr(nerrors.New("you must set the flag 'auth.command' for method 'hook'")) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		auther = &auth.HookAuth{Command: command} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if auther == nil { |  | ||||||
| 		panic(errors.ErrInvalidAuthMethod) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return method, auther |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Auther) { |  | ||||||
| 	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd
 |  | ||||||
| 
 |  | ||||||
| 	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, "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, "\nServer:") |  | ||||||
| 	fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log) |  | ||||||
| 	fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port) |  | ||||||
| 	fmt.Fprintf(w, "\tBase URL:\t%s\n", ser.BaseURL) |  | ||||||
| 	fmt.Fprintf(w, "\tRoot:\t%s\n", ser.Root) |  | ||||||
| 	fmt.Fprintf(w, "\tSocket:\t%s\n", ser.Socket) |  | ||||||
| 	fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address) |  | ||||||
| 	fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert) |  | ||||||
| 	fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey) |  | ||||||
| 	fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec) |  | ||||||
| 	fmt.Fprintln(w, "\nDefaults:") |  | ||||||
| 	fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope) |  | ||||||
| 	fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale) |  | ||||||
| 	fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode) |  | ||||||
| 	fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick) |  | ||||||
| 	fmt.Fprintf(w, "\tCommands:\t%s\n", strings.Join(set.Defaults.Commands, " ")) |  | ||||||
| 	fmt.Fprintf(w, "\tSorting:\n") |  | ||||||
| 	fmt.Fprintf(w, "\t\tBy:\t%s\n", set.Defaults.Sorting.By) |  | ||||||
| 	fmt.Fprintf(w, "\t\tAsc:\t%t\n", set.Defaults.Sorting.Asc) |  | ||||||
| 	fmt.Fprintf(w, "\tPermissions:\n") |  | ||||||
| 	fmt.Fprintf(w, "\t\tAdmin:\t%t\n", set.Defaults.Perm.Admin) |  | ||||||
| 	fmt.Fprintf(w, "\t\tExecute:\t%t\n", set.Defaults.Perm.Execute) |  | ||||||
| 	fmt.Fprintf(w, "\t\tCreate:\t%t\n", set.Defaults.Perm.Create) |  | ||||||
| 	fmt.Fprintf(w, "\t\tRename:\t%t\n", set.Defaults.Perm.Rename) |  | ||||||
| 	fmt.Fprintf(w, "\t\tModify:\t%t\n", set.Defaults.Perm.Modify) |  | ||||||
| 	fmt.Fprintf(w, "\t\tDelete:\t%t\n", set.Defaults.Perm.Delete) |  | ||||||
| 	fmt.Fprintf(w, "\t\tShare:\t%t\n", set.Defaults.Perm.Share) |  | ||||||
| 	fmt.Fprintf(w, "\t\tDownload:\t%t\n", set.Defaults.Perm.Download) |  | ||||||
| 	w.Flush() |  | ||||||
| 
 |  | ||||||
| 	b, err := json.MarshalIndent(auther, "", "  ") |  | ||||||
| 	checkErr(err) |  | ||||||
| 	fmt.Printf("\nAuther configuration (raw):\n\n%s\n\n", string(b)) |  | ||||||
| } |  | ||||||
|  | @ -1,25 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	configCmd.AddCommand(configCatCmd) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var configCatCmd = &cobra.Command{ |  | ||||||
| 	Use:   "cat", |  | ||||||
| 	Short: "Prints the configuration", |  | ||||||
| 	Long:  `Prints the configuration.`, |  | ||||||
| 	Args:  cobra.NoArgs, |  | ||||||
| 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { |  | ||||||
| 		set, err := d.store.Settings.Get() |  | ||||||
| 		checkErr(err) |  | ||||||
| 		ser, err := d.store.Settings.GetServer() |  | ||||||
| 		checkErr(err) |  | ||||||
| 		auther, err := d.store.Auth.Get(set.AuthMethod) |  | ||||||
| 		checkErr(err) |  | ||||||
| 		printSettings(ser, set, auther) |  | ||||||
| 	}, pythonConfig{}), |  | ||||||
| } |  | ||||||
|  | @ -1,37 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	configCmd.AddCommand(configExportCmd) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var configExportCmd = &cobra.Command{ |  | ||||||
| 	Use:   "export <path>", |  | ||||||
| 	Short: "Export the configuration to a file", |  | ||||||
| 	Long: `Export the configuration to a file. The path must be for a |  | ||||||
| json or yaml file. This exported configuration can be changed, |  | ||||||
| and imported again with 'config import' command.`, |  | ||||||
| 	Args: jsonYamlArg, |  | ||||||
| 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { |  | ||||||
| 		settings, err := d.store.Settings.Get() |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		server, err := d.store.Settings.GetServer() |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		auther, err := d.store.Auth.Get(settings.AuthMethod) |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		data := &settingsFile{ |  | ||||||
| 			Settings: settings, |  | ||||||
| 			Auther:   auther, |  | ||||||
| 			Server:   server, |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		err = marshal(args[0], data) |  | ||||||
| 		checkErr(err) |  | ||||||
| 	}, pythonConfig{}), |  | ||||||
| } |  | ||||||
|  | @ -1,94 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"reflect" |  | ||||||
| 
 |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/auth" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/settings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	configCmd.AddCommand(configImportCmd) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type settingsFile struct { |  | ||||||
| 	Settings *settings.Settings `json:"settings"` |  | ||||||
| 	Server   *settings.Server   `json:"server"` |  | ||||||
| 	Auther   interface{}        `json:"auther"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var configImportCmd = &cobra.Command{ |  | ||||||
| 	Use:   "import <path>", |  | ||||||
| 	Short: "Import a configuration file", |  | ||||||
| 	Long: `Import a configuration file. This will replace all the existing |  | ||||||
| configuration. Can be used with or without unexisting databases. |  | ||||||
| 
 |  | ||||||
| If used with a nonexisting database, a key will be generated |  | ||||||
| automatically. Otherwise the key will be kept the same as in the |  | ||||||
| database. |  | ||||||
| 
 |  | ||||||
| The path must be for a json or yaml file.`, |  | ||||||
| 	Args: jsonYamlArg, |  | ||||||
| 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { |  | ||||||
| 		var key []byte |  | ||||||
| 		if d.hadDB { |  | ||||||
| 			settings, err := d.store.Settings.Get() |  | ||||||
| 			checkErr(err) |  | ||||||
| 			key = settings.Key |  | ||||||
| 		} else { |  | ||||||
| 			key = generateKey() |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		file := settingsFile{} |  | ||||||
| 		err := unmarshal(args[0], &file) |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		file.Settings.Key = key |  | ||||||
| 		err = d.store.Settings.Save(file.Settings) |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		err = d.store.Settings.SaveServer(file.Server) |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		var rawAuther interface{} |  | ||||||
| 		if filepath.Ext(args[0]) != ".json" { //nolint:goconst
 |  | ||||||
| 			rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{})) |  | ||||||
| 		} else { |  | ||||||
| 			rawAuther = file.Auther |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var auther auth.Auther |  | ||||||
| 		switch file.Settings.AuthMethod { |  | ||||||
| 		case auth.MethodJSONAuth: |  | ||||||
| 			auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth) |  | ||||||
| 		case auth.MethodNoAuth: |  | ||||||
| 			auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth) |  | ||||||
| 		case auth.MethodProxyAuth: |  | ||||||
| 			auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth) |  | ||||||
| 		case auth.MethodHookAuth: |  | ||||||
| 			auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth) |  | ||||||
| 		default: |  | ||||||
| 			checkErr(errors.New("invalid auth method")) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		err = d.store.Auth.Save(auther) |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		printSettings(file.Server, file.Settings, auther) |  | ||||||
| 	}, pythonConfig{allowNoDB: true}), |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func getAuther(sample auth.Auther, data interface{}) interface{} { |  | ||||||
| 	authType := reflect.TypeOf(sample) |  | ||||||
| 	auther := reflect.New(authType).Interface() |  | ||||||
| 	bytes, err := json.Marshal(data) |  | ||||||
| 	checkErr(err) |  | ||||||
| 	err = json.Unmarshal(bytes, &auther) |  | ||||||
| 	checkErr(err) |  | ||||||
| 	return auther |  | ||||||
| } |  | ||||||
|  | @ -1,69 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 
 |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/settings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	configCmd.AddCommand(configInitCmd) |  | ||||||
| 	addConfigFlags(configInitCmd.Flags()) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var configInitCmd = &cobra.Command{ |  | ||||||
| 	Use:   "init", |  | ||||||
| 	Short: "Initialize a new database", |  | ||||||
| 	Long: `Initialize a new database to use with File Browser. All of |  | ||||||
| this options can be changed in the future with the command |  | ||||||
| 'filebrowser config set'. The user related flags apply |  | ||||||
| to the defaults when creating new users and you don't |  | ||||||
| override the options.`, |  | ||||||
| 	Args: cobra.NoArgs, |  | ||||||
| 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { |  | ||||||
| 		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) |  | ||||||
| 		checkErr(err) |  | ||||||
| 		err = d.store.Settings.SaveServer(ser) |  | ||||||
| 		checkErr(err) |  | ||||||
| 		err = d.store.Auth.Save(auther) |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		fmt.Printf(` |  | ||||||
| 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) |  | ||||||
| 	}, pythonConfig{noDB: true}), |  | ||||||
| } |  | ||||||
|  | @ -1,82 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| 	"github.com/spf13/pflag" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	configCmd.AddCommand(configSetCmd) |  | ||||||
| 	addConfigFlags(configSetCmd.Flags()) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var configSetCmd = &cobra.Command{ |  | ||||||
| 	Use:   "set", |  | ||||||
| 	Short: "Updates the configuration", |  | ||||||
| 	Long: `Updates the configuration. Set the flags for the options |  | ||||||
| you want to change. Other options will remain unchanged.`, |  | ||||||
| 	Args: cobra.NoArgs, |  | ||||||
| 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { |  | ||||||
| 		flags := cmd.Flags() |  | ||||||
| 		set, err := d.store.Settings.Get() |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		ser, err := d.store.Settings.GetServer() |  | ||||||
| 		checkErr(err) |  | ||||||
| 
 |  | ||||||
| 		hasAuth := false |  | ||||||
| 		flags.Visit(func(flag *pflag.Flag) { |  | ||||||
| 			switch flag.Name { |  | ||||||
| 			case "baseurl": |  | ||||||
| 				ser.BaseURL = mustGetString(flags, flag.Name) |  | ||||||
| 			case "root": |  | ||||||
| 				ser.Root = mustGetString(flags, flag.Name) |  | ||||||
| 			case "socket": |  | ||||||
| 				ser.Socket = mustGetString(flags, flag.Name) |  | ||||||
| 			case "cert": |  | ||||||
| 				ser.TLSCert = mustGetString(flags, flag.Name) |  | ||||||
| 			case "key": |  | ||||||
| 				ser.TLSKey = mustGetString(flags, flag.Name) |  | ||||||
| 			case "address": |  | ||||||
| 				ser.Address = mustGetString(flags, flag.Name) |  | ||||||
| 			case "port": |  | ||||||
| 				ser.Port = mustGetString(flags, flag.Name) |  | ||||||
| 			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) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 
 |  | ||||||
| 		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) |  | ||||||
| 
 |  | ||||||
| 		err = d.store.Auth.Save(auther) |  | ||||||
| 		checkErr(err) |  | ||||||
| 		err = d.store.Settings.Save(set) |  | ||||||
| 		checkErr(err) |  | ||||||
| 		err = d.store.Settings.SaveServer(ser) |  | ||||||
| 		checkErr(err) |  | ||||||
| 		printSettings(ser, set, auther) |  | ||||||
| 	}, pythonConfig{}), |  | ||||||
| } |  | ||||||
|  | @ -1,139 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"sort" |  | ||||||
| 	"strings" |  | ||||||
| 
 |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| 	"github.com/spf13/pflag" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	rootCmd.AddCommand(docsCmd) |  | ||||||
| 	docsCmd.Flags().StringP("path", "p", "./docs", "path to save the docs") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func printToc(names []string) { |  | ||||||
| 	for i, name := range names { |  | ||||||
| 		name = strings.TrimSuffix(name, filepath.Ext(name)) |  | ||||||
| 		name = strings.Replace(name, "-", " ", -1) |  | ||||||
| 		names[i] = name |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	sort.Strings(names) |  | ||||||
| 
 |  | ||||||
| 	toc := "" |  | ||||||
| 	for _, name := range names { |  | ||||||
| 		toc += "* [" + name + "](cli/" + strings.Replace(name, " ", "-", -1) + ".md)\n" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fmt.Println(toc) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var docsCmd = &cobra.Command{ |  | ||||||
| 	Use:    "docs", |  | ||||||
| 	Hidden: true, |  | ||||||
| 	Args:   cobra.NoArgs, |  | ||||||
| 	Run: func(cmd *cobra.Command, args []string) { |  | ||||||
| 		dir := mustGetString(cmd.Flags(), "path") |  | ||||||
| 		generateDocs(rootCmd, dir) |  | ||||||
| 		names := []string{} |  | ||||||
| 
 |  | ||||||
| 		err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { |  | ||||||
| 			if err != nil || info.IsDir() { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if !strings.HasPrefix(info.Name(), "filebrowser") { |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			names = append(names, info.Name()) |  | ||||||
| 			return nil |  | ||||||
| 		}) |  | ||||||
| 
 |  | ||||||
| 		checkErr(err) |  | ||||||
| 		printToc(names) |  | ||||||
| 	}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func generateDocs(cmd *cobra.Command, dir string) { |  | ||||||
| 	for _, c := range cmd.Commands() { |  | ||||||
| 		if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		generateDocs(c, dir) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md" |  | ||||||
| 	filename := filepath.Join(dir, basename) |  | ||||||
| 	f, err := os.Create(filename) |  | ||||||
| 	checkErr(err) |  | ||||||
| 	defer f.Close() |  | ||||||
| 	generateMarkdown(cmd, f) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func generateMarkdown(cmd *cobra.Command, w io.Writer) { |  | ||||||
| 	cmd.InitDefaultHelpCmd() |  | ||||||
| 	cmd.InitDefaultHelpFlag() |  | ||||||
| 
 |  | ||||||
| 	buf := new(bytes.Buffer) |  | ||||||
| 	name := cmd.CommandPath() |  | ||||||
| 
 |  | ||||||
| 	short := cmd.Short |  | ||||||
| 	long := cmd.Long |  | ||||||
| 	if long == "" { |  | ||||||
| 		long = short |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	buf.WriteString("---\ndescription: " + short + "\n---\n\n") |  | ||||||
| 	buf.WriteString("# " + name + "\n\n") |  | ||||||
| 	buf.WriteString("## Synopsis\n\n") |  | ||||||
| 	buf.WriteString(long + "\n\n") |  | ||||||
| 
 |  | ||||||
| 	if cmd.Runnable() { |  | ||||||
| 		buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if len(cmd.Example) > 0 { |  | ||||||
| 		buf.WriteString("## Examples\n\n") |  | ||||||
| 		buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	printOptions(buf, cmd) |  | ||||||
| 	_, err := buf.WriteTo(w) |  | ||||||
| 	checkErr(err) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) { |  | ||||||
| 	_, _ = buf.WriteString("| Name | Shorthand | Usage |\n") |  | ||||||
| 	_, _ = buf.WriteString("|------|-----------|-------|\n") |  | ||||||
| 
 |  | ||||||
| 	fs.VisitAll(func(f *pflag.Flag) { |  | ||||||
| 		_, _ = buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n") |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func printOptions(buf *bytes.Buffer, cmd *cobra.Command) { |  | ||||||
| 	flags := cmd.NonInheritedFlags() |  | ||||||
| 	flags.SetOutput(buf) |  | ||||||
| 	if flags.HasAvailableFlags() { |  | ||||||
| 		buf.WriteString("## Options\n\n") |  | ||||||
| 		generateFlagsTable(flags, buf) |  | ||||||
| 		buf.WriteString("\n") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	parentFlags := cmd.InheritedFlags() |  | ||||||
| 	parentFlags.SetOutput(buf) |  | ||||||
| 	if parentFlags.HasAvailableFlags() { |  | ||||||
| 		buf.WriteString("### Inherited\n\n") |  | ||||||
| 		generateFlagsTable(parentFlags, buf) |  | ||||||
| 		buf.WriteString("\n") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -2,7 +2,7 @@ package cmd | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
| 	"errors" | 	"flag" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/fs" | 	"io/fs" | ||||||
| 	"log" | 	"log" | ||||||
|  | @ -10,15 +10,13 @@ import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| 	"path/filepath" |  | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" |  | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/spf13/pflag" | ||||||
|  | 
 | ||||||
| 	"github.com/spf13/afero" | 	"github.com/spf13/afero" | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
| 	"github.com/spf13/pflag" |  | ||||||
| 	v "github.com/spf13/viper" |  | ||||||
| 	lumberjack "gopkg.in/natefinch/lumberjack.v2" | 	lumberjack "gopkg.in/natefinch/lumberjack.v2" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gtsteffaniak/filebrowser/auth" | 	"github.com/gtsteffaniak/filebrowser/auth" | ||||||
|  | @ -32,7 +30,7 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	cfgFile string | 	configFile string | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type dirFS struct { | type dirFS struct { | ||||||
|  | @ -44,106 +42,28 @@ func (d dirFS) Open(name string) (fs.File, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
| 	cobra.OnInitialize(initConfig) | 	// Define a flag for the config option (-c or --config)
 | ||||||
| 	cobra.MousetrapHelpText = "" | 	configFlag := pflag.StringP("config", "c", "filebrowser.yaml", "Path to the config file") | ||||||
| 	rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n") | 	// Bind the flags to the pflag command line parser
 | ||||||
| 
 | 	pflag.CommandLine.AddGoFlagSet(flag.CommandLine) | ||||||
| 	flags := rootCmd.Flags() | 	pflag.Parse() | ||||||
| 
 | 	log.Println("Initializing with config file:", *configFlag) | ||||||
| 	persistent := rootCmd.PersistentFlags() | 	settings.Initialize(*configFlag) | ||||||
| 
 |  | ||||||
| 	persistent.StringVarP(&cfgFile, "config", "c", "", "config file path") |  | ||||||
| 	persistent.StringP("database", "d", "./filebrowser.db", "database path") |  | ||||||
| 	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{ | var rootCmd = &cobra.Command{ | ||||||
| 	Use: "filebrowser", | 	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. |  | ||||||
| 
 |  | ||||||
| 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: |  | ||||||
| 
 |  | ||||||
| - ./ |  | ||||||
| - $HOME/ |  | ||||||
| - /etc/filebrowser/ |  | ||||||
| 
 |  | ||||||
| The precedence of the configuration values are as follows: |  | ||||||
| 
 |  | ||||||
| - flags |  | ||||||
| - environment variables |  | ||||||
| - configuration file |  | ||||||
| - 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) { | 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { | ||||||
| 		log.Println(cfgFile) | 		serverConfig := settings.GlobalConfiguration.Server | ||||||
| 
 |  | ||||||
| 		if !d.hadDB { | 		if !d.hadDB { | ||||||
| 			quickSetup(cmd.Flags(), d) | 			quickSetup(d) | ||||||
| 		} | 		} | ||||||
| 
 | 		if serverConfig.NumImageProcessors < 1 { | ||||||
| 		// build img service
 |  | ||||||
| 		workersCount, err := cmd.Flags().GetInt("img-processors") |  | ||||||
| 		checkErr(err) |  | ||||||
| 		if workersCount < 1 { |  | ||||||
| 			log.Fatal("Image resize workers count could not be < 1") | 			log.Fatal("Image resize workers count could not be < 1") | ||||||
| 		} | 		} | ||||||
| 		imgSvc := img.New(workersCount) | 		imgSvc := img.New(serverConfig.NumImageProcessors) | ||||||
| 
 |  | ||||||
| 		var fileCache diskcache.Interface = diskcache.NewNoOp() | 		var fileCache diskcache.Interface = diskcache.NewNoOp() | ||||||
| 		cacheDir, err := cmd.Flags().GetString("cache-dir") | 		cacheDir := "/tmp" | ||||||
| 		checkErr(err) |  | ||||||
| 		if cacheDir != "" { | 		if cacheDir != "" { | ||||||
| 			if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd
 | 			if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd
 | ||||||
| 				log.Fatalf("can't make directory %s: %s", cacheDir, err) | 				log.Fatalf("can't make directory %s: %s", cacheDir, err) | ||||||
|  | @ -151,51 +71,38 @@ user created with the credentials from options "username" and "password".`, | ||||||
| 			fileCache = diskcache.New(afero.NewOsFs(), cacheDir) | 			fileCache = diskcache.New(afero.NewOsFs(), cacheDir) | ||||||
| 		} | 		} | ||||||
| 		// initialize indexing and schedule indexing ever n minutes (default 5)
 | 		// initialize indexing and schedule indexing ever n minutes (default 5)
 | ||||||
| 		indexingInterval := getEnvVariableAsUint32("INDEXING_INTERVAL") | 		go search.InitializeIndex(serverConfig.IndexingInterval) | ||||||
| 		go search.InitializeIndex(indexingInterval) | 		_, err := os.Stat(serverConfig.Root) | ||||||
| 
 |  | ||||||
| 		server := getRunParams(cmd.Flags(), d.store) |  | ||||||
| 		setupLog(server.Log) |  | ||||||
| 
 |  | ||||||
| 		root, err := filepath.Abs(server.Root) |  | ||||||
| 		checkErr(err) | 		checkErr(err) | ||||||
| 		server.Root = root |  | ||||||
| 
 |  | ||||||
| 		adr := server.Address + ":" + server.Port |  | ||||||
| 
 |  | ||||||
| 		var listener net.Listener | 		var listener net.Listener | ||||||
| 
 | 		address := serverConfig.Address + ":" + strconv.Itoa(serverConfig.Port) | ||||||
| 		switch { | 		switch { | ||||||
| 		case server.Socket != "": | 		case serverConfig.Socket != "": | ||||||
| 			listener, err = net.Listen("unix", server.Socket) | 			listener, err = net.Listen("unix", serverConfig.Socket) | ||||||
| 			checkErr(err) | 			checkErr(err) | ||||||
| 			socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
 | 			socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
 | ||||||
| 			checkErr(err) | 			checkErr(err) | ||||||
| 			err = os.Chmod(server.Socket, os.FileMode(socketPerm)) | 			err = os.Chmod(serverConfig.Socket, os.FileMode(socketPerm)) | ||||||
| 			checkErr(err) | 			checkErr(err) | ||||||
| 		case server.TLSKey != "" && server.TLSCert != "": | 		case serverConfig.TLSKey != "" && serverConfig.TLSCert != "": | ||||||
| 			cer, err := tls.LoadX509KeyPair(server.TLSCert, server.TLSKey) //nolint:govet
 | 			cer, err := tls.LoadX509KeyPair(serverConfig.TLSCert, serverConfig.TLSKey) //nolint:govet
 | ||||||
| 			checkErr(err) | 			checkErr(err) | ||||||
| 			listener, err = tls.Listen("tcp", adr, &tls.Config{ | 			listener, err = tls.Listen("tcp", address, &tls.Config{ | ||||||
| 				MinVersion:   tls.VersionTLS12, | 				MinVersion:   tls.VersionTLS12, | ||||||
| 				Certificates: []tls.Certificate{cer}}, | 				Certificates: []tls.Certificate{cer}}, | ||||||
| 			) | 			) | ||||||
| 			checkErr(err) | 			checkErr(err) | ||||||
| 		default: | 		default: | ||||||
| 			listener, err = net.Listen("tcp", adr) | 			listener, err = net.Listen("tcp", address) | ||||||
| 			checkErr(err) | 			checkErr(err) | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		sigc := make(chan os.Signal, 1) | 		sigc := make(chan os.Signal, 1) | ||||||
| 		signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) | 		signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) | ||||||
| 		go cleanupHandler(listener, sigc) | 		go cleanupHandler(listener, sigc) | ||||||
| 
 |  | ||||||
| 		assetsFs := dirFS{Dir: http.Dir("frontend/dist")} | 		assetsFs := dirFS{Dir: http.Dir("frontend/dist")} | ||||||
| 		handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, server, assetsFs) | 		handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs) | ||||||
| 		checkErr(err) | 		checkErr(err) | ||||||
| 
 |  | ||||||
| 		defer listener.Close() | 		defer listener.Close() | ||||||
| 
 |  | ||||||
| 		log.Println("Listening on", listener.Addr().String()) | 		log.Println("Listening on", listener.Addr().String()) | ||||||
| 		//nolint: gosec
 | 		//nolint: gosec
 | ||||||
| 		if err := http.Serve(listener, handler); err != nil { | 		if err := http.Serve(listener, handler); err != nil { | ||||||
|  | @ -204,6 +111,12 @@ user created with the credentials from options "username" and "password".`, | ||||||
| 	}, pythonConfig{allowNoDB: true}), | 	}, pythonConfig{allowNoDB: true}), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func StartFilebrowser() { | ||||||
|  | 	if err := rootCmd.Execute(); err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfacer
 | func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfacer
 | ||||||
| 	sig := <-c | 	sig := <-c | ||||||
| 	log.Printf("Caught signal %s: shutting down.", sig) | 	log.Printf("Caught signal %s: shutting down.", sig) | ||||||
|  | @ -212,103 +125,12 @@ func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfac | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| //nolint:gocyclo
 | //nolint:gocyclo
 | ||||||
| func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server { | func getRunParams(st *storage.Storage) *settings.Server { | ||||||
| 	server, err := st.Settings.GetServer() | 	server, err := st.Settings.GetServer() | ||||||
| 	checkErr(err) | 	checkErr(err) | ||||||
| 
 |  | ||||||
| 	if val, set := getParamB(flags, "root"); set { |  | ||||||
| 		server.Root = val |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if val, set := getParamB(flags, "baseurl"); set { |  | ||||||
| 		server.BaseURL = val |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if val, set := getParamB(flags, "log"); set { |  | ||||||
| 		server.Log = val |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	isSocketSet := false |  | ||||||
| 	isAddrSet := false |  | ||||||
| 
 |  | ||||||
| 	if val, set := getParamB(flags, "address"); set { |  | ||||||
| 		server.Address = val |  | ||||||
| 		isAddrSet = isAddrSet || set |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if val, set := getParamB(flags, "port"); set { |  | ||||||
| 		server.Port = val |  | ||||||
| 		isAddrSet = isAddrSet || set |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if val, set := getParamB(flags, "key"); set { |  | ||||||
| 		server.TLSKey = val |  | ||||||
| 		isAddrSet = isAddrSet || set |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if val, set := getParamB(flags, "cert"); set { |  | ||||||
| 		server.TLSCert = val |  | ||||||
| 		isAddrSet = isAddrSet || set |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if val, set := getParamB(flags, "socket"); set { |  | ||||||
| 		server.Socket = val |  | ||||||
| 		isSocketSet = isSocketSet || set |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if isAddrSet && isSocketSet { |  | ||||||
| 		checkErr(errors.New("--socket flag cannot be used with --address, --port, --key nor --cert")) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Do not use saved Socket if address was manually set.
 |  | ||||||
| 	if isAddrSet && server.Socket != "" { |  | ||||||
| 		server.Socket = "" |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	_, disableThumbnails := getParamB(flags, "disable-thumbnails") |  | ||||||
| 	server.EnableThumbnails = !disableThumbnails |  | ||||||
| 
 |  | ||||||
| 	_, disablePreviewResize := getParamB(flags, "disable-preview-resize") |  | ||||||
| 	server.ResizePreview = !disablePreviewResize |  | ||||||
| 
 |  | ||||||
| 	_, disableTypeDetectionByHeader := getParamB(flags, "disable-type-detection-by-header") |  | ||||||
| 	server.TypeDetectionByHeader = !disableTypeDetectionByHeader |  | ||||||
| 
 |  | ||||||
| 	_, disableExec := getParamB(flags, "disable-exec") |  | ||||||
| 	server.EnableExec = !disableExec |  | ||||||
| 
 |  | ||||||
| 	return server | 	return server | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // getParamB returns a parameter as a string and a boolean to tell if it is different from the default
 |  | ||||||
| //
 |  | ||||||
| // NOTE: we could simply bind the flags to viper and use IsSet.
 |  | ||||||
| // Although there is a bug on Viper that always returns true on IsSet
 |  | ||||||
| // if a flag is binded. Our alternative way is to manually check
 |  | ||||||
| // the flag and then the value from env/config/gotten by viper.
 |  | ||||||
| // https://github.com/spf13/viper/pull/331
 |  | ||||||
| func getParamB(flags *pflag.FlagSet, key string) (string, bool) { |  | ||||||
| 	value, _ := flags.GetString(key) |  | ||||||
| 
 |  | ||||||
| 	// If set on Flags, use it.
 |  | ||||||
| 	if flags.Changed(key) { |  | ||||||
| 		return value, true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// If set through viper (env, config), return it.
 |  | ||||||
| 	if v.IsSet(key) { |  | ||||||
| 		return v.GetString(key), true |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Otherwise use default value on flags.
 |  | ||||||
| 	return value, false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func getParam(flags *pflag.FlagSet, key string) string { |  | ||||||
| 	val, _ := getParamB(flags, key) |  | ||||||
| 	return val |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func setupLog(logMethod string) { | func setupLog(logMethod string) { | ||||||
| 	switch logMethod { | 	switch logMethod { | ||||||
| 	case "stdout": | 	case "stdout": | ||||||
|  | @ -327,101 +149,31 @@ func setupLog(logMethod string) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func quickSetup(flags *pflag.FlagSet, d pythonData) { | func quickSetup(d pythonData) { | ||||||
| 	set := &settings.Settings{ | 	settings.GlobalConfiguration.Key = generateKey() | ||||||
| 		Key:              generateKey(), |  | ||||||
| 		Signup:           false, |  | ||||||
| 		CreateUserDir:    false, |  | ||||||
| 		UserHomeBasePath: settings.DefaultUsersHomeBasePath, |  | ||||||
| 		Defaults: settings.UserDefaults{ |  | ||||||
| 			Scope:       ".", |  | ||||||
| 			Locale:      "en", |  | ||||||
| 			SingleClick: false, |  | ||||||
| 			Perm: users.Permissions{ |  | ||||||
| 				Admin:    false, |  | ||||||
| 				Execute:  true, |  | ||||||
| 				Create:   true, |  | ||||||
| 				Rename:   true, |  | ||||||
| 				Modify:   true, |  | ||||||
| 				Delete:   true, |  | ||||||
| 				Share:    true, |  | ||||||
| 				Download: true, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		AuthMethod: "", |  | ||||||
| 		Branding:   settings.Branding{}, |  | ||||||
| 		Commands:   nil, |  | ||||||
| 		Shell:      nil, |  | ||||||
| 		Rules:      nil, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var err error | 	var err error | ||||||
| 	if _, noauth := getParamB(flags, "noauth"); noauth { | 	if settings.GlobalConfiguration.Auth.Method == "noauth" { | ||||||
| 		set.AuthMethod = auth.MethodNoAuth |  | ||||||
| 		err = d.store.Auth.Save(&auth.NoAuth{}) | 		err = d.store.Auth.Save(&auth.NoAuth{}) | ||||||
| 	} else { | 	} else { | ||||||
| 		set.AuthMethod = auth.MethodJSONAuth | 		settings.GlobalConfiguration.Auth.Method = "password" | ||||||
| 		err = d.store.Auth.Save(&auth.JSONAuth{}) | 		err = d.store.Auth.Save(&auth.JSONAuth{}) | ||||||
| 	} | 	} | ||||||
| 
 | 	err = d.store.Settings.Save(&settings.GlobalConfiguration) | ||||||
| 	err = d.store.Settings.Save(set) |  | ||||||
| 	checkErr(err) | 	checkErr(err) | ||||||
| 
 | 	err = d.store.Settings.SaveServer(&settings.GlobalConfiguration.Server) | ||||||
| 	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) |  | ||||||
| 	checkErr(err) | 	checkErr(err) | ||||||
| 
 | 	username := settings.GlobalConfiguration.AdminUsername | ||||||
| 	username := getParam(flags, "username") | 	password := settings.GlobalConfiguration.AdminPassword | ||||||
| 	password := getParam(flags, "password") |  | ||||||
| 
 |  | ||||||
| 	if password == "" { |  | ||||||
| 		password, err = users.HashPwd("admin") |  | ||||||
| 		checkErr(err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if username == "" || password == "" { | 	if username == "" || password == "" { | ||||||
| 		log.Fatal("username and password cannot be empty during quick setup") | 		log.Fatal("username and password cannot be empty during quick setup") | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	user := &users.User{ | 	user := &users.User{ | ||||||
| 		Username:     username, | 		Username:     username, | ||||||
| 		Password:     password, | 		Password:     password, | ||||||
| 		LockPassword: false, | 		LockPassword: false, | ||||||
| 	} | 	} | ||||||
| 
 | 	settings.GlobalConfiguration.UserDefaults.Apply(user) | ||||||
| 	set.Defaults.Apply(user) |  | ||||||
| 	user.Perm.Admin = true | 	user.Perm.Admin = true | ||||||
| 
 |  | ||||||
| 	err = d.store.Users.Save(user) | 	err = d.store.Users.Save(user) | ||||||
| 	checkErr(err) | 	checkErr(err) | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func initConfig() { |  | ||||||
| 	if cfgFile == "" { |  | ||||||
| 		v.AddConfigPath(".") |  | ||||||
| 		v.AddConfigPath("/etc/filebrowser/") |  | ||||||
| 		v.SetConfigName(".filebrowser") |  | ||||||
| 	} else { |  | ||||||
| 		v.SetConfigFile(cfgFile) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	v.SetEnvPrefix("FB") |  | ||||||
| 	v.AutomaticEnv() |  | ||||||
| 	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) |  | ||||||
| 	if err := v.ReadInConfig(); err != nil { |  | ||||||
| 		if _, ok := err.(v.ConfigParseError); ok { |  | ||||||
| 			panic(err) |  | ||||||
| 		} |  | ||||||
| 		cfgFile = "No config file used" |  | ||||||
| 	} else { |  | ||||||
| 		cfgFile = "Using config file: " + v.ConfigFileUsed() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| package cmd |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"github.com/spf13/cobra" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/storage/bolt/importer" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func init() { |  | ||||||
| 	rootCmd.AddCommand(upgradeCmd) |  | ||||||
| 
 |  | ||||||
| 	upgradeCmd.Flags().String("old.database", "", "") |  | ||||||
| 	upgradeCmd.Flags().String("old.config", "", "") |  | ||||||
| 	_ = upgradeCmd.MarkFlagRequired("old.database") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var upgradeCmd = &cobra.Command{ |  | ||||||
| 	Use:   "upgrade", |  | ||||||
| 	Short: "Upgrades an old configuration", |  | ||||||
| 	Long: `Upgrades an old configuration. This command DOES NOT |  | ||||||
| import share links because they are incompatible with |  | ||||||
| this version.`, |  | ||||||
| 	Args: cobra.NoArgs, |  | ||||||
| 	Run: func(cmd *cobra.Command, args []string) { |  | ||||||
| 		flags := cmd.Flags() |  | ||||||
| 		oldDB := mustGetString(flags, "old.database") |  | ||||||
| 		oldConf := mustGetString(flags, "old.config") |  | ||||||
| 		err := importer.Import(oldDB, oldConf, getParam(flags, "database")) |  | ||||||
| 		checkErr(err) |  | ||||||
| 	}, |  | ||||||
| } |  | ||||||
|  | @ -79,8 +79,8 @@ func addUserFlags(flags *pflag.FlagSet) { | ||||||
| 	flags.Bool("singleClick", false, "use single clicks only") | 	flags.Bool("singleClick", false, "use single clicks only") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getViewMode(flags *pflag.FlagSet) users.ViewMode { | func getViewMode(flags *pflag.FlagSet) string { | ||||||
| 	viewMode := users.ViewMode(mustGetString(flags, "viewMode")) | 	viewMode := settings.GlobalConfiguration.UserDefaults.ViewMode | ||||||
| 	if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode { | 	if viewMode != users.ListViewMode && viewMode != users.MosaicViewMode { | ||||||
| 		checkErr(errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"")) | 		checkErr(errors.New("view mode must be \"" + string(users.ListViewMode) + "\" or \"" + string(users.MosaicViewMode) + "\"")) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -19,7 +19,6 @@ var usersAddCmd = &cobra.Command{ | ||||||
| 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { | 	Run: python(func(cmd *cobra.Command, args []string, d pythonData) { | ||||||
| 		s, err := d.store.Settings.Get() | 		s, err := d.store.Settings.Get() | ||||||
| 		checkErr(err) | 		checkErr(err) | ||||||
| 		getUserDefaults(cmd.Flags(), &s.Defaults, false) |  | ||||||
| 
 | 
 | ||||||
| 		password, err := users.HashPwd(args[1]) | 		password, err := users.HashPwd(args[1]) | ||||||
| 		checkErr(err) | 		checkErr(err) | ||||||
|  | @ -30,7 +29,7 @@ var usersAddCmd = &cobra.Command{ | ||||||
| 			LockPassword: mustGetBool(cmd.Flags(), "lockPassword"), | 			LockPassword: mustGetBool(cmd.Flags(), "lockPassword"), | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		s.Defaults.Apply(user) | 		s.UserDefaults.Apply(user) | ||||||
| 
 | 
 | ||||||
| 		servSettings, err := d.store.Settings.GetServer() | 		servSettings, err := d.store.Settings.GetServer() | ||||||
| 		checkErr(err) | 		checkErr(err) | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ package cmd | ||||||
| import ( | import ( | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gtsteffaniak/filebrowser/settings" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/users" | 	"github.com/gtsteffaniak/filebrowser/users" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -37,27 +36,15 @@ options you want to change.`, | ||||||
| 		} else { | 		} else { | ||||||
| 			user, err = d.store.Users.Get("", username) | 			user, err = d.store.Users.Get("", username) | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		checkErr(err) | 		checkErr(err) | ||||||
| 
 | 		user.Scope = user.Scope | ||||||
| 		defaults := settings.UserDefaults{ | 		user.Locale = user.Locale | ||||||
| 			Scope:       user.Scope, | 		user.ViewMode = user.ViewMode | ||||||
| 			Locale:      user.Locale, | 		user.SingleClick = user.SingleClick | ||||||
| 			ViewMode:    user.ViewMode, | 		user.Perm = user.Perm | ||||||
| 			SingleClick: user.SingleClick, | 		user.Commands = user.Commands | ||||||
| 			Perm:        user.Perm, | 		user.Sorting = user.Sorting | ||||||
| 			Sorting:     user.Sorting, | 		user.LockPassword = user.LockPassword | ||||||
| 			Commands:    user.Commands, |  | ||||||
| 		} |  | ||||||
| 		getUserDefaults(flags, &defaults, false) |  | ||||||
| 		user.Scope = defaults.Scope |  | ||||||
| 		user.Locale = defaults.Locale |  | ||||||
| 		user.ViewMode = defaults.ViewMode |  | ||||||
| 		user.SingleClick = defaults.SingleClick |  | ||||||
| 		user.Perm = defaults.Perm |  | ||||||
| 		user.Commands = defaults.Commands |  | ||||||
| 		user.Sorting = defaults.Sorting |  | ||||||
| 		user.LockPassword = mustGetBool(flags, "lockPassword") |  | ||||||
| 
 | 
 | ||||||
| 		if newUsername != "" { | 		if newUsername != "" { | ||||||
| 			user.Username = newUsername | 			user.Username = newUsername | ||||||
|  |  | ||||||
|  | @ -10,9 +10,9 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/asdine/storm/v3" | 	"github.com/asdine/storm/v3" | ||||||
|  | 	"github.com/goccy/go-yaml" | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
| 	"github.com/spf13/pflag" | 	"github.com/spf13/pflag" | ||||||
| 	yaml "gopkg.in/yaml.v2" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/gtsteffaniak/filebrowser/settings" | 	"github.com/gtsteffaniak/filebrowser/settings" | ||||||
| 	"github.com/gtsteffaniak/filebrowser/storage" | 	"github.com/gtsteffaniak/filebrowser/storage" | ||||||
|  | @ -85,8 +85,7 @@ func dbExists(path string) (bool, error) { | ||||||
| func python(fn pythonFunc, cfg pythonConfig) cobraFunc { | func python(fn pythonFunc, cfg pythonConfig) cobraFunc { | ||||||
| 	return func(cmd *cobra.Command, args []string) { | 	return func(cmd *cobra.Command, args []string) { | ||||||
| 		data := pythonData{hadDB: true} | 		data := pythonData{hadDB: true} | ||||||
| 
 | 		path := settings.GlobalConfiguration.Server.Database | ||||||
| 		path := getParam(cmd.Flags(), "database") |  | ||||||
| 		exists, err := dbExists(path) | 		exists, err := dbExists(path) | ||||||
| 
 | 
 | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -100,6 +99,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc { | ||||||
| 		data.hadDB = exists | 		data.hadDB = exists | ||||||
| 		db, err := storm.Open(path) | 		db, err := storm.Open(path) | ||||||
| 		checkErr(err) | 		checkErr(err) | ||||||
|  | 
 | ||||||
| 		defer db.Close() | 		defer db.Close() | ||||||
| 		data.store, err = bolt.NewStorage(db) | 		data.store, err = bolt.NewStorage(db) | ||||||
| 		checkErr(err) | 		checkErr(err) | ||||||
|  | @ -118,8 +118,8 @@ func marshal(filename string, data interface{}) error { | ||||||
| 		encoder.SetIndent("", "    ") | 		encoder.SetIndent("", "    ") | ||||||
| 		return encoder.Encode(data) | 		return encoder.Encode(data) | ||||||
| 	case ".yml", ".yaml": //nolint:goconst
 | 	case ".yml", ".yaml": //nolint:goconst
 | ||||||
| 		encoder := yaml.NewEncoder(fd) | 		_, err := yaml.Marshal(fd) | ||||||
| 		return encoder.Encode(data) | 		return err | ||||||
| 	default: | 	default: | ||||||
| 		return errors.New("invalid format: " + ext) | 		return errors.New("invalid format: " + ext) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | server: | ||||||
|  |   port: 8080 | ||||||
|  |   baseURL: "/" | ||||||
|  | auth: | ||||||
|  |   method: noauth | ||||||
|  |   signup: true | ||||||
|  | userDefaults: | ||||||
|  |   scope: "." | ||||||
|  |   hideDotfiles: true | ||||||
|  |   singleClick: false | ||||||
|  |   permissions: | ||||||
|  |     admin: false | ||||||
|  |     create: true | ||||||
|  |     rename: true | ||||||
|  |     modify: true | ||||||
|  |     delete: true | ||||||
|  |     share: true | ||||||
|  |     download: true | ||||||
|  | frontend: | ||||||
|  |   theme: dark | ||||||
|  | @ -1,32 +1,29 @@ | ||||||
| module github.com/gtsteffaniak/filebrowser | module github.com/gtsteffaniak/filebrowser | ||||||
| 
 | 
 | ||||||
| go 1.20 | go 1.21.0 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	github.com/asdine/storm/v3 v3.2.1 | 	github.com/asdine/storm/v3 v3.2.1 | ||||||
| 	github.com/disintegration/imaging v1.6.2 | 	github.com/disintegration/imaging v1.6.2 | ||||||
| 	github.com/dsoprea/go-exif/v3 v3.0.1 | 	github.com/dsoprea/go-exif/v3 v3.0.1 | ||||||
| 	github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 | 	github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 | ||||||
|  | 	github.com/goccy/go-yaml v1.11.0 | ||||||
| 	github.com/golang-jwt/jwt/v4 v4.5.0 | 	github.com/golang-jwt/jwt/v4 v4.5.0 | ||||||
|  | 	github.com/google/go-cmp v0.5.9 | ||||||
| 	github.com/gorilla/mux v1.8.0 | 	github.com/gorilla/mux v1.8.0 | ||||||
| 	github.com/gorilla/websocket v1.5.0 |  | ||||||
| 	github.com/maruel/natural v1.1.0 | 	github.com/maruel/natural v1.1.0 | ||||||
| 	github.com/marusama/semaphore/v2 v2.5.0 | 	github.com/marusama/semaphore/v2 v2.5.0 | ||||||
| 	github.com/mholt/archiver/v3 v3.5.1 | 	github.com/mholt/archiver/v3 v3.5.1 | ||||||
| 	github.com/pelletier/go-toml/v2 v2.0.9 | 	github.com/shirou/gopsutil/v3 v3.23.8 | ||||||
| 	github.com/shirou/gopsutil/v3 v3.23.7 |  | ||||||
| 	github.com/spf13/afero v1.9.5 | 	github.com/spf13/afero v1.9.5 | ||||||
| 	github.com/spf13/cobra v1.7.0 | 	github.com/spf13/cobra v1.7.0 | ||||||
| 	github.com/spf13/pflag v1.0.5 | 	github.com/spf13/pflag v1.0.5 | ||||||
| 	github.com/spf13/viper v1.16.0 |  | ||||||
| 	github.com/stretchr/testify v1.8.4 | 	github.com/stretchr/testify v1.8.4 | ||||||
| 	github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce | 	github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce | ||||||
| 	go.etcd.io/bbolt v1.3.7 |  | ||||||
| 	golang.org/x/crypto v0.12.0 | 	golang.org/x/crypto v0.12.0 | ||||||
| 	golang.org/x/image v0.11.0 | 	golang.org/x/image v0.12.0 | ||||||
| 	golang.org/x/text v0.12.0 | 	golang.org/x/text v0.13.0 | ||||||
| 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 | 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 | ||||||
| 	gopkg.in/yaml.v2 v2.4.0 |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
|  | @ -35,29 +32,27 @@ require ( | ||||||
| 	github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect | 	github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect | ||||||
| 	github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect | 	github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect | ||||||
| 	github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect | 	github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect | ||||||
| 	github.com/fsnotify/fsnotify v1.6.0 // indirect | 	github.com/fatih/color v1.10.0 // indirect | ||||||
| 	github.com/go-errors/errors v1.4.2 // indirect | 	github.com/go-errors/errors v1.4.2 // indirect | ||||||
| 	github.com/go-ole/go-ole v1.2.6 // indirect | 	github.com/go-ole/go-ole v1.2.6 // indirect | ||||||
| 	github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect | 	github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect | ||||||
| 	github.com/golang/snappy v0.0.2 // indirect | 	github.com/golang/snappy v0.0.2 // indirect | ||||||
| 	github.com/hashicorp/hcl v1.0.0 // indirect |  | ||||||
| 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||||||
| 	github.com/klauspost/compress v1.11.4 // indirect | 	github.com/klauspost/compress v1.11.4 // indirect | ||||||
| 	github.com/klauspost/pgzip v1.2.5 // indirect | 	github.com/klauspost/pgzip v1.2.5 // indirect | ||||||
| 	github.com/magiconair/properties v1.8.7 // indirect | 	github.com/mattn/go-colorable v0.1.8 // indirect | ||||||
| 	github.com/mitchellh/mapstructure v1.5.0 // indirect | 	github.com/mattn/go-isatty v0.0.12 // indirect | ||||||
| 	github.com/nwaples/rardecode v1.1.0 // indirect | 	github.com/nwaples/rardecode v1.1.0 // indirect | ||||||
| 	github.com/pierrec/lz4/v4 v4.1.2 // indirect | 	github.com/pierrec/lz4/v4 v4.1.2 // indirect | ||||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||||
| 	github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect | 	github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect | ||||||
| 	github.com/spf13/cast v1.5.1 // indirect |  | ||||||
| 	github.com/spf13/jwalterweatherman v1.1.0 // indirect |  | ||||||
| 	github.com/subosito/gotenv v1.4.2 // indirect |  | ||||||
| 	github.com/ulikunitz/xz v0.5.9 // indirect | 	github.com/ulikunitz/xz v0.5.9 // indirect | ||||||
| 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect | 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect | ||||||
| 	github.com/yusufpapurcu/wmi v1.2.3 // indirect | 	github.com/yusufpapurcu/wmi v1.2.3 // indirect | ||||||
|  | 	go.etcd.io/bbolt v1.3.4 // indirect | ||||||
| 	golang.org/x/net v0.10.0 // indirect | 	golang.org/x/net v0.10.0 // indirect | ||||||
| 	golang.org/x/sys v0.11.0 // indirect | 	golang.org/x/sys v0.11.0 // indirect | ||||||
| 	gopkg.in/ini.v1 v1.67.0 // indirect | 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect | ||||||
|  | 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -86,11 +86,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m | ||||||
| github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= | ||||||
| github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= | ||||||
| github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||||||
|  | github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= | ||||||
|  | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= | ||||||
| github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= | ||||||
| github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= | ||||||
| github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= |  | ||||||
| github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= |  | ||||||
| github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= |  | ||||||
| github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= | ||||||
| github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= | github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= | ||||||
| github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= | github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= | ||||||
|  | @ -101,6 +100,14 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 | ||||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||||
| github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= | ||||||
| github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= | ||||||
|  | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= | ||||||
|  | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= | ||||||
|  | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= | ||||||
|  | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= | ||||||
|  | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= | ||||||
|  | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= | ||||||
|  | github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= | ||||||
|  | github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= | ||||||
| github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= | ||||||
| github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= | github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= | ||||||
| github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= | github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= | ||||||
|  | @ -131,8 +138,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W | ||||||
| github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | ||||||
| github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= | ||||||
| github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||||
|  | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= | ||||||
| github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||||
| github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= |  | ||||||
| github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||||
| github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= | github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= | ||||||
| github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||||
|  | @ -171,12 +178,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m | ||||||
| github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= | ||||||
| github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||||||
| github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||||||
| github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= |  | ||||||
| github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= |  | ||||||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||||
| github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||||
| github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= |  | ||||||
| github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= |  | ||||||
| github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||||
| github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||||
| github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | ||||||
|  | @ -193,26 +196,26 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo | ||||||
| github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= | github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= | ||||||
| github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= | ||||||
| github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= | ||||||
|  | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | ||||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||||
| github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= |  | ||||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||||
|  | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | ||||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= | ||||||
|  | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= | ||||||
| github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= | ||||||
| github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= |  | ||||||
| github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= |  | ||||||
| github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ= | github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ= | ||||||
| github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ= | github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ= | ||||||
| github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM= | github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM= | ||||||
| github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ= | github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ= | ||||||
|  | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= | ||||||
|  | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | ||||||
|  | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= | ||||||
|  | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | ||||||
| github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= | github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= | ||||||
| github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= | github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= | ||||||
| github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= |  | ||||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= |  | ||||||
| github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= | github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= | ||||||
| github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= | github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= | ||||||
| github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= |  | ||||||
| github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= |  | ||||||
| github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM= | github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM= | ||||||
| github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | ||||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||||
|  | @ -223,24 +226,17 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF | ||||||
| github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= | ||||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||||
| github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||||||
| github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= |  | ||||||
| github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||||
| github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= | github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE= | ||||||
| github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4= | github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= | ||||||
| github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= | ||||||
| github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= | github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= | ||||||
| github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= | github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= | ||||||
| github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= | github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= | ||||||
| github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= |  | ||||||
| github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= |  | ||||||
| github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= | ||||||
| github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= | ||||||
| github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= |  | ||||||
| github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= |  | ||||||
| github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||||
| github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= |  | ||||||
| github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= |  | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||||
|  | @ -252,10 +248,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ | ||||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||||
| github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||||||
| github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||||
| github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= | ||||||
| github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= | ||||||
| github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= |  | ||||||
| github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= |  | ||||||
| github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= | github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= | ||||||
| github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= | github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= | ||||||
| github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= | ||||||
|  | @ -272,9 +266,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec | ||||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||||
| github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= | ||||||
| github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= | ||||||
|  | go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= | ||||||
| go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= | go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= | ||||||
| go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= |  | ||||||
| go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= |  | ||||||
| go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||||
| go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | ||||||
| go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||||
|  | @ -304,8 +297,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk | ||||||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||||
| golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||||
| golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= | golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ= | ||||||
| golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= | golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk= | ||||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||||
| golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | ||||||
| golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||||
|  | @ -403,6 +396,7 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w | ||||||
| golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | @ -430,11 +424,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc | ||||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= | ||||||
| golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
|  | @ -449,8 +441,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||||
| golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||||
| golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= | ||||||
| golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
|  | @ -506,6 +498,7 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= | ||||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= | ||||||
| google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= | ||||||
|  | @ -595,15 +588,13 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 | ||||||
| google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||||
| google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||||
| google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= | ||||||
|  | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= | ||||||
| google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= | ||||||
| google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= |  | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= | ||||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | ||||||
| gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= |  | ||||||
| gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= |  | ||||||
| gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= | ||||||
| gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= | ||||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ const ( | ||||||
| type userInfo struct { | type userInfo struct { | ||||||
| 	ID           uint              `json:"id"` | 	ID           uint              `json:"id"` | ||||||
| 	Locale       string            `json:"locale"` | 	Locale       string            `json:"locale"` | ||||||
| 	ViewMode     users.ViewMode    `json:"viewMode"` | 	ViewMode     string            `json:"viewMode"` | ||||||
| 	SingleClick  bool              `json:"singleClick"` | 	SingleClick  bool              `json:"singleClick"` | ||||||
| 	Perm         users.Permissions `json:"perm"` | 	Perm         users.Permissions `json:"perm"` | ||||||
| 	Commands     []string          `json:"commands"` | 	Commands     []string          `json:"commands"` | ||||||
|  | @ -102,12 +102,12 @@ func withAdmin(fn handleFunc) handleFunc { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var loginHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { | var loginHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { | ||||||
| 	auther, err := d.store.Auth.Get(d.settings.AuthMethod) | 	auther, err := d.store.Auth.Get(d.settings.Auth.Method) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return http.StatusInternalServerError, err | 		return http.StatusInternalServerError, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	user, err := auther.Auth(r, d.store.Users, d.settings, d.server) | 	user, err := auther.Auth(r, d.store.Users) | ||||||
| 	if err == os.ErrPermission { | 	if err == os.ErrPermission { | ||||||
| 		return http.StatusForbidden, nil | 		return http.StatusForbidden, nil | ||||||
| 	} else if err != nil { | 	} else if err != nil { | ||||||
|  | @ -145,7 +145,7 @@ var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, | ||||||
| 		Username: info.Username, | 		Username: info.Username, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	d.settings.Defaults.Apply(user) | 	d.settings.UserDefaults.Apply(user) | ||||||
| 
 | 
 | ||||||
| 	pwd, err := users.HashPwd(info.Password) | 	pwd, err := users.HashPwd(info.Password) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -1,111 +0,0 @@ | ||||||
| package http |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bufio" |  | ||||||
| 	"io" |  | ||||||
| 	"log" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os/exec" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gorilla/websocket" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/runner" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	WSWriteDeadline = 10 * time.Second |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var upgrader = websocket.Upgrader{ |  | ||||||
| 	ReadBufferSize:  1024, |  | ||||||
| 	WriteBufferSize: 1024, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	cmdNotAllowed = []byte("Command not allowed.") |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| //nolint:unparam
 |  | ||||||
| func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) { |  | ||||||
| 	txt := http.StatusText(status) |  | ||||||
| 	if err != nil || status >= 400 { |  | ||||||
| 		log.Printf("%s: %v %s %v", r.URL.Path, status, r.RemoteAddr, err) |  | ||||||
| 	} |  | ||||||
| 	if err := ws.WriteControl(websocket.CloseInternalServerErr, []byte(txt), time.Now().Add(WSWriteDeadline)); err != nil { |  | ||||||
| 		log.Print(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { |  | ||||||
| 	conn, err := upgrader.Upgrade(w, r, nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return http.StatusInternalServerError, err |  | ||||||
| 	} |  | ||||||
| 	defer conn.Close() |  | ||||||
| 
 |  | ||||||
| 	var raw string |  | ||||||
| 
 |  | ||||||
| 	for { |  | ||||||
| 		_, msg, err := conn.ReadMessage() //nolint:govet
 |  | ||||||
| 		if err != nil { |  | ||||||
| 			wsErr(conn, r, http.StatusInternalServerError, err) |  | ||||||
| 			return 0, nil |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		raw = strings.TrimSpace(string(msg)) |  | ||||||
| 		if raw != "" { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	command, err := runner.ParseCommand(d.settings, raw) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:govet
 |  | ||||||
| 			wsErr(conn, r, http.StatusInternalServerError, err) |  | ||||||
| 		} |  | ||||||
| 		return 0, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !d.server.EnableExec || !d.user.CanExecute(command[0]) { |  | ||||||
| 		if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
 |  | ||||||
| 			wsErr(conn, r, http.StatusInternalServerError, err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return 0, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
 |  | ||||||
| 	cmd.Dir = d.user.FullPath(r.URL.Path) |  | ||||||
| 
 |  | ||||||
| 	stdout, err := cmd.StdoutPipe() |  | ||||||
| 	if err != nil { |  | ||||||
| 		wsErr(conn, r, http.StatusInternalServerError, err) |  | ||||||
| 		return 0, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	stderr, err := cmd.StderrPipe() |  | ||||||
| 	if err != nil { |  | ||||||
| 		wsErr(conn, r, http.StatusInternalServerError, err) |  | ||||||
| 		return 0, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := cmd.Start(); err != nil { |  | ||||||
| 		wsErr(conn, r, http.StatusInternalServerError, err) |  | ||||||
| 		return 0, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	s := bufio.NewScanner(io.MultiReader(stdout, stderr)) |  | ||||||
| 	for s.Scan() { |  | ||||||
| 		if err := conn.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil { |  | ||||||
| 			log.Print(err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := cmd.Wait(); err != nil { |  | ||||||
| 		wsErr(conn, r, http.StatusInternalServerError, err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return 0, nil |  | ||||||
| }) |  | ||||||
|  | @ -32,58 +32,44 @@ func NewHandler( | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| 	index, static := getStaticHandlers(store, server, assetsFs) | 	index, static := getStaticHandlers(store, server, assetsFs) | ||||||
| 
 |  | ||||||
| 	// NOTE: This fixes the issue where it would redirect if people did not put a
 | 	// NOTE: This fixes the issue where it would redirect if people did not put a
 | ||||||
| 	// trailing slash in the end. I hate this decision since this allows some awful
 | 	// trailing slash in the end. I hate this decision since this allows some awful
 | ||||||
| 	// URLs https://www.gorillatoolkit.org/pkg/mux#Router.SkipClean
 | 	// URLs https://www.gorillatoolkit.org/pkg/mux#Router.SkipClean
 | ||||||
| 	r = r.SkipClean(true) | 	r = r.SkipClean(true) | ||||||
| 
 |  | ||||||
| 	monkey := func(fn handleFunc, prefix string) http.Handler { | 	monkey := func(fn handleFunc, prefix string) http.Handler { | ||||||
| 		return handle(fn, prefix, store, server) | 		return handle(fn, prefix, store, server) | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	r.HandleFunc("/health", healthHandler) | 	r.HandleFunc("/health", healthHandler) | ||||||
| 	r.PathPrefix("/static").Handler(static) | 	r.PathPrefix("/static").Handler(static) | ||||||
| 	r.NotFoundHandler = index | 	r.NotFoundHandler = index | ||||||
| 
 |  | ||||||
| 	api := r.PathPrefix("/api").Subrouter() | 	api := r.PathPrefix("/api").Subrouter() | ||||||
| 
 |  | ||||||
| 	api.Handle("/login", monkey(loginHandler, "")) | 	api.Handle("/login", monkey(loginHandler, "")) | ||||||
| 	api.Handle("/signup", monkey(signupHandler, "")) | 	api.Handle("/signup", monkey(signupHandler, "")) | ||||||
| 	api.Handle("/renew", monkey(renewHandler, "")) | 	api.Handle("/renew", monkey(renewHandler, "")) | ||||||
| 
 |  | ||||||
| 	users := api.PathPrefix("/users").Subrouter() | 	users := api.PathPrefix("/users").Subrouter() | ||||||
| 	users.Handle("", monkey(usersGetHandler, "")).Methods("GET") | 	users.Handle("", monkey(usersGetHandler, "")).Methods("GET") | ||||||
| 	users.Handle("", monkey(userPostHandler, "")).Methods("POST") | 	users.Handle("", monkey(userPostHandler, "")).Methods("POST") | ||||||
| 	users.Handle("/{id:[0-9]+}", monkey(userPutHandler, "")).Methods("PUT") | 	users.Handle("/{id:[0-9]+}", monkey(userPutHandler, "")).Methods("PUT") | ||||||
| 	users.Handle("/{id:[0-9]+}", monkey(userGetHandler, "")).Methods("GET") | 	users.Handle("/{id:[0-9]+}", monkey(userGetHandler, "")).Methods("GET") | ||||||
| 	users.Handle("/{id:[0-9]+}", monkey(userDeleteHandler, "")).Methods("DELETE") | 	users.Handle("/{id:[0-9]+}", monkey(userDeleteHandler, "")).Methods("DELETE") | ||||||
| 
 |  | ||||||
| 	api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET") | 	api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET") | ||||||
| 	api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE") | 	api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE") | ||||||
| 	api.PathPrefix("/resources").Handler(monkey(resourcePostHandler(fileCache), "/api/resources")).Methods("POST") | 	api.PathPrefix("/resources").Handler(monkey(resourcePostHandler(fileCache), "/api/resources")).Methods("POST") | ||||||
| 	api.PathPrefix("/resources").Handler(monkey(resourcePutHandler, "/api/resources")).Methods("PUT") | 	api.PathPrefix("/resources").Handler(monkey(resourcePutHandler, "/api/resources")).Methods("PUT") | ||||||
| 	api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler(fileCache), "/api/resources")).Methods("PATCH") | 	api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler(fileCache), "/api/resources")).Methods("PATCH") | ||||||
| 
 |  | ||||||
| 	api.PathPrefix("/usage").Handler(monkey(diskUsage, "/api/usage")).Methods("GET") | 	api.PathPrefix("/usage").Handler(monkey(diskUsage, "/api/usage")).Methods("GET") | ||||||
| 
 |  | ||||||
| 	api.Path("/shares").Handler(monkey(shareListHandler, "/api/shares")).Methods("GET") | 	api.Path("/shares").Handler(monkey(shareListHandler, "/api/shares")).Methods("GET") | ||||||
| 	api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET") | 	api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET") | ||||||
| 	api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST") | 	api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST") | ||||||
| 	api.PathPrefix("/share").Handler(monkey(shareDeleteHandler, "/api/share")).Methods("DELETE") | 	api.PathPrefix("/share").Handler(monkey(shareDeleteHandler, "/api/share")).Methods("DELETE") | ||||||
| 
 |  | ||||||
| 	api.Handle("/settings", monkey(settingsGetHandler, "")).Methods("GET") | 	api.Handle("/settings", monkey(settingsGetHandler, "")).Methods("GET") | ||||||
| 	api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT") | 	api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT") | ||||||
| 
 |  | ||||||
| 	api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET") | 	api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET") | ||||||
| 	api.PathPrefix("/preview/{size}/{path:.*}"). | 	api.PathPrefix("/preview/{size}/{path:.*}"). | ||||||
| 		Handler(monkey(previewHandler(imgSvc, fileCache, server.EnableThumbnails, server.ResizePreview), "/api/preview")).Methods("GET") | 		Handler(monkey(previewHandler(imgSvc, fileCache, server.EnableThumbnails, server.ResizePreview), "/api/preview")).Methods("GET") | ||||||
| 	api.PathPrefix("/command").Handler(monkey(commandsHandler, "/api/command")).Methods("GET") |  | ||||||
| 	api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET") | 	api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET") | ||||||
| 
 |  | ||||||
| 	public := api.PathPrefix("/public").Subrouter() | 	public := api.PathPrefix("/public").Subrouter() | ||||||
| 	public.PathPrefix("/dl").Handler(monkey(publicDlHandler, "/api/public/dl/")).Methods("GET") | 	public.PathPrefix("/dl").Handler(monkey(publicDlHandler, "/api/public/dl/")).Methods("GET") | ||||||
| 	public.PathPrefix("/share").Handler(monkey(publicShareHandler, "/api/public/share/")).Methods("GET") | 	public.PathPrefix("/share").Handler(monkey(publicShareHandler, "/api/public/share/")).Methods("GET") | ||||||
| 
 |  | ||||||
| 	return stripPrefix(server.BaseURL, r), nil | 	return stripPrefix(server.BaseURL, r), nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ type settingsData struct { | ||||||
| 	UserHomeBasePath string                `json:"userHomeBasePath"` | 	UserHomeBasePath string                `json:"userHomeBasePath"` | ||||||
| 	Defaults         settings.UserDefaults `json:"defaults"` | 	Defaults         settings.UserDefaults `json:"defaults"` | ||||||
| 	Rules            []rules.Rule          `json:"rules"` | 	Rules            []rules.Rule          `json:"rules"` | ||||||
| 	Branding         settings.Branding     `json:"branding"` | 	Frontend         settings.Frontend     `json:"frontend"` | ||||||
| 	Shell            []string              `json:"shell"` | 	Shell            []string              `json:"shell"` | ||||||
| 	Commands         map[string][]string   `json:"commands"` | 	Commands         map[string][]string   `json:"commands"` | ||||||
| } | } | ||||||
|  | @ -24,9 +24,9 @@ var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, | ||||||
| 		Signup:           d.settings.Signup, | 		Signup:           d.settings.Signup, | ||||||
| 		CreateUserDir:    d.settings.CreateUserDir, | 		CreateUserDir:    d.settings.CreateUserDir, | ||||||
| 		UserHomeBasePath: d.settings.UserHomeBasePath, | 		UserHomeBasePath: d.settings.UserHomeBasePath, | ||||||
| 		Defaults:         d.settings.Defaults, | 		Defaults:         d.settings.UserDefaults, | ||||||
| 		Rules:            d.settings.Rules, | 		Rules:            d.settings.Rules, | ||||||
| 		Branding:         d.settings.Branding, | 		Frontend:         d.settings.Frontend, | ||||||
| 		Shell:            d.settings.Shell, | 		Shell:            d.settings.Shell, | ||||||
| 		Commands:         d.settings.Commands, | 		Commands:         d.settings.Commands, | ||||||
| 	} | 	} | ||||||
|  | @ -44,12 +44,11 @@ var settingsPutHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, | ||||||
| 	d.settings.Signup = req.Signup | 	d.settings.Signup = req.Signup | ||||||
| 	d.settings.CreateUserDir = req.CreateUserDir | 	d.settings.CreateUserDir = req.CreateUserDir | ||||||
| 	d.settings.UserHomeBasePath = req.UserHomeBasePath | 	d.settings.UserHomeBasePath = req.UserHomeBasePath | ||||||
| 	d.settings.Defaults = req.Defaults | 	d.settings.UserDefaults = req.Defaults | ||||||
| 	d.settings.Rules = req.Rules | 	d.settings.Rules = req.Rules | ||||||
| 	d.settings.Branding = req.Branding | 	d.settings.Frontend = req.Frontend | ||||||
| 	d.settings.Shell = req.Shell | 	d.settings.Shell = req.Shell | ||||||
| 	d.settings.Commands = req.Commands | 	d.settings.Commands = req.Commands | ||||||
| 
 |  | ||||||
| 	err = d.store.Settings.Save(d.settings) | 	err = d.store.Settings.Save(d.settings) | ||||||
| 	return errToStatus(err), err | 	return errToStatus(err), err | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -21,33 +21,33 @@ import ( | ||||||
| func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys fs.FS, file, contentType string) (int, error) { | func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys fs.FS, file, contentType string) (int, error) { | ||||||
| 	w.Header().Set("Content-Type", contentType) | 	w.Header().Set("Content-Type", contentType) | ||||||
| 
 | 
 | ||||||
| 	auther, err := d.store.Auth.Get(d.settings.AuthMethod) | 	auther, err := d.store.Auth.Get(d.settings.Auth.Method) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return http.StatusInternalServerError, err | 		return http.StatusInternalServerError, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	data := map[string]interface{}{ | 	data := map[string]interface{}{ | ||||||
| 		"Name":                  d.settings.Branding.Name, | 		"Name":                  d.settings.Frontend.Name, | ||||||
| 		"DisableExternal":       d.settings.Branding.DisableExternal, | 		"DisableExternal":       d.settings.Frontend.DisableExternal, | ||||||
| 		"DisableUsedPercentage": d.settings.Branding.DisableUsedPercentage, | 		"DisableUsedPercentage": d.settings.Frontend.DisableUsedPercentage, | ||||||
| 		"Color":                 d.settings.Branding.Color, | 		"Color":                 d.settings.Frontend.Color, | ||||||
| 		"BaseURL":               d.server.BaseURL, | 		"BaseURL":               d.server.BaseURL, | ||||||
| 		"Version":               version.Version, | 		"Version":               version.Version, | ||||||
| 		"StaticURL":             path.Join(d.server.BaseURL, "/static"), | 		"StaticURL":             path.Join(d.server.BaseURL, "/static"), | ||||||
| 		"Signup":                d.settings.Signup, | 		"Signup":                d.settings.Signup, | ||||||
| 		"NoAuth":                d.settings.AuthMethod == auth.MethodNoAuth, | 		"NoAuth":                d.settings.Auth.Method == "noauth", | ||||||
| 		"AuthMethod":            d.settings.AuthMethod, | 		"AuthMethod":            d.settings.Auth.Method, | ||||||
| 		"LoginPage":             auther.LoginPage(), | 		"LoginPage":             auther.LoginPage(), | ||||||
| 		"CSS":                   false, | 		"CSS":                   false, | ||||||
| 		"ReCaptcha":             false, | 		"ReCaptcha":             false, | ||||||
| 		"Theme":                 d.settings.Branding.Theme, | 		"Theme":                 d.settings.Frontend.Theme, | ||||||
| 		"EnableThumbs":          d.server.EnableThumbnails, | 		"EnableThumbs":          d.server.EnableThumbnails, | ||||||
| 		"ResizePreview":         d.server.ResizePreview, | 		"ResizePreview":         d.server.ResizePreview, | ||||||
| 		"EnableExec":            d.server.EnableExec, | 		"EnableExec":            d.server.EnableExec, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if d.settings.Branding.Files != "" { | 	if d.settings.Frontend.Files != "" { | ||||||
| 		fPath := filepath.Join(d.settings.Branding.Files, "custom.css") | 		fPath := filepath.Join(d.settings.Frontend.Files, "custom.css") | ||||||
| 		_, err := os.Stat(fPath) //nolint:govet
 | 		_, err := os.Stat(fPath) //nolint:govet
 | ||||||
| 
 | 
 | ||||||
| 		if err != nil && !os.IsNotExist(err) { | 		if err != nil && !os.IsNotExist(err) { | ||||||
|  | @ -59,8 +59,8 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if d.settings.AuthMethod == auth.MethodJSONAuth { | 	if d.settings.Auth.Method == "password" { | ||||||
| 		raw, err := d.store.Auth.Get(d.settings.AuthMethod) //nolint:govet
 | 		raw, err := d.store.Auth.Get(d.settings.Auth.Method) //nolint:govet
 | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return http.StatusInternalServerError, err | 			return http.StatusInternalServerError, err | ||||||
| 		} | 		} | ||||||
|  | @ -115,15 +115,15 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs | ||||||
| 		const maxAge = 86400 // 1 day
 | 		const maxAge = 86400 // 1 day
 | ||||||
| 		w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%v", maxAge)) | 		w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%v", maxAge)) | ||||||
| 
 | 
 | ||||||
| 		if d.settings.Branding.Files != "" { | 		if d.settings.Frontend.Files != "" { | ||||||
| 			if strings.HasPrefix(r.URL.Path, "img/") { | 			if strings.HasPrefix(r.URL.Path, "img/") { | ||||||
| 				fPath := filepath.Join(d.settings.Branding.Files, r.URL.Path) | 				fPath := filepath.Join(d.settings.Frontend.Files, r.URL.Path) | ||||||
| 				if _, err := os.Stat(fPath); err == nil { | 				if _, err := os.Stat(fPath); err == nil { | ||||||
| 					http.ServeFile(w, r, fPath) | 					http.ServeFile(w, r, fPath) | ||||||
| 					return 0, nil | 					return 0, nil | ||||||
| 				} | 				} | ||||||
| 			} else if r.URL.Path == "custom.css" && d.settings.Branding.Files != "" { | 			} else if r.URL.Path == "custom.css" && d.settings.Frontend.Files != "" { | ||||||
| 				http.ServeFile(w, r, filepath.Join(d.settings.Branding.Files, "custom.css")) | 				http.ServeFile(w, r, filepath.Join(d.settings.Frontend.Files, "custom.css")) | ||||||
| 				return 0, nil | 				return 0, nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -5,5 +5,5 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
| 	cmd.Execute() | 	cmd.StartFilebrowser() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| #!/bin/sh | #!/bin/sh | ||||||
| ## TEST file used by docker testing containers | ## TEST file used by docker testing containers | ||||||
| touch render.yml |  | ||||||
| checkExit() { | checkExit() { | ||||||
|     if [ "$?" -ne 0 ];then |     if [ "$?" -ne 0 ];then | ||||||
|         exit 1 |         exit 1 | ||||||
|  | @ -10,7 +9,7 @@ checkExit() { | ||||||
| if command -v go &> /dev/null | if command -v go &> /dev/null | ||||||
| then | then | ||||||
|     printf "\n == Running benchmark == \n" |     printf "\n == Running benchmark == \n" | ||||||
|     go test -bench=. -benchmem ./... |     go test -bench=. -benchtime=10x -benchmem ./... | ||||||
|     checkExit |     checkExit | ||||||
| else | else | ||||||
|     echo "ERROR: unable to perform tests" |     echo "ERROR: unable to perform tests" | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| #!/bin/sh | #!/bin/sh | ||||||
| ## TEST file used by docker testing containers | ## TEST file used by docker testing containers | ||||||
| touch render.yml |  | ||||||
| checkExit() { | checkExit() { | ||||||
|     if [ "$?" -ne 0 ];then |     if [ "$?" -ne 0 ];then | ||||||
|         exit 1 |         exit 1 | ||||||
|  |  | ||||||
|  | @ -61,7 +61,7 @@ func BenchmarkSearchAllIndexes(b *testing.B) { | ||||||
| 	indexes = make(map[string][]string) | 	indexes = make(map[string][]string) | ||||||
| 
 | 
 | ||||||
| 	// Create mock data
 | 	// Create mock data
 | ||||||
| 	createMockData(500, 3) // 1000 dirs, 3 files per dir
 | 	createMockData(50, 3) // 1000 dirs, 3 files per dir
 | ||||||
| 
 | 
 | ||||||
| 	// Generate 100 random search terms
 | 	// Generate 100 random search terms
 | ||||||
| 	searchTerms := generateRandomSearchTerms(100) | 	searchTerms := generateRandomSearchTerms(100) | ||||||
|  | @ -71,10 +71,9 @@ func BenchmarkSearchAllIndexes(b *testing.B) { | ||||||
| 	for i := 0; i < b.N; i++ { | 	for i := 0; i < b.N; i++ { | ||||||
| 		// Execute the SearchAllIndexes function
 | 		// Execute the SearchAllIndexes function
 | ||||||
| 		for _, term := range searchTerms { | 		for _, term := range searchTerms { | ||||||
| 			SearchAllIndexes(term, "/") | 			SearchAllIndexes(term, "/", "test") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	printBenchmarkResults(b) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func BenchmarkFillIndex(b *testing.B) { | func BenchmarkFillIndex(b *testing.B) { | ||||||
|  | @ -82,14 +81,13 @@ func BenchmarkFillIndex(b *testing.B) { | ||||||
| 	b.ResetTimer() | 	b.ResetTimer() | ||||||
| 	b.ReportAllocs() | 	b.ReportAllocs() | ||||||
| 	for i := 0; i < b.N; i++ { | 	for i := 0; i < b.N; i++ { | ||||||
| 		createMockData(10000, 10) // 1000 dirs, 3 files per dir
 | 		createMockData(50, 3) // 1000 dirs, 3 files per dir
 | ||||||
| 	} | 	} | ||||||
| 	printBenchmarkResults(b) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createMockData(numDirs, numFilesPerDir int) { | func createMockData(numDirs, numFilesPerDir int) { | ||||||
| 	for i := 0; i < numDirs; i++ { | 	for i := 0; i < numDirs; i++ { | ||||||
| 		dirName := "srv/" + getRandomTerm() | 		dirName := generateRandomPath(rand.Intn(3) + 1) | ||||||
| 		addToIndex("/", dirName, true) | 		addToIndex("/", dirName, true) | ||||||
| 		for j := 0; j < numFilesPerDir; j++ { | 		for j := 0; j < numFilesPerDir; j++ { | ||||||
| 			fileName := "file-" + getRandomTerm() + getRandomExtension() | 			fileName := "file-" + getRandomTerm() + getRandomExtension() | ||||||
|  | @ -98,6 +96,15 @@ func createMockData(numDirs, numFilesPerDir int) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func generateRandomPath(levels int) string { | ||||||
|  | 	rand.Seed(time.Now().UnixNano()) | ||||||
|  | 	dirName := "srv" | ||||||
|  | 	for i := 0; i < levels; i++ { | ||||||
|  | 		dirName += "/" + getRandomTerm() | ||||||
|  | 	} | ||||||
|  | 	return dirName | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func getRandomTerm() string { | func getRandomTerm() string { | ||||||
| 	wordbank := []string{ | 	wordbank := []string{ | ||||||
| 		"hi", "test", "other", "name", | 		"hi", "test", "other", "name", | ||||||
|  | @ -161,11 +168,3 @@ func formatMemory(bytes int64) string { | ||||||
| 	} | 	} | ||||||
| 	return fmt.Sprintf("%d %s", bytes, sizes[i]) | 	return fmt.Sprintf("%d %s", bytes, sizes[i]) | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // Output the benchmark results with human-readable units
 |  | ||||||
| func printBenchmarkResults(b *testing.B) { |  | ||||||
| 	averageTimePerIteration := b.Elapsed() / time.Duration(b.N) |  | ||||||
| 	fmt.Printf("\nIterations            : %d\n", b.N) |  | ||||||
| 	fmt.Printf("Total time            : %s\n", formatDuration(b.Elapsed())) |  | ||||||
| 	fmt.Printf("Avg time per op       : %s\n", formatDuration(averageTimePerIteration)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,83 @@ | ||||||
|  | package settings | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"github.com/goccy/go-yaml" | ||||||
|  | 	"github.com/gtsteffaniak/filebrowser/users" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var GlobalConfiguration Settings | ||||||
|  | 
 | ||||||
|  | func Initialize(configFile string) { | ||||||
|  | 	yamlData := loadConfigFile(configFile) | ||||||
|  | 	GlobalConfiguration = setDefaults() | ||||||
|  | 	err := yaml.Unmarshal(yamlData, &GlobalConfiguration) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Error unmarshaling YAML data: %v", err) | ||||||
|  | 	} | ||||||
|  | 	GlobalConfiguration.UserDefaults.Perm = GlobalConfiguration.UserDefaults.Permissions | ||||||
|  | 	GlobalConfiguration.Server.Root = "/srv" // hardcoded for now. TODO allow changing
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func loadConfigFile(configFile string) []byte { | ||||||
|  | 	// Open and read the YAML file
 | ||||||
|  | 	yamlFile, err := os.Open(configFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("ERROR: opening config file\n %v\n WARNING: Using default config only\n If this was a mistake, please make sure the file exists and is accessible by the filebrowser binary.\n\n", err) | ||||||
|  | 		setDefaults() | ||||||
|  | 		return []byte{} | ||||||
|  | 	} | ||||||
|  | 	defer yamlFile.Close() | ||||||
|  | 
 | ||||||
|  | 	stat, err := yamlFile.Stat() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Error getting file information: %s", err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	yamlData := make([]byte, stat.Size()) | ||||||
|  | 	_, err = yamlFile.Read(yamlData) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Error reading YAML data: %v", err) | ||||||
|  | 	} | ||||||
|  | 	return yamlData | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func setDefaults() Settings { | ||||||
|  | 	return Settings{ | ||||||
|  | 		Signup:        true, | ||||||
|  | 		AdminUsername: "admin", | ||||||
|  | 		AdminPassword: "admin", | ||||||
|  | 		Server: Server{ | ||||||
|  | 			EnableThumbnails:   true, | ||||||
|  | 			EnableExec:         false, | ||||||
|  | 			IndexingInterval:   5, | ||||||
|  | 			Port:               8080, | ||||||
|  | 			NumImageProcessors: 4, | ||||||
|  | 			BaseURL:            "", | ||||||
|  | 			Database:           "database.db", | ||||||
|  | 			Log:                "stdout", | ||||||
|  | 			Root:               "/srv", | ||||||
|  | 		}, | ||||||
|  | 		Auth: Auth{ | ||||||
|  | 			Method: "password", | ||||||
|  | 			Recaptcha: Recaptcha{ | ||||||
|  | 				Host: "", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		UserDefaults: UserDefaults{ | ||||||
|  | 			Scope:        ".", | ||||||
|  | 			LockPassword: false, | ||||||
|  | 			HideDotfiles: true, | ||||||
|  | 			Permissions: users.Permissions{ | ||||||
|  | 				Create:   true, | ||||||
|  | 				Rename:   true, | ||||||
|  | 				Modify:   true, | ||||||
|  | 				Delete:   true, | ||||||
|  | 				Share:    true, | ||||||
|  | 				Download: true, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -35,3 +35,7 @@ func GenerateKey() ([]byte, error) { | ||||||
| 
 | 
 | ||||||
| 	return b, nil | 	return b, nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func GetSettingsConfig(nameType string, Value string) string { | ||||||
|  | 	return nameType + Value | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,46 @@ | ||||||
|  | package settings | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/goccy/go-yaml" | ||||||
|  | 	"github.com/google/go-cmp/cmp" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestConfigLoadChanged(t *testing.T) { | ||||||
|  | 	configYml = "./testingConfig.yaml" | ||||||
|  | 	yamlData := loadConfigFile() | ||||||
|  | 	// Marshal the YAML data to a more human-readable format
 | ||||||
|  | 	newConfig := setDefaults() | ||||||
|  | 	GlobalConfiguration := setDefaults() | ||||||
|  | 
 | ||||||
|  | 	err := yaml.Unmarshal(yamlData, &newConfig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Error unmarshaling YAML data: %v", err) | ||||||
|  | 	} | ||||||
|  | 	// Use go-cmp to compare the two structs
 | ||||||
|  | 	if diff := cmp.Diff(newConfig, GlobalConfiguration); diff == "" { | ||||||
|  | 		t.Errorf("No change when there should have been (-want +got):\n%s", diff) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestConfigLoadSpecificValues(t *testing.T) { | ||||||
|  | 	configYml = "./testingConfig.yaml" | ||||||
|  | 	yamlData := loadConfigFile() | ||||||
|  | 	// Marshal the YAML data to a more human-readable format
 | ||||||
|  | 	newConfig := setDefaults() | ||||||
|  | 	GlobalConfiguration := setDefaults() | ||||||
|  | 
 | ||||||
|  | 	err := yaml.Unmarshal(yamlData, &newConfig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Error unmarshaling YAML data: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if GlobalConfiguration.Auth.Method == newConfig.Auth.Method { | ||||||
|  | 		log.Fatalf("Differences should have been found, but were not on Auth method") | ||||||
|  | 	} | ||||||
|  | 	if GlobalConfiguration.UserDefaults.HideDotfiles == newConfig.UserDefaults.HideDotfiles { | ||||||
|  | 		log.Fatalf("Differences should have been found, but were not on Auth method") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -50,16 +50,16 @@ func (s *Storage) Save(set *Settings) error { | ||||||
| 		return errors.ErrEmptyKey | 		return errors.ErrEmptyKey | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if set.Defaults.Locale == "" { | 	if set.UserDefaults.Locale == "" { | ||||||
| 		set.Defaults.Locale = "en" | 		set.UserDefaults.Locale = "en" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if set.Defaults.Commands == nil { | 	if set.UserDefaults.Commands == nil { | ||||||
| 		set.Defaults.Commands = []string{} | 		set.UserDefaults.Commands = []string{} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if set.Defaults.ViewMode == "" { | 	if set.UserDefaults.ViewMode == "" { | ||||||
| 		set.Defaults.ViewMode = users.MosaicViewMode | 		set.UserDefaults.ViewMode = users.MosaicViewMode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if set.Rules == nil { | 	if set.Rules == nil { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| package settings | package settings | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/gtsteffaniak/filebrowser/files" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/rules" | 	"github.com/gtsteffaniak/filebrowser/rules" | ||||||
| 	"github.com/gtsteffaniak/filebrowser/users" | 	"github.com/gtsteffaniak/filebrowser/users" | ||||||
| ) | ) | ||||||
|  | @ -24,26 +23,35 @@ type Settings struct { | ||||||
| 	Signup           bool                `json:"signup"` | 	Signup           bool                `json:"signup"` | ||||||
| 	CreateUserDir    bool                `json:"createUserDir"` | 	CreateUserDir    bool                `json:"createUserDir"` | ||||||
| 	UserHomeBasePath string              `json:"userHomeBasePath"` | 	UserHomeBasePath string              `json:"userHomeBasePath"` | ||||||
| 	Defaults         UserDefaults        `json:"defaults"` |  | ||||||
| 	Commands         map[string][]string `json:"commands"` | 	Commands         map[string][]string `json:"commands"` | ||||||
| 	Shell            []string            `json:"shell"` | 	Shell            []string            `json:"shell"` | ||||||
|  | 	AdminUsername    string              `json:"adminUsername"` | ||||||
|  | 	AdminPassword    string              `json:"adminPassword"` | ||||||
| 	Rules            []rules.Rule        `json:"rules"` | 	Rules            []rules.Rule        `json:"rules"` | ||||||
| 	Server           Server              `json:"server"` | 	Server           Server              `json:"server"` | ||||||
| 	AuthMethod       string              `json:"authMethod"` | 	Auth             Auth                `json:"auth"` | ||||||
| 	Auth             struct { | 	Frontend         Frontend            `json:"frontend"` | ||||||
|  | 	UserDefaults     UserDefaults        `json:"userDefaults"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Auth struct { | ||||||
|  | 	Recaptcha Recaptcha `json:"recaptcha"` | ||||||
| 	Header    string    `json:"header"` | 	Header    string    `json:"header"` | ||||||
| 	Method    string    `json:"method"` | 	Method    string    `json:"method"` | ||||||
| 	Command   string    `json:"command"` | 	Command   string    `json:"command"` | ||||||
| 	Signup    bool      `json:"signup"` | 	Signup    bool      `json:"signup"` | ||||||
| 	Shell     string    `json:"shell"` | 	Shell     string    `json:"shell"` | ||||||
| 	} `json:"auth"` | } | ||||||
| 
 | 
 | ||||||
| 	Branding Branding `json:"branding"` | type Recaptcha struct { | ||||||
| 
 | 	Host   string `json:"host"` | ||||||
| 	UserDefaults UserDefaults `json:"userDefaults"` | 	Key    string `json:"key"` | ||||||
|  | 	Secret string `json:"secret"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Server struct { | type Server struct { | ||||||
|  | 	IndexingInterval      uint32 `json:"indexingInterval"` | ||||||
|  | 	NumImageProcessors    int    `json:"numImageProcessors"` | ||||||
| 	Socket                string `json:"socket"` | 	Socket                string `json:"socket"` | ||||||
| 	TLSKey                string `json:"tlsKey"` | 	TLSKey                string `json:"tlsKey"` | ||||||
| 	TLSCert               string `json:"tlsCert"` | 	TLSCert               string `json:"tlsCert"` | ||||||
|  | @ -52,16 +60,15 @@ type Server struct { | ||||||
| 	EnableExec            bool   `json:"enableExec"` | 	EnableExec            bool   `json:"enableExec"` | ||||||
| 	TypeDetectionByHeader bool   `json:"typeDetectionByHeader"` | 	TypeDetectionByHeader bool   `json:"typeDetectionByHeader"` | ||||||
| 	AuthHook              string `json:"authHook"` | 	AuthHook              string `json:"authHook"` | ||||||
| 	Port                  string `json:"port"` | 	Port                  int    `json:"port"` | ||||||
| 	BaseURL               string `json:"baseURL"` | 	BaseURL               string `json:"baseURL"` | ||||||
| 	Address               string `json:"address"` | 	Address               string `json:"address"` | ||||||
| 	Log                   string `json:"log"` | 	Log                   string `json:"log"` | ||||||
| 	Database              string `json:"database"` | 	Database              string `json:"database"` | ||||||
| 	Root                  string `json:"root"` | 	Root                  string `json:"root"` | ||||||
| 	EnablePreviewResize   bool   `json:"disable-preview-resize"` |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Branding struct { | type Frontend struct { | ||||||
| 	Name                  string `json:"name"` | 	Name                  string `json:"name"` | ||||||
| 	DisableExternal       bool   `json:"disableExternal"` | 	DisableExternal       bool   `json:"disableExternal"` | ||||||
| 	DisableUsedPercentage bool   `json:"disableUsedPercentage"` | 	DisableUsedPercentage bool   `json:"disableUsedPercentage"` | ||||||
|  | @ -73,55 +80,18 @@ type Branding struct { | ||||||
| // UserDefaults is a type that holds the default values
 | // UserDefaults is a type that holds the default values
 | ||||||
| // for some fields on User.
 | // for some fields on User.
 | ||||||
| type UserDefaults struct { | type UserDefaults struct { | ||||||
|  | 	LockPassword bool   `json:"lockPassword"` | ||||||
| 	Scope        string `json:"scope"` | 	Scope        string `json:"scope"` | ||||||
| 	Locale       string `json:"locale"` | 	Locale       string `json:"locale"` | ||||||
| 	ViewMode     users.ViewMode    `json:"viewMode"` | 	ViewMode     string `json:"viewMode"` | ||||||
| 	SingleClick  bool   `json:"singleClick"` | 	SingleClick  bool   `json:"singleClick"` | ||||||
| 	Sorting      files.Sorting     `json:"sorting"` | 	Sorting      struct { | ||||||
|  | 		By  string `json:"by"` | ||||||
|  | 		Asc bool   `json:"asc"` | ||||||
|  | 	} `json:"sorting"` | ||||||
| 	Perm         users.Permissions `json:"perm"` | 	Perm         users.Permissions `json:"perm"` | ||||||
|  | 	Permissions  users.Permissions `json:"permissions"` | ||||||
| 	Commands     []string          `json:"commands"` | 	Commands     []string          `json:"commands"` | ||||||
| 	HideDotfiles bool              `json:"hideDotfiles"` | 	HideDotfiles bool              `json:"hideDotfiles"` | ||||||
| 	DateFormat   bool              `json:"dateFormat"` | 	DateFormat   bool              `json:"dateFormat"` | ||||||
| } | } | ||||||
| 
 |  | ||||||
| //{
 |  | ||||||
| //	"server":{
 |  | ||||||
| //	   "port":8080,
 |  | ||||||
| //	   "baseURL":"",
 |  | ||||||
| //	   "address":"",
 |  | ||||||
| //	   "log":"stdout",
 |  | ||||||
| //	   "database":"./database.db",
 |  | ||||||
| //	   "root":"/srv",
 |  | ||||||
| //	   "disable-thumbnails":false,
 |  | ||||||
| //	   "disable-preview-resize":false,
 |  | ||||||
| //	   "disable-exec":false,
 |  | ||||||
| //	   "disable-type-detection-by-header":false
 |  | ||||||
| //	},
 |  | ||||||
| //	"auth":{
 |  | ||||||
| //	   "header":"",
 |  | ||||||
| //	   "method":"",
 |  | ||||||
| //	   "command":"",
 |  | ||||||
| //	   "signup":false,
 |  | ||||||
| //	   "shell":""
 |  | ||||||
| //	},
 |  | ||||||
| //	"branding":{
 |  | ||||||
| //	   "name":"",
 |  | ||||||
| //	   "color":"",
 |  | ||||||
| //	   "files":"",
 |  | ||||||
| //	   "disableExternal":"",
 |  | ||||||
| //	   "disableUsedPercentage":""
 |  | ||||||
| //	},
 |  | ||||||
| //	"permissions":{
 |  | ||||||
| //	   "Admin":false,
 |  | ||||||
| //	   "Execute":true,
 |  | ||||||
| //	   "Create":true,
 |  | ||||||
| //	   "Rename":true,
 |  | ||||||
| //	   "Modify":true,
 |  | ||||||
| //	   "Delete":true,
 |  | ||||||
| //	   "Share":true,
 |  | ||||||
| //	   "Download":true
 |  | ||||||
| //	},
 |  | ||||||
| //	"commands":{},
 |  | ||||||
| //	"shell":{},
 |  | ||||||
| //	"rules":{}
 |  | ||||||
| // }
 |  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,52 @@ | ||||||
|  | server: | ||||||
|  |   indexingInterval: 5 | ||||||
|  |   numImageProcessors: 4 | ||||||
|  |   socket: "" | ||||||
|  |   tlsKey: "" | ||||||
|  |   tlsCert: "" | ||||||
|  |   enableThumbnails: false | ||||||
|  |   resizePreview: true | ||||||
|  |   typeDetectionByHeader: true | ||||||
|  |   port: 8080 | ||||||
|  |   baseURL: "/" | ||||||
|  |   address: "" | ||||||
|  |   log: "stdout" | ||||||
|  |   database: "database.db" | ||||||
|  |   root: "/srv" | ||||||
|  | auth: | ||||||
|  |   recaptcha: | ||||||
|  |     host: "" | ||||||
|  |     key: "" | ||||||
|  |     secret: "" | ||||||
|  |   header: "" | ||||||
|  |   method: json | ||||||
|  |   command: "" | ||||||
|  |   signup: false | ||||||
|  |   shell: "" | ||||||
|  | frontend: | ||||||
|  |   name: "" | ||||||
|  |   disableExternal: false | ||||||
|  |   disableUsedPercentage: true | ||||||
|  |   files: "" | ||||||
|  |   theme: "" | ||||||
|  |   color: "" | ||||||
|  | userDefaults: | ||||||
|  |   scope: "" | ||||||
|  |   locale: "" | ||||||
|  |   viewMode: "" | ||||||
|  |   singleClick: true | ||||||
|  |   sorting: | ||||||
|  |     by: "" | ||||||
|  |     asc: true | ||||||
|  |   permissions: | ||||||
|  |     admin: true | ||||||
|  |     execute: true | ||||||
|  |     create: true | ||||||
|  |     rename: true | ||||||
|  |     modify: true | ||||||
|  |     delete: true | ||||||
|  |     share: true | ||||||
|  |     download: true | ||||||
|  |   commands: [] | ||||||
|  |   hideDotfiles: false | ||||||
|  |   dateFormat: false | ||||||
|  | @ -2,7 +2,6 @@ package bolt | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/asdine/storm/v3" | 	"github.com/asdine/storm/v3" | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/auth" | 	"github.com/gtsteffaniak/filebrowser/auth" | ||||||
| 	"github.com/gtsteffaniak/filebrowser/errors" | 	"github.com/gtsteffaniak/filebrowser/errors" | ||||||
| ) | ) | ||||||
|  | @ -13,20 +12,18 @@ type authBackend struct { | ||||||
| 
 | 
 | ||||||
| func (s authBackend) Get(t string) (auth.Auther, error) { | func (s authBackend) Get(t string) (auth.Auther, error) { | ||||||
| 	var auther auth.Auther | 	var auther auth.Auther | ||||||
| 
 |  | ||||||
| 	switch t { | 	switch t { | ||||||
| 	case auth.MethodJSONAuth: | 	case "password": | ||||||
| 		auther = &auth.JSONAuth{} | 		auther = &auth.JSONAuth{} | ||||||
| 	case auth.MethodProxyAuth: | 	case "proxy": | ||||||
| 		auther = &auth.ProxyAuth{} | 		auther = &auth.ProxyAuth{} | ||||||
| 	case auth.MethodHookAuth: | 	case "hook": | ||||||
| 		auther = &auth.HookAuth{} | 		auther = &auth.HookAuth{} | ||||||
| 	case auth.MethodNoAuth: | 	case "noauth": | ||||||
| 		auther = &auth.NoAuth{} | 		auther = &auth.NoAuth{} | ||||||
| 	default: | 	default: | ||||||
| 		return nil, errors.ErrInvalidAuthMethod | 		return nil, errors.ErrInvalidAuthMethod | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	return auther, get(s.db, "auther", auther) | 	return auther, get(s.db, "auther", auther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ package bolt | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/asdine/storm/v3" | 	"github.com/asdine/storm/v3" | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/settings" | 	"github.com/gtsteffaniak/filebrowser/settings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -20,7 +19,10 @@ func (s settingsBackend) Save(set *settings.Settings) error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s settingsBackend) GetServer() (*settings.Server, error) { | func (s settingsBackend) GetServer() (*settings.Server, error) { | ||||||
| 	server := &settings.Server{} | 	server := &settings.Server{ | ||||||
|  | 		Port: 8080, | ||||||
|  | 		NumImageProcessors: 1, | ||||||
|  | 	} | ||||||
| 	return server, get(s.db, "server", server) | 	return server, get(s.db, "server", server) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,187 +0,0 @@ | ||||||
| package importer |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 
 |  | ||||||
| 	"github.com/asdine/storm/v3" |  | ||||||
| 	"github.com/pelletier/go-toml/v2" |  | ||||||
| 	"gopkg.in/yaml.v2" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/auth" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/settings" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/storage" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/users" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type oldDefs struct { |  | ||||||
| 	Commands      []string `json:"commands" yaml:"commands" toml:"commands"` |  | ||||||
| 	Scope         string   `json:"scope" yaml:"scope" toml:"scope"` |  | ||||||
| 	ViewMode      string   `json:"viewMode" yaml:"viewMode" toml:"viewMode"` |  | ||||||
| 	Locale        string   `json:"locale" yaml:"locale" toml:"locale"` |  | ||||||
| 	AllowCommands bool     `json:"allowCommands" yaml:"allowCommands" toml:"allowCommands"` |  | ||||||
| 	AllowEdit     bool     `json:"allowEdit" yaml:"allowEdit" toml:"allowEdit"` |  | ||||||
| 	AllowNew      bool     `json:"allowNew" yaml:"allowNew" toml:"allowNew"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type oldAuth struct { |  | ||||||
| 	Method  string `json:"method" yaml:"method" toml:"method"` // default none proxy
 |  | ||||||
| 	Header  string `json:"header" yaml:"header" toml:"header"` |  | ||||||
| 	Command string `json:"command" yaml:"command" toml:"command"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type oldConf struct { |  | ||||||
| 	Port      string  `json:"port" yaml:"port" toml:"port"` |  | ||||||
| 	BaseURL   string  `json:"baseURL" yaml:"baseURL" toml:"baseURL"` |  | ||||||
| 	Log       string  `json:"log" yaml:"log" toml:"log"` |  | ||||||
| 	Address   string  `json:"address" yaml:"address" toml:"address"` |  | ||||||
| 	Defaults  oldDefs `json:"defaults" yaml:"defaults" toml:"defaults"` |  | ||||||
| 	ReCaptcha struct { |  | ||||||
| 		Key    string `json:"key" yaml:"key" toml:"key"` |  | ||||||
| 		Secret string `json:"secret" yaml:"secret" toml:"secret"` |  | ||||||
| 		Host   string `json:"host" yaml:"host" toml:"host"` |  | ||||||
| 	} `json:"recaptcha" yaml:"recaptcha" toml:"recaptcha"` |  | ||||||
| 	Auth oldAuth `json:"auth" yaml:"auth" toml:"auth"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var defaults = &oldConf{ |  | ||||||
| 	Port: "0", |  | ||||||
| 	Log:  "stdout", |  | ||||||
| 	Defaults: oldDefs{ |  | ||||||
| 		Commands:      []string{"git", "svn", "hg"}, |  | ||||||
| 		ViewMode:      string(users.MosaicViewMode), |  | ||||||
| 		AllowCommands: true, |  | ||||||
| 		AllowEdit:     true, |  | ||||||
| 		AllowNew:      true, |  | ||||||
| 		Locale:        "en", |  | ||||||
| 	}, |  | ||||||
| 	Auth: oldAuth{ |  | ||||||
| 		Method: "default", |  | ||||||
| 	}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func readConf(path string) (*oldConf, error) { |  | ||||||
| 	cfg := &oldConf{} |  | ||||||
| 	if path != "" { |  | ||||||
| 		ext := filepath.Ext(path) |  | ||||||
| 
 |  | ||||||
| 		fd, err := os.Open(path) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		defer fd.Close() |  | ||||||
| 
 |  | ||||||
| 		switch ext { |  | ||||||
| 		case ".json": |  | ||||||
| 			err = json.NewDecoder(fd).Decode(cfg) |  | ||||||
| 		case ".toml": |  | ||||||
| 			err = toml.NewDecoder(fd).Decode(cfg) |  | ||||||
| 		case ".yaml", ".yml": |  | ||||||
| 			err = yaml.NewDecoder(fd).Decode(cfg) |  | ||||||
| 		default: |  | ||||||
| 			return nil, errors.New("unsupported config extension " + ext) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		cfg = defaults |  | ||||||
| 		path, err := filepath.Abs(".") |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		cfg.Defaults.Scope = path |  | ||||||
| 	} |  | ||||||
| 	return cfg, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func importConf(db *storm.DB, path string, sto *storage.Storage) error { |  | ||||||
| 	cfg, err := readConf(path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	commands := map[string][]string{} |  | ||||||
| 	err = db.Get("config", "commands", &commands) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	key := []byte{} |  | ||||||
| 	err = db.Get("config", "key", &key) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	s := &settings.Settings{ |  | ||||||
| 		Key:    key, |  | ||||||
| 		Signup: false, |  | ||||||
| 		Defaults: settings.UserDefaults{ |  | ||||||
| 			Scope:    cfg.Defaults.Scope, |  | ||||||
| 			Commands: cfg.Defaults.Commands, |  | ||||||
| 			ViewMode: users.ViewMode(cfg.Defaults.ViewMode), |  | ||||||
| 			Locale:   cfg.Defaults.Locale, |  | ||||||
| 			Perm: users.Permissions{ |  | ||||||
| 				Admin:    false, |  | ||||||
| 				Execute:  cfg.Defaults.AllowCommands, |  | ||||||
| 				Create:   cfg.Defaults.AllowNew, |  | ||||||
| 				Rename:   cfg.Defaults.AllowEdit, |  | ||||||
| 				Modify:   cfg.Defaults.AllowEdit, |  | ||||||
| 				Delete:   cfg.Defaults.AllowEdit, |  | ||||||
| 				Share:    true, |  | ||||||
| 				Download: true, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	server := &settings.Server{ |  | ||||||
| 		BaseURL: cfg.BaseURL, |  | ||||||
| 		Port:    cfg.Port, |  | ||||||
| 		Address: cfg.Address, |  | ||||||
| 		Log:     cfg.Log, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var auther auth.Auther |  | ||||||
| 	switch cfg.Auth.Method { |  | ||||||
| 	case "proxy": |  | ||||||
| 		auther = &auth.ProxyAuth{Header: cfg.Auth.Header} |  | ||||||
| 		s.AuthMethod = string(auth.MethodProxyAuth) |  | ||||||
| 	case "hook": |  | ||||||
| 		auther = &auth.HookAuth{Command: cfg.Auth.Command} |  | ||||||
| 		s.AuthMethod = string(auth.MethodHookAuth) |  | ||||||
| 	case "none": |  | ||||||
| 		auther = &auth.NoAuth{} |  | ||||||
| 		s.AuthMethod = string(auth.MethodNoAuth) |  | ||||||
| 	default: |  | ||||||
| 		auther = &auth.JSONAuth{ |  | ||||||
| 			ReCaptcha: &auth.ReCaptcha{ |  | ||||||
| 				Host:   cfg.ReCaptcha.Host, |  | ||||||
| 				Key:    cfg.ReCaptcha.Key, |  | ||||||
| 				Secret: cfg.ReCaptcha.Secret, |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 		s.AuthMethod = string(auth.MethodJSONAuth) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = sto.Auth.Save(auther) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = sto.Settings.Save(s) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = sto.Settings.SaveServer(server) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fmt.Println("Configuration successfully imported.") |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  | @ -1,39 +0,0 @@ | ||||||
| package importer |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"github.com/asdine/storm/v3" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/storage/bolt" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Import imports an old configuration to a newer database.
 |  | ||||||
| func Import(oldDBPath, oldConf, newDBPath string) error { |  | ||||||
| 	oldDB, err := storm.Open(oldDBPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer oldDB.Close() |  | ||||||
| 
 |  | ||||||
| 	newDB, err := storm.Open(newDBPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer newDB.Close() |  | ||||||
| 
 |  | ||||||
| 	sto, err := bolt.NewStorage(newDB) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = importUsers(oldDB, sto) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = importConf(oldDB, oldConf, sto) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
|  | @ -1,114 +0,0 @@ | ||||||
| package importer |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 
 |  | ||||||
| 	"github.com/asdine/storm/v3" |  | ||||||
| 	bolt "go.etcd.io/bbolt" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/rules" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/storage" |  | ||||||
| 	"github.com/gtsteffaniak/filebrowser/users" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type oldUser struct { |  | ||||||
| 	ID            int           `storm:"id,increment"` |  | ||||||
| 	Admin         bool          `json:"admin"` |  | ||||||
| 	AllowCommands bool          `json:"allowCommands"` // Execute commands
 |  | ||||||
| 	AllowEdit     bool          `json:"allowEdit"`     // Edit/rename files
 |  | ||||||
| 	AllowNew      bool          `json:"allowNew"`      // Create files and folders
 |  | ||||||
| 	AllowPublish  bool          `json:"allowPublish"`  // Publish content (to use with static gen)
 |  | ||||||
| 	LockPassword  bool          `json:"lockPassword"` |  | ||||||
| 	Commands      []string      `json:"commands"` |  | ||||||
| 	Locale        string        `json:"locale"` |  | ||||||
| 	Password      string        `json:"password"` |  | ||||||
| 	Rules         []*rules.Rule `json:"rules"` |  | ||||||
| 	Scope         string        `json:"filesystem"` |  | ||||||
| 	Username      string        `json:"username" storm:"index,unique"` |  | ||||||
| 	ViewMode      string        `json:"viewMode"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func readOldUsers(db *storm.DB) ([]*oldUser, error) { |  | ||||||
| 	var oldUsers []*oldUser |  | ||||||
| 	err := db.Bolt.View(func(tx *bolt.Tx) error { |  | ||||||
| 		return tx.Bucket([]byte("User")).ForEach(func(k []byte, v []byte) error { |  | ||||||
| 			if len(v) > 0 && string(v)[0] == '{' { |  | ||||||
| 				user := &oldUser{} |  | ||||||
| 				err := json.Unmarshal(v, user) |  | ||||||
| 
 |  | ||||||
| 				if err != nil { |  | ||||||
| 					return err |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				oldUsers = append(oldUsers, user) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			return nil |  | ||||||
| 		}) |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	return oldUsers, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func convertUsersToNew(old []*oldUser) ([]*users.User, error) { |  | ||||||
| 	list := []*users.User{} |  | ||||||
| 
 |  | ||||||
| 	for _, oldUser := range old { |  | ||||||
| 		user := &users.User{ |  | ||||||
| 			Username:     oldUser.Username, |  | ||||||
| 			Password:     oldUser.Password, |  | ||||||
| 			Scope:        oldUser.Scope, |  | ||||||
| 			Locale:       oldUser.Locale, |  | ||||||
| 			LockPassword: oldUser.LockPassword, |  | ||||||
| 			ViewMode:     users.ViewMode(oldUser.ViewMode), |  | ||||||
| 			Commands:     oldUser.Commands, |  | ||||||
| 			Rules:        []rules.Rule{}, |  | ||||||
| 			Perm: users.Permissions{ |  | ||||||
| 				Admin:    oldUser.Admin, |  | ||||||
| 				Execute:  oldUser.AllowCommands, |  | ||||||
| 				Create:   oldUser.AllowNew, |  | ||||||
| 				Rename:   oldUser.AllowEdit, |  | ||||||
| 				Modify:   oldUser.AllowEdit, |  | ||||||
| 				Delete:   oldUser.AllowEdit, |  | ||||||
| 				Share:    true, |  | ||||||
| 				Download: true, |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, rule := range oldUser.Rules { |  | ||||||
| 			user.Rules = append(user.Rules, *rule) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		err := user.Clean("") |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		list = append(list, user) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return list, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func importUsers(old *storm.DB, sto *storage.Storage) error { |  | ||||||
| 	oldUsers, err := readOldUsers(old) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	newUsers, err := convertUsersToNew(oldUsers) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, user := range newUsers { |  | ||||||
| 		err = sto.Users.Save(user) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fmt.Printf("%d users successfully imported into the new DB.\n", len(newUsers)) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,113 @@ | ||||||
|  | 
 | ||||||
|  |  == Running tests ==  | ||||||
|  | /usr/local/go/bin/go | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/auth	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/cmd	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/errors	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/files	[no test files] | ||||||
|  | === RUN   TestFileCache | ||||||
|  | --- PASS: TestFileCache (0.00s) | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/diskcache	(cached) | ||||||
|  | === RUN   TestCommonPrefix | ||||||
|  | === RUN   TestCommonPrefix/sub_folder | ||||||
|  | === RUN   TestCommonPrefix/relative_path | ||||||
|  | === RUN   TestCommonPrefix/no_common_path | ||||||
|  | === RUN   TestCommonPrefix/same_lvl | ||||||
|  | --- PASS: TestCommonPrefix (0.00s) | ||||||
|  |     --- PASS: TestCommonPrefix/sub_folder (0.00s) | ||||||
|  |     --- PASS: TestCommonPrefix/relative_path (0.00s) | ||||||
|  |     --- PASS: TestCommonPrefix/no_common_path (0.00s) | ||||||
|  |     --- PASS: TestCommonPrefix/same_lvl (0.00s) | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/fileutils	(cached) | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/settings	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/share	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/storage	[no test files] | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/storage/bolt	[no test files] | ||||||
|  | 2023/09/02 13:07:17 Error opening YAML file: open filebrowser.yaml: no such file or directory | ||||||
|  | FAIL	github.com/gtsteffaniak/filebrowser/http	0.008s | ||||||
|  | === RUN   TestService_Resize | ||||||
|  | === RUN   TestService_Resize/convert_to_png | ||||||
|  | === RUN   TestService_Resize/convert_to_tiff | ||||||
|  | === RUN   TestService_Resize/resize_bmp | ||||||
|  | === RUN   TestService_Resize/resize_with_medium_quality | ||||||
|  | === RUN   TestService_Resize/resize_with_low_quality | ||||||
|  | === RUN   TestService_Resize/get_thumbnail_from_file_with_APP0_JFIF | ||||||
|  | === RUN   TestService_Resize/fill_upscale | ||||||
|  | === RUN   TestService_Resize/fit_upscale | ||||||
|  | === RUN   TestService_Resize/convert_to_gif | ||||||
|  | === RUN   TestService_Resize/convert_to_bmp | ||||||
|  | === RUN   TestService_Resize/resize_tiff | ||||||
|  | === RUN   TestService_Resize/resize_with_high_quality | ||||||
|  | === RUN   TestService_Resize/fill_downscale | ||||||
|  | === RUN   TestService_Resize/keep_original_format | ||||||
|  | === RUN   TestService_Resize/convert_to_unknown | ||||||
|  | === RUN   TestService_Resize/get_thumbnail_from_file_without_APP0_JFIF | ||||||
|  | === RUN   TestService_Resize/resize_for_higher_quality_levels | ||||||
|  | === RUN   TestService_Resize/broken_file | ||||||
|  | === RUN   TestService_Resize/fit_downscale | ||||||
|  | === RUN   TestService_Resize/convert_to_jpeg | ||||||
|  | === RUN   TestService_Resize/resize_png | ||||||
|  | === RUN   TestService_Resize/resize_gif | ||||||
|  | === RUN   TestService_Resize/resize_with_unknown_quality | ||||||
|  | === RUN   TestService_Resize/resize_from_file_without_IFD1_thumbnail | ||||||
|  | --- PASS: TestService_Resize (1.36s) | ||||||
|  |     --- PASS: TestService_Resize/convert_to_png (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/convert_to_tiff (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/resize_bmp (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/resize_with_medium_quality (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/resize_with_low_quality (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/get_thumbnail_from_file_with_APP0_JFIF (0.02s) | ||||||
|  |     --- PASS: TestService_Resize/fill_upscale (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/fit_upscale (0.00s) | ||||||
|  |     --- PASS: TestService_Resize/convert_to_gif (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/convert_to_bmp (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/resize_tiff (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/resize_with_high_quality (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/fill_downscale (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/keep_original_format (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/convert_to_unknown (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/get_thumbnail_from_file_without_APP0_JFIF (0.03s) | ||||||
|  |     --- PASS: TestService_Resize/resize_for_higher_quality_levels (0.03s) | ||||||
|  |     --- PASS: TestService_Resize/broken_file (0.00s) | ||||||
|  |     --- PASS: TestService_Resize/fit_downscale (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/convert_to_jpeg (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/resize_png (0.02s) | ||||||
|  |     --- PASS: TestService_Resize/resize_gif (0.02s) | ||||||
|  |     --- PASS: TestService_Resize/resize_with_unknown_quality (0.01s) | ||||||
|  |     --- PASS: TestService_Resize/resize_from_file_without_IFD1_thumbnail (1.09s) | ||||||
|  | === RUN   TestService_FormatFromExtension | ||||||
|  | === RUN   TestService_FormatFromExtension/gif | ||||||
|  | === RUN   TestService_FormatFromExtension/tiff | ||||||
|  | === RUN   TestService_FormatFromExtension/bmp | ||||||
|  | === RUN   TestService_FormatFromExtension/unknown | ||||||
|  | === RUN   TestService_FormatFromExtension/jpg | ||||||
|  | === RUN   TestService_FormatFromExtension/jpeg | ||||||
|  | === RUN   TestService_FormatFromExtension/png | ||||||
|  | --- PASS: TestService_FormatFromExtension (0.00s) | ||||||
|  |     --- PASS: TestService_FormatFromExtension/gif (0.00s) | ||||||
|  |     --- PASS: TestService_FormatFromExtension/tiff (0.00s) | ||||||
|  |     --- PASS: TestService_FormatFromExtension/bmp (0.00s) | ||||||
|  |     --- PASS: TestService_FormatFromExtension/unknown (0.00s) | ||||||
|  |     --- PASS: TestService_FormatFromExtension/jpg (0.00s) | ||||||
|  |     --- PASS: TestService_FormatFromExtension/jpeg (0.00s) | ||||||
|  |     --- PASS: TestService_FormatFromExtension/png (0.00s) | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/img	(cached) | ||||||
|  | === RUN   TestMatchHidden | ||||||
|  | --- PASS: TestMatchHidden (0.00s) | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/rules	(cached) | ||||||
|  | 2023/09/02 13:07:17 Error opening YAML file: open filebrowser.yaml: no such file or directory | ||||||
|  | FAIL	github.com/gtsteffaniak/filebrowser/runner	0.007s | ||||||
|  | === RUN   TestParseSearch | ||||||
|  | --- PASS: TestParseSearch (0.00s) | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/search	(cached) | ||||||
|  | ?   	github.com/gtsteffaniak/filebrowser/version	[no test files] | ||||||
|  | testing: warning: no tests to run | ||||||
|  | PASS | ||||||
|  | ok  	github.com/gtsteffaniak/filebrowser/users	(cached) [no tests to run] | ||||||
|  | FAIL | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| package users |  | ||||||
| 
 |  | ||||||
| // Permissions describe a user's permissions.
 |  | ||||||
| type Permissions struct { |  | ||||||
| 	Admin    bool `json:"admin"` |  | ||||||
| 	Execute  bool `json:"execute"` |  | ||||||
| 	Create   bool `json:"create"` |  | ||||||
| 	Rename   bool `json:"rename"` |  | ||||||
| 	Modify   bool `json:"modify"` |  | ||||||
| 	Delete   bool `json:"delete"` |  | ||||||
| 	Share    bool `json:"share"` |  | ||||||
| 	Download bool `json:"download"` |  | ||||||
| } |  | ||||||
|  | @ -11,14 +11,22 @@ import ( | ||||||
| 	"github.com/gtsteffaniak/filebrowser/rules" | 	"github.com/gtsteffaniak/filebrowser/rules" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // ViewMode describes a view mode.
 | var ( | ||||||
| type ViewMode string | 	ListViewMode   = "list" | ||||||
| 
 | 	MosaicViewMode = "mosaic" | ||||||
| const ( |  | ||||||
| 	ListViewMode   ViewMode = "list" |  | ||||||
| 	MosaicViewMode ViewMode = "mosaic" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | type Permissions struct { | ||||||
|  | 	Admin    bool `json:"admin"` | ||||||
|  | 	Execute  bool `json:"execute"` | ||||||
|  | 	Create   bool `json:"create"` | ||||||
|  | 	Rename   bool `json:"rename"` | ||||||
|  | 	Modify   bool `json:"modify"` | ||||||
|  | 	Delete   bool `json:"delete"` | ||||||
|  | 	Share    bool `json:"share"` | ||||||
|  | 	Download bool `json:"download"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // User describes a user.
 | // User describes a user.
 | ||||||
| type User struct { | type User struct { | ||||||
| 	ID           uint          `storm:"id,increment" json:"id"` | 	ID           uint          `storm:"id,increment" json:"id"` | ||||||
|  | @ -27,7 +35,7 @@ type User struct { | ||||||
| 	Scope        string        `json:"scope"` | 	Scope        string        `json:"scope"` | ||||||
| 	Locale       string        `json:"locale"` | 	Locale       string        `json:"locale"` | ||||||
| 	LockPassword bool          `json:"lockPassword"` | 	LockPassword bool          `json:"lockPassword"` | ||||||
| 	ViewMode     ViewMode      `json:"viewMode"` | 	ViewMode     string        `json:"viewMode"` | ||||||
| 	SingleClick  bool          `json:"singleClick"` | 	SingleClick  bool          `json:"singleClick"` | ||||||
| 	Perm         Permissions   `json:"perm"` | 	Perm         Permissions   `json:"perm"` | ||||||
| 	Commands     []string      `json:"commands"` | 	Commands     []string      `json:"commands"` | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ package version | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	// Version is the current File Browser version.
 | 	// Version is the current File Browser version.
 | ||||||
| 	Version = "(0.1.4)" | 	Version = "(0.2.0)" | ||||||
| 	// CommitSHA is the commmit sha.
 | 	// CommitSHA is the commmit sha.
 | ||||||
| 	CommitSHA = "(unknown)" | 	CommitSHA = "(unknown)" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 9.1 KiB | 
| Before Width: | Height: | Size: 22 KiB | 
| Before Width: | Height: | Size: 7.2 KiB | 
| Before Width: | Height: | Size: 843 B | 
| Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 190 KiB | 
| Before Width: | Height: | Size: 7.0 KiB | 
| Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 7.5 KiB | 
| Before Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 4.9 KiB | 
|  | @ -33,13 +33,14 @@ header { | ||||||
| 
 | 
 | ||||||
| #search #input { | #search #input { | ||||||
|   background: var(--surfaceSecondary); |   background: var(--surfaceSecondary); | ||||||
|   border-color: var(--surfacePrimary); |   border-color: var(--surfaceSecondary); | ||||||
| } | } | ||||||
| #search #input input::placeholder { | #search #input input::placeholder { | ||||||
|   color: var(--textSecondary); |   color: var(--textSecondary); | ||||||
| } | } | ||||||
| #search.active #input { | #search.active #input { | ||||||
|   background: var(--surfacePrimary); |   background: var(--surfacePrimary); | ||||||
|  |   border-color: white; | ||||||
| } | } | ||||||
| #search.active input { | #search.active input { | ||||||
|   color: var(--textPrimary); |   color: var(--textPrimary); | ||||||
|  | @ -131,8 +132,8 @@ nav > div { | ||||||
| .input { | .input { | ||||||
|   background: var(--surfaceSecondary); |   background: var(--surfaceSecondary); | ||||||
|   color: var(--textPrimary); |   color: var(--textPrimary); | ||||||
|   border: 1px solid rgba(255, 255, 255, 0.05); |  | ||||||
| } | } | ||||||
|  | 
 | ||||||
| .input:hover, | .input:hover, | ||||||
| .input:focus { | .input:focus { | ||||||
|   border-color: rgba(255, 255, 255, 0.15); |   border-color: rgba(255, 255, 255, 0.15); | ||||||
|  | @ -213,3 +214,7 @@ nav { | ||||||
|   background: var(--background); |   background: var(--background); | ||||||
|   color: white |   color: white | ||||||
| } | } | ||||||
|  | #result-desktop #result-list { | ||||||
|  |   background: #2a3137; | ||||||
|  |   max-height: unset; | ||||||
|  | } | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| <template> | <template> | ||||||
|   <div> |  | ||||||
|   <router-view></router-view> |   <router-view></router-view> | ||||||
|   </div> |  | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
|  |  | ||||||
|  | @ -37,7 +37,9 @@ | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         <template v-if="isEmpty"> |         <template v-if="isEmpty"> | ||||||
|           <button class="mobile-boxes" v-if="value.length === 0 && !showBoxes " @click="resetSearchFilters()" >Reset filters</button> |           <button class="mobile-boxes" v-if="value.length === 0 && !showBoxes" @click="resetSearchFilters()"> | ||||||
|  |             Reset filters | ||||||
|  |           </button> | ||||||
|           <template v-if="value.length === 0 && showBoxes"> |           <template v-if="value.length === 0 && showBoxes"> | ||||||
|             <div class="boxes"> |             <div class="boxes"> | ||||||
|               <h3>{{ $t("search.types") }}</h3> |               <h3>{{ $t("search.types") }}</h3> | ||||||
|  | @ -53,11 +55,9 @@ | ||||||
|         </template> |         </template> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     <div v-if="!isMobile && active" id="result-desktop" ref="result"> |     <div v-show="!isMobile && active" id="result-desktop" ref="result"> | ||||||
|  |       <div class="searchContext">Search Context: {{ getContext(this.$route.path) }}</div> | ||||||
|       <div id="result-list"> |       <div id="result-list"> | ||||||
|         <div class="button fluid"> |  | ||||||
|           Search Context: {{ getContext(this.$route.path) }} |  | ||||||
|         </div> |  | ||||||
|         <template> |         <template> | ||||||
|           <p v-show="isEmpty && isRunning" id="renew"> |           <p v-show="isEmpty && isRunning" id="renew"> | ||||||
|             <i class="material-icons spin">autorenew</i> |             <i class="material-icons spin">autorenew</i> | ||||||
|  | @ -67,15 +67,27 @@ | ||||||
|             <div class="helpButton" @click="toggleHelp()">Help</div> |             <div class="helpButton" @click="toggleHelp()">Help</div> | ||||||
|           </div> |           </div> | ||||||
|           <div class="helpText" v-if="showHelp"> |           <div class="helpText" v-if="showHelp"> | ||||||
|             <p>Search occurs on each character you type (3 character minimum for search terms).</p> |             <p> | ||||||
|             <p><b>The index:</b> Search utilizes the index which automatically gets updated on the configured interval |               Search occurs on each character you type (3 character minimum for search | ||||||
|               (default: 5 minutes). |               terms). | ||||||
|               Searching when the program has just started may result in incomplete results.</p> |             </p> | ||||||
|             <p><b>Filter by type:</b> You can have multiple type filters by adding <code>type:condition</code> followed by |             <p> | ||||||
|               search terms.</p> |               <b>The index:</b> Search utilizes the index which automatically gets updated | ||||||
|             <p><b>Multiple Search terms:</b> Additional terms separated by <code>|</code>, |               on the configured interval (default: 5 minutes). Searching when the program | ||||||
|               for example <code>"test|not"</code> searches for both terms independently.</p> |               has just started may result in incomplete results. | ||||||
|             <p><b>File size:</b> Searching files by size may have significantly longer search times.</p> |             </p> | ||||||
|  |             <p> | ||||||
|  |               <b>Filter by type:</b> You can have multiple type filters by adding | ||||||
|  |               <code>type:condition</code> followed by search terms. | ||||||
|  |             </p> | ||||||
|  |             <p> | ||||||
|  |               <b>Multiple Search terms:</b> Additional terms separated by <code>|</code>, | ||||||
|  |               for example <code>"test|not"</code> searches for both terms independently. | ||||||
|  |             </p> | ||||||
|  |             <p> | ||||||
|  |               <b>File size:</b> Searching files by size may have significantly longer | ||||||
|  |               search times. | ||||||
|  |             </p> | ||||||
|           </div> |           </div> | ||||||
|           <template> |           <template> | ||||||
|             <ButtonGroup :buttons="folderSelect" @button-clicked="addToTypes" @remove-button-clicked="removeFromTypes" |             <ButtonGroup :buttons="folderSelect" @button-clicked="addToTypes" @remove-button-clicked="removeFromTypes" | ||||||
|  | @ -85,12 +97,12 @@ | ||||||
|             <div class="sizeConstraints"> |             <div class="sizeConstraints"> | ||||||
|               <div class="sizeInputWrapper"> |               <div class="sizeInputWrapper"> | ||||||
|                 <p>Smaller Than:</p> |                 <p>Smaller Than:</p> | ||||||
|                 <input class="sizeInput" v-model="smallerThan" type="text" placeholder="number"> |                 <input class="sizeInput" v-model="smallerThan" type="number" min="0" placeholder="number" /> | ||||||
|                 <p>MB</p> |                 <p>MB</p> | ||||||
|               </div> |               </div> | ||||||
|               <div class="sizeInputWrapper"> |               <div class="sizeInputWrapper"> | ||||||
|                 <p>Larger Than:</p> |                 <p>Larger Than:</p> | ||||||
|                 <input class="sizeInput" v-model="largerThan" type="text" placeholder="number"> |                 <input class="sizeInput" v-model="largerThan" type="number" placeholder="number" /> | ||||||
|                 <p>MB</p> |                 <p>MB</p> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|  | @ -118,7 +130,242 @@ | ||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
| .main-input { | .main-input { | ||||||
|   width: 100% |   width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .searchContext { | ||||||
|  |   width: 100%; | ||||||
|  |   padding: 0.5em 1em; | ||||||
|  |   background: var(--blue); | ||||||
|  |   color: white; | ||||||
|  |   border-left: 1px solid gray; | ||||||
|  |   border-right: 1px solid gray; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #result-desktop>#result-list { | ||||||
|  |   max-height: 80vh; | ||||||
|  |   width: 35em; | ||||||
|  |   overflow: scroll; | ||||||
|  |   padding-bottom: 1em; | ||||||
|  |   -webkit-transition: width 0.3s ease 0s; | ||||||
|  |   transition: width 0.3s ease 0s; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #result-desktop { | ||||||
|  |   -webkit-animation: SlideDown 0.5s forwards; | ||||||
|  |   animation: SlideDown 0.5s forwards; | ||||||
|  |   border-radius: 1m; | ||||||
|  |   border-width: 1px; | ||||||
|  |   border-style: solid; | ||||||
|  |   border-radius: 1em; | ||||||
|  |   max-height: 100%; | ||||||
|  |   border-top: none; | ||||||
|  |   border-top-width: initial; | ||||||
|  |   border-top-style: none; | ||||||
|  |   border-top-color: initial; | ||||||
|  |   border-top-left-radius: 0px; | ||||||
|  |   border-top-right-radius: 0px; | ||||||
|  |   -webkit-transform: translateX(-50%); | ||||||
|  |   transform: translateX(-50%); | ||||||
|  |   -webkit-box-shadow: 0px 2em 50px 10px rgba(0, 0, 0, 0.3); | ||||||
|  |   box-shadow: 0px 2em 50px 10px rgba(0, 0, 0, 0.3); | ||||||
|  |   background-color: lightgray; | ||||||
|  |   max-height: 80vh; | ||||||
|  |   overflow: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search.active #result-desktop ul li a { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   padding: .3em 0; | ||||||
|  |   margin-right: .3em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search #result-list.active { | ||||||
|  |   width: 65em !important; | ||||||
|  |   max-width: 85vw !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Animations */ | ||||||
|  | @keyframes SlideDown { | ||||||
|  |   0% { | ||||||
|  |     transform: translateY(-3em); | ||||||
|  |     opacity: 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   100% { | ||||||
|  |     transform: translateY(0); | ||||||
|  |     opacity: 1; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Search */ | ||||||
|  | #search { | ||||||
|  |   z-index:3; | ||||||
|  |   position: fixed; | ||||||
|  |   top: .5em; | ||||||
|  |   min-width: 35em; | ||||||
|  |   left: 50%; | ||||||
|  |   -webkit-transform: translateX(-50%); | ||||||
|  |   transform: translateX(-50%); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search #input { | ||||||
|  |   background-color: rgba(100, 100, 100, 0.2); | ||||||
|  |   display: flex; | ||||||
|  |   height: 100%; | ||||||
|  |   padding: 0em 0.75em; | ||||||
|  |   border-style: solid; | ||||||
|  |   border-radius: 1em; | ||||||
|  |   border-style: unset; | ||||||
|  |   border-width: 1px; | ||||||
|  |   align-items: center; | ||||||
|  |   height: 3em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search input { | ||||||
|  |   border: 0; | ||||||
|  |   background-color: transparent; | ||||||
|  |   padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #result-list p { | ||||||
|  |   margin: 1em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Hiding scrollbar for Chrome, Safari and Opera */ | ||||||
|  | #result-list::-webkit-scrollbar { | ||||||
|  |   display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Hiding scrollbar for IE, Edge and Firefox */ | ||||||
|  | #result-list { | ||||||
|  |   scrollbar-width: none; | ||||||
|  |   /* Firefox */ | ||||||
|  |   -ms-overflow-style: none; | ||||||
|  |   /* IE and Edge */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .text-container { | ||||||
|  |   white-space: nowrap; | ||||||
|  |   overflow: hidden; | ||||||
|  |   text-overflow: ellipsis; | ||||||
|  |   width: 100%; | ||||||
|  |   text-align: left; | ||||||
|  |   direction: rtl; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #search #result { | ||||||
|  |   padding-top: 1em; | ||||||
|  |   overflow: hidden; | ||||||
|  |   background: white; | ||||||
|  |   display: flex; | ||||||
|  |   top: -4em; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: center; | ||||||
|  |   text-align: left; | ||||||
|  |   color: rgba(0, 0, 0, 0.6); | ||||||
|  |   height: 0; | ||||||
|  |   transition: 2s ease height, 2s ease padding; | ||||||
|  |   transition: 2s ease width, 2s ease padding; | ||||||
|  |   z-index: 3; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body.rtl #search #result { | ||||||
|  |   direction: ltr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search #result>div>*:first-child { | ||||||
|  |   margin-top: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body.rtl #search #result { | ||||||
|  |   direction: rtl; | ||||||
|  |   text-align: right; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Search Results */ | ||||||
|  | body.rtl #search #result ul>* { | ||||||
|  |   direction: ltr; | ||||||
|  |   text-align: left; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search ul { | ||||||
|  |   margin-top: 1em; | ||||||
|  |   padding: 0; | ||||||
|  |   list-style: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search li { | ||||||
|  |   margin: 0.5em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search #renew { | ||||||
|  |   width: 100%; | ||||||
|  |   text-align: center; | ||||||
|  |   display: none; | ||||||
|  |   margin: 1em; | ||||||
|  |   max-width: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search.ongoing #renew { | ||||||
|  |   display: block; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search.active #input { | ||||||
|  |   background-color: lightgray; | ||||||
|  |   border-color: black; | ||||||
|  |   border-style: solid; | ||||||
|  |   border-bottom-style: none; | ||||||
|  |   border-bottom-right-radius: 0; | ||||||
|  |   border-bottom-left-radius: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Search Input Placeholder */ | ||||||
|  | #search::-webkit-input-placeholder { | ||||||
|  |   color: rgba(255, 255, 255, 0.5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search:-moz-placeholder { | ||||||
|  |   opacity: 1; | ||||||
|  |   color: rgba(255, 255, 255, 0.5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search::-moz-placeholder { | ||||||
|  |   opacity: 1; | ||||||
|  |   color: rgba(255, 255, 255, 0.5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search:-ms-input-placeholder { | ||||||
|  |   color: rgba(255, 255, 255, 0.5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Search Boxes */ | ||||||
|  | #search .boxes { | ||||||
|  |   margin: 1em; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search .boxes h3 { | ||||||
|  |   margin: 0; | ||||||
|  |   font-weight: 500; | ||||||
|  |   font-size: 1em; | ||||||
|  |   color: #212121; | ||||||
|  |   padding: 0.5em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body.rtl #search .boxes h3 { | ||||||
|  |   text-align: right; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search .boxes p { | ||||||
|  |   margin: 1em 0 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #search .boxes i { | ||||||
|  |   color: #fff !important; | ||||||
|  |   font-size: 3.5em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .mobile-boxes { | .mobile-boxes { | ||||||
|  | @ -131,6 +378,7 @@ | ||||||
|   border-radius: 1em; |   border-radius: 1em; | ||||||
|   text-align: center; |   text-align: center; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| /* Hiding scrollbar for Chrome, Safari and Opera */ | /* Hiding scrollbar for Chrome, Safari and Opera */ | ||||||
| .mobile-boxes::-webkit-scrollbar { | .mobile-boxes::-webkit-scrollbar { | ||||||
|   display: none; |   display: none; | ||||||
|  | @ -138,11 +386,14 @@ | ||||||
| 
 | 
 | ||||||
| /* Hiding scrollbar for IE, Edge and Firefox */ | /* Hiding scrollbar for IE, Edge and Firefox */ | ||||||
| .mobile-boxes { | .mobile-boxes { | ||||||
|   scrollbar-width: none;  /* Firefox */ |   scrollbar-width: none; | ||||||
|   -ms-overflow-style: none;  /* IE and Edge */ |   /* Firefox */ | ||||||
|  |   -ms-overflow-style: none; | ||||||
|  |   /* IE and Edge */ | ||||||
| } | } | ||||||
|  | 
 | ||||||
| .helpText { | .helpText { | ||||||
|   padding: 1em |   padding: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .sizeConstraints { | .sizeConstraints { | ||||||
|  | @ -160,20 +411,23 @@ | ||||||
|   width: 5em; |   width: 5em; | ||||||
|   border-radius: 1em; |   border-radius: 1em; | ||||||
|   padding: 1em; |   padding: 1em; | ||||||
| 
 |   backdrop-filter: invert(.1); | ||||||
|   border: solid !important; |   border: none !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .sizeInputWrapper { | .sizeInputWrapper { | ||||||
|   border-radius: 1em; |   border-radius: 1em; | ||||||
|   margin-left: 0.5em; |   margin-left: 0.5em; | ||||||
|   margin-right: 0.5em; |   margin-right: 0.5em; | ||||||
|   border-style: groove; |   display: -ms-flexbox; | ||||||
|   display: flex; |   display: flex; | ||||||
|   background-color: rgb(245, 245, 245); |   background-color: rgb(245, 245, 245); | ||||||
|   padding: .25em; |   padding: 0.25em; | ||||||
|   height: 3em; |   height: 3em; | ||||||
|  |   -webkit-box-align: center; | ||||||
|  |   -ms-flex-align: center; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|  |   border: 1px solid #ccc | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .helpButton { | .helpButton { | ||||||
|  | @ -244,6 +498,17 @@ export default { | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   watch: { |   watch: { | ||||||
|  |     active(active) { | ||||||
|  |       const resultList = document.getElementById("result-list"); | ||||||
|  |       if (!active) { | ||||||
|  |         resultList.classList.remove("active"); | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  |       setTimeout(() => { | ||||||
|  |         resultList.classList.add("active"); | ||||||
|  |       }, 100); | ||||||
|  | 
 | ||||||
|  |     }, | ||||||
|     show(val, old) { |     show(val, old) { | ||||||
|       this.active = val === "search"; |       this.active = val === "search"; | ||||||
|       if (old === "search" && !this.active) { |       if (old === "search" && !this.active) { | ||||||
|  | @ -298,7 +563,7 @@ export default { | ||||||
|       return this.ongoing; |       return this.ongoing; | ||||||
|     }, |     }, | ||||||
|     searchHelp() { |     searchHelp() { | ||||||
|       return this.showHelp |       return this.showHelp; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|  | @ -320,25 +585,24 @@ export default { | ||||||
|       return path.replace(/\/+$/, "") + "/"; |       return path.replace(/\/+$/, "") + "/"; | ||||||
|     }, |     }, | ||||||
|     basePath(str) { |     basePath(str) { | ||||||
|       if (!str.includes("/")) { |       let parts = str.split("/"); | ||||||
|         return ""; |       if (parts.length <= 2) { | ||||||
|  |         return "/"; | ||||||
|       } |       } | ||||||
|       let parts = str.replace(/(\/$|^\/)/, "").split("/"); //remove first and last slash |  | ||||||
|       parts.pop(); |       parts.pop(); | ||||||
|       parts = parts.join("/") + "/" |       parts = parts.join("/") + "/"; | ||||||
|       if (str.endsWith("/")) { |       if (str.endsWith("/")) { | ||||||
|         parts = "/" + parts // weird rtl bug |         parts = "/" + parts; | ||||||
|       } |       } | ||||||
|       return parts; |       return parts; | ||||||
|     }, |     }, | ||||||
|     baseName(str) { |     baseName(str) { | ||||||
|       let parts = str.replace(/(\/$|^\/)/, "").split("/") |       let parts = str.replace(/(\/$|^\/)/, "").split("/"); | ||||||
|       return parts.pop() |       return parts.pop(); | ||||||
|     }, |     }, | ||||||
|     ...mapMutations(["showHover", "closeHovers", "setReload"]), |     ...mapMutations(["showHover", "closeHovers", "setReload"]), | ||||||
|     open() { |     open() { | ||||||
|       this.showHover("search"); |       this.showHover("search"); | ||||||
|       this.showBoxes = true; |  | ||||||
|     }, |     }, | ||||||
|     close(event) { |     close(event) { | ||||||
|       event.stopPropagation(); |       event.stopPropagation(); | ||||||
|  | @ -353,19 +617,20 @@ export default { | ||||||
|     }, |     }, | ||||||
|     addToTypes(string) { |     addToTypes(string) { | ||||||
|       if (this.searchTypes.includes(string)) { |       if (this.searchTypes.includes(string)) { | ||||||
|         return true |         return true; | ||||||
|       } |       } | ||||||
|       if (string == null || string == "") { |       if (string == null || string == "") { | ||||||
|         return false |         return false; | ||||||
|       } |       } | ||||||
|       this.searchTypes = this.searchTypes + string + " " |       this.searchTypes = this.searchTypes + string + " "; | ||||||
|  | 
 | ||||||
|     }, |     }, | ||||||
|     resetSearchFilters() { |     resetSearchFilters() { | ||||||
|       this.searchTypes = ""; |       this.searchTypes = ""; | ||||||
|     }, |     }, | ||||||
|     removeFromTypes(string) { |     removeFromTypes(string) { | ||||||
|       if (string == null || string == "") { |       if (string == null || string == "") { | ||||||
|         return false |         return false; | ||||||
|       } |       } | ||||||
|       this.searchTypes = this.searchTypes.replace(string + " ", ""); |       this.searchTypes = this.searchTypes.replace(string + " ", ""); | ||||||
|       if (this.isMobile) { |       if (this.isMobile) { | ||||||
|  | @ -384,15 +649,15 @@ export default { | ||||||
|       if (this.value === "" || this.value.length < 3) { |       if (this.value === "" || this.value.length < 3) { | ||||||
|         this.ongoing = false; |         this.ongoing = false; | ||||||
|         this.results = []; |         this.results = []; | ||||||
|         this.noneMessage = "Not enough characters to search (min 3)" |         this.noneMessage = "Not enough characters to search (min 3)"; | ||||||
|         return |         return; | ||||||
|       } |       } | ||||||
|       let searchTypesFull = this.searchTypes |       let searchTypesFull = this.searchTypes; | ||||||
|       if (this.largerThan != "") { |       if (this.largerThan != "") { | ||||||
|         searchTypesFull = searchTypesFull + "type:largerThan=" + this.largerThan + " " |         searchTypesFull = searchTypesFull + "type:largerThan=" + this.largerThan + " "; | ||||||
|       } |       } | ||||||
|       if (this.smallerThan != "") { |       if (this.smallerThan != "") { | ||||||
|         searchTypesFull = searchTypesFull + "type:smallerThan=" + this.smallerThan + " " |         searchTypesFull = searchTypesFull + "type:smallerThan=" + this.smallerThan + " "; | ||||||
|       } |       } | ||||||
|       let path = this.$route.path; |       let path = this.$route.path; | ||||||
|       this.ongoing = true; |       this.ongoing = true; | ||||||
|  | @ -401,14 +666,14 @@ export default { | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|         this.$showError(error); |         this.$showError(error); | ||||||
|       } |       } | ||||||
|       if (this.results.length == 0 && this.ongoing == false) { |  | ||||||
|         this.noneMessage = "No results found in indexed search." |  | ||||||
|       } |  | ||||||
|       this.ongoing = false; |       this.ongoing = false; | ||||||
|  |       if (this.results.length == 0) { | ||||||
|  |         this.noneMessage = "No results found in indexed search."; | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     toggleHelp() { |     toggleHelp() { | ||||||
|       this.showHelp = !this.showHelp |       this.showHelp = !this.showHelp; | ||||||
|     } |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  | @ -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> |  | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
|           <i class="material-icons">note_add</i> |           <i class="material-icons">note_add</i> | ||||||
|           <span>{{ $t("sidebar.newFile") }}</span> |           <span>{{ $t("sidebar.newFile") }}</span> | ||||||
|         </button> |         </button> | ||||||
|         <button id="upload-button" @click="upload($event)" class="action" :aria-label="$t('sidebar.upload')" > |         <button id="upload-button" @click="upload($event)" class="action" > | ||||||
|           <i class="material-icons">file_upload</i> |           <i class="material-icons">file_upload</i> | ||||||
|           <span>Upload file</span> |           <span>Upload file</span> | ||||||
|         </button> |         </button> | ||||||
|  |  | ||||||
|  | @ -1,24 +1,12 @@ | ||||||
| <template> | <template> | ||||||
|   <header> |   <header> | ||||||
|     <action |  | ||||||
|       class="menu-button" |  | ||||||
|       icon="menu" |  | ||||||
|       :label="$t('buttons.toggleSidebar')" |  | ||||||
|       @action="toggleSidebar()" |  | ||||||
|     /> |  | ||||||
| 
 |  | ||||||
|     <slot /> |     <slot /> | ||||||
| 
 |  | ||||||
|     <div id="dropdown" :class="{ active: this.$store.state.show === 'more' }"> |  | ||||||
|       <slot name="actions" /> |  | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|   </header> |   </header> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import { logoURL } from "@/utils/constants"; | import { logoURL } from "@/utils/constants"; | ||||||
| import Action from "@/components/header/Action"; | import Action from "@/components/header/Action.vue"; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   name: "header-bar", |   name: "header-bar", | ||||||
|  |  | ||||||
|  | @ -4,8 +4,19 @@ body { | ||||||
|   padding-top: 4em; |   padding-top: 4em; | ||||||
|   background-color: #fafafa; |   background-color: #fafafa; | ||||||
|   color: #333333; |   color: #333333; | ||||||
|  |   overflow:auto; | ||||||
|  |   overflow:initial; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | body::-webkit-scrollbar { | ||||||
|  |   z-index: 1000; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* Hiding scrollbar for IE, Edge and Firefox */ | ||||||
|  | body { | ||||||
|  |   scrollbar-width: none; /* Firefox */ | ||||||
|  |   -ms-overflow-style: none; /* IE and Edge */ | ||||||
|  | } | ||||||
| body.rtl { | body.rtl { | ||||||
|   direction: rtl; |   direction: rtl; | ||||||
| } | } | ||||||
|  | @ -63,7 +74,7 @@ nav { | ||||||
|   top: 0; |   top: 0; | ||||||
|   padding-top: 4em; |   padding-top: 4em; | ||||||
|   left: -19em; |   left: -19em; | ||||||
|   z-index: 10000; |   z-index: 4; | ||||||
|   background: #fff; |   background: #fff; | ||||||
|   height: 100%; |   height: 100%; | ||||||
|   box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); |   box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); | ||||||
|  | @ -110,7 +121,18 @@ nav .action > * { | ||||||
| 
 | 
 | ||||||
| /* Main Content */ | /* Main Content */ | ||||||
| main { | main { | ||||||
|   margin: 1em; |   position: fixed; | ||||||
|  |   padding: 1em; | ||||||
|  |   padding-top: 4em; | ||||||
|  |   overflow: scroll; | ||||||
|  |   top: 0; | ||||||
|  |   height: 100%; | ||||||
|  |   width: 100%; | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  | } | ||||||
|  | main > div { | ||||||
|  |   height: calc(100% - 3em); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .breadcrumbs { | .breadcrumbs { | ||||||
|  | @ -165,28 +187,7 @@ body.rtl .breadcrumbs a { | ||||||
|   transition: 0.2s ease width; |   transition: 0.2s ease width; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Animations */ |  | ||||||
| @keyframes flyInFromTop { |  | ||||||
|   0% { |  | ||||||
|     transform: translateY(100%); |  | ||||||
|     opacity: 0; |  | ||||||
|   } |  | ||||||
|   100% { |  | ||||||
|     transform: translateY(0); |  | ||||||
|     opacity: 1; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| #search.active #result-desktop ul li a { |  | ||||||
|   display: flex; |  | ||||||
|   align-items: center; |  | ||||||
|   padding: .3em 0; |  | ||||||
|   margin-right: .3em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #result-desktop { |  | ||||||
|   animation: flyInFromTop 0.5s forwards; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| /* File Selection */ | /* File Selection */ | ||||||
| #file-selection { | #file-selection { | ||||||
|  |  | ||||||
|  | @ -302,7 +302,7 @@ body.rtl .card .card-title>*:first-child { | ||||||
|   left: 0; |   left: 0; | ||||||
|   height: 100%; |   height: 100%; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   z-index: 9999; |   z-index: 3; | ||||||
|   animation: .3s show ease-in; |   animation: .3s show ease-in; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,7 @@ | ||||||
| /* Header */ | /* Header */ | ||||||
| header { | header { | ||||||
|   z-index: 1000; |   z-index: 5; | ||||||
|   position: fixed; |   position: fixed; | ||||||
|   z-index: 10000; |  | ||||||
|   top: 0; |   top: 0; | ||||||
|   left: 0; |   left: 0; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|  | @ -54,139 +53,6 @@ header .action span { | ||||||
|   display: none; |   display: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| header>div div { |  | ||||||
|   vertical-align: middle; |  | ||||||
|   position: relative; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Search */ |  | ||||||
| #search { |  | ||||||
|   position: absolute; |  | ||||||
|   height: 3em; |  | ||||||
|   width: 50%; |  | ||||||
|   max-width: 50em; |  | ||||||
|   left: 50%; |  | ||||||
|   transform: translate(-50%, 0%); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search #input { |  | ||||||
|   background-color: rgba(100, 100, 100, 0.2); |  | ||||||
|   display: flex; |  | ||||||
|   height: 100%; |  | ||||||
|   padding: 0em 0.75em; |  | ||||||
|   border-radius: 0.3em; |  | ||||||
|   transition: .1s ease all; |  | ||||||
|   align-items: center; |  | ||||||
|   z-index: 2; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search input { |  | ||||||
|   border: 0; |  | ||||||
|   background-color: transparent; |  | ||||||
|   padding: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #result-list p { |  | ||||||
|   margin: 1em; |  | ||||||
| } |  | ||||||
| #result-list { |  | ||||||
|   width: 60em; |  | ||||||
|   max-width: 100%; |  | ||||||
|   overflow-x: hidden; |  | ||||||
|   overflow-y: auto; |  | ||||||
|   border-color: gray; |  | ||||||
| } |  | ||||||
| /* Hiding scrollbar for Chrome, Safari and Opera */ |  | ||||||
| #result-list::-webkit-scrollbar { |  | ||||||
|   display: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Hiding scrollbar for IE, Edge and Firefox */ |  | ||||||
| #result-list { |  | ||||||
| scrollbar-width: none;  /* Firefox */ |  | ||||||
| -ms-overflow-style: none;  /* IE and Edge */ |  | ||||||
| } |  | ||||||
| @media (min-width: 800px) { |  | ||||||
|   #result-list { |  | ||||||
|     padding-top: 0; |  | ||||||
|     border-radius: .5em; |  | ||||||
|     border-width: 2px; |  | ||||||
|     border-style: solid; |  | ||||||
|     background-color: white; |  | ||||||
|     margin-top: 1em; |  | ||||||
|     max-height: 80vh; |  | ||||||
|     left: 50%; |  | ||||||
|     max-width: 90vw; |  | ||||||
|     transform: translateX(-50%); |  | ||||||
|     box-shadow: 0px 2em 50px 10px rgba(0, 0, 0, 0.3) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .text-container { |  | ||||||
|   white-space: nowrap; |  | ||||||
|   overflow: hidden; |  | ||||||
|   text-overflow: ellipsis; |  | ||||||
|   width: 100%; |  | ||||||
|   text-align: left; |  | ||||||
|   direction: rtl; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search #result { |  | ||||||
|   padding-top: 1em; |  | ||||||
|   overflow: hidden; |  | ||||||
|   background: white; |  | ||||||
|   display: flex; |  | ||||||
|   top: -4em; |  | ||||||
|   flex-direction: column; |  | ||||||
|   align-items: center; |  | ||||||
|   text-align: left; |  | ||||||
|   color: rgba(0, 0, 0, 0.6); |  | ||||||
|   height: 0; |  | ||||||
|   transition: .2s ease height, .2s ease padding; |  | ||||||
|   z-index: 1; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| body.rtl #search #result { |  | ||||||
|   direction: ltr; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search #result>div>*:first-child { |  | ||||||
|   margin-top: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| body.rtl #search #result { |  | ||||||
|   direction: rtl; |  | ||||||
|   text-align: right; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Search Results */ |  | ||||||
| body.rtl #search #result ul>* { |  | ||||||
|   direction: ltr; |  | ||||||
|   text-align: left; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search ul { |  | ||||||
|   margin-top: 1em; |  | ||||||
|   padding: 0; |  | ||||||
|   list-style: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search li { |  | ||||||
|   margin: .5em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search #renew { |  | ||||||
|   width: 100%; |  | ||||||
|   text-align: center; |  | ||||||
|   display: none; |  | ||||||
|   margin: 1em; |  | ||||||
|   max-width: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search.ongoing #renew { |  | ||||||
|   display: block; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Icon Colors */ | /* Icon Colors */ | ||||||
| .folder-icons { | .folder-icons { | ||||||
|   color: var(--icon-blue); |   color: var(--icon-blue); | ||||||
|  | @ -208,49 +74,3 @@ body.rtl #search #result ul>* { | ||||||
|   color: plum; |   color: plum; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Search Input Placeholder */ |  | ||||||
| #search::-webkit-input-placeholder { |  | ||||||
|   color: rgba(255, 255, 255, .5); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search:-moz-placeholder { |  | ||||||
|   opacity: 1; |  | ||||||
|   color: rgba(255, 255, 255, .5); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search::-moz-placeholder { |  | ||||||
|   opacity: 1; |  | ||||||
|   color: rgba(255, 255, 255, .5); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search:-ms-input-placeholder { |  | ||||||
|   color: rgba(255, 255, 255, .5); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Search Boxes */ |  | ||||||
| #search .boxes { |  | ||||||
|   margin: 1em; |  | ||||||
|   text-align: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search .boxes h3 { |  | ||||||
|   margin: 0; |  | ||||||
|   font-weight: 500; |  | ||||||
|   font-size: 1em; |  | ||||||
|   color: #212121; |  | ||||||
|   padding: .5em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| body.rtl #search .boxes h3 { |  | ||||||
|   text-align: right; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #search .boxes p { |  | ||||||
|   margin: 1em 0 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #search .boxes i { |  | ||||||
|   color: #fff !important; |  | ||||||
|   font-size: 3.5em; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -52,21 +52,22 @@ | ||||||
|   body.rtl .dashboard .row { |   body.rtl .dashboard .row { | ||||||
|     margin-right: unset; |     margin-right: unset; | ||||||
|   } |   } | ||||||
|  |   #search { | ||||||
|  |     min-width: unset; | ||||||
|  |     max-width: 60%; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   #search.active { |   #search.active { | ||||||
|     display: block; |     display: block; | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   #search.active { |  | ||||||
|     position: fixed; |     position: fixed; | ||||||
|     top: 0; |     top: 0; | ||||||
|     right: 0; |     left: 50%; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     max-width: 100%; |     max-width: 100%; | ||||||
|     height: 100%; |  | ||||||
|     z-index: 9999; |  | ||||||
|   } |   } | ||||||
| 
 |   #search #input { | ||||||
|  |     transition: 1s ease all; | ||||||
|  |   } | ||||||
|   #search.active #input { |   #search.active #input { | ||||||
|     border-bottom: 3px solid rgba(0, 0, 0, 0.075); |     border-bottom: 3px solid rgba(0, 0, 0, 0.075); | ||||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |     box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); | ||||||
|  | @ -79,7 +80,8 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   #search.active #result { |   #search.active #result { | ||||||
|     height: 100vh |     height: 100vh; | ||||||
|  |     padding-top: 0; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   #search.active #result>p>i { |   #search.active #result>p>i { | ||||||
|  | @ -102,7 +104,12 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   #result-list { |   #result-list { | ||||||
|     padding-top: 3em; |     width:100%; | ||||||
|  |     left: 0; | ||||||
|  |     top: 4em; | ||||||
|  |     -webkit-box-direction: normal; | ||||||
|  |     -ms-flex-direction: column; | ||||||
|  |     overflow: scroll; | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ | ||||||
| @import "./_variables.css"; | @import "./_variables.css"; | ||||||
| @import "./_buttons.css"; | @import "./_buttons.css"; | ||||||
| @import "./_inputs.css"; | @import "./_inputs.css"; | ||||||
| @import "./_shell.css"; |  | ||||||
| @import "./_share.css"; | @import "./_share.css"; | ||||||
| @import "./fonts.css"; | @import "./fonts.css"; | ||||||
| @import "./base.css"; | @import "./base.css"; | ||||||
|  | @ -281,15 +280,11 @@ main .spinner .bounce2 { | ||||||
| #editor-container { | #editor-container { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   background-color: #fafafa; |   background-color: none; | ||||||
|   position: fixed; |  | ||||||
|   padding-top: 4em; |  | ||||||
|   top: 0; |  | ||||||
|   left: 0; |  | ||||||
|   height: 100%; |   height: 100%; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   z-index: 9999; |  | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
|  |   position: relative; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #editor-container #editor { | #editor-container #editor { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <header-bar v-if="showHeader" showMenu showLogo /> |  | ||||||
| 
 |  | ||||||
|     <h2 class="message"> |     <h2 class="message"> | ||||||
|       <i class="material-icons">{{ info.icon }}</i> |       <i class="material-icons">{{ info.icon }}</i> | ||||||
|       <span>{{ $t(info.message) }}</span> |       <span>{{ $t(info.message) }}</span> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <header-bar v-if="error || req.type == null" showMenu showLogo /> |  | ||||||
| 
 |  | ||||||
|     <breadcrumbs base="/files" /> |     <breadcrumbs base="/files" /> | ||||||
| 
 | 
 | ||||||
|     <errors v-if="error" :errorCode="error.status" /> |     <errors v-if="error" :errorCode="error.status" /> | ||||||
|  | @ -91,8 +89,12 @@ export default { | ||||||
|     } |     } | ||||||
|     this.$store.commit("updateRequest", {}); |     this.$store.commit("updateRequest", {}); | ||||||
|   }, |   }, | ||||||
|  |   currentView(newView) { | ||||||
|  |     // Commit the new value to the store | ||||||
|  |     this.setCurrentValue(this.newValue); | ||||||
|  |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     ...mapMutations(["setLoading"]), |     ...mapMutations(["setLoading","setCurrentView"]), | ||||||
|     async fetchData() { |     async fetchData() { | ||||||
|       // Reset view information. |       // Reset view information. | ||||||
|       this.$store.commit("setReload", false); |       this.$store.commit("setReload", false); | ||||||
|  |  | ||||||
|  | @ -3,10 +3,12 @@ | ||||||
|     <div v-if="progress" class="progress"> |     <div v-if="progress" class="progress"> | ||||||
|       <div v-bind:style="{ width: this.progress + '%' }"></div> |       <div v-bind:style="{ width: this.progress + '%' }"></div> | ||||||
|     </div> |     </div> | ||||||
|  |     <listingBar v-if="currentView === 'listing'"></listingBar> | ||||||
|  |     <editorBar v-else-if="currentView === 'editor'"></editorBar> | ||||||
|  |     <defaultBar v-else></defaultBar> | ||||||
|     <sidebar></sidebar> |     <sidebar></sidebar> | ||||||
|     <main> |     <main> | ||||||
|       <router-view></router-view> |       <router-view></router-view> | ||||||
|       <shell v-if="isExecEnabled && isLogged && user.perm.execute" /> |  | ||||||
|     </main> |     </main> | ||||||
|     <prompts></prompts> |     <prompts></prompts> | ||||||
|     <upload-files></upload-files> |     <upload-files></upload-files> | ||||||
|  | @ -14,32 +16,70 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import { mapState, mapGetters } from "vuex"; | import editorBar from "./bars/EditorBar.vue" | ||||||
| import Sidebar from "@/components/Sidebar"; | import defaultBar from "./bars/Default.vue" | ||||||
|  | import listingBar from "./bars/ListingBar.vue" | ||||||
| import Prompts from "@/components/prompts/Prompts"; | import Prompts from "@/components/prompts/Prompts"; | ||||||
| import Shell from "@/components/Shell"; | import Action from "@/components/header/Action"; | ||||||
|  | import { mapState, mapGetters } from "vuex"; | ||||||
|  | import Sidebar from "@/components/Sidebar.vue"; | ||||||
| import UploadFiles from "../components/prompts/UploadFiles"; | import UploadFiles from "../components/prompts/UploadFiles"; | ||||||
| import { enableExec } from "@/utils/constants"; | import { enableExec } from "@/utils/constants"; | ||||||
| 
 |  | ||||||
| export default { | export default { | ||||||
|   name: "layout", |   name: "layout", | ||||||
|   components: { |   components: { | ||||||
|  |     defaultBar, | ||||||
|  |     editorBar, | ||||||
|  |     listingBar, | ||||||
|  |     Action, | ||||||
|     Sidebar, |     Sidebar, | ||||||
|     Prompts, |     Prompts, | ||||||
|     Shell, |  | ||||||
|     UploadFiles, |     UploadFiles, | ||||||
|   }, |   }, | ||||||
|  |   data: function () { | ||||||
|  |     return { | ||||||
|  |       showContexts: true, | ||||||
|  |       dragCounter: 0, | ||||||
|  |       width: window.innerWidth, | ||||||
|  |       itemWeight: 0, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     ...mapGetters(["isLogged", "progress"]), |     ...mapGetters(["isLogged", "progress", "isListing"]), | ||||||
|     ...mapState(["user"]), |     ...mapState(["req", "user", "state"]), | ||||||
|  | 
 | ||||||
|     isExecEnabled: () => enableExec, |     isExecEnabled: () => enableExec, | ||||||
|  |     currentView() { | ||||||
|  |       if (this.req.type == undefined) { | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (this.req.isDir) { | ||||||
|  |         return "listing"; | ||||||
|  |       } else if ( | ||||||
|  |         this.req.type === "text" || | ||||||
|  |         this.req.type === "textImmutable" | ||||||
|  |       ) { | ||||||
|  |         return "editor"; | ||||||
|  |       } else { | ||||||
|  |         return "preview"; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   watch: { |   watch: { | ||||||
|     $route: function () { |     $route: function () { | ||||||
|       this.$store.commit("resetSelected"); |       this.$store.commit("resetSelected"); | ||||||
|       this.$store.commit("multiple", false); |       this.$store.commit("multiple", false); | ||||||
|       if (this.$store.state.show !== "success") |       if (this.$store.state.show !== "success") this.$store.commit("closeHovers"); | ||||||
|         this.$store.commit("closeHovers"); |     }, | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     getTitle() { | ||||||
|  |       let title = "Title" | ||||||
|  |       if (this.$route.path.startsWith('/settings/')) { | ||||||
|  |         title = "Settings" | ||||||
|  |       } | ||||||
|  |       return title | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| <template> | <template> | ||||||
|   <div class="dashboard"> |   <div class="dashboard"> | ||||||
|     <header-bar showMenu showLogo /> |  | ||||||
| 
 |  | ||||||
|     <div id="nav"> |     <div id="nav"> | ||||||
|       <div class="wrapper"> |       <div class="wrapper"> | ||||||
|         <ul> |         <ul> | ||||||
|  | @ -52,15 +50,14 @@ | ||||||
| <script> | <script> | ||||||
| import { mapState } from "vuex"; | import { mapState } from "vuex"; | ||||||
| 
 | 
 | ||||||
| import HeaderBar from "@/components/header/HeaderBar"; |  | ||||||
| 
 |  | ||||||
| export default { | export default { | ||||||
|   name: "settings", |   name: "settings", | ||||||
|   components: { |   mounted() { | ||||||
|     HeaderBar, |     // Update the req name property | ||||||
|  |     this.$store.commit("updateRequest", { name: "Settings" }); | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     ...mapState(["user", "loading"]), |     ...mapState(["user", "loading","req"]), | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -1,30 +1,5 @@ | ||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <header-bar showMenu showLogo> |  | ||||||
|       <title /> |  | ||||||
| 
 |  | ||||||
|       <action |  | ||||||
|         v-if="selectedCount" |  | ||||||
|         icon="file_download" |  | ||||||
|         :label="$t('buttons.download')" |  | ||||||
|         @action="download" |  | ||||||
|         :counter="selectedCount" |  | ||||||
|       /> |  | ||||||
|       <button |  | ||||||
|         v-if="isSingleFile()" |  | ||||||
|         class="action copy-clipboard" |  | ||||||
|         :data-clipboard-text="linkSelected()" |  | ||||||
|         :aria-label="$t('buttons.copyDownloadLinkToClipboard')" |  | ||||||
|         :title="$t('buttons.copyDownloadLinkToClipboard')" |  | ||||||
|       > |  | ||||||
|         <i class="material-icons">content_paste</i> |  | ||||||
|       </button> |  | ||||||
|       <action |  | ||||||
|         icon="check_circle" |  | ||||||
|         :label="$t('buttons.selectMultiple')" |  | ||||||
|         @action="toggleMultipleSelection" |  | ||||||
|       /> |  | ||||||
|     </header-bar> |  | ||||||
| 
 | 
 | ||||||
|     <breadcrumbs :base="'/share/' + hash" /> |     <breadcrumbs :base="'/share/' + hash" /> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,634 @@ | ||||||
|  | <template> | ||||||
|  |   <header-bar> | ||||||
|  |     <action icon="close" :label="$t('buttons.close')" @action="close()" /> | ||||||
|  |     <title class="topTitle">{{ req.name }}</title> | ||||||
|  | 
 | ||||||
|  |   </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 url from "@/utils/url"; | ||||||
|  | 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"]), | ||||||
|  |     isSettings() { | ||||||
|  |       return this.$route.path.includes("/settings/") | ||||||
|  |     }, | ||||||
|  |     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); | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|  |     close() { | ||||||
|  |       if (this.isSettings) { // Use this.isSettings to access the computed property | ||||||
|  |         this.$router.push({ path: "/files/" }, () => { }); | ||||||
|  |         this.$store.commit("closeHovers"); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       this.$store.commit("updateRequest", {}); | ||||||
|  |       let uri = url.removeLastDir(this.$route.path) + "/"; | ||||||
|  |       console.log(url) | ||||||
|  |       this.$router.push({ path: uri }); | ||||||
|  |     }, | ||||||
|  |     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> | ||||||
|  | @ -0,0 +1,141 @@ | ||||||
|  | <template> | ||||||
|  |   <header-bar> | ||||||
|  |     <action icon="close" :label="$t('buttons.close')" @action="close()" /> | ||||||
|  |     <title class="topTitle">{{ req.name }}</title> | ||||||
|  | 
 | ||||||
|  |     <action v-if="user.perm.modify" id="save-button" icon="save" :label="$t('buttons.save')" | ||||||
|  |       @action="save()" /> | ||||||
|  |   </header-bar> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  | .flexbar { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: block; | ||||||
|  |   justify-content: space-between; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .topTitle { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import { mapState } from "vuex"; | ||||||
|  | import { files as api } from "@/api"; | ||||||
|  | import { theme } from "@/utils/constants"; | ||||||
|  | import buttons from "@/utils/buttons"; | ||||||
|  | import url from "@/utils/url"; | ||||||
|  | 
 | ||||||
|  | import ace from "ace-builds/src-min-noconflict/ace.js"; | ||||||
|  | import modelist from "ace-builds/src-min-noconflict/ext-modelist.js"; | ||||||
|  | import "ace-builds/webpack-resolver"; | ||||||
|  | 
 | ||||||
|  | import HeaderBar from "@/components/header/HeaderBar"; | ||||||
|  | import Action from "@/components/header/Action"; | ||||||
|  | import Breadcrumbs from "@/components/Breadcrumbs"; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   name: "editor", | ||||||
|  |   components: { | ||||||
|  |     HeaderBar, | ||||||
|  |     Action, | ||||||
|  |     Breadcrumbs, | ||||||
|  |   }, | ||||||
|  |   data: function () { | ||||||
|  |     return {}; | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     ...mapState(["req", "user", "currentView"]), | ||||||
|  |     breadcrumbs() { | ||||||
|  |       let parts = this.$route.path.split("/"); | ||||||
|  | 
 | ||||||
|  |       if (parts[0] === "") { | ||||||
|  |         parts.shift(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (parts[parts.length - 1] === "") { | ||||||
|  |         parts.pop(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       let breadcrumbs = []; | ||||||
|  | 
 | ||||||
|  |       for (let i = 0; i < parts.length; i++) { | ||||||
|  |         breadcrumbs.push({ name: decodeURIComponent(parts[i]) }); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       breadcrumbs.shift(); | ||||||
|  | 
 | ||||||
|  |       if (breadcrumbs.length > 3) { | ||||||
|  |         while (breadcrumbs.length !== 4) { | ||||||
|  |           breadcrumbs.shift(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         breadcrumbs[0].name = "..."; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return breadcrumbs; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   created() { | ||||||
|  |     window.addEventListener("keydown", this.keyEvent); | ||||||
|  |   }, | ||||||
|  |   beforeDestroy() { | ||||||
|  |     window.removeEventListener("keydown", this.keyEvent); | ||||||
|  |     this.editor.destroy(); | ||||||
|  |   }, | ||||||
|  |   mounted: function () { | ||||||
|  |     const fileContent = this.req.content || ""; | ||||||
|  | 
 | ||||||
|  |     this.editor = ace.edit("editor", { | ||||||
|  |       value: fileContent, | ||||||
|  |       showPrintMargin: false, | ||||||
|  |       readOnly: this.req.type === "textImmutable", | ||||||
|  |       theme: "ace/theme/chrome", | ||||||
|  |       mode: modelist.getModeForPath(this.req.name).mode, | ||||||
|  |       wrap: true, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if (theme == "dark") { | ||||||
|  |       this.editor.setTheme("ace/theme/twilight"); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     back() { | ||||||
|  |       let uri = url.removeLastDir(this.$route.path) + "/"; | ||||||
|  |       this.$router.push({ path: uri }); | ||||||
|  |     }, | ||||||
|  |     keyEvent(event) { | ||||||
|  |       if (!event.ctrlKey && !event.metaKey) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (String.fromCharCode(event.which).toLowerCase() !== "s") { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       event.preventDefault(); | ||||||
|  |       this.save(); | ||||||
|  |     }, | ||||||
|  |     async save() { | ||||||
|  |       const button = "save"; | ||||||
|  |       buttons.loading("save"); | ||||||
|  | 
 | ||||||
|  |       try { | ||||||
|  |         await api.put(this.$route.path, this.editor.getValue()); | ||||||
|  |         buttons.success(button); | ||||||
|  |       } catch (e) { | ||||||
|  |         buttons.done(button); | ||||||
|  |         this.$showError(e); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     close() { | ||||||
|  |       this.$store.commit("updateRequest", {}); | ||||||
|  |       let uri = url.removeLastDir(this.$route.path) + "/"; | ||||||
|  |       this.$router.push({ path: uri }); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </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 HeaderBar from "@/components/header/HeaderBar.vue"; | ||||||
|  | import Action from "@/components/header/Action.vue"; | ||||||
|  | import url from "@/utils/url"; | ||||||
|  | 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> | ||||||
|  | @ -1,20 +1,5 @@ | ||||||
| <template> | <template> | ||||||
|     <div id="editor-container"> |     <div id="editor-container"> | ||||||
|     <header-bar> |  | ||||||
|       <action icon="close" :label="$t('buttons.close')" @action="close()" /> |  | ||||||
|       <title>{{ req.name }}</title> |  | ||||||
| 
 |  | ||||||
|       <action |  | ||||||
|         v-if="user.perm.modify" |  | ||||||
|         id="save-button" |  | ||||||
|         icon="save" |  | ||||||
|         :label="$t('buttons.save')" |  | ||||||
|         @action="save()" |  | ||||||
|       /> |  | ||||||
|     </header-bar> |  | ||||||
| 
 |  | ||||||
|     <breadcrumbs base="/files" noLink /> |  | ||||||
| 
 |  | ||||||
|     <form id="editor"></form> |     <form id="editor"></form> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  | @ -45,7 +30,7 @@ export default { | ||||||
|     return {}; |     return {}; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     ...mapState(["req", "user"]), |     ...mapState(["req", "user","currentView"]), | ||||||
|     breadcrumbs() { |     breadcrumbs() { | ||||||
|       let parts = this.$route.path.split("/"); |       let parts = this.$route.path.split("/"); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,14 +1,7 @@ | ||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <header-bar showMenu showLogo> |     <div v-if="selectedCount > 0" id="file-selection"> | ||||||
|       <search /> |       <span >{{ selectedCount }} selected</span> | ||||||
|       <template #actions> |  | ||||||
|         <action :icon="viewIcon" :label="$t('buttons.switchView')" @action="switchView" /> |  | ||||||
|       </template> |  | ||||||
|     </header-bar> |  | ||||||
| 
 |  | ||||||
|     <div id="file-selection"> |  | ||||||
|       <span v-if="selectedCount > 0">{{ selectedCount }} selected</span> |  | ||||||
|       <template> |       <template> | ||||||
|         <action |         <action | ||||||
|           v-if="headerButtons.select" |           v-if="headerButtons.select" | ||||||
|  | @ -217,17 +210,13 @@ import * as upload from "@/utils/upload"; | ||||||
| import css from "@/utils/css"; | import css from "@/utils/css"; | ||||||
| import throttle from "lodash.throttle"; | import throttle from "lodash.throttle"; | ||||||
| 
 | 
 | ||||||
| import HeaderBar from "@/components/header/HeaderBar"; |  | ||||||
| import Action from "@/components/header/Action"; | import Action from "@/components/header/Action"; | ||||||
| import Search from "@/components/Search"; |  | ||||||
| import Item from "@/components/files/ListingItem"; | import Item from "@/components/files/ListingItem"; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   name: "listing", |   name: "listing", | ||||||
|   components: { |   components: { | ||||||
|     HeaderBar, |  | ||||||
|     Action, |     Action, | ||||||
|     Search, |  | ||||||
|     Item, |     Item, | ||||||
|   }, |   }, | ||||||
|   data: function () { |   data: function () { | ||||||
|  | @ -312,7 +301,6 @@ export default { | ||||||
|         select: this.selectedCount > 0, |         select: this.selectedCount > 0, | ||||||
|         upload: this.user.perm.create && this.selectedCount > 0, |         upload: this.user.perm.create && this.selectedCount > 0, | ||||||
|         download: this.user.perm.download && this.selectedCount > 0, |         download: this.user.perm.download && this.selectedCount > 0, | ||||||
|         shell: this.user.perm.execute && enableExec, |  | ||||||
|         delete: this.selectedCount > 0 && this.user.perm.delete, |         delete: this.selectedCount > 0 && this.user.perm.delete, | ||||||
|         rename: this.selectedCount === 1 && this.user.perm.rename, |         rename: this.selectedCount === 1 && this.user.perm.rename, | ||||||
|         share: this.selectedCount === 1 && this.user.perm.share, |         share: this.selectedCount === 1 && this.user.perm.share, | ||||||
|  | @ -740,27 +728,7 @@ export default { | ||||||
|         }, |         }, | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|     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); |  | ||||||
| 
 |  | ||||||
|       // Await ensures correct value for setItemWeight() |  | ||||||
|       await this.$store.commit("updateUser", data); |  | ||||||
| 
 |  | ||||||
|       this.setItemWeight(); |  | ||||||
|       this.fillWindow(); |  | ||||||
|     }, |  | ||||||
|     upload: function () { |     upload: function () { | ||||||
|       if ( |       if ( | ||||||
|         typeof window.DataTransferItem !== "undefined" && |         typeof window.DataTransferItem !== "undefined" && | ||||||
|  |  | ||||||
|  | @ -4,48 +4,6 @@ | ||||||
|     @mousemove="toggleNavigation" |     @mousemove="toggleNavigation" | ||||||
|     @touchstart="toggleNavigation" |     @touchstart="toggleNavigation" | ||||||
|   > |   > | ||||||
|     <header-bar> |  | ||||||
|       <action icon="close" :label="$t('buttons.close')" @action="close()" /> |  | ||||||
|       <title>{{ name }}</title> |  | ||||||
|       <action |  | ||||||
|         :disabled="loading" |  | ||||||
|         v-if="isResizeEnabled && req.type === 'image'" |  | ||||||
|         :icon="fullSize ? 'photo_size_select_large' : 'hd'" |  | ||||||
|         @action="toggleSize" |  | ||||||
|       /> |  | ||||||
| 
 |  | ||||||
|       <template #actions> |  | ||||||
|         <action |  | ||||||
|           :disabled="loading" |  | ||||||
|           v-if="user.perm.rename" |  | ||||||
|           icon="mode_edit" |  | ||||||
|           :label="$t('buttons.rename')" |  | ||||||
|           show="rename" |  | ||||||
|         /> |  | ||||||
|         <action |  | ||||||
|           :disabled="loading" |  | ||||||
|           v-if="user.perm.delete" |  | ||||||
|           icon="delete" |  | ||||||
|           :label="$t('buttons.delete')" |  | ||||||
|           @action="deleteFile" |  | ||||||
|           id="delete-button" |  | ||||||
|         /> |  | ||||||
|         <action |  | ||||||
|           :disabled="loading" |  | ||||||
|           v-if="user.perm.download" |  | ||||||
|           icon="file_download" |  | ||||||
|           :label="$t('buttons.download')" |  | ||||||
|           @action="download" |  | ||||||
|         /> |  | ||||||
|         <action |  | ||||||
|           :disabled="loading" |  | ||||||
|           icon="info" |  | ||||||
|           :label="$t('buttons.info')" |  | ||||||
|           show="info" |  | ||||||
|         /> |  | ||||||
|       </template> |  | ||||||
|     </header-bar> |  | ||||||
| 
 |  | ||||||
|     <div class="loading delayed" v-if="loading"> |     <div class="loading delayed" v-if="loading"> | ||||||
|       <div class="spinner"> |       <div class="spinner"> | ||||||
|         <div class="bounce1"></div> |         <div class="bounce1"></div> | ||||||
|  |  | ||||||
|  | @ -56,7 +56,7 @@ | ||||||
|           <p> |           <p> | ||||||
|             <input |             <input | ||||||
|               type="checkbox" |               type="checkbox" | ||||||
|               v-model="settings.branding.disableExternal" |               v-model="settings.frontend.disableExternal" | ||||||
|               id="branding-links" |               id="branding-links" | ||||||
|             /> |             /> | ||||||
|             {{ $t("settings.disableExternalLinks") }} |             {{ $t("settings.disableExternalLinks") }} | ||||||
|  | @ -65,7 +65,7 @@ | ||||||
|           <p> |           <p> | ||||||
|             <input |             <input | ||||||
|               type="checkbox" |               type="checkbox" | ||||||
|               v-model="settings.branding.disableUsedPercentage" |               v-model="settings.frontend.disableUsedPercentage" | ||||||
|               id="branding-links" |               id="branding-links" | ||||||
|             /> |             /> | ||||||
|             {{ $t("settings.disableUsedDiskPercentage") }} |             {{ $t("settings.disableUsedDiskPercentage") }} | ||||||
|  | @ -75,7 +75,7 @@ | ||||||
|             <label for="theme">{{ $t("settings.themes.title") }}</label> |             <label for="theme">{{ $t("settings.themes.title") }}</label> | ||||||
|             <themes |             <themes | ||||||
|               class="input input--block" |               class="input input--block" | ||||||
|               :theme.sync="settings.branding.theme" |               :theme.sync="settings.frontend.theme" | ||||||
|               id="theme" |               id="theme" | ||||||
|             ></themes> |             ></themes> | ||||||
|           </p> |           </p> | ||||||
|  | @ -85,7 +85,7 @@ | ||||||
|             <input |             <input | ||||||
|               class="input input--block" |               class="input input--block" | ||||||
|               type="text" |               type="text" | ||||||
|               v-model="settings.branding.name" |               v-model="settings.frontend.name" | ||||||
|               id="branding-name" |               id="branding-name" | ||||||
|             /> |             /> | ||||||
|           </p> |           </p> | ||||||
|  | @ -97,7 +97,7 @@ | ||||||
|             <input |             <input | ||||||
|               class="input input--block" |               class="input input--block" | ||||||
|               type="text" |               type="text" | ||||||
|               v-model="settings.branding.files" |               v-model="settings.frontend.files" | ||||||
|               id="branding-files" |               id="branding-files" | ||||||
|             /> |             /> | ||||||
|           </p> |           </p> | ||||||
|  |  | ||||||