Fix server page server name

Server name came from metadata endpoint, but it is incorrect if viewing a server page on a network

Added a new endpoint /v1/serverIdentity?server=... to get the identifiable name of the server.
This commit is contained in:
Aurora Lahtela 2022-08-20 09:46:24 +03:00
parent eaae1456d6
commit d39c377756
11 changed files with 146 additions and 16 deletions

View File

@ -175,6 +175,12 @@ public class JSONFactory {
return new PlayerKillMutator(kills).toJSONAsMap(formatters);
}
public Optional<ServerDto> serverForIdentifier(String identifier) {
return dbSystem.getDatabase()
.query(ServerQueries.fetchServerMatchingIdentifier(identifier))
.map(ServerDto::fromServer);
}
public Map<String, Object> serversAsJSONMaps() {
Database db = dbSystem.getDatabase();
long now = System.currentTimeMillis();

View File

@ -62,7 +62,8 @@ public class RootJSONResolver {
QueryJSONResolver queryJSONResolver,
VersionJSONResolver versionJSONResolver,
MetadataJSONResolver metadataJSONResolver,
WhoAmIJSONResolver whoAmIJSONResolver
WhoAmIJSONResolver whoAmIJSONResolver,
ServerIdentityJSONResolver serverIdentityJSONResolver
) {
this.identifiers = identifiers;
this.asyncJSONResolverService = asyncJSONResolverService;
@ -87,6 +88,7 @@ public class RootJSONResolver {
.add("version", versionJSONResolver)
.add("locale", localeJSONResolver)
.add("metadata", metadataJSONResolver)
.add("serverIdentity", serverIdentityJSONResolver)
.add("whoami", whoAmIJSONResolver)
.build();
}

View File

@ -0,0 +1,86 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ws.rs.GET;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
/**
* @author AuroraLS3
*/
@Singleton
public class ServerIdentityJSONResolver implements Resolver {
private final JSONFactory jsonFactory;
@Inject
public ServerIdentityJSONResolver(JSONFactory jsonFactory) {
this.jsonFactory = jsonFactory;
}
@Override
public boolean canAccess(Request request) {
return request.getUser()
.map(user -> user.hasPermission("page.server"))
.orElse(false);
}
@GET
@Operation(
description = "Get server identity for an identifier",
responses = {
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, schema = @Schema(implementation = ServerDto.class))),
@ApiResponse(responseCode = "400", description = "If 'server' parameter is not given"),
@ApiResponse(responseCode = "404", description = "If 'server' parameter is not an existing server")
},
parameters = @Parameter(in = ParameterIn.QUERY, required = true, name = "server", description = "Server identifier to get data for", examples = {
@ExampleObject("Server 1"),
@ExampleObject("1"),
@ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"),
}),
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
)
@Override
public Optional<Response> resolve(Request request) {
String serverIdentifier = request.getQuery().get("server")
.orElseThrow(() -> new BadRequestException("Missing 'server' query parameter"));
ServerDto server = jsonFactory.serverForIdentifier(serverIdentifier)
.orElseThrow(() -> new NotFoundException("Server with identifier '" + serverIdentifier + "' was not found in the database"));
return Optional.of(Response.builder()
.setJSONContent(server)
.build());
}
}

View File

@ -32,6 +32,7 @@ public enum ErrorPageLang implements Lang {
FORBIDDEN_403("html.error.403Forbidden", "Forbidden"),
ACCESS_DENIED_403("403AccessDenied", "Access Denied"),
NOT_FOUND_404("html.error.404NotFound", "Not Found"),
NO_SUCH_SERVER_404("html.error.serverNotSeen", "Server doesn't exist"),
PAGE_NOT_FOUND_404("html.error.404PageNotFound", "Page does not exist.");
private final String key;

View File

@ -55,19 +55,19 @@ const MainPageRedirect = () => {
}
if (authRequired && !loggedIn) {
return (<Navigate to="login" replace={true}/>)
return (<Navigate to="/login" replace={true}/>)
} else if (authRequired && loggedIn) {
if (isProxy && user.permissions.includes('page.network')) {
return (<Navigate to={"network/overview"} replace={true}/>)
return (<Navigate to={"/network/overview"} replace={true}/>)
} else if (user.permissions.includes('page.server')) {
return (<Navigate to={"server/" + encodeURIComponent(serverName) + "/overview"} replace={true}/>)
return (<Navigate to={"/server/" + encodeURIComponent(serverName) + "/overview"} replace={true}/>)
} else if (user.permissions.includes('page.player.other')) {
return (<Navigate to={"players"} replace={true}/>)
return (<Navigate to={"/players"} replace={true}/>)
} else if (user.permissions.includes('page.player.self')) {
return (<Navigate to={"player/" + user.linkedToUuid} replace={true}/>)
return (<Navigate to={"/player/" + user.linkedToUuid} replace={true}/>)
}
} else {
return (<Navigate to={isProxy ? "network/overview" : "server/" + encodeURIComponent(serverName) + "/overview"}
return (<Navigate to={isProxy ? "/network/overview" : "/server/" + encodeURIComponent(serverName) + "/overview"}
replace={true}/>)
}
}

View File

@ -217,8 +217,6 @@ const Sidebar = ({items, showBackButton}) => {
const collapseSidebar = () => setSidebarExpanded(windowWidth > 1350);
useEffect(collapseSidebar, [windowWidth, currentTab, setSidebarExpanded]);
if (!items.length) return <></>
return (
<>
{sidebarExpanded &&
@ -227,9 +225,9 @@ const Sidebar = ({items, showBackButton}) => {
<Divider/>
{showBackButton && <>
<Item active={false} href="/" icon={faArrowLeft} name={t('html.label.toMainPage')}/>
<Divider showMargin={!items[0].contents && items[0].href === undefined}/>
<Divider showMargin={items.length && !items[0].contents && items[0].href === undefined}/>
</>}
{items.map((item, i) => renderItem(item, i, openCollapse, toggleCollapse, t))}
{items.length ? items.map((item, i) => renderItem(item, i, openCollapse, toggleCollapse, t)) : ''}
<Divider/>
<FooterButtons/>
</ul>}

View File

@ -13,6 +13,7 @@ export const useDataRequest = (fetchMethod, parameters) => {
setData(json);
finishUpdate(json.timestamp, json.timestamp_f);
} else if (error) {
console.warn(error);
setLoadingError(error);
}
});

View File

@ -26,6 +26,7 @@ export const doSomeRequest = async (url, statusOptions, axiosFunction) => {
for (const statusOption of statusOptions) {
if (response.status === statusOption.status) {
return {
status: response.status,
data: statusOption.get(response),
error: undefined
};
@ -37,6 +38,7 @@ export const doSomeRequest = async (url, statusOptions, axiosFunction) => {
for (const statusOption of statusOptions) {
if (e.response.status === statusOption.status) {
return {
status: e.response.status,
data: undefined,
error: statusOption.get(response, e)
};
@ -45,6 +47,7 @@ export const doSomeRequest = async (url, statusOptions, axiosFunction) => {
return {
data: undefined,
error: {
status: e.response.status,
message: e.message,
url,
data: e.response.data
@ -54,6 +57,7 @@ export const doSomeRequest = async (url, statusOptions, axiosFunction) => {
return {
data: undefined,
error: {
status: undefined,
message: e.message,
url
}

View File

@ -1,5 +1,11 @@
import {doGetRequest} from "./backendConfiguration";
export const fetchServerIdentity = async (identifier) => {
const url = `/v1/serverIdentity?server=${identifier}`;
return doGetRequest(url);
}
export const fetchServerOverview = async (identifier) => {
const timestamp = Date.now();
const url = `/v1/serverOverview?server=${identifier}&timestamp=${timestamp}`;

View File

@ -9,7 +9,7 @@ export const ErrorViewText = ({error}) => {
<>
<p>{error.message} {error.url && <a href={error.url}>{error.url}</a>}</p>
{error.data && <><br/>
<pre>{error.data}</pre>
<pre>{JSON.stringify(error.data)}</pre>
</>}
</>
)

View File

@ -1,6 +1,6 @@
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {Outlet} from "react-router-dom";
import {Outlet, useParams} from "react-router-dom";
import {useNavigation} from "../../hooks/navigationHook";
import {
faCampground,
@ -23,11 +23,17 @@ import {useMetadata} from "../../hooks/metadataHook";
import {faCalendarCheck} from "@fortawesome/free-regular-svg-icons";
import ErrorPage from "./ErrorPage";
import {SwitchTransition} from "react-transition-group";
import MainPageRedirect from "../../components/navigation/MainPageRedirect";
import {useDataRequest} from "../../hooks/dataFetchHook";
import {fetchServerIdentity} from "../../service/serverService";
const ServerPage = () => {
const {t, i18n} = useTranslation();
const {identifier} = useParams();
const {isProxy, serverName} = useMetadata();
const {data: serverIdentity, loadingError: identityLoadingError} = useDataRequest(fetchServerIdentity, [identifier])
const [error] = useState(undefined);
const [sidebarItems, setSidebarItems] = useState([]);
@ -82,12 +88,32 @@ const ServerPage = () => {
window.document.title = `Plan | Server Analysis`;
}, [t, i18n])
const {authRequired, user} = useAuth();
const {authRequired, loggedIn, user} = useAuth();
if (authRequired && !loggedIn) return <MainPageRedirect/>
const showBackButton = isProxy && (!authRequired || user.permissions.filter(perm => perm !== 'page.network').length);
if (error) return <ErrorPage error={error}/>;
const displayedServerName = !isProxy && serverName && serverName.startsWith('Server') ? "Plan" : serverName;
const getDisplayedServerName = () => {
if (serverIdentity) {
return serverIdentity.serverName;
}
if (isProxy) {
return identifier;
} else {
return serverName && serverName.startsWith('Server') ? "Plan" : serverName
}
}
const displayedServerName = getDisplayedServerName();
if (error) return <ErrorPage error={error}/>;
if (identityLoadingError) {
if (identityLoadingError.status === 404) return <ErrorPage
error={{title: t('html.error.404NotFound'), message: t('html.error.serverNotSeen')}}/>
return <ErrorPage error={identityLoadingError}/>
}
return (
<>
<NightModeCss/>