diff --git a/.golangci.yml b/.golangci.yml
index 3cdceded..5dbaf961 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -51,5 +51,6 @@ issues:
- .safeBrowsingServer. is unused
# errcheck
- Error return value of .s.closeConn. is not checked
+ - Error return value of ..*.Shutdown.
# goconst
- string .forcesafesearch.google.com. has 3 occurrences
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 9cc18d7a..608fdd26 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,6 @@ $(STATIC): $(JSFILES) client/node_modules
npm --prefix client run build-prod
$(TARGET): $(STATIC) *.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
- go get -d .
GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) GO111MODULE=off go get -v github.com/gobuffalo/packr/...
PATH=$(GOPATH)/bin:$(PATH) packr -z
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VersionString=$(GIT_VERSION)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
diff --git a/app.go b/app.go
index 39aba5c7..e7ca88c8 100644
--- a/app.go
+++ b/app.go
@@ -1,7 +1,6 @@
package main
import (
- "bufio"
"fmt"
stdlog "log"
"net"
@@ -17,11 +16,11 @@ import (
"github.com/gobuffalo/packr"
"github.com/hmage/golibs/log"
- "golang.org/x/crypto/ssh/terminal"
)
// VersionString will be set through ldflags, contains current version
var VersionString = "undefined"
+var httpServer *http.Server
const (
// Used in config to indicate that syslog or eventlog (win) should be used for logger output
@@ -67,18 +66,15 @@ func run(args options) {
// print the first message after logger is configured
log.Printf("AdGuard Home, version %s\n", VersionString)
- log.Printf("Current working directory is %s", config.ourBinaryDir)
+ log.Tracef("Current working directory is %s", config.ourBinaryDir)
if args.runningAsService {
log.Printf("AdGuard Home is running as a service")
}
- err := askUsernamePasswordIfPossible()
- if err != nil {
- log.Fatal(err)
- }
+ config.firstRun = detectFirstRun()
// Do the upgrade if necessary
- err = upgradeConfig()
+ err := upgradeConfig()
if err != nil {
log.Fatal(err)
}
@@ -121,14 +117,16 @@ func run(args options) {
log.Fatal(err)
}
- err = startDNSServer()
- if err != nil {
- log.Fatal(err)
- }
+ if !config.firstRun {
+ err = startDNSServer()
+ if err != nil {
+ log.Fatal(err)
+ }
- err = startDHCPServer()
- if err != nil {
- log.Fatal(err)
+ err = startDHCPServer()
+ if err != nil {
+ log.Fatal(err)
+ }
}
// Update filters we've just loaded right away, don't wait for periodic update timer
@@ -145,13 +143,33 @@ func run(args options) {
// Initialize and run the admin Web interface
box := packr.NewBox("build/static")
- http.Handle("/", optionalAuthHandler(http.FileServer(box)))
+ // if not configured, redirect / to /install.html, otherwise redirect /install.html to /
+ http.Handle("/", postInstallHandler(optionalAuthHandler(http.FileServer(box))))
registerControlHandlers()
- address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
- URL := fmt.Sprintf("http://%s", address)
- log.Println("Go to " + URL)
- log.Fatal(http.ListenAndServe(address, nil))
+ // add handlers for /install paths, we only need them when we're not configured yet
+ if config.firstRun {
+ log.Printf("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
+ http.Handle("/install.html", preInstallHandler(http.FileServer(box)))
+ registerInstallHandlers()
+ }
+
+ // this loop is used as an ability to change listening host and/or port
+ for {
+ address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
+ URL := fmt.Sprintf("http://%s", address)
+ log.Println("Go to " + URL)
+ // we need to have new instance, because after Shutdown() the Server is not usable
+ httpServer = &http.Server{
+ Addr: address,
+ }
+ err := httpServer.ListenAndServe()
+ if err != http.ErrServerClosed {
+ log.Fatal(err)
+ os.Exit(1)
+ }
+ // We use ErrServerClosed as a sign that we need to rebind on new address, so go back to the start of the loop
+ }
}
// initWorkingDir initializes the ourBinaryDir (basically, we use it as a working dir)
@@ -222,14 +240,6 @@ func cleanup() {
}
}
-func getInput() (string, error) {
- scanner := bufio.NewScanner(os.Stdin)
- scanner.Scan()
- text := scanner.Text()
- err := scanner.Err()
- return text, err
-}
-
// command-line arguments
type options struct {
verbose bool // is verbose logging enabled
@@ -318,79 +328,3 @@ func loadOptions() options {
return o
}
-
-func promptAndGet(prompt string) (string, error) {
- for {
- fmt.Print(prompt)
- input, err := getInput()
- if err != nil {
- log.Printf("Failed to get input, aborting: %s", err)
- return "", err
- }
- if len(input) != 0 {
- return input, nil
- }
- // try again
- }
-}
-
-func promptAndGetPassword(prompt string) (string, error) {
- for {
- fmt.Print(prompt)
- password, err := terminal.ReadPassword(int(os.Stdin.Fd()))
- fmt.Print("\n")
- if err != nil {
- log.Printf("Failed to get input, aborting: %s", err)
- return "", err
- }
- if len(password) != 0 {
- return string(password), nil
- }
- // try again
- }
-}
-
-func askUsernamePasswordIfPossible() error {
- configFile := config.getConfigFilename()
- _, err := os.Stat(configFile)
- if !os.IsNotExist(err) {
- // do nothing, file exists
- return nil
- }
- if !terminal.IsTerminal(int(os.Stdin.Fd())) {
- return nil // do nothing
- }
- if !terminal.IsTerminal(int(os.Stdout.Fd())) {
- return nil // do nothing
- }
- fmt.Printf("Would you like to set user/password for the web interface authentication (yes/no)?\n")
- yesno, err := promptAndGet("Please type 'yes' or 'no': ")
- if err != nil {
- return err
- }
- if yesno[0] != 'y' && yesno[0] != 'Y' {
- return nil
- }
- username, err := promptAndGet("Please enter the username: ")
- if err != nil {
- return err
- }
-
- password, err := promptAndGetPassword("Please enter the password: ")
- if err != nil {
- return err
- }
-
- password2, err := promptAndGetPassword("Please enter password again: ")
- if err != nil {
- return err
- }
- if password2 != password {
- fmt.Printf("Passwords do not match! Aborting\n")
- os.Exit(1)
- }
-
- config.AuthName = username
- config.AuthPass = password
- return nil
-}
diff --git a/client/.eslintrc b/client/.eslintrc
index 98799db6..aa104244 100644
--- a/client/.eslintrc
+++ b/client/.eslintrc
@@ -45,9 +45,7 @@
}],
"class-methods-use-this": "off",
"no-shadow": "off",
- "camelcase": ["error", {
- "properties": "never"
- }],
+ "camelcase": "off",
"no-console": ["warn", { "allow": ["warn", "error"] }],
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
"import/prefer-default-export": "off"
diff --git a/client/package-lock.json b/client/package-lock.json
index 585d2b4c..fc06a0d6 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -388,6 +388,12 @@
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
"dev": true
},
+ "ansi-colors": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
+ "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
+ "dev": true
+ },
"ansi-escapes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
@@ -493,9 +499,9 @@
"dev": true
},
"array-flatten": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz",
- "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
+ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
"dev": true
},
"array-includes": {
@@ -1798,21 +1804,21 @@
"dev": true
},
"body-parser": {
- "version": "1.18.2",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
- "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
+ "version": "1.18.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
+ "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
"dev": true,
"requires": {
"bytes": "3.0.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
- "depd": "~1.1.1",
- "http-errors": "~1.6.2",
- "iconv-lite": "0.4.19",
+ "depd": "~1.1.2",
+ "http-errors": "~1.6.3",
+ "iconv-lite": "0.4.23",
"on-finished": "~2.3.0",
- "qs": "6.5.1",
- "raw-body": "2.3.2",
- "type-is": "~1.6.15"
+ "qs": "6.5.2",
+ "raw-body": "2.3.3",
+ "type-is": "~1.6.16"
},
"dependencies": {
"debug": {
@@ -1825,16 +1831,13 @@
}
},
"iconv-lite": {
- "version": "0.4.19",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
- "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
- "dev": true
- },
- "qs": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
- "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
- "dev": true
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
}
}
},
@@ -2900,12 +2903,12 @@
"dev": true
},
"compressible": {
- "version": "2.0.14",
- "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz",
- "integrity": "sha1-MmxfUH+7BV9UEWeCuWmoG2einac=",
+ "version": "2.0.15",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz",
+ "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==",
"dev": true,
"requires": {
- "mime-db": ">= 1.34.0 < 2"
+ "mime-db": ">= 1.36.0 < 2"
}
},
"compression": {
@@ -2966,9 +2969,9 @@
}
},
"connect-history-api-fallback": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz",
- "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
+ "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
"dev": true
},
"console-browserify": {
@@ -3679,6 +3682,46 @@
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
},
+ "default-gateway": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz",
+ "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==",
+ "dev": true,
+ "requires": {
+ "execa": "^0.10.0",
+ "ip-regex": "^2.1.0"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "execa": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz",
+ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^3.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ }
+ }
+ },
"default-require-extensions": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz",
@@ -3817,9 +3860,9 @@
}
},
"detect-node": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz",
- "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
+ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
"dev": true
},
"diff": {
@@ -4681,12 +4724,12 @@
"dev": true
},
"eventsource": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz",
- "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
+ "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
"dev": true,
"requires": {
- "original": ">=0.0.5"
+ "original": "^1.0.0"
}
},
"evp_bytestokey": {
@@ -4756,14 +4799,14 @@
}
},
"express": {
- "version": "4.16.3",
- "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz",
- "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
+ "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
"dev": true,
"requires": {
"accepts": "~1.3.5",
"array-flatten": "1.1.1",
- "body-parser": "1.18.2",
+ "body-parser": "1.18.3",
"content-disposition": "0.5.2",
"content-type": "~1.0.4",
"cookie": "0.3.1",
@@ -4780,10 +4823,10 @@
"on-finished": "~2.3.0",
"parseurl": "~1.3.2",
"path-to-regexp": "0.1.7",
- "proxy-addr": "~2.0.3",
- "qs": "6.5.1",
+ "proxy-addr": "~2.0.4",
+ "qs": "6.5.2",
"range-parser": "~1.2.0",
- "safe-buffer": "5.1.1",
+ "safe-buffer": "5.1.2",
"send": "0.16.2",
"serve-static": "1.13.2",
"setprototypeof": "1.1.0",
@@ -4813,18 +4856,6 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
"dev": true
- },
- "qs": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
- "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
- "dev": true
- },
- "safe-buffer": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
- "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
- "dev": true
}
}
},
@@ -5598,7 +5629,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -5619,12 +5651,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5639,17 +5673,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -5766,7 +5803,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -5778,6 +5816,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -5792,6 +5831,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -5799,12 +5839,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -5823,6 +5865,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -5903,7 +5946,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -5915,6 +5959,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -6000,7 +6045,8 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -6036,6 +6082,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -6055,6 +6102,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -6098,12 +6146,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
@@ -6265,9 +6315,9 @@
"integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
},
"handle-thing": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
- "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz",
+ "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==",
"dev": true
},
"handlebars": {
@@ -6680,9 +6730,9 @@
}
},
"http-parser-js": {
- "version": "0.4.13",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz",
- "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz",
+ "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==",
"dev": true
},
"http-proxy": {
@@ -6697,17 +6747,247 @@
}
},
"http-proxy-middleware": {
- "version": "0.17.4",
- "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz",
- "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=",
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz",
+ "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==",
"dev": true,
"requires": {
"http-proxy": "^1.16.2",
- "is-glob": "^3.1.0",
- "lodash": "^4.17.2",
- "micromatch": "^2.3.11"
+ "is-glob": "^4.0.0",
+ "lodash": "^4.17.5",
+ "micromatch": "^3.1.9"
},
"dependencies": {
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -6715,12 +6995,65 @@
"dev": true
},
"is-glob": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
- "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
+ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
"dev": true,
"requires": {
- "is-extglob": "^2.1.0"
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
}
}
}
@@ -6826,13 +7159,67 @@
"dev": true
},
"import-local": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/import-local/-/import-local-0.1.1.tgz",
- "integrity": "sha1-sReVcqrNwRxqkQCftDDbyrX2aKg=",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+ "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
"dev": true,
"requires": {
- "pkg-dir": "^2.0.0",
+ "pkg-dir": "^3.0.0",
"resolve-cwd": "^2.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz",
+ "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
+ "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ }
}
},
"imurmurhash": {
@@ -6915,188 +7302,13 @@
}
},
"internal-ip": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz",
- "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz",
+ "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==",
"dev": true,
"requires": {
- "meow": "^3.3.0"
- },
- "dependencies": {
- "camelcase": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
- "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
- "dev": true
- },
- "camelcase-keys": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
- "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
- "dev": true,
- "requires": {
- "camelcase": "^2.0.0",
- "map-obj": "^1.0.0"
- }
- },
- "find-up": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
- "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
- "dev": true,
- "requires": {
- "path-exists": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- }
- },
- "get-stdin": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
- "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
- "dev": true
- },
- "indent-string": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
- "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
- "dev": true,
- "requires": {
- "repeating": "^2.0.0"
- }
- },
- "load-json-file": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
- "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "parse-json": "^2.2.0",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0",
- "strip-bom": "^2.0.0"
- }
- },
- "map-obj": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
- "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
- "dev": true
- },
- "meow": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
- "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
- "dev": true,
- "requires": {
- "camelcase-keys": "^2.0.0",
- "decamelize": "^1.1.2",
- "loud-rejection": "^1.0.0",
- "map-obj": "^1.0.1",
- "minimist": "^1.1.3",
- "normalize-package-data": "^2.3.4",
- "object-assign": "^4.0.1",
- "read-pkg-up": "^1.0.1",
- "redent": "^1.0.0",
- "trim-newlines": "^1.0.0"
- }
- },
- "minimist": {
- "version": "1.2.0",
- "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
- "dev": true
- },
- "parse-json": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
- "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
- "dev": true,
- "requires": {
- "error-ex": "^1.2.0"
- }
- },
- "path-exists": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
- "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
- "dev": true,
- "requires": {
- "pinkie-promise": "^2.0.0"
- }
- },
- "path-type": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
- "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- }
- },
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
- "dev": true
- },
- "read-pkg": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
- "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
- "dev": true,
- "requires": {
- "load-json-file": "^1.0.0",
- "normalize-package-data": "^2.3.2",
- "path-type": "^1.0.0"
- }
- },
- "read-pkg-up": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
- "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
- "dev": true,
- "requires": {
- "find-up": "^1.0.0",
- "read-pkg": "^1.0.0"
- }
- },
- "redent": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
- "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
- "dev": true,
- "requires": {
- "indent-string": "^2.1.0",
- "strip-indent": "^1.0.1"
- }
- },
- "strip-bom": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
- "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
- "dev": true,
- "requires": {
- "is-utf8": "^0.2.0"
- }
- },
- "strip-indent": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
- "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
- "dev": true,
- "requires": {
- "get-stdin": "^4.0.1"
- }
- },
- "trim-newlines": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
- "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
- "dev": true
- }
+ "default-gateway": "^2.6.0",
+ "ipaddr.js": "^1.5.2"
}
},
"interpret": {
@@ -7125,6 +7337,12 @@
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
"dev": true
},
+ "ip-regex": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
+ "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
+ "dev": true
+ },
"ipaddr.js": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
@@ -8494,9 +8712,9 @@
"dev": true
},
"killable": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz",
- "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
+ "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==",
"dev": true
},
"kind-of": {
@@ -8776,6 +8994,15 @@
"tmpl": "1.0.x"
}
},
+ "map-age-cleaner": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
+ "dev": true,
+ "requires": {
+ "p-defer": "^1.0.0"
+ }
+ },
"map-cache": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
@@ -8912,9 +9139,9 @@
}
},
"merge": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz",
- "integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo=",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz",
+ "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==",
"dev": true
},
"merge-descriptors": {
@@ -9194,6 +9421,12 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true
},
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
"no-case": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
@@ -9507,9 +9740,9 @@
}
},
"opn": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
- "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==",
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz",
+ "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==",
"dev": true,
"requires": {
"is-wsl": "^1.1.0"
@@ -9583,12 +9816,24 @@
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
+ "p-defer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
+ "dev": true
+ },
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
},
+ "p-is-promise": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
+ "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=",
+ "dev": true
+ },
"p-limit": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
@@ -9826,9 +10071,9 @@
"dev": true
},
"portfinder": {
- "version": "1.0.17",
- "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.17.tgz",
- "integrity": "sha512-syFcRIRzVI1BoEFOCaAiizwDolh1S1YXSodsVhncbhjzjZQulhczNRbqnUl9N31Q4dKGOXsNDqxC2BWBgSMqeQ==",
+ "version": "1.0.20",
+ "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz",
+ "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==",
"dev": true,
"requires": {
"async": "^1.5.2",
@@ -12745,9 +12990,9 @@
"dev": true
},
"querystringify": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz",
- "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz",
+ "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==",
"dev": true
},
"quick-lru": {
@@ -12828,46 +13073,25 @@
"dev": true
},
"raw-body": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
- "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+ "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
"dev": true,
"requires": {
"bytes": "3.0.0",
- "http-errors": "1.6.2",
- "iconv-lite": "0.4.19",
+ "http-errors": "1.6.3",
+ "iconv-lite": "0.4.23",
"unpipe": "1.0.0"
},
"dependencies": {
- "depd": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
- "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=",
- "dev": true
- },
- "http-errors": {
- "version": "1.6.2",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
- "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
+ "iconv-lite": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
"dev": true,
"requires": {
- "depd": "1.1.1",
- "inherits": "2.0.3",
- "setprototypeof": "1.0.3",
- "statuses": ">= 1.3.1 < 2"
+ "safer-buffer": ">= 2.1.2 < 3"
}
- },
- "iconv-lite": {
- "version": "0.4.19",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
- "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
- "dev": true
- },
- "setprototypeof": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
- "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=",
- "dev": true
}
}
},
@@ -13702,9 +13926,9 @@
"dev": true
},
"selfsigned": {
- "version": "1.10.3",
- "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.3.tgz",
- "integrity": "sha512-vmZenZ+8Al3NLHkWnhBQ0x6BkML1eCP2xEi3JE+f3D9wW9fipD9NNJHYtE9XJM4TsPaHGZJIamrSI6MTg1dU2Q==",
+ "version": "1.10.4",
+ "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz",
+ "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==",
"dev": true,
"requires": {
"node-forge": "0.7.5"
@@ -14037,44 +14261,36 @@
}
},
"sockjs": {
- "version": "0.3.18",
- "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.18.tgz",
- "integrity": "sha1-2bKJMWyn33dZXvKZ4HXw+TfrQgc=",
+ "version": "0.3.19",
+ "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
+ "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
"dev": true,
"requires": {
"faye-websocket": "^0.10.0",
- "uuid": "^2.0.2"
- },
- "dependencies": {
- "uuid": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
- "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
- "dev": true
- }
+ "uuid": "^3.0.1"
}
},
"sockjs-client": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.4.tgz",
- "integrity": "sha1-W6vjhrd15M8U51IJEUUmVAFsixI=",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz",
+ "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==",
"dev": true,
"requires": {
- "debug": "^2.6.6",
- "eventsource": "0.1.6",
- "faye-websocket": "~0.11.0",
- "inherits": "^2.0.1",
+ "debug": "^3.2.5",
+ "eventsource": "^1.0.7",
+ "faye-websocket": "~0.11.1",
+ "inherits": "^2.0.3",
"json3": "^3.3.2",
- "url-parse": "^1.1.8"
+ "url-parse": "^1.4.3"
},
"dependencies": {
"debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
- "ms": "2.0.0"
+ "ms": "^2.1.1"
}
},
"faye-websocket": {
@@ -14085,6 +14301,12 @@
"requires": {
"websocket-driver": ">=0.5.1"
}
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
}
}
},
@@ -14178,52 +14400,73 @@
"dev": true
},
"spdy": {
- "version": "3.4.7",
- "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz",
- "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz",
+ "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==",
"dev": true,
"requires": {
- "debug": "^2.6.8",
- "handle-thing": "^1.2.5",
+ "debug": "^4.1.0",
+ "handle-thing": "^2.0.0",
"http-deceiver": "^1.2.7",
- "safe-buffer": "^5.0.1",
"select-hose": "^2.0.0",
- "spdy-transport": "^2.0.18"
+ "spdy-transport": "^3.0.0"
},
"dependencies": {
"debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
- "ms": "2.0.0"
+ "ms": "^2.1.1"
}
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
}
}
},
"spdy-transport": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz",
- "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
+ "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
"dev": true,
"requires": {
- "debug": "^2.6.8",
- "detect-node": "^2.0.3",
+ "debug": "^4.1.0",
+ "detect-node": "^2.0.4",
"hpack.js": "^2.1.6",
- "obuf": "^1.1.1",
- "readable-stream": "^2.2.9",
- "safe-buffer": "^5.0.1",
- "wbuf": "^1.7.2"
+ "obuf": "^1.1.2",
+ "readable-stream": "^3.0.6",
+ "wbuf": "^1.7.3"
},
"dependencies": {
"debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
- "ms": "2.0.0"
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz",
+ "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
}
}
}
@@ -15037,15 +15280,9 @@
}
},
"thunky": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.2.tgz",
- "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=",
- "dev": true
- },
- "time-stamp": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-2.1.0.tgz",
- "integrity": "sha512-lJbq6KsFhZJtN3fPUVje1tq/hHsJOKUUcUj/MGCiQR6qWBDcyi5kxL9J7/RnaEChCn0+L/DUN2WvemDrkk4i3Q==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz",
+ "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==",
"dev": true
},
"timers-browserify": {
@@ -15559,9 +15796,9 @@
}
},
"url-parse": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz",
- "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==",
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz",
+ "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==",
"dev": true,
"requires": {
"querystringify": "^2.0.0",
@@ -16030,93 +16267,109 @@
}
},
"webpack-dev-middleware": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz",
- "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz",
+ "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==",
"dev": true,
"requires": {
"memory-fs": "~0.4.1",
- "mime": "^1.5.0",
- "path-is-absolute": "^1.0.0",
+ "mime": "^2.3.1",
"range-parser": "^1.0.3",
- "time-stamp": "^2.0.0"
- },
- "dependencies": {
- "mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "dev": true
- }
+ "webpack-log": "^2.0.0"
}
},
"webpack-dev-server": {
- "version": "2.9.4",
- "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.9.4.tgz",
- "integrity": "sha512-thrqC0EQEoSjXeYgP6pUXcUCZ+LNrKsDPn+mItLnn5VyyNZOJKd06hUP5vqkYwL8nWWXsii0loSF9NHNccT6ow==",
+ "version": "3.1.14",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz",
+ "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==",
"dev": true,
"requires": {
"ansi-html": "0.0.7",
- "array-includes": "^3.0.3",
"bonjour": "^3.5.0",
- "chokidar": "^1.6.0",
+ "chokidar": "^2.0.0",
"compression": "^1.5.2",
"connect-history-api-fallback": "^1.3.0",
"debug": "^3.1.0",
"del": "^3.0.0",
- "express": "^4.13.3",
+ "express": "^4.16.2",
"html-entities": "^1.2.0",
- "http-proxy-middleware": "~0.17.4",
- "import-local": "^0.1.1",
- "internal-ip": "1.2.0",
+ "http-proxy-middleware": "~0.18.0",
+ "import-local": "^2.0.0",
+ "internal-ip": "^3.0.1",
"ip": "^1.1.5",
"killable": "^1.0.0",
"loglevel": "^1.4.1",
"opn": "^5.1.0",
"portfinder": "^1.0.9",
+ "schema-utils": "^1.0.0",
"selfsigned": "^1.9.1",
+ "semver": "^5.6.0",
"serve-index": "^1.7.2",
- "sockjs": "0.3.18",
- "sockjs-client": "1.1.4",
- "spdy": "^3.4.1",
- "strip-ansi": "^3.0.1",
- "supports-color": "^4.2.1",
- "webpack-dev-middleware": "^1.11.0",
- "yargs": "^6.6.0"
+ "sockjs": "0.3.19",
+ "sockjs-client": "1.3.0",
+ "spdy": "^4.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^5.1.0",
+ "url": "^0.11.0",
+ "webpack-dev-middleware": "3.4.0",
+ "webpack-log": "^2.0.0",
+ "yargs": "12.0.2"
},
"dependencies": {
- "camelcase": {
+ "ansi-regex": {
"version": "3.0.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
- "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
- "chokidar": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
- "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
- "dev": true,
- "requires": {
- "anymatch": "^1.3.0",
- "async-each": "^1.0.0",
- "fsevents": "^1.0.0",
- "glob-parent": "^2.0.0",
- "inherits": "^2.0.1",
- "is-binary-path": "^1.0.0",
- "is-glob": "^2.0.0",
- "path-is-absolute": "^1.0.0",
- "readdirp": "^2.0.0"
- }
+ "camelcase": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+ "dev": true
},
"cliui": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
- "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
"dev": true,
"requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0",
"wrap-ansi": "^2.0.0"
+ },
+ "dependencies": {
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ }
+ }
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "decamelize": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz",
+ "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==",
+ "dev": true,
+ "requires": {
+ "xregexp": "4.0.0"
}
},
"del": {
@@ -16133,14 +16386,37 @@
"rimraf": "^2.2.8"
}
},
- "find-up": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
- "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
"dev": true,
"requires": {
- "path-exists": "^2.0.0",
- "pinkie-promise": "^2.0.0"
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
}
},
"globby": {
@@ -16164,167 +16440,151 @@
}
}
},
- "has-flag": {
+ "invert-kv": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
- "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
"dev": true
},
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "load-json-file": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
- "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "parse-json": "^2.2.0",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0",
- "strip-bom": "^2.0.0"
- },
- "dependencies": {
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
- "dev": true
- }
- }
- },
- "parse-json": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
- "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
- "dev": true,
- "requires": {
- "error-ex": "^1.2.0"
- }
- },
- "path-exists": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
- "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
- "dev": true,
- "requires": {
- "pinkie-promise": "^2.0.0"
- }
- },
- "path-type": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
- "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- },
- "dependencies": {
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
- "dev": true
- }
- }
- },
- "read-pkg": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
- "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
- "dev": true,
- "requires": {
- "load-json-file": "^1.0.0",
- "normalize-package-data": "^2.3.2",
- "path-type": "^1.0.0"
- }
- },
- "read-pkg-up": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
- "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
- "dev": true,
- "requires": {
- "find-up": "^1.0.0",
- "read-pkg": "^1.0.0"
- }
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
- "strip-bom": {
+ "lcid": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
- "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
"dev": true,
"requires": {
- "is-utf8": "^0.2.0"
+ "invert-kv": "^2.0.0"
}
},
- "supports-color": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
- "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
- "has-flag": "^2.0.0"
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
}
},
- "y18n": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
- "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
+ "mem": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz",
+ "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==",
+ "dev": true,
+ "requires": {
+ "map-age-cleaner": "^0.1.1",
+ "mimic-fn": "^1.0.0",
+ "p-is-promise": "^1.1.0"
+ }
+ },
+ "os-locale": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+ "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
+ "dev": true,
+ "requires": {
+ "execa": "^1.0.0",
+ "lcid": "^2.0.0",
+ "mem": "^4.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz",
+ "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
+ "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
+ "dev": true
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ },
+ "semver": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
+ "dev": true
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"yargs": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz",
- "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=",
+ "version": "12.0.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz",
+ "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==",
"dev": true,
"requires": {
- "camelcase": "^3.0.0",
- "cliui": "^3.2.0",
- "decamelize": "^1.1.1",
+ "cliui": "^4.0.0",
+ "decamelize": "^2.0.0",
+ "find-up": "^3.0.0",
"get-caller-file": "^1.0.1",
- "os-locale": "^1.4.0",
- "read-pkg-up": "^1.0.1",
+ "os-locale": "^3.0.0",
"require-directory": "^2.1.1",
"require-main-filename": "^1.0.1",
"set-blocking": "^2.0.0",
- "string-width": "^1.0.2",
- "which-module": "^1.0.0",
- "y18n": "^3.2.1",
- "yargs-parser": "^4.2.0"
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^10.1.0"
}
},
"yargs-parser": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz",
- "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=",
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
+ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
"dev": true,
"requires": {
- "camelcase": "^3.0.0"
+ "camelcase": "^4.1.0"
}
}
}
},
+ "webpack-log": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
+ "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^3.0.0",
+ "uuid": "^3.3.2"
+ }
+ },
"webpack-merge": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.4.tgz",
@@ -16513,6 +16773,12 @@
"sax": "^1.2.1"
}
},
+ "xregexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz",
+ "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==",
+ "dev": true
+ },
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
diff --git a/client/package.json b/client/package.json
index 6b518d60..a5cf9e3d 100644
--- a/client/package.json
+++ b/client/package.json
@@ -73,7 +73,7 @@
"uglifyjs-webpack-plugin": "^1.2.7",
"url-loader": "^1.0.1",
"webpack": "3.8.1",
- "webpack-dev-server": "2.9.4",
+ "webpack-dev-server": "^3.1.14",
"webpack-merge": "^4.1.3"
}
}
diff --git a/client/public/index.html b/client/public/index.html
index e6b99e20..6b003a23 100644
--- a/client/public/index.html
+++ b/client/public/index.html
@@ -1,16 +1,16 @@
-
-
-
-
-
- AdGuard Home
-
-
-
- You need to enable JavaScript to run this app.
-
-
-
+
+
+
+
+
+ AdGuard Home
+
+
+
+ You need to enable JavaScript to run this app.
+
+
+
diff --git a/client/public/install.html b/client/public/install.html
new file mode 100644
index 00000000..45065fe3
--- /dev/null
+++ b/client/public/install.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ Setup AdGuard Home
+
+
+
+ You need to enable JavaScript to run this app.
+
+
+
+
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 10471501..2bae1d12 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -28,6 +28,7 @@
"dhcp_ip_addresses": "IP addresses",
"dhcp_table_hostname": "Hostname",
"dhcp_table_expires": "Expires",
+ "dhcp_warning": "If you want to enable the built-in DHCP server, make sure that there is no other active DHCP server. Otherwise, it can break the internet for connected devices!",
"back": "Back",
"dashboard": "Dashboard",
"settings": "Settings",
@@ -157,5 +158,57 @@
"category_label": "Category",
"rule_label": "Rule",
"filter_label": "Filter",
- "unknown_filter": "Unknown filter {{filterId}}"
+ "unknown_filter": "Unknown filter {{filterId}}",
+ "install_welcome_title": "Welcome to AdGuard Home!",
+ "install_welcome_desc": "AdGuard Home is a network-wide ad-and-tracker blocking DNS server. Its purpose is to let you control your entire network and all your devices, and it does not require using a client-side program.",
+ "install_settings_title": "Admin Web Interface",
+ "install_settings_listen": "Listen interface",
+ "install_settings_port": "Port",
+ "install_settings_interface_link": "Your AdGuard Home admin web interface will be available on the following addresses:",
+ "form_error_port": "Enter valid port value",
+ "install_settings_dns": "DNS server",
+ "install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server on the following addresses:",
+ "install_settings_all_interfaces": "All interfaces",
+ "install_auth_title": "Authentication",
+ "install_auth_desc": "It is highly recommended to configure password authentication to your AdGuard Home admin web interface. Even if it is accessible only in your local network, it is still important to have it protected from unrestricted access.",
+ "install_auth_username": "Username",
+ "install_auth_password": "Password",
+ "install_auth_confirm": "Confirm password",
+ "install_auth_username_enter": "Enter username",
+ "install_auth_password_enter": "Enter password",
+ "install_step": "Step",
+ "install_devices_title": "Configure your devices",
+ "install_devices_desc": "In order for AdGuard Home to start working, you need to configure your devices to use it.",
+ "install_submit_title": "Congratulations!",
+ "install_submit_desc": "The setup procedure is finished and you are ready to start using AdGuard Home.",
+ "install_devices_router": "Router",
+ "install_devices_router_desc": "This setup will automatically cover all the devices connected to your home router and you will not need to configure each of them manually.",
+ "install_devices_address": "AdGuard Home DNS server is listening to the following addresses",
+ "install_devices_router_list_1": "Open the preferences for your router. Usually, you can access it from your browser via a URL (like http://192.168.0.1/ or http://192.168.1.1/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer/phone.",
+ "install_devices_router_list_2": "Find the DHCP/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.",
+ "install_devices_router_list_3": "Enter your AdGuard Home server addresses there.",
+ "install_devices_windows_list_1": "Open Control Panel through Start menu or Windows search.",
+ "install_devices_windows_list_2": "Go to Network and Internet category and then to Network and Sharing Center.",
+ "install_devices_windows_list_3": "On the left side of the screen find Change adapter settings and click on it.",
+ "install_devices_windows_list_4": "Select your active connection, right-click on it and choose Properties.",
+ "install_devices_windows_list_5": "Find Internet Protocol Version 4 (TCP/IP) in the list, select it and then click on Properties again.",
+ "install_devices_windows_list_6": "Choose Use the following DNS server addresses and enter your AdGuard Home server addresses.",
+ "install_devices_macos_list_1": "Click on Apple icon and go to System Preferences.",
+ "install_devices_macos_list_2": "Click on Network.",
+ "install_devices_macos_list_3": "Select the first connection in your list and click Advanced.",
+ "install_devices_macos_list_4": "Select the DNS tab and enter your AdGuard Home server addresses.",
+ "install_devices_android_list_1": "From the Android Menu home screen, tap Settings.",
+ "install_devices_android_list_2": "Tap Wi-Fi on the menu. The screen listing all of the available networks will be shown (it is impossible to set custom DNS for mobile connection).",
+ "install_devices_android_list_3": "Long press the network you're connected to, and tap Modify Network.",
+ "install_devices_android_list_4": "On some devices, you may need to check the box for Advanced to see further settings. To adjust your Android DNS settings, you will need to switch the IP settings from DHCP to Static.",
+ "install_devices_android_list_5": "Change set DNS 1 and DNS 2 values to your AdGuard Home server addresses.",
+ "install_devices_ios_list_1": "From the home screen, tap Settings.",
+ "install_devices_ios_list_2": "Choose Wi-Fi in the left menu (it is impossible to configure DNS for mobile networks).",
+ "install_devices_ios_list_3": "Tap on the name of the currently active network.",
+ "install_devices_ios_list_4": "In the DNS field enter your AdGuard Home server addresses.",
+ "get_started": "Get Started",
+ "next": "Next",
+ "open_dashboard": "Open Dashboard",
+ "install_saved": "All settings saved",
+ "form_error_password": "Password mismatched"
}
\ No newline at end of file
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index e6a2a6c8..1bb99064 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -4,6 +4,7 @@ import { t } from 'i18next';
import { showLoading, hideLoading } from 'react-redux-loading-bar';
import { normalizeHistory, normalizeFilteringStatus, normalizeLogs } from '../helpers/helpers';
+import { SETTINGS_NAMES } from '../helpers/constants';
import Api from '../api/Api';
const apiClient = new Api();
@@ -18,9 +19,8 @@ export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
export const toggleSetting = (settingKey, status) => async (dispatch) => {
let successMessage = '';
try {
- // TODO move setting keys to constants
switch (settingKey) {
- case 'filtering':
+ case SETTINGS_NAMES.filtering:
if (status) {
successMessage = 'disabled_filtering_toast';
await apiClient.disableFiltering();
@@ -30,7 +30,7 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
}
dispatch(toggleSettingStatus({ settingKey }));
break;
- case 'safebrowsing':
+ case SETTINGS_NAMES.safebrowsing:
if (status) {
successMessage = 'disabled_safe_browsing_toast';
await apiClient.disableSafebrowsing();
@@ -40,7 +40,7 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
}
dispatch(toggleSettingStatus({ settingKey }));
break;
- case 'parental':
+ case SETTINGS_NAMES.parental:
if (status) {
successMessage = 'disabled_parental_toast';
await apiClient.disableParentalControl();
@@ -50,7 +50,7 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
}
dispatch(toggleSettingStatus({ settingKey }));
break;
- case 'safesearch':
+ case SETTINGS_NAMES.safesearch:
if (status) {
successMessage = 'disabled_safe_search_toast';
await apiClient.disableSafesearch();
@@ -434,7 +434,6 @@ export const downloadQueryLogRequest = createAction('DOWNLOAD_QUERY_LOG_REQUEST'
export const downloadQueryLogFailure = createAction('DOWNLOAD_QUERY_LOG_FAILURE');
export const downloadQueryLogSuccess = createAction('DOWNLOAD_QUERY_LOG_SUCCESS');
-// TODO create some common flasher with all server errors
export const downloadQueryLog = () => async (dispatch) => {
let data;
dispatch(downloadQueryLogRequest());
@@ -573,36 +572,40 @@ export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
// TODO rewrite findActiveDhcp part
-export const setDhcpConfig = config => async (dispatch) => {
+export const setDhcpConfig = values => async (dispatch, getState) => {
+ const { config } = getState().dhcp;
+ const updatedConfig = { ...config, ...values };
dispatch(setDhcpConfigRequest());
- try {
- if (config.interface_name) {
- dispatch(findActiveDhcpRequest());
- try {
- const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
- dispatch(findActiveDhcpSuccess(activeDhcp));
-
- if (!activeDhcp.found) {
- await apiClient.setDhcpConfig(config);
+ if (values.interface_name) {
+ dispatch(findActiveDhcpRequest());
+ try {
+ const activeDhcp = await apiClient.findActiveDhcp(values.interface_name);
+ dispatch(findActiveDhcpSuccess(activeDhcp));
+ if (!activeDhcp.found) {
+ try {
+ await apiClient.setDhcpConfig(updatedConfig);
+ dispatch(setDhcpConfigSuccess(updatedConfig));
dispatch(addSuccessToast('dhcp_config_saved'));
- dispatch(setDhcpConfigSuccess());
- dispatch(getDhcpStatus());
- } else {
- dispatch(addErrorToast({ error: 'dhcp_found' }));
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(setDhcpConfigFailure());
}
- } catch (error) {
- dispatch(addErrorToast({ error }));
- dispatch(findActiveDhcpFailure());
+ } else {
+ dispatch(addErrorToast({ error: 'dhcp_found' }));
}
- } else {
- await apiClient.setDhcpConfig(config);
- dispatch(addSuccessToast('dhcp_config_saved'));
- dispatch(setDhcpConfigSuccess());
- dispatch(getDhcpStatus());
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(findActiveDhcpFailure());
+ }
+ } else {
+ try {
+ await apiClient.setDhcpConfig(updatedConfig);
+ dispatch(setDhcpConfigSuccess(updatedConfig));
+ dispatch(addSuccessToast('dhcp_config_saved'));
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(setDhcpConfigFailure());
}
- } catch (error) {
- dispatch(addErrorToast({ error }));
- dispatch(setDhcpConfigFailure());
}
};
@@ -615,11 +618,10 @@ export const toggleDhcp = config => async (dispatch) => {
dispatch(toggleDhcpRequest());
if (config.enabled) {
- dispatch(addSuccessToast('disabled_dhcp'));
try {
await apiClient.setDhcpConfig({ ...config, enabled: false });
dispatch(toggleDhcpSuccess());
- dispatch(getDhcpStatus());
+ dispatch(addSuccessToast('disabled_dhcp'));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleDhcpFailure());
@@ -634,12 +636,11 @@ export const toggleDhcp = config => async (dispatch) => {
try {
await apiClient.setDhcpConfig({ ...config, enabled: true });
dispatch(toggleDhcpSuccess());
- dispatch(getDhcpStatus());
+ dispatch(addSuccessToast('enabled_dhcp'));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleDhcpFailure());
}
- dispatch(addSuccessToast('enabled_dhcp'));
} else {
dispatch(addErrorToast({ error: 'dhcp_found' }));
}
diff --git a/client/src/actions/install.js b/client/src/actions/install.js
new file mode 100644
index 00000000..c738f75d
--- /dev/null
+++ b/client/src/actions/install.js
@@ -0,0 +1,46 @@
+import { createAction } from 'redux-actions';
+import Api from '../api/Api';
+import { addErrorToast, addSuccessToast } from './index';
+
+const apiClient = new Api();
+
+export const nextStep = createAction('NEXT_STEP');
+export const prevStep = createAction('PREV_STEP');
+
+export const getDefaultAddressesRequest = createAction('GET_DEFAULT_ADDRESSES_REQUEST');
+export const getDefaultAddressesFailure = createAction('GET_DEFAULT_ADDRESSES_FAILURE');
+export const getDefaultAddressesSuccess = createAction('GET_DEFAULT_ADDRESSES_SUCCESS');
+
+export const getDefaultAddresses = () => async (dispatch) => {
+ dispatch(getDefaultAddressesRequest());
+ try {
+ const addresses = await apiClient.getDefaultAddresses();
+ dispatch(getDefaultAddressesSuccess(addresses));
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(getDefaultAddressesFailure());
+ }
+};
+
+export const setAllSettingsRequest = createAction('SET_ALL_SETTINGS_REQUEST');
+export const setAllSettingsFailure = createAction('SET_ALL_SETTINGS_FAILURE');
+export const setAllSettingsSuccess = createAction('SET_ALL_SETTINGS_SUCCESS');
+
+export const setAllSettings = values => async (dispatch) => {
+ dispatch(setAllSettingsRequest());
+ try {
+ const {
+ confirm_password,
+ ...config
+ } = values;
+
+ await apiClient.setAllSettings(config);
+ dispatch(setAllSettingsSuccess());
+ dispatch(addSuccessToast('install_saved'));
+ dispatch(nextStep());
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(setAllSettingsFailure());
+ dispatch(prevStep());
+ }
+};
diff --git a/client/src/api/Api.js b/client/src/api/Api.js
index 4592fae7..0dac781a 100644
--- a/client/src/api/Api.js
+++ b/client/src/api/Api.js
@@ -336,4 +336,22 @@ export default class Api {
};
return this.makeRequest(path, method, parameters);
}
+
+ // Installation
+ INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
+ INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
+
+ getDefaultAddresses() {
+ const { path, method } = this.INSTALL_GET_ADDRESSES;
+ return this.makeRequest(path, method);
+ }
+
+ setAllSettings(config) {
+ const { path, method } = this.INSTALL_CONFIGURE;
+ const parameters = {
+ data: config,
+ headers: { 'Content-Type': 'application/json' },
+ };
+ return this.makeRequest(path, method, parameters);
+ }
}
diff --git a/client/src/components/App/index.css b/client/src/components/App/index.css
index 61ee53a4..391ddabd 100644
--- a/client/src/components/App/index.css
+++ b/client/src/components/App/index.css
@@ -1,7 +1,7 @@
body {
margin: 0;
padding: 0;
- font-family: sans-serif;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}
.status {
@@ -26,3 +26,7 @@ body {
height: 3px;
background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%);
}
+
+.hidden {
+ display: none;
+}
diff --git a/client/src/components/Filters/UserRules.js b/client/src/components/Filters/UserRules.js
index 9aae0281..7a251f3a 100644
--- a/client/src/components/Filters/UserRules.js
+++ b/client/src/components/Filters/UserRules.js
@@ -25,7 +25,7 @@ class UserRules extends Component {
diff --git a/client/src/components/Filters/index.js b/client/src/components/Filters/index.js
index 339bc673..daf5b6e9 100644
--- a/client/src/components/Filters/index.js
+++ b/client/src/components/Filters/index.js
@@ -84,7 +84,7 @@ class Filters extends Component {
columns={this.columns}
showPagination={true}
defaultPageSize={10}
- minRows={4} // TODO find out what to show if rules.length is 0
+ minRows={4}
// Text
previousText={ t('previous_btn') }
nextText={ t('next_btn') }
@@ -96,14 +96,14 @@ class Filters extends Component {
/>
add_filter_btn
{
const {
t,
handleSubmit,
- pristine,
submitting,
+ invalid,
+ processingConfig,
} = props;
return (
@@ -124,8 +125,8 @@ const Form = (props) => {
{t('save_config')}
@@ -135,11 +136,11 @@ const Form = (props) => {
Form.propTypes = {
handleSubmit: PropTypes.func,
- pristine: PropTypes.bool,
submitting: PropTypes.bool,
+ invalid: PropTypes.bool,
interfaces: PropTypes.object,
- processing: PropTypes.bool,
initialValues: PropTypes.object,
+ processingConfig: PropTypes.bool,
t: PropTypes.func,
};
diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js
index eed256a6..5335586b 100644
--- a/client/src/components/Settings/Dhcp/index.js
+++ b/client/src/components/Settings/Dhcp/index.js
@@ -13,17 +13,14 @@ class Dhcp extends Component {
this.props.setDhcpConfig(values);
};
- handleFormChange = (value) => {
- this.props.setDhcpConfig(value);
- }
-
handleToggle = (config) => {
this.props.toggleDhcp(config);
- this.props.findActiveDhcp(config.interface_name);
}
getToggleDhcpButton = () => {
- const { config, active, processingDhcp } = this.props.dhcp;
+ const {
+ config, active, processingDhcp, processingConfig,
+ } = this.props.dhcp;
const activeDhcpFound = active && active.found;
const filledConfig = Object.keys(config).every((key) => {
if (key === 'enabled') {
@@ -37,9 +34,9 @@ class Dhcp extends Component {
return (
this.props.toggleDhcp(config)}
- disabled={processingDhcp}
+ disabled={processingDhcp || processingConfig}
>
dhcp_disable
@@ -49,9 +46,14 @@ class Dhcp extends Component {
return (
this.handleToggle(config)}
- disabled={!filledConfig || activeDhcpFound || processingDhcp}
+ disabled={
+ !filledConfig
+ || activeDhcpFound
+ || processingDhcp
+ || processingConfig
+ }
>
dhcp_enable
@@ -64,14 +66,14 @@ class Dhcp extends Component {
if (active) {
if (active.error) {
return (
-
+
{active.error}
);
}
return (
-
+
{active.found ? (
dhcp_found
@@ -81,7 +83,7 @@ class Dhcp extends Component {
dhcp_not_found
)}
-
+
);
}
@@ -91,9 +93,14 @@ class Dhcp extends Component {
render() {
const { t, dhcp } = this.props;
const statusButtonClass = classnames({
- 'btn btn-primary btn-standart': true,
- 'btn btn-primary btn-standart btn-loading': dhcp.processingStatus,
+ 'btn btn-primary btn-standard': true,
+ 'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
});
+ const {
+ enabled,
+ interface_name,
+ ...values
+ } = dhcp.config;
return (
@@ -102,17 +109,17 @@ class Dhcp extends Component {
{!dhcp.processing &&
@@ -123,12 +130,18 @@ class Dhcp extends Component {
onClick={() =>
this.props.findActiveDhcp(dhcp.config.interface_name)
}
- disabled={!dhcp.config.interface_name}
+ disabled={
+ !dhcp.config.interface_name
+ || dhcp.processingConfig
+ }
>
check_dhcp_servers
{this.getActiveDhcpMessage()}
+
+ dhcp_warning
+
}
diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css
index a4ff2394..9530ef36 100644
--- a/client/src/components/Settings/Settings.css
+++ b/client/src/components/Settings/Settings.css
@@ -11,7 +11,7 @@
margin-bottom: 15px;
}
-.btn-standart {
+.btn-standard {
padding-left: 20px;
padding-right: 20px;
}
diff --git a/client/src/components/Settings/Upstream.js b/client/src/components/Settings/Upstream.js
index 4ae0598a..fe24c4d7 100644
--- a/client/src/components/Settings/Upstream.js
+++ b/client/src/components/Settings/Upstream.js
@@ -21,8 +21,8 @@ class Upstream extends Component {
render() {
const testButtonClass = classnames({
- 'btn btn-primary btn-standart mr-2': true,
- 'btn btn-primary btn-standart mr-2 btn-loading': this.props.processingTestUpstream,
+ 'btn btn-primary btn-standard mr-2': true,
+ 'btn btn-primary btn-standard mr-2 btn-loading': this.props.processingTestUpstream,
});
const { t } = this.props;
@@ -49,7 +49,7 @@ class Upstream extends Component {
test_upstream_btn
diff --git a/client/src/components/ui/Icons.js b/client/src/components/ui/Icons.js
new file mode 100644
index 00000000..dec6f0cd
--- /dev/null
+++ b/client/src/components/ui/Icons.js
@@ -0,0 +1,27 @@
+import React from 'react';
+
+const Icons = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default Icons;
diff --git a/client/src/components/ui/Tab.js b/client/src/components/ui/Tab.js
new file mode 100644
index 00000000..1498e3ab
--- /dev/null
+++ b/client/src/components/ui/Tab.js
@@ -0,0 +1,41 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import classnames from 'classnames';
+
+class Tab extends Component {
+ handleClick = () => {
+ this.props.onClick(this.props.label);
+ }
+
+ render() {
+ const {
+ activeTab,
+ label,
+ } = this.props;
+
+ const tabClass = classnames({
+ tab__control: true,
+ 'tab__control--active': activeTab === label,
+ });
+
+ return (
+
+
+
+
+ {label}
+
+ );
+ }
+}
+
+Tab.propTypes = {
+ activeTab: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ onClick: PropTypes.func.isRequired,
+};
+
+export default Tab;
diff --git a/client/src/components/ui/Tabler.css b/client/src/components/ui/Tabler.css
index 82ab58d4..afe6dbc1 100644
--- a/client/src/components/ui/Tabler.css
+++ b/client/src/components/ui/Tabler.css
@@ -3783,7 +3783,7 @@ tbody.collapse.show {
line-height: 1.5;
color: #495057;
vertical-align: middle;
- background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'%3E%3Cpath fill='#999' d='M0 0L10 0L5 5L0 0'/%3E%3C/svg%3E") no-repeat right 0.75rem center;
+ background: #fff url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMCA1Jz48cGF0aCBmaWxsPScjOTk5JyBkPSdNMCAwTDEwIDBMNSA1TDAgMCcvPjwvc3ZnPg==") no-repeat right 0.75rem center;
background-size: 8px 10px;
border: 1px solid rgba(0, 40, 100, 0.12);
border-radius: 3px;
diff --git a/client/src/components/ui/Tabs.css b/client/src/components/ui/Tabs.css
new file mode 100644
index 00000000..9c7c567f
--- /dev/null
+++ b/client/src/components/ui/Tabs.css
@@ -0,0 +1,51 @@
+.tabs__controls {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+ padding: 15px 0;
+ border-bottom: 1px solid #e8e8e8;
+}
+
+.tab__control {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-width: 70px;
+ font-size: 13px;
+ color: #555555;
+ cursor: pointer;
+ opacity: 0.6;
+}
+
+.tab__control:hover,
+.tab__control:focus {
+ opacity: 1;
+}
+
+.tab__control--active {
+ font-weight: 700;
+ color: #4a4a4a;
+ opacity: 1;
+}
+
+.tab__title {
+ margin-bottom: 10px;
+ font-size: 16px;
+ font-weight: 700;
+}
+
+.tab__icon {
+ width: 24px;
+ height: 24px;
+ margin-bottom: 6px;
+ fill: #4a4a4a;
+}
+
+.tab__text {
+ line-height: 1.7;
+}
+
+.tab__text li,
+.tab__text p {
+ margin-bottom: 5px;
+}
diff --git a/client/src/components/ui/Tabs.js b/client/src/components/ui/Tabs.js
new file mode 100644
index 00000000..f376e825
--- /dev/null
+++ b/client/src/components/ui/Tabs.js
@@ -0,0 +1,59 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import Tab from './Tab';
+import './Tabs.css';
+
+class Tabs extends Component {
+ state = {
+ activeTab: this.props.children[0].props.label,
+ };
+
+ onClickTabControl = (tab) => {
+ this.setState({ activeTab: tab });
+ }
+
+ render() {
+ const {
+ props: {
+ children,
+ },
+ state: {
+ activeTab,
+ },
+ } = this;
+
+ return (
+
+
+ {children.map((child) => {
+ const { label } = child.props;
+
+ return (
+
+ );
+ })}
+
+
+ {children.map((child) => {
+ if (child.props.label !== activeTab) {
+ return false;
+ }
+ return child.props.children;
+ })}
+
+
+ );
+ }
+}
+
+Tabs.propTypes = {
+ children: PropTypes.array.isRequired,
+};
+
+export default Tabs;
diff --git a/client/src/components/Header/logo.svg b/client/src/components/ui/svg/logo.svg
similarity index 100%
rename from client/src/components/Header/logo.svg
rename to client/src/components/ui/svg/logo.svg
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index fb7bbe64..3f3172b9 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -60,3 +60,16 @@ export const LANGUAGES = [
name: 'æ£é«”ä¸æ–‡',
},
];
+
+export const INSTALL_FIRST_STEP = 1;
+export const INSTALL_TOTAL_STEPS = 5;
+
+export const SETTINGS_NAMES = {
+ filtering: 'filtering',
+ safebrowsing: 'safebrowsing',
+ parental: 'parental',
+ safesearch: 'safesearch',
+};
+
+export const STANDARD_DNS_PORT = 53;
+export const STANDARD_WEB_PORT = 80;
diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js
index 870d320f..0630416d 100644
--- a/client/src/helpers/helpers.js
+++ b/client/src/helpers/helpers.js
@@ -4,7 +4,7 @@ import subHours from 'date-fns/sub_hours';
import addHours from 'date-fns/add_hours';
import round from 'lodash/round';
-import { STATS_NAMES } from './constants';
+import { STATS_NAMES, STANDARD_DNS_PORT, STANDARD_WEB_PORT } from './constants';
export const formatTime = (time) => {
const parsedTime = dateParse(time);
@@ -85,3 +85,58 @@ export const getPercent = (amount, number) => {
};
export const captitalizeWords = text => text.split(/[ -_]/g).map(str => str.charAt(0).toUpperCase() + str.substr(1)).join(' ');
+
+export const getInterfaceIp = (option) => {
+ const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
+ let interfaceIP = option.ip_addresses[0];
+
+ if (!onlyIPv6) {
+ option.ip_addresses.forEach((ip) => {
+ if (!ip.includes(':')) {
+ interfaceIP = ip;
+ }
+ });
+ }
+
+ return interfaceIP;
+};
+
+export const getIpList = (interfaces) => {
+ let list = [];
+
+ Object.keys(interfaces).forEach((item) => {
+ list = [...list, ...interfaces[item].ip_addresses];
+ });
+
+ return list.sort();
+};
+
+export const getDnsAddress = (ip, port = '') => {
+ const isStandardDnsPort = port === STANDARD_DNS_PORT;
+ let address = ip;
+
+ if (port) {
+ if (ip.includes(':') && !isStandardDnsPort) {
+ address = `[${ip}]:${port}`;
+ } else if (!isStandardDnsPort) {
+ address = `${ip}:${port}`;
+ }
+ }
+
+ return address;
+};
+
+export const getWebAddress = (ip, port = '') => {
+ const isStandardWebPort = port === STANDARD_WEB_PORT;
+ let address = `http://${ip}`;
+
+ if (port) {
+ if (ip.includes(':') && !isStandardWebPort) {
+ address = `http://[${ip}]:${port}`;
+ } else if (!isStandardWebPort) {
+ address = `http://${ip}:${port}`;
+ }
+ }
+
+ return address;
+};
diff --git a/client/src/install/Setup/AddressList.js b/client/src/install/Setup/AddressList.js
new file mode 100644
index 00000000..425d6504
--- /dev/null
+++ b/client/src/install/Setup/AddressList.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { getIpList, getDnsAddress, getWebAddress } from '../../helpers/helpers';
+
+const AddressList = (props) => {
+ let webAddress = getWebAddress(props.address, props.port);
+ let dnsAddress = getDnsAddress(props.address, props.port);
+
+ if (props.address === '0.0.0.0') {
+ return getIpList(props.interfaces).map((ip) => {
+ webAddress = getWebAddress(ip, props.port);
+ dnsAddress = getDnsAddress(ip, props.port);
+
+ if (props.isDns) {
+ return (
+
+
+ {dnsAddress}
+
+
+ );
+ }
+
+ return (
+
+
+ {webAddress}
+
+
+ );
+ });
+ }
+
+ if (props.isDns) {
+ return (
+
+ {dnsAddress}
+
+ );
+ }
+
+ return (
+
+ {webAddress}
+
+ );
+};
+
+AddressList.propTypes = {
+ interfaces: PropTypes.object.isRequired,
+ address: PropTypes.string.isRequired,
+ port: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ isDns: PropTypes.bool,
+};
+
+export default AddressList;
diff --git a/client/src/install/Setup/Auth.js b/client/src/install/Setup/Auth.js
new file mode 100644
index 00000000..d8234d94
--- /dev/null
+++ b/client/src/install/Setup/Auth.js
@@ -0,0 +1,108 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Field, reduxForm } from 'redux-form';
+import { withNamespaces, Trans } from 'react-i18next';
+import flow from 'lodash/flow';
+
+import i18n from '../../i18n';
+import Controls from './Controls';
+import renderField from './renderField';
+
+const required = (value) => {
+ if (value || value === 0) {
+ return false;
+ }
+ return form_error_required ;
+};
+
+const validate = (values) => {
+ const errors = {};
+
+ if (values.confirm_password !== values.password) {
+ errors.confirm_password = i18n.t('form_error_password');
+ }
+
+ return errors;
+};
+
+const Auth = (props) => {
+ const {
+ handleSubmit,
+ pristine,
+ invalid,
+ t,
+ } = props;
+
+ return (
+
+ );
+};
+
+Auth.propTypes = {
+ handleSubmit: PropTypes.func.isRequired,
+ pristine: PropTypes.bool.isRequired,
+ invalid: PropTypes.bool.isRequired,
+ t: PropTypes.func.isRequired,
+};
+
+export default flow([
+ withNamespaces(),
+ reduxForm({
+ form: 'install',
+ destroyOnUnmount: false,
+ forceUnregisterOnUnmount: true,
+ validate,
+ }),
+])(Auth);
diff --git a/client/src/install/Setup/Controls.js b/client/src/install/Setup/Controls.js
new file mode 100644
index 00000000..aa5a2a42
--- /dev/null
+++ b/client/src/install/Setup/Controls.js
@@ -0,0 +1,113 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import { Trans } from 'react-i18next';
+
+import * as actionCreators from '../../actions/install';
+
+class Controls extends Component {
+ renderPrevButton(step) {
+ switch (step) {
+ case 2:
+ case 3:
+ return (
+
+ back
+
+ );
+ default:
+ return false;
+ }
+ }
+
+ renderNextButton(step) {
+ switch (step) {
+ case 1:
+ return (
+
+ get_started
+
+ );
+ case 2:
+ case 3:
+ return (
+
+ next
+
+ );
+ case 4:
+ return (
+
+ next
+
+ );
+ case 5:
+ return (
+ this.props.openDashboard(this.props.address)}
+ >
+ open_dashboard
+
+ );
+ default:
+ return false;
+ }
+ }
+
+ render() {
+ const { install } = this.props;
+
+ return (
+
+
+ {this.renderPrevButton(install.step)}
+ {this.renderNextButton(install.step)}
+
+
+ );
+ }
+}
+
+Controls.propTypes = {
+ install: PropTypes.object.isRequired,
+ nextStep: PropTypes.func,
+ prevStep: PropTypes.func,
+ openDashboard: PropTypes.func,
+ submitting: PropTypes.bool,
+ invalid: PropTypes.bool,
+ pristine: PropTypes.bool,
+ address: PropTypes.string,
+};
+
+const mapStateToProps = (state) => {
+ const { install } = state;
+ const props = { install };
+ return props;
+};
+
+export default connect(
+ mapStateToProps,
+ actionCreators,
+)(Controls);
diff --git a/client/src/install/Setup/Devices.js b/client/src/install/Setup/Devices.js
new file mode 100644
index 00000000..4a5f7c13
--- /dev/null
+++ b/client/src/install/Setup/Devices.js
@@ -0,0 +1,134 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { reduxForm, formValueSelector } from 'redux-form';
+import { Trans, withNamespaces } from 'react-i18next';
+import flow from 'lodash/flow';
+
+import Tabs from '../../components/ui/Tabs';
+import Icons from '../../components/ui/Icons';
+import Controls from './Controls';
+import AddressList from './AddressList';
+
+let Devices = props => (
+
+
+
+ install_devices_title
+
+
+
install_devices_desc
+
+ install_devices_address :
+
+
+
+
+
+
+
+ install_devices_router
+
+
+
install_devices_router_desc
+
+ install_devices_router_list_1
+ install_devices_router_list_2
+ install_devices_router_list_3
+
+
+
+
+
+ Windows
+
+
+
+ install_devices_windows_list_1
+ install_devices_windows_list_2
+ install_devices_windows_list_3
+ install_devices_windows_list_4
+ install_devices_windows_list_5
+ install_devices_windows_list_6
+
+
+
+
+
+ macOS
+
+
+
+ install_devices_macos_list_1
+ install_devices_macos_list_2
+ install_devices_macos_list_3
+ install_devices_macos_list_4
+
+
+
+
+
+ Android
+
+
+
+ install_devices_android_list_1
+ install_devices_android_list_2
+ install_devices_android_list_3
+ install_devices_android_list_4
+ install_devices_android_list_5
+
+
+
+
+
+ iOS
+
+
+
+ install_devices_ios_list_1
+ install_devices_ios_list_2
+ install_devices_ios_list_3
+ install_devices_ios_list_4
+
+
+
+
+
+
+
+);
+
+Devices.propTypes = {
+ interfaces: PropTypes.object.isRequired,
+ dnsIp: PropTypes.string.isRequired,
+ dnsPort: PropTypes.number.isRequired,
+};
+
+const selector = formValueSelector('install');
+
+Devices = connect((state) => {
+ const dnsIp = selector(state, 'dns.ip');
+ const dnsPort = selector(state, 'dns.port');
+
+ return {
+ dnsIp,
+ dnsPort,
+ };
+})(Devices);
+
+export default flow([
+ withNamespaces(),
+ reduxForm({
+ form: 'install',
+ destroyOnUnmount: false,
+ forceUnregisterOnUnmount: true,
+ }),
+])(Devices);
diff --git a/client/src/install/Setup/Greeting.js b/client/src/install/Setup/Greeting.js
new file mode 100644
index 00000000..914d89c8
--- /dev/null
+++ b/client/src/install/Setup/Greeting.js
@@ -0,0 +1,23 @@
+import React, { Component } from 'react';
+import { Trans, withNamespaces } from 'react-i18next';
+import Controls from './Controls';
+
+class Greeting extends Component {
+ render() {
+ return (
+
+
+
+ install_welcome_title
+
+
+ install_welcome_desc
+
+
+
+
+ );
+ }
+}
+
+export default withNamespaces()(Greeting);
diff --git a/client/src/install/Setup/Progress.js b/client/src/install/Setup/Progress.js
new file mode 100644
index 00000000..76f161f6
--- /dev/null
+++ b/client/src/install/Setup/Progress.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Trans, withNamespaces } from 'react-i18next';
+
+import { INSTALL_TOTAL_STEPS } from '../../helpers/constants';
+
+const getProgressPercent = step => (step / INSTALL_TOTAL_STEPS) * 100;
+
+const Progress = props => (
+
+
install_step {props.step}/{INSTALL_TOTAL_STEPS}
+
+
+);
+
+Progress.propTypes = {
+ step: PropTypes.number.isRequired,
+};
+
+export default withNamespaces()(Progress);
diff --git a/client/src/install/Setup/Settings.js b/client/src/install/Setup/Settings.js
new file mode 100644
index 00000000..cc127117
--- /dev/null
+++ b/client/src/install/Setup/Settings.js
@@ -0,0 +1,221 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import { Field, reduxForm, formValueSelector } from 'redux-form';
+import { Trans, withNamespaces } from 'react-i18next';
+import flow from 'lodash/flow';
+
+import Controls from './Controls';
+import AddressList from './AddressList';
+import renderField from './renderField';
+import { getInterfaceIp } from '../../helpers/helpers';
+
+const required = (value) => {
+ if (value || value === 0) {
+ return false;
+ }
+ return form_error_required ;
+};
+
+const port = (value) => {
+ if (value < 1 || value > 65535) {
+ return form_error_port ;
+ }
+ return false;
+};
+
+const toNumber = value => value && parseInt(value, 10);
+
+const renderInterfaces = (interfaces => (
+ Object.keys(interfaces).map((item) => {
+ const option = interfaces[item];
+ const { name } = option;
+
+ if (option.ip_addresses && option.ip_addresses.length > 0) {
+ const ip = getInterfaceIp(option);
+
+ return (
+
+ {name} - {ip}
+
+ );
+ }
+
+ return false;
+ })
+));
+
+let Settings = (props) => {
+ const {
+ handleSubmit,
+ webIp,
+ webPort,
+ dnsIp,
+ dnsPort,
+ interfaces,
+ invalid,
+ webWarning,
+ dnsWarning,
+ } = props;
+
+ return (
+
+ );
+};
+
+Settings.propTypes = {
+ handleSubmit: PropTypes.func.isRequired,
+ webIp: PropTypes.string.isRequired,
+ dnsIp: PropTypes.string.isRequired,
+ webPort: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ dnsPort: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ webWarning: PropTypes.string.isRequired,
+ dnsWarning: PropTypes.string.isRequired,
+ interfaces: PropTypes.object.isRequired,
+ invalid: PropTypes.bool.isRequired,
+ initialValues: PropTypes.object,
+};
+
+const selector = formValueSelector('install');
+
+Settings = connect((state) => {
+ const webIp = selector(state, 'web.ip');
+ const webPort = selector(state, 'web.port');
+ const dnsIp = selector(state, 'dns.ip');
+ const dnsPort = selector(state, 'dns.port');
+
+ return {
+ webIp,
+ webPort,
+ dnsIp,
+ dnsPort,
+ };
+})(Settings);
+
+export default flow([
+ withNamespaces(),
+ reduxForm({
+ form: 'install',
+ destroyOnUnmount: false,
+ forceUnregisterOnUnmount: true,
+ }),
+])(Settings);
diff --git a/client/src/install/Setup/Setup.css b/client/src/install/Setup/Setup.css
new file mode 100644
index 00000000..c88e8b82
--- /dev/null
+++ b/client/src/install/Setup/Setup.css
@@ -0,0 +1,117 @@
+.setup {
+ min-height: calc(100vh - 80px);
+ line-height: 1.48;
+}
+
+@media screen and (min-width: 768px) {
+ .setup {
+ padding: 50px 0;
+ }
+}
+
+.setup__container {
+ max-width: 650px;
+ margin: 0 auto;
+ padding: 30px 20px;
+ line-height: 1.6;
+ background-color: #fff;
+ box-shadow: 0 1px 4px rgba(74, 74, 74, .36);
+ border-radius: 3px;
+}
+
+@media screen and (min-width: 768px) {
+ .setup__container {
+ width: 650px;
+ padding: 40px 30px;
+ }
+}
+
+.setup__logo {
+ display: block;
+ margin: 0 auto 40px;
+ max-width: 140px;
+}
+
+.setup__nav {
+ text-align: center;
+}
+
+.setup__step {
+ margin-bottom: 25px;
+}
+
+.setup__title {
+ margin-bottom: 30px;
+ font-size: 28px;
+ text-align: center;
+ font-weight: 700;
+}
+
+.setup__subtitle {
+ margin-bottom: 10px;
+ font-size: 17px;
+ font-weight: 700;
+}
+
+.setup__desc {
+ margin-bottom: 20px;
+ font-size: 15px;
+}
+
+.setup__group {
+ margin-bottom: 35px;
+}
+
+.setup__group:last-child {
+ margin-bottom: 0;
+}
+
+.setup__progress {
+ font-size: 13px;
+ text-align: center;
+}
+
+.setup__progress-wrap {
+ height: 4px;
+ margin: 20px -20px -30px -20px;
+ overflow: hidden;
+ background-color: #eaeaea;
+ border-radius: 0 0 3px 3px;
+}
+
+@media screen and (min-width: 768px) {
+ .setup__progress-wrap {
+ margin: 20px -30px -40px -30px;
+ }
+}
+
+.setup__progress-inner {
+ width: 0;
+ height: 100%;
+ font-size: 1.2rem;
+ line-height: 20px;
+ color: #fff;
+ text-align: center;
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+ transition: width 0.6s ease;
+ background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%);
+}
+
+.btn-standard {
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+.form__message {
+ font-size: 11px;
+}
+
+.form__message--error {
+ color: #cd201f;
+}
+
+.setup__button {
+ min-width: 120px;
+ padding-left: 30px;
+ padding-right: 30px;
+}
diff --git a/client/src/install/Setup/Submit.js b/client/src/install/Setup/Submit.js
new file mode 100644
index 00000000..8456909a
--- /dev/null
+++ b/client/src/install/Setup/Submit.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import { reduxForm, formValueSelector } from 'redux-form';
+import { Trans, withNamespaces } from 'react-i18next';
+import flow from 'lodash/flow';
+
+import Controls from './Controls';
+import { getWebAddress } from '../../helpers/helpers';
+
+let Submit = props => (
+
+
+
+ install_submit_title
+
+
+ install_submit_desc
+
+
+
+
+);
+
+Submit.propTypes = {
+ webIp: PropTypes.string.isRequired,
+ webPort: PropTypes.number.isRequired,
+ handleSubmit: PropTypes.func.isRequired,
+ pristine: PropTypes.bool.isRequired,
+ submitting: PropTypes.bool.isRequired,
+ openDashboard: PropTypes.func.isRequired,
+};
+
+const selector = formValueSelector('install');
+
+Submit = connect((state) => {
+ const webIp = selector(state, 'web.ip');
+ const webPort = selector(state, 'web.port');
+
+ return {
+ webIp,
+ webPort,
+ };
+})(Submit);
+
+
+export default flow([
+ withNamespaces(),
+ reduxForm({
+ form: 'install',
+ destroyOnUnmount: false,
+ forceUnregisterOnUnmount: true,
+ }),
+])(Submit);
diff --git a/client/src/install/Setup/index.js b/client/src/install/Setup/index.js
new file mode 100644
index 00000000..b1501536
--- /dev/null
+++ b/client/src/install/Setup/index.js
@@ -0,0 +1,125 @@
+import React, { Component, Fragment } from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+
+import * as actionCreators from '../../actions/install';
+import { INSTALL_FIRST_STEP, INSTALL_TOTAL_STEPS } from '../../helpers/constants';
+
+import Loading from '../../components/ui/Loading';
+import Greeting from './Greeting';
+import Settings from './Settings';
+import Auth from './Auth';
+import Devices from './Devices';
+import Submit from './Submit';
+import Progress from './Progress';
+
+import Toasts from '../../components/Toasts';
+import Footer from '../../components/ui/Footer';
+import logo from '../../components/ui/svg/logo.svg';
+
+import './Setup.css';
+import '../../components/ui/Tabler.css';
+
+class Setup extends Component {
+ componentDidMount() {
+ this.props.getDefaultAddresses();
+ }
+
+ handleFormSubmit = (values) => {
+ this.props.setAllSettings(values);
+ };
+
+ openDashboard = (address) => {
+ window.location.replace(address);
+ }
+
+ nextStep = () => {
+ if (this.props.install.step < INSTALL_TOTAL_STEPS) {
+ this.props.nextStep();
+ }
+ }
+
+ prevStep = () => {
+ if (this.props.install.step > INSTALL_FIRST_STEP) {
+ this.props.prevStep();
+ }
+ }
+
+ renderPage(step, config, interfaces) {
+ switch (step) {
+ case 1:
+ return ;
+ case 2:
+ return (
+
+ );
+ case 3:
+ return (
+
+ );
+ case 4:
+ return ;
+ case 5:
+ return ;
+ default:
+ return false;
+ }
+ }
+
+ render() {
+ const {
+ processingDefault,
+ step,
+ web,
+ dns,
+ interfaces,
+ } = this.props.install;
+
+ return (
+
+ {processingDefault && }
+ {!processingDefault &&
+
+
+
+
+ {this.renderPage(step, { web, dns }, interfaces)}
+
+
+
+
+
+
+ }
+
+ );
+ }
+}
+
+Setup.propTypes = {
+ getDefaultAddresses: PropTypes.func.isRequired,
+ setAllSettings: PropTypes.func.isRequired,
+ nextStep: PropTypes.func.isRequired,
+ prevStep: PropTypes.func.isRequired,
+ install: PropTypes.object.isRequired,
+ step: PropTypes.number,
+ web: PropTypes.object,
+ dns: PropTypes.object,
+};
+
+const mapStateToProps = (state) => {
+ const { install, toasts } = state;
+ const props = { install, toasts };
+ return props;
+};
+
+export default connect(
+ mapStateToProps,
+ actionCreators,
+)(Setup);
diff --git a/client/src/install/Setup/renderField.js b/client/src/install/Setup/renderField.js
new file mode 100644
index 00000000..a323f17c
--- /dev/null
+++ b/client/src/install/Setup/renderField.js
@@ -0,0 +1,19 @@
+import React, { Fragment } from 'react';
+
+const renderField = ({
+ input, className, placeholder, type, disabled, autoComplete, meta: { touched, error },
+}) => (
+
+
+ {!disabled && touched && (error && {error} )}
+
+);
+
+export default renderField;
diff --git a/client/src/install/index.js b/client/src/install/index.js
new file mode 100644
index 00000000..2905bd4b
--- /dev/null
+++ b/client/src/install/index.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+
+import '../components/App/index.css';
+import '../components/ui/ReactTable.css';
+import configureStore from '../configureStore';
+import reducers from '../reducers/install';
+import '../i18n';
+import Setup from './Setup';
+
+const store = configureStore(reducers, {}); // set initial state
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root'),
+);
diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js
index 1b770adc..19bbbf63 100644
--- a/client/src/reducers/index.js
+++ b/client/src/reducers/index.js
@@ -1,11 +1,11 @@
import { combineReducers } from 'redux';
import { handleActions } from 'redux-actions';
import { loadingBarReducer } from 'react-redux-loading-bar';
-import nanoid from 'nanoid';
import { reducer as formReducer } from 'redux-form';
import versionCompare from '../helpers/versionCompare';
import * as actions from '../actions';
+import toasts from './toasts';
const settings = handleActions({
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
@@ -241,34 +241,6 @@ const filtering = handleActions({
userRules: '',
});
-const toasts = handleActions({
- [actions.addErrorToast]: (state, { payload }) => {
- const errorToast = {
- id: nanoid(),
- message: payload.error.toString(),
- type: 'error',
- };
-
- const newState = { ...state, notices: [...state.notices, errorToast] };
- return newState;
- },
- [actions.addSuccessToast]: (state, { payload }) => {
- const successToast = {
- id: nanoid(),
- message: payload,
- type: 'success',
- };
-
- const newState = { ...state, notices: [...state.notices, successToast] };
- return newState;
- },
- [actions.removeToast]: (state, { payload }) => {
- const filtered = state.notices.filter(notice => notice.id !== payload);
- const newState = { ...state, notices: filtered };
- return newState;
- },
-}, { notices: [] });
-
const dhcp = handleActions({
[actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }),
[actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }),
@@ -308,11 +280,21 @@ const dhcp = handleActions({
const newState = { ...state, config: newConfig, processingDhcp: false };
return newState;
},
+
+ [actions.setDhcpConfigRequest]: state => ({ ...state, processingConfig: true }),
+ [actions.setDhcpConfigFailure]: state => ({ ...state, processingConfig: false }),
+ [actions.setDhcpConfigSuccess]: (state, { payload }) => {
+ const { config } = state;
+ const newConfig = { ...config, ...payload };
+ const newState = { ...state, config: newConfig, processingConfig: false };
+ return newState;
+ },
}, {
processing: true,
processingStatus: false,
processingInterfaces: false,
processingDhcp: false,
+ processingConfig: false,
config: {
enabled: false,
},
diff --git a/client/src/reducers/install.js b/client/src/reducers/install.js
new file mode 100644
index 00000000..7533f152
--- /dev/null
+++ b/client/src/reducers/install.js
@@ -0,0 +1,47 @@
+import { combineReducers } from 'redux';
+import { handleActions } from 'redux-actions';
+import { reducer as formReducer } from 'redux-form';
+
+import * as actions from '../actions/install';
+import toasts from './toasts';
+import { INSTALL_FIRST_STEP } from '../helpers/constants';
+
+const install = handleActions({
+ [actions.getDefaultAddressesRequest]: state => ({ ...state, processingDefault: true }),
+ [actions.getDefaultAddressesFailure]: state => ({ ...state, processingDefault: false }),
+ [actions.getDefaultAddressesSuccess]: (state, { payload }) => {
+ const values = payload;
+ values.web.ip = state.web.ip;
+ values.dns.ip = state.dns.ip;
+ const newState = { ...state, ...values, processingDefault: false };
+ return newState;
+ },
+
+ [actions.nextStep]: state => ({ ...state, step: state.step + 1 }),
+ [actions.prevStep]: state => ({ ...state, step: state.step - 1 }),
+
+ [actions.setAllSettingsRequest]: state => ({ ...state, processingSubmit: true }),
+ [actions.setAllSettingsFailure]: state => ({ ...state, processingSubmit: false }),
+ [actions.setAllSettingsSuccess]: state => ({ ...state, processingSubmit: false }),
+}, {
+ step: INSTALL_FIRST_STEP,
+ processingDefault: true,
+ processingSubmit: false,
+ web: {
+ ip: '0.0.0.0',
+ port: 80,
+ warning: '',
+ },
+ dns: {
+ ip: '0.0.0.0',
+ port: 53,
+ warning: '',
+ },
+ interfaces: {},
+});
+
+export default combineReducers({
+ install,
+ toasts,
+ form: formReducer,
+});
diff --git a/client/src/reducers/toasts.js b/client/src/reducers/toasts.js
new file mode 100644
index 00000000..c56085d3
--- /dev/null
+++ b/client/src/reducers/toasts.js
@@ -0,0 +1,34 @@
+import { handleActions } from 'redux-actions';
+import nanoid from 'nanoid';
+
+import { addErrorToast, addSuccessToast, removeToast } from '../actions';
+
+const toasts = handleActions({
+ [addErrorToast]: (state, { payload }) => {
+ const errorToast = {
+ id: nanoid(),
+ message: payload.error.toString(),
+ type: 'error',
+ };
+
+ const newState = { ...state, notices: [...state.notices, errorToast] };
+ return newState;
+ },
+ [addSuccessToast]: (state, { payload }) => {
+ const successToast = {
+ id: nanoid(),
+ message: payload,
+ type: 'success',
+ };
+
+ const newState = { ...state, notices: [...state.notices, successToast] };
+ return newState;
+ },
+ [removeToast]: (state, { payload }) => {
+ const filtered = state.notices.filter(notice => notice.id !== payload);
+ const newState = { ...state, notices: filtered };
+ return newState;
+ },
+}, { notices: [] });
+
+export default toasts;
diff --git a/client/webpack.common.js b/client/webpack.common.js
index 4ca3b1e4..aa1b2383 100644
--- a/client/webpack.common.js
+++ b/client/webpack.common.js
@@ -8,7 +8,9 @@ const CleanWebpackPlugin = require('clean-webpack-plugin');
const RESOURCES_PATH = path.resolve(__dirname);
const ENTRY_REACT = path.resolve(RESOURCES_PATH, 'src/index.js');
+const ENTRY_INSTALL = path.resolve(RESOURCES_PATH, 'src/install/index.js');
const HTML_PATH = path.resolve(RESOURCES_PATH, 'public/index.html');
+const HTML_INSTALL_PATH = path.resolve(RESOURCES_PATH, 'public/install.html');
const PUBLIC_PATH = path.resolve(__dirname, '../build/static');
@@ -16,7 +18,8 @@ const config = {
target: 'web',
context: RESOURCES_PATH,
entry: {
- bundle: ENTRY_REACT,
+ main: ENTRY_REACT,
+ install: ENTRY_INSTALL,
},
output: {
path: PUBLIC_PATH,
@@ -101,8 +104,16 @@ const config = {
new HtmlWebpackPlugin({
inject: true,
cache: false,
+ chunks: ['main'],
template: HTML_PATH,
}),
+ new HtmlWebpackPlugin({
+ inject: true,
+ cache: false,
+ chunks: ['install'],
+ filename: 'install.html',
+ template: HTML_INSTALL_PATH,
+ }),
new ExtractTextPlugin({
filename: '[name].[contenthash].css',
}),
diff --git a/client/webpack.dev.js b/client/webpack.dev.js
index 977e6c9f..79589a08 100644
--- a/client/webpack.dev.js
+++ b/client/webpack.dev.js
@@ -2,7 +2,6 @@ const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
- devtool: 'inline-source-map',
module: {
rules: [{
test: /\.js$/,
diff --git a/config.go b/config.go
index 33d717ef..751e3c41 100644
--- a/config.go
+++ b/config.go
@@ -29,6 +29,7 @@ type logSettings struct {
type configuration struct {
ourConfigFilename string // Config filename (can be overridden via the command line arguments)
ourBinaryDir string // Location of our directory, used to protect against CWD being somewhere else
+ firstRun bool // if set to true, don't run any services except HTTP web inteface, and serve only first-run html
BindHost string `yaml:"bind_host"`
BindPort int `yaml:"bind_port"`
@@ -63,7 +64,7 @@ var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"}
var config = configuration{
ourConfigFilename: "AdGuardHome.yaml",
BindPort: 3000,
- BindHost: "127.0.0.1",
+ BindHost: "0.0.0.0",
DNS: dnsConfig{
BindHost: "0.0.0.0",
Port: 53,
@@ -114,7 +115,7 @@ func getLogSettings() logSettings {
// parseConfig loads configuration from the YAML file
func parseConfig() error {
configFile := config.getConfigFilename()
- log.Printf("Reading YAML file: %s", configFile)
+ log.Tracef("Reading YAML file: %s", configFile)
yamlFile, err := readConfigFile()
if err != nil {
log.Printf("Couldn't read config file: %s", err)
@@ -152,8 +153,12 @@ func readConfigFile() ([]byte, error) {
func (c *configuration) write() error {
c.Lock()
defer c.Unlock()
+ if config.firstRun {
+ log.Tracef("Silently refusing to write config because first run and not configured yet")
+ return nil
+ }
configFile := config.getConfigFilename()
- log.Printf("Writing YAML file: %s", configFile)
+ log.Tracef("Writing YAML file: %s", configFile)
yamlText, err := yaml.Marshal(&config)
if err != nil {
log.Printf("Couldn't generate YAML file: %s", err)
diff --git a/control.go b/control.go
index 4600c184..2077549d 100644
--- a/control.go
+++ b/control.go
@@ -1,6 +1,7 @@
package main
import (
+ "context"
"encoding/json"
"fmt"
"io/ioutil"
@@ -693,42 +694,188 @@ func handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
}
}
-func registerControlHandlers() {
- http.HandleFunc("/control/status", optionalAuth(ensureGET(handleStatus)))
- http.HandleFunc("/control/enable_protection", optionalAuth(ensurePOST(handleProtectionEnable)))
- http.HandleFunc("/control/disable_protection", optionalAuth(ensurePOST(handleProtectionDisable)))
- http.HandleFunc("/control/querylog", optionalAuth(ensureGET(dnsforward.HandleQueryLog)))
- http.HandleFunc("/control/querylog_enable", optionalAuth(ensurePOST(handleQueryLogEnable)))
- http.HandleFunc("/control/querylog_disable", optionalAuth(ensurePOST(handleQueryLogDisable)))
- http.HandleFunc("/control/set_upstream_dns", optionalAuth(ensurePOST(handleSetUpstreamDNS)))
- http.HandleFunc("/control/test_upstream_dns", optionalAuth(ensurePOST(handleTestUpstreamDNS)))
- http.HandleFunc("/control/i18n/change_language", optionalAuth(ensurePOST(handleI18nChangeLanguage)))
- http.HandleFunc("/control/i18n/current_language", optionalAuth(ensureGET(handleI18nCurrentLanguage)))
- http.HandleFunc("/control/stats_top", optionalAuth(ensureGET(dnsforward.HandleStatsTop)))
- http.HandleFunc("/control/stats", optionalAuth(ensureGET(dnsforward.HandleStats)))
- http.HandleFunc("/control/stats_history", optionalAuth(ensureGET(dnsforward.HandleStatsHistory)))
- http.HandleFunc("/control/stats_reset", optionalAuth(ensurePOST(dnsforward.HandleStatsReset)))
- http.HandleFunc("/control/version.json", optionalAuth(handleGetVersionJSON))
- http.HandleFunc("/control/filtering/enable", optionalAuth(ensurePOST(handleFilteringEnable)))
- http.HandleFunc("/control/filtering/disable", optionalAuth(ensurePOST(handleFilteringDisable)))
- http.HandleFunc("/control/filtering/add_url", optionalAuth(ensurePUT(handleFilteringAddURL)))
- http.HandleFunc("/control/filtering/remove_url", optionalAuth(ensureDELETE(handleFilteringRemoveURL)))
- http.HandleFunc("/control/filtering/enable_url", optionalAuth(ensurePOST(handleFilteringEnableURL)))
- http.HandleFunc("/control/filtering/disable_url", optionalAuth(ensurePOST(handleFilteringDisableURL)))
- http.HandleFunc("/control/filtering/refresh", optionalAuth(ensurePOST(handleFilteringRefresh)))
- http.HandleFunc("/control/filtering/status", optionalAuth(ensureGET(handleFilteringStatus)))
- http.HandleFunc("/control/filtering/set_rules", optionalAuth(ensurePUT(handleFilteringSetRules)))
- http.HandleFunc("/control/safebrowsing/enable", optionalAuth(ensurePOST(handleSafeBrowsingEnable)))
- http.HandleFunc("/control/safebrowsing/disable", optionalAuth(ensurePOST(handleSafeBrowsingDisable)))
- http.HandleFunc("/control/safebrowsing/status", optionalAuth(ensureGET(handleSafeBrowsingStatus)))
- http.HandleFunc("/control/parental/enable", optionalAuth(ensurePOST(handleParentalEnable)))
- http.HandleFunc("/control/parental/disable", optionalAuth(ensurePOST(handleParentalDisable)))
- http.HandleFunc("/control/parental/status", optionalAuth(ensureGET(handleParentalStatus)))
- http.HandleFunc("/control/safesearch/enable", optionalAuth(ensurePOST(handleSafeSearchEnable)))
- http.HandleFunc("/control/safesearch/disable", optionalAuth(ensurePOST(handleSafeSearchDisable)))
- http.HandleFunc("/control/safesearch/status", optionalAuth(ensureGET(handleSafeSearchStatus)))
- http.HandleFunc("/control/dhcp/status", optionalAuth(ensureGET(handleDHCPStatus)))
- http.HandleFunc("/control/dhcp/interfaces", optionalAuth(ensureGET(handleDHCPInterfaces)))
- http.HandleFunc("/control/dhcp/set_config", optionalAuth(ensurePOST(handleDHCPSetConfig)))
- http.HandleFunc("/control/dhcp/find_active_dhcp", optionalAuth(ensurePOST(handleDHCPFindActiveServer)))
+type ipport struct {
+ IP string `json:"ip,omitempty"`
+ Port int `json:"port"`
+ Warning string `json:"warning"`
+}
+
+type firstRunData struct {
+ Web ipport `json:"web"`
+ DNS ipport `json:"dns"`
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+ Interfaces map[string]interface{} `json:"interfaces"`
+}
+
+func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
+ data := firstRunData{}
+ ifaces, err := getValidNetInterfaces()
+ if err != nil {
+ httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
+ return
+ }
+ if len(ifaces) == 0 {
+ httpError(w, http.StatusServiceUnavailable, "Couldn't find any legible interface, plase try again later")
+ return
+ }
+
+ // fill out the fields
+
+ // find out if port 80 is available -- if not, fall back to 3000
+ if checkPortAvailable("", 80) == nil {
+ data.Web.Port = 80
+ } else {
+ data.Web.Port = 3000
+ }
+
+ // find out if port 53 is available -- if not, show a big warning
+ data.DNS.Port = 53
+ if checkPacketPortAvailable("", 53) != nil {
+ data.DNS.Warning = "Port 53 is not available for binding -- this will make DNS clients unable to contact AdGuard Home."
+ }
+
+ data.Interfaces = make(map[string]interface{})
+ for _, iface := range ifaces {
+ addrs, err := iface.Addrs()
+ if err != nil {
+ httpError(w, http.StatusInternalServerError, "Failed to get addresses for interface %s: %s", iface.Name, err)
+ return
+ }
+
+ jsonIface := netInterface{
+ Name: iface.Name,
+ MTU: iface.MTU,
+ HardwareAddr: iface.HardwareAddr.String(),
+ }
+
+ if iface.Flags != 0 {
+ jsonIface.Flags = iface.Flags.String()
+ }
+
+ // we don't want link-local addresses in json, so skip them
+ for _, addr := range addrs {
+ ipnet, ok := addr.(*net.IPNet)
+ if !ok {
+ // not an IPNet, should not happen
+ httpError(w, http.StatusInternalServerError, "SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
+ return
+ }
+ // ignore link-local
+ if ipnet.IP.IsLinkLocalUnicast() {
+ continue
+ }
+ jsonIface.Addresses = append(jsonIface.Addresses, ipnet.IP.String())
+ }
+ if len(jsonIface.Addresses) != 0 {
+ data.Interfaces[iface.Name] = jsonIface
+ }
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ err = json.NewEncoder(w).Encode(data)
+ if err != nil {
+ httpError(w, http.StatusInternalServerError, "Unable to marshal default addresses to json: %s", err)
+ return
+ }
+}
+
+func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
+ newSettings := firstRunData{}
+ err := json.NewDecoder(r.Body).Decode(&newSettings)
+ if err != nil {
+ httpError(w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err)
+ return
+ }
+
+ restartHTTP := true
+ if config.BindHost == newSettings.Web.IP && config.BindPort == newSettings.Web.Port {
+ // no need to rebind
+ restartHTTP = false
+ }
+
+ // validate that hosts and ports are bindable
+ if restartHTTP {
+ err = checkPortAvailable(newSettings.Web.IP, newSettings.Web.Port)
+ if err != nil {
+ httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", net.JoinHostPort(newSettings.Web.IP, strconv.Itoa(newSettings.Web.Port)), err)
+ return
+ }
+ }
+
+ err = checkPacketPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port)
+ if err != nil {
+ httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", net.JoinHostPort(newSettings.DNS.IP, strconv.Itoa(newSettings.DNS.Port)), err)
+ return
+ }
+
+ config.firstRun = false
+ config.BindHost = newSettings.Web.IP
+ config.BindPort = newSettings.Web.Port
+ config.DNS.BindHost = newSettings.DNS.IP
+ config.DNS.Port = newSettings.DNS.Port
+ config.AuthName = newSettings.Username
+ config.AuthPass = newSettings.Password
+
+ if config.DNS.Port != 0 {
+ err = startDNSServer()
+ if err != nil {
+ httpError(w, http.StatusInternalServerError, "Couldn't start DNS server: %s", err)
+ return
+ }
+ }
+
+ httpUpdateConfigReloadDNSReturnOK(w, r)
+ // this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block
+ // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
+ if restartHTTP {
+ go func() {
+ httpServer.Shutdown(context.TODO())
+ }()
+ }
+}
+
+func registerInstallHandlers() {
+ http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(handleInstallGetAddresses)))
+ http.HandleFunc("/control/install/configure", preInstall(ensurePOST(handleInstallConfigure)))
+}
+
+func registerControlHandlers() {
+ http.HandleFunc("/control/status", postInstall(optionalAuth(ensureGET(handleStatus))))
+ http.HandleFunc("/control/enable_protection", postInstall(optionalAuth(ensurePOST(handleProtectionEnable))))
+ http.HandleFunc("/control/disable_protection", postInstall(optionalAuth(ensurePOST(handleProtectionDisable))))
+ http.HandleFunc("/control/querylog", postInstall(optionalAuth(ensureGET(dnsforward.HandleQueryLog))))
+ http.HandleFunc("/control/querylog_enable", postInstall(optionalAuth(ensurePOST(handleQueryLogEnable))))
+ http.HandleFunc("/control/querylog_disable", postInstall(optionalAuth(ensurePOST(handleQueryLogDisable))))
+ http.HandleFunc("/control/set_upstream_dns", postInstall(optionalAuth(ensurePOST(handleSetUpstreamDNS))))
+ http.HandleFunc("/control/test_upstream_dns", postInstall(optionalAuth(ensurePOST(handleTestUpstreamDNS))))
+ http.HandleFunc("/control/i18n/change_language", postInstall(optionalAuth(ensurePOST(handleI18nChangeLanguage))))
+ http.HandleFunc("/control/i18n/current_language", postInstall(optionalAuth(ensureGET(handleI18nCurrentLanguage))))
+ http.HandleFunc("/control/stats_top", postInstall(optionalAuth(ensureGET(dnsforward.HandleStatsTop))))
+ http.HandleFunc("/control/stats", postInstall(optionalAuth(ensureGET(dnsforward.HandleStats))))
+ http.HandleFunc("/control/stats_history", postInstall(optionalAuth(ensureGET(dnsforward.HandleStatsHistory))))
+ http.HandleFunc("/control/stats_reset", postInstall(optionalAuth(ensurePOST(dnsforward.HandleStatsReset))))
+ http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
+ http.HandleFunc("/control/filtering/enable", postInstall(optionalAuth(ensurePOST(handleFilteringEnable))))
+ http.HandleFunc("/control/filtering/disable", postInstall(optionalAuth(ensurePOST(handleFilteringDisable))))
+ http.HandleFunc("/control/filtering/add_url", postInstall(optionalAuth(ensurePUT(handleFilteringAddURL))))
+ http.HandleFunc("/control/filtering/remove_url", postInstall(optionalAuth(ensureDELETE(handleFilteringRemoveURL))))
+ http.HandleFunc("/control/filtering/enable_url", postInstall(optionalAuth(ensurePOST(handleFilteringEnableURL))))
+ http.HandleFunc("/control/filtering/disable_url", postInstall(optionalAuth(ensurePOST(handleFilteringDisableURL))))
+ http.HandleFunc("/control/filtering/refresh", postInstall(optionalAuth(ensurePOST(handleFilteringRefresh))))
+ http.HandleFunc("/control/filtering/status", postInstall(optionalAuth(ensureGET(handleFilteringStatus))))
+ http.HandleFunc("/control/filtering/set_rules", postInstall(optionalAuth(ensurePUT(handleFilteringSetRules))))
+ http.HandleFunc("/control/safebrowsing/enable", postInstall(optionalAuth(ensurePOST(handleSafeBrowsingEnable))))
+ http.HandleFunc("/control/safebrowsing/disable", postInstall(optionalAuth(ensurePOST(handleSafeBrowsingDisable))))
+ http.HandleFunc("/control/safebrowsing/status", postInstall(optionalAuth(ensureGET(handleSafeBrowsingStatus))))
+ http.HandleFunc("/control/parental/enable", postInstall(optionalAuth(ensurePOST(handleParentalEnable))))
+ http.HandleFunc("/control/parental/disable", postInstall(optionalAuth(ensurePOST(handleParentalDisable))))
+ http.HandleFunc("/control/parental/status", postInstall(optionalAuth(ensureGET(handleParentalStatus))))
+ http.HandleFunc("/control/safesearch/enable", postInstall(optionalAuth(ensurePOST(handleSafeSearchEnable))))
+ http.HandleFunc("/control/safesearch/disable", postInstall(optionalAuth(ensurePOST(handleSafeSearchDisable))))
+ http.HandleFunc("/control/safesearch/status", postInstall(optionalAuth(ensureGET(handleSafeSearchStatus))))
+ http.HandleFunc("/control/dhcp/status", postInstall(optionalAuth(ensureGET(handleDHCPStatus))))
+ http.HandleFunc("/control/dhcp/interfaces", postInstall(optionalAuth(ensureGET(handleDHCPInterfaces))))
+ http.HandleFunc("/control/dhcp/set_config", postInstall(optionalAuth(ensurePOST(handleDHCPSetConfig))))
+ http.HandleFunc("/control/dhcp/find_active_dhcp", postInstall(optionalAuth(ensurePOST(handleDHCPFindActiveServer))))
}
diff --git a/dhcp.go b/dhcp.go
index 744e7a35..a67b0ef6 100644
--- a/dhcp.go
+++ b/dhcp.go
@@ -70,50 +70,54 @@ func handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{}
- ifaces, err := net.Interfaces()
+ ifaces, err := getValidNetInterfaces()
if err != nil {
- httpError(w, http.StatusInternalServerError, "Couldn't get list of interfaces: %s", err)
+ httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
return
}
- type responseInterface struct {
- Name string `json:"name"`
- MTU int `json:"mtu"`
- HardwareAddr string `json:"hardware_address"`
- Addresses []string `json:"ip_addresses"`
- }
-
- for i := range ifaces {
- if ifaces[i].Flags&net.FlagLoopback != 0 {
+ for _, iface := range ifaces {
+ if iface.Flags&net.FlagLoopback != 0 {
// it's a loopback, skip it
continue
}
- if ifaces[i].Flags&net.FlagBroadcast == 0 {
+ if iface.Flags&net.FlagBroadcast == 0 {
// this interface doesn't support broadcast, skip it
continue
}
- if ifaces[i].Flags&net.FlagPointToPoint != 0 {
- // this interface is ppp, don't do dhcp over it
- continue
- }
- iface := responseInterface{
- Name: ifaces[i].Name,
- MTU: ifaces[i].MTU,
- HardwareAddr: ifaces[i].HardwareAddr.String(),
- }
- addrs, errAddrs := ifaces[i].Addrs()
- if errAddrs != nil {
- httpError(w, http.StatusInternalServerError, "Failed to get addresses for interface %v: %s", ifaces[i].Name, errAddrs)
+ addrs, err := iface.Addrs()
+ if err != nil {
+ httpError(w, http.StatusInternalServerError, "Failed to get addresses for interface %s: %s", iface.Name, err)
return
}
+
+ jsonIface := netInterface{
+ Name: iface.Name,
+ MTU: iface.MTU,
+ HardwareAddr: iface.HardwareAddr.String(),
+ }
+
+ if iface.Flags != 0 {
+ jsonIface.Flags = iface.Flags.String()
+ }
+ // we don't want link-local addresses in json, so skip them
for _, addr := range addrs {
- iface.Addresses = append(iface.Addresses, addr.String())
+ ipnet, ok := addr.(*net.IPNet)
+ if !ok {
+ // not an IPNet, should not happen
+ httpError(w, http.StatusInternalServerError, "SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
+ return
+ }
+ // ignore link-local
+ if ipnet.IP.IsLinkLocalUnicast() {
+ continue
+ }
+ jsonIface.Addresses = append(jsonIface.Addresses, ipnet.IP.String())
}
- if len(iface.Addresses) == 0 {
- // this interface has no addresses, skip it
- continue
+ if len(jsonIface.Addresses) != 0 {
+ response[iface.Name] = jsonIface
}
- response[ifaces[i].Name] = iface
+
}
err = json.NewEncoder(w).Encode(response)
diff --git a/dhcpd/helpers.go b/dhcpd/helpers.go
index 04a3ae62..24cfb029 100644
--- a/dhcpd/helpers.go
+++ b/dhcpd/helpers.go
@@ -31,11 +31,11 @@ func getIfaceIPv4(iface *net.Interface) *net.IPNet {
}
if ipnet.IP.To4() == nil {
- log.Printf("Got IP that is not IPv4: %v", ipnet.IP)
+ log.Tracef("Got IP that is not IPv4: %v", ipnet.IP)
continue
}
- log.Printf("Got IP that is IPv4: %v", ipnet.IP)
+ log.Tracef("Got IP that is IPv4: %v", ipnet.IP)
return &net.IPNet{
IP: ipnet.IP.To4(),
Mask: ipnet.Mask,
diff --git a/dnsfilter/dnsfilter.go b/dnsfilter/dnsfilter.go
index 1ccb854b..90bd61ea 100644
--- a/dnsfilter/dnsfilter.go
+++ b/dnsfilter/dnsfilter.go
@@ -892,15 +892,16 @@ func New(c *Config) *Dnsfilter {
d.whiteList = newRulesTable()
d.blackList = newRulesTable()
- // Customize the Transport to have larger connection pool
- defaultRoundTripper := http.DefaultTransport
- defaultTransportPointer, ok := defaultRoundTripper.(*http.Transport)
- if !ok {
- panic(fmt.Sprintf("defaultRoundTripper not an *http.Transport"))
+ // Customize the Transport to have larger connection pool,
+ // We are not (re)using http.DefaultTransport because of race conditions found by tests
+ d.transport = &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ MaxIdleConns: defaultHTTPMaxIdleConnections, // default 100
+ MaxIdleConnsPerHost: defaultHTTPMaxIdleConnections, // default 2
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
}
- d.transport = defaultTransportPointer // dereference it to get a copy of the struct that the pointer points to
- d.transport.MaxIdleConns = defaultHTTPMaxIdleConnections // default 100
- d.transport.MaxIdleConnsPerHost = defaultHTTPMaxIdleConnections // default 2
d.client = http.Client{
Transport: d.transport,
Timeout: defaultHTTPTimeout,
diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go
index f362ec4e..b2cf0556 100644
--- a/dnsforward/dnsforward.go
+++ b/dnsforward/dnsforward.go
@@ -110,7 +110,7 @@ func (s *Server) startInternal(config *ServerConfig) error {
return err
}
- log.Printf("Loading stats from querylog")
+ log.Tracef("Loading stats from querylog")
err = fillStatsFromQueryLog()
if err != nil {
return errorx.Decorate(err, "failed to load stats from querylog")
@@ -152,7 +152,7 @@ func (s *Server) startInternal(config *ServerConfig) error {
// Initializes the DNS filter
func (s *Server) initDNSFilter() error {
- log.Printf("Creating dnsfilter")
+ log.Tracef("Creating dnsfilter")
s.dnsFilter = dnsfilter.New(&s.Config)
// add rules only if they are enabled
if s.FilteringEnabled {
diff --git a/filter.go b/filter.go
index 9c7a73dd..49b54f0e 100644
--- a/filter.go
+++ b/filter.go
@@ -228,7 +228,7 @@ func (filter *filter) load() error {
}
filterFilePath := filter.Path()
- log.Printf("Loading filter %d contents to: %s", filter.ID, filterFilePath)
+ log.Tracef("Loading filter %d contents to: %s", filter.ID, filterFilePath)
if _, err := os.Stat(filterFilePath); os.IsNotExist(err) {
// do nothing, file doesn't exist
@@ -240,7 +240,7 @@ func (filter *filter) load() error {
return err
}
- log.Printf("File %s, id %d, length %d", filterFilePath, filter.ID, len(filterFileContents))
+ log.Tracef("File %s, id %d, length %d", filterFilePath, filter.ID, len(filterFileContents))
rulesCount, _, rules := parseFilterContents(filterFileContents)
filter.RulesCount = rulesCount
diff --git a/go.mod b/go.mod
index cc8f3327..b2b0a049 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,6 @@ require (
github.com/shirou/gopsutil v2.18.10+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
go.uber.org/goleak v0.10.0
- golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/net v0.0.0-20181220203305-927f97764cc3
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb
gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477
diff --git a/helpers.go b/helpers.go
index 28412a58..1bea694e 100644
--- a/helpers.go
+++ b/helpers.go
@@ -3,13 +3,16 @@ package main
import (
"bufio"
"errors"
+ "fmt"
"io"
"io/ioutil"
+ "net"
"net/http"
"os"
"path"
"path/filepath"
"runtime"
+ "strconv"
"strings"
)
@@ -84,24 +87,78 @@ type authHandler struct {
}
func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if config.AuthName == "" || config.AuthPass == "" {
- a.handler.ServeHTTP(w, r)
- return
- }
- user, pass, ok := r.BasicAuth()
- if !ok || user != config.AuthName || pass != config.AuthPass {
- w.Header().Set("WWW-Authenticate", `Basic realm="dnsfilter"`)
- w.WriteHeader(http.StatusUnauthorized)
- w.Write([]byte("Unauthorised.\n"))
- return
- }
- a.handler.ServeHTTP(w, r)
+ optionalAuth(a.handler.ServeHTTP)(w, r)
}
func optionalAuthHandler(handler http.Handler) http.Handler {
return &authHandler{handler}
}
+// -------------------
+// first run / install
+// -------------------
+func detectFirstRun() bool {
+ configfile := config.ourConfigFilename
+ if !filepath.IsAbs(configfile) {
+ configfile = filepath.Join(config.ourBinaryDir, config.ourConfigFilename)
+ }
+ _, err := os.Stat(configfile)
+ if !os.IsNotExist(err) {
+ // do nothing, file exists
+ return false
+ }
+ return true
+}
+
+// preInstall lets the handler run only if firstRun is true, no redirects
+func preInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if !config.firstRun {
+ // if it's not first run, don't let users access it (for example /install.html when configuration is done)
+ http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
+ return
+ }
+ handler(w, r)
+ }
+}
+
+// preInstallStruct wraps preInstall into a struct that can be returned as an interface where necessary
+type preInstallHandlerStruct struct {
+ handler http.Handler
+}
+
+func (p *preInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ preInstall(p.handler.ServeHTTP)(w, r)
+}
+
+// preInstallHandler returns http.Handler interface for preInstall wrapper
+func preInstallHandler(handler http.Handler) http.Handler {
+ return &preInstallHandlerStruct{handler}
+}
+
+// postInstall lets the handler run only if firstRun is false, and redirects to /install.html otherwise
+func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if config.firstRun && !strings.HasPrefix(r.URL.Path, "/install.") {
+ http.Redirect(w, r, "/install.html", http.StatusSeeOther) // should not be cacheable
+ return
+ }
+ handler(w, r)
+ }
+}
+
+type postInstallHandlerStruct struct {
+ handler http.Handler
+}
+
+func (p *postInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ postInstall(p.handler.ServeHTTP)(w, r)
+}
+
+func postInstallHandler(handler http.Handler) http.Handler {
+ return &postInstallHandlerStruct{handler}
+}
+
// -------------------------------------------------
// helper functions for parsing parameters from body
// -------------------------------------------------
@@ -125,6 +182,59 @@ func parseParametersFromBody(r io.Reader) (map[string]string, error) {
return parameters, nil
}
+// ------------------
+// network interfaces
+// ------------------
+type netInterface struct {
+ Name string `json:"name"`
+ MTU int `json:"mtu"`
+ HardwareAddr string `json:"hardware_address"`
+ Addresses []string `json:"ip_addresses"`
+ Flags string `json:"flags"`
+}
+
+// getValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP
+// invalid interface is a ppp interface or the one that doesn't allow broadcasts
+func getValidNetInterfaces() ([]net.Interface, error) {
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ return nil, fmt.Errorf("Couldn't get list of interfaces: %s", err)
+ }
+
+ netIfaces := []net.Interface{}
+
+ for i := range ifaces {
+ if ifaces[i].Flags&net.FlagPointToPoint != 0 {
+ // this interface is ppp, we're not interested in this one
+ continue
+ }
+
+ iface := ifaces[i]
+ netIfaces = append(netIfaces, iface)
+ }
+
+ return netIfaces, nil
+}
+
+// checkPortAvailable is not a cheap test to see if the port is bindable, because it's actually doing the bind momentarily
+func checkPortAvailable(host string, port int) error {
+ ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
+ if err != nil {
+ return err
+ }
+ ln.Close()
+ return nil
+}
+
+func checkPacketPortAvailable(host string, port int) error {
+ ln, err := net.ListenPacket("udp", net.JoinHostPort(host, strconv.Itoa(port)))
+ if err != nil {
+ return err
+ }
+ ln.Close()
+ return err
+}
+
// ---------------------
// debug logging helpers
// ---------------------
diff --git a/upgrade.go b/upgrade.go
index 3d79e744..02629797 100644
--- a/upgrade.go
+++ b/upgrade.go
@@ -34,7 +34,7 @@ func upgradeConfig() error {
}
schemaVersionInterface, ok := diskConfig["schema_version"]
- log.Printf("%s(): got schema version %v", _Func(), schemaVersionInterface)
+ log.Tracef("got schema version %v", schemaVersionInterface)
if !ok {
// no schema version, set it to 0
schemaVersionInterface = 0