UI for editing, loading new record page, dynamic fields based on type, etc. And change the format for single zone edit pages to be better suited for editing/new record with the same page files

develop
Thomas Lynch 1 year ago
parent edbe05bdc6
commit e65c28ee43
  1. 5
      api.js
  2. 34
      controllers/dns.js
  3. 4
      pages/certs.js
  4. 4
      pages/clusters.js
  5. 4
      pages/csr.js
  6. 116
      pages/dns/[domain]/[zone]/[type].js
  7. 16
      pages/dns/[domain]/index.js
  8. 6
      pages/domains.js
  9. 4
      pages/map/[name].js
  10. 4
      pages/onboarding.js
  11. 4
      pages/stats.js
  12. 3
      router.js

@ -40,8 +40,8 @@ export async function deleteDomain(body, dispatch, errorCallback, router) {
export async function getDnsDomain(domain, dispatch, errorCallback, router) {
return ApiCall(`/dns/${domain}.json`, 'GET', null, dispatch, errorCallback, router);
}
export async function getDnsRecords(domain, zone, dispatch, errorCallback, router) {
return ApiCall(`/dns/${domain}/${zone}.json`, 'GET', null, dispatch, errorCallback, router);
export async function getDnsRecords(domain, zone, type, dispatch, errorCallback, router) {
return ApiCall(`/dns/${domain}/${zone}/${type}.json`, 'GET', null, dispatch, errorCallback, router);
}
// Certs
@ -142,6 +142,7 @@ export async function ApiCall(route, method='get', body, dispatch, errorCallback
return;
}
dispatch(response);
return response;
} else {
errorCallback('An error occurred');
}

@ -30,10 +30,15 @@ exports.dnsRecordPage = async (app, req, res) => {
if (!res.locals.user.domains.includes(req.params.domain)) {
return res.redirect('/domains');
}
const recordSet = await redis.hget(`${req.params.domain}.`, req.params.zone);
return app.render(req, res, `/dns/${req.params.domain}/${req.params.zone}/${req.params.type}`, {
let recordSet = [{}];
if (req.params.zone && req.params.type) {
const recordSetRaw = await redis.hget(`${req.params.domain}.`, req.params.zone);
recordSet = recordSetRaw[req.params.type];
recordSet = Array.isArray(recordSet) ? recordSet : [recordSet];
}
return app.render(req, res, `/dns/${req.params.domain}/${req.params.zone||"name"}/${req.params.type||"a"}`, {
csrf: req.csrfToken(),
recordSets: [{ [req.params.zone]: recordSet||{} }],
recordSet,
});
};
@ -58,18 +63,35 @@ exports.dnsDomainJson = async (req, res) => {
};
/**
* GET /dns/:domain/:zone.json
* GET /dns/:domain/:zone/:type.json
* domains json data
*/
exports.dnsRecordJson = async (req, res) => {
if (!res.locals.user.domains.includes(req.params.domain)) {
return dynamicResponse(req, res, 403, { error: 'No permission for this domain' });
}
const recordSet = await redis.hget(`${req.params.domain}.`, req.params.zone);
let recordSet = [{}];
if (req.params.zone && req.params.type) {
const recordSetRaw = await redis.hget(`${req.params.domain}.`, req.params.zone);
recordSet = recordSetRaw[req.params.type];
recordSet = Array.isArray(recordSet) ? recordSet : [recordSet];
}
return res.json({
csrf: req.csrfToken(),
user: res.locals.user,
recordSets: [{ [req.params.zone]: recordSet||{} }],
recordSet,
});
};
/**
* POST /post/:domain/:zone/:type
* domains json data
*/
exports.dnsRecordUpdate = async (req, res) => {
if (!res.locals.user.domains.includes(req.params.domain)) {
return dynamicResponse(req, res, 403, { error: 'No permission for this domain' });
}
return dynamicResponse(req, res, 403, { error: 'Not implemented' });
};

@ -22,7 +22,9 @@ export default function Certs(props) {
<div className="d-flex flex-column">
{error && <ErrorAlert error={error} />}
<div className="text-center mb-4">
Loading...
<div className="spinner-border mt-5" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);

@ -23,7 +23,9 @@ export default function Clusters(props) {
<div className="d-flex flex-column">
{error && <ErrorAlert error={error} />}
<div className="text-center mb-4">
Loading...
<div className="spinner-border mt-5" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);

@ -22,7 +22,9 @@ export default function Csr(props) {
<div className="d-flex flex-column">
{error && <ErrorAlert error={error} />}
<div className="text-center mb-4">
Loading...
<div className="spinner-border mt-5" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);

@ -8,47 +8,56 @@ import * as API from '../../../../api.js';
const DnsEditRecordPage = (props) => {
const router = useRouter();
const { domain, zone, type } = router.query;
const [state, dispatch] = useState({
...props,
});
const [state, dispatch] = useState(props);
const { domain, zone: routerZone, type: routerType } = router.query;
const newRecord = router.asPath === `/dns/${domain}/new`;
const [zone, setZone] = useState(routerZone || "name");
const [type, setType] = useState(routerType || "a");
const [recordSet, setRecordSet] = useState(state.recordSet)
const [error, setError] = useState();
useEffect(() => {
if (!state.recordSets) {
API.getDnsRecords(domain, zone, dispatch, setError, router);
if (!recordSet) {
API.getDnsRecords(domain, zone, type, dispatch, setError, router)
.then(res => {
if (res && res.recordSet) {
setRecordSet([...res.recordSet]);
}
});
}
}, [state.recordSets, domain, router]);
}, [recordSet, domain, zone, type, router]);
if (state.recordSets == null) {
if (!recordSet && !newRecord) {
return (
<div className="d-flex flex-column">
{error && <ErrorAlert error={error} />}
<div className="text-center mb-4">
Loading...
<div className="spinner-border mt-5" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);
}
const { user, recordSets, csrf, editing } = state;
let recordSet = recordSets.find(x => x[zone] != null)[zone][type];
recordSet = Array.isArray(recordSet) ? recordSet : [recordSet]
const { csrf } = state;
const supportsGeo = ["a", "aaaa"].includes(type);
const supportsHealth = ["a", "aaaa"].includes(type);
return (
<>
<Head>
<title>
{domain} / Records list / Edit record set
{`${domain} / Records list / ${newRecord?'New':'Edit'} record set`}
</title>
</Head>
{error && <ErrorAlert error={error} />}
<h5 className="fw-bold">
{domain} / Records list / Edit record set:
{domain} / Records list / {newRecord?'New':'Edit'} record set:
</h5>
{/* Record editing form */}
@ -56,12 +65,18 @@ const DnsEditRecordPage = (props) => {
e.preventDefault();
console.log(e)
}}>
<input type="hidden" name="_csrf" value={csrf} />
<div className="row mb-3">
<div className="col">
<label className="w-100">
Type (required)
<select className="form-select" name="value" defaultValue={type} required>
<select
className="form-select"
name="type"
defaultValue={type}
onChange={e => setType(e.target.value)}
required
disabled={!newRecord}>
<option value="">Type</option>
<option value="a">A</option>
<option value="aaaa">AAAA</option>
@ -78,15 +93,27 @@ const DnsEditRecordPage = (props) => {
<div className="col">
<label className="w-100">
Name
<input className="form-control" type="text" name="key" value={zone} readOnly required />
<input
className="form-control"
type="text"
name="name"
defaultValue={zone}
required
disabled={!newRecord}
onChange={e => setZone(e.target.value)}
/>
</label>
</div>
<div className="col">
<label className="w-100">
TTL
<input
className="form-control" type="number" name="key" min="30" required
value={Array.isArray(recordSet) ? recordSet[0].ttl : recordSet.ttl}
className="form-control"
type="number"
name="ttl"
min="30"
required
defaultValue={recordSet[0].ttl || 300}
/>
</label>
</div>
@ -97,7 +124,7 @@ const DnsEditRecordPage = (props) => {
<div className="col-4">
Record selection mode:
<div className="form-check">
<input className="form-check-input" type="radio" name="selection" id="roundrobin" checked={!recordSet[0].geok} />
<input className="form-check-input" type="radio" name="selection" id="roundrobin" defaultChecked />
<label className="form-check-label" htmlFor="roundrobin">
Round Robin
</label>
@ -109,7 +136,7 @@ const DnsEditRecordPage = (props) => {
</label>
</div>
<div className="form-check">
<input className="form-check-input" type="radio" name="selection" id="geo" checked={recordSet[0].geok != null} />
<input className="form-check-input" type="radio" name="selection" id="geo" />
<label className="form-check-label" htmlFor="geo">
Geolocation
</label>
@ -141,31 +168,39 @@ const DnsEditRecordPage = (props) => {
</div>
{recordSet.map((rec, i) => (<>
<div className="row">
<div className="col-2">
{supportsHealth && <div className="col-2">
ID:
<input className="form-control" type="text" name={`id${i}`} defaultValue={rec.id} required />
</div>
</div>}
<div className="col">
<label className="w-100">
Value
<input className="form-control" type="text" name={`value_${i}`} defaultValue={rec.ip || rec.host || rec.value || rec.ns || rec.text} required />
</label>
</div>
<div className="col-auto align-self-end mb-2">
{supportsHealth && <div className="col-auto align-self-end mb-2">
<div className="form-check form-switch">
<input className="form-check-input" type="checkbox" value="" id="flexCheckDefault" checked={rec.h} />
<input className="form-check-input" type="checkbox" value="" id="flexCheckDefault" defaultChecked={rec.h} />
<label className="form-check-label" htmlFor="flexCheckDefault">
Health Check
</label>
</div>
</div>
</div>}
<div className="col-auto ms-auto">
<button className="btn btn-danger mt-4">
<button
className="btn btn-danger mt-4"
onClick={(e) =>{
e.preventDefault();
recordSet.splice(i, 1);
setRecordSet([...recordSet]);
}}
disabled={i === 0}
>
×
</button>
</div>
</div>
<div className="row">
{supportsGeo && <div className="row">
<div className="col-2">
<label className="w-100">
Geo Key
@ -181,9 +216,9 @@ const DnsEditRecordPage = (props) => {
<input className="form-control" type="text" name={`geov_${i}`} defaultValue={(rec.geov||[]).join(', ')} required />
</label>
</div>
<div className="col-2">
<div className="col-3">
<label className="w-100">
Selector
Fallback Selector
<select className="form-select" name={`sel_${i}`} defaultValue={rec.sel} required>
<option value="0">None</option>
<option value="1">First</option>
@ -192,7 +227,7 @@ const DnsEditRecordPage = (props) => {
</select>
</label>
</div>
<div className="col-2">
<div className="col-3">
<label className="w-100">
Backup Selector
<select className="form-select" name={`bsel_${i}`} defaultValue={rec.bsel} required>
@ -203,10 +238,21 @@ const DnsEditRecordPage = (props) => {
</select>
</label>
</div>
</div>
{i < recordSet.length && <hr className="mb-2 mt-3" />}
</div>}
{i < recordSet.length-1 && <hr className="mb-2 mt-3" />}
</>))}
<div className="row mt-4">
<div className="row mt-2">
<div className="col-auto ms-auto">
<button className="ms-auto btn btn-success mt-2" onClick={(e) =>{
e.preventDefault();
recordSet.push({})
setRecordSet([...recordSet]);
}}>
+
</button>
</div>
</div>
<div className="row mt-5">
<div className="col-auto me-auto">
<BackButton to={`/dns/${domain}`} />
</div>

@ -1,4 +1,5 @@
import { useRouter } from "next/router";
import Link from 'next/link';
import React, { useState, useEffect } from 'react';
import Head from 'next/head';
import RecordSetRow from '../../../components/RecordSetRow.js';
@ -26,7 +27,9 @@ const DnsDomainIndexPage = (props) => {
<div className="d-flex flex-column">
{error && <ErrorAlert error={error} />}
<div className="text-center mb-4">
Loading...
<div className="spinner-border mt-5" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);
@ -56,7 +59,7 @@ const DnsDomainIndexPage = (props) => {
<Head>
<title>
{domain} / Records list
{`${domain} / Records list`}
</title>
</Head>
@ -99,6 +102,15 @@ const DnsDomainIndexPage = (props) => {
</table>
</div>
<div className="my-3">
<Link href={`/dns/${domain}/new`}>
<a className="btn btn-success">
+
</a>
</Link>
</div>
{/* back to account */}
<BackButton to="/domains" />

@ -23,7 +23,9 @@ export default function Domains(props) {
<div className="d-flex flex-column">
{error && <ErrorAlert error={error} />}
<div className="text-center mb-4">
Loading...
<div className="spinner-border mt-5" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);
@ -66,7 +68,7 @@ export default function Domains(props) {
: <span className="text-danger"><i className="bi-exclamation-triangle-fill pe-none me-2" width="16" height="16" />No Certificate</span>}
</td>
<td className="col-1 text-center">
{d.split('.').length < 2 && <Link href={`/dns/${d}`}>
{d.split('.').length <= 2 && <Link href={`/dns/${d}`}>
<a className="btn btn-outline-secondary">
<i className="bi-card-list pe-none me-2" width="16" height="16" />
DNS

@ -25,7 +25,9 @@ const MapPage = (props) => {
<div className="d-flex flex-column">
{error && <ErrorAlert error={error} />}
<div className="text-center mb-4">
Loading...
<div className="spinner-border mt-5" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);

@ -22,7 +22,9 @@ export default function Onboarding(props) {
<div className="d-flex flex-column">
{error && <ErrorAlert error={error} />}
<div className="text-center mb-4">
Loading...
<div className="spinner-border mt-5" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);

@ -22,7 +22,9 @@ export default function Domains(props) {
<div className="d-flex flex-column">
{error && <ErrorAlert error={error} />}
<div className="text-center mb-4">
Loading...
<div className="spinner-border mt-5" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);

@ -166,9 +166,10 @@ const testRouter = (server, app) => {
server.get('/clusters.json', useSession, fetchSession, checkSession, csrfMiddleware, clustersController.clustersJson);
server.get('/domains', useSession, fetchSession, checkSession, csrfMiddleware, domainsController.domainsPage.bind(null, app));
server.get('/domains.json', useSession, fetchSession, checkSession, csrfMiddleware, domainsController.domainsJson);
server.get('/dns/:domain([a-zA-Z0-9-\.]+)/new', useSession, fetchSession, checkSession, csrfMiddleware, dnsController.dnsRecordPage.bind(null, app));
server.get('/dns/:domain([a-zA-Z0-9-\.]+).json', useSession, fetchSession, checkSession, csrfMiddleware, dnsController.dnsDomainJson);
server.get('/dns/:domain([a-zA-Z0-9-\.]+)', useSession, fetchSession, checkSession, csrfMiddleware, dnsController.dnsDomainPage.bind(null, app));
server.get('/dns/:domain([a-zA-Z0-9-\.]+)/:zone([a-zA-Z0-9-\.@]+).json', useSession, fetchSession, checkSession, csrfMiddleware, dnsController.dnsRecordJson);
server.get('/dns/:domain([a-zA-Z0-9-\.]+)/:zone([a-zA-Z0-9-\.@]+)/:type([a-z]+).json', useSession, fetchSession, checkSession, csrfMiddleware, dnsController.dnsRecordJson);
server.get('/dns/:domain([a-zA-Z0-9-\.]+)/:zone([a-zA-Z0-9-\.@]+)/:type([a-z]+)', useSession, fetchSession, checkSession, csrfMiddleware, dnsController.dnsRecordPage.bind(null, app));
server.get('/certs', useSession, fetchSession, checkSession, useHaproxy, csrfMiddleware, certsController.certsPage.bind(null, app));
server.get('/certs.json', useSession, fetchSession, checkSession, useHaproxy, csrfMiddleware, certsController.certsJson);

Loading…
Cancel
Save