aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <m.shulhan@gmail.com>2020-05-18 21:47:45 +0700
committerShulhan <m.shulhan@gmail.com>2020-07-26 03:48:51 +0700
commit7574bb6ebff609b9a2889fc573ce3edce256e030 (patch)
treed4a118ef858d5916ed19e1fc93a4d02b9fe4b588
parent7b9cba12d356c6fa9ee3988b5c96f8718aafd5f1 (diff)
downloadrescached-7574bb6ebff609b9a2889fc573ce3edce256e030.tar.xz
all: implement the user interface to change configuration
This is the first web UI (wui) where user can change configuration on the fly. The wui is implemented using svelte.dev and can be accessed on http://127.0.0.1:5380.
-rw-r--r--.gitignore5
-rw-r--r--Makefile5
-rw-r--r--_doc/rescached.cfg.adoc13
-rw-r--r--_www/.gitignore4
-rw-r--r--_www/package-lock.json598
-rw-r--r--_www/package.json21
-rw-r--r--_www/public/favicon.pngbin0 -> 12685 bytes
-rw-r--r--_www/public/global.css66
-rw-r--r--_www/public/index.html17
-rw-r--r--_www/rollup.config.js71
-rw-r--r--_www/src/App.svelte53
-rw-r--r--_www/src/Environment.svelte248
-rw-r--r--_www/src/InputAddress.svelte48
-rw-r--r--_www/src/InputNumber.svelte35
-rw-r--r--_www/src/LabelHint.svelte46
-rw-r--r--_www/src/main.js10
-rw-r--r--cmd/rescached/main.go21
-rw-r--r--cmd/rescached/rescached.cfg28
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rw-r--r--internal/generate_memfs.go31
-rw-r--r--options.go74
-rw-r--r--rescached.go51
-rw-r--r--rescached_httpd.go144
24 files changed, 1547 insertions, 48 deletions
diff --git a/.gitignore b/.gitignore
index b2ea592..4335f28 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,11 @@
/README.html
/TODO
-/cover.html
-/cover.out
/_doc/benchmark.html
/_doc/rescached.cfg.5.gz
/_doc/resolver.1.gz
+/cmd/rescached/memfs.go
+/cover.html
+/cover.out
/heap*
/rescached
/rescached.1.gz
diff --git a/Makefile b/Makefile
index f723d09..0d9ba2c 100644
--- a/Makefile
+++ b/Makefile
@@ -57,9 +57,12 @@ coverbrowse: $(COVER_HTML)
lint:
-golangci-lint run --enable-all ./...
-$(RESCACHED_BIN): $(SRC)
+$(RESCACHED_BIN): cmd/rescached/memfs.go $(SRC)
go build $(DEBUG) ./cmd/rescached
+cmd/rescached/memfs.go: internal/generate_memfs.go _www/public/*
+ go run ./internal/generate_memfs.go
+
$(RESOLVER_BIN): $(SRC)
go build $(DEBUG) ./cmd/resolver
diff --git a/_doc/rescached.cfg.adoc b/_doc/rescached.cfg.adoc
index 4a28bcb..0408441 100644
--- a/_doc/rescached.cfg.adoc
+++ b/_doc/rescached.cfg.adoc
@@ -19,7 +19,6 @@ rescached.cfg - Configuration for rescached service
== DESCRIPTION
These file configure the behaviour of *rescached*(1) service.
-In those file you can see some comment for any option and some possible value.
This section will explain more about each option and how they effect
+rescached+.
@@ -37,6 +36,13 @@ in square bracket:
This group of options contain the main configuration that related to
rescached.
+[[wui.listen]]
+==== +wui.listen+
+
+Format:: [host]:port
+Default:: 127.0.0.1:5380
+Description:: The address to listen for web user interface.
+
[[dir.hosts]]
==== +dir.hosts+
@@ -158,7 +164,7 @@ Description:: Port to serve DNS over HTTP.
Format:: Number
Default:: 853
-Description:: Port to listen for DNS over TLS.
+Description:: Port to serve DNS over TLS.
[[tls.certificate]]
==== +tls.certificate+
@@ -202,6 +208,7 @@ Description:: Delay for pruning caches.
Every N seconds/minutes/hours, rescached will traverse all
caches and remove response that has not been accessed less than
+cache.prune_threshold+.
+Its value must be equal or greater than 1 hour (3600 seconds).
[[cache.prune_threshold]]
==== +cache.prune_threshold+
@@ -209,7 +216,7 @@ caches and remove response that has not been accessed less than
Format:: Duration with time unit. Valid time units are "s", "m", "h".
Default:: -1h
Description:: The duration when the cache will be considered expired.
-Its value must be negative and less than -1 minute.
+Its value must be negative and greater or equal than -1 hour (-3600 seconds).
== EXAMPLE
diff --git a/_www/.gitignore b/_www/.gitignore
new file mode 100644
index 0000000..da93220
--- /dev/null
+++ b/_www/.gitignore
@@ -0,0 +1,4 @@
+/node_modules/
+/public/build/
+
+.DS_Store
diff --git a/_www/package-lock.json b/_www/package-lock.json
new file mode 100644
index 0000000..c5093e1
--- /dev/null
+++ b/_www/package-lock.json
@@ -0,0 +1,598 @@
+{
+ "name": "svelte-app",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
+ "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.8.3"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.9.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz",
+ "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz",
+ "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.9.0",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@polka/url": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz",
+ "integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw=="
+ },
+ "@rollup/plugin-commonjs": {
+ "version": "11.0.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.2.tgz",
+ "integrity": "sha512-MPYGZr0qdbV5zZj8/2AuomVpnRVXRU5XKXb3HVniwRoRCreGlf5kOE081isNWeiLIi6IYkwTX9zE0/c7V8g81g==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.0.0",
+ "estree-walker": "^1.0.1",
+ "is-reference": "^1.1.2",
+ "magic-string": "^0.25.2",
+ "resolve": "^1.11.0"
+ }
+ },
+ "@rollup/plugin-node-resolve": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz",
+ "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.0.8",
+ "@types/resolve": "0.0.8",
+ "builtin-modules": "^3.1.0",
+ "is-module": "^1.0.0",
+ "resolve": "^1.14.2"
+ }
+ },
+ "@rollup/pluginutils": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.10.tgz",
+ "integrity": "sha512-d44M7t+PjmMrASHbhgpSbVgtL6EFyX7J4mYxwQ/c5eoaE6N2VgCgEcWVzNnwycIloti+/MpwFr8qfw+nRw00sw==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "0.0.39",
+ "estree-walker": "^1.0.1",
+ "picomatch": "^2.2.2"
+ }
+ },
+ "@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "13.13.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz",
+ "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==",
+ "dev": true
+ },
+ "@types/resolve": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
+ "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "acorn": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz",
+ "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "anymatch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+ "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "async-limiter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
+ "dev": true
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
+ "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "chokidar": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
+ "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.1.2",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.4.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "console-clear": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz",
+ "integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ=="
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "fsevents": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "dev": true,
+ "optional": true
+ },
+ "get-port": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
+ "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw="
+ },
+ "glob-parent": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+ "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+ "dev": true
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "is-reference": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
+ "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "0.0.39"
+ }
+ },
+ "jest-worker": {
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
+ "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
+ "dev": true,
+ "requires": {
+ "merge-stream": "^2.0.0",
+ "supports-color": "^6.1.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="
+ },
+ "livereload": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.1.tgz",
+ "integrity": "sha512-9g7sua11kkyZNo2hLRCG3LuZZwqexoyEyecSlV8cAsfAVVCZqLzVir6XDqmH0r+Vzgnd5LrdHDMyjtFnJQLAYw==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^3.3.0",
+ "livereload-js": "^3.1.0",
+ "opts": ">= 1.2.0",
+ "ws": "^6.2.1"
+ }
+ },
+ "livereload-js": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.2.2.tgz",
+ "integrity": "sha512-xhScbNeC687ZINjEf/bD+BMiPx4s4q0mehcLb3zCc8+mykOtmaBR4vqzyIV9rIGdG9JjHaT0LiFdscvivCjX1Q==",
+ "dev": true
+ },
+ "local-access": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/local-access/-/local-access-1.0.1.tgz",
+ "integrity": "sha512-ykt2pgN0aqIy6KQC1CqdWTWkmUwNgaOS6dcpHVjyBJONA+Xi7AtSB1vuxC/U/0tjIP3wcRudwQk1YYzUvzk2bA=="
+ },
+ "magic-string": {
+ "version": "0.25.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+ "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
+ "dev": true,
+ "requires": {
+ "sourcemap-codec": "^1.4.4"
+ }
+ },
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "mime": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz",
+ "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w=="
+ },
+ "mri": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.5.tgz",
+ "integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg=="
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "opts": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/opts/-/opts-1.2.7.tgz",
+ "integrity": "sha512-hwZhzGGG/GQ7igxAVFOEun2N4fWul31qE9nfBdCnZGQCB5+L7tN9xZ+94B4aUpLOJx/of3zZs5XsuubayQYQjA==",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+ "dev": true
+ },
+ "readdirp": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
+ "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "require-relative": {
+ "version": "0.8.7",
+ "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
+ "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+ "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "rollup": {
+ "version": "1.32.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz",
+ "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "*",
+ "@types/node": "*",
+ "acorn": "^7.1.0"
+ }
+ },
+ "rollup-plugin-livereload": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-livereload/-/rollup-plugin-livereload-1.3.0.tgz",
+ "integrity": "sha512-abyqXaB21+nFHo+vJULBqfzNx6zXABC19UyvqgDfdoxR/8pFAd041GO+GIUe8ZYC2DbuMUmioh1Lvbk14YLZgw==",
+ "dev": true,
+ "requires": {
+ "livereload": "^0.9.1"
+ }
+ },
+ "rollup-plugin-svelte": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-5.2.1.tgz",
+ "integrity": "sha512-wc93cN66sRpX6uFljVFqvWT6NU3V5ab/uLXKt2UiARuexFU/ctolzkmdXM7WM5iKdTX9scToS9sabJTJV4DUMA==",
+ "dev": true,
+ "requires": {
+ "require-relative": "^0.8.7",
+ "rollup-pluginutils": "^2.8.2",
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "rollup-plugin-terser": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.0.tgz",
+ "integrity": "sha512-XGMJihTIO3eIBsVGq7jiNYOdDMb3pVxuzY0uhOE/FM4x/u9nQgr3+McsjzqBn3QfHIpNSZmFnpoKAwHBEcsT7g==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.5.5",
+ "jest-worker": "^24.9.0",
+ "rollup-pluginutils": "^2.8.2",
+ "serialize-javascript": "^2.1.2",
+ "terser": "^4.6.2"
+ }
+ },
+ "rollup-pluginutils": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
+ "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
+ "dev": true,
+ "requires": {
+ "estree-walker": "^0.6.1"
+ },
+ "dependencies": {
+ "estree-walker": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
+ "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
+ "dev": true
+ }
+ }
+ },
+ "sade": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.3.tgz",
+ "integrity": "sha512-m4BctppMvJ60W1dXnHq7jMmFe3hPJZDAH85kQ3ACTo7XZNVUuTItCQ+2HfyaMeV5cKrbw7l4vD/6We3GBxvdJw==",
+ "requires": {
+ "mri": "^1.1.0"
+ }
+ },
+ "serialize-javascript": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
+ "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
+ "dev": true
+ },
+ "sirv": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-0.4.2.tgz",
+ "integrity": "sha512-dQbZnsMaIiTQPZmbGmktz+c74zt/hyrJEB4tdp2Jj0RNv9J6B/OWR5RyrZEvIn9fyh9Zlg2OlE2XzKz6wMKGAw==",
+ "requires": {
+ "@polka/url": "^0.5.0",
+ "mime": "^2.3.1"
+ }
+ },
+ "sirv-cli": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-0.4.5.tgz",
+ "integrity": "sha512-Fl6icSm0EwPrXSGid2xphMp//WNTSX2yENRAGnJuuZNmdc8LvE/BtdZD3MPn28ifAfDqTMwbB3dpcZojAIOiBg==",
+ "requires": {
+ "console-clear": "^1.1.0",
+ "get-port": "^3.2.0",
+ "kleur": "^3.0.0",
+ "local-access": "^1.0.1",
+ "sade": "^1.4.0",
+ "sirv": "^0.4.2",
+ "tinydate": "^1.0.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "source-map-support": {
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+ "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "svelte": {
+ "version": "3.22.2",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.22.2.tgz",
+ "integrity": "sha512-DxumO0+vvHA6NSc2jtVty08I8lFI43q8P2zX6JxZL8J1kqK5NVjad6TRM/twhnWXC+QScnwkZ15O6X1aTsEKTA==",
+ "dev": true
+ },
+ "terser": {
+ "version": "4.6.13",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.13.tgz",
+ "integrity": "sha512-wMvqukYgVpQlymbnNbabVZbtM6PN63AzqexpwJL8tbh/mRT9LE5o+ruVduAGL7D6Fpjl+Q+06U5I9Ul82odAhw==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.20.0",
+ "source-map": "~0.6.1",
+ "source-map-support": "~0.5.12"
+ }
+ },
+ "tinydate": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.2.0.tgz",
+ "integrity": "sha512-3GwPk8VhDFnUZ2TrgkhXJs6hcMAIIw4x/xkz+ayK6dGoQmp2nUwKzBXK0WnMsqkh6vfUhpqQicQF3rbshfyJkg=="
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "ws": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+ "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+ "dev": true,
+ "requires": {
+ "async-limiter": "~1.0.0"
+ }
+ }
+ }
+}
diff --git a/_www/package.json b/_www/package.json
new file mode 100644
index 0000000..267b670
--- /dev/null
+++ b/_www/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "svelte-app",
+ "version": "1.0.0",
+ "scripts": {
+ "build": "rollup -c",
+ "dev": "rollup -c -w",
+ "start": "sirv public"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "11.0.2",
+ "@rollup/plugin-node-resolve": "^7.0.0",
+ "rollup": "^1.20.0",
+ "rollup-plugin-livereload": "^1.0.0",
+ "rollup-plugin-svelte": "^5.0.3",
+ "rollup-plugin-terser": "^5.1.2",
+ "svelte": "^3.0.0"
+ },
+ "dependencies": {
+ "sirv-cli": "^0.4.4"
+ }
+}
diff --git a/_www/public/favicon.png b/_www/public/favicon.png
new file mode 100644
index 0000000..0a3c077
--- /dev/null
+++ b/_www/public/favicon.png
Binary files differ
diff --git a/_www/public/global.css b/_www/public/global.css
new file mode 100644
index 0000000..ec905f5
--- /dev/null
+++ b/_www/public/global.css
@@ -0,0 +1,66 @@
+html, body {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ color: #333;
+ margin: 0;
+ padding: 8px;
+ box-sizing: border-box;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+}
+
+a {
+ color: rgb(0,100,200);
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+a:visited {
+ color: rgb(0,80,160);
+}
+
+label {
+ display: block;
+}
+
+input, button, select, textarea {
+ font-family: inherit;
+ font-size: inherit;
+ padding: 0.4em;
+ margin: 0 0 0.5em 0;
+ box-sizing: border-box;
+ border: 1px solid #ccc;
+ border-radius: 2px;
+}
+
+input:disabled {
+ color: #ccc;
+}
+
+input[type="range"] {
+ height: 0;
+}
+
+button {
+ color: #333;
+ background-color: #f4f4f4;
+ outline: none;
+}
+
+button:disabled {
+ color: #999;
+}
+
+button:not(:disabled):active {
+ background-color: #ddd;
+}
+
+button:focus {
+ border-color: #666;
+}
diff --git a/_www/public/index.html b/_www/public/index.html
new file mode 100644
index 0000000..e8e6f92
--- /dev/null
+++ b/_www/public/index.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+
+ <title>rescached</title>
+
+ <link rel="icon" type="image/png" href="/favicon.png" />
+ <link rel="stylesheet" href="/global.css" />
+ <link rel="stylesheet" href="/build/bundle.css" />
+
+ <script defer src="/build/bundle.js"></script>
+ </head>
+
+ <body></body>
+</html>
diff --git a/_www/rollup.config.js b/_www/rollup.config.js
new file mode 100644
index 0000000..ce3c9eb
--- /dev/null
+++ b/_www/rollup.config.js
@@ -0,0 +1,71 @@
+import svelte from 'rollup-plugin-svelte';
+import resolve from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import livereload from 'rollup-plugin-livereload';
+import { terser } from 'rollup-plugin-terser';
+
+const production = !process.env.ROLLUP_WATCH;
+
+export default {
+ input: 'src/main.js',
+ output: {
+ sourcemap: true,
+ format: 'iife',
+ name: 'app',
+ file: 'public/build/bundle.js'
+ },
+ plugins: [
+ svelte({
+ // enable run-time checks when not in production
+ dev: !production,
+ // we'll extract any component CSS out into
+ // a separate file - better for performance
+ css: css => {
+ css.write('public/build/bundle.css');
+ }
+ }),
+
+ // If you have external dependencies installed from
+ // npm, you'll most likely need these plugins. In
+ // some cases you'll need additional configuration -
+ // consult the documentation for details:
+ // https://github.com/rollup/plugins/tree/master/packages/commonjs
+ resolve({
+ browser: true,
+ dedupe: ['svelte']
+ }),
+ commonjs(),
+
+ // In dev mode, call `npm run start` once
+ // the bundle has been generated
+ !production && serve(),
+
+ // Watch the `public` directory and refresh the
+ // browser on changes when not in production
+ !production && livereload('public'),
+
+ // If we're building for production (npm run build
+ // instead of npm run dev), minify
+ production && terser()
+ ],
+ watch: {
+ clearScreen: false
+ }
+};
+
+function serve() {
+ let started = false;
+
+ return {
+ writeBundle() {
+ if (!started) {
+ started = true;
+
+ require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
+ stdio: ['ignore', 'inherit', 'inherit'],
+ shell: true
+ });
+ }
+ }
+ };
+}
diff --git a/_www/src/App.svelte b/_www/src/App.svelte
new file mode 100644
index 0000000..3cde46c
--- /dev/null
+++ b/_www/src/App.svelte
@@ -0,0 +1,53 @@
+<script>
+ import Environment from './Environment.svelte';
+
+ export let name;
+ let state;
+
+ function showEnvironment() {
+ if (state === 'environment') {
+ state = '';
+ } else {
+ state = 'environment';
+ }
+ }
+</script>
+
+<style>
+ div.main {
+ padding: 1em;
+ max-width: 800px;
+ margin: 0px auto;
+ }
+
+ h1 {
+ color: #ff3e00;
+ text-transform: uppercase;
+ font-size: normal;
+ font-weight: 100;
+ }
+
+ @media (width: 640px) {
+ div.main {
+ max-width: none;
+ }
+ }
+</style>
+
+<div class="main">
+ <h1> {name} </h1>
+ <nav class="menu">
+ <a href="#" on:click={showEnvironment}>
+ Environment
+ </a>
+ </nav>
+
+ {#if state === 'environment'}
+ <Environment/>
+ {:else if state === 'hosts'}
+ {:else}
+ <p>
+ Welcome to rescached!
+ </p>
+ {/if}
+</div>
diff --git a/_www/src/Environment.svelte b/_www/src/Environment.svelte
new file mode 100644
index 0000000..cab0c5f
--- /dev/null
+++ b/_www/src/Environment.svelte
@@ -0,0 +1,248 @@
+<script>
+ import { onMount } from 'svelte';
+
+ import LabelHint from "./LabelHint.svelte";
+ import InputNumber from "./InputNumber.svelte";
+ import InputAddress from "./InputAddress.svelte";
+
+ const nanoSeconds = 1000000000;
+ let apiEnvironment = "http://127.0.0.1:5380/api/environment"
+ let env = {
+ NameServers: [],
+ };
+
+ onMount(async () => {
+ const res = await fetch(apiEnvironment);
+ let got = await res.json();
+ got.PruneDelay = got.PruneDelay / nanoSeconds;
+ got.PruneThreshold = got.PruneThreshold / nanoSeconds;
+ env = Object.assign(env, got)
+ });
+
+ function addNameServer() {
+ env.NameServers = [...env.NameServers, '']
+ }
+
+ function deleteNameServer(ns) {
+ for (let x = 0; x < env.NameServers.length; x++) {
+ if (env.NameServers[x] === ns) {
+ env.NameServers.splice(x, 1);
+ env.NameServers = env.NameServers;
+ break;
+ }
+ }
+ }
+
+ async function updateEnvironment() {
+ let got = {};
+
+ Object.assign(got, env)
+ got.PruneDelay = got.PruneDelay * nanoSeconds;
+ got.PruneThreshold = got.PruneThreshold * nanoSeconds;
+
+ const res = await fetch(apiEnvironment, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(got),
+ });
+
+ const resJSON = await res.json()
+
+ console.log(resJSON);
+ }
+</script>
+
+<style>
+ input {
+ width: 100%;
+ }
+ .input-deletable {
+ width: 100%;
+ }
+ .input-deletable > input {
+ float: left;
+ max-width: calc(100% - 80px);
+ }
+ .input-deletable > button {
+ float: left;
+ width: 80px;
+ }
+ .input-suffix input {
+ width: 70%;
+ }
+ .input-suffix input[type="checkbox"] {
+ width: auto;
+ }
+ .input-suffix .suffix {
+ width: 30%;
+ }
+</style>
+
+<div class="environment">
+<h2>
+ / Environment
+</h2>
+
+<p>
+This page allow you to change the rescached environment.
+Upon save, the rescached service will be restarted.
+</p>
+
+<h3>rescached</h3>
+<div>
+ <LabelHint
+ target="FileResolvConf"
+ title="System resolv.conf"
+ info="A path to dynamically generated resolv.conf(5) by
+resolvconf(8). If set, the nameserver values in referenced file will
+replace 'parent' value and 'parent' will become a fallback in
+case the referenced file being deleted or can not be parsed."
+ ></LabelHint>
+ <input name="FileResolvConf" bind:value={env.FileResolvConf}>
+
+ <LabelHint
+ target="Debug"
+ title="Debug level"
+ info="This option only used for debugging program or if user
+want to monitor what kind of traffic goes in and out of rescached."
+ ></LabelHint>
+ <InputNumber min=0 max=3 bind:val={env.Debug} unit="">
+ </InputNumber>
+</div>
+
+<h3>DNS server</h3>
+<div>
+ <LabelHint
+ target="NameServers"
+ title="Name servers"
+ info="List of parent DNS servers."
+ ></LabelHint>
+ {#each env.NameServers as ns}
+ <div class="input-deletable">
+ <input bind:value={ns}>
+ <button on:click={deleteNameServer(ns)}>
+ Delete
+ </button>
+ </div>
+ {/each}
+ <button on:click={addNameServer}>
+ Add
+ </button>
+
+ <LabelHint
+ target="ListenAddress"
+ title="Listen address"
+ info="Address in local network where rescached will
+listening for query from client through UDP and TCP.
+<br/>
+If you want rescached to serve a query from another host in your local
+network, change this value to <tt>0.0.0.0:53</tt>."
+ ></LabelHint>
+ <InputAddress
+ bind:value={env.ListenAddress}
+ ></InputAddress>
+
+ <LabelHint
+ target="HTTPPort"
+ title="HTTP listen port"
+ info="Port to serve DNS over HTTP"
+ ></LabelHint>
+ <InputNumber min=0 max=65535 bind:val={env.HTTPPort} unit="">
+ </InputNumber>
+
+ <LabelHint
+ target="TLSPort"
+ title="TLS listen port"
+ info="Port to listen for DNS over TLS"
+ ></LabelHint>
+ <InputNumber min=0 max=65535 bind:val={env.TLSPort} unit="">
+ </InputNumber>
+
+ <LabelHint
+ target="TLSCertFile"
+ title="TLS certificate"
+ info="Path to certificate file to serve DNS over TLS and
+HTTPS"></LabelHint>
+ <input name="TLSCertFile" bind:value={env.TLSCertFile}>
+
+ <LabelHint
+ target="TLSPrivateKey"
+ title="TLS private key"
+ info="Path to certificate private key file to serve DNS over TLS and
+HTTPS."
+ ></LabelHint>
+ <input name="TLSPrivateKey" bind:value={env.TLSPrivateKey}>
+
+ <LabelHint
+ target="TLSAllowInsecure"
+ title="TLS allow insecure"
+ info="If its true, allow serving DoH and DoT with self signed
+certificate."
+ ></LabelHint>
+ <div class="input-suffix">
+ <input
+ name="TLSAllowInsecure"
+ type=checkbox
+ bind:checked={env.TLSAllowInsecure}
+ >
+ <span class="suffix">
+ Yes
+ </span>
+ </div>
+
+ <LabelHint
+ target="DoHBehindProxy"
+ title="DoH behind proxy"
+ info="If its true, serve DNS over HTTP only, even if
+certificate files is defined.
+This allow serving DNS request forwarded by another proxy server."
+ ></LabelHint>
+ <div class="input-suffix">
+ <input
+ name="DoHBehindProxy"
+ type=checkbox
+ bind:checked={env.DoHBehindProxy}
+ >
+ <span class="suffix">
+ Yes
+ </span>
+ </div>
+
+ <LabelHint
+ target="PruneDelay"
+ title="Prune delay"
+ info="Delay for pruning caches.
+Every N seconds, rescached will traverse all caches and remove response that
+has not been accessed less than cache.prune_threshold.
+Its value must be equal or greater than 1 hour (3600 seconds).
+"
+ ></LabelHint>
+ <InputNumber
+ min=3600
+ max=36000
+ bind:val={env.PruneDelay}
+ unit="Seconds"
+ ></InputNumber>
+
+ <LabelHint
+ target="PruneThreshold"
+ title="Prune threshold"
+ info="The duration when the cache will be considered expired.
+Its value must be negative and greater or equal than -1 hour (-3600 seconds)."
+ ></LabelHint>
+ <InputNumber
+ min=-36000
+ max=-3600
+ bind:val={env.PruneThreshold}
+ unit="Seconds"
+ ></InputNumber>
+</div>
+
+<div>
+ <button on:click={updateEnvironment}>
+ Save
+ </button>
+</div>
+</div>
diff --git a/_www/src/InputAddress.svelte b/_www/src/InputAddress.svelte
new file mode 100644
index 0000000..014004a
--- /dev/null
+++ b/_www/src/InputAddress.svelte
@@ -0,0 +1,48 @@
+<script>
+ export let value = "";
+ let isInvalid = false;
+ let error = "";
+
+ function onBlur() {
+ const ipport = value.split(":");
+ if (ipport.length !== 2) {
+ isInvalid = true;
+ return;
+ }
+ const ip = ipport[0];
+ if (ip.length > 0) {
+ const nums = ip.split(".");
+ if (nums.length != 4) {
+ isInvalid = true;
+ error = "invalid IP address";
+ return;
+ }
+ }
+ const port = parseInt(ipport[1]);
+ if (isNaN(port) || port <= 0 || port >= 65535) {
+ isInvalid = true;
+ error = "invalid port number";
+ return;
+ }
+ isInvalid = false;
+ value = ip +":"+ port;
+ }
+</script>
+
+<style>
+ .invalid {
+ color: red;
+ }
+</style>
+
+<div class="input-address">
+ <input
+ type="text"
+ bind:value={value}
+ on:blur={onBlur}
+ class:invalid={isInvalid}
+ >
+ {#if isInvalid}
+ <span class="invalid">{error}</span>
+ {/if}
+</div>
diff --git a/_www/src/InputNumber.svelte b/_www/src/InputNumber.svelte
new file mode 100644
index 0000000..6efee06
--- /dev/null
+++ b/_www/src/InputNumber.svelte
@@ -0,0 +1,35 @@
+<script>
+ export let min;
+ export let max;
+ export let val = 0;
+ export let unit;
+
+ function onChange() {
+ value = +value
+ if (isNaN(value)) {
+ value = max
+ } else if (value < min) {
+ value = min
+ } else if (value > max) {
+ value = max
+ }
+ }
+</script>
+
+<style>
+ .input-number input {
+ width: 70%;
+ }
+ .input-number .suffix {
+ width: 30%;
+ }
+</style>
+
+<div class="input-number">
+ <input type="number" on:change={onChange} bind:value={val}>
+ {#if unit !== ''}
+ <span class="suffix">
+ {unit}
+ </span>
+ {/if}
+</div>
diff --git a/_www/src/LabelHint.svelte b/_www/src/LabelHint.svelte
new file mode 100644
index 0000000..72b2786
--- /dev/null
+++ b/_www/src/LabelHint.svelte
@@ -0,0 +1,46 @@
+<script>
+ export let target;
+ export let title;
+ export let info;
+ let showInfo = false;
+</script>
+
+<style>
+ label.label-hint {
+ margin-top: 1em;
+ max-width: 100%;
+ }
+ .label-hint-title {
+ margin-bottom: 4px;
+ }
+ .label-hint-toggle {
+ border-radius: 50%;
+ border: 1px solid grey;
+ cursor: pointer;
+ display: inline-block;
+ float: right;
+ font-size: 12px;
+ height: 14px;
+ line-height: 14px;
+ padding: 2px;
+ text-align: center;
+ width: 14px;
+ }
+ .label-hint-info {
+ background-color: #eee;
+ border-radius: 8px;
+ margin: 8px 0px;
+ padding: 1em;
+ }
+</style>
+
+<label for={target} class="label-hint"></label>
+<div class="label-hint-title">
+ {title}:
+ <span class="label-hint-toggle" on:click={() => showInfo = !showInfo}>
+ ?
+ </span>
+</div>
+{#if showInfo}
+<div class="label-hint-info">{@html info}</div>
+{/if}
diff --git a/_www/src/main.js b/_www/src/main.js
new file mode 100644
index 0000000..756243d
--- /dev/null
+++ b/_www/src/main.js
@@ -0,0 +1,10 @@
+import App from "./App.svelte"
+
+const app = new App({
+ target: document.body,
+ props: {
+ name: "rescached",
+ },
+})
+
+export default app
diff --git a/cmd/rescached/main.go b/cmd/rescached/main.go
index 8d259f4..09f6e1d 100644
--- a/cmd/rescached/main.go
+++ b/cmd/rescached/main.go
@@ -33,9 +33,12 @@ func main() {
log.Fatal(err)
}
- go run(rcd)
+ err = rcd.Start()
+ if err != nil {
+ log.Fatal(err)
+ }
- if debug.Value >= 2 {
+ if debug.Value >= 3 {
go debugRuntime()
}
@@ -60,17 +63,3 @@ func debugRuntime() {
memHeap.DiffHeapObjects)
}
}
-
-func run(rcd *rescached.Server) {
- defer func() {
- err := recover()
- if err != nil {
- log.Println("panic: ", err)
- }
- }()
-
- err := rcd.Start()
- if err != nil {
- log.Println(err)
- }
-}
diff --git a/cmd/rescached/rescached.cfg b/cmd/rescached/rescached.cfg
index 9f403b0..0df343b 100644
--- a/cmd/rescached/rescached.cfg
+++ b/cmd/rescached/rescached.cfg
@@ -5,28 +5,34 @@
##
[rescached]
-#dir.hosts=/etc/rescached/hosts.d
-#dir.master=/etc/rescached/master.d
-#file.resolvconf=/etc/rescached/resolv.conf
-#debug=0
+dir.hosts=
+dir.master=
+file.resolvconf=
+debug=0
[dns "server"]
#parent=udp://18.136.35.199
#parent=tcp://18.136.35.199
-parent=https://kilabit.info/dns-query
listen = 127.0.0.1:53
## Uncomment line below if you want to serve DNS to other computers.
#listen = 0.0.0.0:53
#http.port = 443
+http.port = 0
+
#tls.port = 853
+tls.port = 0
+
+#tls.certificate = /etc/rescached/localhost.cert.pem
+tls.certificate =
-#tls.certificate = /etc/rescached/localhost.cert.pem
-#tls.private_key = /etc/rescached/localhost.key.pem
-#tls.allow_insecure = false
+#tls.private_key = /etc/rescached/localhost.key.pem
+tls.private_key =
-#doh.behind_proxy = false
+tls.allow_insecure = false
+doh.behind_proxy = false
-#cache.prune_delay = 1h
-#cache.prune_threshold = -1h
+cache.prune_delay = 1h0m0s
+cache.prune_threshold = -1h0m0s
+parent = https://kilabit.info/dns-query
diff --git a/go.mod b/go.mod
index 529f86e..3945da1 100644
--- a/go.mod
+++ b/go.mod
@@ -2,6 +2,6 @@ module github.com/shuLhan/rescached-go/v3
go 1.13
-require github.com/shuLhan/share v0.15.0
+require github.com/shuLhan/share v0.15.1-0.20200516135503-9e0bd9a6fefc
//replace github.com/shuLhan/share => ../share
diff --git a/go.sum b/go.sum
index 7e77a65..5e3ab57 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/shuLhan/share v0.15.0 h1:VxLmHI426yYSJg8tMKSc9HmhQcF0L8O0Tr3ixtgzUYY=
-github.com/shuLhan/share v0.15.0/go.mod h1:mpa0ub5qmuko/muUlOROOqLCSHKU76GzuAR/sUaSwRo=
+github.com/shuLhan/share v0.15.1-0.20200516135503-9e0bd9a6fefc h1:DqO6rIUITvdYjT/T/357cnZohfdWMyhjLfLBD3FeVPY=
+github.com/shuLhan/share v0.15.1-0.20200516135503-9e0bd9a6fefc/go.mod h1:mpa0ub5qmuko/muUlOROOqLCSHKU76GzuAR/sUaSwRo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
diff --git a/internal/generate_memfs.go b/internal/generate_memfs.go
new file mode 100644
index 0000000..552b924
--- /dev/null
+++ b/internal/generate_memfs.go
@@ -0,0 +1,31 @@
+// Copyright 2020, Shulhan <ms@kilabit.info>. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+package main
+
+import (
+ "log"
+
+ "github.com/shuLhan/share/lib/memfs"
+)
+
+func main() {
+ includes := []string{
+ `.*\.html`,
+ `.*\.js`,
+ `.*\.css`,
+ }
+
+ mfs, err := memfs.New("_www/public/", includes, nil, true)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = mfs.GoGenerate("main", "cmd/rescached/memfs.go", memfs.EncodingGzip)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/options.go b/options.go
index ae9cf6f..93fb499 100644
--- a/options.go
+++ b/options.go
@@ -8,6 +8,7 @@ import (
"fmt"
"io/ioutil"
"log"
+ "strconv"
"github.com/shuLhan/share/lib/debug"
"github.com/shuLhan/share/lib/dns"
@@ -16,11 +17,37 @@ import (
libstrings "github.com/shuLhan/share/lib/strings"
)
+const (
+ defWuiAddress = "127.0.0.1:5380"
+)
+
+const (
+ sectionNameDNS = "dns"
+ sectionNameRescached = "rescached"
+
+ subNameServer = "server"
+
+ keyDebug = "debug"
+ keyFileResolvConf = "file.resolvconf"
+
+ keyCachePruneDelay = "cache.prune_delay"
+ keyCachePruneThreshold = "cache.prune_threshold"
+ keyDohBehindProxy = "doh.behind_proxy"
+ keyHTTPPort = "http.port"
+ keyListen = "listen"
+ keyParent = "parent"
+ keyTLSAllowInsecure = "tls.allow_insecure"
+ keyTLSCertificate = "tls.certificate"
+ keyTLSPort = "tls.port"
+ keyTLSPrivateKey = "tls.private_key"
+)
+
//
// Options for running rescached.
//
type Options struct {
dns.ServerOptions
+ WuiListen string `ini:"rescached::wui.listen"`
DirHosts string `ini:"rescached::dir.hosts"`
DirMaster string `ini:"rescached::dir.master"`
FileResolvConf string `ini:"rescached::file.resolvconf"`
@@ -43,7 +70,7 @@ func loadOptions(file string) (opts *Options) {
err = ini.Unmarshal(cfg, opts)
if err != nil {
- log.Println("rescached: loadOptions %q: %s", file, err)
+ log.Printf("rescached: loadOptions %q: %s", file, err)
return opts
}
@@ -68,6 +95,9 @@ func NewOptions() *Options {
// init check and initialize the Options instance with default values.
//
func (opts *Options) init() {
+ if len(opts.WuiListen) == 0 {
+ opts.WuiListen = defWuiAddress
+ }
if len(opts.ListenAddress) == 0 {
opts.ListenAddress = "127.0.0.1:53"
}
@@ -106,3 +136,45 @@ func (opts *Options) loadResolvConf() (ok bool, err error) {
return true, nil
}
+
+//
+// write the options values back to file.
+//
+func (opts *Options) write(file string) (err error) {
+ in, err := ini.Open(file)
+ if err != nil {
+ return fmt.Errorf("write: %w", err)
+ }
+
+ in.Set(sectionNameRescached, "", keyFileResolvConf, opts.FileResolvConf)
+ in.Set(sectionNameRescached, "", keyDebug, strconv.Itoa(opts.Debug))
+
+ in.UnsetAll(sectionNameDNS, subNameServer, keyParent)
+ for _, ns := range opts.NameServers {
+ in.Add(sectionNameDNS, subNameServer, keyParent, ns)
+ }
+
+ in.Set(sectionNameDNS, subNameServer, keyListen,
+ opts.ServerOptions.ListenAddress)
+
+ in.Set(sectionNameDNS, subNameServer, keyHTTPPort,
+ strconv.Itoa(int(opts.ServerOptions.HTTPPort)))
+
+ in.Set(sectionNameDNS, subNameServer, keyTLSPort,
+ strconv.Itoa(int(opts.ServerOptions.TLSPort)))
+ in.Set(sectionNameDNS, subNameServer, keyTLSCertificate,
+ opts.ServerOptions.TLSCertFile)
+ in.Set(sectionNameDNS, subNameServer, keyTLSPrivateKey,
+ opts.ServerOptions.TLSPrivateKey)
+ in.Set(sectionNameDNS, subNameServer, keyTLSAllowInsecure,
+ fmt.Sprintf("%t", opts.ServerOptions.TLSAllowInsecure))
+ in.Set(sectionNameDNS, subNameServer, keyDohBehindProxy,
+ fmt.Sprintf("%t", opts.ServerOptions.DoHBehindProxy))
+
+ in.Set(sectionNameDNS, subNameServer, keyCachePruneDelay,
+ fmt.Sprintf("%s", opts.ServerOptions.PruneDelay))
+ in.Set(sectionNameDNS, subNameServer, keyCachePruneThreshold,
+ fmt.Sprintf("%s", opts.ServerOptions.PruneThreshold))
+
+ return in.Save(file)
+}
diff --git a/rescached.go b/rescached.go
index ccb2e39..74fc949 100644
--- a/rescached.go
+++ b/rescached.go
@@ -8,9 +8,11 @@ package rescached
import (
"fmt"
"log"
+ "sync"
"github.com/shuLhan/share/lib/debug"
"github.com/shuLhan/share/lib/dns"
+ "github.com/shuLhan/share/lib/http"
libio "github.com/shuLhan/share/lib/io"
)
@@ -20,6 +22,9 @@ type Server struct {
dns *dns.Server
opts *Options
rcWatcher *libio.Watcher
+
+ httpd *http.Server
+ httpdRunner sync.Once
}
//
@@ -32,21 +37,16 @@ func New(fileConfig string) (srv *Server, err error) {
fmt.Printf("rescached: config: %+v\n", opts)
}
- dnsServer, err := dns.NewServer(&opts.ServerOptions)
- if err != nil {
- return nil, err
- }
-
- dnsServer.LoadHostsDir(opts.DirHosts)
- dnsServer.LoadMasterDir(opts.DirMaster)
- dnsServer.LoadHostsFile("")
-
srv = &Server{
fileConfig: fileConfig,
- dns: dnsServer,
opts: opts,
}
+ err = srv.httpdInit()
+ if err != nil {
+ return nil, err
+ }
+
return srv, nil
}
@@ -55,6 +55,15 @@ func New(fileConfig string) (srv *Server, err error) {
// it.
//
func (srv *Server) Start() (err error) {
+ srv.dns, err = dns.NewServer(&srv.opts.ServerOptions)
+ if err != nil {
+ return err
+ }
+
+ srv.dns.LoadHostsDir(srv.opts.DirHosts)
+ srv.dns.LoadMasterDir(srv.opts.DirMaster)
+ srv.dns.LoadHostsFile("")
+
if len(srv.opts.FileResolvConf) > 0 {
srv.rcWatcher, err = libio.NewWatcher(
srv.opts.FileResolvConf, 0, srv.watchResolvConf)
@@ -63,7 +72,27 @@ func (srv *Server) Start() (err error) {
}
}
- return srv.dns.ListenAndServe()
+ go func() {
+ srv.httpdRunner.Do(srv.httpdRun)
+ }()
+
+ go srv.run()
+
+ return nil
+}
+
+func (srv *Server) run() {
+ defer func() {
+ err := recover()
+ if err != nil {
+ log.Println("panic: ", err)
+ }
+ }()
+
+ err := srv.dns.ListenAndServe()
+ if err != nil {
+ log.Println(err)
+ }
}
//
diff --git a/rescached_httpd.go b/rescached_httpd.go
new file mode 100644
index 0000000..4ded760
--- /dev/null
+++ b/rescached_httpd.go
@@ -0,0 +1,144 @@
+// Copyright 2020, Shulhan <ms@kilabit.info>. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rescached
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ stdhttp "net/http"
+
+ liberrors "github.com/shuLhan/share/lib/errors"
+ "github.com/shuLhan/share/lib/http"
+)
+
+const (
+ defHTTPDRootDir = "_www/public/"
+)
+
+func (srv *Server) httpdInit() (err error) {
+ opts := &http.ServerOptions{
+ Root: defHTTPDRootDir,
+ Address: srv.opts.WuiListen,
+ Includes: []string{
+ `.*\.css`,
+ `.*\.html`,
+ `.*\.js`,
+ },
+ CORSAllowOrigins: []string{
+ "http://127.0.0.1:5000",
+ },
+ CORSAllowHeaders: []string{
+ http.HeaderContentType,
+ },
+ }
+
+ srv.httpd, err = http.NewServer(opts)
+ if err != nil {
+ return fmt.Errorf("newHTTPServer: %w", err)
+ }
+
+ err = srv.httpdRegisterEndpoints()
+ if err != nil {
+ return fmt.Errorf("newHTTPServer: %w", err)
+ }
+
+ return nil
+}
+
+func (srv *Server) httpdRegisterEndpoints() (err error) {
+ epAPIGetEnvironment := &http.Endpoint{
+ Method: http.RequestMethodGet,
+ Path: "/api/environment",
+ RequestType: http.RequestTypeJSON,
+ ResponseType: http.ResponseTypeJSON,
+ Call: srv.httpdAPIGetEnvironment,
+ }
+
+ err = srv.httpd.RegisterEndpoint(epAPIGetEnvironment)
+ if err != nil {
+ return err
+ }
+
+ epAPIPostEnvironment := &http.Endpoint{
+ Method: http.RequestMethodPost,
+ Path: "/api/environment",
+ RequestType: http.RequestTypeJSON,
+ ResponseType: http.ResponseTypeJSON,
+ Call: srv.httpdAPIPostEnvironment,
+ }
+
+ err = srv.httpd.RegisterEndpoint(epAPIPostEnvironment)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (srv *Server) httpdRun() {
+ defer func() {
+ err := recover()
+ if err != nil {
+ log.Printf("httpServer: %s", err)
+ }
+ }()
+
+ log.Printf("=== rescached: httpd listening at %s", srv.opts.WuiListen)
+
+ err := srv.httpd.Start()
+ if err != nil {
+ log.Printf("httpServer.run: %s", err)
+ }
+}
+
+func (srv *Server) httpdAPIGetEnvironment(
+ httpRes stdhttp.ResponseWriter, req *stdhttp.Request, reqBody []byte,
+) (
+ resBody []byte, err error,
+) {
+ return json.Marshal(srv.opts)
+}
+
+func (srv *Server) httpdAPIPostEnvironment(
+ httpRes stdhttp.ResponseWriter, req *stdhttp.Request, reqBody []byte,
+) (
+ resBody []byte, err error,
+) {
+ newOpts := new(Options)
+ err = json.Unmarshal(reqBody, newOpts)
+ if err != nil {
+ return nil, err
+ }
+
+ newOpts.init()
+
+ fmt.Printf("new options: %+v\n", newOpts)
+
+ res := &liberrors.E{
+ Code: stdhttp.StatusOK,
+ Message: "Restarting DNS server",
+ }
+
+ err = newOpts.write(srv.fileConfig)
+ if err != nil {
+ log.Println("httpdAPIPostEnvironment:", err.Error())
+ res.Code = stdhttp.StatusInternalServerError
+ res.Message = err.Error()
+ return json.Marshal(res)
+ }
+
+ srv.opts = newOpts
+
+ srv.Stop()
+ err = srv.Start()
+ if err != nil {
+ log.Println("httpdAPIPostEnvironment:", err.Error())
+ res.Code = stdhttp.StatusInternalServerError
+ res.Message = err.Error()
+ }
+
+ return json.Marshal(res)
+}