Move interactive setup to own package

Remove reflex installation for windows
This commit is contained in:
zeroZshadow 2021-07-15 21:23:59 +01:00
parent 19463ddb4f
commit 56a7a4132d
7 changed files with 397 additions and 408 deletions

View File

@ -53,7 +53,7 @@ tasks:
GORELEASER_VERSION: "0.159.0"
GOLINTER_VERSION: "1.31.0"
cmds:
- go install github.com/cespare/reflex
- '{{ if ne OS "windows" }} go install github.com/cespare/reflex {{ else }} {{ end }}'
- go install github.com/gobuffalo/packr/...
- go install github.com/haya14busa/goverage
- go install github.com/snikch/goodman/cmd/goodman

View File

@ -2,17 +2,16 @@ package main
import (
"bufio"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/ansible-semaphore/semaphore/cli/setup"
"github.com/ansible-semaphore/semaphore/db"
"github.com/ansible-semaphore/semaphore/db/factory"
"github.com/gorilla/context"
"io/ioutil"
"net/http"
"os"
"path"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/ansible-semaphore/semaphore/api"
@ -31,7 +30,6 @@ func cropTrailingSlashMiddleware(next http.Handler) http.Handler {
})
}
func main() {
util.ConfigInit()
@ -78,7 +76,7 @@ func main() {
route := api.Route()
route.Use(func (next http.Handler) http.Handler {
route.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
context.Set(r, "store", store)
next.ServeHTTP(w, r)
@ -100,79 +98,33 @@ func main() {
//nolint: gocyclo
func doSetup() int {
fmt.Print(`
Hello! You will now be guided through a setup to:
1. Set up configuration for a MySQL/MariaDB database
2. Set up a path for your playbooks (auto-created)
3. Run database Migrations
4. Set up initial semaphore user & password
`)
var b []byte
setup := util.NewConfig()
var config *util.ConfigType
for {
setup.Scan()
setup.GenerateCookieSecrets()
config = &util.ConfigType{}
config.GenerateCookieSecrets()
setup.InteractiveSetup(config)
var err error
b, err = json.MarshalIndent(&setup, " ", "\t")
if err != nil {
panic(err)
}
fmt.Printf("\n Generated configuration:\n %v\n\n", string(b))
fmt.Print(" > Is this correct? (yes/no): ")
var answer string
util.ScanErrorChecker(fmt.Scanln(&answer))
if answer == "yes" || answer == "y" {
if setup.VerifyConfig(config) {
break
}
fmt.Println()
setup = util.NewConfig()
}
confDir, err := os.Getwd()
if err != nil {
confDir = "/etc/semaphore"
}
fmt.Print(" > Config output directory (default " + confDir + "): ")
var answer string
util.ScanErrorChecker(fmt.Scanln(&answer))
if len(answer) > 0 {
confDir = answer
}
fmt.Printf(" Running: mkdir -p %v..\n", confDir)
err = os.MkdirAll(confDir, 0755) //nolint: gas
if err != nil {
log.Panic("Could not create config directory: " + err.Error())
}
configPath := path.Join(confDir, "/config.json")
if err = ioutil.WriteFile(configPath, b, 0644); err != nil {
panic(err)
}
fmt.Printf(" Configuration written to %v..\n", configPath)
configPath := setup.ScanConfigPathAndSave(config)
util.Config = config
fmt.Println(" Pinging db..")
util.Config = setup
store := factory.CreateStore()
if err = store.Connect(); err != nil {
fmt.Printf("\n Cannot connect to database!\n %v\n", err.Error())
if err := store.Connect(); err != nil {
fmt.Printf("Cannot connect to database!\n %v\n", err.Error())
os.Exit(1)
}
fmt.Println("\n Running DB Migrations..")
if err = store.Migrate(); err != nil {
fmt.Printf("\n Database migrations failed!\n %v\n", err.Error())
fmt.Println("Running DB Migrations..")
if err := store.Migrate(); err != nil {
fmt.Printf("Database migrations failed!\n %v\n", err.Error())
os.Exit(1)
}

210
cli/setup/setup.go Normal file
View File

@ -0,0 +1,210 @@
package setup
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/ansible-semaphore/semaphore/util"
)
const yesLong = "yes"
const yesShort = "y"
const interactiveSetupBlurb = `
Hello! You will now be guided through a setup to:
1. Set up configuration for a MySQL/MariaDB database
2. Set up a path for your playbooks (auto-created)
3. Run database Migrations
4. Set up initial semaphore user & password
`
func InteractiveSetup(conf *util.ConfigType) {
stdin := bufio.NewReader(os.Stdin)
fmt.Print(interactiveSetupBlurb)
dbPrompt := `What database to use:
1 - MySQL
2 - BoltDB
`
var db int
promptValue(stdin, dbPrompt, "1", &db)
switch db {
case 1:
scanMySQL(conf, stdin)
case 2:
scanBoltDb(conf, stdin)
}
defaultPlaybookPath := filepath.Join(os.TempDir(), "semaphore")
promptValue(stdin, "Playbook path", defaultPlaybookPath, &conf.TmpPath)
conf.TmpPath = filepath.Clean(conf.TmpPath)
promptValue(stdin, "Web root URL (optional, see https://github.com/ansible-semaphore/semaphore/wiki/Web-root-URL)", "", &conf.WebHost)
promptConfirmation(stdin, "Enable email alerts?", false, &conf.EmailAlert)
if conf.EmailAlert {
promptValue(stdin, "Mail server host", "localhost", &conf.EmailHost)
promptValue(stdin, "Mail server port", "25", &conf.EmailPort)
promptValue(stdin, "Mail sender address", "semaphore@localhost", &conf.EmailSender)
}
promptConfirmation(stdin, "Enable telegram alerts?", false, &conf.TelegramAlert)
if conf.TelegramAlert {
promptValue(stdin, "Telegram bot token (you can get it from @BotFather)", "", &conf.TelegramToken)
promptValue(stdin, "Telegram chat ID", "", &conf.TelegramChat)
}
promptConfirmation(stdin, "Enable LDAP authentication?", false, &conf.LdapEnable)
if conf.LdapEnable {
promptValue(stdin, "LDAP server host", "localhost:389", &conf.LdapServer)
promptConfirmation(stdin, "Enable LDAP TLS connection", false, &conf.LdapNeedTLS)
promptValue(stdin, "LDAP DN for bind", "cn=user,ou=users,dc=example", &conf.LdapBindDN)
promptValue(stdin, "Password for LDAP bind user", "pa55w0rd", &conf.LdapBindPassword)
promptValue(stdin, "LDAP DN for user search", "ou=users,dc=example", &conf.LdapSearchDN)
promptValue(stdin, "LDAP search filter", `(uid=%s)`, &conf.LdapSearchFilter)
promptValue(stdin, "LDAP mapping for DN field", "dn", &conf.LdapMappings.DN)
promptValue(stdin, "LDAP mapping for username field", "uid", &conf.LdapMappings.UID)
promptValue(stdin, "LDAP mapping for full name field", "cn", &conf.LdapMappings.CN)
promptValue(stdin, "LDAP mapping for email field", "mail", &conf.LdapMappings.Mail)
}
}
func scanBoltDb(conf *util.ConfigType, stdin *bufio.Reader) {
workingDirectory, err := os.Getwd()
if err != nil {
workingDirectory = os.TempDir()
}
defaultBoltDBPath := filepath.Join(workingDirectory, "database.boltdb")
promptValue(stdin, "DB filename", defaultBoltDBPath, &conf.BoltDb.Hostname)
}
func scanMySQL(conf *util.ConfigType, stdin *bufio.Reader) {
promptValue(stdin, "DB Hostname", "127.0.0.1:3306", &conf.MySQL.Hostname)
promptValue(stdin, "DB User", "root", &conf.MySQL.Username)
promptValue(stdin, "DB Password", "", &conf.MySQL.Password)
promptValue(stdin, "DB Name", "semaphore", &conf.MySQL.DbName)
}
func ScanConfigPathAndSave(config *util.ConfigType) string {
stdin := bufio.NewReader(os.Stdin)
configDirectory, err := os.Getwd()
if err != nil {
configDirectory, err = os.UserConfigDir()
if err != nil {
// Final fallback
configDirectory = "/etc/semaphore"
}
configDirectory = filepath.Join(configDirectory, "semaphore")
}
promptValue(stdin, "Config output directory", configDirectory, &configDirectory)
fmt.Printf("Running: mkdir -p %v..\n", configDirectory)
err = os.MkdirAll(configDirectory, 0755)
if err != nil {
log.Panic("Could not create config directory: " + err.Error())
}
// Marshal config to json
bytes, err := config.ToJSON()
if err != nil {
panic(err)
}
configPath := filepath.Join(configDirectory, "config.json")
if err = ioutil.WriteFile(configPath, bytes, 0644); err != nil {
panic(err)
}
fmt.Printf("Configuration written to %v..\n", configPath)
return configPath
}
func VerifyConfig(config *util.ConfigType) bool {
stdin := bufio.NewReader(os.Stdin)
bytes, err := config.ToJSON()
if err != nil {
panic(err)
}
fmt.Printf("\nGenerated configuration:\n %v\n\n", string(bytes))
var correct bool
promptConfirmation(stdin, "Is this correct?", true, &correct)
return correct
}
func promptValue(stdin *bufio.Reader, prompt string, def string, item interface{}) {
// Print prompt with optional default value
fmt.Print(prompt)
if len(def) != 0 {
fmt.Print(" (default " + def + ")")
}
fmt.Print("\n> ")
str, err := stdin.ReadString('\n')
if err != nil {
log.WithFields(log.Fields{"level": "Warn"}).Warn(err.Error())
}
// Remove newlines
str = strings.TrimSuffix(str, "\n")
str = strings.TrimSuffix(str, "\r")
// If default, print default on input line
if len(str) == 0 {
str = def
fmt.Print("\033[1A")
fmt.Println("> " + def)
}
//Parse
if _, err := fmt.Sscanln(str, item); err != nil && err != io.EOF {
log.WithFields(log.Fields{"level": "Warn"}).Warn(err.Error())
}
// Empty line after prompt
fmt.Println("")
}
func promptConfirmation(stdin *bufio.Reader, prompt string, def bool, item *bool) {
defString := "yes"
if !def {
defString = "no"
}
fmt.Print(prompt + " (yes/no) (default " + defString + ")")
fmt.Print("\n> ")
str, err := stdin.ReadString('\n')
if err != nil {
log.WithFields(log.Fields{"level": "Warn"}).Warn(err.Error())
}
switch strings.ToLower(str) {
case "y", "yes":
*item = true
case "n", "no":
*item = false
default:
*item = def
fmt.Print("\033[1A")
fmt.Println("> " + defString)
}
// Empty line after prompt
fmt.Println("")
}

6
go.mod
View File

@ -9,8 +9,8 @@ require (
github.com/Sirupsen/logrus v1.0.4
github.com/cespare/reflex v0.3.0 // indirect
github.com/go-gorp/gorp/v3 v3.0.2
github.com/go-openapi/loads v0.19.4
github.com/go-openapi/spec v0.19.6
github.com/go-openapi/loads v0.19.4 // indirect
github.com/go-openapi/spec v0.19.6 // indirect
github.com/go-sql-driver/mysql v1.4.1
github.com/go-swagger/go-swagger v0.22.0 // indirect
github.com/go-task/task v2.2.0+incompatible // indirect
@ -39,7 +39,7 @@ require (
github.com/radovskyb/watcher v1.0.7 // indirect
github.com/russross/blackfriday v1.5.2
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa // indirect
github.com/spf13/cobra v0.0.5 // indirect
github.com/ziutek/mymysql v1.5.4 // indirect
go.etcd.io/bbolt v1.3.2

1
go.sum
View File

@ -165,6 +165,7 @@ github.com/haya14busa/goverage v0.0.0-20180129164344-eec3514a20b5/go.mod h1:0YZ2
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=

View File

@ -6,10 +6,8 @@ import (
"errors"
"flag"
"fmt"
log "github.com/Sirupsen/logrus"
"os"
"path"
"path/filepath"
"net/url"
@ -35,11 +33,6 @@ var Upgrade bool
// WebHostURL is the public route to the semaphore server
var WebHostURL *url.URL
const (
longPos = "yes"
shortPos = "y"
)
type DbDriver int
const (
@ -64,8 +57,8 @@ type ldapMappings struct {
//ConfigType mapping between Config and the json file that sets it
type ConfigType struct {
MySQL DbConfig `json:"mysql"`
BoltDb DbConfig `json:"bolt"`
MySQL DbConfig `json:"mysql"`
BoltDb DbConfig `json:"bolt"`
// Format `:port_num` eg, :3000
// if : is missing it will be corrected
@ -119,21 +112,9 @@ type ConfigType struct {
//Config exposes the application configuration storage for use in the application
var Config *ConfigType
var confPath *string
// NewConfig returns a reference to a new blank configType
// nolint: golint
func NewConfig() *ConfigType {
return &ConfigType{}
}
// ScanErrorChecker deals with errors encountered while scanning lines
// since we do not fail on these errors currently we can simply note them
// and move on
func ScanErrorChecker(n int, err error) {
if err != nil {
log.Warn("An input error occurred:" + err.Error())
}
// ToJSON returns a JSON string of the config
func (config *ConfigType) ToJSON() ([]byte, error) {
return json.MarshalIndent(&config, " ", "\t")
}
// ConfigInit reads in cli flags, and switches actions appropriately on them
@ -141,7 +122,7 @@ func ConfigInit() {
flag.BoolVar(&InteractiveSetup, "setup", false, "perform interactive setup")
flag.BoolVar(&Migration, "migrate", false, "execute migrations")
flag.BoolVar(&Upgrade, "upgrade", false, "upgrade semaphore")
confPath = flag.String("config", "", "config path")
configPath := flag.String("config", "", "config path")
var unhashedPwd string
flag.StringVar(&unhashedPwd, "hash", "", "generate hash of given password")
@ -164,7 +145,7 @@ func ConfigInit() {
}
if printConfig {
cfg := &ConfigType{
config := &ConfigType{
MySQL: DbConfig{
Hostname: "127.0.0.1:3306",
Username: "root",
@ -173,10 +154,10 @@ func ConfigInit() {
Port: ":3000",
TmpPath: "/tmp/semaphore",
}
cfg.GenerateCookieSecrets()
config.GenerateCookieSecrets()
b, _ := json.MarshalIndent(cfg, "", "\t")
fmt.Println(string(b))
bytes, _ := config.ToJSON()
fmt.Println(string(bytes))
os.Exit(0)
}
@ -188,7 +169,7 @@ func ConfigInit() {
os.Exit(0)
}
loadConfig()
loadConfig(configPath)
validateConfig()
var encryption []byte
@ -205,24 +186,27 @@ func ConfigInit() {
}
}
func loadConfig() {
//If the confPath option has been set try to load and decode it
if confPath != nil && len(*confPath) > 0 {
file, err := os.Open(*confPath)
func loadConfig(configPath *string) {
//If the configPath option has been set try to load and decode it
var usedPath string
if configPath != nil && len(*configPath) > 0 {
path := *configPath
file, err := os.Open(path)
exitOnConfigError(err)
decodeConfig(file)
usedPath = path
} else {
// if no confPath look in the cwd
// if no configPath look in the cwd
cwd, err := os.Getwd()
exitOnConfigError(err)
cwd = cwd + "/config.json"
confPath = &cwd
file, err := os.Open(*confPath)
defaultPath := path.Join(cwd, "config.json")
file, err := os.Open(defaultPath)
exitOnConfigError(err)
decodeConfig(file)
usedPath = defaultPath
}
fmt.Println("Using config file: " + *confPath)
fmt.Println("Using config file: " + usedPath)
}
func validateConfig() {
@ -325,205 +309,3 @@ func (conf *ConfigType) GenerateCookieSecrets() {
conf.CookieHash = base64.StdEncoding.EncodeToString(hash)
conf.CookieEncryption = base64.StdEncoding.EncodeToString(encryption)
}
func (conf *ConfigType) ScanBoltDb() {
filename, err := os.Getwd() // os.UserHomeDir()
exitOnConfigError(err)
filename = filepath.Join(filename, "database.bolt")
fmt.Print(" > DB filename (default " + filename + "): ")
ScanErrorChecker(fmt.Scanln(&conf.BoltDb.Hostname))
if len(conf.BoltDb.Hostname) == 0 {
conf.BoltDb.Hostname = filename
}
}
func (conf *ConfigType) ScanMySQL() {
fmt.Print(" > DB Hostname (default 127.0.0.1:3306): ")
ScanErrorChecker(fmt.Scanln(&conf.MySQL.Hostname))
if len(conf.MySQL.Hostname) == 0 {
conf.MySQL.Hostname = "127.0.0.1:3306"
}
fmt.Print(" > DB User (default root): ")
ScanErrorChecker(fmt.Scanln(&conf.MySQL.Username))
if len(conf.MySQL.Username) == 0 {
conf.MySQL.Username = "root"
}
fmt.Print(" > DB Password: ")
ScanErrorChecker(fmt.Scanln(&conf.MySQL.Password))
fmt.Print(" > DB Name (default semaphore): ")
ScanErrorChecker(fmt.Scanln(&conf.MySQL.DbName))
if len(conf.MySQL.DbName) == 0 {
conf.MySQL.DbName = "semaphore"
}
}
//nolint: gocyclo
func (conf *ConfigType) Scan() {
db := 1
fmt.Println(" > DB")
fmt.Println(" 1 - MySQL")
fmt.Println(" 2 - bbolt")
fmt.Print(" (default 1): ")
ScanErrorChecker(fmt.Scanln(&db))
switch db {
case 1:
conf.ScanMySQL()
case 2:
conf.ScanBoltDb()
}
fmt.Print(" > Playbook path (default /tmp/semaphore): ")
ScanErrorChecker(fmt.Scanln(&conf.TmpPath))
if len(conf.TmpPath) == 0 {
conf.TmpPath = "/tmp/semaphore"
}
conf.TmpPath = path.Clean(conf.TmpPath)
fmt.Print(" > Web root URL (optional, see https://github.com/ansible-semaphore/semaphore/wiki/Web-root-URL): ")
ScanErrorChecker(fmt.Scanln(&conf.WebHost))
var EmailAlertAnswer string
fmt.Print(" > Enable email alerts (y/n, default n): ")
ScanErrorChecker(fmt.Scanln(&EmailAlertAnswer))
if EmailAlertAnswer == longPos || EmailAlertAnswer == shortPos {
conf.EmailAlert = true
fmt.Print(" > Mail server host (default localhost): ")
ScanErrorChecker(fmt.Scanln(&conf.EmailHost))
if len(conf.EmailHost) == 0 {
conf.EmailHost = "localhost"
}
fmt.Print(" > Mail server port (default 25): ")
ScanErrorChecker(fmt.Scanln(&conf.EmailPort))
if len(conf.EmailPort) == 0 {
conf.EmailPort = "25"
}
fmt.Print(" > Mail sender address (default semaphore@localhost): ")
ScanErrorChecker(fmt.Scanln(&conf.EmailSender))
if len(conf.EmailSender) == 0 {
conf.EmailSender = "semaphore@localhost"
}
} else {
conf.EmailAlert = false
}
var TelegramAlertAnswer string
fmt.Print(" > Enable telegram alerts (y/n, default n): ")
ScanErrorChecker(fmt.Scanln(&TelegramAlertAnswer))
if TelegramAlertAnswer == longPos || TelegramAlertAnswer == shortPos {
conf.TelegramAlert = true
fmt.Print(" > Telegram bot token (you can get it from @BotFather) (default ''): ")
ScanErrorChecker(fmt.Scanln(&conf.TelegramToken))
if len(conf.TelegramToken) == 0 {
conf.TelegramToken = ""
}
fmt.Print(" > Telegram chat ID (default ''): ")
ScanErrorChecker(fmt.Scanln(&conf.TelegramChat))
if len(conf.TelegramChat) == 0 {
conf.TelegramChat = ""
}
} else {
conf.TelegramAlert = false
}
var LdapAnswer string
fmt.Print(" > Enable LDAP authentication (y/n, default n): ")
ScanErrorChecker(fmt.Scanln(&LdapAnswer))
if LdapAnswer == longPos || LdapAnswer == shortPos {
conf.LdapEnable = true
fmt.Print(" > LDAP server host (default localhost:389): ")
ScanErrorChecker(fmt.Scanln(&conf.LdapServer))
if len(conf.LdapServer) == 0 {
conf.LdapServer = "localhost:389"
}
var LdapTLSAnswer string
fmt.Print(" > Enable LDAP TLS connection (y/n, default n): ")
ScanErrorChecker(fmt.Scanln(&LdapTLSAnswer))
if LdapTLSAnswer == longPos || LdapTLSAnswer == shortPos {
conf.LdapNeedTLS = true
} else {
conf.LdapNeedTLS = false
}
fmt.Print(" > LDAP DN for bind (default cn=user,ou=users,dc=example): ")
ScanErrorChecker(fmt.Scanln(&conf.LdapBindDN))
if len(conf.LdapBindDN) == 0 {
conf.LdapBindDN = "cn=user,ou=users,dc=example"
}
fmt.Print(" > Password for LDAP bind user (default pa55w0rd): ")
ScanErrorChecker(fmt.Scanln(&conf.LdapBindPassword))
if len(conf.LdapBindPassword) == 0 {
conf.LdapBindPassword = "pa55w0rd"
}
fmt.Print(" > LDAP DN for user search (default ou=users,dc=example): ")
ScanErrorChecker(fmt.Scanln(&conf.LdapSearchDN))
if len(conf.LdapSearchDN) == 0 {
conf.LdapSearchDN = "ou=users,dc=example"
}
fmt.Print(" > LDAP search filter (default (uid=" + "%" + "s)): ")
ScanErrorChecker(fmt.Scanln(&conf.LdapSearchFilter))
if len(conf.LdapSearchFilter) == 0 {
conf.LdapSearchFilter = "(uid=%s)"
}
fmt.Print(" > LDAP mapping for DN field (default dn): ")
ScanErrorChecker(fmt.Scanln(&conf.LdapMappings.DN))
if len(conf.LdapMappings.DN) == 0 {
conf.LdapMappings.DN = "dn"
}
fmt.Print(" > LDAP mapping for username field (default uid): ")
ScanErrorChecker(fmt.Scanln(&conf.LdapMappings.UID))
if len(conf.LdapMappings.UID) == 0 {
conf.LdapMappings.UID = "uid"
}
fmt.Print(" > LDAP mapping for full name field (default cn): ")
ScanErrorChecker(fmt.Scanln(&conf.LdapMappings.CN))
if len(conf.LdapMappings.CN) == 0 {
conf.LdapMappings.CN = "cn"
}
fmt.Print(" > LDAP mapping for email field (default mail): ")
ScanErrorChecker(fmt.Scanln(&conf.LdapMappings.Mail))
if len(conf.LdapMappings.Mail) == 0 {
conf.LdapMappings.Mail = "mail"
}
} else {
conf.LdapEnable = false
}
}

234
web2/package-lock.json generated
View File

@ -1913,17 +1913,6 @@
"unique-filename": "^1.1.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@ -1971,25 +1960,6 @@
"path-exists": "^4.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@ -2045,25 +2015,15 @@
"dev": true
},
"ssri": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz",
"integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.1.tgz",
"integrity": "sha512-w+daCzXN89PseTL99MkA+fxJEcU3wfaE/ah0i0lnOlpG1CYLJ2ZjzEry68YBKfLs4JfoTShrTEsJkAZuNZ/stw==",
"dev": true,
"requires": {
"figgy-pudding": "^3.5.1",
"minipass": "^3.1.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"terser-webpack-plugin": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz",
@ -2081,18 +2041,6 @@
"webpack-sources": "^1.4.3"
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.0.0-beta.10",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.10.tgz",
"integrity": "sha512-PflGsj4RHW3tuYFmSPhcozAkds8ELXf8d19twWorQTjcuWxl/Xqb9W1NgfsY7AAkCkkRRYy2FwIX4tSnskfKig==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
}
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@ -3291,15 +3239,30 @@
}
},
"browserslist": {
"version": "4.14.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz",
"integrity": "sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==",
"version": "4.16.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
"integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001135",
"electron-to-chromium": "^1.3.571",
"escalade": "^3.1.0",
"node-releases": "^1.1.61"
"caniuse-lite": "^1.0.30001219",
"colorette": "^1.2.2",
"electron-to-chromium": "^1.3.723",
"escalade": "^3.1.1",
"node-releases": "^1.1.71"
},
"dependencies": {
"caniuse-lite": {
"version": "1.0.30001245",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001245.tgz",
"integrity": "sha512-768fM9j1PKXpOCKws6eTo3RHmvTUsG9UrpT4WoREFeZgJBTi4/X9g565azS/rVUGtqb8nt7FjLeF5u4kukERnA==",
"dev": true
},
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true
}
}
},
"buffer": {
@ -5228,9 +5191,9 @@
"dev": true
},
"dns-packet": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
"integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz",
"integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==",
"dev": true,
"requires": {
"ip": "^1.1.0",
@ -5427,9 +5390,9 @@
"dev": true
},
"electron-to-chromium": {
"version": "1.3.572",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.572.tgz",
"integrity": "sha512-TKqdEukCCl7JC20SwEoWTbtnGt4YjfHWAv4tcNky0a9qGo0WdM+Lrd60tps+nkaJCmktKBJjr99fLtEBU1ipWQ==",
"version": "1.3.776",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.776.tgz",
"integrity": "sha512-V0w7eFSBoFPpdw4xexjVPZ770UDZIevSwkkj4W97XbE3IsCsTRFpa7/yXGZ88EOQAUEA09JMMsWK0xsw0kRAYw==",
"dev": true
},
"elliptic": {
@ -5576,9 +5539,9 @@
"dev": true
},
"escalade": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz",
"integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"dev": true
},
"escape-html": {
@ -6918,9 +6881,9 @@
}
},
"glob-parent": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
@ -7191,9 +7154,9 @@
"dev": true
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"hpack.js": {
@ -8679,9 +8642,9 @@
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash.clonedeep": {
@ -9845,9 +9808,9 @@
}
},
"node-releases": {
"version": "1.1.61",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz",
"integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==",
"version": "1.1.73",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz",
"integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==",
"dev": true
},
"node-sass": {
@ -10951,9 +10914,9 @@
"dev": true
},
"postcss": {
"version": "7.0.34",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.34.tgz",
"integrity": "sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw==",
"version": "7.0.36",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
@ -14122,9 +14085,9 @@
}
},
"url-parse": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
"dev": true,
"requires": {
"querystringify": "^2.1.1",
@ -14333,6 +14296,87 @@
}
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.3.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.3.0.tgz",
"integrity": "sha512-UDgni/tUVSdwHuQo+vuBmEgamWx88SuSlEb5fgdvHrlJSPB9qMBRF6W7bfPWSqDns425Gt1wxAUif+f+h/rWjg==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-router": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.4.tgz",
@ -15100,9 +15144,9 @@
}
},
"ws": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==",
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
"dev": true
},
"xml-name-validator": {
@ -15124,9 +15168,9 @@
"dev": true
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true
},
"yallist": {