Added a /query page

What currently works:
- Adding filters dynamically
This commit is contained in:
Risto Lahtela 2020-03-27 13:39:08 +02:00
parent 8f5db846af
commit 6ff2d68b99
8 changed files with 543 additions and 2 deletions

View File

@ -222,4 +222,11 @@ public class PageFactory {
public Page registerPage() throws IOException {
return new LoginPage(getResource("register.html"), serverInfo.get());
}
public Page queryPage() throws IOException {
return new QueryPage(
getResource("query.html"),
locale.get(), theme.get(), versionChecker.get()
);
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.settings.locale.Locale;
import com.djrapitops.plan.settings.theme.Theme;
import com.djrapitops.plan.utilities.java.UnaryChain;
import com.djrapitops.plan.version.VersionChecker;
/**
* Page to display error stacktrace.
*
* @author Rsl1122
*/
public class QueryPage implements Page {
private final String template;
private final Locale locale;
private final Theme theme;
private final VersionChecker versionChecker;
public QueryPage(
String template,
Locale locale, Theme theme, VersionChecker versionChecker
) {
this.template = template;
this.locale = locale;
this.theme = theme;
this.versionChecker = versionChecker;
}
@Override
public String toHtml() {
PlaceholderReplacer placeholders = new PlaceholderReplacer();
placeholders.put("version", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton()));
placeholders.put("updateModal", versionChecker.getUpdateModal());
placeholders.put("contributors", Contributors.generateContributorHtml());
return UnaryChain.of(template)
.chain(theme::replaceThemeColors)
.chain(placeholders::apply)
.chain(locale::replaceLanguageInHtml)
.apply();
}
}

View File

@ -423,4 +423,12 @@ public class ResponseFactory {
return forInternalError(e, "Failed to generate player page");
}
}
public Response queryPageResponse() {
try {
return forPage(pageFactory.queryPage());
} catch (IOException e) {
return forInternalError(e, "Failed to generate query page");
}
}
}

View File

@ -54,6 +54,7 @@ import java.util.regex.Pattern;
public class ResponseResolver {
private final DebugPageResolver debugPageResolver;
private QueryPageResolver queryPageResolver;
private final PlayersPageResolver playersPageResolver;
private final PlayerPageResolver playerPageResolver;
private final ServerPageResolver serverPageResolver;
@ -78,6 +79,7 @@ public class ResponseResolver {
Lazy<WebServer> webServer,
DebugPageResolver debugPageResolver,
QueryPageResolver queryPageResolver,
PlayersPageResolver playersPageResolver,
PlayerPageResolver playerPageResolver,
ServerPageResolver serverPageResolver,
@ -97,6 +99,7 @@ public class ResponseResolver {
this.responseFactory = responseFactory;
this.webServer = webServer;
this.debugPageResolver = debugPageResolver;
this.queryPageResolver = queryPageResolver;
this.playersPageResolver = playersPageResolver;
this.playerPageResolver = playerPageResolver;
this.serverPageResolver = serverPageResolver;
@ -115,6 +118,7 @@ public class ResponseResolver {
String plugin = "Plan";
resolverService.registerResolver(plugin, "/robots.txt", (NoAuthResolver) request -> Optional.of(responseFactory.robotsResponse()));
resolverService.registerResolver(plugin, "/debug", debugPageResolver);
resolverService.registerResolver(plugin, "/query", queryPageResolver);
resolverService.registerResolver(plugin, "/players", playersPageResolver);
resolverService.registerResolver(plugin, "/player", playerPageResolver);
resolverService.registerResolver(plugin, "/favicon.ico", (NoAuthResolver) request -> Optional.of(responseFactory.faviconResponse()));

View File

@ -0,0 +1,47 @@
/*
* 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;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
import javax.inject.Inject;
import java.util.Optional;
public class QueryPageResolver implements Resolver {
private final ResponseFactory responseFactory;
@Inject
public QueryPageResolver(
ResponseFactory responseFactory
) {
this.responseFactory = responseFactory;
}
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission("page.players")).orElse(false);
}
@Override
public Optional<Response> resolve(Request request) {
return Optional.of(responseFactory.queryPageResponse());
}
}

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.FilterQuery;
import com.djrapitops.plan.utilities.java.Maps;
import java.util.List;
import java.util.Map;
@ -42,12 +43,15 @@ public class PluginGroupsFilter extends MultiOptionFilter {
@Override
public String getKind() {
return null;
return "pluginGroups";
}
@Override
public Map<String, Object> getOptions() {
return null;
return Maps.builder(String.class, Object.class)
.put("plugin", pluginName)
.put("options", groups)
.build();
}
@Override

View File

@ -0,0 +1,73 @@
var filterCount = 0;
function addFilter(parentSelector, filterIndex) {
$(parentSelector).append(createElement(filters[filterIndex]));
filterCount++;
}
function createElement(filter) {
switch (filter.kind) {
case "activityIndexNow":
return createMultipleChoiceSelector(`are in Activity Groups`, filter.options);
case "banned":
return createMultipleChoiceSelector(`are`, filter.options);
case "operators":
return createMultipleChoiceSelector(`are`, filter.options);
case "pluginGroups":
return createMultipleChoiceSelector(`are in ${filter.options.plugin} Groups`, filter.options);
case "playedBetween":
return createBetweenSelector("Played between", filter.options);
case "registeredBetween":
return createBetweenSelector("Registered between", filter.options);
default:
throw new Error("Unsupported filter kind: '" + filter.kind + "'")
}
}
function createFilterSelector(parent, index, filter) {
return `<a class="dropdown-item" href="#" onclick="addFilter('${parent}', ${index})">${filter.kind}</a>`;
}
function createBetweenSelector(label, options) {
var select = filterCount === 0 ? "of Players who " : "and ";
return `<label class="ml-2 mt-0 mb-0">${select}${label}:</label>` +
`<div class="mt-2 input-group input-row">` +
`<div class="col-3"><div class="input-group mb-2">` +
`<div class="input-group-prepend"><div class="input-group-text"><i class="far fa-calendar"></i></div></div>` +
`<input class="form-control" placeholder="${options.after[0]}" type="text">` +
`</div></div>` +
`<div class="col-2"><div class="input-group mb-2">` +
`<div class="input-group-prepend"><div class="input-group-text"><i class="far fa-clock"></i></div></div>` +
`<input class="form-control" placeholder="${options.after[1]}" type="text">` +
`</div></div>` +
`<div class="col-auto"><label class="mt-2 mb-0" for="inlineFormCustomSelectPref">&</label></div>` +
`<div class="col-3"><div class="input-group mb-2">` +
`<div class="input-group-prepend"><div class="input-group-text"><i class="far fa-calendar"></i></div></div>` +
`<input class="form-control" placeholder="${options.before[0]}" type="text">` +
`</div></div>` +
`<div class="col-2"><div class="input-group mb-2">` +
`<div class="input-group-prepend"><div class="input-group-text"><i class="far fa-clock"></i></div></div>` +
`<input class="form-control" placeholder="${options.before[1]}" type="text">` +
`</div></div>` +
`</div>`;
}
function createMultipleChoiceSelector(label, options) {
var select = filterCount === 0 ? "of Players who " : "and ";
var html = `<div class="mt-2 input-group input-row">` +
`<div class="col-12">
<label for="exampleFormControlSelect2">${select}${label}:</label>` +
`<select class="form-control" multiple>`;
for (var option of options.options) {
html += `<option>${option}</option>`
}
html += `</select></div> </div>`;
return html;
}

View File

@ -0,0 +1,337 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="Player Analytics, generic error/debug page" name="description">
<meta content="Rsl1122" name="author">
<title>Plan | Query</title>
<!-- Custom fonts for this template-->
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet">
<link crossorigin="anonymous" href="https://fonts.googleapis.com/css?family=Nunito:300,400,600,700,800,900"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="css/sb-admin-2.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
</head>
<body id="page-top">
<!-- Page Wrapper -->
<div id="wrapper">
<!-- Sidebar -->
<ul class="navbar-nav bg-plan sidebar sidebar-dark accordion" id="accordionSidebar">
<!-- Sidebar - Brand -->
<a class="sidebar-brand d-flex align-items-center justify-content-center">
<img class="w-22" src="img/Flaticon_circle.png">
</a>
<!-- Divider -->
<hr class="sidebar-divider my-0">
<li class="nav-item nav-button active">
<a class="nav-link" href="/">
<i class="far fa-fw fa-hand-point-left"></i>
<span>to main page</span></a>
</li>
<!-- Divider -->
<hr class="sidebar-divider">
<div class="ml-3 align-items-center justify-content-between">
<button class="btn bg-plan" data-target="#colorChooserModal" data-toggle="modal" type="button">
<i class="fa fa-palette"></i>
</button>
<button class="btn bg-plan" data-target="#informationModal" data-toggle="modal" type="button">
<i class="fa fa-fw fa-question-circle"></i>
</button>
</div>
<div class="ml-3 align-items-center justify-content-between">
${version}
</div>
</ul>
<!-- End of Sidebar -->
<!-- Content Wrapper -->
<div class="d-flex flex-column" id="content-wrapper">
<div class="sidebar-close-modal hidden"></div>
<!-- Main Content -->
<div id="content" style="display: flex;">
<!-- Begin Player Overview Tab -->
<div class="tab" style="width: 100%;">
<div class="container-fluid mt-4">
<!-- Page Heading -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800"><i class="sidebar-toggler fa fa-fw fa-bars"></i>Plan &middot;
Query</h1>
</div>
<div class="row">
<!-- Card -->
<div class="col-xs-12 col-sm-12 col-lg-11">
<div class="card shadow mb-4">
<div class="card-body" id="data_player_info">
<label class="mt-2 mb-0" for="inlineFormCustomSelectPref">Show a view</label>
<div class="mt-2 input-group input-row">
<div class="col-auto">
<label class="mt-2 mb-0" for="inlineFormCustomSelectPref">from</label>
</div>
<div class="col-3">
<label class="sr-only" for="inlineFormInputGroup">Date: DD/MM/YYYY</label>
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="far fa-calendar"></i></div>
</div>
<input class="form-control" id="inlineFormInputGroup"
placeholder="31/12/2016" type="text">
</div>
</div>
<div class="col-2">
<label class="sr-only" for="inlineFormInputGroup">Time: H H : M M</label>
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="far fa-clock"></i></div>
</div>
<input class="form-control" id="inlineFormInputGroup"
placeholder="23:59" type="text">
</div>
</div>
<div class="col-auto">
<label class="mt-2 mb-0" for="inlineFormCustomSelectPref">to</label>
</div>
<div class="col-3">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="far fa-calendar"></i></div>
</div>
<input class="form-control" id="inlineFormInputGroup"
placeholder="23/03/2020" type="text">
</div>
</div>
<div class="col-2">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="far fa-clock"></i></div>
</div>
<input class="form-control" id="inlineFormInputGroup"
placeholder="21:26" type="text">
</div>
</div>
</div>
<hr>
<div id="filters"></div>
<div class="mt-2 dropdown" id="addFilter">
<button aria-expanded="false" aria-haspopup="true" class="btn dropdown-toggle"
data-toggle="dropdown" id="filterDropdown" type="button">
<i class="fa fa-plus"></i> Add a filter..
</button>
<div aria-labelledby="filterDropdown" class="dropdown-menu">
<a class="dropdown-item" id="dropdown-loading">Loading filters..</a>
</div>
</div>
</div>
<button class="mt-2 mb-2 btn col-plan"><i class="fa fa-search"></i> Perform Query!
</button>
</div>
</div>
</div>
</div>
</div> <!-- /.container-fluid -->
</div> <!-- End of tab -->
</div> <!-- End of Main Content -->
</div><!-- End of Content Wrapper -->
<!-- Color Chooser Modal -->
<div aria-hidden="true" aria-labelledby="colorChooserModalLabel" class="modal fade" id="colorChooserModal"
role="dialog" tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="colorChooserModalLabel"><i class="fa fa-palette"></i> Theme Select
</h5>
<button aria-label="Close" class="close" data-dismiss="modal" type="button">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<button class="btn color-chooser" id="choose-plan" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-red" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-pink" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-purple" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-deep-purple" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-indigo" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-blue" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-light-blue" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-cyan" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-teal" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-green" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-light-green" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-lime" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-yellow" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-amber" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-orange" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-deep-orange" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-brown" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-grey" type="button"><i
class="fa fa-palette"></i></button>
<button class="btn color-chooser" id="choose-blue-grey" type="button"><i
class="fa fa-palette"></i></button>
</div>
<div class="modal-footer">
<button class="btn" id="night-mode-toggle" type="button"><i class="fa fa-fw fa-cloud-moon"></i>
Night
Mode
</button>
<button class="btn bg-plan" data-dismiss="modal" type="button">OK</button>
</div>
</div>
</div>
</div>
<!-- Information Modal -->
<div aria-hidden="true" aria-labelledby="informationModalLabel" class="modal fade" id="informationModal"
role="dialog" tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="informationModalLabel"><i class="fa fa-fw fa-question-circle"></i>
Information about the plugin
</h5>
<button aria-label="Close" class="close" data-dismiss="modal" type="button">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Player Analytics is developed and licensed under <a
href="https://opensource.org/licenses/LGPL-3.0" rel="noopener noreferrer"
target="_blank">Lesser General Public License
v3.0</a></p>
<hr>
<a class="btn col-plan" href="https://github.com/Rsl1122/Plan-PlayerAnalytics/wiki"
rel="noopener noreferrer" target="_blank"><i class="fa fa-fw fa-graduation-cap"></i> Plan
Wiki,
Tutorials & Documentation</a>
<a class="btn col-plan" href="https://github.com/Rsl1122/Plan-PlayerAnalytics/issues"
rel="noopener noreferrer" target="_blank"><i class="fa fa-fw fa-bug"></i> Report Issues</a>
<a class="btn col-plan" href="https://discord.gg/yXKmjzT" rel="noopener noreferrer"
target="_blank"><i class="fab fa-fw fa-discord"></i> General Support on Discord</a>
<hr>
<p>Player Analytics is developed by Rsl1122.</p>
<p>In addition following <span class="col-plan">awesome people</span> have contributed:</p>
<ul>
${contributors}
<li>& Bug reporters!</li>
</ul>
<small><i class="fa fa-fw fa-code"></i> code contributor <i class="fa fa-fw fa-language"></i>
translator
</small>
<hr>
<p class="col-plan">Extra special thanks to those who have monetarily supported the development.
<i class="fa fa-fw fa-star col-amber"></i></p>
<hr>
<h6>bStats Metrics</h6>
<a class="btn col-plan" href="https://bstats.org/plugin/bukkit/Plan" rel="noopener noreferrer"
target="_blank"><i class="fa fa-fw fa-chart-area"></i> Bukkit</a>
<a class="btn col-plan" href="https://bstats.org/plugin/bungeecord/Plan"
rel="noopener noreferrer" target="_blank"><i class="fa fa-fw fa-chart-area"></i>
BungeeCord</a>
<a class="btn col-plan" href="https://bstats.org/plugin/sponge/plan" rel="noopener noreferrer"
target="_blank"><i class="fa fa-fw fa-chart-area"></i> Sponge</a>
</div>
<div class="modal-footer">
<button class="btn bg-plan" data-dismiss="modal" type="button">OK</button>
</div>
</div>
</div>
</div>
<!-- Update Modal -->
<div aria-hidden="true" aria-labelledby="updateModalLabel" class="modal fade" id="updateModal" role="dialog"
tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
${updateModal}
<div class="modal-footer">
<button class="btn bg-plan" data-dismiss="modal" type="button">OK</button>
</div>
</div>
</div>
</div>
</div>
<!-- End of Page Wrapper -->
<!-- Scroll to Top Button-->
<a class="scroll-to-top rounded" href="#page-top">
<i class="fas fa-fw fa-angle-up"></i>
</a>
<!-- Bootstrap core JavaScript-->
<script src="./vendor/jquery/jquery.min.js"></script>
<script src="./vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="./vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Page level plugins -->
<script src="./vendor/datatables/jquery.dataTables.min.js"></script>
<script src="./vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src='./vendor/momentjs/moment.js'></script>
<!-- Custom scripts for all pages-->
<script src="./js/sb-admin-2.js"></script>
<script src="./js/xmlhttprequests.js"></script>
<script src="./js/color-selector.js"></script>
<!-- Page level custom scripts -->
<script src='./js/query.js'></script>
<script id="mainScript">
var filters = [];
jsonRequest("./v1/filters", function (json, error) {
var dropdown = $('#addFilter .dropdown-menu');
filters.push(...json.filters);
console.log(filters);
var filterElements = [];
for (var i = 0; i < filters.length; i++) {
filterElements.push(createFilterSelector('#filters', i, filters[i]));
}
$('#dropdown-loading').replaceWith(filterElements);
});
</script>
</body>
</html>