Next.js+React web interface for controlling HAProxy clusters (groups of servers), in conjunction with with https://gitgud.io/fatchan/haproxy-protection.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
4.8 KiB
191 lines
4.8 KiB
import { useRouter } from "next/router";
|
|
import React, { useState } from 'react';
|
|
import Head from 'next/head';
|
|
import Link from 'next/link';
|
|
import MapRow from '../../components/MapRow.js';
|
|
import BackButton from '../../components/BackButton.js';
|
|
import ErrorAlert from '../../components/ErrorAlert.js';
|
|
import ApiCall from '../../api.js';
|
|
|
|
const MapPage = (props) => {
|
|
|
|
const router = useRouter();
|
|
|
|
const { name: mapName } = router.query;
|
|
|
|
const [mapData, setMapData] = useState(props);
|
|
const [error, setError] = useState();
|
|
|
|
React.useEffect(() => {
|
|
if (!mapData.user) {
|
|
ApiCall(`/map/${mapName}.json`, 'GET', null, setMapData, setError, null, router);
|
|
}
|
|
}, [mapData.user, mapName, router]);
|
|
|
|
if (!mapData.user) {
|
|
return (
|
|
<>
|
|
Loading...
|
|
{error && <ErrorAlert error={error} />}
|
|
</>
|
|
);
|
|
}
|
|
|
|
const { user, mapValueNames, mapId, map, csrf, name, showValues } = mapData;
|
|
|
|
async function addToMap(e) {
|
|
e.preventDefault();
|
|
await ApiCall(`/forms/map/${mapId.name}/add`, 'POST', JSON.stringify({ _csrf: csrf, key: e.target.key.value, value: e.target.value?.value }), null, setError, 0.5, router);
|
|
await ApiCall(`/map/${mapId.name}.json`, 'GET', null, setMapData, setError, null, router);
|
|
e.target.reset();
|
|
}
|
|
|
|
async function deleteFromMap(e) {
|
|
e.preventDefault();
|
|
await ApiCall(`/forms/map/${mapId.name}/delete`, 'POST', JSON.stringify({ _csrf: csrf, key: e.target.key.value }), null, setError, 0.5, router);
|
|
await ApiCall(`/map/${mapId.name}.json`, 'GET', null, setMapData, setError, null, router);
|
|
}
|
|
|
|
const mapRows = map.map((row, i) => {
|
|
//TODO: address prop drilling
|
|
return (
|
|
<MapRow
|
|
key={i}
|
|
row={row}
|
|
name={mapId.name}
|
|
csrf={csrf}
|
|
showValues={showValues}
|
|
mapValueNames={mapValueNames}
|
|
onDeleteSubmit={deleteFromMap}
|
|
/>
|
|
)
|
|
});
|
|
|
|
|
|
let formElements;
|
|
//TODO: env var case map names
|
|
switch (mapId.name) {
|
|
case "ddos": {
|
|
const mapValueOptions = Object.entries(mapValueNames)
|
|
.map((entry, i) => (<option key={'option'+i} value={entry[0]}>{entry[1]}</option>))
|
|
formElements = (
|
|
<>
|
|
<input type="hidden" name="_csrf" value={csrf} />
|
|
<input className="btn btn-success" type="submit" value="+" />
|
|
<input className="form-control mx-3" type="text" name="key" placeholder="domain/path" required />
|
|
<select className="form-select mx-3" name="value" required>
|
|
<option selected />
|
|
{mapValueOptions}
|
|
</select>
|
|
</>
|
|
);
|
|
break;
|
|
}
|
|
case "hosts":
|
|
case "maintenance": {
|
|
const activeDomains = map.map(e => e.split(' ')[1]);
|
|
const inactiveDomains = user.domains.filter(d => !activeDomains.includes(d));
|
|
const domainSelectOptions = inactiveDomains.map((d, i) => (<option key={'option'+i} value={d}>{d}</option>));
|
|
formElements = (
|
|
<>
|
|
<input type="hidden" name="_csrf" value={csrf} />
|
|
<input className="btn btn-success" type="submit" value="+" />
|
|
<select className="form-select mx-3" name="key" required>
|
|
<option selected />
|
|
{domainSelectOptions}
|
|
</select>
|
|
{
|
|
(process.env.NEXT_PUBLIC_CUSTOM_BACKENDS_ENABLED && mapId.name === "hosts") &&
|
|
<input
|
|
className="form-control ml-2"
|
|
type="text"
|
|
name="value"
|
|
placeholder="backend ip:port"
|
|
required
|
|
/>
|
|
}
|
|
</>
|
|
);
|
|
break;
|
|
}
|
|
case "blocked":
|
|
case "whitelist":
|
|
formElements = (
|
|
<>
|
|
<input type="hidden" name="_csrf" value={csrf} />
|
|
<input className="btn btn-success" type="submit" value="+" />
|
|
<input className="form-control mx-3" type="text" name="key" placeholder="ip or subnet" required />
|
|
</>
|
|
);
|
|
break;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
|
|
<Head>
|
|
<title>
|
|
{mapId.fname}
|
|
</title>
|
|
</Head>
|
|
|
|
{error && <ErrorAlert error={error} />}
|
|
|
|
{/* Map friendly name (same as shown on acc page) */}
|
|
<h5 className="fw-bold">
|
|
{mapId.fname}:
|
|
</h5>
|
|
|
|
{/* Map table */}
|
|
<div className="table-responsive">
|
|
<table className="table table-bordered text-nowrap">
|
|
<tbody>
|
|
|
|
{/* header row */}
|
|
{mapRows.length > 0 && (
|
|
<tr>
|
|
<th />
|
|
<th>
|
|
{mapId.columnNames[0]}
|
|
</th>
|
|
{showValues === true && (
|
|
<th>
|
|
{mapId.columnNames[1]}
|
|
</th>
|
|
)}
|
|
</tr>
|
|
)}
|
|
|
|
{mapRows}
|
|
|
|
{/* Add new row form */}
|
|
<tr className="align-middle">
|
|
<td className="col-1 text-center" colSpan="3">
|
|
<form onSubmit={addToMap} className="d-flex" action={`/forms/map/${mapId.name}/add`} method="post">
|
|
{formElements}
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* back to account */}
|
|
<BackButton to="/account" />
|
|
|
|
</>
|
|
);
|
|
|
|
};
|
|
|
|
export async function getServerSideProps({ req, res, query, resolvedUrl, locale, locales, defaultLocale}) {
|
|
return {
|
|
props: {
|
|
user: res.locals.user || null,
|
|
...query
|
|
}
|
|
};
|
|
}
|
|
|
|
export default MapPage;
|
|
|