mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-03-02 11:11:21 +01:00
Initial version of server extension cards
TODO: Extension DataTables mapping
This commit is contained in:
parent
ce81179388
commit
ea16adb13d
@ -23,6 +23,7 @@ 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 com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
||||
@ -66,7 +67,8 @@ public class ExtensionJSONResolver implements Resolver {
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return false;
|
||||
WebUser permissions = request.getUser().orElse(new WebUser(""));
|
||||
return permissions.hasPermission("page.server") || permissions.hasPermission("page.network");
|
||||
}
|
||||
|
||||
@GET
|
||||
|
@ -30,6 +30,8 @@ import AllPlayers from "./views/players/AllPlayers";
|
||||
import ServerGeolocations from "./views/server/ServerGeolocations";
|
||||
import LoginPage from "./views/layout/LoginPage";
|
||||
import ServerPerformance from "./views/server/ServerPerformance";
|
||||
import ServerPluginData from "./views/server/ServerPluginData";
|
||||
import ServerWidePluginData from "./views/server/ServerWidePluginData";
|
||||
|
||||
const SwaggerView = React.lazy(() => import("./views/SwaggerView"));
|
||||
|
||||
@ -89,7 +91,8 @@ function App() {
|
||||
<Route path="players" element={<ServerPlayers/>}/>
|
||||
<Route path="geolocations" element={<ServerGeolocations/>}/>
|
||||
<Route path="performance" element={<ServerPerformance/>}/>
|
||||
<Route path="plugins-overview" element={<></>}/>
|
||||
<Route path="plugins-overview" element={<ServerPluginData/>}/>
|
||||
<Route path="plugins/:plugin" element={<ServerWidePluginData/>}/>
|
||||
<Route path="*" element={<ErrorView error={{
|
||||
message: 'Unknown tab address, please correct the address',
|
||||
title: 'No such tab',
|
||||
|
@ -4,6 +4,7 @@ import ExtensionIcon from "./ExtensionIcon";
|
||||
import Datapoint from "../Datapoint";
|
||||
import Masonry from 'masonry-layout'
|
||||
import {useTheme} from "../../hooks/themeHook";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
export const ExtensionCardWrapper = ({extension, children}) => {
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
@ -51,17 +52,29 @@ const ExtensionValue = ({data}) => {
|
||||
/>);
|
||||
}
|
||||
|
||||
const ExtensionValues = ({tab}) => (
|
||||
<Card.Body>
|
||||
{tab.values.map((data, i) => {
|
||||
return (<ExtensionValue key={i} data={data}/>);
|
||||
}
|
||||
)}
|
||||
</Card.Body>
|
||||
)
|
||||
const ExtensionValues = ({tab}) => {
|
||||
return (
|
||||
<>
|
||||
{Boolean(tab.values.length) && <Card.Body>
|
||||
{tab.values.map((data, i) => {
|
||||
return (<ExtensionValue key={i} data={data}/>);
|
||||
}
|
||||
)}
|
||||
</Card.Body>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ExtensionTable = ({table}) => {
|
||||
const {nightModeEnabled} = useTheme();
|
||||
const {t} = useTranslation();
|
||||
|
||||
const columns = table.table.rows.length ? table.table.rows.map((row, i) => <tr key={i}>{row.map((value, j) => <td
|
||||
key={i + '' + j}>{value}</td>)}</tr>) :
|
||||
<tr>{table.table.columns.map((column, i) =>
|
||||
<td key={i}>{i === 0 ? t('generic.noData') : '-'}</td>)}
|
||||
</tr>
|
||||
|
||||
return (
|
||||
<table className={"table table-striped" + (nightModeEnabled ? " table-dark" : '')}>
|
||||
<thead className={table.tableColorClass}>
|
||||
@ -72,11 +85,10 @@ const ExtensionTable = ({table}) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.table.rows.map((row, i) => <tr key={i}>{row.map((value, j) => <td
|
||||
key={i + '' + j}>{value}</td>)}</tr>)}
|
||||
{columns}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
const ExtensionTables = ({tab}) => {
|
||||
@ -113,7 +125,7 @@ const ExtensionCard = ({extension}) => {
|
||||
{extension.onlyGenericTab ? '' :
|
||||
extension.tabs.map((tab, i) => <li key={i} role="presentation" className="nav-item col-black">
|
||||
<button className={"nav-link col-black"
|
||||
+ (openTabIndex === i ? ' active' : '')} onClick={() => toggleTabIndex(i)}>
|
||||
+ (openTabIndex === i ? ' active' : '')} onClick={() => toggleTabIndex(i)}>
|
||||
<ExtensionIcon icon={tab.tabInformation.icon}/> {tab.tabInformation.tabName}
|
||||
</button>
|
||||
</li>)
|
||||
|
@ -42,6 +42,12 @@ export const fetchPerformanceOverview = async (identifier) => {
|
||||
return doGetRequest(url);
|
||||
}
|
||||
|
||||
export const fetchExtensionData = async (identifier) => {
|
||||
const timestamp = Date.now();
|
||||
const url = `/v1/extensionData?server=${identifier}×tamp=${timestamp}`;
|
||||
return doGetRequest(url);
|
||||
}
|
||||
|
||||
export const fetchSessions = async (identifier) => {
|
||||
const timestamp = Date.now();
|
||||
const url = `/v1/sessions?server=${identifier}×tamp=${timestamp}`;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Outlet, useParams} from "react-router-dom";
|
||||
import {Outlet, useOutletContext, useParams} from "react-router-dom";
|
||||
import {useNavigation} from "../../hooks/navigationHook";
|
||||
import {
|
||||
faCampground,
|
||||
@ -25,14 +25,22 @@ 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";
|
||||
import {fetchExtensionData, fetchServerIdentity} from "../../service/serverService";
|
||||
import ExtensionIcon from "../../components/extensions/ExtensionIcon";
|
||||
|
||||
const ServerPage = () => {
|
||||
const {t, i18n} = useTranslation();
|
||||
const {identifier} = useParams();
|
||||
const {isProxy, serverName} = useMetadata();
|
||||
|
||||
const {data: serverIdentity, loadingError: identityLoadingError} = useDataRequest(fetchServerIdentity, [identifier])
|
||||
const {
|
||||
data: serverIdentity,
|
||||
loadingError: identityLoadingError
|
||||
} = useDataRequest(fetchServerIdentity, [identifier]);
|
||||
const {
|
||||
data: extensionData,
|
||||
loadingError: extensionDataLoadingError
|
||||
} = useDataRequest(fetchExtensionData, [identifier]);
|
||||
|
||||
const [error] = useState(undefined);
|
||||
const [sidebarItems, setSidebarItems] = useState([]);
|
||||
@ -76,17 +84,30 @@ const ServerPage = () => {
|
||||
{name: 'html.label.performance', icon: faCogs, href: "performance"},
|
||||
{},
|
||||
{name: 'html.label.plugins'},
|
||||
{name: 'html.label.pluginsOverview', icon: faCubes, href: "plugins-overview"},
|
||||
{},
|
||||
{name: 'html.label.links'},
|
||||
{name: 'html.label.query', icon: faSearch, href: "/query"},
|
||||
]
|
||||
|
||||
// TODO Extensions
|
||||
if (extensionData) {
|
||||
items.push({name: 'html.label.pluginsOverview', icon: faCubes, href: "plugins-overview"})
|
||||
extensionData.extensions.filter(extension => extension.wide)
|
||||
.map(extension => extension.extensionInformation)
|
||||
.map(info => {
|
||||
return {
|
||||
name: info.pluginName,
|
||||
icon: <ExtensionIcon icon={info.icon}/>,
|
||||
href: `plugins/${encodeURIComponent(info.pluginName)}`
|
||||
}
|
||||
}).forEach(item => items.push(item))
|
||||
}
|
||||
|
||||
items.push(
|
||||
{},
|
||||
{name: 'html.label.links'},
|
||||
{name: 'html.label.query', icon: faSearch, href: "/query"}
|
||||
);
|
||||
|
||||
setSidebarItems(items);
|
||||
window.document.title = `Plan | Server Analysis`;
|
||||
}, [t, i18n])
|
||||
}, [t, i18n, extensionData])
|
||||
|
||||
const {authRequired, loggedIn, user} = useAuth();
|
||||
if (authRequired && !loggedIn) return <MainPageRedirect/>
|
||||
@ -113,6 +134,10 @@ const ServerPage = () => {
|
||||
error={{title: t('html.error.404NotFound'), message: t('html.error.serverNotSeen')}}/>
|
||||
return <ErrorPage error={identityLoadingError}/>
|
||||
}
|
||||
if (extensionDataLoadingError) {
|
||||
return <ErrorPage error={extensionDataLoadingError}/>
|
||||
}
|
||||
if (!extensionData) return <></>
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -123,7 +148,7 @@ const ServerPage = () => {
|
||||
<div id="content" style={{display: 'flex'}}>
|
||||
<main className="container-fluid mt-4">
|
||||
<SwitchTransition>
|
||||
<Outlet context={{}}/>
|
||||
<Outlet context={extensionData}/>
|
||||
</SwitchTransition>
|
||||
</main>
|
||||
<aside>
|
||||
@ -135,4 +160,8 @@ const ServerPage = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export const useServer = () => {
|
||||
return useOutletContext();
|
||||
}
|
||||
|
||||
export default ServerPage;
|
@ -1,6 +1,6 @@
|
||||
import React, {useEffect} from "react";
|
||||
import ExtensionCard, {ExtensionCardWrapper} from "../../components/extensions/ExtensionCard";
|
||||
import {Card, Row} from "react-bootstrap-v5";
|
||||
import {Card, Col, Row} from "react-bootstrap-v5";
|
||||
import {useParams} from "react-router-dom";
|
||||
import Masonry from "masonry-layout";
|
||||
import {usePlayer} from "../layout/PlayerPage";
|
||||
@ -14,6 +14,7 @@ const PlayerPluginData = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const masonryRow = document.getElementById('extension-masonry-row');
|
||||
if (!masonryRow) return;
|
||||
let masonry = Masonry.data(masonryRow);
|
||||
if (!masonry) {
|
||||
masonry = new Masonry(masonryRow, {"percentPosition": true, "itemSelector": ".extension-wrapper"});
|
||||
@ -28,11 +29,13 @@ const PlayerPluginData = () => {
|
||||
<LoadIn>
|
||||
<section className="player_plugin_data">
|
||||
<Row style={{overflowY: 'hidden'}}>
|
||||
<Card>
|
||||
<Card.Body>
|
||||
<p>No Extension data for {serverName}</p>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<Col md={12}>
|
||||
<Card>
|
||||
<Card.Body>
|
||||
<p>No Extension data for {serverName}</p>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</section>
|
||||
</LoadIn>
|
||||
|
61
Plan/react/dashboard/src/views/server/ServerPluginData.js
Normal file
61
Plan/react/dashboard/src/views/server/ServerPluginData.js
Normal file
@ -0,0 +1,61 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import {useServer} from "../layout/ServerPage";
|
||||
import Masonry from "masonry-layout";
|
||||
import LoadIn from "../../components/animation/LoadIn";
|
||||
import {Card, Col, Row} from "react-bootstrap-v5";
|
||||
import ExtensionCard, {ExtensionCardWrapper} from "../../components/extensions/ExtensionCard";
|
||||
|
||||
const ServerPluginData = () => {
|
||||
const extensionData = useServer();
|
||||
console.log(extensionData);
|
||||
const extensions = extensionData ? extensionData.extensions.filter(extension => !extension.wide) : [];
|
||||
|
||||
useEffect(() => {
|
||||
const masonryRow = document.getElementById('extension-masonry-row');
|
||||
if (!masonryRow) return;
|
||||
|
||||
let masonry = Masonry.data(masonryRow);
|
||||
if (!masonry) {
|
||||
masonry = new Masonry(masonryRow, {"percentPosition": true, "itemSelector": ".extension-wrapper"});
|
||||
}
|
||||
return () => {
|
||||
if (masonry.element) masonry.destroy();
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!extensions || !extensions.length) {
|
||||
return (
|
||||
<LoadIn>
|
||||
<section className="server_plugin_data">
|
||||
<Row style={{overflowY: 'hidden'}}>
|
||||
<Col md={12}>
|
||||
<Card>
|
||||
<Card.Body>
|
||||
<p>No Extension data</p>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</section>
|
||||
</LoadIn>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<LoadIn>
|
||||
<section className="server_plugin_data">
|
||||
<Row id="extension-masonry-row"
|
||||
data-masonry='{"percentPosition": true, "itemSelector": ".extension-wrapper"}'
|
||||
style={{overflowY: 'hidden'}}>
|
||||
{extensions.map((extension, i) =>
|
||||
<ExtensionCardWrapper key={'ext-' + i} extension={extension}>
|
||||
<ExtensionCard extension={extension}/>
|
||||
</ExtensionCardWrapper>
|
||||
)}
|
||||
</Row>
|
||||
</section>
|
||||
</LoadIn>
|
||||
)
|
||||
};
|
||||
|
||||
export default ServerPluginData
|
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import {useServer} from "../layout/ServerPage";
|
||||
import ErrorView from "../ErrorView";
|
||||
import LoadIn from "../../components/animation/LoadIn";
|
||||
import {Card, Col, Row} from "react-bootstrap-v5";
|
||||
import ExtensionCard from "../../components/extensions/ExtensionCard";
|
||||
import {useParams} from "react-router-dom";
|
||||
|
||||
const ServerWidePluginData = () => {
|
||||
const {plugin} = useParams();
|
||||
const {extensionData, extensionDataLoadingError} = useServer();
|
||||
|
||||
if (extensionDataLoadingError) return <ErrorView error={extensionDataLoadingError}/>;
|
||||
|
||||
const extension = extensionData?.find(extension => extension.extensionInformation.pluginName === plugin)
|
||||
|
||||
if (!extension) {
|
||||
return (
|
||||
<LoadIn>
|
||||
<section className="server_plugin_data">
|
||||
<Row style={{overflowY: 'hidden'}}>
|
||||
<Col md={12}>
|
||||
<Card>
|
||||
<Card.Body>
|
||||
<p>No Extension data</p>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</section>
|
||||
</LoadIn>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<LoadIn>
|
||||
<section className="server_plugin_data">
|
||||
<Row id={"extension-" + extension.extensionInformation.pluginName}
|
||||
style={{overflowY: 'hidden'}}>
|
||||
<Col md={12}>
|
||||
<ExtensionCard extension={extension}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</section>
|
||||
</LoadIn>
|
||||
)
|
||||
};
|
||||
|
||||
export default ServerWidePluginData
|
Loading…
Reference in New Issue
Block a user