Add UI for allowlist

This commit is contained in:
Aurora Lahtela 2024-03-10 09:34:42 +02:00
parent bf16eb5c65
commit cd70f6d77a
11 changed files with 158 additions and 1 deletions

View File

@ -57,6 +57,7 @@ public enum DataID {
JOIN_ADDRESSES_BY_DAY,
PLAYER_RETENTION,
PLAYER_JOIN_ADDRESSES,
PLAYER_ALLOWLIST_BOUNCES,
;
public String of(ServerUUID serverUUID) {

View File

@ -108,7 +108,7 @@ public class AllowlistJSONResolver extends JSONResolver {
ServerUUID serverUUID = identifiers.getServerUUID(request);
Database database = dbSystem.getDatabase();
return jsonResolverService.resolve(timestamp, DataID.PLAYER_RETENTION, serverUUID,
return jsonResolverService.resolve(timestamp, DataID.PLAYER_ALLOWLIST_BOUNCES, serverUUID,
theUUID -> Map.of(
"allowlist_bounces", database.query(AllowlistQueries.getBounces(serverUUID)),
"last_seen_by_uuid", database.query(SessionQueries.getLastSeen(serverUUID))

View File

@ -285,6 +285,15 @@ public enum HtmlLang implements Lang {
LABEL_TABLE_SHOW_PER_PAGE("html.label.table.showPerPage", "Show per page"),
LABEL_EXPORT("html.label.export", "Export"),
LABEL_ALLOWLIST("html.label.allowlist", "Allowlist"),
LABEL_ALLOWLIST_BOUNCES("html.label.allowlistBounces", "Allowlist Bounces"),
LABEL_ATTEMPTS("html.label.attempts", "Attempts"),
LABEL_LAST_KNOWN_ATTEMPT("html.label.lastKnownAttempt", "Last Known Attempt"),
LABEL_PREVIOUS_ATTEMPT("html.label.lastBlocked", "Last Blocked"),
LABEL_LAST_ALLOWED_LOGIN("html.label.lastAllowed", "Last Allowed"),
LABEL_BLOCKED("html.label.blocked", "Blocked"),
LABEL_ALLOWED("html.label.allowed", "Allowed"),
LOGIN_LOGIN("html.login.login", "Login"),
LOGIN_LOGOUT("html.login.logout", "Logout"),
LOGIN_USERNAME("html.login.username", "Username"),

View File

@ -63,6 +63,7 @@ public class AllowlistBounceTable {
.column(USER_NAME, Sql.varchar(36)).notNull()
.column(SERVER_ID, Sql.INT).notNull()
.column(TIMES, Sql.INT).notNull().defaultValue("0")
.column(LAST_BOUNCE, Sql.LONG).notNull()
.foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID)
.toString();
}

View File

@ -136,6 +136,7 @@ class AccessControlVisibilityTest {
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYER_VERSUS_OVERVIEW, "pvp-pve-as-numbers", "pvppve"),
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYER_VERSUS_OVERVIEW, "pvp-pve-insights", "pvppve"),
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYER_VERSUS_KILL_LIST, "pvp-kills-table", "pvppve"),
Arguments.arguments(WebPermission.PAGE_SERVER_ALLOWLIST_BOUNCE, "allowlist-bounce-table", "allowlist"),
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_OVERVIEW, "playerbase-trends", "playerbase"),
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_OVERVIEW, "playerbase-insights", "playerbase"),
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS, "playerbase-graph", "playerbase"),

View File

@ -32,6 +32,7 @@ const ServerOverview = React.lazy(() => import("./views/server/ServerOverview"))
const OnlineActivity = React.lazy(() => import("./views/server/OnlineActivity"));
const ServerSessions = React.lazy(() => import("./views/server/ServerSessions"));
const ServerPvpPve = React.lazy(() => import("./views/server/ServerPvpPve"));
const ServerAllowList = React.lazy(() => import("./views/server/ServerAllowList"));
const PlayerbaseOverview = React.lazy(() => import("./views/server/PlayerbaseOverview"));
const ServerPlayers = React.lazy(() => import("./views/server/ServerPlayers"));
const ServerGeolocations = React.lazy(() => import("./views/server/ServerGeolocations"));
@ -159,6 +160,7 @@ function App() {
<Route path="online-activity" element={<Lazy><OnlineActivity/></Lazy>}/>
<Route path="sessions" element={<Lazy><ServerSessions/></Lazy>}/>
<Route path="pvppve" element={<Lazy><ServerPvpPve/></Lazy>}/>
<Route path="allowlist" element={<Lazy><ServerAllowList/></Lazy>}/>
<Route path="playerbase" element={<Lazy><PlayerbaseOverview/></Lazy>}/>
<Route path="join-addresses" element={<Lazy><ServerJoinAddresses/></Lazy>}/>
<Route path="retention" element={<Lazy><ServerPlayerRetention/></Lazy>}/>

View File

@ -0,0 +1,22 @@
import React from "react";
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
import {faFilterCircleXmark} from "@fortawesome/free-solid-svg-icons";
import {Card} from "react-bootstrap";
import AllowlistBounceTable from "../../../table/AllowlistBounceTable.jsx";
import {useTranslation} from "react-i18next";
const AllowlistBounceTableCard = ({bounces, lastSeen}) => {
const {t} = useTranslation();
return (
<Card id={'allowlist-table'}>
<Card.Header>
<h6 className="col-black">
<Fa icon={faFilterCircleXmark} className="col-orange"/> {t('html.label.allowlistBounces')}
</h6>
</Card.Header>
<AllowlistBounceTable bounces={bounces} lastSeen={lastSeen}/>
</Card>
)
};
export default AllowlistBounceTableCard;

View File

@ -0,0 +1,72 @@
import React, {useCallback} from "react";
import {useTranslation} from "react-i18next";
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
import {faDoorOpen, faRepeat, faUser} from "@fortawesome/free-solid-svg-icons";
import {usePreferences} from "../../hooks/preferencesHook.jsx";
import DataTablesTable from "./DataTablesTable.jsx";
import {formatDate, useDatePreferences} from "../text/FormattedDate.jsx";
import {faCalendarCheck, faCalendarTimes} from "@fortawesome/free-regular-svg-icons";
import {Link} from "react-router-dom";
const AllowlistBounceTable = ({bounces, lastSeen}) => {
const {t} = useTranslation();
const {preferencesLoaded} = usePreferences();
const datePreferences = useDatePreferences();
const formatDateEasy = date => {
return formatDate(date, datePreferences.offset, datePreferences.pattern, false, datePreferences.recentDaysPattern, t);
}
const columns = [{
title: <><Fa icon={faUser}/> {t('html.label.player')}</>,
data: {_: "player", display: "link"}
}, {
title: <><Fa icon={faRepeat}/> {t('html.label.attempts')}</>,
data: "attempts"
}, {
title: <><Fa icon={faDoorOpen}/> {t('html.label.lastKnownAttempt')}</>,
data: "lastKnownAttempt"
}, {
title: <><Fa icon={faCalendarTimes}/> {t('html.label.lastBlocked')}</>,
data: {_: "date", display: "dateFormatted"}
}, {
title: <><Fa icon={faCalendarCheck}/> {t('html.label.lastAllowed')}</>,
data: {_: "lastSeen", display: "lastSeenFormatted"}
}];
const rows = bounces.map(bounce => {
const seenAfterBounce = bounce.lastBounce < lastSeen[bounce.playerUUID];
const playerId = bounce.playerName + ' / ' + bounce.playerUUID;
return {
player: playerId,
link: lastSeen[bounce.playerUUID] ? <Link to={"/player/" + bounce.playerUUID}>{playerId}</Link> : playerId,
date: bounce.lastTime,
dateFormatted: formatDateEasy(bounce.lastTime),
attempts: bounce.count,
lastKnownAttempt: seenAfterBounce ? t('html.label.allowed') : t('html.label.blocked'),
lastSeen: lastSeen[bounce.playerUUID],
lastSeenFormatted: formatDateEasy(lastSeen[bounce.playerUUID])
};
});
const options = {
responsive: true,
deferRender: true,
columns: columns,
data: rows,
paginationCount: 2,
order: [[1, "desc"]]
}
if (!preferencesLoaded) return <></>;
const rowKeyFunction = useCallback((row, column) => {
return row.player + "-" + (column ? JSON.stringify(column.data) : '');
}, []);
return (
<DataTablesTable id={"allowlist-bounce-table"} options={options} colorClass={"bg-orange"}
rowKeyFunction={rowKeyFunction}/>
)
};
export default AllowlistBounceTable;

View File

@ -100,6 +100,7 @@ export const fetchPlayersTable = async (timestamp, identifier) => {
return await fetchPlayersTableNetwork(timestamp);
}
}
const fetchPlayersTableServer = async (timestamp, identifier) => {
let url = `/v1/playersTable?server=${identifier}`;
if (staticSite) url = `/data/playersTable-${identifier}.json`;
@ -112,6 +113,12 @@ const fetchPlayersTableNetwork = async (timestamp) => {
return doGetRequest(url, timestamp);
}
export const fetchAllowlistBounces = async (timestamp, identifier) => {
let url = `/v1/gameAllowlistBounces?server=${identifier}`;
if (staticSite) url = `/data/gameAllowlistBounces-${identifier}.json`;
return doGetRequest(url, timestamp);
}
export const fetchPingTable = async (timestamp, identifier) => {
let url = `/v1/pingTable?server=${identifier}`;
if (staticSite) url = `/data/pingTable-${identifier}.json`;

View File

@ -9,6 +9,7 @@ import {
faCodeCompare,
faCogs,
faCubes,
faFilterCircleXmark,
faGlobe,
faInfoCircle,
faLocationArrow,
@ -72,6 +73,12 @@ const ServerSidebar = () => {
icon: faCampground,
href: "pvppve",
permission: 'page.server.player.versus'
},
{
name: 'html.label.allowlist',
icon: faFilterCircleXmark,
href: "allowlist",
permission: 'page.server.allowlist.bounce'
}
],
},

View File

@ -0,0 +1,35 @@
import React from 'react';
import {useDataRequest} from "../../hooks/dataFetchHook";
import {useParams} from "react-router-dom";
import {fetchAllowlistBounces} from "../../service/serverService";
import ErrorView from "../ErrorView";
import {Col} from "react-bootstrap";
import LoadIn from "../../components/animation/LoadIn";
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
import {useAuth} from "../../hooks/authenticationHook";
import AllowlistBounceTableCard from "../../components/cards/server/tables/AllowlistBounceTableCard.jsx";
const ServerAllowList = () => {
const {hasPermission} = useAuth();
const {identifier} = useParams();
const seeBounce = hasPermission('page.server.allowlist.bounce');
const {data, loadingError} = useDataRequest(fetchAllowlistBounces, [identifier], seeBounce);
if (loadingError) return <ErrorView error={loadingError}/>
return (
<LoadIn>
<section className="server-allowlist">
{seeBounce && <ExtendableRow id={'row-server-allowlist-0'}>
<Col md={12}>
<AllowlistBounceTableCard bounces={data?.allowlist_bounces || []}
lastSeen={data?.last_seen_by_uuid || {}}/>
</Col>
</ExtendableRow>}
</section>
</LoadIn>
)
};
export default ServerAllowList