Remove the stupid concept of clusters being in the DB, purely read from .env for now

master
Thomas Lynch 2 weeks ago
parent a7256f3078
commit ccf0e19e28
  1. 21
      components/ClusterRow.js
  2. 27
      controllers/account.js
  3. 96
      controllers/clusters.js
  4. 49
      pages/account.js
  5. 121
      pages/clusters.js
  6. 2
      reset.js
  7. 96
      router.js
  8. 4
      stats/worker.js

@ -1,21 +0,0 @@
export default function ClusterRow({ i, cluster, setCluster, deleteCluster, csrf, user }) {
const splitCluster = cluster.split(',');
return (
<tr className='align-middle'>
<td className='text-left' style={{width:0}}>
<a className='btn btn-sm btn-danger' onClick={() => deleteCluster(csrf, key)}>
<i className='bi-trash-fill pe-none' width='16' height='16' />
</a>
</td>
<td className='col-1 text-center'>
<input onSubmit={() => setCluster(csrf, i)} className='btn btn-sm btn-primary' type='button' value='Select' disabled={(i === user.activeCluster ? 'disabled' : null)} />
</td>
<td>
({splitCluster.length}): {splitCluster.map(c => new URL(c).hostname).join(', ')}
</td>
</tr>
);
};

@ -20,21 +20,16 @@ const publicResolvers = [cloudflareResolver, googleResolver, quad9Resolver];
* account page data shared between html/json routes
*/
export async function accountData(req, res, _next) {
let maps = []
, globalAcl
, txtRecords = [];
if (res.locals.user.clusters.length > 0) {
maps = res.locals
.dataPlaneRetry('getAllRuntimeMapFiles')
.then(res => res.data)
.then(data => data.map(extractMap))
.then(maps => maps.filter(n => n))
.then(maps => maps.sort((a, b) => a.fname.localeCompare(b.fname)));
globalAcl = res.locals
.dataPlaneRetry('getOneRuntimeMap', 'ddos_global')
.then(res => res.data.description.split('').reverse()[0]);
txtRecords = basedflareNSResolver.resolve(process.env.NAMESERVER_TXT_DOMAIN, 'TXT');
}
let maps = res.locals
.dataPlaneRetry('getAllRuntimeMapFiles')
.then(res => res.data)
.then(data => data.map(extractMap))
.then(maps => maps.filter(n => n))
.then(maps => maps.sort((a, b) => a.fname.localeCompare(b.fname)));
let globalAcl = res.locals
.dataPlaneRetry('getOneRuntimeMap', 'ddos_global')
.then(res => res.data.description.split('').reverse()[0]);
let txtRecords = basedflareNSResolver.resolve(process.env.NAMESERVER_TXT_DOMAIN, 'TXT');
([maps, globalAcl, txtRecords] = await Promise.all([maps, globalAcl, txtRecords]));
return {
csrf: req.csrfToken(),
@ -207,8 +202,6 @@ export async function register(req, res) {
displayName: req.body.username,
passwordHash: passwordHash,
domains: [],
clusters: process.env.DEFAULT_CLUSTER ? [process.env.DEFAULT_CLUSTER] : [],
activeCluster: 0,
onboarding: false,
});

@ -1,96 +0,0 @@
import * as db from '../db.js';
import { validClustersString, makeArrayIfSingle, extractMap, dynamicResponse } from '../util.js';
export async function clustersPage(app, req, res, next) {
res.locals.data = {
user: res.locals.user,
csrf: req.csrfToken(),
};
return app.render(req, res, '/clusters');
};
export async function clustersJson(req, res, next) {
return res.json({
csrf: req.csrfToken(),
user: res.locals.user,
});
};
/**
* POST /cluster
* set active cluster
*/
export async function setCluster(req, res, next) {
if (res.locals.user.username !== 'admin') {
return dynamicResponse(req, res, 403, { error: 'Changing cluster is only supported on enterprise plans' });
}
if (req.body == null || req.body.cluster == null) {
return dynamicResponse(req, res, 404, { error: 'Invalid cluster' });
}
req.body.cluster = parseInt(req.body.cluster, 10) || 0;
if (!Number.isSafeInteger(req.body.cluster)
|| req.body.cluster > res.locals.user.clusters.length-1) {
return dynamicResponse(req, res, 404, { error: 'Invalid cluster' });
}
try {
await db.db().collection('accounts')
.updateOne({_id: res.locals.user.username}, {$set: {activeCluster: req.body.cluster }});
} catch (e) {
return next(e);
}
return dynamicResponse(req, res, 302, { redirect: '/account' });
};
/**
* POST /cluster/add
* add cluster
*/
export async function addCluster(req, res, next) {
if (res.locals.user.username !== 'admin') {
return dynamicResponse(req, res, 403, { error: 'Adding clusters is only supported on enterprise plans' });
}
if (!req.body || !req.body.cluster
|| typeof req.body.cluster !== 'string'
|| !validClustersString(req.body.cluster)) {
return dynamicResponse(req, res, 400, { error: 'Invalid cluster' });
}
try {
await db.db().collection('accounts')
.updateOne({_id: res.locals.user.username}, {$addToSet: {clusters: req.body.cluster }});
} catch (e) {
return next(e);
}
return dynamicResponse(req, res, 302, { redirect: '/clusters' });
};
/**
* POST /cluster/delete
* delete cluster
*/
export async function deleteClusters(req, res, next) {
if (res.locals.user.username !== 'admin') {
return dynamicResponse(req, res, 403, { error: 'Removing clusters is only supported on enterprise plans' });
}
//TODO: warning modal and extra "confirm" param before deleting cluster
const existingClusters = new Set(res.locals.user.clusters);
req.body.cluster = makeArrayIfSingle(req.body.cluster);
if (!req.body || !req.body.cluster
|| !req.body.cluster.some(c => existingClusters.has(c))) {
return dynamicResponse(req, res, 400, { error: 'Invalid cluster' });
}
const filteredClusters = res.locals.clusters.filter(c => !req.body.cluster.includes(c));
// if (filteredClusters.length === 0) {
// return dynamicResponse(req, res, 400, { error: 'Cannot delete last cluster' });
// }
let newActiveCluster = res.locals.user.activeCluster;
if (res.locals.user.activeCluster > filteredClusters.length-1) {
newActiveCluster = 0;
}
try {
await db.db().collection('accounts')
.updateOne({_id: res.locals.user.username}, {$set: {clusters: filteredClusters, activeCluster: newActiveCluster }});
} catch (e) {
return next(e);
}
return dynamicResponse(req, res, 302, { redirect: '/clusters' });
};

@ -28,18 +28,9 @@ export default function Account(props) {
const isAdmin = user.username === 'admin';
// Next cluster number for > browse button
const nextCluster = user.clusters[user.activeCluster+1] ? user.activeCluster+1 : 0;
// Links to each map and bubble/pill for map counts
const mapLinks = maps.map((map, i) => <MapLink key={i} map={map} />);
async function switchCluster(e) {
e.preventDefault();
await API.changeCluster({ _csrf: csrf, cluster: nextCluster }, dispatch, setError, router);
await API.getAccount(dispatch, setError, router);
}
async function toggleGlobal(e) {
e.preventDefault();
await API.globalToggle({ _csrf: csrf },dispatch, setError, router);
@ -77,46 +68,6 @@ export default function Account(props) {
</div>
</Link>
{/* Manage Clusters */}
{/*<div className="list-group-item list-group-item-action d-flex align-items-start flex-column">
<div className="flex-row d-flex w-100">
<div className="ms-2 me-auto">
<div className="fw-bold">
Manage Clusters
<span className="fw-normal">
{' '}- Add/Delete/Select cluster
</span>
</div>
</div>
<span className="ml-auto badge bg-info rounded-pill" style={{ maxHeight: "1.6em" }}>
Cluster: {user.activeCluster+1}/{user.clusters.length}
</span>
</div>
<div className="d-flex w-100 justify-content-between mt-2">
<div className="ms-2 overflow-hidden">
<div className="fw-bold overflow-hidden text-truncate">
Cluster ({user.clusters.length === 0 ? 0 : user.clusters[user.activeCluster].split(',').length} servers)
{user.clusters.length > 0 && (<span className="fw-normal">
: {user.clusters[user.activeCluster].split(',').map(x => {
const cUrl = new URL(x);
return cUrl.hostname;
}).join(', ')}
</span>)}
</div>
</div>
<span className="ml-auto d-flex flex-row">
<form onSubmit={switchCluster} action="/forms/cluster" method="post">
<input type="hidden" name="_csrf" value={csrf}/>
<input type="hidden" name="cluster" value={nextCluster}/>
<input className="btn btn-primary px-2 py-0" type="submit" value="&gt;" />
</form>
<Link href="/clusters" className="btn btn-success px-2 py-0 ms-2" style={{ maxHeight: "1.6em" }}>
+
</Link>
</span>
</div>
</div>*/}
{/* Domains */}
<Link href='/domains' className='list-group-item list-group-item-action d-flex align-items-start'>
<div className='ms-2 me-auto'>

@ -1,121 +0,0 @@
import React, { useState, useEffect } from 'react';
import Head from 'next/head';
import BackButton from '../components/BackButton.js';
import ErrorAlert from '../components/ErrorAlert.js';
import ClusterRow from '../components/ClusterRow.js';
import * as API from '../api.js';
import { useRouter } from 'next/router';
export default function Clusters(props) {
const router = useRouter();
const [state, dispatch] = useState(props);
const [error, setError] = useState();
useEffect(() => {
if (!state.user) {
API.getClusters(dispatch, setError, router);
}
}, [state.user, router]);
if (!state.user) {
return (
<div className='d-flex flex-column'>
{error && <ErrorAlert error={error} />}
<div className='text-center mb-4'>
<div className='spinner-border mt-5' role='status'>
<span className='visually-hidden'>Loading...</span>
</div>
</div>
</div>
);
}
const { user, csrf } = state;
if (user && !user.onboarding) {
router.push('/onboarding');
}
async function addCluster(e) {
e.preventDefault();
setError(null);
await API.addCluster({ _csrf: csrf, cluster: e.target.cluster.value }, dispatch, setError, router);
await API.getClusters(dispatch, setError, router);
e.target.reset();
}
async function deleteCluster(csrf, cluster) {
setError(null);
await API.deleteCluster({ _csrf: csrf, cluster }, dispatch, setError, router);
await API.getClusters(dispatch, setError, router);
}
async function setCluster(csrf, cluster) {
setError(null);
await API.changeCluster({ _csrf: csrf, cluster }, dispatch, setError, router);
await API.getClusters(dispatch, setError, router);
}
const clusterList = user.clusters.map((cluster, i) => (<ClusterRow
i={i}
key={cluster}
cluster={cluster}
csrf={csrf}
user={user}
setCluster={setCluster}
deleteCluster={deleteCluster}
/>));
return (
<>
<Head>
<title>Clusters</title>
</Head>
<h5 className='fw-bold'>
Clusters ({user.clusters.length}):
</h5>
{/* Clusters table */}
<div className='table-responsive round-shadow'>
<form className='d-flex' onSubmit={addCluster} action='/forms/cluster/add' method='post'>
<input type='hidden' name='_csrf' value={csrf} />
<table className='table text-nowrap'>
<tbody>
{clusterList}
{/* Add new cluster form */}
<tr className='align-middle'>
<td>
<button className='btn btn-sm btn-success' type='submit'>
<i className='bi-plus-lg pe-none' width='16' height='16' />
</button>
</td>
<td colSpan='2'>
<input className='form-control' type='text' name='cluster' placeholder='http://username:password@host:port, comma separated for multiple' required />
</td>
</tr>
</tbody>
</table>
</form>
</div>
{error && <span className='mx-1'>
<ErrorAlert error={error} />
</span>}
{/* back to account */}
<BackButton to='/account' />
</>
);
}
export async function getServerSideProps({ req, res, query, resolvedUrl, locale, locales, defaultLocale}) {
return { props: res.locals.data };
}

@ -17,8 +17,6 @@ async function reset() {
_id: 'admin',
passwordHash: passwordHash,
domains: ['localhost'],
clusters: [process.env.DEFAULT_CLUSTER],
activeCluster: 0,
balance: 0,
onboarding: null,
});

@ -14,7 +14,6 @@ import agent from './agent.js';
import * as accountController from './controllers/account.js';
import * as mapsController from './controllers/maps.js';
import * as clustersController from './controllers/clusters.js';
import * as certsController from './controllers/certs.js';
import * as dnsController from './controllers/dns.js';
import * as domainsController from './controllers/domains.js';
@ -47,23 +46,9 @@ export default function router(server, app) {
if (account) {
const numCerts = await db.db().collection('certs')
.countDocuments({ username: account._id });
const strippedClusters = account.clusters
.map((c) => {
return c.split(',')
.map((clusterString) => {
const clusterUrl = new URL(clusterString);
clusterUrl.username = '';
clusterUrl.password = '';
return clusterUrl.toString();
})
.join(',');
});
res.locals.clusters = account.clusters;
res.locals.user = {
username: account._id,
domains: account.domains,
clusters: strippedClusters,
activeCluster: account.activeCluster,
onboarding: account.onboarding,
numCerts,
};
@ -90,17 +75,13 @@ export default function router(server, app) {
const csrfMiddleware = csrf();
//dataplaneapi middleware
const clusterUrls = process.env.DEFAULT_CLUSTER.split(',').map(u => new URL(u));
//dataplaneapi middleware
const useHaproxy = (req, res, next) => {
if (res.locals.clusters.length === 0) {
return next();
}
try {
res.locals.fMap = server.locals.fMap;
res.locals.mapValueNames = server.locals.mapValueNames;
const clusterUrls = res.locals.clusters[res.locals.user.activeCluster]
.split(',')
.map((u) => new URL(u));
const firstClusterURL = clusterUrls[0];
//NOTE: all servers in cluster must have same credentials for now
@ -201,17 +182,7 @@ export default function router(server, app) {
}
};
const hasCluster = (req, res, next) => {
if (
res.locals.user.clusters.length > 0 ||
(req.baseUrl + req.path) === '/forms/cluster/add'
) {
return next();
}
return dynamicResponse(req, res, 302, { redirect: '/clusters' });
};
//unauthed pages
//unauthed pages
server.get('/', useSession, fetchSession, (req, res, next) => {
return app.render(req, res, '/index');
});
@ -318,7 +289,6 @@ export default function router(server, app) {
checkSession,
checkOnboarding,
useHaproxy,
hasCluster,
csrfMiddleware,
mapsController.mapPage.bind(null, app),
);
@ -328,27 +298,9 @@ export default function router(server, app) {
fetchSession,
checkSession,
useHaproxy,
hasCluster,
csrfMiddleware,
mapsController.mapJson,
);
server.get(
'/clusters',
useSession,
fetchSession,
checkSession,
checkOnboarding,
csrfMiddleware,
clustersController.clustersPage.bind(null, app),
);
server.get(
'/clusters.json',
useSession,
fetchSession,
checkSession,
csrfMiddleware,
clustersController.clustersJson,
);
server.get(
'/domains',
useSession,
@ -448,7 +400,6 @@ export default function router(server, app) {
fetchSession,
checkSession,
useHaproxy,
hasCluster,
csrfMiddleware,
accountController.globalToggle,
);
@ -458,7 +409,6 @@ export default function router(server, app) {
fetchSession,
checkSession,
useHaproxy,
hasCluster,
csrfMiddleware,
mapsController.patchMapForm,
);
@ -468,7 +418,6 @@ export default function router(server, app) {
fetchSession,
checkSession,
useHaproxy,
hasCluster,
csrfMiddleware,
mapsController.deleteMapForm,
);
@ -488,40 +437,12 @@ export default function router(server, app) {
csrfMiddleware,
dnsController.dnsRecordUpdate,
);
clusterRouter.post(
'/cluster',
useSession,
fetchSession,
checkSession,
hasCluster,
csrfMiddleware,
clustersController.setCluster,
);
clusterRouter.post(
'/cluster/add',
useSession,
fetchSession,
checkSession,
hasCluster,
csrfMiddleware,
clustersController.addCluster,
);
clusterRouter.post(
'/cluster/delete',
useSession,
fetchSession,
checkSession,
hasCluster,
csrfMiddleware,
clustersController.deleteClusters,
);
clusterRouter.post(
'/domain/add',
useSession,
fetchSession,
checkSession,
useHaproxy,
hasCluster,
csrfMiddleware,
domainsController.addDomain,
);
@ -531,7 +452,6 @@ export default function router(server, app) {
fetchSession,
checkSession,
useHaproxy,
hasCluster,
csrfMiddleware,
domainsController.deleteDomain,
);
@ -541,7 +461,6 @@ export default function router(server, app) {
fetchSession,
checkSession,
useHaproxy,
hasCluster,
csrfMiddleware,
certsController.addCert,
);
@ -551,7 +470,6 @@ export default function router(server, app) {
fetchSession,
checkSession,
useHaproxy,
hasCluster,
csrfMiddleware,
certsController.uploadCert,
);
@ -561,7 +479,6 @@ export default function router(server, app) {
fetchSession,
checkSession,
useHaproxy,
hasCluster,
csrfMiddleware,
certsController.deleteCert,
);
@ -570,7 +487,6 @@ export default function router(server, app) {
useSession,
fetchSession,
checkSession,
hasCluster,
csrfMiddleware,
certsController.verifyUserCSR,
);
@ -579,7 +495,6 @@ export default function router(server, app) {
useSession,
fetchSession,
checkSession,
hasCluster,
csrfMiddleware,
async (req, res, next) => {
if (res.locals.user.username !== 'admin') {
@ -600,7 +515,6 @@ export default function router(server, app) {
useSession,
fetchSession,
checkSession,
hasCluster,
csrfMiddleware,
async (req, res, next) => {
if (res.locals.user.username !== 'admin') {
@ -615,7 +529,6 @@ export default function router(server, app) {
useSession,
fetchSession,
checkSession,
hasCluster,
csrfMiddleware,
async (req, res, next) => {
if (res.locals.user.username !== 'admin') {
@ -639,7 +552,6 @@ export default function router(server, app) {
useSession,
fetchSession,
checkSession,
hasCluster,
csrfMiddleware,
(req, res, next) => {
return res.send(req.csrfToken());

@ -129,10 +129,10 @@ async function processHost(host) {
points = points.concat(statPoints);
});
});
console.time(`Flushed ${points.length} points to influx`);
console.time(`Flushed ${points.length} points for ${hostname} to influx`);
await writeApi.writePoints(points);
await writeApi.flush();
console.timeEnd(`Flushed ${points.length} points to influx`);
console.timeEnd(`Flushed ${points.length} points for ${hostname} to influx`);
} catch (e) {
if (e && e.cause && e.cause.code && e.cause.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
console.error('Error writing stats', new URL(host).hostname, e.cause.code);

Loading…
Cancel
Save