V0.2.1 - update signup (#47)
Co-authored-by: Graham Steffaniak <graham.steffaniak@autodesk.com>
This commit is contained in:
parent
1429963e76
commit
d53426b580
|
@ -118,7 +118,7 @@ func cleanupHandler(listener net.Listener, c chan os.Signal) { //nolint:interfac
|
||||||
}
|
}
|
||||||
|
|
||||||
func quickSetup(d pythonData) {
|
func quickSetup(d pythonData) {
|
||||||
settings.GlobalConfiguration.Key = generateKey()
|
settings.GlobalConfiguration.Auth.Key = generateKey()
|
||||||
if settings.GlobalConfiguration.Auth.Method == "noauth" {
|
if settings.GlobalConfiguration.Auth.Method == "noauth" {
|
||||||
err := d.store.Auth.Save(&auth.NoAuth{})
|
err := d.store.Auth.Save(&auth.NoAuth{})
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
@ -131,8 +131,8 @@ func quickSetup(d pythonData) {
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
err = d.store.Settings.SaveServer(&settings.GlobalConfiguration.Server)
|
err = d.store.Settings.SaveServer(&settings.GlobalConfiguration.Server)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
username := settings.GlobalConfiguration.AdminUsername
|
username := settings.GlobalConfiguration.Auth.AdminUsername
|
||||||
password := settings.GlobalConfiguration.AdminPassword
|
password := settings.GlobalConfiguration.Auth.AdminPassword
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
server:
|
server:
|
||||||
port: 8080
|
port: 8080
|
||||||
baseURL: "/"
|
baseURL: "/"
|
||||||
|
root: "/Users/steffag/git/go"
|
||||||
auth:
|
auth:
|
||||||
method: password
|
method: password
|
||||||
signup: true
|
signup: false
|
||||||
userDefaults:
|
userDefaults:
|
||||||
darkMode: true
|
darkMode: true
|
||||||
disableSettings: false
|
disableSettings: false
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
|
||||||
func withUser(fn handleFunc) handleFunc {
|
func withUser(fn handleFunc) handleFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
||||||
return d.settings.Key, nil
|
return d.settings.Auth.Key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var tk authToken
|
var tk authToken
|
||||||
|
@ -112,7 +112,7 @@ type signupBody struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
if !d.settings.Signup {
|
if !settings.GlobalConfiguration.Auth.Signup {
|
||||||
return http.StatusMethodNotAllowed, nil
|
return http.StatusMethodNotAllowed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int,
|
||||||
}
|
}
|
||||||
user.Scope = userHome
|
user.Scope = userHome
|
||||||
log.Printf("new user: %s, home dir: [%s].", user.Username, userHome)
|
log.Printf("new user: %s, home dir: [%s].", user.Username, userHome)
|
||||||
|
settings.GlobalConfiguration.UserDefaults.Apply(user)
|
||||||
err = d.store.Users.Save(user)
|
err = d.store.Users.Save(user)
|
||||||
if err == errors.ErrExist {
|
if err == errors.ErrExist {
|
||||||
return http.StatusConflict, err
|
return http.StatusConflict, err
|
||||||
|
@ -168,7 +168,7 @@ func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.Use
|
||||||
}
|
}
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
signed, err := token.SignedString(d.settings.Key)
|
signed, err := token.SignedString(d.settings.Auth.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,11 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
|
||||||
if err := storage.Users.Save(&users.User{Username: "username", Password: "pw"}); err != nil {
|
if err := storage.Users.Save(&users.User{Username: "username", Password: "pw"}); err != nil {
|
||||||
t.Fatalf("failed to save user: %v", err)
|
t.Fatalf("failed to save user: %v", err)
|
||||||
}
|
}
|
||||||
if err := storage.Settings.Save(&settings.Settings{Key: []byte("key")}); err != nil {
|
if err := storage.Settings.Save(&settings.Settings{
|
||||||
|
Auth: settings.Auth{
|
||||||
|
Key: []byte("key"),
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
t.Fatalf("failed to save settings: %v", err)
|
t.Fatalf("failed to save settings: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,9 @@ type settingsData struct {
|
||||||
|
|
||||||
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
data := &settingsData{
|
data := &settingsData{
|
||||||
Signup: d.settings.Signup,
|
Signup: settings.GlobalConfiguration.Auth.Signup,
|
||||||
CreateUserDir: d.settings.CreateUserDir,
|
CreateUserDir: settings.GlobalConfiguration.Server.CreateUserDir,
|
||||||
UserHomeBasePath: d.settings.UserHomeBasePath,
|
UserHomeBasePath: settings.GlobalConfiguration.Server.UserHomeBasePath,
|
||||||
Defaults: d.settings.UserDefaults,
|
Defaults: d.settings.UserDefaults,
|
||||||
Rules: d.settings.Rules,
|
Rules: d.settings.Rules,
|
||||||
Frontend: d.settings.Frontend,
|
Frontend: d.settings.Frontend,
|
||||||
|
@ -41,9 +41,8 @@ var settingsPutHandler = withAdmin(func(w http.ResponseWriter, r *http.Request,
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.settings.Signup = req.Signup
|
d.settings.Server.CreateUserDir = req.CreateUserDir
|
||||||
d.settings.CreateUserDir = req.CreateUserDir
|
d.settings.Server.UserHomeBasePath = req.UserHomeBasePath
|
||||||
d.settings.UserHomeBasePath = req.UserHomeBasePath
|
|
||||||
d.settings.UserDefaults = req.Defaults
|
d.settings.UserDefaults = req.Defaults
|
||||||
d.settings.Rules = req.Rules
|
d.settings.Rules = req.Rules
|
||||||
d.settings.Frontend = req.Frontend
|
d.settings.Frontend = req.Frontend
|
||||||
|
|
|
@ -30,11 +30,12 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
||||||
"Name": d.settings.Frontend.Name,
|
"Name": d.settings.Frontend.Name,
|
||||||
"DisableExternal": d.settings.Frontend.DisableExternal,
|
"DisableExternal": d.settings.Frontend.DisableExternal,
|
||||||
"DisableUsedPercentage": d.settings.Frontend.DisableUsedPercentage,
|
"DisableUsedPercentage": d.settings.Frontend.DisableUsedPercentage,
|
||||||
|
"darkMode": settings.GlobalConfiguration.UserDefaults.DarkMode,
|
||||||
"Color": d.settings.Frontend.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": settings.GlobalConfiguration.Auth.Signup,
|
||||||
"NoAuth": d.settings.Auth.Method == "noauth",
|
"NoAuth": d.settings.Auth.Method == "noauth",
|
||||||
"AuthMethod": d.settings.Auth.Method,
|
"AuthMethod": d.settings.Auth.Method,
|
||||||
"LoginPage": auther.LoginPage(),
|
"LoginPage": auther.LoginPage(),
|
||||||
|
|
|
@ -45,9 +45,6 @@ func loadConfigFile(configFile string) []byte {
|
||||||
|
|
||||||
func setDefaults() Settings {
|
func setDefaults() Settings {
|
||||||
return Settings{
|
return Settings{
|
||||||
Signup: true,
|
|
||||||
AdminUsername: "admin",
|
|
||||||
AdminPassword: "admin",
|
|
||||||
Server: Server{
|
Server: Server{
|
||||||
EnableThumbnails: true,
|
EnableThumbnails: true,
|
||||||
EnableExec: false,
|
EnableExec: false,
|
||||||
|
@ -60,8 +57,10 @@ func setDefaults() Settings {
|
||||||
Root: "/srv",
|
Root: "/srv",
|
||||||
},
|
},
|
||||||
Auth: Auth{
|
Auth: Auth{
|
||||||
Method: "password",
|
AdminUsername: "admin",
|
||||||
Signup: true,
|
AdminPassword: "admin",
|
||||||
|
Method: "password",
|
||||||
|
Signup: true,
|
||||||
Recaptcha: Recaptcha{
|
Recaptcha: Recaptcha{
|
||||||
Host: "",
|
Host: "",
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,13 +21,13 @@ var (
|
||||||
// MakeUserDir makes the user directory according to settings.
|
// MakeUserDir makes the user directory according to settings.
|
||||||
func (s *Settings) MakeUserDir(username, userScope, serverRoot string) (string, error) {
|
func (s *Settings) MakeUserDir(username, userScope, serverRoot string) (string, error) {
|
||||||
userScope = strings.TrimSpace(userScope)
|
userScope = strings.TrimSpace(userScope)
|
||||||
if userScope == "" && s.CreateUserDir {
|
if userScope == "" && s.Server.CreateUserDir {
|
||||||
username = cleanUsername(username)
|
username = cleanUsername(username)
|
||||||
if username == "" || username == "-" || username == "." {
|
if username == "" || username == "-" || username == "." {
|
||||||
log.Printf("create user: invalid user for home dir creation: [%s]", username)
|
log.Printf("create user: invalid user for home dir creation: [%s]", username)
|
||||||
return "", errors.New("invalid user for home dir creation")
|
return "", errors.New("invalid user for home dir creation")
|
||||||
}
|
}
|
||||||
userScope = path.Join(s.UserHomeBasePath, username)
|
userScope = path.Join(s.Server.UserHomeBasePath, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
userScope = path.Join("/", userScope)
|
userScope = path.Join("/", userScope)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
func TestSettings_MakeUserDir(t *testing.T) {
|
func TestSettings_MakeUserDir(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
Key []byte
|
|
||||||
Signup bool
|
Signup bool
|
||||||
CreateUserDir bool
|
CreateUserDir bool
|
||||||
UserHomeBasePath string
|
UserHomeBasePath string
|
||||||
|
@ -40,20 +39,14 @@ func TestSettings_MakeUserDir(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
s := &Settings{
|
s := &Settings{
|
||||||
Key: tt.fields.Key,
|
Commands: tt.fields.Commands,
|
||||||
Signup: tt.fields.Signup,
|
Shell: tt.fields.Shell,
|
||||||
CreateUserDir: tt.fields.CreateUserDir,
|
Rules: tt.fields.Rules,
|
||||||
UserHomeBasePath: tt.fields.UserHomeBasePath,
|
Server: tt.fields.Server,
|
||||||
Commands: tt.fields.Commands,
|
Auth: tt.fields.Auth,
|
||||||
Shell: tt.fields.Shell,
|
Frontend: tt.fields.Frontend,
|
||||||
AdminUsername: tt.fields.AdminUsername,
|
Users: tt.fields.Users,
|
||||||
AdminPassword: tt.fields.AdminPassword,
|
UserDefaults: tt.fields.UserDefaults,
|
||||||
Rules: tt.fields.Rules,
|
|
||||||
Server: tt.fields.Server,
|
|
||||||
Auth: tt.fields.Auth,
|
|
||||||
Frontend: tt.fields.Frontend,
|
|
||||||
Users: tt.fields.Users,
|
|
||||||
UserDefaults: tt.fields.UserDefaults,
|
|
||||||
}
|
}
|
||||||
got, err := s.MakeUserDir(tt.args.username, tt.args.userScope, tt.args.serverRoot)
|
got, err := s.MakeUserDir(tt.args.username, tt.args.userScope, tt.args.serverRoot)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
|
|
|
@ -29,8 +29,8 @@ func (s *Storage) Get() (*Settings, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if set.UserHomeBasePath == "" {
|
if set.Server.UserHomeBasePath == "" {
|
||||||
set.UserHomeBasePath = DefaultUsersHomeBasePath
|
set.Server.UserHomeBasePath = DefaultUsersHomeBasePath
|
||||||
}
|
}
|
||||||
return set, nil
|
return set, nil
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ var defaultEvents = []string{
|
||||||
|
|
||||||
// Save saves the settings for the current instance.
|
// Save saves the settings for the current instance.
|
||||||
func (s *Storage) Save(set *Settings) error {
|
func (s *Storage) Save(set *Settings) error {
|
||||||
if len(set.Key) == 0 {
|
if len(set.Auth.Key) == 0 {
|
||||||
return errors.ErrEmptyKey
|
return errors.ErrEmptyKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,29 +6,26 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Key []byte `json:"key"`
|
Commands map[string][]string `json:"commands"`
|
||||||
Signup bool `json:"signup"`
|
Shell []string `json:"shell"`
|
||||||
CreateUserDir bool `json:"createUserDir"`
|
Rules []rules.Rule `json:"rules"`
|
||||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
Server Server `json:"server"`
|
||||||
Commands map[string][]string `json:"commands"`
|
Auth Auth `json:"auth"`
|
||||||
Shell []string `json:"shell"`
|
Frontend Frontend `json:"frontend"`
|
||||||
AdminUsername string `json:"adminUsername"`
|
Users []UserDefaults `json:"users,omitempty"`
|
||||||
AdminPassword string `json:"adminPassword"`
|
UserDefaults UserDefaults `json:"userDefaults"`
|
||||||
Rules []rules.Rule `json:"rules"`
|
|
||||||
Server Server `json:"server"`
|
|
||||||
Auth Auth `json:"auth"`
|
|
||||||
Frontend Frontend `json:"frontend"`
|
|
||||||
Users []UserDefaults `json:"users,omitempty"`
|
|
||||||
UserDefaults UserDefaults `json:"userDefaults"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
Recaptcha Recaptcha `json:"recaptcha"`
|
Recaptcha Recaptcha `json:"recaptcha"`
|
||||||
Header string `json:"header"`
|
Header string `json:"header"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
Signup bool `json:"signup"`
|
Signup bool `json:"signup"`
|
||||||
Shell string `json:"shell"`
|
Shell string `json:"shell"`
|
||||||
|
AdminUsername string `json:"adminUsername"`
|
||||||
|
AdminPassword string `json:"adminPassword"`
|
||||||
|
Key []byte `json:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Recaptcha struct {
|
type Recaptcha struct {
|
||||||
|
@ -54,6 +51,8 @@ type Server struct {
|
||||||
Log string `json:"log"`
|
Log string `json:"log"`
|
||||||
Database string `json:"database"`
|
Database string `json:"database"`
|
||||||
Root string `json:"root"`
|
Root string `json:"root"`
|
||||||
|
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||||
|
CreateUserDir bool `json:"createUserDir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Frontend struct {
|
type Frontend struct {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
|
@ -74,7 +73,6 @@ func (st usersBackend) Update(user *users.User, fields ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st usersBackend) Save(user *users.User) error {
|
func (st usersBackend) Save(user *users.User) error {
|
||||||
log.Println("userinfo", user.Password)
|
|
||||||
pass, err := users.HashPwd(user.Password)
|
pass, err := users.HashPwd(user.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
package users
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HashPwd hashes a password.
|
// HashPwd hashes a password.
|
||||||
func HashPwd(password string) (string, error) {
|
func HashPwd(password string) (string, error) {
|
||||||
log.Println("hashing password", password)
|
|
||||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
return string(bytes), err
|
return string(bytes), err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,11 @@ This document covers the available configuration options, their defaults, and ho
|
||||||
Here is an expanded config file which includes all possible configurations:
|
Here is an expanded config file which includes all possible configurations:
|
||||||
|
|
||||||
```
|
```
|
||||||
signup: false
|
|
||||||
adminUsername: admin
|
adminUsername: admin
|
||||||
adminPassword: admin
|
adminPassword: admin
|
||||||
server:
|
server:
|
||||||
|
CreateUserDir: false
|
||||||
|
UserHomeBasePath: ""
|
||||||
indexingInterval: 5
|
indexingInterval: 5
|
||||||
numImageProcessors: 4
|
numImageProcessors: 4
|
||||||
socket: ""
|
socket: ""
|
||||||
|
@ -33,6 +34,7 @@ auth:
|
||||||
header: ""
|
header: ""
|
||||||
method: json
|
method: json
|
||||||
command: ""
|
command: ""
|
||||||
|
signup: false
|
||||||
shell: ""
|
shell: ""
|
||||||
frontend:
|
frontend:
|
||||||
name: ""
|
name: ""
|
||||||
|
@ -67,7 +69,6 @@ userDefaults:
|
||||||
Here are the defaults if nothing is set:
|
Here are the defaults if nothing is set:
|
||||||
|
|
||||||
```
|
```
|
||||||
signup: true
|
|
||||||
adminUsername: admin
|
adminUsername: admin
|
||||||
adminPassword: admin
|
adminPassword: admin
|
||||||
server:
|
server:
|
||||||
|
@ -107,12 +108,6 @@ userDefaults:
|
||||||
|
|
||||||
## About each configuration
|
## About each configuration
|
||||||
|
|
||||||
- `Signup`: This boolean value indicates whether user signup is enabled on the login page. NOTE: Be mindful of `userDefaults` settings if enabled. Default: `false`
|
|
||||||
|
|
||||||
- `AdminUsername`: This is the username of the admin user. Default: `admin`
|
|
||||||
|
|
||||||
- `AdminPassword`: This is the password of the admin user. Default: `admin`
|
|
||||||
|
|
||||||
### Server configuration settings
|
### Server configuration settings
|
||||||
|
|
||||||
- `indexingInterval`: This is the time in minutes the system waits before checking for filesystem changes. Default: `5`
|
- `indexingInterval`: This is the time in minutes the system waits before checking for filesystem changes. Default: `5`
|
||||||
|
@ -143,6 +138,10 @@ userDefaults:
|
||||||
|
|
||||||
- `root`: This is the root directory path. Default: `/srv`
|
- `root`: This is the root directory path. Default: `/srv`
|
||||||
|
|
||||||
|
- `CreateUserDir`: Boolean to create user directory on user creation. Default: `false`
|
||||||
|
|
||||||
|
- `UserHomeBasePath`: String to define user home directory base path. Default: `""`
|
||||||
|
|
||||||
### Auth configuration settings
|
### Auth configuration settings
|
||||||
|
|
||||||
- `recaptcha`:
|
- `recaptcha`:
|
||||||
|
@ -166,6 +165,12 @@ userDefaults:
|
||||||
|
|
||||||
- `shell`: This is the shell configuration.
|
- `shell`: This is the shell configuration.
|
||||||
|
|
||||||
|
- `Signup`: This boolean value indicates whether user signup is enabled on the login page. NOTE: Be mindful of `userDefaults` settings if enabled. Default: `false`
|
||||||
|
|
||||||
|
- `AdminUsername`: This is the username of the admin user. Default: `admin`
|
||||||
|
|
||||||
|
- `AdminPassword`: This is the password of the admin user. Default: `admin`
|
||||||
|
|
||||||
### Frontend configuration settings
|
### Frontend configuration settings
|
||||||
|
|
||||||
- `name`: This is the name of the frontend.
|
- `name`: This is the name of the frontend.
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
|
||||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
|
||||||
preserveAspectRatio="xMidYMid meet">
|
|
||||||
<metadata>
|
|
||||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
|
||||||
</metadata>
|
|
||||||
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
|
||||||
fill="#000000" stroke="none">
|
|
||||||
<path d="M3245 6989 c-522 -39 -1042 -197 -1480 -449 -849 -488 -1459 -1308
|
|
||||||
-1673 -2250 -177 -776 -89 -1582 250 -2301 368 -778 1052 -1418 1857 -1739
|
|
||||||
903 -359 1927 -325 2812 92 778 368 1418 1052 1739 1857 359 903 325 1927 -92
|
|
||||||
2812 -296 627 -806 1175 -1423 1529 -587 338 -1308 500 -1990 449z m555 -580
|
|
||||||
c519 -51 1018 -245 1446 -565 788 -588 1229 -1526 1174 -2496 -16 -277 -58
|
|
||||||
-500 -145 -763 -144 -440 -378 -819 -710 -1150 -452 -452 -1005 -730 -1655
|
|
||||||
-832 -91 -14 -175 -18 -405 -18 -304 0 -369 6 -595 51 -1105 223 -1999 1092
|
|
||||||
-2259 2197 -52 221 -73 412 -73 667 0 397 64 732 204 1080 304 752 886 1334
|
|
||||||
1638 1638 431 174 895 238 1380 191z"/>
|
|
||||||
<path d="M2670 5215 c0 -13 -44 -15 -335 -15 -352 0 -383 -3 -399 -45 -3 -9
|
|
||||||
-6 -758 -6 -1663 0 -1168 -3 -1643 -11 -1632 -8 11 -9 8 -4 -15 3 -16 17 -41
|
|
||||||
31 -55 l24 -25 1530 0 1530 0 24 25 c14 14 26 36 27 50 1 14 1 711 1 1550 l-2
|
|
||||||
1526 -228 142 -229 142 -136 0 -137 0 0 -600 0 -600 -705 0 -705 0 0 615 0
|
|
||||||
615 -135 0 c-113 0 -135 -2 -135 -15z m-264 -190 c57 -29 89 -71 103 -137 35
|
|
||||||
-154 -98 -282 -258 -247 -55 12 -122 62 -148 113 -36 69 -12 186 49 243 62 58
|
|
||||||
170 70 254 28z m2316 -1702 c17 -15 18 -49 18 -670 l0 -653 -1245 0 -1245 0 0
|
|
||||||
654 c0 582 2 656 16 670 14 14 139 16 1226 16 1113 0 1213 -1 1230 -17z
|
|
||||||
m-2602 -1363 c40 -40 13 -100 -43 -100 -60 0 -88 59 -47 100 11 11 31 20 45
|
|
||||||
20 14 0 34 -9 45 -20z m2840 0 c41 -41 11 -100 -52 -100 -35 0 -58 24 -58 60
|
|
||||||
0 54 71 79 110 40z"/>
|
|
||||||
<path d="M2431 3091 c-7 -13 -7 -23 2 -35 11 -15 97 -16 1067 -14 l1055 3 0
|
|
||||||
30 0 30 -1057 3 c-1023 2 -1058 1 -1067 -17z"/>
|
|
||||||
<path d="M2436 2675 c-19 -19 -11 -41 17 -49 41 -11 2067 -7 2088 4 23 13 25
|
|
||||||
46 3 54 -9 3 -483 6 -1054 6 -919 0 -1040 -2 -1054 -15z"/>
|
|
||||||
<path d="M2447 2273 c-14 -4 -17 -13 -15 -36 l3 -32 1049 -3 c767 -1 1052 1
|
|
||||||
1062 9 20 16 17 47 -5 59 -20 10 -2055 13 -2094 3z"/>
|
|
||||||
<path d="M3822 5027 c-21 -23 -22 -30 -22 -293 0 -258 1 -271 20 -292 27 -29
|
|
||||||
56 -35 140 -30 56 3 75 8 93 26 22 22 22 26 22 298 l0 276 -24 19 c-19 16 -40
|
|
||||||
19 -115 19 -84 0 -95 -2 -114 -23z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -1,147 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xml:space="preserve"
|
|
||||||
width="560"
|
|
||||||
height="560"
|
|
||||||
version="1.1"
|
|
||||||
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
|
|
||||||
viewBox="0 0 560 560"
|
|
||||||
id="svg44"
|
|
||||||
sodipodi:docname="icon_raw.svg"
|
|
||||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
|
||||||
inkscape:export-filename="/home/umarcor/filebrowser/logo/icon_raw.svg.png"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96"><metadata
|
|
||||||
id="metadata48"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:window-height="711"
|
|
||||||
id="namedview46"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="0.33714286"
|
|
||||||
inkscape:cx="-172.33051"
|
|
||||||
inkscape:cy="280"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="20"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="svg44" />
|
|
||||||
<defs
|
|
||||||
id="defs4">
|
|
||||||
<style
|
|
||||||
type="text/css"
|
|
||||||
id="style2">
|
|
||||||
<![CDATA[
|
|
||||||
.fil1 {fill:#FEFEFE}
|
|
||||||
.fil6 {fill:#006498}
|
|
||||||
.fil7 {fill:#0EA5EB}
|
|
||||||
.fil8 {fill:#2979FF}
|
|
||||||
.fil3 {fill:#2BBCFF}
|
|
||||||
.fil0 {fill:#455A64}
|
|
||||||
.fil4 {fill:#53C6FC}
|
|
||||||
.fil5 {fill:#BDEAFF}
|
|
||||||
.fil2 {fill:#332C2B;fill-opacity:0.149020}
|
|
||||||
]]>
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<g
|
|
||||||
id="g85"
|
|
||||||
transform="translate(-70,-70)"><path
|
|
||||||
class="fil1"
|
|
||||||
d="M 350,71 C 504,71 629,196 629,350 629,504 504,629 350,629 196,629 71,504 71,350 71,196 196,71 350,71 Z"
|
|
||||||
id="path9"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#fefefe" /><path
|
|
||||||
class="fil2"
|
|
||||||
d="M 475,236 593,387 C 596,503 444,639 301,585 L 225,486 339,330 c 0,0 138,-95 136,-94 z"
|
|
||||||
id="path11"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#332c2b;fill-opacity:0.14902003" /><path
|
|
||||||
class="fil3"
|
|
||||||
d="m 231,211 h 208 l 38,24 v 246 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 V 219 c 0,-5 3,-8 8,-8 z"
|
|
||||||
id="path13"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#2bbcff" /><path
|
|
||||||
class="fil4"
|
|
||||||
d="m 231,211 h 208 l 38,24 v 2 L 440,214 H 231 c -4,0 -7,3 -7,7 v 263 c -1,-1 -1,-2 -1,-3 V 219 c 0,-5 3,-8 8,-8 z"
|
|
||||||
id="path15"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#53c6fc" /><polygon
|
|
||||||
class="fil5"
|
|
||||||
points="305,212 418,212 418,310 305,310 "
|
|
||||||
id="polygon17"
|
|
||||||
style="fill:#bdeaff" /><path
|
|
||||||
class="fil5"
|
|
||||||
d="m 255,363 h 189 c 3,0 5,2 5,4 V 483 H 250 V 367 c 0,-2 2,-4 5,-4 z"
|
|
||||||
id="path19"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#bdeaff" /><polygon
|
|
||||||
class="fil6"
|
|
||||||
points="250,470 449,470 449,483 250,483 "
|
|
||||||
id="polygon21"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil6"
|
|
||||||
d="m 380,226 h 10 c 3,0 6,2 6,5 v 40 c 0,3 -3,6 -6,6 h -10 c -3,0 -6,-3 -6,-6 v -40 c 0,-3 3,-5 6,-5 z"
|
|
||||||
id="path23"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil1"
|
|
||||||
d="m 254,226 c 10,0 17,7 17,17 0,9 -7,16 -17,16 -9,0 -17,-7 -17,-16 0,-10 8,-17 17,-17 z"
|
|
||||||
id="path25"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#fefefe" /><path
|
|
||||||
class="fil6"
|
|
||||||
d="m 267,448 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,3 -3,3 H 267 c -2,0 -3,-2 -3,-3 v 0 c 0,-2 1,-3 3,-3 z"
|
|
||||||
id="path27"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil6"
|
|
||||||
d="m 267,415 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,2 -3,2 H 267 c -2,0 -3,-1 -3,-2 v 0 c 0,-2 1,-3 3,-3 z"
|
|
||||||
id="path29"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil6"
|
|
||||||
d="m 267,381 h 165 c 2,0 3,2 3,3 v 0 c 0,2 -1,3 -3,3 H 267 c -2,0 -3,-1 -3,-3 v 0 c 0,-1 1,-3 3,-3 z"
|
|
||||||
id="path31"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil1"
|
|
||||||
d="m 236,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
|
|
||||||
id="path33"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#fefefe" /><path
|
|
||||||
class="fil1"
|
|
||||||
d="m 463,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
|
|
||||||
id="path35"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#fefefe" /><polygon
|
|
||||||
class="fil6"
|
|
||||||
points="305,212 284,212 284,310 305,310 "
|
|
||||||
id="polygon37"
|
|
||||||
style="fill:#006498" /><path
|
|
||||||
class="fil7"
|
|
||||||
d="m 477,479 v 2 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 v -2 c 0,4 3,8 8,8 h 238 c 5,0 8,-4 8,-8 z"
|
|
||||||
id="path39"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#0ea5eb" /><path
|
|
||||||
class="fil8"
|
|
||||||
d="M 350,70 C 505,70 630,195 630,350 630,505 505,630 350,630 195,630 70,505 70,350 70,195 195,70 350,70 Z m 0,46 C 479,116 584,221 584,350 584,479 479,584 350,584 221,584 116,479 116,350 116,221 221,116 350,116 Z"
|
|
||||||
id="path41"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#2979ff" /></g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 5.4 KiB |
|
@ -120,17 +120,22 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
<div id="loading">
|
[{[ if .darkMode -]}]
|
||||||
|
<div id="loading dark-mode">
|
||||||
<div class="spinner">
|
<div class="spinner">
|
||||||
<div class="bounce1"></div>
|
<div class="bounce1"></div>
|
||||||
<div class="bounce2"></div>
|
<div class="bounce2"></div>
|
||||||
<div class="bounce3"></div>
|
<div class="bounce3"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
[{[ else ]}]
|
||||||
[{[ if .darkMode -]}]
|
<div id="loading">
|
||||||
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/dark.css" />
|
<div class="spinner">
|
||||||
[{[ end ]}]
|
<div class="bounce1"></div>
|
||||||
|
<div class="bounce2"></div>
|
||||||
|
<div class="bounce3"></div>
|
||||||
|
</div>
|
||||||
|
</div> [{[ end ]}]
|
||||||
[{[ if .CSS -]}]
|
[{[ if .CSS -]}]
|
||||||
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
||||||
[{[ end ]}]
|
[{[ end ]}]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#login {
|
#login {
|
||||||
background: #fff;
|
background: var(--background);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
@ -3,11 +3,12 @@ const disableExternal = window.FileBrowser.DisableExternal;
|
||||||
const disableUsedPercentage = window.FileBrowser.DisableUsedPercentage;
|
const disableUsedPercentage = window.FileBrowser.DisableUsedPercentage;
|
||||||
const baseURL = window.FileBrowser.BaseURL;
|
const baseURL = window.FileBrowser.BaseURL;
|
||||||
const staticURL = window.FileBrowser.StaticURL;
|
const staticURL = window.FileBrowser.StaticURL;
|
||||||
|
const darkMode = window.FileBrowser.darkMode;
|
||||||
const recaptcha = window.FileBrowser.ReCaptcha;
|
const recaptcha = window.FileBrowser.ReCaptcha;
|
||||||
const recaptchaKey = window.FileBrowser.ReCaptchaKey;
|
const recaptchaKey = window.FileBrowser.ReCaptchaKey;
|
||||||
const signup = window.FileBrowser.Signup;
|
const signup = window.FileBrowser.Signup;
|
||||||
const version = window.FileBrowser.Version;
|
const version = window.FileBrowser.Version;
|
||||||
const logoURL = `${staticURL}/img/logo.svg`;
|
const logoURL = `${staticURL}/img/logo.png`;
|
||||||
const noAuth = window.FileBrowser.NoAuth;
|
const noAuth = window.FileBrowser.NoAuth;
|
||||||
const authMethod = window.FileBrowser.AuthMethod;
|
const authMethod = window.FileBrowser.AuthMethod;
|
||||||
const loginPage = window.FileBrowser.LoginPage;
|
const loginPage = window.FileBrowser.LoginPage;
|
||||||
|
@ -16,6 +17,7 @@ const resizePreview = window.FileBrowser.ResizePreview;
|
||||||
const enableExec = window.FileBrowser.EnableExec;
|
const enableExec = window.FileBrowser.EnableExec;
|
||||||
const origin = window.location.origin;
|
const origin = window.location.origin;
|
||||||
|
|
||||||
|
console.log(window.FileBrowser)
|
||||||
export {
|
export {
|
||||||
name,
|
name,
|
||||||
disableExternal,
|
disableExternal,
|
||||||
|
@ -33,4 +35,5 @@ export {
|
||||||
resizePreview,
|
resizePreview,
|
||||||
enableExec,
|
enableExec,
|
||||||
origin,
|
origin,
|
||||||
|
darkMode
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="login" :class="{ recaptcha: recaptcha }">
|
<div id="login" :class="{ recaptcha: recaptcha, 'dark-mode': isDarkMode }">
|
||||||
<form @submit="submit">
|
<form @submit="submit">
|
||||||
<img :src="logoURL" alt="File Browser" />
|
<img :src="logoURL" alt="File Browser" />
|
||||||
<h1>{{ name }}</h1>
|
<h1>{{ name }}</h1>
|
||||||
|
@ -51,6 +51,7 @@ import {
|
||||||
recaptcha,
|
recaptcha,
|
||||||
recaptchaKey,
|
recaptchaKey,
|
||||||
signup,
|
signup,
|
||||||
|
darkMode,
|
||||||
} from "@/utils/constants";
|
} from "@/utils/constants";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -59,6 +60,9 @@ export default {
|
||||||
signup: () => signup,
|
signup: () => signup,
|
||||||
name: () => name,
|
name: () => name,
|
||||||
logoURL: () => logoURL,
|
logoURL: () => logoURL,
|
||||||
|
isDarkMode() {
|
||||||
|
return darkMode === true
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
||||||
this.$store.commit("updateRequest", { name: "Settings" });
|
this.$store.commit("updateRequest", { name: "Settings" });
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["user"]),
|
...mapState(["user", "loading"]),
|
||||||
settingsEnabled() {
|
settingsEnabled() {
|
||||||
return this.user.disableSettings == false;
|
return this.user.disableSettings == false;
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
@mousemove="toggleNavigation"
|
@mousemove="toggleNavigation"
|
||||||
@touchstart="toggleNavigation"
|
@touchstart="toggleNavigation"
|
||||||
>
|
>
|
||||||
<div class="loading delayed" v-if="loading">
|
<div class="loading delayed" :class="{ 'dark-mode': isDarkMode }" v-if="loading">
|
||||||
<div class="spinner">
|
<div class="spinner">
|
||||||
<div class="bounce1"></div>
|
<div class="bounce1"></div>
|
||||||
<div class="bounce2"></div>
|
<div class="bounce2"></div>
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import { resizePreview } from "@/utils/constants";
|
import { resizePreview, darkMode } from "@/utils/constants";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import throttle from "lodash.throttle";
|
import throttle from "lodash.throttle";
|
||||||
import ExtendedImage from "@/components/files/ExtendedImage";
|
import ExtendedImage from "@/components/files/ExtendedImage";
|
||||||
|
@ -132,6 +132,9 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "user", "oldReq", "jwt", "loading", "show"]),
|
...mapState(["req", "user", "oldReq", "jwt", "loading", "show"]),
|
||||||
|
isDarkMode() {
|
||||||
|
return this.user && this.user.darkMode ? this.user.darkMode : darkMode;
|
||||||
|
},
|
||||||
hasPrevious() {
|
hasPrevious() {
|
||||||
return this.previousLink !== "";
|
return this.previousLink !== "";
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="$store.state.show === 'deleteUser'" class="card floating">
|
<div v-if="showDeletePrompt" class="card floating">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>Are you sure you want to delete this user?</p>
|
<p>Are you sure you want to delete this user?</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,6 +78,7 @@ export default {
|
||||||
error: null,
|
error: null,
|
||||||
originalUser: null,
|
originalUser: null,
|
||||||
user: {},
|
user: {},
|
||||||
|
showDelete: false,
|
||||||
createUserDir: false,
|
createUserDir: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -89,6 +90,9 @@ export default {
|
||||||
return this.$route.path === "/settings/users/new";
|
return this.$route.path === "/settings/users/new";
|
||||||
},
|
},
|
||||||
...mapState(["loading"]),
|
...mapState(["loading"]),
|
||||||
|
showDeletePrompt() {
|
||||||
|
return this.showDelete
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route: "fetchData",
|
$route: "fetchData",
|
||||||
|
@ -125,7 +129,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deletePrompt() {
|
deletePrompt() {
|
||||||
this.showHover("deleteUser");
|
this.showDelete = true
|
||||||
},
|
},
|
||||||
async deleteUser(event) {
|
async deleteUser(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
Loading…
Reference in New Issue