From b22bc09d3410ad2c1e5cd6b084e2b96afe72023b Mon Sep 17 00:00:00 2001 From: Thomas Lynch Date: Mon, 1 May 2023 00:42:49 +1000 Subject: [PATCH] Start on DNS UI Change table appearances Change display of missing/good cert on domains list Change icons usedin sidebar for consistent outlined variant --- Dockerfile | 2 - README.md | 10 +- components/BackButton.js | 2 +- components/MenuLinks.js | 16 ++- components/RecordSetRow.js | 73 ++++++++++++++ controllers/account.js | 5 +- docker-compose.yml | 3 +- package.json | 3 +- pages/certs.js | 2 +- pages/clusters.js | 2 +- pages/csr.js | 2 +- pages/dns/[domain]/[zone]/[name].js | 7 ++ pages/dns/[domain]/index.js | 149 ++++++++++++++++++++++++++++ pages/domains.js | 26 ++++- pages/map/[name].js | 2 +- pages/stats.js | 2 +- 16 files changed, 277 insertions(+), 29 deletions(-) create mode 100644 components/RecordSetRow.js create mode 100644 pages/dns/[domain]/[zone]/[name].js create mode 100644 pages/dns/[domain]/index.js diff --git a/Dockerfile b/Dockerfile index 56d4051..43d9e68 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,5 +12,3 @@ COPY . /opt RUN npm run build RUN npx next telemetry disable - -CMD ["npm","start"] diff --git a/README.md b/README.md index bb085f3..1c7dd8d 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,8 @@ Provides a control panel interface to conveniently manage clusters (groups of id - Maintenance mode, disables proxying for selected domains and serves an "under maintenance" page from haproxy. - Statistics page with server and backend-level breakdowns based on haproxy stats socket data. Ability to export statistics to influxdb. -##### Todo: -- Better Multi-user support - - allow domain/cluster editing (with user dupe check) for non-admins - - problems w/ ip whitelist and blacklist -- Some kind of payment system -- More advanced rules and ability to allow/block/bot mode based on those rules. -- Show statistics from clusters or servers within a cluster. - - Intelligent auto toggling of proteciton modes based on these stats +## License +GNU AGPLv3, see [LICENSE](LICENSE). #### Screenshots diff --git a/components/BackButton.js b/components/BackButton.js index 435b528..15ee3be 100644 --- a/components/BackButton.js +++ b/components/BackButton.js @@ -3,7 +3,7 @@ import Link from 'next/link'; export default function BackButton({ to }) { return ( - + Back diff --git a/components/MenuLinks.js b/components/MenuLinks.js index 5f25e22..d2931cf 100644 --- a/components/MenuLinks.js +++ b/components/MenuLinks.js @@ -31,15 +31,23 @@ export default withRouter(function MenuLinks({ router }) {
  • - + Domains
  • + {/*
  • + + + + DNS + + +
  • */}
  • - + Clusters @@ -87,7 +95,7 @@ export default withRouter(function MenuLinks({ router }) {
  • - + Rewrites @@ -95,7 +103,7 @@ export default withRouter(function MenuLinks({ router }) {
  • - + Redirects diff --git a/components/RecordSetRow.js b/components/RecordSetRow.js new file mode 100644 index 0000000..f752938 --- /dev/null +++ b/components/RecordSetRow.js @@ -0,0 +1,73 @@ +import Link from 'next/link'; + +export default function RecordSetRow({ domain, name, recordSet }) { + +/* + "a": [ + { "id": "a", "ttl": 300, "ip": "203.28.238.247", "geok": "cn", "geov": ["OC"], "h": true, "fb": ["b", "c"], "sel": 1, "bsel": 3 }, + { "id": "b", "ttl": 300, "ip": "38.60.199.224", "geok": "cn", "geov": ["AS"], "h": true, "fb": ["a", "c"], "sel": 1, "bsel": 3 }, + { "id": "c", "ttl": 300, "ip": "45.88.201.168", "geok": "cn", "geov": ["NA"], "h": true, "fb": ["e", "d"], "sel": 1, "bsel": 3 }, + { "id": "d", "ttl": 300, "ip": "185.125.168.21", "geok": "cn", "geov": ["EU", "AF"], "h": true, "fb": ["c"], "sel": 1, "bsel": 3 }, + { "id": "e", "ttl": 300, "ip": "38.54.57.171", "geok": "cn", "geov": ["SA", "AF"], "h": true, "fb": [], "sel": 1, "bsel": 3 } + ], + "aaaa": [ + { "id": "a", "ttl": 300, "ip": "2a03:94e0:ffff:185:125:168:0:21", "geok": "cn", "geov": ["EU", "AF"], "h": true, "fb": ["b"], "sel": 1, "bsel": 3 }, + { "id": "b", "ttl": 300, "ip": "2a03:94e1:ffff:45:88:201:0:168", "geok": "cn", "geov": ["NA", "SA", "AS", "OC"], "h": true, "fb": ["a"], "sel": 1, "bsel": 3 } + ], + "caa": [ + { "flag": 0, "tag": "issue", "value": "letsencrypt.org" }, + { "flag": 0, "tag": "iodef", "value": "mailto:tom@69420.me" } + ], + "soa": { "ttl": 86400, "minttl": 30, "mbox": "root.basedflare.com.", "ns": "esther.kikeflare.com.", "refresh": 86400, "retry": 7200, "expire": 3600 }, + "txt": [ + { "ttl": 300, "text": "v=spf1 -all" } + ], + "ns": [ + { "ttl": 86400, "host": "esther.kikeflare.com." }, + { "ttl": 86400, "host": "aronowitz.bfcdn.host." }, + { "ttl": 86400, "host": "goldberg.fatpeople.lol." } + ] +*/ + + const type = recordSet[0] + const recordSetArray = Array.isArray(recordSet[1]) ? recordSet[1] : [recordSet[1]]; + return ( + + + {name} + + + {type.toUpperCase()} + + + {recordSetArray.map((r, i) => { + const healthClass = r.h != null + ? (r.h ? "text-success" : "text-danger") + : ""; + return (
    + {r.id ? r.id+': ' : ''} + {r.ip || r.host || r.value || r.ns || r.text} +
    ) + })} + + + {recordSetArray[0].ttl} + + + {recordSetArray.map((r, i) => ( +
    + {r.geok ? 'Geo: ' : ''}{(r.geov||[]).join(', ')} +
    + ))} + + + + + Edit + + + + + ); + +} diff --git a/controllers/account.js b/controllers/account.js index 6a944d5..c1b4a22 100644 --- a/controllers/account.js +++ b/controllers/account.js @@ -201,9 +201,9 @@ exports.login = async (req, res) => { * POST /forms/register * regiser */ -exports.register = async (req, res) => { +exports.register = (req, res) => { return dynamicResponse(req, res, 400, { error: 'Registration is currently disabled, please try again later.' }); - +/* const username = req.body.username.toLowerCase(); const password = req.body.password; const rPassword = req.body.repeat_password; @@ -237,6 +237,7 @@ exports.register = async (req, res) => { }); return dynamicResponse(req, res, 302, { redirect: '/login' }); +*/ }; /** diff --git a/docker-compose.yml b/docker-compose.yml index a96bad6..c1f8714 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,8 @@ services: ports: - "127.0.0.1:3000:3000" environment: - - NODE_ENV=production + - NODE_ENV=development + command: npm run start-dev volumes: - type: bind source: /tmp/acme-tests/.well-known/acme-challenge/ diff --git a/package.json b/package.json index cb2ba79..993036c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "next build", "lint": "next lint", "next": "next build", - "start": "node_modules/gulp/bin/gulp.js; NODE_ENV=production node server.js" + "start": "node_modules/gulp/bin/gulp.js; NODE_ENV=production node server.js", + "start-dev": "node_modules/gulp/bin/gulp.js; NODE_ENV=development node server.js" }, "keywords": [], "author": "Thomas Lynch (fatchan) ", diff --git a/pages/certs.js b/pages/certs.js index 7f2d980..00e9d31 100644 --- a/pages/certs.js +++ b/pages/certs.js @@ -152,7 +152,7 @@ export default function Certs(props) { {/* Certs table */}
    - +
    diff --git a/pages/clusters.js b/pages/clusters.js index fdd4074..cf68559 100644 --- a/pages/clusters.js +++ b/pages/clusters.js @@ -73,7 +73,7 @@ export default function Clusters(props) { {/* Clusters table */}
    -
    +
    {clusterList} diff --git a/pages/csr.js b/pages/csr.js index ef25b6f..4ffe72f 100644 --- a/pages/csr.js +++ b/pages/csr.js @@ -51,7 +51,7 @@ export default function Csr(props) {

    -
    +
    + ); @@ -79,7 +92,7 @@ export default function Domains(props) { {/* Domains table */}
    -
    diff --git a/pages/dns/[domain]/[zone]/[name].js b/pages/dns/[domain]/[zone]/[name].js new file mode 100644 index 0000000..da20ccf --- /dev/null +++ b/pages/dns/[domain]/[zone]/[name].js @@ -0,0 +1,7 @@ +export default function EditRecordPage({ domain, name, recordSet }) { + return 'todo'; +} + +export async function getServerSideProps({ req, res, query, resolvedUrl, locale, locales, defaultLocale}) { + return { props: { user: res.locals.user || null, ...query } } +} diff --git a/pages/dns/[domain]/index.js b/pages/dns/[domain]/index.js new file mode 100644 index 0000000..6888ba1 --- /dev/null +++ b/pages/dns/[domain]/index.js @@ -0,0 +1,149 @@ +import { useRouter } from "next/router"; +import React, { useState, useEffect } from 'react'; +import Head from 'next/head'; +import RecordSetRow from '../../../components/RecordSetRow.js'; +import BackButton from '../../../components/BackButton.js'; +import ErrorAlert from '../../../components/ErrorAlert.js'; +import * as API from '../../../api.js'; + +const DnsDomainIndexPage = (props) => { + + const router = useRouter(); + const { domain } = router.query; + const [state, dispatch] = useState({ + ...props, + recordSets: [ + { + "@": { + "a": [ + { "id": "a", "ttl": 300, "ip": "203.28.238.247", "geok": "cn", "geov": ["OC"], "h": true, "fb": ["b", "c"], "sel": 1, "bsel": 3 }, + { "id": "b", "ttl": 300, "ip": "38.60.199.224", "geok": "cn", "geov": ["AS"], "h": false, "fb": ["a", "c"], "sel": 1, "bsel": 3 }, + { "id": "c", "ttl": 300, "ip": "45.88.201.168", "geok": "cn", "geov": ["NA"], "h": true, "fb": ["e", "d"], "sel": 1, "bsel": 3 }, + { "id": "d", "ttl": 300, "ip": "185.125.168.21", "geok": "cn", "geov": ["EU", "AF"], "h": true, "fb": ["c"], "sel": 1, "bsel": 3 }, + { "id": "e", "ttl": 300, "ip": "38.54.57.171", "geok": "cn", "geov": ["SA", "AF"], "h": true, "fb": [], "sel": 1, "bsel": 3 } + ], + "aaaa": [ + { "id": "a", "ttl": 300, "ip": "2a03:94e0:ffff:185:125:168:0:21", "geok": "cn", "geov": ["EU", "AF"], "h": true, "fb": ["b"], "sel": 1, "bsel": 3 }, + { "id": "b", "ttl": 300, "ip": "2a03:94e1:ffff:45:88:201:0:168", "geok": "cn", "geov": ["NA", "SA", "AS", "OC"], "h": true, "fb": ["a"], "sel": 1, "bsel": 3 } + ], + "caa": [ + { "flag": 0, "tag": "issue", "value": "letsencrypt.org" }, + { "flag": 0, "tag": "iodef", "value": "mailto:tom@69420.me" } + ], + "soa": { "ttl": 86400, "minttl": 30, "mbox": "root.basedflare.com.", "ns": "esther.kikeflare.com.", "refresh": 86400, "retry": 7200, "expire": 3600 }, + "txt": [ + { "ttl": 300, "text": "v=spf1 -all" } + ], + "ns": [ + { "ttl": 86400, "host": "esther.kikeflare.com." }, + { "ttl": 86400, "host": "aronowitz.bfcdn.host." }, + { "ttl": 86400, "host": "goldberg.fatpeople.lol." } + ] + } + } + ] + }); + const [error, setError] = useState(); + + useEffect(() => { + if (!state.recordSets) { + // API.getDomainRecordSets(domain, dispatch, setError, router); + } + }, [state.recordSets, domain, router]); + + if (state.recordSets == null) { + return ( +
    + {error && } +
    + Loading... +
    +
    + ); + } + + const { user, recordSets, csrf } = state; + + const recordSetRows = recordSets + .map(recordSet => { + return Object.entries(recordSet) + .map(e => { + return Object.entries(e[1]) + .map((recordSet, i) => ( + + )); + }); + }) + + + return ( + <> + + + + {domain} / Records List + + + + {error && } + +
    + {domain} / Records List: +
    + + {/* Record sets table */} +
    + + + + {/* header row */} + + + + + + + + + + {recordSetRows} + + +
    + Name + + Type + + Content + + TTL + + Details + + Actions +
    +
    + + {/* back to account */} + + + + ); + +}; + +export async function getServerSideProps({ req, res, query, resolvedUrl, locale, locales, defaultLocale}) { + return { + props: { + user: res.locals.user || null, + ...query + } + }; +} + +export default DnsDomainIndexPage; diff --git a/pages/domains.js b/pages/domains.js index bc4726c..3dfed07 100644 --- a/pages/domains.js +++ b/pages/domains.js @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; import Head from 'next/head'; import BackButton from '../components/BackButton.js'; import ErrorAlert from '../components/ErrorAlert.js'; @@ -42,7 +43,9 @@ export default function Domains(props) { await API.getDomains(dispatch, setError, router); } - const domainList = user.domains.map((d, i) => { + const domainList = user.domains + .sort((a, b) => a.localeCompare(b)) + .map((d, i) => { //TODO: refactor, to component const domainCert = certs.find(c => c.subject === d || c.altnames.includes(d)); return ( @@ -58,7 +61,17 @@ export default function Domains(props) { {d}
    - {domainCert ? '🔒 '+domainCert.storageName : '-'} + {domainCert + ? {domainCert.storageName} + : No Certificate} + + {d.split('.').length < 2 && + + + DNS + + }
    +
    @@ -88,7 +101,10 @@ export default function Domains(props) { Domain + @@ -96,7 +112,7 @@ export default function Domains(props) { {/* Add new domain form */} -
    - HTTPS? + HTTPS Certificate + + Edit DNS
    +
    diff --git a/pages/map/[name].js b/pages/map/[name].js index 07df413..1649093 100644 --- a/pages/map/[name].js +++ b/pages/map/[name].js @@ -161,7 +161,7 @@ const MapPage = (props) => { {/* Map table */}
    - +
    {/* header row */} diff --git a/pages/stats.js b/pages/stats.js index 91bc03c..45f90f8 100644 --- a/pages/stats.js +++ b/pages/stats.js @@ -52,7 +52,7 @@ export default function Domains(props) {
    -
    +