Rewrite /errors page with React

This commit is contained in:
Aurora Lahtela 2022-09-01 18:59:59 +03:00
parent 80fe1ce982
commit c607ef2869
8 changed files with 91 additions and 121 deletions

View File

@ -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>";
}
}

View File

@ -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;
}
}

View File

@ -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;
})

View File

@ -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>}/>

View File

@ -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>)}

View File

@ -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

View File

@ -13,4 +13,9 @@ export const fetchPlanVersion = async () => {
export const fetchAvailableLocales = async () => {
const url = '/v1/locale';
return doGetRequest(url);
}
export const fetchErrorLogs = async () => {
const url = '/v1/errors';
return doGetRequest(url);
}

View 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