mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-03-10 05:39:19 +01:00
Rewrite /errors page with React
This commit is contained in:
parent
80fe1ce982
commit
c607ef2869
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* 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.rendering.pages;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.PlaceholderReplacer;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Contributors;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.utilities.java.UnaryChain;
|
||||
import com.djrapitops.plan.version.VersionChecker;
|
||||
|
||||
/**
|
||||
* Html String generator for /login and /register page.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class ErrorsPage implements Page {
|
||||
|
||||
private final String template;
|
||||
private final Locale locale;
|
||||
private final Theme theme;
|
||||
private final VersionChecker versionChecker;
|
||||
|
||||
ErrorsPage(
|
||||
String htmlTemplate,
|
||||
Locale locale,
|
||||
Theme theme,
|
||||
VersionChecker versionChecker) {
|
||||
this.template = htmlTemplate;
|
||||
this.locale = locale;
|
||||
this.theme = theme;
|
||||
this.versionChecker = versionChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
PlaceholderReplacer placeholders = new PlaceholderReplacer();
|
||||
placeholders.put("title", Icon.called("bug").build().toHtml() + " Error logs");
|
||||
placeholders.put("titleText", "Error logs");
|
||||
placeholders.put("paragraph", buildBody());
|
||||
placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton()));
|
||||
placeholders.put("version", versionChecker.getCurrentVersion());
|
||||
placeholders.put("updateModal", versionChecker.getUpdateModal());
|
||||
placeholders.put("contributors", Contributors.generateContributorHtml());
|
||||
return UnaryChain.of(template)
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(placeholders::apply)
|
||||
.chain(locale::replaceLanguageInHtml)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private String buildBody() {
|
||||
//language=HTML
|
||||
return "<table class=\"table\" id=\"tableAccordion\" style='table-layout: fixed;'>\n" +
|
||||
" <thead>\n" +
|
||||
" <tr>\n" +
|
||||
" <th><i class=\"fa fa-fw fa-bug\"></i> Logfile</th>\n" +
|
||||
" <th><i class=\"far fa-fw fa-clock\"></i> Occurrences</th>\n" +
|
||||
" </tr>\n" +
|
||||
" </thead>\n" +
|
||||
" <tbody id=\"error-logs\"></tbody>\n" +
|
||||
"</table>\n" +
|
||||
"<script src=\"./js/xmlhttprequests.js\"></script>\n" +
|
||||
"<script src=\"./js/loadErrors.js\"></script>";
|
||||
}
|
||||
|
||||
}
|
@ -248,6 +248,7 @@ public class PageFactory {
|
||||
}
|
||||
|
||||
public Page errorsPage() throws IOException {
|
||||
return new ErrorsPage(getResource("error.html"), locale.get(), theme.get(), versionChecker.get());
|
||||
String reactHtml = getResource("index.html");
|
||||
return () -> reactHtml;
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
const errorLogs = document.getElementById('error-logs');
|
||||
|
||||
function renderErrorLog(i, errorLog) {
|
||||
return createErrorAccordionTitle(i, errorLog) + createErrorAccordionBody(i, errorLog);
|
||||
}
|
||||
|
||||
function createErrorAccordionTitle(i, errorLog) {
|
||||
let style = 'bg-amber-outline';
|
||||
const tagLine = errorLog.contents[0];
|
||||
return `<tr id="error_h_${i}" aria-controls="error_t_${i}" aria-expanded="false"
|
||||
class="clickable collapsed ${style}" data-bs-target="#error_t_${i}" data-bs-toggle="collapse">
|
||||
<td>${errorLog.fileName}</td>
|
||||
<td>${tagLine}</td>
|
||||
</tr>`
|
||||
}
|
||||
|
||||
function createErrorAccordionBody(i, errorLog) {
|
||||
return `<tr class="collapse" data-bs-parent="#tableAccordion" id="error_t_${i}">
|
||||
<td colspan="2">
|
||||
<pre class="pre-scrollable" style="overflow-x: scroll">${errorLog.contents.join('\n')}</pre>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
jsonRequest("./v1/errors", (json, error) => {
|
||||
if (error) {
|
||||
return errorLogs.innerText = `Failed to load /v1/errors: ${error}`;
|
||||
}
|
||||
|
||||
let html = ``;
|
||||
for (let i = 0; i < json.length; i++) {
|
||||
html += renderErrorLog(i, json[i]);
|
||||
}
|
||||
|
||||
errorLogs.innerHTML = html;
|
||||
})
|
@ -32,6 +32,7 @@ import LoginPage from "./views/layout/LoginPage";
|
||||
import ServerPerformance from "./views/server/ServerPerformance";
|
||||
import ServerPluginData from "./views/server/ServerPluginData";
|
||||
import ServerWidePluginData from "./views/server/ServerWidePluginData";
|
||||
import ErrorsPage from "./views/layout/ErrorsPage";
|
||||
|
||||
const SwaggerView = React.lazy(() => import("./views/SwaggerView"));
|
||||
|
||||
@ -99,6 +100,7 @@ function App() {
|
||||
icon: faMapSigns
|
||||
}}/>}/>
|
||||
</Route>
|
||||
<Route path="/errors" element={<ErrorsPage/>}/>
|
||||
<Route path="/docs" element={<React.Suspense fallback={<></>}>
|
||||
<SwaggerView/>
|
||||
</React.Suspense>}/>
|
||||
|
@ -40,7 +40,7 @@ const NoDataRow = ({width}) => {
|
||||
</tr>);
|
||||
}
|
||||
|
||||
const Accordion = ({headers, slices, open}) => {
|
||||
const Accordion = ({headers, slices, open, style}) => {
|
||||
const [openSlice, setOpenSlice] = useState(open ? 0 : -1);
|
||||
const {nightModeEnabled} = useTheme();
|
||||
|
||||
@ -51,7 +51,8 @@ const Accordion = ({headers, slices, open}) => {
|
||||
const width = headers.length;
|
||||
|
||||
return (
|
||||
<table className={"table accordion-striped" + (nightModeEnabled ? " table-dark" : '')} id="tableAccordion">
|
||||
<table className={"table accordion-striped" + (nightModeEnabled ? " table-dark" : '')} id="tableAccordion"
|
||||
style={style}>
|
||||
<thead>
|
||||
<tr>
|
||||
{headers.map((header, i) => <th key={i}>{header}</th>)}
|
||||
|
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import Accordion from "./Accordion";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faBug} from "@fortawesome/free-solid-svg-icons";
|
||||
import {faClock} from "@fortawesome/free-regular-svg-icons";
|
||||
|
||||
const ErrorBody = ({error}) => {
|
||||
return (<pre className="pre-scrollable" style={{overflowX: "scroll"}}>
|
||||
{error.contents.map((line) => <>{line}<br/></>)}
|
||||
</pre>)
|
||||
}
|
||||
|
||||
const ErrorHeader = ({error}) => {
|
||||
return <>
|
||||
<td>{error.fileName}</td>
|
||||
<td>{error.contents.length ? error.contents[0] : '{Empty file}'}</td>
|
||||
</>
|
||||
}
|
||||
|
||||
const ErrorsAccordion = ({errors}) => {
|
||||
const headers = [
|
||||
<><FontAwesomeIcon icon={faBug}/> Logfile</>,
|
||||
<><FontAwesomeIcon icon={faClock}/> Occurrences</>
|
||||
];
|
||||
const slices = errors ? errors.map(error => {
|
||||
return {
|
||||
body: <ErrorBody error={error}/>,
|
||||
header: <ErrorHeader error={error}/>,
|
||||
color: 'orange',
|
||||
outline: true
|
||||
}
|
||||
}) : [];
|
||||
|
||||
return (
|
||||
<Accordion headers={headers} slices={slices} open={false} style={{tableLayout: "fixed"}}/>
|
||||
)
|
||||
};
|
||||
|
||||
export default ErrorsAccordion
|
@ -14,3 +14,8 @@ export const fetchAvailableLocales = async () => {
|
||||
const url = '/v1/locale';
|
||||
return doGetRequest(url);
|
||||
}
|
||||
|
||||
export const fetchErrorLogs = async () => {
|
||||
const url = '/v1/errors';
|
||||
return doGetRequest(url);
|
||||
}
|
40
Plan/react/dashboard/src/views/layout/ErrorsPage.js
Normal file
40
Plan/react/dashboard/src/views/layout/ErrorsPage.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import {NightModeCss} from "../../hooks/themeHook";
|
||||
import Sidebar from "../../components/navigation/Sidebar";
|
||||
import Header from "../../components/navigation/Header";
|
||||
import ColorSelectorModal from "../../components/modal/ColorSelectorModal";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faBug} from "@fortawesome/free-solid-svg-icons";
|
||||
import {useDataRequest} from "../../hooks/dataFetchHook";
|
||||
import {fetchErrorLogs} from "../../service/metadataService";
|
||||
import ErrorPage from "./ErrorPage";
|
||||
import ErrorsAccordion from "../../components/accordion/ErrorsAccordion";
|
||||
import {Card} from "react-bootstrap-v5";
|
||||
|
||||
const ErrorsPage = () => {
|
||||
const {data, loadingError} = useDataRequest(fetchErrorLogs, []);
|
||||
|
||||
if (loadingError) return <ErrorPage error={loadingError}/>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<NightModeCss/>
|
||||
<Sidebar items={[]} showBackButton={true}/>
|
||||
<div className="d-flex flex-column" id="content-wrapper">
|
||||
<Header page={<><FontAwesomeIcon icon={faBug}/> Error Logs</>}/>
|
||||
<div id="content" style={{display: 'flex'}}>
|
||||
<main className="container-fluid mt-4">
|
||||
<Card>
|
||||
<ErrorsAccordion errors={data}/>
|
||||
</Card>
|
||||
</main>
|
||||
<aside>
|
||||
<ColorSelectorModal/>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default ErrorsPage
|
Loading…
Reference in New Issue
Block a user