diff options
| author | Shulhan <m.shulhan@gmail.com> | 2020-05-18 21:47:45 +0700 |
|---|---|---|
| committer | Shulhan <m.shulhan@gmail.com> | 2020-07-26 03:48:51 +0700 |
| commit | 7574bb6ebff609b9a2889fc573ce3edce256e030 (patch) | |
| tree | d4a118ef858d5916ed19e1fc93a4d02b9fe4b588 | |
| parent | 7b9cba12d356c6fa9ee3988b5c96f8718aafd5f1 (diff) | |
| download | rescached-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-- | .gitignore | 5 | ||||
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | _doc/rescached.cfg.adoc | 13 | ||||
| -rw-r--r-- | _www/.gitignore | 4 | ||||
| -rw-r--r-- | _www/package-lock.json | 598 | ||||
| -rw-r--r-- | _www/package.json | 21 | ||||
| -rw-r--r-- | _www/public/favicon.png | bin | 0 -> 12685 bytes | |||
| -rw-r--r-- | _www/public/global.css | 66 | ||||
| -rw-r--r-- | _www/public/index.html | 17 | ||||
| -rw-r--r-- | _www/rollup.config.js | 71 | ||||
| -rw-r--r-- | _www/src/App.svelte | 53 | ||||
| -rw-r--r-- | _www/src/Environment.svelte | 248 | ||||
| -rw-r--r-- | _www/src/InputAddress.svelte | 48 | ||||
| -rw-r--r-- | _www/src/InputNumber.svelte | 35 | ||||
| -rw-r--r-- | _www/src/LabelHint.svelte | 46 | ||||
| -rw-r--r-- | _www/src/main.js | 10 | ||||
| -rw-r--r-- | cmd/rescached/main.go | 21 | ||||
| -rw-r--r-- | cmd/rescached/rescached.cfg | 28 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 4 | ||||
| -rw-r--r-- | internal/generate_memfs.go | 31 | ||||
| -rw-r--r-- | options.go | 74 | ||||
| -rw-r--r-- | rescached.go | 51 | ||||
| -rw-r--r-- | rescached_httpd.go | 144 |
24 files changed, 1547 insertions, 48 deletions
@@ -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 @@ -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 Binary files differnew file mode 100644 index 0000000..0a3c077 --- /dev/null +++ b/_www/public/favicon.png 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 @@ -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 @@ -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) + } +} @@ -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) +} |
