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"`
|
||||||
Header string `json:"header"`
|
UserDefaults UserDefaults `json:"userDefaults"`
|
||||||
Method string `json:"method"`
|
}
|
||||||
Command string `json:"command"`
|
|
||||||
Signup bool `json:"signup"`
|
|
||||||
Shell string `json:"shell"`
|
|
||||||
} `json:"auth"`
|
|
||||||
|
|
||||||
Branding Branding `json:"branding"`
|
type Auth struct {
|
||||||
|
Recaptcha Recaptcha `json:"recaptcha"`
|
||||||
|
Header string `json:"header"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Command string `json:"command"`
|
||||||
|
Signup bool `json:"signup"`
|
||||||
|
Shell string `json:"shell"`
|
||||||
|
}
|
||||||
|
|
||||||
UserDefaults UserDefaults `json:"userDefaults"`
|
type Recaptcha struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
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 {
|
||||||
Scope string `json:"scope"`
|
LockPassword bool `json:"lockPassword"`
|
||||||
Locale string `json:"locale"`
|
Scope string `json:"scope"`
|
||||||
ViewMode users.ViewMode `json:"viewMode"`
|
Locale string `json:"locale"`
|
||||||
SingleClick bool `json:"singleClick"`
|
ViewMode string `json:"viewMode"`
|
||||||
Sorting files.Sorting `json:"sorting"`
|
SingleClick bool `json:"singleClick"`
|
||||||
|
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,8 +37,10 @@
|
||||||
</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()">
|
||||||
<template v-if="value.length === 0 && showBoxes ">
|
Reset filters
|
||||||
|
</button>
|
||||||
|
<template v-if="value.length === 0 && showBoxes">
|
||||||
<div class="boxes">
|
<div class="boxes">
|
||||||
<h3>{{ $t("search.types") }}</h3>
|
<h3>{{ $t("search.types") }}</h3>
|
||||||
<div>
|
<div>
|
||||||
|
@ -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,18 +378,22 @@
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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) {
|
||||||
|
@ -373,7 +638,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
folderSelectClicked() {
|
folderSelectClicked() {
|
||||||
this.isTypeSelectDisabled = true; // Disable the other ButtonGroup
|
this.isTypeSelectDisabled = true; // Disable the other ButtonGroup
|
||||||
},
|
},
|
||||||
resetButtonGroups() {
|
resetButtonGroups() {
|
||||||
this.isTypeSelectDisabled = false;
|
this.isTypeSelectDisabled = false;
|
||||||
|
@ -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,33 +16,71 @@
|
||||||
</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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
|
@ -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>
|
||||||
|
|