mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-23 01:27:42 +01:00
2905/remove old web files (#3225)
* Removed Plugin.Use_Legacy_Frontend feature flag * Remove version modal html generation in Java * Remove contributor list html generation in Java * Remove more html generation in Java * Delete old web file bundle * Removed locale html/js translation from backend * Move Html#swapColorsToSpan to the React side * Reimplement datatables and backend for preferences * Load preferences from backend * Usability fixes to the players table * Delete WebAssetVersionCheckTask * Add new locale to locale files * Export defaultPreferences as json on react export Affects issues: - Close #2905
This commit is contained in:
parent
2fc4ee7ac1
commit
01d904c7d1
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.extension.icon;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Object that represents an icon on the website.
|
||||
* <p>
|
||||
@ -72,6 +74,19 @@ public class Icon {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Icon icon = (Icon) o;
|
||||
return type == icon.type && Objects.equals(getName(), icon.getName()) && getColor() == icon.getColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, type, getName(), getColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Icon{" + type.name() + ", '" + name + '\'' + ", " + color.name() + '}';
|
||||
|
@ -21,10 +21,8 @@ import com.djrapitops.plan.extension.icon.Color;
|
||||
import com.djrapitops.plan.extension.icon.Icon;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Object for giving Plan table data.
|
||||
@ -100,6 +98,52 @@ public final class Table {
|
||||
return tableColumnFormats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Table table = (Table) o;
|
||||
return Arrays.equals(getColumns(), table.getColumns())
|
||||
&& Arrays.equals(getIcons(), table.getIcons())
|
||||
&& Arrays.equals(getTableColumnFormats(), table.getTableColumnFormats())
|
||||
&& valuesEqual(table);
|
||||
}
|
||||
|
||||
private boolean valuesEqual(Table other) {
|
||||
List<Object[]> rows1 = getRows();
|
||||
List<Object[]> rows2 = other.getRows();
|
||||
if (rows1.size() != rows2.size()) return false;
|
||||
for (int i = 0; i < rows1.size(); i++) {
|
||||
Object[] values1 = rows1.get(i);
|
||||
Object[] values2 = rows2.get(i);
|
||||
for (int j = 0; j < getMaxColumnSize(); j++) {
|
||||
if (!Objects.equals(Objects.toString(values1[0]), Objects.toString(values2[0]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(getRows());
|
||||
result = 31 * result + Arrays.hashCode(getColumns());
|
||||
result = 31 * result + Arrays.hashCode(getIcons());
|
||||
result = 31 * result + Arrays.hashCode(getTableColumnFormats());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Table{" +
|
||||
"columns=" + Arrays.toString(columns) +
|
||||
", icons=" + Arrays.toString(icons) +
|
||||
", tableColumnFormats=" + Arrays.toString(tableColumnFormats) +
|
||||
", rows=" + rows.stream().map(Arrays::toString).collect(Collectors.toList()) +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for creating new {@link Table} objects.
|
||||
*/
|
||||
|
@ -19,7 +19,6 @@ package com.djrapitops.plan.modules.bukkit;
|
||||
import com.djrapitops.plan.TaskSystem;
|
||||
import com.djrapitops.plan.addons.placeholderapi.PlaceholderCacheRefreshTask;
|
||||
import com.djrapitops.plan.delivery.web.ResourceWriteTask;
|
||||
import com.djrapitops.plan.delivery.web.WebAssetVersionCheckTask;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieExpiryCleanupTask;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage;
|
||||
import com.djrapitops.plan.delivery.webserver.configuration.AddressAllowList;
|
||||
@ -94,10 +93,6 @@ public interface BukkitTaskModule {
|
||||
@IntoSet
|
||||
TaskSystem.Task bindResourceWriteTask(ResourceWriteTask resourceWriteTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindWebAssetVersionCheckTask(WebAssetVersionCheckTask webAssetVersionCheckTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindActiveCookieStoreExpiryTask(ActiveCookieExpiryCleanupTask activeCookieExpiryCleanupTask);
|
||||
|
@ -18,7 +18,6 @@ package com.djrapitops.plan.modules.bungee;
|
||||
|
||||
import com.djrapitops.plan.TaskSystem;
|
||||
import com.djrapitops.plan.delivery.web.ResourceWriteTask;
|
||||
import com.djrapitops.plan.delivery.web.WebAssetVersionCheckTask;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieExpiryCleanupTask;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage;
|
||||
import com.djrapitops.plan.delivery.webserver.configuration.AddressAllowList;
|
||||
@ -81,10 +80,6 @@ public interface BungeeTaskModule {
|
||||
@IntoSet
|
||||
TaskSystem.Task bindResourceWriteTask(ResourceWriteTask resourceWriteTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindWebAssetVersionCheckTask(WebAssetVersionCheckTask webAssetVersionCheckTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindActiveCookieStoreExpiryTask(ActiveCookieExpiryCleanupTask activeCookieExpiryCleanupTask);
|
||||
|
@ -136,6 +136,7 @@ task yarnStart(type: YarnTask) {
|
||||
task copyYarnBuildResults {
|
||||
inputs.files(fileTree("$rootDir/react/dashboard/build"))
|
||||
outputs.dir("$rootDir/common/build/resources/main/assets/plan/web")
|
||||
outputs.dir("$rootDir/common/build/resources/test/assets/plan/web")
|
||||
|
||||
dependsOn yarnBundle
|
||||
doLast {
|
||||
@ -144,6 +145,10 @@ task copyYarnBuildResults {
|
||||
from "$rootDir/react/dashboard/build"
|
||||
into "$rootDir/common/build/resources/main/assets/plan/web"
|
||||
}
|
||||
copy {
|
||||
into "$rootDir/common/build/resources/main/assets/plan/web"
|
||||
into "$rootDir/common/build/resources/test/assets/plan/web"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.domain.datatransfer;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionDescriptionDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PlayerListDto {
|
||||
|
||||
private final List<TablePlayerDto> players;
|
||||
private final List<ExtensionDescriptionDto> extensionDescriptors;
|
||||
|
||||
public PlayerListDto(List<TablePlayerDto> players, List<ExtensionDescriptionDto> extensionDescriptors) {
|
||||
this.players = players;
|
||||
this.extensionDescriptors = extensionDescriptors;
|
||||
}
|
||||
|
||||
public List<TablePlayerDto> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public List<ExtensionDescriptionDto> getExtensionDescriptors() {
|
||||
return extensionDescriptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlayerListDto{" +
|
||||
"players=" + players +
|
||||
", extensionDescriptors=" + extensionDescriptors +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.domain.datatransfer;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionValueDataDto;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a row for players table.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class TablePlayerDto {
|
||||
|
||||
private UUID playerUUID;
|
||||
private String playerName;
|
||||
private double activityIndex;
|
||||
private Long playtimeActive;
|
||||
private Long sessionCount;
|
||||
private Long lastSeen;
|
||||
private Long registered;
|
||||
private String country;
|
||||
|
||||
private Map<String, ExtensionValueDataDto> extensionValues;
|
||||
|
||||
private TablePlayerDto() {
|
||||
// Builder constructor
|
||||
}
|
||||
|
||||
public static TablePlayerDtoBuilder builder() {
|
||||
return new TablePlayerDtoBuilder();
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
private void setPlayerName(String playerName) {
|
||||
this.playerName = playerName;
|
||||
}
|
||||
|
||||
public double getActivityIndex() {
|
||||
return activityIndex;
|
||||
}
|
||||
|
||||
private void setActivityIndex(double activityIndex) {
|
||||
this.activityIndex = activityIndex;
|
||||
}
|
||||
|
||||
public Long getPlaytimeActive() {
|
||||
return playtimeActive;
|
||||
}
|
||||
|
||||
private void setPlaytimeActive(Long playtimeActive) {
|
||||
this.playtimeActive = playtimeActive;
|
||||
}
|
||||
|
||||
public Long getSessionCount() {
|
||||
return sessionCount;
|
||||
}
|
||||
|
||||
private void setSessionCount(Long sessionCount) {
|
||||
this.sessionCount = sessionCount;
|
||||
}
|
||||
|
||||
public Long getLastSeen() {
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
private void setLastSeen(Long lastSeen) {
|
||||
this.lastSeen = lastSeen;
|
||||
}
|
||||
|
||||
public Long getRegistered() {
|
||||
return registered;
|
||||
}
|
||||
|
||||
private void setRegistered(Long registered) {
|
||||
this.registered = registered;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
private void setCountry(String country) {
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public Map<String, ExtensionValueDataDto> getExtensionValues() {
|
||||
return extensionValues;
|
||||
}
|
||||
|
||||
private void setExtensionValues(Map<String, ExtensionValueDataDto> extensionValues) {
|
||||
this.extensionValues = extensionValues;
|
||||
}
|
||||
|
||||
public UUID getPlayerUUID() {
|
||||
return playerUUID;
|
||||
}
|
||||
|
||||
public void setPlayerUUID(UUID playerUUID) {
|
||||
this.playerUUID = playerUUID;
|
||||
}
|
||||
|
||||
public static final class TablePlayerDtoBuilder {
|
||||
private final TablePlayerDto tablePlayerDto;
|
||||
|
||||
private TablePlayerDtoBuilder() {tablePlayerDto = new TablePlayerDto();}
|
||||
|
||||
public TablePlayerDtoBuilder withUuid(UUID playerUUID) {
|
||||
tablePlayerDto.setPlayerUUID(playerUUID);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDtoBuilder withName(String name) {
|
||||
tablePlayerDto.setPlayerName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDtoBuilder withActivityIndex(double activityIndex) {
|
||||
tablePlayerDto.setActivityIndex(activityIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDtoBuilder withPlaytimeActive(Long playtimeActive) {
|
||||
tablePlayerDto.setPlaytimeActive(playtimeActive);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDtoBuilder withSessionCount(Long sessionCount) {
|
||||
tablePlayerDto.setSessionCount(sessionCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDtoBuilder withLastSeen(Long lastSeen) {
|
||||
tablePlayerDto.setLastSeen(lastSeen);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDtoBuilder withRegistered(Long registered) {
|
||||
tablePlayerDto.setRegistered(registered);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDtoBuilder withCountry(String country) {
|
||||
tablePlayerDto.setCountry(country);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDtoBuilder withExtensionValues(Map<String, ExtensionValueDataDto> extensionValues) {
|
||||
tablePlayerDto.setExtensionValues(extensionValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDto build() {return tablePlayerDto;}
|
||||
}
|
||||
}
|
@ -16,12 +16,14 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.domain.datatransfer.extension;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.extension.FormatType;
|
||||
import com.djrapitops.plan.extension.implementation.results.ExtensionTabData;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ExtensionTabDataDto {
|
||||
@ -36,25 +38,29 @@ public class ExtensionTabDataDto {
|
||||
tableData = extensionTabData.getTableData().stream().map(ExtensionTableDataDto::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<ExtensionValueDataDto> constructValues(List<String> order, ExtensionTabData tabData) {
|
||||
public static Optional<ExtensionValueDataDto> mapToValue(ExtensionTabData tabData, String key) {
|
||||
Formatters formatters = Formatters.getInstance();
|
||||
Formatter<Double> decimalFormatter = formatters.decimals();
|
||||
Formatter<Double> percentageFormatter = formatters.percentage();
|
||||
|
||||
Map<FormatType, Formatter<Long>> numberFormatters = new EnumMap<>(FormatType.class);
|
||||
numberFormatters.put(FormatType.DATE_SECOND, formatters.secondLong());
|
||||
numberFormatters.put(FormatType.DATE_YEAR, formatters.yearLong());
|
||||
numberFormatters.put(FormatType.TIME_MILLISECONDS, formatters.timeAmount());
|
||||
numberFormatters.put(FormatType.NONE, Object::toString);
|
||||
Optional<ExtensionValueDataDto> booleanValue = tabData.getBoolean(key).map(data -> new ExtensionValueDataDto(data.getDescription(), "BOOLEAN", data.getFormattedValue()));
|
||||
if (booleanValue.isPresent()) return booleanValue;
|
||||
Optional<ExtensionValueDataDto> doubleValue = tabData.getDouble(key).map(data -> new ExtensionValueDataDto(data.getDescription(), "DOUBLE", data.getFormattedValue(formatters.decimals())));
|
||||
if (doubleValue.isPresent()) return doubleValue;
|
||||
Optional<ExtensionValueDataDto> percentage = tabData.getPercentage(key).map(data -> new ExtensionValueDataDto(data.getDescription(), "PERCENTAGE", data.getFormattedValue(formatters.percentage())));
|
||||
if (percentage.isPresent()) return percentage;
|
||||
Optional<ExtensionValueDataDto> number = tabData.getNumber(key).map(data -> new ExtensionValueDataDto(data.getDescription(), data.getFormatType() == FormatType.NONE ? "NUMBER" : data.getFormatType().name(), data.getFormattedValue(formatters.getNumberFormatter(data.getFormatType()))));
|
||||
if (number.isPresent()) return number;
|
||||
Optional<ExtensionValueDataDto> string = tabData.getString(key).map(data -> new ExtensionValueDataDto(data.getDescription(), data.isPlayerName() ? "LINK" : "STRING", data.getFormattedValue()));
|
||||
if (string.isPresent()) return string;
|
||||
|
||||
// If component is not found either return empty Optional.
|
||||
return tabData.getComponent(key).map(data -> new ExtensionValueDataDto(data.getDescription(), "COMPONENT", data.getFormattedValue()));
|
||||
}
|
||||
|
||||
private List<ExtensionValueDataDto> constructValues(List<String> order, ExtensionTabData tabData) {
|
||||
|
||||
List<ExtensionValueDataDto> extensionValues = new ArrayList<>();
|
||||
for (String key : order) {
|
||||
tabData.getBoolean(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), "BOOLEAN", data.getFormattedValue())));
|
||||
tabData.getDouble(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), "DOUBLE", data.getFormattedValue(decimalFormatter))));
|
||||
tabData.getPercentage(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), "PERCENTAGE", data.getFormattedValue(percentageFormatter))));
|
||||
tabData.getNumber(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), data.getFormatType() == FormatType.NONE ? "NUMBER" : data.getFormatType().name(), data.getFormattedValue(numberFormatters.get(data.getFormatType())))));
|
||||
tabData.getString(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), data.isPlayerName() ? "HTML" : "STRING", data.getFormattedValue())));
|
||||
tabData.getComponent(key).ifPresent(data -> extensionValues.add(new ExtensionValueDataDto(data.getDescription(), "COMPONENT", data.getFormattedValue())));
|
||||
mapToValue(tabData, key).ifPresent(extensionValues::add);
|
||||
}
|
||||
return extensionValues;
|
||||
}
|
||||
|
@ -16,8 +16,10 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.domain.datatransfer.extension;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.html.structure.HtmlTable;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Html;
|
||||
import com.djrapitops.plan.extension.table.Table;
|
||||
import com.djrapitops.plan.extension.table.TableColumnFormat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -41,11 +43,48 @@ public class TableDto {
|
||||
.map(IconDto::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
rows = HtmlTable.mapToRows(table.getRows(), table.getTableColumnFormats()).stream()
|
||||
rows = mapToRows(table.getRows(), table.getTableColumnFormats()).stream()
|
||||
.map(row -> constructRow(columns, row))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<TableCellDto[]> mapToRows(List<Object[]> rows, TableColumnFormat[] tableColumnFormats) {
|
||||
return rows.stream()
|
||||
.map(row -> {
|
||||
List<TableCellDto> mapped = new ArrayList<>(row.length);
|
||||
for (int i = 0; i < row.length; i++) {
|
||||
Object value = row[i];
|
||||
if (value == null) {
|
||||
mapped.add(null);
|
||||
} else {
|
||||
TableColumnFormat format = tableColumnFormats[i];
|
||||
mapped.add(new TableCellDto(applyFormat(format, value), value));
|
||||
}
|
||||
}
|
||||
return mapped.toArray(new TableCellDto[0]);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static String applyFormat(TableColumnFormat format, Object value) {
|
||||
try {
|
||||
switch (format) {
|
||||
case TIME_MILLISECONDS:
|
||||
return Formatters.getInstance().timeAmount().apply(Long.parseLong(value.toString()));
|
||||
case DATE_YEAR:
|
||||
return Formatters.getInstance().yearLong().apply(Long.parseLong(value.toString()));
|
||||
case DATE_SECOND:
|
||||
return Formatters.getInstance().secondLong().apply(Long.parseLong(value.toString()));
|
||||
case PLAYER_NAME:
|
||||
return Html.LINK.create("../player/" + Html.encodeToURL(value.toString()));
|
||||
default:
|
||||
return value.toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return Objects.toString(value);
|
||||
}
|
||||
}
|
||||
|
||||
private List<TableCellDto> constructRow(List<String> columns, TableCellDto[] row) {
|
||||
List<TableCellDto> constructedRow = new ArrayList<>();
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.domain.datatransfer.preferences;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class GraphThresholds {
|
||||
|
||||
private double highThreshold;
|
||||
private double mediumThreshold;
|
||||
|
||||
public GraphThresholds(double highThreshold, double mediumThreshold) {
|
||||
this.highThreshold = highThreshold;
|
||||
this.mediumThreshold = mediumThreshold;
|
||||
}
|
||||
|
||||
public double getHighThreshold() {
|
||||
return highThreshold;
|
||||
}
|
||||
|
||||
public void setHighThreshold(int highThreshold) {
|
||||
this.highThreshold = highThreshold;
|
||||
}
|
||||
|
||||
public double getMediumThreshold() {
|
||||
return mediumThreshold;
|
||||
}
|
||||
|
||||
public void setMediumThreshold(int mediumThreshold) {
|
||||
this.mediumThreshold = mediumThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
GraphThresholds that = (GraphThresholds) o;
|
||||
return getHighThreshold() == that.getHighThreshold() && getMediumThreshold() == that.getMediumThreshold();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getHighThreshold(), getMediumThreshold());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GraphThresholds{" +
|
||||
"highThreshold=" + highThreshold +
|
||||
", mediumThreshold=" + mediumThreshold +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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.domain.datatransfer.preferences;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class Preferences {
|
||||
|
||||
private boolean recentDaysInDateFormat;
|
||||
private String dateFormatFull;
|
||||
private String dateFormatNoSeconds;
|
||||
private String dateFormatClock;
|
||||
private TimeFormat timeFormat;
|
||||
private String decimalFormat;
|
||||
private int firstDay;
|
||||
|
||||
private String playerHeadImageUrl;
|
||||
|
||||
private GraphThresholds tpsThresholds;
|
||||
private GraphThresholds diskThresholds;
|
||||
|
||||
private Preferences() {
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Preferences.Builder();
|
||||
}
|
||||
|
||||
public boolean isRecentDaysInDateFormat() {
|
||||
return recentDaysInDateFormat;
|
||||
}
|
||||
|
||||
private void setRecentDaysInDateFormat(boolean recentDaysInDateFormat) {
|
||||
this.recentDaysInDateFormat = recentDaysInDateFormat;
|
||||
}
|
||||
|
||||
public String getDateFormatFull() {
|
||||
return dateFormatFull;
|
||||
}
|
||||
|
||||
private void setDateFormatFull(String dateFormatFull) {
|
||||
this.dateFormatFull = dateFormatFull;
|
||||
}
|
||||
|
||||
public String getDateFormatNoSeconds() {
|
||||
return dateFormatNoSeconds;
|
||||
}
|
||||
|
||||
private void setDateFormatNoSeconds(String dateFormatNoSeconds) {
|
||||
this.dateFormatNoSeconds = dateFormatNoSeconds;
|
||||
}
|
||||
|
||||
public String getDateFormatClock() {
|
||||
return dateFormatClock;
|
||||
}
|
||||
|
||||
private void setDateFormatClock(String dateFormatClock) {
|
||||
this.dateFormatClock = dateFormatClock;
|
||||
}
|
||||
|
||||
public TimeFormat getTimeFormat() {
|
||||
return timeFormat;
|
||||
}
|
||||
|
||||
private void setTimeFormat(TimeFormat timeFormat) {
|
||||
this.timeFormat = timeFormat;
|
||||
}
|
||||
|
||||
public String getDecimalFormat() {
|
||||
return decimalFormat;
|
||||
}
|
||||
|
||||
private void setDecimalFormat(String decimalFormat) {
|
||||
this.decimalFormat = decimalFormat;
|
||||
}
|
||||
|
||||
public int getFirstDay() {
|
||||
return firstDay;
|
||||
}
|
||||
|
||||
private void setFirstDay(int firstDay) {
|
||||
this.firstDay = firstDay;
|
||||
}
|
||||
|
||||
public String getPlayerHeadImageUrl() {
|
||||
return playerHeadImageUrl;
|
||||
}
|
||||
|
||||
private void setPlayerHeadImageUrl(String playerHeadImageUrl) {
|
||||
this.playerHeadImageUrl = playerHeadImageUrl;
|
||||
}
|
||||
|
||||
public GraphThresholds getTpsThresholds() {
|
||||
return tpsThresholds;
|
||||
}
|
||||
|
||||
private void setTpsThresholds(GraphThresholds tpsThresholds) {
|
||||
this.tpsThresholds = tpsThresholds;
|
||||
}
|
||||
|
||||
public GraphThresholds getDiskThresholds() {
|
||||
return diskThresholds;
|
||||
}
|
||||
|
||||
private void setDiskThresholds(GraphThresholds diskThresholds) {
|
||||
this.diskThresholds = diskThresholds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Preferences that = (Preferences) o;
|
||||
return isRecentDaysInDateFormat() == that.isRecentDaysInDateFormat() && getFirstDay() == that.getFirstDay() && Objects.equals(getDateFormatFull(), that.getDateFormatFull()) && Objects.equals(getDateFormatNoSeconds(), that.getDateFormatNoSeconds()) && Objects.equals(getDateFormatClock(), that.getDateFormatClock()) && Objects.equals(getTimeFormat(), that.getTimeFormat()) && Objects.equals(getDecimalFormat(), that.getDecimalFormat()) && Objects.equals(getPlayerHeadImageUrl(), that.getPlayerHeadImageUrl()) && Objects.equals(getTpsThresholds(), that.getTpsThresholds()) && Objects.equals(getDiskThresholds(), that.getDiskThresholds());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(isRecentDaysInDateFormat(), getDateFormatFull(), getDateFormatNoSeconds(), getDateFormatClock(), getTimeFormat(), getDecimalFormat(), getFirstDay(), getPlayerHeadImageUrl(), getTpsThresholds(), getDiskThresholds());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Preferences{" +
|
||||
"recentDaysInDateFormat=" + recentDaysInDateFormat +
|
||||
", dateFormatFull='" + dateFormatFull + '\'' +
|
||||
", dateFormatNoSeconds='" + dateFormatNoSeconds + '\'' +
|
||||
", dateFormatClock='" + dateFormatClock + '\'' +
|
||||
", timeFormat=" + timeFormat +
|
||||
", decimalFormat='" + decimalFormat + '\'' +
|
||||
", firstDay=" + firstDay +
|
||||
", playerHeadImageUrl='" + playerHeadImageUrl + '\'' +
|
||||
", tpsThresholds=" + tpsThresholds +
|
||||
", diskThresholds=" + diskThresholds +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final Preferences preferences;
|
||||
|
||||
private Builder() {preferences = new Preferences();}
|
||||
|
||||
public Builder withRecentDaysInDateFormat(boolean recentDaysInDateFormat) {
|
||||
preferences.setRecentDaysInDateFormat(recentDaysInDateFormat);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDateFormatFull(String dateFormatFull) {
|
||||
preferences.setDateFormatFull(dateFormatFull);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDateFormatNoSeconds(String dateFormatNoSeconds) {
|
||||
preferences.setDateFormatNoSeconds(dateFormatNoSeconds);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDateFormatClock(String dateFormatClock) {
|
||||
preferences.setDateFormatClock(dateFormatClock);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTimeFormat(TimeFormat timeFormat) {
|
||||
preferences.setTimeFormat(timeFormat);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDecimalFormat(String decimalFormat) {
|
||||
preferences.setDecimalFormat(decimalFormat);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFirstDay(int firstDay) {
|
||||
preferences.setFirstDay(firstDay);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPlayerHeadImageUrl(String playerHeadImageUrl) {
|
||||
preferences.setPlayerHeadImageUrl(playerHeadImageUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTpsThresholds(GraphThresholds tpsThresholds) {
|
||||
preferences.setTpsThresholds(tpsThresholds);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDiskThresholds(GraphThresholds diskThresholds) {
|
||||
preferences.setDiskThresholds(diskThresholds);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Preferences build() {return preferences;}
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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.domain.datatransfer.preferences;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class TimeFormat {
|
||||
private String year;
|
||||
private String years;
|
||||
private String month;
|
||||
private String months;
|
||||
private String day;
|
||||
private String days;
|
||||
private String hours;
|
||||
private String minutes;
|
||||
private String seconds;
|
||||
private String zero;
|
||||
|
||||
private TimeFormat() {
|
||||
// Constructor is private for builder
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public String getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
private void setYear(String year) {
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public String getYears() {
|
||||
return years;
|
||||
}
|
||||
|
||||
private void setYears(String years) {
|
||||
this.years = years;
|
||||
}
|
||||
|
||||
public String getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
private void setMonth(String month) {
|
||||
this.month = month;
|
||||
}
|
||||
|
||||
public String getMonths() {
|
||||
return months;
|
||||
}
|
||||
|
||||
private void setMonths(String months) {
|
||||
this.months = months;
|
||||
}
|
||||
|
||||
public String getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
private void setDay(String day) {
|
||||
this.day = day;
|
||||
}
|
||||
|
||||
public String getDays() {
|
||||
return days;
|
||||
}
|
||||
|
||||
private void setDays(String days) {
|
||||
this.days = days;
|
||||
}
|
||||
|
||||
public String getHours() {
|
||||
return hours;
|
||||
}
|
||||
|
||||
private void setHours(String hours) {
|
||||
this.hours = hours;
|
||||
}
|
||||
|
||||
public String getMinutes() {
|
||||
return minutes;
|
||||
}
|
||||
|
||||
private void setMinutes(String minutes) {
|
||||
this.minutes = minutes;
|
||||
}
|
||||
|
||||
public String getSeconds() {
|
||||
return seconds;
|
||||
}
|
||||
|
||||
private void setSeconds(String seconds) {
|
||||
this.seconds = seconds;
|
||||
}
|
||||
|
||||
public String getZero() {
|
||||
return zero;
|
||||
}
|
||||
|
||||
private void setZero(String zero) {
|
||||
this.zero = zero;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
TimeFormat that = (TimeFormat) o;
|
||||
return Objects.equals(getYear(), that.getYear()) && Objects.equals(getYears(), that.getYears()) && Objects.equals(getMonth(), that.getMonth()) && Objects.equals(getMonths(), that.getMonths()) && Objects.equals(getDay(), that.getDay()) && Objects.equals(getDays(), that.getDays()) && Objects.equals(getHours(), that.getHours()) && Objects.equals(getMinutes(), that.getMinutes()) && Objects.equals(getSeconds(), that.getSeconds()) && Objects.equals(getZero(), that.getZero());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getYear(), getYears(), getMonth(), getMonths(), getDay(), getDays(), getHours(), getMinutes(), getSeconds(), getZero());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TimeFormat{" +
|
||||
"year='" + year + '\'' +
|
||||
", years='" + years + '\'' +
|
||||
", month='" + month + '\'' +
|
||||
", months='" + months + '\'' +
|
||||
", day='" + day + '\'' +
|
||||
", days='" + days + '\'' +
|
||||
", hours='" + hours + '\'' +
|
||||
", minutes='" + minutes + '\'' +
|
||||
", seconds='" + seconds + '\'' +
|
||||
", zero='" + zero + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final TimeFormat timeFormat;
|
||||
|
||||
private Builder() {timeFormat = new TimeFormat();}
|
||||
|
||||
public Builder withYear(String year) {
|
||||
timeFormat.setYear(year);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withYears(String years) {
|
||||
timeFormat.setYears(years);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMonth(String month) {
|
||||
timeFormat.setMonth(month);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMonths(String months) {
|
||||
timeFormat.setMonths(months);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDay(String day) {
|
||||
timeFormat.setDay(day);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDays(String days) {
|
||||
timeFormat.setDays(days);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withHours(String hours) {
|
||||
timeFormat.setHours(hours);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMinutes(String minutes) {
|
||||
timeFormat.setMinutes(minutes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSeconds(String seconds) {
|
||||
timeFormat.setSeconds(seconds);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withZero(String zero) {
|
||||
timeFormat.setZero(zero);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TimeFormat build() {return timeFormat;}
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.ExportSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
||||
@ -86,11 +85,6 @@ public class ExportScheduler extends PluginRunnable {
|
||||
}
|
||||
|
||||
private void scheduleReactExport() {
|
||||
if (config.isTrue(PluginSettings.LEGACY_FRONTEND) ||
|
||||
config.isFalse(ExportSettings.SERVER_PAGE) &&
|
||||
config.isFalse(ExportSettings.PLAYER_PAGES) &&
|
||||
config.isFalse(ExportSettings.PLAYERS_PAGE)) {return;}
|
||||
|
||||
runnableFactory.create(
|
||||
new ExportTask(exporter, Exporter::exportReact, errorLogger)
|
||||
).runTaskLaterAsynchronously(TimeAmount.toTicks(5, TimeUnit.SECONDS));
|
||||
|
@ -16,23 +16,16 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.export;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.pages.Page;
|
||||
import com.djrapitops.plan.delivery.rendering.pages.PageFactory;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.RootJSONResolver;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.storage.file.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
@ -54,25 +47,19 @@ public class NetworkPageExporter extends FileExporter {
|
||||
private final PlanFiles files;
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final PageFactory pageFactory;
|
||||
private final RootJSONResolver jsonHandler;
|
||||
private final Theme theme;
|
||||
|
||||
@Inject
|
||||
public NetworkPageExporter(
|
||||
PlanFiles files,
|
||||
PlanConfig config,
|
||||
DBSystem dbSystem,
|
||||
PageFactory pageFactory,
|
||||
RootJSONResolver jsonHandler,
|
||||
Theme theme
|
||||
RootJSONResolver jsonHandler
|
||||
) {
|
||||
this.files = files;
|
||||
this.config = config;
|
||||
this.dbSystem = dbSystem;
|
||||
this.pageFactory = pageFactory;
|
||||
this.jsonHandler = jsonHandler;
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,35 +76,10 @@ public class NetworkPageExporter extends FileExporter {
|
||||
|
||||
ExportPaths exportPaths = new ExportPaths();
|
||||
exportPaths.put("./players", toRelativePathFromRoot("players"));
|
||||
exportRequiredResources(exportPaths, toDirectory);
|
||||
exportJSON(exportPaths, toDirectory, server);
|
||||
exportHtml(exportPaths, toDirectory);
|
||||
exportReactRedirects(toDirectory);
|
||||
}
|
||||
|
||||
private void exportHtml(ExportPaths exportPaths, Path toDirectory) throws IOException {
|
||||
if (config.isFalse(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
Path to = toDirectory
|
||||
.resolve("network")
|
||||
.resolve("index.html");
|
||||
|
||||
Page page = pageFactory.networkPage();
|
||||
|
||||
// Fixes refreshingJsonRequest ignoring old data of export
|
||||
String html = StringUtils.replaceEach(page.toHtml(),
|
||||
new String[]{"loadPlayersOnlineGraph, 'network-overview', true);",
|
||||
"· Performance",
|
||||
"<head>"
|
||||
},
|
||||
new String[]{"loadPlayersOnlineGraph, 'network-overview');",
|
||||
"· Performance (Unavailable with Export)",
|
||||
"<head><style>.refresh-element {display: none;}</style>"
|
||||
});
|
||||
|
||||
export(to, exportPaths.resolveExportPaths(html));
|
||||
}
|
||||
|
||||
public static String[] getRedirections() {
|
||||
return new String[]{
|
||||
"network",
|
||||
@ -134,8 +96,6 @@ public class NetworkPageExporter extends FileExporter {
|
||||
}
|
||||
|
||||
private void exportReactRedirects(Path toDirectory) throws IOException {
|
||||
if (config.isTrue(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
exportReactRedirects(toDirectory, files, config, getRedirections());
|
||||
}
|
||||
|
||||
@ -170,7 +130,8 @@ public class NetworkPageExporter extends FileExporter {
|
||||
"sessions",
|
||||
"extensionData?server=" + serverUUID,
|
||||
"retention",
|
||||
"joinAddresses"
|
||||
"joinAddresses",
|
||||
"playersTable"
|
||||
);
|
||||
}
|
||||
|
||||
@ -210,75 +171,6 @@ public class NetworkPageExporter extends FileExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void exportRequiredResources(ExportPaths exportPaths, Path toDirectory) throws IOException {
|
||||
if (config.isFalse(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
exportResources(exportPaths, toDirectory,
|
||||
"./img/Flaticon_circle.png",
|
||||
"./css/sb-admin-2.css",
|
||||
"./css/style.css",
|
||||
"./css/noauth.css",
|
||||
"./vendor/datatables/datatables.min.js",
|
||||
"./vendor/datatables/datatables.min.css",
|
||||
"./vendor/highcharts/modules/map.js",
|
||||
"./vendor/highcharts/mapdata/world.js",
|
||||
"./vendor/highcharts/modules/drilldown.js",
|
||||
"./vendor/highcharts/highcharts.js",
|
||||
"./vendor/highcharts/modules/no-data-to-display.js",
|
||||
"./vendor/masonry/masonry.pkgd.min.js",
|
||||
"./vendor/fontawesome-free/css/all.min.css",
|
||||
"./vendor/fontawesome-free/webfonts/fa-brands-400.eot",
|
||||
"./vendor/fontawesome-free/webfonts/fa-brands-400.ttf",
|
||||
"./vendor/fontawesome-free/webfonts/fa-brands-400.woff",
|
||||
"./vendor/fontawesome-free/webfonts/fa-brands-400.woff2",
|
||||
"./vendor/fontawesome-free/webfonts/fa-regular-400.eot",
|
||||
"./vendor/fontawesome-free/webfonts/fa-regular-400.ttf",
|
||||
"./vendor/fontawesome-free/webfonts/fa-regular-400.woff",
|
||||
"./vendor/fontawesome-free/webfonts/fa-regular-400.woff2",
|
||||
"./vendor/fontawesome-free/webfonts/fa-solid-900.eot",
|
||||
"./vendor/fontawesome-free/webfonts/fa-solid-900.ttf",
|
||||
"./vendor/fontawesome-free/webfonts/fa-solid-900.woff",
|
||||
"./vendor/fontawesome-free/webfonts/fa-solid-900.woff2",
|
||||
"./js/domUtils.js",
|
||||
"./js/sb-admin-2.js",
|
||||
"./js/xmlhttprequests.js",
|
||||
"./js/color-selector.js",
|
||||
"./js/sessionAccordion.js",
|
||||
"./js/pingTable.js",
|
||||
"./js/graphs.js",
|
||||
"./js/network-values.js"
|
||||
);
|
||||
}
|
||||
|
||||
private void exportResources(ExportPaths exportPaths, Path toDirectory, String... resourceNames) throws IOException {
|
||||
for (String resourceName : resourceNames) {
|
||||
String nonRelativePath = toNonRelativePath(resourceName);
|
||||
exportResource(toDirectory, nonRelativePath);
|
||||
exportPaths.put(resourceName, toRelativePathFromRoot(nonRelativePath));
|
||||
}
|
||||
}
|
||||
|
||||
private void exportResource(Path toDirectory, String resourceName) throws IOException {
|
||||
WebResource resource = ResourceService.getInstance().getResource("Plan", resourceName,
|
||||
() -> files.getResourceFromJar("web/" + resourceName).asWebResource());
|
||||
Path to = toDirectory.resolve(resourceName);
|
||||
|
||||
if (resourceName.endsWith(".css") || resourceName.endsWith("color-selector.js")) {
|
||||
export(to, theme.replaceThemeColors(resource.asString()));
|
||||
} else if ("js/network-values.js".equalsIgnoreCase(resourceName) || "js/sessionAccordion.js".equalsIgnoreCase(resourceName)) {
|
||||
String relativePlayerLink = toRelativePathFromRoot("player");
|
||||
String relativeServerLink = toRelativePathFromRoot("server/");
|
||||
export(to, StringUtils.replaceEach(resource.asString(),
|
||||
new String[]{"../player", "./player", "./server/", "server/"},
|
||||
new String[]{relativePlayerLink, relativePlayerLink, relativeServerLink, relativeServerLink}
|
||||
));
|
||||
} else if (Resource.isTextResource(resourceName)) {
|
||||
export(to, resource.asString());
|
||||
} else {
|
||||
export(to, resource);
|
||||
}
|
||||
}
|
||||
|
||||
private String toRelativePathFromRoot(String resourceName) {
|
||||
// Network html is exported at /network//index.html or /server/index.html
|
||||
return "../" + toNonRelativePath(resourceName);
|
||||
|
@ -16,25 +16,16 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.export;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.container.PlayerContainer;
|
||||
import com.djrapitops.plan.delivery.rendering.pages.Page;
|
||||
import com.djrapitops.plan.delivery.rendering.pages.PageFactory;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.RootJSONResolver;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.PlayerFetchQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.storage.file.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -56,25 +47,19 @@ public class PlayerPageExporter extends FileExporter {
|
||||
private final PlanFiles files;
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final PageFactory pageFactory;
|
||||
private final RootJSONResolver jsonHandler;
|
||||
private final Theme theme;
|
||||
|
||||
@Inject
|
||||
public PlayerPageExporter(
|
||||
PlanFiles files,
|
||||
PlanConfig config,
|
||||
DBSystem dbSystem,
|
||||
PageFactory pageFactory,
|
||||
RootJSONResolver jsonHandler,
|
||||
Theme theme
|
||||
RootJSONResolver jsonHandler
|
||||
) {
|
||||
this.files = files;
|
||||
this.config = config;
|
||||
this.dbSystem = dbSystem;
|
||||
this.pageFactory = pageFactory;
|
||||
this.jsonHandler = jsonHandler;
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
public static String[] getRedirections(UUID playerUUID) {
|
||||
@ -106,33 +91,14 @@ public class PlayerPageExporter extends FileExporter {
|
||||
ExportPaths exportPaths = new ExportPaths();
|
||||
exportPaths.put("../network", toRelativePathFromRoot("network"));
|
||||
exportPaths.put("../server/", toRelativePathFromRoot("server"));
|
||||
exportRequiredResources(exportPaths, toDirectory);
|
||||
|
||||
Path playerDirectory = toDirectory.resolve("player/" + toFileName(playerUUID.toString()));
|
||||
exportJSON(exportPaths, playerDirectory, playerUUID);
|
||||
exportHtml(exportPaths, playerDirectory, playerUUID);
|
||||
exportReactRedirects(toDirectory, playerUUID);
|
||||
exportPaths.clear();
|
||||
}
|
||||
|
||||
private void exportHtml(ExportPaths exportPaths, Path playerDirectory, UUID playerUUID) throws IOException {
|
||||
if (config.isFalse(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
Path to = playerDirectory.resolve("index.html");
|
||||
|
||||
try {
|
||||
Database db = dbSystem.getDatabase();
|
||||
PlayerContainer player = db.query(ContainerFetchQueries.fetchPlayerContainer(playerUUID));
|
||||
Page page = pageFactory.playerPage(player);
|
||||
export(to, exportPaths.resolveExportPaths(page.toHtml()));
|
||||
} catch (IllegalStateException notFound) {
|
||||
throw new NotFoundException(notFound.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void exportReactRedirects(Path toDirectory, UUID playerUUID) throws IOException {
|
||||
if (config.isTrue(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
exportReactRedirects(toDirectory, files, config, getRedirections(playerUUID));
|
||||
}
|
||||
|
||||
@ -163,70 +129,6 @@ public class PlayerPageExporter extends FileExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void exportRequiredResources(ExportPaths exportPaths, Path toDirectory) throws IOException {
|
||||
if (config.isFalse(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
// Style
|
||||
exportResources(exportPaths, toDirectory,
|
||||
"../img/Flaticon_circle.png",
|
||||
"../css/sb-admin-2.css",
|
||||
"../css/style.css",
|
||||
"../css/noauth.css",
|
||||
"../vendor/datatables/datatables.min.js",
|
||||
"../vendor/datatables/datatables.min.css",
|
||||
"../vendor/highcharts/modules/map.js",
|
||||
"../vendor/highcharts/mapdata/world.js",
|
||||
"../vendor/highcharts/modules/drilldown.js",
|
||||
"../vendor/highcharts/highcharts.js",
|
||||
"../vendor/highcharts/modules/no-data-to-display.js",
|
||||
"../vendor/fullcalendar/fullcalendar.min.css",
|
||||
"../vendor/momentjs/moment.js",
|
||||
"../vendor/masonry/masonry.pkgd.min.js",
|
||||
"../vendor/fullcalendar/fullcalendar.min.js",
|
||||
"../vendor/fontawesome-free/css/all.min.css",
|
||||
"../vendor/fontawesome-free/webfonts/fa-brands-400.eot",
|
||||
"../vendor/fontawesome-free/webfonts/fa-brands-400.ttf",
|
||||
"../vendor/fontawesome-free/webfonts/fa-brands-400.woff",
|
||||
"../vendor/fontawesome-free/webfonts/fa-brands-400.woff2",
|
||||
"../vendor/fontawesome-free/webfonts/fa-regular-400.eot",
|
||||
"../vendor/fontawesome-free/webfonts/fa-regular-400.ttf",
|
||||
"../vendor/fontawesome-free/webfonts/fa-regular-400.woff",
|
||||
"../vendor/fontawesome-free/webfonts/fa-regular-400.woff2",
|
||||
"../vendor/fontawesome-free/webfonts/fa-solid-900.eot",
|
||||
"../vendor/fontawesome-free/webfonts/fa-solid-900.ttf",
|
||||
"../vendor/fontawesome-free/webfonts/fa-solid-900.woff",
|
||||
"../vendor/fontawesome-free/webfonts/fa-solid-900.woff2",
|
||||
"../js/sb-admin-2.js",
|
||||
"../js/xmlhttprequests.js",
|
||||
"../js/color-selector.js",
|
||||
"../js/sessionAccordion.js",
|
||||
"../js/graphs.js",
|
||||
"../js/player-values.js"
|
||||
);
|
||||
}
|
||||
|
||||
private void exportResources(ExportPaths exportPaths, Path toDirectory, String... resourceNames) throws IOException {
|
||||
for (String resourceName : resourceNames) {
|
||||
String nonRelativePath = toNonRelativePath(resourceName);
|
||||
exportResource(toDirectory, nonRelativePath);
|
||||
exportPaths.put(resourceName, toRelativePathFromRoot(nonRelativePath));
|
||||
}
|
||||
}
|
||||
|
||||
private void exportResource(Path toDirectory, String resourceName) throws IOException {
|
||||
WebResource resource = ResourceService.getInstance().getResource("Plan", resourceName,
|
||||
() -> files.getResourceFromJar("web/" + resourceName).asWebResource());
|
||||
Path to = toDirectory.resolve(resourceName);
|
||||
|
||||
if (resourceName.endsWith(".css") || resourceName.endsWith("color-selector.js")) {
|
||||
export(to, theme.replaceThemeColors(resource.asString()));
|
||||
} else if (Resource.isTextResource(resourceName)) {
|
||||
export(to, resource.asString());
|
||||
} else {
|
||||
export(to, resource);
|
||||
}
|
||||
}
|
||||
|
||||
private String toRelativePathFromRoot(String resourceName) {
|
||||
// Player html is exported at /player/<uuid>/index.html
|
||||
return "../../" + toNonRelativePath(resourceName);
|
||||
|
@ -16,23 +16,16 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.export;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.pages.Page;
|
||||
import com.djrapitops.plan.delivery.rendering.pages.PageFactory;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.RootJSONResolver;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.storage.file.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -53,28 +46,23 @@ public class PlayersPageExporter extends FileExporter {
|
||||
private final PlanFiles files;
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final PageFactory pageFactory;
|
||||
private final RootJSONResolver jsonHandler;
|
||||
private final Theme theme;
|
||||
private final ServerInfo serverInfo;
|
||||
|
||||
private final ExportPaths exportPaths;
|
||||
private static final String PLAYERS_TABLE = "playersTable";
|
||||
|
||||
@Inject
|
||||
public PlayersPageExporter(
|
||||
PlanFiles files,
|
||||
PlanConfig config, DBSystem dbSystem,
|
||||
PageFactory pageFactory,
|
||||
RootJSONResolver jsonHandler,
|
||||
Theme theme,
|
||||
ServerInfo serverInfo
|
||||
) {
|
||||
this.files = files;
|
||||
this.config = config;
|
||||
this.dbSystem = dbSystem;
|
||||
this.pageFactory = pageFactory;
|
||||
this.jsonHandler = jsonHandler;
|
||||
this.theme = theme;
|
||||
this.serverInfo = serverInfo;
|
||||
|
||||
exportPaths = new ExportPaths();
|
||||
@ -85,121 +73,42 @@ public class PlayersPageExporter extends FileExporter {
|
||||
if (dbState == Database.State.CLOSED || dbState == Database.State.CLOSING) return;
|
||||
|
||||
exportPaths.put("href=\"/\"", "href=\"" + toRelativePathFromRoot(serverInfo.getServer().isProxy() ? "network" : "server") + '"');
|
||||
exportRequiredResources(toDirectory);
|
||||
exportJSON(toDirectory);
|
||||
exportHtml(toDirectory);
|
||||
exportReactRedirects(toDirectory);
|
||||
exportPaths.clear();
|
||||
}
|
||||
|
||||
private void exportHtml(Path toDirectory) throws IOException {
|
||||
if (config.isFalse(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
Path to = toDirectory
|
||||
.resolve("players")
|
||||
.resolve("index.html");
|
||||
|
||||
Page page = pageFactory.playersPage();
|
||||
|
||||
// Fixes refreshingJsonRequest ignoring old data of export
|
||||
String html = StringUtils.replaceEach(page.toHtml(),
|
||||
new String[]{
|
||||
"}, 'playerlist', true);",
|
||||
"<head>"
|
||||
},
|
||||
new String[]{
|
||||
"}, 'playerlist');",
|
||||
"<head><style>.refresh-element {display: none;}</style>"
|
||||
});
|
||||
|
||||
export(to, exportPaths.resolveExportPaths(html));
|
||||
}
|
||||
|
||||
private void exportReactRedirects(Path toDirectory) throws IOException {
|
||||
if (config.isTrue(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
String[] redirections = {"players"};
|
||||
exportReactRedirects(toDirectory, files, config, redirections);
|
||||
}
|
||||
|
||||
private void exportJSON(Path toDirectory) throws IOException {
|
||||
Response response = getJSONResponse("players")
|
||||
Response response = getJSONResponse()
|
||||
.orElseThrow(() -> new NotFoundException("players page was not properly exported: not found"));
|
||||
|
||||
String jsonResourceName = toFileName(toJSONResourceName("players")) + ".json";
|
||||
String jsonResourceName = toFileName(toJSONResourceName()) + ".json";
|
||||
|
||||
export(toDirectory.resolve("data").resolve(jsonResourceName),
|
||||
// Replace ../player in urls to fix player page links
|
||||
StringUtils.replace(response.getAsString(), "../player", toRelativePathFromRoot("player"))
|
||||
);
|
||||
exportPaths.put("./v1/players", toRelativePathFromRoot("data/" + jsonResourceName));
|
||||
exportPaths.put("./v1/" + PLAYERS_TABLE, toRelativePathFromRoot("data/" + jsonResourceName));
|
||||
}
|
||||
|
||||
private String toJSONResourceName(String resource) {
|
||||
return StringUtils.replaceEach(resource, new String[]{"?", "&", "type=", "server="}, new String[]{"-", "_", "", ""});
|
||||
private String toJSONResourceName() {
|
||||
return StringUtils.replaceEach(PLAYERS_TABLE, new String[]{"?", "&", "type=", "server="}, new String[]{"-", "_", "", ""});
|
||||
}
|
||||
|
||||
private Optional<Response> getJSONResponse(String resource) {
|
||||
private Optional<Response> getJSONResponse() {
|
||||
try {
|
||||
return jsonHandler.getResolver().resolve(new Request("GET", "/v1/" + resource, null, Collections.emptyMap()));
|
||||
return jsonHandler.getResolver().resolve(new Request("GET", "/v1/" + PLAYERS_TABLE, null, Collections.emptyMap()));
|
||||
} catch (WebUserAuthException e) {
|
||||
// The rest of the exceptions should not be thrown
|
||||
throw new IllegalStateException("Unexpected exception thrown: " + e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void exportRequiredResources(Path toDirectory) throws IOException {
|
||||
if (config.isFalse(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
// Style
|
||||
exportResources(toDirectory,
|
||||
"img/Flaticon_circle.png",
|
||||
"css/sb-admin-2.css",
|
||||
"css/style.css",
|
||||
"css/noauth.css",
|
||||
"vendor/datatables/datatables.min.js",
|
||||
"vendor/datatables/datatables.min.css",
|
||||
"vendor/fontawesome-free/css/all.min.css",
|
||||
"vendor/fontawesome-free/webfonts/fa-brands-400.eot",
|
||||
"vendor/fontawesome-free/webfonts/fa-brands-400.ttf",
|
||||
"vendor/fontawesome-free/webfonts/fa-brands-400.woff",
|
||||
"vendor/fontawesome-free/webfonts/fa-brands-400.woff2",
|
||||
"vendor/fontawesome-free/webfonts/fa-regular-400.eot",
|
||||
"vendor/fontawesome-free/webfonts/fa-regular-400.ttf",
|
||||
"vendor/fontawesome-free/webfonts/fa-regular-400.woff",
|
||||
"vendor/fontawesome-free/webfonts/fa-regular-400.woff2",
|
||||
"vendor/fontawesome-free/webfonts/fa-solid-900.eot",
|
||||
"vendor/fontawesome-free/webfonts/fa-solid-900.ttf",
|
||||
"vendor/fontawesome-free/webfonts/fa-solid-900.woff",
|
||||
"vendor/fontawesome-free/webfonts/fa-solid-900.woff2",
|
||||
"js/sb-admin-2.js",
|
||||
"js/xmlhttprequests.js",
|
||||
"js/color-selector.js"
|
||||
);
|
||||
}
|
||||
|
||||
private void exportResources(Path toDirectory, String... resourceNames) throws IOException {
|
||||
for (String resourceName : resourceNames) {
|
||||
exportResource(toDirectory, resourceName);
|
||||
}
|
||||
}
|
||||
|
||||
private void exportResource(Path toDirectory, String resourceName) throws IOException {
|
||||
WebResource resource = ResourceService.getInstance().getResource("Plan", resourceName,
|
||||
() -> files.getResourceFromJar("web/" + resourceName).asWebResource());
|
||||
Path to = toDirectory.resolve(resourceName);
|
||||
|
||||
if (resourceName.endsWith(".css") || resourceName.endsWith("color-selector.js")) {
|
||||
export(to, theme.replaceThemeColors(resource.asString()));
|
||||
} else if (Resource.isTextResource(resourceName)) {
|
||||
export(to, resource.asString());
|
||||
} else {
|
||||
export(to, resource);
|
||||
}
|
||||
|
||||
exportPaths.put(resourceName, toRelativePathFromRoot(resourceName));
|
||||
}
|
||||
|
||||
private String toRelativePathFromRoot(String resourceName) {
|
||||
// Players html is exported at /players/index.html or /server/index.html
|
||||
return "../" + toNonRelativePath(resourceName);
|
||||
|
@ -84,6 +84,7 @@ public class ReactExporter extends FileExporter {
|
||||
exportJson(toDirectory, "metadata");
|
||||
exportJson(toDirectory, "version");
|
||||
exportJson(toDirectory, "networkMetadata");
|
||||
exportJson(toDirectory, "preferences");
|
||||
}
|
||||
|
||||
private void exportLocaleJson(Path toDirectory) throws IOException {
|
||||
|
@ -16,25 +16,18 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.export;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.pages.Page;
|
||||
import com.djrapitops.plan.delivery.rendering.pages.PageFactory;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.RootJSONResolver;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.storage.file.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
@ -55,10 +48,8 @@ public class ServerPageExporter extends FileExporter {
|
||||
|
||||
private final PlanFiles files;
|
||||
private final PlanConfig config;
|
||||
private final PageFactory pageFactory;
|
||||
private final DBSystem dbSystem;
|
||||
private final RootJSONResolver jsonHandler;
|
||||
private final Theme theme;
|
||||
private final ServerInfo serverInfo;
|
||||
|
||||
private final ExportPaths exportPaths;
|
||||
@ -67,18 +58,14 @@ public class ServerPageExporter extends FileExporter {
|
||||
public ServerPageExporter(
|
||||
PlanFiles files,
|
||||
PlanConfig config,
|
||||
PageFactory pageFactory,
|
||||
DBSystem dbSystem,
|
||||
RootJSONResolver jsonHandler,
|
||||
Theme theme,
|
||||
ServerInfo serverInfo // To know if current server is a Proxy
|
||||
) {
|
||||
this.files = files;
|
||||
this.config = config;
|
||||
this.pageFactory = pageFactory;
|
||||
this.dbSystem = dbSystem;
|
||||
this.jsonHandler = jsonHandler;
|
||||
this.theme = theme;
|
||||
this.serverInfo = serverInfo;
|
||||
|
||||
exportPaths = new ExportPaths();
|
||||
@ -97,41 +84,11 @@ public class ServerPageExporter extends FileExporter {
|
||||
if (dbState == Database.State.CLOSED || dbState == Database.State.CLOSING) return;
|
||||
|
||||
exportPaths.put("../network", toRelativePathFromRoot("network"));
|
||||
exportRequiredResources(toDirectory);
|
||||
exportJSON(toDirectory, server);
|
||||
exportHtml(toDirectory, server);
|
||||
exportReactRedirects(toDirectory, server.getUuid());
|
||||
exportPaths.clear();
|
||||
}
|
||||
|
||||
private void exportHtml(Path toDirectory, Server server) throws IOException {
|
||||
if (config.isFalse(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
ServerUUID serverUUID = server.getUuid();
|
||||
Path to = toDirectory
|
||||
.resolve(serverInfo.getServer().isProxy() ? "server/" + toFileName(server.getName()) : "server")
|
||||
.resolve("index.html");
|
||||
|
||||
Page page = pageFactory.serverPage(serverUUID);
|
||||
|
||||
// Fixes refreshingJsonRequest ignoring old data of export
|
||||
String html = StringUtils.replaceEach(page.toHtml(),
|
||||
new String[]{
|
||||
"loadOptimizedPerformanceGraph, 'performance', true);",
|
||||
"loadserverCalendar, 'online-activity-overview', true);",
|
||||
"}, 'playerlist', true);",
|
||||
"<head>"
|
||||
},
|
||||
new String[]{
|
||||
"loadOptimizedPerformanceGraph, 'performance');",
|
||||
"loadserverCalendar, 'online-activity-overview');",
|
||||
"}, 'playerlist');",
|
||||
"<head><style>.refresh-element {display: none;}</style>"
|
||||
});
|
||||
|
||||
export(to, exportPaths.resolveExportPaths(html));
|
||||
}
|
||||
|
||||
public static String[] getRedirections(ServerUUID serverUUID) {
|
||||
String server = "server/";
|
||||
return new String[]{
|
||||
@ -151,8 +108,6 @@ public class ServerPageExporter extends FileExporter {
|
||||
}
|
||||
|
||||
private void exportReactRedirects(Path toDirectory, ServerUUID serverUUID) throws IOException {
|
||||
if (config.isTrue(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
exportReactRedirects(toDirectory, files, config, getRedirections(serverUUID));
|
||||
}
|
||||
|
||||
@ -186,7 +141,7 @@ public class ServerPageExporter extends FileExporter {
|
||||
"graph?type=joinAddressByDay&server=" + serverUUID,
|
||||
"graph?type=serverCalendar&server=" + serverUUID,
|
||||
"graph?type=punchCard&server=" + serverUUID,
|
||||
"players?server=" + serverUUID,
|
||||
"playersTable?server=" + serverUUID,
|
||||
"kills?server=" + serverUUID,
|
||||
"pingTable?server=" + serverUUID,
|
||||
"sessions?server=" + serverUUID,
|
||||
@ -233,71 +188,6 @@ public class ServerPageExporter extends FileExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void exportRequiredResources(Path toDirectory) throws IOException {
|
||||
if (config.isFalse(PluginSettings.LEGACY_FRONTEND)) return;
|
||||
|
||||
// Style
|
||||
exportResources(toDirectory,
|
||||
"../img/Flaticon_circle.png",
|
||||
"../css/sb-admin-2.css",
|
||||
"../css/style.css",
|
||||
"../vendor/datatables/datatables.min.js",
|
||||
"../vendor/datatables/datatables.min.css",
|
||||
"../vendor/highcharts/modules/map.js",
|
||||
"../vendor/highcharts/mapdata/world.js",
|
||||
"../vendor/highcharts/modules/drilldown.js",
|
||||
"../vendor/highcharts/highcharts.js",
|
||||
"../vendor/highcharts/modules/no-data-to-display.js",
|
||||
"../vendor/fullcalendar/fullcalendar.min.css",
|
||||
"../vendor/momentjs/moment.js",
|
||||
"../vendor/masonry/masonry.pkgd.min.js",
|
||||
"../vendor/fullcalendar/fullcalendar.min.js",
|
||||
"../vendor/fontawesome-free/css/all.min.css",
|
||||
"../vendor/fontawesome-free/webfonts/fa-brands-400.eot",
|
||||
"../vendor/fontawesome-free/webfonts/fa-brands-400.ttf",
|
||||
"../vendor/fontawesome-free/webfonts/fa-brands-400.woff",
|
||||
"../vendor/fontawesome-free/webfonts/fa-brands-400.woff2",
|
||||
"../vendor/fontawesome-free/webfonts/fa-regular-400.eot",
|
||||
"../vendor/fontawesome-free/webfonts/fa-regular-400.ttf",
|
||||
"../vendor/fontawesome-free/webfonts/fa-regular-400.woff",
|
||||
"../vendor/fontawesome-free/webfonts/fa-regular-400.woff2",
|
||||
"../vendor/fontawesome-free/webfonts/fa-solid-900.eot",
|
||||
"../vendor/fontawesome-free/webfonts/fa-solid-900.ttf",
|
||||
"../vendor/fontawesome-free/webfonts/fa-solid-900.woff",
|
||||
"../vendor/fontawesome-free/webfonts/fa-solid-900.woff2",
|
||||
"../js/domUtils.js",
|
||||
"../js/sb-admin-2.js",
|
||||
"../js/xmlhttprequests.js",
|
||||
"../js/color-selector.js",
|
||||
"../js/sessionAccordion.js",
|
||||
"../js/pingTable.js",
|
||||
"../js/graphs.js",
|
||||
"../js/server-values.js"
|
||||
);
|
||||
}
|
||||
|
||||
private void exportResources(Path toDirectory, String... resourceNames) throws IOException {
|
||||
for (String resourceName : resourceNames) {
|
||||
String nonRelativePath = toNonRelativePath(resourceName);
|
||||
exportResource(toDirectory, nonRelativePath);
|
||||
exportPaths.put(resourceName, toRelativePathFromRoot(nonRelativePath));
|
||||
}
|
||||
}
|
||||
|
||||
private void exportResource(Path toDirectory, String resourceName) throws IOException {
|
||||
WebResource resource = ResourceService.getInstance().getResource("Plan", resourceName,
|
||||
() -> files.getResourceFromJar("web/" + resourceName).asWebResource());
|
||||
Path to = toDirectory.resolve(resourceName);
|
||||
|
||||
if (resourceName.endsWith(".css") || resourceName.endsWith("color-selector.js")) {
|
||||
export(to, theme.replaceThemeColors(resource.asString()));
|
||||
} else if (Resource.isTextResource(resourceName)) {
|
||||
export(to, resource.asString());
|
||||
} else {
|
||||
export(to, resource);
|
||||
}
|
||||
}
|
||||
|
||||
private String toRelativePathFromRoot(String resourceName) {
|
||||
// Server html is exported at /server/<name>/index.html or /server/index.html
|
||||
return (serverInfo.getServer().isProxy() ? "../../" : "../") + toNonRelativePath(resourceName);
|
||||
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.formatting;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.DateHolder;
|
||||
import com.djrapitops.plan.delivery.formatting.time.*;
|
||||
import com.djrapitops.plan.extension.FormatType;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
|
||||
@ -160,6 +161,20 @@ public class Formatters {
|
||||
return value -> byteSizeFormatter.apply((double) value);
|
||||
}
|
||||
|
||||
public Formatter<Long> getNumberFormatter(FormatType type) {
|
||||
switch (type) {
|
||||
case DATE_SECOND:
|
||||
return secondLong();
|
||||
case DATE_YEAR:
|
||||
return yearLong();
|
||||
case TIME_MILLISECONDS:
|
||||
return timeAmount();
|
||||
case NONE:
|
||||
default:
|
||||
return Object::toString;
|
||||
}
|
||||
}
|
||||
|
||||
static class Holder {
|
||||
static final AtomicReference<Formatters> formatters = new AtomicReference<>();
|
||||
|
||||
|
@ -113,31 +113,12 @@ public class Contributors {
|
||||
// Static method class
|
||||
}
|
||||
|
||||
public static String generateContributorHtml() {
|
||||
int estimatedLength = CONTRIBUTOR_ARRAY.length * 40 + 50;
|
||||
StringBuilder html = new StringBuilder(estimatedLength);
|
||||
Arrays.stream(CONTRIBUTOR_ARRAY).sorted()
|
||||
.forEach(contributor -> contributor.appendHtml(html));
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
public static List<Contributor> getContributors() {
|
||||
return Arrays.stream(CONTRIBUTOR_ARRAY).sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
enum For {
|
||||
CODE("fa-code"),
|
||||
LANG("fa-language");
|
||||
|
||||
private final String icon;
|
||||
|
||||
For(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
String toHtml() {
|
||||
return " <i class=\"fa fa-fw " + icon + "\"></i>";
|
||||
}
|
||||
CODE, LANG
|
||||
}
|
||||
|
||||
private static class Contributor implements Comparable<Contributor> {
|
||||
@ -149,15 +130,6 @@ public class Contributors {
|
||||
this.contributed = contributed;
|
||||
}
|
||||
|
||||
public void appendHtml(StringBuilder html) {
|
||||
html.append("<li class=\"col-4\">")
|
||||
.append(name);
|
||||
for (For contribution : contributed) {
|
||||
html.append(contribution.toHtml());
|
||||
}
|
||||
html.append("</li>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -18,7 +18,6 @@ package com.djrapitops.plan.delivery.rendering.html;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.StringSubstitutor;
|
||||
import org.apache.commons.text.TextStringBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URLEncoder;
|
||||
@ -31,39 +30,8 @@ import java.util.Map;
|
||||
*/
|
||||
public enum Html {
|
||||
|
||||
COLOR_0("<span class=\"black\">"),
|
||||
COLOR_1("<span class=\"darkblue\">"),
|
||||
COLOR_2("<span class=\"darkgreen\">"),
|
||||
COLOR_3("<span class=\"darkaqua\">"),
|
||||
COLOR_4("<span class=\"darkred\">"),
|
||||
COLOR_5("<span class=\"darkpurple\">"),
|
||||
COLOR_6("<span class=\"gold\">"),
|
||||
COLOR_7("<span class=\"gray\">"),
|
||||
COLOR_8("<span class=\"darkgray\">"),
|
||||
COLOR_9("<span class=\"blue\">"),
|
||||
COLOR_A("<span class=\"green\">"),
|
||||
COLOR_B("<span class=\"aqua\">"),
|
||||
COLOR_C("<span class=\"red\">"),
|
||||
COLOR_D("<span class=\"pink\">"),
|
||||
COLOR_E("<span class=\"yellow\">"),
|
||||
COLOR_F("<span class=\"white\">"),
|
||||
|
||||
SPAN("${0}</span>"),
|
||||
LINK("<a class=\"link\" href=\"${0}\">${1}</a>"),
|
||||
LINK_EXTERNAL("<a class=\"link\" rel=\"noopener noreferrer\" target=\"_blank\" href=\"${0}\">${1}</a>"),
|
||||
|
||||
BACK_BUTTON_NETWORK("<a class=\"btn bg-plan btn-icon-split\" href=\"../network\">" +
|
||||
"<span class=\"icon text-white-50\">" +
|
||||
"<i class=\"fas fa-fw fa-arrow-left\"></i><i class=\"fas fa-fw fa-cloud\"></i>" +
|
||||
"</span>" +
|
||||
"<span class=\"text\">Network page</span>" +
|
||||
"</a>"),
|
||||
BACK_BUTTON_SERVER("<a class=\"btn bg-plan btn-icon-split\" href=\"../server/\">" +
|
||||
"<span class=\"icon text-white-50\">" +
|
||||
"<i class=\"fas fa-fw fa-arrow-left\"></i><i class=\"fas fa-fw fa-server\"></i>" +
|
||||
"</span>" +
|
||||
"<span class=\"text\">Server page</span>" +
|
||||
"</a>");
|
||||
LINK_EXTERNAL("<a class=\"link\" rel=\"noopener noreferrer\" target=\"_blank\" href=\"${0}\">${1}</a>");
|
||||
|
||||
private final String html;
|
||||
|
||||
@ -71,105 +39,6 @@ public enum Html {
|
||||
this.html = html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes Minecraft color codes to HTML span elements with correct color class assignments.
|
||||
*
|
||||
* @param string String to replace Minecraft color codes from
|
||||
* @return String with span elements.
|
||||
*/
|
||||
public static String swapColorCodesToSpan(String string) {
|
||||
return swapColorCodesToSpan(string, string.contains("§") ? "§" : "§");
|
||||
}
|
||||
|
||||
private static String swapColorCodesToSpan(String string, String splitWith) {
|
||||
if (string == null) return null;
|
||||
if (!string.contains(splitWith)) return string;
|
||||
|
||||
Html[] replacer = new Html[]{
|
||||
Html.COLOR_0, Html.COLOR_1, Html.COLOR_2, Html.COLOR_3,
|
||||
Html.COLOR_4, Html.COLOR_5, Html.COLOR_6, Html.COLOR_7,
|
||||
Html.COLOR_8, Html.COLOR_9, Html.COLOR_A, Html.COLOR_B,
|
||||
Html.COLOR_C, Html.COLOR_D, Html.COLOR_E, Html.COLOR_F
|
||||
};
|
||||
Map<Character, String> colorMap = new HashMap<>();
|
||||
|
||||
for (Html html : replacer) {
|
||||
colorMap.put(Character.toLowerCase(html.name().charAt(6)), html.create());
|
||||
colorMap.put('k', "");
|
||||
colorMap.put('l', "");
|
||||
colorMap.put('m', "");
|
||||
colorMap.put('n', "");
|
||||
colorMap.put('o', "");
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder(string.length());
|
||||
String[] split = string.split(splitWith);
|
||||
// Skip first part if it does not start with §
|
||||
boolean skipFirst = !string.startsWith(splitWith);
|
||||
|
||||
int placedSpans = 0;
|
||||
int hexNumbersLeft = 0;
|
||||
|
||||
for (String part : split) {
|
||||
if (part.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (skipFirst) {
|
||||
result.append(part);
|
||||
skipFirst = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
char colorChar = part.charAt(0);
|
||||
// Deal with hex colors
|
||||
if (hexNumbersLeft > 1) {
|
||||
result.append(colorChar);
|
||||
hexNumbersLeft--;
|
||||
continue;
|
||||
} else if (hexNumbersLeft == 1) {
|
||||
result.append(colorChar).append(";\">").append(part.substring(1));
|
||||
hexNumbersLeft--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (colorChar == 'r') {
|
||||
appendEndTags(result, placedSpans);
|
||||
placedSpans = 0; // Colors were reset
|
||||
result.append(part.substring(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Deal with hex colors
|
||||
if (colorChar == 'x') {
|
||||
result.append("<span style=\"color: #");
|
||||
hexNumbersLeft = 6;
|
||||
placedSpans++;
|
||||
continue;
|
||||
}
|
||||
|
||||
String replacement = colorMap.get(colorChar);
|
||||
if (replacement != null) {
|
||||
result.append(replacement).append(part.substring(1));
|
||||
|
||||
if (!replacement.isEmpty()) {
|
||||
placedSpans++;
|
||||
}
|
||||
} else {
|
||||
result.append(part);
|
||||
}
|
||||
}
|
||||
|
||||
appendEndTags(result, placedSpans);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String separateWithDots(String... elements) {
|
||||
TextStringBuilder builder = new TextStringBuilder();
|
||||
builder.appendWithSeparators(elements, " • ");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The HTML String
|
||||
*/
|
||||
@ -177,10 +46,6 @@ public enum Html {
|
||||
return html;
|
||||
}
|
||||
|
||||
private static void appendEndTags(StringBuilder result, int placedSpans) {
|
||||
result.append("</span>".repeat(Math.max(0, placedSpans)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param replacements The replacement Strings
|
||||
* @return The HTML String
|
||||
|
@ -1,115 +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.html.structure;
|
||||
|
||||
import com.djrapitops.plan.extension.table.Table;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @deprecated Table html generation is to be done in frontend in the future.
|
||||
*/
|
||||
@Deprecated(since = "5.5")
|
||||
public class DynamicHtmlTable implements HtmlTable {
|
||||
private final Header[] headers;
|
||||
private final List<Object[]> rows;
|
||||
|
||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM d yyyy, HH:mm");
|
||||
|
||||
public DynamicHtmlTable(Header[] headers, List<Object[]> rows) {
|
||||
this.headers = headers;
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
public DynamicHtmlTable(Table table) {
|
||||
this(HtmlTable.mapToHeaders(table), table.getRows());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
return "<table class=\"table table-bordered table-striped table-hover player-plugin-table\" style=\"width: 100%\">" +
|
||||
buildTableHeader() +
|
||||
buildTableBody() +
|
||||
"</table>";
|
||||
}
|
||||
|
||||
private String buildTableHeader() {
|
||||
StringBuilder builtHeader = new StringBuilder("<thead><tr>");
|
||||
for (Header header : headers) {
|
||||
builtHeader.append("<th>")
|
||||
.append(header.getIcon().toHtml())
|
||||
.append(' ')
|
||||
.append(header.getText())
|
||||
.append("</th>");
|
||||
}
|
||||
builtHeader.append("</tr></thead>");
|
||||
return builtHeader.toString();
|
||||
}
|
||||
|
||||
private String buildTableBody() {
|
||||
StringBuilder builtBody = new StringBuilder();
|
||||
builtBody.append("<tbody>");
|
||||
if (rows.isEmpty()) {
|
||||
appendRow(builtBody, "No Data");
|
||||
}
|
||||
for (Object[] row : rows) {
|
||||
appendRow(builtBody, row);
|
||||
}
|
||||
return builtBody.append("</tbody>").toString();
|
||||
}
|
||||
|
||||
private void appendRow(StringBuilder builtBody, Object... row) {
|
||||
int headerLength = row.length - 1;
|
||||
builtBody.append("<tr>");
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
try {
|
||||
if (i > headerLength) {
|
||||
builtBody.append("<td>-");
|
||||
} else {
|
||||
appendValue(builtBody, row[i]);
|
||||
}
|
||||
builtBody.append("</td>");
|
||||
} catch (ClassCastException | ArrayIndexOutOfBoundsException e) {
|
||||
throw new IllegalStateException("Invalid formatter given at index " + i + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
builtBody.append("</tr>");
|
||||
}
|
||||
|
||||
private void appendValue(StringBuilder builtBody, Object value) {
|
||||
String valueString = value != null ? value.toString() : "-";
|
||||
try {
|
||||
long time = dateFormat.parse(valueString).getTime();
|
||||
builtBody.append("<td data-order=\"").append(time).append("\">").append(valueString);
|
||||
} catch (ParseException e) {
|
||||
if (NumberUtils.isParsable(valueString)) {
|
||||
builtBody.append("<td data-order=\"").append(valueString).append("\">").append(valueString);
|
||||
} else {
|
||||
// Removes non numbers from the value
|
||||
String numbersInValue = valueString.replaceAll("\\D", "");
|
||||
if (!numbersInValue.isEmpty()) {
|
||||
builtBody.append("<td data-order=\"").append(numbersInValue).append("\">").append(valueString);
|
||||
} else {
|
||||
builtBody.append("<td>").append(valueString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,122 +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.html.structure;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.extension.TableCellDto;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Html;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Color;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.extension.table.Table;
|
||||
import com.djrapitops.plan.extension.table.TableColumnFormat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @deprecated Table html generation is to be done in frontend in the future.
|
||||
*/
|
||||
@Deprecated(since = "5.5")
|
||||
public interface HtmlTable {
|
||||
|
||||
static HtmlTable fromExtensionTable(Table table, com.djrapitops.plan.extension.icon.Color tableColor) {
|
||||
return fromExtensionTable(table, Color.getByName(tableColor.name()).orElse(Color.NONE));
|
||||
}
|
||||
|
||||
static HtmlTable fromExtensionTable(Table table, Color tableColor) {
|
||||
if (table.getRows().size() > 10) {
|
||||
return new DynamicHtmlTable(table);
|
||||
} else {
|
||||
return new HtmlTableWithColoredHeader(table, tableColor);
|
||||
}
|
||||
}
|
||||
|
||||
static Header[] mapToHeaders(Table table) {
|
||||
ArrayList<Header> headers = new ArrayList<>();
|
||||
|
||||
com.djrapitops.plan.extension.icon.Icon[] icons = table.getIcons();
|
||||
String[] columns = table.getColumns();
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
String column = columns[i];
|
||||
if (column == null) {
|
||||
break;
|
||||
}
|
||||
headers.add(new Header(Icon.fromExtensionIcon(icons[i]), column));
|
||||
}
|
||||
|
||||
return headers.toArray(new Header[0]);
|
||||
}
|
||||
|
||||
static List<TableCellDto[]> mapToRows(List<Object[]> rows, TableColumnFormat[] tableColumnFormats) {
|
||||
return rows.stream()
|
||||
.map(row -> {
|
||||
List<TableCellDto> mapped = new ArrayList<>(row.length);
|
||||
for (int i = 0; i < row.length; i++) {
|
||||
Object value = row[i];
|
||||
if (value == null) {
|
||||
mapped.add(null);
|
||||
} else {
|
||||
TableColumnFormat format = tableColumnFormats[i];
|
||||
mapped.add(new TableCellDto(applyFormat(format, value), value));
|
||||
}
|
||||
}
|
||||
return mapped.toArray(new TableCellDto[0]);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
static String applyFormat(TableColumnFormat format, Object value) {
|
||||
try {
|
||||
switch (format) {
|
||||
case TIME_MILLISECONDS:
|
||||
return Formatters.getInstance().timeAmount().apply(Long.parseLong(value.toString()));
|
||||
case DATE_YEAR:
|
||||
return Formatters.getInstance().yearLong().apply(Long.parseLong(value.toString()));
|
||||
case DATE_SECOND:
|
||||
return Formatters.getInstance().secondLong().apply(Long.parseLong(value.toString()));
|
||||
case PLAYER_NAME:
|
||||
return Html.LINK.create("../player/" + Html.encodeToURL(Html.swapColorCodesToSpan(value.toString())));
|
||||
default:
|
||||
return Html.swapColorCodesToSpan(value.toString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return Objects.toString(value);
|
||||
}
|
||||
}
|
||||
|
||||
String toHtml();
|
||||
|
||||
class Header {
|
||||
private final Icon icon;
|
||||
private final String text;
|
||||
|
||||
public Header(Icon icon, String text) {
|
||||
this.icon = icon;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public Icon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,99 +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.html.structure;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.extension.TableCellDto;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Color;
|
||||
import com.djrapitops.plan.extension.table.Table;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @deprecated Table html generation is to be done in frontend in the future.
|
||||
*/
|
||||
@Deprecated(since = "5.5")
|
||||
public class HtmlTableWithColoredHeader implements HtmlTable {
|
||||
private final Header[] headers;
|
||||
private final Color headerColor;
|
||||
private final List<TableCellDto[]> rows;
|
||||
|
||||
public HtmlTableWithColoredHeader(Header[] headers, Color headerColor, List<TableCellDto[]> rows) {
|
||||
this.headers = headers;
|
||||
this.headerColor = headerColor;
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
public HtmlTableWithColoredHeader(Table table, Color headerColor) {
|
||||
this(HtmlTable.mapToHeaders(table), headerColor, HtmlTable.mapToRows(table.getRows(), table.getTableColumnFormats()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
return "<div class=\"scrollbar\">" +
|
||||
"<table class=\"table table-striped\">" +
|
||||
buildTableHeader() +
|
||||
buildTableBody() +
|
||||
"</table>" +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
private String buildTableHeader() {
|
||||
StringBuilder builtHeader = new StringBuilder("<thead class=\"" + headerColor.getBackgroundColorClass() + "\"><tr>");
|
||||
for (Header header : headers) {
|
||||
builtHeader.append("<th>")
|
||||
.append(header.getIcon().toHtml())
|
||||
.append(' ')
|
||||
.append(header.getText())
|
||||
.append("</th>");
|
||||
}
|
||||
builtHeader.append("</tr></thead>");
|
||||
return builtHeader.toString();
|
||||
}
|
||||
|
||||
private String buildTableBody() {
|
||||
StringBuilder builtBody = new StringBuilder();
|
||||
builtBody.append("<tbody>");
|
||||
if (rows.isEmpty()) {
|
||||
appendRow(builtBody, new TableCellDto("No Data"));
|
||||
}
|
||||
for (TableCellDto[] row : rows) {
|
||||
appendRow(builtBody, row);
|
||||
}
|
||||
return builtBody.append("</tbody>").toString();
|
||||
}
|
||||
|
||||
private void appendRow(StringBuilder builtBody, TableCellDto... row) {
|
||||
int headerLength = row.length - 1;
|
||||
builtBody.append("<tr>");
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
try {
|
||||
if (i > headerLength) {
|
||||
builtBody.append("<td>-");
|
||||
} else {
|
||||
builtBody.append("<td>");
|
||||
TableCellDto cell = row[i];
|
||||
builtBody.append(cell != null ? cell.getValue() : '-');
|
||||
}
|
||||
builtBody.append("</td>");
|
||||
} catch (ClassCastException | ArrayIndexOutOfBoundsException e) {
|
||||
throw new IllegalStateException("Invalid formatter given at index " + i + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
builtBody.append("</tr>");
|
||||
}
|
||||
|
||||
}
|
@ -1,78 +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.html.structure;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Html utility for creating navigation link html.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class NavLink {
|
||||
|
||||
private final Icon icon;
|
||||
private final String tabID;
|
||||
private final String tabName;
|
||||
private final boolean collapsed;
|
||||
|
||||
private NavLink(Icon icon, String tabID, String tabName, boolean collapsed) {
|
||||
this.icon = icon;
|
||||
this.tabID = tabID;
|
||||
this.tabName = tabName;
|
||||
this.collapsed = collapsed;
|
||||
}
|
||||
|
||||
public static NavLink main(Icon icon, String tabName) {
|
||||
return new NavLink(icon, null, tabName, false);
|
||||
}
|
||||
|
||||
public static NavLink main(Icon icon, String tabID, String tabName) {
|
||||
return new NavLink(icon, tabID, tabName, false);
|
||||
}
|
||||
|
||||
public static NavLink collapsed(Icon icon, String tabName) {
|
||||
return new NavLink(icon, null, tabName, true);
|
||||
}
|
||||
|
||||
public static NavLink collapsed(Icon icon, String tabID, String tabName) {
|
||||
return new NavLink(icon, tabID, tabName, true);
|
||||
}
|
||||
|
||||
public static String format(String id) {
|
||||
return StringUtils.replaceChars(StringUtils.lowerCase(id), ' ', '-');
|
||||
}
|
||||
|
||||
public String toHtml() {
|
||||
String usedId = getUsedTabId();
|
||||
if (collapsed) {
|
||||
return "<a class=\"collapse-item nav-button\" href=\"#tab-" + usedId + "\">" +
|
||||
icon.toHtml() + ' ' +
|
||||
tabName + "</a>";
|
||||
}
|
||||
return "<li class=\"nav-item nav-button\">" +
|
||||
"<a class=\"nav-link\" href=\"#tab-" + usedId + "\">" +
|
||||
icon.toHtml() +
|
||||
"<span>" + tabName + "</span></a>" +
|
||||
"</li>";
|
||||
}
|
||||
|
||||
private String getUsedTabId() {
|
||||
return format(tabID != null ? tabID : tabName);
|
||||
}
|
||||
}
|
@ -1,92 +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.html.structure;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
|
||||
/**
|
||||
* Represents a structural HTML element that has Tabs on the top.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class TabsElement {
|
||||
|
||||
private final int pluginId;
|
||||
private final Tab[] tabs;
|
||||
|
||||
public TabsElement(int pluginId, Tab... tabs) {
|
||||
this.pluginId = pluginId;
|
||||
this.tabs = tabs;
|
||||
}
|
||||
|
||||
public String toHtmlFull() {
|
||||
String[] navAndContent = toHtml();
|
||||
return navAndContent[0] + navAndContent[1];
|
||||
}
|
||||
|
||||
public String[] toHtml() {
|
||||
StringBuilder nav = new StringBuilder();
|
||||
StringBuilder content = new StringBuilder();
|
||||
|
||||
nav.append("<ul class=\"nav nav-tabs tab-nav-right\" role=\"tablist\">");
|
||||
content.append("<div class=\"tab-content\">");
|
||||
boolean first = true;
|
||||
for (Tab tab : tabs) {
|
||||
String id = tab.getId(pluginId);
|
||||
String navHtml = tab.getNavHtml();
|
||||
String contentHtml = tab.getContentHtml();
|
||||
|
||||
nav.append("<li role=\"presentation\" class=\"nav-item col-black\"")
|
||||
.append("><a href=\"#").append(id).append("\" class=\"nav-link col-black").append(first ? " active" : "").append('"').append(" data-bs-toggle=\"tab\">")
|
||||
.append(navHtml).append("</a></li>");
|
||||
content.append("<div role=\"tabpanel\" class=\"tab-pane fade").append(first ? " in active show" : "")
|
||||
.append("\" id=\"").append(id).append("\">")
|
||||
.append(contentHtml).append("</div>");
|
||||
first = false;
|
||||
}
|
||||
content.append("</div>");
|
||||
nav.append("</ul>");
|
||||
|
||||
return new String[]{nav.toString(), content.toString()};
|
||||
}
|
||||
|
||||
public static class Tab {
|
||||
|
||||
private final Icon icon;
|
||||
private final String title;
|
||||
private final String contentHtml;
|
||||
|
||||
public Tab(Icon icon, String title, String contentHtml) {
|
||||
this.icon = icon;
|
||||
this.title = title;
|
||||
this.contentHtml = contentHtml;
|
||||
}
|
||||
|
||||
public String getNavHtml() {
|
||||
return icon.toHtml() + ' ' + title;
|
||||
}
|
||||
|
||||
public String getContentHtml() {
|
||||
return contentHtml;
|
||||
}
|
||||
|
||||
public String getId(int pluginId) {
|
||||
return "tab_" + pluginId + "_" + RegExUtils.removeAll(title, "[^a-zA-Z0-9]*").toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
@ -96,7 +96,7 @@ public class JSONFactory {
|
||||
this.formatters = formatters;
|
||||
}
|
||||
|
||||
public Map<String, Object> serverPlayersTableJSON(ServerUUID serverUUID) {
|
||||
public PlayersTableJSONCreator serverPlayersTableJSON(ServerUUID serverUUID) {
|
||||
Integer xMostRecentPlayers = config.get(DisplaySettings.PLAYERS_PER_SERVER_PAGE);
|
||||
Long playtimeThreshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD);
|
||||
boolean openPlayerLinksInNewTab = config.isTrue(DisplaySettings.OPEN_PLAYER_LINKS_IN_NEW_TAB);
|
||||
@ -108,10 +108,10 @@ public class JSONFactory {
|
||||
database.query(new ExtensionServerTableDataQuery(serverUUID, xMostRecentPlayers)),
|
||||
openPlayerLinksInNewTab,
|
||||
formatters, locale
|
||||
).toJSONMap();
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> networkPlayersTableJSON() {
|
||||
public PlayersTableJSONCreator networkPlayersTableJSON() {
|
||||
Integer xMostRecentPlayers = config.get(DisplaySettings.PLAYERS_PER_PLAYERS_PAGE);
|
||||
Long playtimeThreshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD);
|
||||
boolean openPlayerLinksInNewTab = config.isTrue(DisplaySettings.OPEN_PLAYER_LINKS_IN_NEW_TAB);
|
||||
@ -147,7 +147,7 @@ public class JSONFactory {
|
||||
openPlayerLinksInNewTab,
|
||||
formatters, locale,
|
||||
true // players page
|
||||
).toJSONMap();
|
||||
);
|
||||
}
|
||||
|
||||
public List<RetentionData> playerRetentionAsJSONMap(ServerUUID serverUUID) {
|
||||
|
@ -23,7 +23,6 @@ import com.djrapitops.plan.delivery.domain.keys.PlayerKeys;
|
||||
import com.djrapitops.plan.delivery.domain.mutators.*;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Html;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.line.PingGraph;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie;
|
||||
@ -51,7 +50,6 @@ import com.djrapitops.plan.storage.database.queries.objects.SessionQueries;
|
||||
import com.djrapitops.plan.utilities.comparators.DateHolderRecentComparator;
|
||||
import com.djrapitops.plan.utilities.java.Lists;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@ -372,7 +370,7 @@ public class PlayerJSONCreator {
|
||||
List<Nickname> mapped = new ArrayList<>();
|
||||
for (com.djrapitops.plan.delivery.domain.Nickname nickname : nicknames) {
|
||||
mapped.add(new Nickname(
|
||||
Html.swapColorCodesToSpan(StringEscapeUtils.escapeHtml4(nickname.getName())),
|
||||
nickname.getName(),
|
||||
serverNames.getOrDefault(nickname.getServerUUID(), nickname.getServerUUID().toString()),
|
||||
dateFormatter.apply(nickname.getDate())
|
||||
));
|
||||
|
@ -17,6 +17,11 @@
|
||||
package com.djrapitops.plan.delivery.rendering.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.TablePlayer;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerListDto;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.TablePlayerDto;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionDescriptionDto;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionTabDataDto;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionValueDataDto;
|
||||
import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
@ -33,6 +38,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Utility for creating jQuery Datatables JSON for a Players Table.
|
||||
@ -109,6 +115,7 @@ public class PlayersTableJSONCreator {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Map<String, Object> toJSONMap() {
|
||||
return Maps.builder(String.class, Object.class)
|
||||
.put("columns", createColumnHeaders())
|
||||
@ -116,6 +123,41 @@ public class PlayersTableJSONCreator {
|
||||
.build();
|
||||
}
|
||||
|
||||
public PlayerListDto toPlayerList() {
|
||||
return new PlayerListDto(toPlayers(), getExtensionDescriptors());
|
||||
}
|
||||
|
||||
private List<TablePlayerDto> toPlayers() {
|
||||
return players.stream()
|
||||
.map(player -> TablePlayerDto.builder()
|
||||
.withUuid(player.getPlayerUUID())
|
||||
.withName(player.getName().orElseGet(() -> player.getPlayerUUID().toString()))
|
||||
.withSessionCount((long) player.getSessionCount().orElse(0))
|
||||
.withPlaytimeActive(player.getActivePlaytime().orElse(null))
|
||||
.withLastSeen(player.getLastSeen().orElse(null))
|
||||
.withRegistered(player.getRegistered().orElse(null))
|
||||
.withCountry(player.getGeolocation().orElse(null))
|
||||
.withExtensionValues(mapToExtensionValues(extensionData.get(player.getPlayerUUID())))
|
||||
.build()
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<ExtensionDescriptionDto> getExtensionDescriptors() {
|
||||
return extensionDescriptions.stream().map(ExtensionDescriptionDto::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Map<String, ExtensionValueDataDto> mapToExtensionValues(ExtensionTabData extensionTabData) {
|
||||
if (extensionTabData == null) return Collections.emptyMap();
|
||||
|
||||
Map<String, ExtensionValueDataDto> values = new HashMap<>();
|
||||
List<ExtensionDescription> descriptions = extensionTabData.getDescriptions();
|
||||
for (ExtensionDescription description : descriptions) {
|
||||
String name = description.getName();
|
||||
ExtensionTabDataDto.mapToValue(extensionTabData, name).ifPresent(value -> values.put(name, value));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> createData() {
|
||||
List<Map<String, Object>> dataJson = new ArrayList<>();
|
||||
|
||||
@ -196,7 +238,7 @@ public class PlayersTableJSONCreator {
|
||||
}
|
||||
|
||||
// If it's a String add a String, otherwise the player has no value for this extension provider.
|
||||
String stringValue = tabData.getString(key).map(ExtensionStringData::getFormattedValue).orElse("-");
|
||||
String stringValue = tabData.getString(key).map(ExtensionStringData::getValue).orElse("-");
|
||||
putDataEntry(dataJson, stringValue, stringValue, key);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
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.theme.Theme;
|
||||
import com.djrapitops.plan.utilities.java.UnaryChain;
|
||||
@ -63,10 +62,7 @@ public class ErrorMessagePage implements Page {
|
||||
placeholders.put("title", icon.toHtml() + " " + errorTitle);
|
||||
placeholders.put("titleText", errorTitle);
|
||||
placeholders.put("paragraph", errorMsg);
|
||||
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)
|
||||
|
@ -17,7 +17,6 @@
|
||||
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.Html;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.exceptions.ExceptionWithContext;
|
||||
@ -58,10 +57,7 @@ public class InternalErrorPage implements Page {
|
||||
placeholders.put("title", Icon.called("bug") + " 500 Internal Error occurred");
|
||||
placeholders.put("titleText", "500 Internal Error occurred");
|
||||
placeholders.put("paragraph", createContent());
|
||||
placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton()));
|
||||
placeholders.put("version", versionChecker.getCurrentVersion());
|
||||
placeholders.put("updateModal", versionChecker.getUpdateModal());
|
||||
placeholders.put("contributors", Contributors.generateContributorHtml());
|
||||
return placeholders.apply(template);
|
||||
}
|
||||
|
||||
|
@ -1,76 +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.web.resource.WebResource;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
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 LoginPage implements Page {
|
||||
|
||||
private final WebResource template;
|
||||
private final ServerInfo serverInfo;
|
||||
private final Locale locale;
|
||||
private final Theme theme;
|
||||
|
||||
private final VersionChecker versionChecker;
|
||||
|
||||
LoginPage(
|
||||
WebResource htmlTemplate,
|
||||
ServerInfo serverInfo,
|
||||
Locale locale,
|
||||
Theme theme,
|
||||
VersionChecker versionChecker
|
||||
) {
|
||||
this.template = htmlTemplate;
|
||||
this.serverInfo = serverInfo;
|
||||
this.locale = locale;
|
||||
this.theme = theme;
|
||||
this.versionChecker = versionChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lastModified() {
|
||||
return template.getLastModified().orElseGet(System::currentTimeMillis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
PlaceholderReplacer placeholders = new PlaceholderReplacer();
|
||||
placeholders.put("command", getCommand());
|
||||
placeholders.put("version", versionChecker.getCurrentVersion());
|
||||
return UnaryChain.of(template.asString())
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(placeholders::apply)
|
||||
.chain(locale::replaceLanguageInHtml)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private String getCommand() {
|
||||
if (serverInfo.getServer().isProxy()) return "planproxy";
|
||||
return "plan";
|
||||
}
|
||||
}
|
@ -1,141 +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.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.domain.container.CachingSupplier;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.formatting.PlaceholderReplacer;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Contributors;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
||||
import com.djrapitops.plan.extension.implementation.results.ExtensionData;
|
||||
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionServerDataQuery;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.ProxySettings;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.settings.theme.ThemeVal;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.utilities.java.UnaryChain;
|
||||
import com.djrapitops.plan.version.VersionChecker;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Html String generator for /network page.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class NetworkPage implements Page {
|
||||
|
||||
private final String templateHtml;
|
||||
private final DBSystem dbSystem;
|
||||
|
||||
private final VersionChecker versionChecker;
|
||||
private final PlanConfig config;
|
||||
private final Theme theme;
|
||||
private final ServerInfo serverInfo;
|
||||
private final JSONStorage jsonStorage;
|
||||
private final Formatters formatters;
|
||||
private final Locale locale;
|
||||
private final ComponentSvc componentSvc;
|
||||
|
||||
NetworkPage(
|
||||
String templateHtml,
|
||||
DBSystem dbSystem,
|
||||
VersionChecker versionChecker,
|
||||
PlanConfig config,
|
||||
Theme theme,
|
||||
ServerInfo serverInfo,
|
||||
JSONStorage jsonStorage,
|
||||
Formatters formatters,
|
||||
Locale locale,
|
||||
ComponentSvc componentSvc
|
||||
) {
|
||||
this.templateHtml = templateHtml;
|
||||
this.dbSystem = dbSystem;
|
||||
this.versionChecker = versionChecker;
|
||||
this.config = config;
|
||||
this.theme = theme;
|
||||
this.serverInfo = serverInfo;
|
||||
this.jsonStorage = jsonStorage;
|
||||
this.formatters = formatters;
|
||||
this.locale = locale;
|
||||
this.componentSvc = componentSvc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
PlaceholderReplacer placeholders = new PlaceholderReplacer();
|
||||
|
||||
ServerUUID serverUUID = serverInfo.getServerUUID();
|
||||
placeholders.put("networkDisplayName", config.get(ProxySettings.NETWORK_NAME));
|
||||
placeholders.put("serverName", config.get(ProxySettings.NETWORK_NAME));
|
||||
placeholders.put("serverUUID", serverUUID.toString());
|
||||
placeholders.put("refreshBarrier", config.get(WebserverSettings.REDUCED_REFRESH_BARRIER));
|
||||
|
||||
placeholders.put("gmPieColors", theme.getValue(ThemeVal.GRAPH_GM_PIE));
|
||||
placeholders.put("playersGraphColor", theme.getValue(ThemeVal.GRAPH_PLAYERS_ONLINE));
|
||||
placeholders.put("worldMapColLow", theme.getValue(ThemeVal.WORLD_MAP_LOW));
|
||||
placeholders.put("worldMapColHigh", theme.getValue(ThemeVal.WORLD_MAP_HIGH));
|
||||
placeholders.put("maxPingColor", theme.getValue(ThemeVal.GRAPH_MAX_PING));
|
||||
placeholders.put("minPingColor", theme.getValue(ThemeVal.GRAPH_MIN_PING));
|
||||
placeholders.put("avgPingColor", theme.getValue(ThemeVal.GRAPH_AVG_PING));
|
||||
placeholders.put("timeZone", config.getTimeZoneOffsetHours());
|
||||
|
||||
placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton()));
|
||||
placeholders.put("version", versionChecker.getCurrentVersion());
|
||||
placeholders.put("updateModal", versionChecker.getUpdateModal());
|
||||
placeholders.put("contributors", Contributors.generateContributorHtml());
|
||||
|
||||
CachingSupplier<ServerPluginTabs> pluginTabs = new CachingSupplier<>(() -> {
|
||||
List<ExtensionData> extensionData = dbSystem.getDatabase().query(new ExtensionServerDataQuery(serverUUID));
|
||||
return new ServerPluginTabs(extensionData, formatters, componentSvc);
|
||||
});
|
||||
|
||||
long after = System.currentTimeMillis() - config.get(WebserverSettings.REDUCED_REFRESH_BARRIER);
|
||||
String navIdentifier = DataID.EXTENSION_NAV.of(serverUUID);
|
||||
String tabIdentifier = DataID.EXTENSION_TABS.of(serverUUID);
|
||||
String nav = jsonStorage.fetchJsonMadeAfter(navIdentifier, after).orElseGet(() -> {
|
||||
jsonStorage.invalidateOlder(navIdentifier, after);
|
||||
return jsonStorage.storeJson(navIdentifier, pluginTabs.get().getNav());
|
||||
}).json;
|
||||
String tabs = jsonStorage.fetchJsonMadeAfter(tabIdentifier, after).orElseGet(() -> {
|
||||
jsonStorage.invalidateOlder(tabIdentifier, after);
|
||||
return jsonStorage.storeJson(tabIdentifier, pluginTabs.get().getTabs());
|
||||
}).json;
|
||||
|
||||
PlaceholderReplacer pluginPlaceholders = new PlaceholderReplacer();
|
||||
pluginPlaceholders.put("networkDisplayName", config.get(ProxySettings.NETWORK_NAME));
|
||||
pluginPlaceholders.put("serverName", config.get(ProxySettings.NETWORK_NAME));
|
||||
pluginPlaceholders.put("serverUUID", serverUUID.toString());
|
||||
pluginPlaceholders.put("navPluginsTabs", nav);
|
||||
pluginPlaceholders.put("tabsPlugins", StringUtils.remove(tabs, "${backButton}"));
|
||||
|
||||
return UnaryChain.of(templateHtml)
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(placeholders::apply)
|
||||
.chain(pluginPlaceholders::apply)
|
||||
.chain(locale::replaceLanguageInHtml)
|
||||
.apply();
|
||||
}
|
||||
}
|
@ -16,26 +16,15 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.rendering.pages;
|
||||
|
||||
import com.djrapitops.plan.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.domain.container.PlayerContainer;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
||||
import com.djrapitops.plan.extension.implementation.results.ExtensionData;
|
||||
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionPlayerDataQuery;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.storage.file.PublicHtmlFiles;
|
||||
@ -47,7 +36,6 @@ import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Factory for creating different {@link Page} objects.
|
||||
@ -60,51 +48,31 @@ public class PageFactory {
|
||||
private final Lazy<VersionChecker> versionChecker;
|
||||
private final Lazy<PlanFiles> files;
|
||||
private final Lazy<PublicHtmlFiles> publicHtmlFiles;
|
||||
private final Lazy<PlanConfig> config;
|
||||
private final Lazy<Theme> theme;
|
||||
private final Lazy<DBSystem> dbSystem;
|
||||
private final Lazy<ServerInfo> serverInfo;
|
||||
private final Lazy<JSONStorage> jsonStorage;
|
||||
private final Lazy<Formatters> formatters;
|
||||
private final Lazy<Locale> locale;
|
||||
private final Lazy<ComponentSvc> componentService;
|
||||
private final Lazy<Addresses> addresses;
|
||||
private static final String ERROR_HTML_FILE = "error.html";
|
||||
|
||||
@Inject
|
||||
public PageFactory(
|
||||
Lazy<VersionChecker> versionChecker,
|
||||
Lazy<PlanFiles> files,
|
||||
Lazy<PublicHtmlFiles> publicHtmlFiles, Lazy<PlanConfig> config,
|
||||
Lazy<PublicHtmlFiles> publicHtmlFiles,
|
||||
Lazy<Theme> theme,
|
||||
Lazy<DBSystem> dbSystem,
|
||||
Lazy<ServerInfo> serverInfo,
|
||||
Lazy<JSONStorage> jsonStorage,
|
||||
Lazy<Formatters> formatters,
|
||||
Lazy<Locale> locale,
|
||||
Lazy<ComponentSvc> componentService,
|
||||
Lazy<Addresses> addresses
|
||||
) {
|
||||
this.versionChecker = versionChecker;
|
||||
this.files = files;
|
||||
this.publicHtmlFiles = publicHtmlFiles;
|
||||
this.config = config;
|
||||
this.theme = theme;
|
||||
this.dbSystem = dbSystem;
|
||||
this.serverInfo = serverInfo;
|
||||
this.jsonStorage = jsonStorage;
|
||||
this.formatters = formatters;
|
||||
this.locale = locale;
|
||||
this.componentService = componentService;
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public Page playersPage() throws IOException {
|
||||
if (config.get().isFalse(PluginSettings.LEGACY_FRONTEND)) {
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
return new PlayersPage(getResourceAsString("players.html"), versionChecker.get(),
|
||||
config.get(), theme.get(), serverInfo.get());
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
public Page reactPage() throws IOException {
|
||||
@ -134,100 +102,24 @@ public class PageFactory {
|
||||
* @throws IOException If the template files can not be read.
|
||||
*/
|
||||
public Page serverPage(ServerUUID serverUUID) throws IOException {
|
||||
Server server = dbSystem.get().getDatabase().query(ServerQueries.fetchServerMatchingIdentifier(serverUUID))
|
||||
.orElseThrow(() -> new NotFoundException("Server not found in the database"));
|
||||
|
||||
if (config.get().isFalse(PluginSettings.LEGACY_FRONTEND)) {
|
||||
return reactPage();
|
||||
if (dbSystem.get().getDatabase().query(ServerQueries.fetchServerMatchingIdentifier(serverUUID)).isEmpty()) {
|
||||
throw new NotFoundException("Server not found in the database");
|
||||
}
|
||||
|
||||
return new ServerPage(
|
||||
getResourceAsString("server.html"),
|
||||
server,
|
||||
config.get(),
|
||||
theme.get(),
|
||||
versionChecker.get(),
|
||||
dbSystem.get(),
|
||||
serverInfo.get(),
|
||||
jsonStorage.get(),
|
||||
formatters.get(),
|
||||
locale.get(),
|
||||
componentService.get()
|
||||
);
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
public Page playerPage(PlayerContainer player) throws IOException {
|
||||
if (config.get().isFalse(PluginSettings.LEGACY_FRONTEND)) {
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
return new PlayerPage(
|
||||
getResourceAsString("player.html"), player,
|
||||
versionChecker.get(),
|
||||
config.get(),
|
||||
this,
|
||||
theme.get(),
|
||||
formatters.get(),
|
||||
serverInfo.get(),
|
||||
locale.get()
|
||||
);
|
||||
}
|
||||
|
||||
public PlayerPluginTab inspectPluginTabs(UUID playerUUID) {
|
||||
Database database = dbSystem.get().getDatabase();
|
||||
|
||||
Map<ServerUUID, List<ExtensionData>> extensionPlayerData = database.query(new ExtensionPlayerDataQuery(playerUUID));
|
||||
|
||||
if (extensionPlayerData.isEmpty()) {
|
||||
return new PlayerPluginTab("", Collections.emptyList(), formatters.get(), componentService.get());
|
||||
}
|
||||
|
||||
List<PlayerPluginTab> playerPluginTabs = new ArrayList<>();
|
||||
for (Map.Entry<ServerUUID, Server> entry : database.query(ServerQueries.fetchPlanServerInformation()).entrySet()) {
|
||||
ServerUUID serverUUID = entry.getKey();
|
||||
String serverName = entry.getValue().getIdentifiableName();
|
||||
|
||||
List<ExtensionData> ofServer = extensionPlayerData.get(serverUUID);
|
||||
if (ofServer == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
playerPluginTabs.add(new PlayerPluginTab(serverName, ofServer, formatters.get(), componentService.get()));
|
||||
}
|
||||
|
||||
StringBuilder navs = new StringBuilder();
|
||||
StringBuilder tabs = new StringBuilder();
|
||||
|
||||
playerPluginTabs.stream().sorted().forEach(tab -> {
|
||||
navs.append(tab.getNav());
|
||||
tabs.append(tab.getTab());
|
||||
});
|
||||
|
||||
return new PlayerPluginTab(navs.toString(), tabs.toString(), componentService.get());
|
||||
public Page playerPage() throws IOException {
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
public Page networkPage() throws IOException {
|
||||
if (config.get().isFalse(PluginSettings.LEGACY_FRONTEND)) {
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
return new NetworkPage(getResourceAsString("network.html"),
|
||||
dbSystem.get(),
|
||||
versionChecker.get(),
|
||||
config.get(),
|
||||
theme.get(),
|
||||
serverInfo.get(),
|
||||
jsonStorage.get(),
|
||||
formatters.get(),
|
||||
locale.get(),
|
||||
componentService.get()
|
||||
);
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
public Page internalErrorPage(String message, @Untrusted Throwable error) {
|
||||
try {
|
||||
return new InternalErrorPage(
|
||||
getResourceAsString("error.html"), message, error,
|
||||
getResourceAsString(ERROR_HTML_FILE), message, error,
|
||||
versionChecker.get());
|
||||
} catch (IOException noParse) {
|
||||
return () -> "Error occurred: " + error.toString() +
|
||||
@ -238,13 +130,13 @@ public class PageFactory {
|
||||
|
||||
public Page errorPage(String title, String error) throws IOException {
|
||||
return new ErrorMessagePage(
|
||||
getResourceAsString("error.html"), title, error,
|
||||
getResourceAsString(ERROR_HTML_FILE), title, error,
|
||||
versionChecker.get(), theme.get());
|
||||
}
|
||||
|
||||
public Page errorPage(Icon icon, String title, String error) throws IOException {
|
||||
return new ErrorMessagePage(
|
||||
getResourceAsString("error.html"), icon, title, error, theme.get(), versionChecker.get());
|
||||
getResourceAsString(ERROR_HTML_FILE), icon, title, error, theme.get(), versionChecker.get());
|
||||
}
|
||||
|
||||
public String getResourceAsString(String name) throws IOException {
|
||||
@ -268,29 +160,15 @@ public class PageFactory {
|
||||
}
|
||||
|
||||
public Page loginPage() throws IOException {
|
||||
if (config.get().isFalse(PluginSettings.LEGACY_FRONTEND)) {
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
return new LoginPage(getResource("login.html"), serverInfo.get(), locale.get(), theme.get(), versionChecker.get());
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
public Page registerPage() throws IOException {
|
||||
if (config.get().isFalse(PluginSettings.LEGACY_FRONTEND)) {
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
return new LoginPage(getResource("register.html"), serverInfo.get(), locale.get(), theme.get(), versionChecker.get());
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
public Page queryPage() throws IOException {
|
||||
if (config.get().isFalse(PluginSettings.LEGACY_FRONTEND)) {
|
||||
return reactPage();
|
||||
}
|
||||
return new QueryPage(
|
||||
getResourceAsString("query.html"),
|
||||
locale.get(), theme.get(), versionChecker.get()
|
||||
);
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
public Page errorsPage() throws IOException {
|
||||
|
@ -1,127 +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.domain.container.PlayerContainer;
|
||||
import com.djrapitops.plan.delivery.domain.keys.PlayerKeys;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.formatting.PlaceholderReplacer;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Contributors;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Html;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DisplaySettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.settings.theme.ThemeVal;
|
||||
import com.djrapitops.plan.utilities.java.UnaryChain;
|
||||
import com.djrapitops.plan.version.VersionChecker;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Html String generator for /player page.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PlayerPage implements Page {
|
||||
|
||||
private final String templateHtml;
|
||||
private final PlayerContainer player;
|
||||
|
||||
private final VersionChecker versionChecker;
|
||||
|
||||
private final PlanConfig config;
|
||||
private final PageFactory pageFactory;
|
||||
private final Theme theme;
|
||||
private final ServerInfo serverInfo;
|
||||
|
||||
private final Formatter<Long> clockLongFormatter;
|
||||
private final Formatter<Long> secondLongFormatter;
|
||||
private final Locale locale;
|
||||
|
||||
PlayerPage(
|
||||
String templateHtml,
|
||||
PlayerContainer player,
|
||||
VersionChecker versionChecker,
|
||||
PlanConfig config,
|
||||
PageFactory pageFactory,
|
||||
Theme theme,
|
||||
Formatters formatters,
|
||||
ServerInfo serverInfo,
|
||||
Locale locale
|
||||
) {
|
||||
this.templateHtml = templateHtml;
|
||||
this.player = player;
|
||||
this.versionChecker = versionChecker;
|
||||
this.config = config;
|
||||
this.pageFactory = pageFactory;
|
||||
this.theme = theme;
|
||||
this.serverInfo = serverInfo;
|
||||
|
||||
clockLongFormatter = formatters.clockLong();
|
||||
secondLongFormatter = formatters.secondLong();
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
if (player.getValue(PlayerKeys.REGISTERED).isEmpty()) {
|
||||
throw new IllegalStateException("Player is not registered");
|
||||
}
|
||||
return createFor(player);
|
||||
}
|
||||
|
||||
public String createFor(PlayerContainer player) {
|
||||
long now = System.currentTimeMillis();
|
||||
UUID playerUUID = player.getUnsafe(PlayerKeys.UUID);
|
||||
|
||||
PlaceholderReplacer placeholders = new PlaceholderReplacer();
|
||||
|
||||
placeholders.put("refresh", clockLongFormatter.apply(now));
|
||||
placeholders.put("refreshFull", secondLongFormatter.apply(now));
|
||||
placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton()));
|
||||
placeholders.put("version", versionChecker.getCurrentVersion());
|
||||
placeholders.put("updateModal", versionChecker.getUpdateModal());
|
||||
|
||||
String playerName = player.getValue(PlayerKeys.NAME).orElse(playerUUID.toString());
|
||||
placeholders.put("playerName", playerName);
|
||||
placeholders.put("playerUUID", playerUUID);
|
||||
placeholders.put("playerHeadUrl", config.get(DisplaySettings.PLAYER_HEAD_IMG_URL));
|
||||
|
||||
placeholders.put("timeZone", config.getTimeZoneOffsetHours());
|
||||
placeholders.put("gmPieColors", theme.getValue(ThemeVal.GRAPH_GM_PIE));
|
||||
|
||||
placeholders.put("contributors", Contributors.generateContributorHtml());
|
||||
|
||||
PlaceholderReplacer pluginPlaceholders = new PlaceholderReplacer();
|
||||
PlayerPluginTab pluginTabs = pageFactory.inspectPluginTabs(playerUUID);
|
||||
|
||||
pluginPlaceholders.put("playerName", playerName);
|
||||
pluginPlaceholders.put("backButton", (serverInfo.getServer().isProxy() ? Html.BACK_BUTTON_NETWORK : Html.BACK_BUTTON_SERVER).create());
|
||||
pluginPlaceholders.put("navPluginsTabs", pluginTabs.getNav());
|
||||
pluginPlaceholders.put("pluginsTabs", pluginTabs.getTab());
|
||||
|
||||
return UnaryChain.of(templateHtml)
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(placeholders::apply)
|
||||
.chain(pluginPlaceholders::apply)
|
||||
.chain(locale::replaceLanguageInHtml)
|
||||
.apply();
|
||||
}
|
||||
}
|
@ -1,252 +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.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.delivery.rendering.html.structure.NavLink;
|
||||
import com.djrapitops.plan.delivery.rendering.html.structure.TabsElement;
|
||||
import com.djrapitops.plan.extension.ElementOrder;
|
||||
import com.djrapitops.plan.extension.FormatType;
|
||||
import com.djrapitops.plan.extension.implementation.TabInformation;
|
||||
import com.djrapitops.plan.extension.implementation.results.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Responsible for generating /player page plugin tabs based on DataExtension API data.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PlayerPluginTab implements Comparable<PlayerPluginTab> {
|
||||
|
||||
private String serverName;
|
||||
private List<ExtensionData> playerData;
|
||||
|
||||
private Map<FormatType, Formatter<Long>> numberFormatters;
|
||||
|
||||
private Formatter<Double> decimalFormatter;
|
||||
private Formatter<Double> percentageFormatter;
|
||||
|
||||
private String nav;
|
||||
private String tab;
|
||||
|
||||
private boolean hasWideTable;
|
||||
private final ComponentSvc componentSvc;
|
||||
|
||||
public PlayerPluginTab(String nav, String tab, ComponentSvc componentSvc) {
|
||||
this.nav = nav;
|
||||
this.tab = tab;
|
||||
this.componentSvc = componentSvc;
|
||||
}
|
||||
|
||||
public PlayerPluginTab(
|
||||
String serverName,
|
||||
List<ExtensionData> playerData,
|
||||
Formatters formatters,
|
||||
ComponentSvc componentSvc
|
||||
) {
|
||||
this.serverName = serverName;
|
||||
this.playerData = playerData;
|
||||
this.componentSvc = componentSvc;
|
||||
|
||||
numberFormatters = new EnumMap<>(FormatType.class);
|
||||
numberFormatters.put(FormatType.DATE_SECOND, formatters.secondLong());
|
||||
numberFormatters.put(FormatType.DATE_YEAR, formatters.yearLong());
|
||||
numberFormatters.put(FormatType.TIME_MILLISECONDS, formatters.timeAmount());
|
||||
numberFormatters.put(FormatType.NONE, Object::toString);
|
||||
|
||||
this.decimalFormatter = formatters.decimals();
|
||||
this.percentageFormatter = formatters.percentage();
|
||||
|
||||
hasWideTable = false;
|
||||
|
||||
generate();
|
||||
}
|
||||
|
||||
public String getNav() {
|
||||
return nav;
|
||||
}
|
||||
|
||||
public String getTab() {
|
||||
return tab;
|
||||
}
|
||||
|
||||
private void generate() {
|
||||
if (playerData.isEmpty()) {
|
||||
nav = NavLink.collapsed(Icon.called("cubes").build(), "plugins-" + serverName + " (No Data)", serverName + " (No Data)").toHtml();
|
||||
tab = wrapInWideTab(
|
||||
serverName + " (No Data)",
|
||||
"<div class=\"card\"><div class=\"card-body\"><p>No Extension Data</p></div></div>"
|
||||
);
|
||||
} else {
|
||||
nav = NavLink.collapsed(Icon.called("cubes").build(), "plugins-" + serverName, serverName).toHtml();
|
||||
tab = generatePageTab();
|
||||
}
|
||||
}
|
||||
|
||||
private String generatePageTab() {
|
||||
Collections.sort(playerData);
|
||||
|
||||
StringBuilder tabBuilder = new StringBuilder();
|
||||
|
||||
for (ExtensionData datum : playerData) {
|
||||
ExtensionInformation extensionInformation = datum.getExtensionInformation();
|
||||
|
||||
boolean onlyGeneric = datum.hasOnlyGenericTab();
|
||||
|
||||
String tabsElement;
|
||||
if (onlyGeneric) {
|
||||
ExtensionTabData genericTabData = datum.getTabs().get(0);
|
||||
tabsElement = buildContentHtml(genericTabData);
|
||||
} else {
|
||||
tabsElement = new TabsElement(
|
||||
datum.getPluginID(),
|
||||
datum.getTabs().stream().map(this::wrapToTabElementTab).toArray(TabsElement.Tab[]::new)
|
||||
).toHtmlFull();
|
||||
}
|
||||
|
||||
tabBuilder.append(wrapInContainer(extensionInformation, tabsElement));
|
||||
}
|
||||
|
||||
return wrapInCardColumnsTab(serverName, tabBuilder.toString());
|
||||
}
|
||||
|
||||
private String wrapInWideTab(String serverName, String content) {
|
||||
return "<div class=\"tab\" id=\"" + NavLink.format("plugins-" + serverName) + "\"><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>" + serverName + " · Plugins Overview</h1>${backButton}" +
|
||||
"</div>" +
|
||||
// End Page heading
|
||||
"<div class=\"row\"><div class=\"col-md-12\">" + content + "</div></div></div></div>";
|
||||
}
|
||||
|
||||
private String wrapInCardColumnsTab(String serverName, String content) {
|
||||
return "<div class=\"tab\" id=\"" + NavLink.format("plugins-" + serverName) + "\"><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>" + serverName + " · Plugins Overview</h1>${backButton}" +
|
||||
"</div>" +
|
||||
// End Page heading
|
||||
"<div class=\"row\" data-masonry='{\"percentPosition\": true}'>" + content + "</div></div></div>";
|
||||
}
|
||||
|
||||
private TabsElement.Tab wrapToTabElementTab(ExtensionTabData tabData) {
|
||||
TabInformation tabInformation = tabData.getTabInformation();
|
||||
String tabContentHtml = buildContentHtml(tabData);
|
||||
|
||||
String tabName = tabInformation.getTabName();
|
||||
return tabName.isEmpty() ? new TabsElement.Tab(Icon.called("info-circle").build(), "General", tabContentHtml)
|
||||
: new TabsElement.Tab(Icon.fromExtensionIcon(tabInformation.getTabIcon()), tabName, tabContentHtml);
|
||||
}
|
||||
|
||||
private String buildContentHtml(ExtensionTabData tabData) {
|
||||
TabInformation tabInformation = tabData.getTabInformation();
|
||||
|
||||
List<ElementOrder> order = tabInformation.getTabElementOrder();
|
||||
String values = buildValuesHtml(tabData);
|
||||
String valuesHtml = values.isEmpty() ? "" : "<div class=\"card-body\">" + values + "</div>";
|
||||
String tablesHtml = buildTablesHtml(tabData);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (ElementOrder ordering : order) {
|
||||
switch (ordering) {
|
||||
case VALUES:
|
||||
builder.append(valuesHtml);
|
||||
break;
|
||||
case TABLE:
|
||||
builder.append(tablesHtml);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String buildTablesHtml(ExtensionTabData tabData) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (ExtensionTableData tableData : tabData.getTableData()) {
|
||||
if (tableData.isWideTable()) {
|
||||
hasWideTable = true;
|
||||
}
|
||||
builder.append(tableData.getHtmlTable().toHtml());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String buildValuesHtml(ExtensionTabData tabData) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String key : tabData.getValueOrder()) {
|
||||
tabData.getBoolean(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue()));
|
||||
tabData.getDouble(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue(decimalFormatter)));
|
||||
tabData.getPercentage(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue(percentageFormatter)));
|
||||
tabData.getNumber(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue(numberFormatters.get(data.getFormatType()))));
|
||||
tabData.getString(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue()));
|
||||
tabData.getComponent(key).ifPresent(data -> append(builder, data.getDescription(), data.getHtmlValue(componentSvc)));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void append(StringBuilder builder, ExtensionDescription description, String formattedValue) {
|
||||
Optional<String> textDescription = description.getDescription();
|
||||
if (textDescription.isPresent()) {
|
||||
builder.append("<p title=\"").append(textDescription.get()).append("\">");
|
||||
} else {
|
||||
builder.append("<p>");
|
||||
}
|
||||
builder.append(Icon.fromExtensionIcon(description.getIcon()))
|
||||
.append(' ').append(description.getText()).append("<span class=\"float-end\"><b>").append(formattedValue).append("</b></span></p>");
|
||||
}
|
||||
|
||||
private String wrapInContainer(ExtensionInformation information, String tabsElement) {
|
||||
String colWidth = hasWideTable ? "col-md-8 col-lg-8" : "col-md-4 col-lg-4";
|
||||
// TODO move large tables to their own tabs
|
||||
return "<div class=\"col-lg-6 col-xxl-4\">" +
|
||||
"<div class=\"card shadow mb-4\">" +
|
||||
"<div class=\"card-header py-3\">" +
|
||||
"<h6 class=\"m-0 fw-bold col-black\">" + Icon.fromExtensionIcon(information.getIcon()) + ' ' + information.getPluginName() + "</h6>" +
|
||||
"</div>" +
|
||||
tabsElement +
|
||||
"</div>" +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof PlayerPluginTab)) return false;
|
||||
PlayerPluginTab that = (PlayerPluginTab) o;
|
||||
return Objects.equals(serverName, that.serverName) &&
|
||||
Objects.equals(nav, that.nav);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(serverName, nav);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PlayerPluginTab other) {
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(this.serverName, other.serverName);
|
||||
}
|
||||
}
|
@ -1,73 +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.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.ProxySettings;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.version.VersionChecker;
|
||||
|
||||
/**
|
||||
* Html String generator for /players page.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PlayersPage implements Page {
|
||||
|
||||
private final String templateHtml;
|
||||
private final VersionChecker versionChecker;
|
||||
private final PlanConfig config;
|
||||
private final Theme theme;
|
||||
private final ServerInfo serverInfo;
|
||||
|
||||
PlayersPage(
|
||||
String templateHtml,
|
||||
VersionChecker versionChecker,
|
||||
PlanConfig config,
|
||||
Theme theme,
|
||||
ServerInfo serverInfo
|
||||
) {
|
||||
this.templateHtml = templateHtml;
|
||||
this.versionChecker = versionChecker;
|
||||
this.config = config;
|
||||
this.theme = theme;
|
||||
this.serverInfo = serverInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
PlaceholderReplacer placeholders = new PlaceholderReplacer();
|
||||
|
||||
placeholders.put("refreshBarrier", config.get(WebserverSettings.REDUCED_REFRESH_BARRIER));
|
||||
placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton()));
|
||||
placeholders.put("version", versionChecker.getCurrentVersion());
|
||||
placeholders.put("updateModal", versionChecker.getUpdateModal());
|
||||
placeholders.put("contributors", Contributors.generateContributorHtml());
|
||||
if (serverInfo.getServer().isProxy()) {
|
||||
placeholders.put("networkName", config.get(ProxySettings.NETWORK_NAME));
|
||||
} else {
|
||||
placeholders.put("networkName", config.get(PluginSettings.SERVER_NAME));
|
||||
}
|
||||
|
||||
return placeholders.apply(theme.replaceThemeColors(templateHtml));
|
||||
}
|
||||
}
|
@ -1,64 +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.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 AuroraLS3
|
||||
*/
|
||||
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("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();
|
||||
}
|
||||
}
|
@ -1,137 +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.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.domain.container.CachingSupplier;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.formatting.PlaceholderReplacer;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Contributors;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Html;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
||||
import com.djrapitops.plan.extension.implementation.results.ExtensionData;
|
||||
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionServerDataQuery;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
import com.djrapitops.plan.settings.theme.ThemeVal;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.utilities.java.UnaryChain;
|
||||
import com.djrapitops.plan.version.VersionChecker;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Html String generator for /server page.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class ServerPage implements Page {
|
||||
|
||||
private final String templateHtml;
|
||||
private final Server server;
|
||||
private final PlanConfig config;
|
||||
private final Theme theme;
|
||||
private final VersionChecker versionChecker;
|
||||
private final DBSystem dbSystem;
|
||||
private final ServerInfo serverInfo;
|
||||
private final JSONStorage jsonStorage;
|
||||
private final Formatters formatters;
|
||||
private final Locale locale;
|
||||
private final ComponentSvc componentSvc;
|
||||
|
||||
ServerPage(
|
||||
String templateHtml, Server server,
|
||||
PlanConfig config,
|
||||
Theme theme,
|
||||
VersionChecker versionChecker,
|
||||
DBSystem dbSystem,
|
||||
ServerInfo serverInfo,
|
||||
JSONStorage jsonStorage,
|
||||
Formatters formatters,
|
||||
Locale locale,
|
||||
ComponentSvc componentSvc
|
||||
) {
|
||||
this.templateHtml = templateHtml;
|
||||
this.server = server;
|
||||
this.config = config;
|
||||
this.theme = theme;
|
||||
this.versionChecker = versionChecker;
|
||||
this.dbSystem = dbSystem;
|
||||
this.serverInfo = serverInfo;
|
||||
this.jsonStorage = jsonStorage;
|
||||
this.formatters = formatters;
|
||||
this.locale = locale;
|
||||
this.componentSvc = componentSvc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
PlaceholderReplacer placeholders = new PlaceholderReplacer();
|
||||
|
||||
ServerUUID serverUUID = server.getUuid();
|
||||
placeholders.put("serverUUID", serverUUID.toString());
|
||||
placeholders.put("serverName", server.getIdentifiableName());
|
||||
placeholders.put("serverDisplayName", server.getName());
|
||||
placeholders.put("refreshBarrier", config.get(WebserverSettings.REDUCED_REFRESH_BARRIER));
|
||||
|
||||
placeholders.put("timeZone", config.getTimeZoneOffsetHours());
|
||||
placeholders.put("gmPieColors", theme.getValue(ThemeVal.GRAPH_GM_PIE));
|
||||
|
||||
placeholders.put("contributors", Contributors.generateContributorHtml());
|
||||
placeholders.put("versionButton", versionChecker.getUpdateButton().orElse(versionChecker.getCurrentVersionButton()));
|
||||
placeholders.put("version", versionChecker.getCurrentVersion());
|
||||
placeholders.put("updateModal", versionChecker.getUpdateModal());
|
||||
|
||||
CachingSupplier<ServerPluginTabs> pluginTabs = new CachingSupplier<>(() -> {
|
||||
List<ExtensionData> extensionData = dbSystem.getDatabase().query(new ExtensionServerDataQuery(serverUUID));
|
||||
return new ServerPluginTabs(extensionData, formatters, componentSvc);
|
||||
});
|
||||
|
||||
long after = System.currentTimeMillis() - config.get(WebserverSettings.REDUCED_REFRESH_BARRIER);
|
||||
String navIdentifier = DataID.EXTENSION_NAV.of(serverUUID);
|
||||
String tabIdentifier = DataID.EXTENSION_TABS.of(serverUUID);
|
||||
String nav = jsonStorage.fetchJsonMadeAfter(navIdentifier, after).orElseGet(() -> {
|
||||
jsonStorage.invalidateOlder(navIdentifier, after);
|
||||
return jsonStorage.storeJson(navIdentifier, pluginTabs.get().getNav());
|
||||
}).json;
|
||||
String tabs = jsonStorage.fetchJsonMadeAfter(tabIdentifier, after).orElseGet(() -> {
|
||||
jsonStorage.invalidateOlder(tabIdentifier, after);
|
||||
return jsonStorage.storeJson(tabIdentifier, pluginTabs.get().getTabs());
|
||||
}).json;
|
||||
|
||||
PlaceholderReplacer pluginPlaceholders = new PlaceholderReplacer();
|
||||
pluginPlaceholders.put("serverUUID", serverUUID.toString());
|
||||
pluginPlaceholders.put("serverName", server.getIdentifiableName());
|
||||
pluginPlaceholders.put("serverDisplayName", server.getName());
|
||||
pluginPlaceholders.put("backButton", serverInfo.getServer().isProxy() ? Html.BACK_BUTTON_NETWORK.create() : "");
|
||||
pluginPlaceholders.put("navPluginsTabs", nav);
|
||||
pluginPlaceholders.put("tabsPlugins", tabs);
|
||||
|
||||
return UnaryChain.of(templateHtml)
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(placeholders::apply)
|
||||
.chain(pluginPlaceholders::apply)
|
||||
.chain(locale::replaceLanguageInHtml)
|
||||
.apply();
|
||||
}
|
||||
}
|
@ -1,263 +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.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.delivery.rendering.html.structure.NavLink;
|
||||
import com.djrapitops.plan.delivery.rendering.html.structure.TabsElement;
|
||||
import com.djrapitops.plan.extension.ElementOrder;
|
||||
import com.djrapitops.plan.extension.FormatType;
|
||||
import com.djrapitops.plan.extension.implementation.TabInformation;
|
||||
import com.djrapitops.plan.extension.implementation.results.*;
|
||||
import com.djrapitops.plan.utilities.java.Lists;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Responsible for generating /server page plugin tabs based on DataExtension API data.
|
||||
* <p>
|
||||
* Currently very similar to {@link PlayerPluginTab}.
|
||||
* This will become more complex once tables are added, since some big tables will be moved to their own tabs.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class ServerPluginTabs {
|
||||
|
||||
private List<ExtensionData> serverData;
|
||||
private List<ExtensionData> extraTabServerData;
|
||||
|
||||
private Map<FormatType, Formatter<Long>> numberFormatters;
|
||||
|
||||
private Formatter<Double> decimalFormatter;
|
||||
private Formatter<Double> percentageFormatter;
|
||||
|
||||
private StringBuilder nav;
|
||||
private String tab;
|
||||
private final ComponentSvc componentSvc;
|
||||
|
||||
public ServerPluginTabs(String nav, String tab, ComponentSvc componentSvc) {
|
||||
this.nav = new StringBuilder(nav);
|
||||
this.tab = tab;
|
||||
this.componentSvc = componentSvc;
|
||||
}
|
||||
|
||||
public ServerPluginTabs(
|
||||
List<ExtensionData> serverData,
|
||||
Formatters formatters,
|
||||
ComponentSvc componentSvc
|
||||
) {
|
||||
this.serverData = serverData;
|
||||
Collections.sort(serverData);
|
||||
this.extraTabServerData = Lists.filter(serverData, ExtensionData::doesNeedWiderSpace);
|
||||
this.serverData.removeAll(extraTabServerData);
|
||||
this.componentSvc = componentSvc;
|
||||
|
||||
numberFormatters = new EnumMap<>(FormatType.class);
|
||||
numberFormatters.put(FormatType.DATE_SECOND, formatters.secondLong());
|
||||
numberFormatters.put(FormatType.DATE_YEAR, formatters.yearLong());
|
||||
numberFormatters.put(FormatType.TIME_MILLISECONDS, formatters.timeAmount());
|
||||
numberFormatters.put(FormatType.NONE, Object::toString);
|
||||
|
||||
this.decimalFormatter = formatters.decimals();
|
||||
this.percentageFormatter = formatters.percentage();
|
||||
|
||||
generate();
|
||||
}
|
||||
|
||||
public String getNav() {
|
||||
return nav.toString();
|
||||
}
|
||||
|
||||
public String getTabs() {
|
||||
return tab;
|
||||
}
|
||||
|
||||
private void generate() {
|
||||
String tabID = "plugins-overview";
|
||||
if (serverData.isEmpty()) {
|
||||
nav = new StringBuilder(NavLink.main(Icon.called("cubes").build(), tabID, "Overview (No Data)").toHtml());
|
||||
tab = wrapInWideColumnTab(
|
||||
"Overview", "<div class=\"card\"><div class=\"card-body\"><p>No Extension Data</p></div></div>"
|
||||
);
|
||||
} else {
|
||||
nav = new StringBuilder(NavLink.main(Icon.called("cubes").build(), tabID, "Overview").toHtml());
|
||||
tab = generateOverviewTab();
|
||||
}
|
||||
tab += generateExtraTabs();
|
||||
}
|
||||
|
||||
private String generateExtraTabs() {
|
||||
StringBuilder tabBuilder = new StringBuilder();
|
||||
|
||||
for (ExtensionData datum : extraTabServerData) {
|
||||
ExtensionInformation extensionInformation = datum.getExtensionInformation();
|
||||
|
||||
boolean onlyGeneric = datum.hasOnlyGenericTab();
|
||||
|
||||
String tabsElement;
|
||||
if (onlyGeneric) {
|
||||
ExtensionTabData genericTabData = datum.getTabs().get(0);
|
||||
tabsElement = buildContentHtml(genericTabData);
|
||||
} else {
|
||||
tabsElement = new TabsElement(
|
||||
datum.getPluginID(),
|
||||
datum.getTabs().stream().map(this::wrapToTabElementTab).toArray(TabsElement.Tab[]::new)
|
||||
).toHtmlFull();
|
||||
}
|
||||
|
||||
String tabName = extensionInformation.getPluginName();
|
||||
tabBuilder.append(wrapInWideColumnTab(tabName, wrapInWideContainer(extensionInformation, tabsElement)));
|
||||
nav.append(NavLink.main(Icon.fromExtensionIcon(extensionInformation.getIcon()), "plugins-" + tabName, tabName).toHtml());
|
||||
}
|
||||
return tabBuilder.toString();
|
||||
}
|
||||
|
||||
private String generateOverviewTab() {
|
||||
StringBuilder contentBuilder = new StringBuilder();
|
||||
|
||||
for (ExtensionData datum : serverData) {
|
||||
ExtensionInformation extensionInformation = datum.getExtensionInformation();
|
||||
|
||||
boolean onlyGeneric = datum.hasOnlyGenericTab();
|
||||
|
||||
String tabsElement;
|
||||
if (onlyGeneric) {
|
||||
ExtensionTabData genericTabData = datum.getTabs().get(0);
|
||||
tabsElement = buildContentHtml(genericTabData);
|
||||
} else {
|
||||
tabsElement = new TabsElement(
|
||||
datum.getPluginID(),
|
||||
datum.getTabs().stream().map(this::wrapToTabElementTab).toArray(TabsElement.Tab[]::new)
|
||||
).toHtmlFull();
|
||||
}
|
||||
|
||||
contentBuilder.append(wrapInContainer(extensionInformation, tabsElement));
|
||||
}
|
||||
|
||||
return wrapInOverviewTab(contentBuilder.toString());
|
||||
}
|
||||
|
||||
private String wrapInWideColumnTab(String tabName, String content) {
|
||||
return "<div class=\"tab\" id=\"" + NavLink.format("plugins-" + tabName) + "\"><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>${serverName} · " + tabName + "</h1>${backButton}" +
|
||||
"</div>" +
|
||||
// End Page heading
|
||||
"<div class=\"row\"><div class=\"col-md-12\">" + content + "</div></div></div></div>";
|
||||
}
|
||||
|
||||
private String wrapInOverviewTab(String content) {
|
||||
return "<div class=\"tab\" id=\"" + NavLink.format("plugins-overview") + "\"><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>${serverName} · Plugins Overview</h1>${backButton}" +
|
||||
"</div>" +
|
||||
// End Page heading
|
||||
"<div class=\"row\" data-masonry='{\"percentPosition\": true}'>" + content + "</div></div></div>";
|
||||
}
|
||||
|
||||
private TabsElement.Tab wrapToTabElementTab(ExtensionTabData tabData) {
|
||||
TabInformation tabInformation = tabData.getTabInformation();
|
||||
String tabContentHtml = buildContentHtml(tabData);
|
||||
|
||||
String tabName = tabInformation.getTabName();
|
||||
return tabName.isEmpty() ? new TabsElement.Tab(Icon.called("info-circle").build(), "General", tabContentHtml)
|
||||
: new TabsElement.Tab(Icon.fromExtensionIcon(tabInformation.getTabIcon()), tabName, tabContentHtml);
|
||||
}
|
||||
|
||||
private String buildContentHtml(ExtensionTabData tabData) {
|
||||
TabInformation tabInformation = tabData.getTabInformation();
|
||||
|
||||
List<ElementOrder> order = tabInformation.getTabElementOrder();
|
||||
String values = buildValuesHtml(tabData);
|
||||
String valuesHtml = values.isEmpty() ? "" : "<div class=\"card-body\">" + values + "</div>";
|
||||
String tablesHtml = buildTablesHtml(tabData);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (ElementOrder ordering : order) {
|
||||
switch (ordering) {
|
||||
case VALUES:
|
||||
builder.append(valuesHtml);
|
||||
break;
|
||||
case TABLE:
|
||||
builder.append(tablesHtml);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String buildTablesHtml(ExtensionTabData tabData) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (ExtensionTableData tableData : tabData.getTableData()) {
|
||||
builder.append(tableData.getHtmlTable().toHtml());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String buildValuesHtml(ExtensionTabData tabData) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String key : tabData.getValueOrder()) {
|
||||
tabData.getBoolean(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue()));
|
||||
tabData.getDouble(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue(decimalFormatter)));
|
||||
tabData.getPercentage(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue(percentageFormatter)));
|
||||
tabData.getNumber(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue(numberFormatters.get(data.getFormatType()))));
|
||||
tabData.getString(key).ifPresent(data -> append(builder, data.getDescription(), data.getFormattedValue()));
|
||||
tabData.getComponent(key).ifPresent(data -> append(builder, data.getDescription(), data.getHtmlValue(componentSvc)));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void append(StringBuilder builder, ExtensionDescription description, String formattedValue) {
|
||||
Optional<String> textDescription = description.getDescription();
|
||||
if (textDescription.isPresent()) {
|
||||
builder.append("<p title=\"").append(textDescription.get()).append("\">");
|
||||
} else {
|
||||
builder.append("<p>");
|
||||
}
|
||||
builder.append(Icon.fromExtensionIcon(description.getIcon()))
|
||||
.append(' ').append(description.getText()).append("<span class=\"float-end\"><b>").append(formattedValue).append("</b></span></p>");
|
||||
}
|
||||
|
||||
private String wrapInContainer(ExtensionInformation information, String tabsElement) {
|
||||
return "<div class=\"col-lg-6 col-xxl-4\">" +
|
||||
"<div class=\"card shadow mb-4\">" +
|
||||
"<div class=\"card-header py-3\">" +
|
||||
"<h6 class=\"m-0 fw-bold col-black\">" + Icon.fromExtensionIcon(information.getIcon()) + ' ' + information.getPluginName() + "</h6>" +
|
||||
"</div>" +
|
||||
tabsElement +
|
||||
"</div>" +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
private String wrapInWideContainer(ExtensionInformation information, String tabsElement) {
|
||||
return "<div class=\"card shadow mb-4\">" +
|
||||
"<div class=\"card-header py-3\">" +
|
||||
"<h6 class=\"m-0 fw-bold col-black\">" + Icon.fromExtensionIcon(information.getIcon()) + ' ' + information.getPluginName() + "</h6>" +
|
||||
"</div>" +
|
||||
tabsElement +
|
||||
"</div>";
|
||||
}
|
||||
}
|
@ -1,145 +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.web;
|
||||
|
||||
import com.djrapitops.plan.TaskSystem;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.settings.config.ConfigNode;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import net.playeranalytics.plugin.scheduling.RunnableFactory;
|
||||
import net.playeranalytics.plugin.server.PluginLogger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Task in charge of checking html customized files on enable to see if they are outdated.
|
||||
*
|
||||
* @deprecated Html customization system will be overhauled for React version of frontend.
|
||||
*/
|
||||
@Singleton
|
||||
@Deprecated(forRemoval = true, since = "#2260") // TODO Remove after Frontend BETA
|
||||
public class WebAssetVersionCheckTask extends TaskSystem.Task {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final PlanFiles files;
|
||||
private final PluginLogger logger;
|
||||
private final AssetVersions assetVersions;
|
||||
private final Formatters formatters;
|
||||
|
||||
@Inject
|
||||
public WebAssetVersionCheckTask(
|
||||
PlanConfig config,
|
||||
PlanFiles files,
|
||||
PluginLogger logger,
|
||||
AssetVersions assetVersions,
|
||||
Formatters formatters
|
||||
) {
|
||||
this.config = config;
|
||||
this.files = files;
|
||||
this.logger = logger;
|
||||
this.assetVersions = assetVersions;
|
||||
this.formatters = formatters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(RunnableFactory runnableFactory) {
|
||||
runnableFactory.create(this).runTaskLaterAsynchronously(3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
runTask();
|
||||
} finally {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void runTask() {
|
||||
Optional<ConfigNode> planCustomizationNode = getPlanCustomizationNode();
|
||||
if (planCustomizationNode.isPresent()) {
|
||||
try {
|
||||
assetVersions.prepare();
|
||||
} catch (IOException e) {
|
||||
logger.warn(String.format("Could not read web asset versions, %s", e.toString()));
|
||||
logger.warn("Web asset version check will be skipped!");
|
||||
return;
|
||||
}
|
||||
|
||||
List<AssetInfo> outdated = new ArrayList<>();
|
||||
|
||||
for (ConfigNode child : planCustomizationNode.get().getChildren()) {
|
||||
if (child.getBoolean()) {
|
||||
String resource = child.getKey(false).replace(',', '.');
|
||||
findOutdatedResource(resource).ifPresent(outdated::add);
|
||||
}
|
||||
}
|
||||
|
||||
if (!outdated.isEmpty()) {
|
||||
logger.warn("You have customized files which are out of date due to recent updates!");
|
||||
logger.warn("Plan may not work properly until these files are updated to include the new modifications.");
|
||||
logger.warn("See https://github.com/plan-player-analytics/Plan/commits/html to compare changes");
|
||||
}
|
||||
for (AssetInfo asset : outdated) {
|
||||
logger.warn(String.format("- %s was modified %s, but the plugin contains a version from %s",
|
||||
asset.filename,
|
||||
formatters.secondLong().apply(asset.modifiedAt),
|
||||
formatters.secondLong().apply(asset.expectedModifiedAt)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<AssetInfo> findOutdatedResource(String resource) {
|
||||
Path dir = config.getResourceSettings().getCustomizationDirectory();
|
||||
Optional<File> resourceFile = files.attemptToFind(dir, resource);
|
||||
Optional<Long> webAssetVersion = assetVersions.getAssetVersion(resource);
|
||||
if (resourceFile.isPresent() && webAssetVersion.isPresent() && webAssetVersion.get() > resourceFile.get().lastModified()) {
|
||||
return Optional.of(new AssetInfo(
|
||||
resource,
|
||||
resourceFile.get().lastModified(),
|
||||
webAssetVersion.get()
|
||||
));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ConfigNode> getPlanCustomizationNode() {
|
||||
return config.getResourceSettings().getCustomizationConfigNode().getNode("Plan");
|
||||
}
|
||||
|
||||
private static class AssetInfo {
|
||||
private final String filename;
|
||||
private final long modifiedAt;
|
||||
private final long expectedModifiedAt;
|
||||
|
||||
public AssetInfo(String filename, long modifiedAt, long expectedModifiedAt) {
|
||||
this.filename = filename;
|
||||
this.modifiedAt = modifiedAt;
|
||||
this.expectedModifiedAt = expectedModifiedAt;
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,6 @@ import com.djrapitops.plan.delivery.rendering.html.icon.Family;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.delivery.rendering.pages.Page;
|
||||
import com.djrapitops.plan.delivery.rendering.pages.PageFactory;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.ResponseBuilder;
|
||||
@ -34,7 +33,6 @@ import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.lang.ErrorPageLang;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
@ -71,7 +69,6 @@ public class ResponseFactory {
|
||||
private static final String STATIC_BUNDLE_FOLDER = "static";
|
||||
|
||||
private final PlanFiles files;
|
||||
private final PlanConfig config;
|
||||
private final PublicHtmlFiles publicHtmlFiles;
|
||||
private final PageFactory pageFactory;
|
||||
private final Locale locale;
|
||||
@ -92,7 +89,6 @@ public class ResponseFactory {
|
||||
Lazy<Addresses> addresses
|
||||
) {
|
||||
this.files = files;
|
||||
this.config = config;
|
||||
this.publicHtmlFiles = publicHtmlFiles;
|
||||
this.pageFactory = pageFactory;
|
||||
this.locale = locale;
|
||||
@ -103,14 +99,6 @@ public class ResponseFactory {
|
||||
httpLastModifiedFormatter = formatters.httpLastModifiedLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UncheckedIOException If reading the resource fails
|
||||
*/
|
||||
public WebResource getResource(@Untrusted String resourceName) {
|
||||
return ResourceService.getInstance().getResource("Plan", resourceName,
|
||||
() -> files.getResourceFromJar("web/" + resourceName).asWebResource());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UncheckedIOException If reading the resource fails
|
||||
*/
|
||||
@ -191,7 +179,7 @@ public class ResponseFactory {
|
||||
}
|
||||
|
||||
private Response getCachedOrNew(long modified, String fileName, Function<String, Response> newResponseFunction) {
|
||||
WebResource resource = config.isFalse(PluginSettings.LEGACY_FRONTEND) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
WebResource resource = getPublicOrJarResource(fileName);
|
||||
Optional<Long> lastModified = resource.getLastModified();
|
||||
if (lastModified.isPresent() && modified == lastModified.get()) {
|
||||
return browserCachedNotChangedResponse();
|
||||
@ -240,16 +228,10 @@ public class ResponseFactory {
|
||||
|
||||
public Response javaScriptResponse(@Untrusted String fileName) {
|
||||
try {
|
||||
WebResource resource = config.isFalse(PluginSettings.LEGACY_FRONTEND) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
WebResource resource = getPublicOrJarResource(fileName);
|
||||
String content = UnaryChain.of(resource.asString())
|
||||
.chain(this::replaceMainAddressPlaceholder)
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(contents -> {
|
||||
if (fileName.contains(STATIC_BUNDLE_FOLDER) || fileName.startsWith("vendor/") || fileName.startsWith("/vendor/")) {
|
||||
return contents;
|
||||
}
|
||||
return locale.replaceLanguageInJavascript(contents);
|
||||
})
|
||||
.chain(contents -> StringUtils.replace(contents,
|
||||
".p=\"/\"",
|
||||
".p=\"" + getBasePath() + "/\""))
|
||||
@ -290,7 +272,7 @@ public class ResponseFactory {
|
||||
|
||||
public Response cssResponse(@Untrusted String fileName) {
|
||||
try {
|
||||
WebResource resource = config.isFalse(PluginSettings.LEGACY_FRONTEND) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
WebResource resource = getPublicOrJarResource(fileName);
|
||||
String content = UnaryChain.of(resource.asString())
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(contents -> StringUtils.replace(contents, "/static", getBasePath() + "/static"))
|
||||
@ -320,7 +302,7 @@ public class ResponseFactory {
|
||||
|
||||
public Response imageResponse(@Untrusted String fileName) {
|
||||
try {
|
||||
WebResource resource = config.isFalse(PluginSettings.LEGACY_FRONTEND) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
WebResource resource = getPublicOrJarResource(fileName);
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(MimeType.IMAGE)
|
||||
.setContent(resource)
|
||||
@ -356,7 +338,7 @@ public class ResponseFactory {
|
||||
type = MimeType.FONT_BYTESTREAM;
|
||||
}
|
||||
try {
|
||||
WebResource resource = config.isFalse(PluginSettings.LEGACY_FRONTEND) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
WebResource resource = getPublicOrJarResource(fileName);
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(type)
|
||||
.setContent(resource);
|
||||
@ -422,7 +404,7 @@ public class ResponseFactory {
|
||||
try {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.FAVICON)
|
||||
.setContent(getResource("favicon.ico"))
|
||||
.setContent(getPublicOrJarResource("favicon.ico"))
|
||||
.build();
|
||||
} catch (UncheckedIOException e) {
|
||||
return forInternalError(e, "Could not read favicon");
|
||||
@ -431,7 +413,7 @@ public class ResponseFactory {
|
||||
|
||||
public Response robotsResponse() {
|
||||
try {
|
||||
WebResource resource = getResource("robots.txt");
|
||||
WebResource resource = getPublicOrJarResource("robots.txt");
|
||||
Long lastModified = resource.getLastModified().orElseGet(System::currentTimeMillis);
|
||||
return Response.builder()
|
||||
.setMimeType("text/plain")
|
||||
@ -523,13 +505,9 @@ public class ResponseFactory {
|
||||
Database db = dbSystem.getDatabase();
|
||||
PlayerContainer player = db.query(ContainerFetchQueries.fetchPlayerContainer(playerUUID));
|
||||
if (player.getValue(PlayerKeys.REGISTERED).isPresent()) {
|
||||
return forPage(request, pageFactory.playerPage(player));
|
||||
return forPage(request, pageFactory.playerPage());
|
||||
} else {
|
||||
if (config.isTrue(PluginSettings.LEGACY_FRONTEND)) {
|
||||
return playerNotFound404();
|
||||
} else {
|
||||
return forPage(request, pageFactory.reactPage(), 404);
|
||||
}
|
||||
return forPage(request, pageFactory.reactPage(), 404);
|
||||
}
|
||||
} catch (IllegalStateException notFoundLegacy) {
|
||||
return playerNotFound404();
|
||||
@ -574,7 +552,7 @@ public class ResponseFactory {
|
||||
try {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setContent(getResource(file))
|
||||
.setContent(getPublicOrJarResource(file))
|
||||
.build();
|
||||
} catch (UncheckedIOException e) {
|
||||
return forInternalError(e, "Could not read " + file);
|
||||
|
@ -17,11 +17,8 @@
|
||||
package com.djrapitops.plan.delivery.webserver;
|
||||
|
||||
import com.djrapitops.plan.SubSystem;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieStore;
|
||||
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.storage.file.PublicHtmlFiles;
|
||||
import net.playeranalytics.plugin.server.PluginLogger;
|
||||
|
||||
@ -36,7 +33,6 @@ import javax.inject.Singleton;
|
||||
@Singleton
|
||||
public class WebServerSystem implements SubSystem {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final Addresses addresses;
|
||||
private final ActiveCookieStore activeCookieStore;
|
||||
private final PublicHtmlFiles publicHtmlFiles;
|
||||
@ -45,13 +41,11 @@ public class WebServerSystem implements SubSystem {
|
||||
|
||||
@Inject
|
||||
public WebServerSystem(
|
||||
PlanConfig config,
|
||||
Addresses addresses,
|
||||
ActiveCookieStore activeCookieStore,
|
||||
PublicHtmlFiles publicHtmlFiles,
|
||||
WebServer webServer,
|
||||
PluginLogger logger) {
|
||||
this.config = config;
|
||||
this.addresses = addresses;
|
||||
this.activeCookieStore = activeCookieStore;
|
||||
this.publicHtmlFiles = publicHtmlFiles;
|
||||
@ -63,24 +57,9 @@ public class WebServerSystem implements SubSystem {
|
||||
public void enable() {
|
||||
activeCookieStore.enable();
|
||||
webServer.enable();
|
||||
if (config.isTrue(PluginSettings.LEGACY_FRONTEND)) {
|
||||
if (!webServer.isAuthRequired()) {
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "error.html", ResourceService.Position.PRE_CONTENT, "./css/noauth.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "server.html", ResourceService.Position.PRE_CONTENT, "../css/noauth.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "player.html", ResourceService.Position.PRE_CONTENT, "../css/noauth.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "players.html", ResourceService.Position.PRE_CONTENT, "./css/noauth.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "network.html", ResourceService.Position.PRE_CONTENT, "./css/noauth.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "query.html", ResourceService.Position.PRE_CONTENT, "./css/noauth.css");
|
||||
}
|
||||
if (webServer.isEnabled()) {
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "server.html", ResourceService.Position.PRE_CONTENT, "../css/querybutton.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "players.html", ResourceService.Position.PRE_CONTENT, "./css/querybutton.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "network.html", ResourceService.Position.PRE_CONTENT, "./css/querybutton.css");
|
||||
}
|
||||
} else {
|
||||
if (publicHtmlFiles.findPublicHtmlResource("index.html").isPresent()) {
|
||||
logger.info("Found index.html in public_html, using a custom React bundle!");
|
||||
}
|
||||
|
||||
if (publicHtmlFiles.findPublicHtmlResource("index.html").isPresent()) {
|
||||
logger.info("Found index.html in public_html, using a custom React bundle!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ import com.djrapitops.plan.identification.ServerUUID;
|
||||
*/
|
||||
public enum DataID {
|
||||
PLAYERS,
|
||||
PLAYERS_V2,
|
||||
SESSIONS,
|
||||
SERVERS,
|
||||
KILLS,
|
||||
|
@ -17,7 +17,6 @@
|
||||
package com.djrapitops.plan.delivery.webserver.resolver;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Html;
|
||||
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;
|
||||
@ -26,7 +25,6 @@ import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||
import com.djrapitops.plan.identification.UUIDUtility;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -44,7 +42,6 @@ import java.util.UUID;
|
||||
@Singleton
|
||||
public class PlayerPageResolver implements Resolver {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final ResponseFactory responseFactory;
|
||||
private final UUIDUtility uuidUtility;
|
||||
|
||||
@ -54,7 +51,6 @@ public class PlayerPageResolver implements Resolver {
|
||||
ResponseFactory responseFactory,
|
||||
UUIDUtility uuidUtility
|
||||
) {
|
||||
this.config = config;
|
||||
this.responseFactory = responseFactory;
|
||||
this.uuidUtility = uuidUtility;
|
||||
}
|
||||
@ -99,10 +95,6 @@ public class PlayerPageResolver implements Resolver {
|
||||
return responseFactory.rawPlayerPageResponse(playerUUID);
|
||||
}
|
||||
|
||||
if (path.getPart(2).isPresent() && config.isTrue(PluginSettings.LEGACY_FRONTEND)) {
|
||||
// Redirect /player/{uuid/name}/ to /player/{uuid}
|
||||
return responseFactory.redirectResponse("../" + Html.encodeToURL(playerUUID.toString()));
|
||||
}
|
||||
return responseFactory.playerPageResponse(request, playerUUID);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.auth.WebPermission;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
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;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
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.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Resolves /v1/players JSON requests.
|
||||
* <p>
|
||||
* Deprecated.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/players")
|
||||
public class PlayersJSONResolver extends JSONResolver {
|
||||
|
||||
private final Identifiers identifiers;
|
||||
private final AsyncJSONResolverService jsonResolverService;
|
||||
private final JSONFactory jsonFactory;
|
||||
|
||||
@Inject
|
||||
public PlayersJSONResolver(
|
||||
Identifiers identifiers,
|
||||
AsyncJSONResolverService jsonResolverService,
|
||||
JSONFactory jsonFactory
|
||||
) {
|
||||
this.identifiers = identifiers;
|
||||
this.jsonResolverService = jsonResolverService;
|
||||
this.jsonFactory = jsonFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {return jsonResolverService.getHttpLastModifiedFormatter();}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
WebUser user = request.getUser().orElse(new WebUser(""));
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
return user.hasPermission(WebPermission.PAGE_SERVER_PLAYERS);
|
||||
}
|
||||
// Assume players page
|
||||
return user.hasPermission(WebPermission.ACCESS_PLAYERS)
|
||||
|| user.hasPermission(WebPermission.ACCESS_NETWORK) && user.hasPermission(WebPermission.PAGE_NETWORK_PLAYERS);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
description = "Get player table data for /players page or a server. Deprecated, use /v1/playersTable instead.",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)),
|
||||
},
|
||||
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
|
||||
@ExampleObject("Server 1"),
|
||||
@ExampleObject("1"),
|
||||
@ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"),
|
||||
}),
|
||||
requestBody = @RequestBody(content = @Content(examples = @ExampleObject())),
|
||||
deprecated = true
|
||||
)
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
return Optional.of(getResponse(request));
|
||||
}
|
||||
|
||||
private Response getResponse(Request request) {
|
||||
JSONStorage.StoredJSON storedJSON = getStoredJSON(request);
|
||||
return getCachedOrNewResponse(request, storedJSON);
|
||||
}
|
||||
|
||||
private JSONStorage.StoredJSON getStoredJSON(@Untrusted Request request) {
|
||||
Optional<Long> timestamp = Identifiers.getTimestamp(request);
|
||||
JSONStorage.StoredJSON storedJSON;
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
||||
storedJSON = jsonResolverService.resolve(timestamp, DataID.PLAYERS, serverUUID, uuid -> jsonFactory.serverPlayersTableJSON(uuid).toJSONMap());
|
||||
} else {
|
||||
// Assume players page
|
||||
storedJSON = jsonResolverService.resolve(timestamp, DataID.PLAYERS, () -> jsonFactory.networkPlayersTableJSON().toJSONMap());
|
||||
}
|
||||
return storedJSON;
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerListDto;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
@ -34,6 +35,7 @@ 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;
|
||||
@ -44,12 +46,12 @@ import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Resolves /v1/players JSON requests.
|
||||
* Resolves /v1/playersTable JSON requests.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/players")
|
||||
@Path("/v1/playersTable")
|
||||
public class PlayersTableJSONResolver extends JSONResolver {
|
||||
|
||||
private final Identifiers identifiers;
|
||||
@ -92,7 +94,7 @@ public class PlayersTableJSONResolver extends JSONResolver {
|
||||
@ExampleObject("1"),
|
||||
@ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"),
|
||||
}),
|
||||
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
|
||||
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = PlayerListDto.class)))
|
||||
)
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
@ -109,10 +111,10 @@ public class PlayersTableJSONResolver extends JSONResolver {
|
||||
JSONStorage.StoredJSON storedJSON;
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
||||
storedJSON = jsonResolverService.resolve(timestamp, DataID.PLAYERS, serverUUID, jsonFactory::serverPlayersTableJSON);
|
||||
storedJSON = jsonResolverService.resolve(timestamp, DataID.PLAYERS_V2, serverUUID, uuid -> jsonFactory.serverPlayersTableJSON(uuid).toPlayerList());
|
||||
} else {
|
||||
// Assume players page
|
||||
storedJSON = jsonResolverService.resolve(timestamp, DataID.PLAYERS, jsonFactory::networkPlayersTableJSON);
|
||||
storedJSON = jsonResolverService.resolve(timestamp, DataID.PLAYERS_V2, () -> jsonFactory.networkPlayersTableJSON().toPlayerList());
|
||||
}
|
||||
return storedJSON;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import com.djrapitops.plan.delivery.domain.DateMap;
|
||||
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.InputQueryDto;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerListDto;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.ViewDto;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
@ -255,13 +256,13 @@ public class QueryJSONResolver implements Resolver {
|
||||
return graphJSONCreator.createActivityGraphJSON(activityData);
|
||||
}
|
||||
|
||||
private Map<String, Object> getPlayersTableData(Set<Integer> userIds, List<ServerUUID> serverUUIDs, long after, long before) {
|
||||
private PlayerListDto getPlayersTableData(Set<Integer> userIds, List<ServerUUID> serverUUIDs, long after, long before) {
|
||||
Database database = dbSystem.getDatabase();
|
||||
return new PlayersTableJSONCreator(
|
||||
database.query(new QueryTablePlayersQuery(userIds, serverUUIDs, after, before, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))),
|
||||
database.query(new ExtensionQueryResultTableDataQuery(serverInfo.getServerUUID(), userIds)),
|
||||
config.get(DisplaySettings.OPEN_PLAYER_LINKS_IN_NEW_TAB),
|
||||
formatters, locale
|
||||
).toJSONMap();
|
||||
).toPlayerList();
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ import com.djrapitops.plan.delivery.web.resolver.CompositeResolver;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.metadata.PreferencesJSONResolver;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.metadata.StorePreferencesJSONResolver;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import dagger.Lazy;
|
||||
|
||||
@ -46,6 +48,7 @@ public class RootJSONResolver {
|
||||
private final WebGroupDeleteJSONResolver webGroupDeleteJSONResolver;
|
||||
|
||||
private final CompositeResolver.Builder readOnlyResourcesBuilder;
|
||||
private final StorePreferencesJSONResolver storePreferencesJSONResolver;
|
||||
private CompositeResolver resolver;
|
||||
|
||||
@Inject
|
||||
@ -57,6 +60,7 @@ public class RootJSONResolver {
|
||||
|
||||
GraphsJSONResolver graphsJSONResolver,
|
||||
SessionsJSONResolver sessionsJSONResolver,
|
||||
PlayersJSONResolver playersJSONResolver,
|
||||
PlayersTableJSONResolver playersTableJSONResolver,
|
||||
ServerOverviewJSONCreator serverOverviewJSONCreator,
|
||||
OnlineActivityOverviewJSONCreator onlineActivityOverviewJSONCreator,
|
||||
@ -81,6 +85,8 @@ public class RootJSONResolver {
|
||||
RetentionJSONResolver retentionJSONResolver,
|
||||
PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver,
|
||||
|
||||
PreferencesJSONResolver preferencesJSONResolver,
|
||||
StorePreferencesJSONResolver storePreferencesJSONResolver,
|
||||
WebGroupJSONResolver webGroupJSONResolver,
|
||||
WebGroupPermissionJSONResolver webGroupPermissionJSONResolver,
|
||||
WebPermissionJSONResolver webPermissionJSONResolver,
|
||||
@ -91,7 +97,8 @@ public class RootJSONResolver {
|
||||
this.asyncJSONResolverService = asyncJSONResolverService;
|
||||
|
||||
readOnlyResourcesBuilder = CompositeResolver.builder()
|
||||
.add("players", playersTableJSONResolver)
|
||||
.add("players", playersJSONResolver)
|
||||
.add("playersTable", playersTableJSONResolver)
|
||||
.add("sessions", sessionsJSONResolver)
|
||||
.add("kills", playerKillsJSONResolver)
|
||||
.add("graph", graphsJSONResolver)
|
||||
@ -115,7 +122,8 @@ public class RootJSONResolver {
|
||||
.add("whoami", whoAmIJSONResolver)
|
||||
.add("extensionData", extensionJSONResolver)
|
||||
.add("retention", retentionJSONResolver)
|
||||
.add("joinAddresses", playerJoinAddressJSONResolver);
|
||||
.add("joinAddresses", playerJoinAddressJSONResolver)
|
||||
.add("preferences", preferencesJSONResolver);
|
||||
|
||||
this.webServer = webServer;
|
||||
// These endpoints require authentication to be enabled.
|
||||
@ -124,6 +132,7 @@ public class RootJSONResolver {
|
||||
this.webPermissionJSONResolver = webPermissionJSONResolver;
|
||||
this.webGroupSaveJSONResolver = webGroupSaveJSONResolver;
|
||||
this.webGroupDeleteJSONResolver = webGroupDeleteJSONResolver;
|
||||
this.storePreferencesJSONResolver = storePreferencesJSONResolver;
|
||||
}
|
||||
|
||||
private <T> ServerTabJSONResolver<T> forJSON(DataID dataID, ServerTabJSONCreator<T> tabJSONCreator, WebPermission permission) {
|
||||
@ -139,6 +148,7 @@ public class RootJSONResolver {
|
||||
.add("permissions", webPermissionJSONResolver)
|
||||
.add("saveGroupPermissions", webGroupSaveJSONResolver)
|
||||
.add("deleteGroup", webGroupDeleteJSONResolver)
|
||||
.add("storePreferences", storePreferencesJSONResolver)
|
||||
.build();
|
||||
} else {
|
||||
resolver = readOnlyResourcesBuilder.build();
|
||||
|
@ -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.metadata;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.preferences.Preferences;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents /v1/preferences endpoint.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/preferences")
|
||||
public class PreferencesJSONResolver implements NoAuthResolver {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
|
||||
@Inject
|
||||
public PreferencesJSONResolver(PlanConfig config, DBSystem dbSystem) {
|
||||
this.config = config;
|
||||
this.dbSystem = dbSystem;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
description = "Get user preferences (if they exist) and default preferences",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON))
|
||||
},
|
||||
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
|
||||
)
|
||||
@Override
|
||||
public Optional<Response> resolve(@Untrusted Request request) {
|
||||
Preferences defaultPreferences = config.getDefaultPreferences();
|
||||
|
||||
return Optional.of(Response.builder()
|
||||
.setJSONContent(Maps.builder(String.class, Preferences.class)
|
||||
.put("defaultPreferences", defaultPreferences)
|
||||
.put("preferences", getUserPreferences(request))
|
||||
.build()
|
||||
).build());
|
||||
}
|
||||
|
||||
private Preferences getUserPreferences(Request request) {
|
||||
// No user -> no preferences
|
||||
// No stored preferences -> no preferences
|
||||
return request.getUser()
|
||||
.flatMap(user -> dbSystem.getDatabase().query(WebUserQueries.fetchPreferences(user)))
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.metadata;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.preferences.Preferences;
|
||||
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.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.transactions.StoreWebUserPreferencesTransaction;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
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.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/storePreferences")
|
||||
public class StorePreferencesJSONResolver implements Resolver {
|
||||
|
||||
private final DBSystem dbSystem;
|
||||
|
||||
@Inject
|
||||
public StorePreferencesJSONResolver(DBSystem dbSystem) {this.dbSystem = dbSystem;}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return request.getUser().isPresent();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Operation(
|
||||
description = "Update user preferences",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "Storage was successful"),
|
||||
@ApiResponse(responseCode = "400", description = "Not logged in (This endpoint only accepts requests if logged in)"),
|
||||
@ApiResponse(responseCode = "400", description = "Request body does not match json format of preferences"),
|
||||
},
|
||||
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = Preferences.class)))
|
||||
)
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
if (!request.getMethod().equals("POST")) {
|
||||
throw new BadRequestException("This endpoint only accepts POST requests.");
|
||||
}
|
||||
WebUser user = request.getUser()
|
||||
.orElseThrow(() -> new BadRequestException("This endpoint only accepts requests if logged in."));
|
||||
String preferencesBody = new String(request.getRequestBody(), StandardCharsets.UTF_8);
|
||||
try {
|
||||
Gson gson = new Gson();
|
||||
@Untrusted String syntaxSanitized = gson.toJson(gson.fromJson(preferencesBody, Preferences.class));
|
||||
dbSystem.getDatabase().executeTransaction(new StoreWebUserPreferencesTransaction(syntaxSanitized, user));
|
||||
} catch (JsonSyntaxException invalidSyntax) {
|
||||
throw new BadRequestException("Request body does not match json format of preferences");
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -16,11 +16,6 @@
|
||||
*/
|
||||
package com.djrapitops.plan.extension.implementation.results;
|
||||
|
||||
import com.djrapitops.plan.component.Component;
|
||||
import com.djrapitops.plan.component.ComponentOperation;
|
||||
import com.djrapitops.plan.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.rendering.html.Html;
|
||||
|
||||
/**
|
||||
* Represents component data returned by a ComponentProvider method.
|
||||
*
|
||||
@ -44,8 +39,4 @@ public class ExtensionComponentData implements DescribedExtensionData {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getHtmlValue(ComponentSvc service) {
|
||||
String legacy = service.convert(service.fromJson(value), ComponentOperation.LEGACY, Component.SECTION);
|
||||
return Html.swapColorCodesToSpan(legacy);
|
||||
}
|
||||
}
|
@ -18,6 +18,8 @@ package com.djrapitops.plan.extension.implementation.results;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.html.Html;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents double data returned by a DoubleProvider or PercentageProvider method.
|
||||
*
|
||||
@ -51,9 +53,19 @@ public class ExtensionStringData implements DescribedExtensionData {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public String getFormattedValue() {
|
||||
String withColors = Html.swapColorCodesToSpan(value);
|
||||
return !playerName ? withColors : Html.LINK.create("../player/" + Html.encodeToURL(value), withColors);
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Object getFormattedValue() {
|
||||
if (playerName) {
|
||||
return Map.of(
|
||||
"link", "/player/" + Html.encodeToURL(value),
|
||||
"text", value
|
||||
);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
ExtensionStringData concatenate(ExtensionStringData other) {
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
package com.djrapitops.plan.extension.implementation.results;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.html.structure.HtmlTable;
|
||||
import com.djrapitops.plan.extension.icon.Color;
|
||||
import com.djrapitops.plan.extension.table.Table;
|
||||
|
||||
@ -39,10 +38,6 @@ public class ExtensionTableData implements Comparable<ExtensionTableData> {
|
||||
this.tableColor = tableColor;
|
||||
}
|
||||
|
||||
public HtmlTable getHtmlTable() {
|
||||
return HtmlTable.fromExtensionTable(table, tableColor);
|
||||
}
|
||||
|
||||
public String getProviderName() {
|
||||
return providerName;
|
||||
}
|
||||
|
@ -16,6 +16,10 @@
|
||||
*/
|
||||
package com.djrapitops.plan.settings.config;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.preferences.GraphThresholds;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.preferences.Preferences;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.preferences.TimeFormat;
|
||||
import com.djrapitops.plan.settings.config.paths.DisplaySettings;
|
||||
import com.djrapitops.plan.settings.config.paths.ExportSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.FormatSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.key.Setting;
|
||||
@ -154,6 +158,36 @@ public class PlanConfig extends Config {
|
||||
return worldAliasSettings;
|
||||
}
|
||||
|
||||
public Preferences getDefaultPreferences() {
|
||||
return Preferences.builder()
|
||||
.withDateFormatFull(get(FormatSettings.DATE_FULL))
|
||||
.withDateFormatNoSeconds(get(FormatSettings.DATE_NO_SECONDS))
|
||||
.withDateFormatClock(get(FormatSettings.DATE_CLOCK))
|
||||
.withRecentDaysInDateFormat(isTrue(FormatSettings.DATE_RECENT_DAYS))
|
||||
.withDecimalFormat(get(FormatSettings.DECIMALS))
|
||||
.withFirstDay(1) // 1 is Monday
|
||||
.withTimeFormat(TimeFormat.builder()
|
||||
.withYear(get(FormatSettings.YEAR))
|
||||
.withYears(get(FormatSettings.YEARS))
|
||||
.withMonth(get(FormatSettings.MONTH))
|
||||
.withMonths(get(FormatSettings.MONTHS))
|
||||
.withDay(get(FormatSettings.DAY))
|
||||
.withDays(get(FormatSettings.DAYS))
|
||||
.withHours(get(FormatSettings.HOURS))
|
||||
.withMinutes(get(FormatSettings.MINUTES))
|
||||
.withSeconds(get(FormatSettings.SECONDS))
|
||||
.withZero(get(FormatSettings.ZERO_SECONDS))
|
||||
.build())
|
||||
.withPlayerHeadImageUrl(get(DisplaySettings.PLAYER_HEAD_IMG_URL))
|
||||
.withTpsThresholds(new GraphThresholds(
|
||||
get(DisplaySettings.GRAPH_TPS_THRESHOLD_HIGH),
|
||||
get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED)))
|
||||
.withDiskThresholds(new GraphThresholds(
|
||||
get(DisplaySettings.GRAPH_DISK_THRESHOLD_HIGH),
|
||||
get(DisplaySettings.GRAPH_DISK_THRESHOLD_MED)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -17,7 +17,6 @@
|
||||
package com.djrapitops.plan.settings.config;
|
||||
|
||||
import com.djrapitops.plan.settings.config.paths.CustomizedFileSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
@ -42,10 +41,6 @@ public class ResourceSettings {
|
||||
}
|
||||
|
||||
public boolean shouldBeCustomized(String plugin, @Untrusted String fileName) {
|
||||
if (config.isTrue(CustomizedFileSettings.WEB_DEV_MODE) && config.isTrue(PluginSettings.LEGACY_FRONTEND)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ConfigNode fileCustomization = getCustomizationConfigNode();
|
||||
fileCustomization.setComment(Collections.singletonList("The files are placed in /Plan/web/ if the setting is 'true' when accessed."));
|
||||
|
||||
|
@ -174,6 +174,9 @@ public class ConfigUpdater {
|
||||
new ConfigChange.MovedValue("Time.Thresholds.Activity_index.Playtime_threshold", "Time.Thresholds.Activity_index.Playtime_threshold.Time"),
|
||||
|
||||
new ConfigChange.Removed("Plugin.Frontend_BETA"),
|
||||
new ConfigChange.Removed("Plugin.Use_Legacy_Frontend"),
|
||||
new ConfigChange.Removed("Customized_files.Enable_web_dev_mode"),
|
||||
new ConfigChange.Removed("Customized_files.Plan"),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -16,12 +16,10 @@
|
||||
*/
|
||||
package com.djrapitops.plan.settings.config.paths;
|
||||
|
||||
import com.djrapitops.plan.settings.config.paths.key.BooleanSetting;
|
||||
import com.djrapitops.plan.settings.config.paths.key.Setting;
|
||||
import com.djrapitops.plan.settings.config.paths.key.StringSetting;
|
||||
|
||||
public class CustomizedFileSettings {
|
||||
public static final Setting<Boolean> WEB_DEV_MODE = new BooleanSetting("Customized_files.Enable_web_dev_mode");
|
||||
public static final Setting<String> PATH = new StringSetting("Customized_files.Path");
|
||||
|
||||
private CustomizedFileSettings() {
|
||||
|
@ -42,7 +42,6 @@ public class PluginSettings {
|
||||
public static final Setting<Boolean> CHECK_FOR_UPDATES = new BooleanSetting("Plugin.Update_notifications.Check_for_updates");
|
||||
public static final Setting<Boolean> NOTIFY_ABOUT_DEV_RELEASES = new BooleanSetting("Plugin.Update_notifications.Notify_about_DEV_releases");
|
||||
public static final Setting<Boolean> PROXY_COPY_CONFIG = new BooleanSetting("Plugin.Configuration.Allow_proxy_to_manage_settings");
|
||||
public static final Setting<Boolean> LEGACY_FRONTEND = new BooleanSetting("Plugin.Use_Legacy_Frontend");
|
||||
|
||||
private PluginSettings() {
|
||||
/* static variable class */
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package com.djrapitops.plan.settings.locale;
|
||||
|
||||
import com.djrapitops.plan.settings.locale.lang.HtmlLang;
|
||||
import com.djrapitops.plan.settings.locale.lang.JSLang;
|
||||
import com.djrapitops.plan.settings.locale.lang.Lang;
|
||||
import com.djrapitops.plan.storage.file.FileResource;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
@ -25,9 +23,9 @@ import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents loaded language information.
|
||||
@ -36,8 +34,6 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class Locale extends HashMap<Lang, Message> {
|
||||
|
||||
private static final Pattern FIND_SCRIPT = Pattern.compile("(<script id=[\"|'].*[\"|']>[\\s\\S]*?</script>|<script>[\\s\\S]*?</script>|<script src=[\"|'].*[\"|']></script>|<link [\\s\\S]*?>)");
|
||||
|
||||
public static Locale forLangCodeString(PlanFiles files, String code) throws IOException {
|
||||
return forLangCode(LangCode.fromString(code), files);
|
||||
}
|
||||
@ -108,99 +104,6 @@ public class Locale extends HashMap<Lang, Message> {
|
||||
this.langCode = locale.langCode;
|
||||
}
|
||||
|
||||
public String replaceLanguageInHtml(String from) {
|
||||
if (isEmpty()) {
|
||||
return from;
|
||||
}
|
||||
|
||||
Matcher scriptMatcher = FIND_SCRIPT.matcher(from);
|
||||
List<String> foundScripts = new ArrayList<>();
|
||||
while (scriptMatcher.find()) {
|
||||
foundScripts.add(scriptMatcher.toMatchResult().group(0));
|
||||
}
|
||||
|
||||
TranslatedString translated = new TranslatedString(from);
|
||||
Arrays.stream(HtmlLang.values())
|
||||
// Longest first so that entries that contain each other don't partially replace.
|
||||
.sorted((one, two) -> Integer.compare(
|
||||
two.getIdentifier().length(),
|
||||
one.getIdentifier().length()
|
||||
))
|
||||
.forEach(lang -> getNonDefault(lang).ifPresent(replacement ->
|
||||
translated.translate(lang.getDefault(), replacement.toString()))
|
||||
);
|
||||
|
||||
StringBuilder complete = new StringBuilder(translated.length());
|
||||
|
||||
String[] parts = FIND_SCRIPT.split(translated.toString());
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
complete.append(parts[i]);
|
||||
if (i < parts.length - 1) {
|
||||
complete.append(replaceLanguageInJavascript(foundScripts.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
return complete.toString();
|
||||
}
|
||||
|
||||
public String replaceLanguageInJavascript(String from) {
|
||||
if (isEmpty()) {
|
||||
return from;
|
||||
}
|
||||
|
||||
TranslatedString translated = new TranslatedString(from);
|
||||
Arrays.stream(JSLang.values())
|
||||
// Longest first so that entries that contain each other don't partially replace.
|
||||
.sorted((one, two) -> Integer.compare(
|
||||
two.getIdentifier().length(),
|
||||
one.getIdentifier().length()
|
||||
))
|
||||
.forEach(lang -> getNonDefault(lang).ifPresent(replacement ->
|
||||
translated.translate(lang.getDefault(), replacement.toString()))
|
||||
);
|
||||
|
||||
for (Lang extra : new Lang[]{
|
||||
HtmlLang.UNIT_NO_DATA,
|
||||
HtmlLang.TITLE_WORLD_PLAYTIME,
|
||||
// HtmlLang.LABEL_OPERATOR,
|
||||
// HtmlLang.LABEL_BANNED,
|
||||
HtmlLang.SIDE_SESSIONS,
|
||||
HtmlLang.LABEL_PLAYTIME,
|
||||
HtmlLang.LABEL_AFK_TIME,
|
||||
HtmlLang.LABEL_LONGEST_SESSION,
|
||||
HtmlLang.LABEL_SESSION_MEDIAN,
|
||||
HtmlLang.LABEL_PLAYER_KILLS,
|
||||
HtmlLang.LABEL_MOB_KILLS,
|
||||
HtmlLang.LABEL_DEATHS,
|
||||
HtmlLang.LABEL_PLAYERS_ONLINE,
|
||||
HtmlLang.LABEL_REGISTERED,
|
||||
HtmlLang.TITLE_SERVER,
|
||||
HtmlLang.TITLE_LENGTH,
|
||||
HtmlLang.TITLE_AVG_PING,
|
||||
HtmlLang.TITLE_BEST_PING,
|
||||
HtmlLang.TITLE_WORST_PING,
|
||||
HtmlLang.LABEL_FREE_DISK_SPACE,
|
||||
HtmlLang.LABEL_NEW_PLAYERS,
|
||||
HtmlLang.LABEL_UNIQUE_PLAYERS,
|
||||
HtmlLang.LABEL_ACTIVE_PLAYTIME,
|
||||
HtmlLang.LABEL_AFK_TIME,
|
||||
HtmlLang.LABEL_AVG_SESSION_LENGTH,
|
||||
HtmlLang.LABEL_AVG_PLAYTIME,
|
||||
HtmlLang.LABEL_AVG_ACTIVE_PLAYTIME,
|
||||
HtmlLang.LABEL_AVG_AFK_TIME,
|
||||
HtmlLang.LABEL_AVG_PLAYTIME,
|
||||
HtmlLang.SIDE_GEOLOCATIONS,
|
||||
HtmlLang.LABEL_PER_PLAYER,
|
||||
HtmlLang.TITLE_JOIN_ADDRESSES
|
||||
|
||||
}) {
|
||||
getNonDefault(extra).ifPresent(replacement ->
|
||||
translated.translate(extra.getDefault(), replacement.toString()));
|
||||
}
|
||||
|
||||
return translated.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -1,133 +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.settings.locale;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility for translating String.
|
||||
* <p>
|
||||
* Improves performance by avoiding a double for-each loop since this class can be considered final in the lambda
|
||||
* expression in {@link Locale#replaceLanguageInHtml(String)}.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
class TranslatedString {
|
||||
private static final Pattern LINK_MATCHER = Pattern.compile("http(s|)://[\\w.\\-_%/?$#@!()&=]+");
|
||||
|
||||
private final List<TranslatedString> translating = new LinkedList<>();
|
||||
|
||||
TranslatedString(String translating) {
|
||||
final Matcher matcher = LINK_MATCHER.matcher(translating);
|
||||
int start = 0;
|
||||
while (matcher.find()) {
|
||||
String link = translating.substring(matcher.start(), matcher.end());
|
||||
String prev = translating.substring(start, matcher.start());
|
||||
if (!prev.isEmpty()) {
|
||||
this.translating.add(new Translatable(prev));
|
||||
}
|
||||
start = matcher.end();
|
||||
this.translating.add(new LockedString(link));
|
||||
}
|
||||
String remaining = translating.substring(start);
|
||||
if (!remaining.isEmpty()) {
|
||||
this.translating.add(new Translatable(remaining));
|
||||
}
|
||||
}
|
||||
|
||||
TranslatedString() {
|
||||
}
|
||||
|
||||
public void translate(String replace, String with) {
|
||||
for (TranslatedString sub : translating) {
|
||||
sub.translate(replace, with);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
toString(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public void toString(StringBuilder builder) {
|
||||
for (TranslatedString sub : translating) {
|
||||
sub.toString(builder);
|
||||
}
|
||||
}
|
||||
|
||||
public int length() {
|
||||
int length = 0;
|
||||
for (TranslatedString sub : translating) {
|
||||
length += sub.length();
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static class Translatable extends TranslatedString {
|
||||
|
||||
private String translating;
|
||||
|
||||
Translatable(String translating) {
|
||||
this.translating = translating;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translate(String replace, String with) {
|
||||
translating = StringUtils.replace(translating, replace, with);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(StringBuilder builder) {
|
||||
builder.append(translating);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return translating.length();
|
||||
}
|
||||
}
|
||||
|
||||
static class LockedString extends TranslatedString {
|
||||
final String text;
|
||||
|
||||
LockedString(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translate(String replace, String with) {
|
||||
/* Some elements should not be translated, like URLs */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(StringBuilder builder) {
|
||||
builder.append(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return text.length();
|
||||
}
|
||||
}
|
||||
}
|
@ -269,6 +269,10 @@ public enum HtmlLang implements Lang {
|
||||
LABEL_SERVER_SELECTOR("html.label.serverSelector", "Server selector"),
|
||||
LABEL_APPLY("html.label.apply", "Apply"),
|
||||
|
||||
LABEL_TABLE_VISIBLE_COLUMNS("html.label.table.visibleColumns", "Visible columns"),
|
||||
LABEL_TABLE_SHOW_N_OF_M("html.label.table.showNofM", "Showing {{n}} of {{mn}} entries"),
|
||||
LABEL_TABLE_SHOW_PER_PAGE("html.label.table.showPerPage", "Show per page"),
|
||||
|
||||
LOGIN_LOGIN("html.login.login", "Login"),
|
||||
LOGIN_LOGOUT("html.login.logout", "Logout"),
|
||||
LOGIN_USERNAME("html.login.username", "Username"),
|
||||
|
@ -483,4 +483,21 @@ public class LargeStoreQueries {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Executable storeAllPreferences(Map<String, String> preferencesByUsername) {
|
||||
if (preferencesByUsername.isEmpty()) return Executable.empty();
|
||||
|
||||
return new ExecBatchStatement(WebUserPreferencesTable.INSERT_STATEMENT) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
for (var entry : preferencesByUsername.entrySet()) {
|
||||
String username = entry.getKey();
|
||||
String preferences = entry.getValue();
|
||||
statement.setString(1, preferences);
|
||||
statement.setString(2, username);
|
||||
statement.addBatch();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -17,12 +17,16 @@
|
||||
package com.djrapitops.plan.storage.database.queries.objects;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.auth.User;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.preferences.Preferences;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.storage.database.queries.Query;
|
||||
import com.djrapitops.plan.storage.database.queries.QueryAllStatement;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.*;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.djrapitops.plan.utilities.java.Lists;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
@ -239,4 +243,25 @@ public class WebUserQueries {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<Optional<Preferences>> fetchPreferences(@Untrusted WebUser user) {
|
||||
return db -> db.queryOptional(WebUserPreferencesTable.SELECT_BY_WEB_USERNAME, WebUserQueries::extractPreferences, user.getUsername());
|
||||
}
|
||||
|
||||
private static Preferences extractPreferences(ResultSet set) throws SQLException {
|
||||
try {
|
||||
String preferences = set.getString(WebUserPreferencesTable.PREFERENCES);
|
||||
return new Gson().fromJson(preferences, Preferences.class);
|
||||
} catch (JsonSyntaxException jsonChangedIncompatibly) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Query<Map<String, String>> fetchAllPreferences() {
|
||||
String sql = SELECT + WebUserPreferencesTable.PREFERENCES + "," + SecurityTable.USERNAME +
|
||||
FROM + WebUserPreferencesTable.TABLE_NAME + " p" +
|
||||
INNER_JOIN + SecurityTable.TABLE_NAME + " s ON s." + SecurityTable.ID + "=p." + WebUserPreferencesTable.WEB_USER_ID;
|
||||
return db -> db.queryMap(sql, (results, to) ->
|
||||
to.put(results.getString(SecurityTable.USERNAME), results.getString(WebUserPreferencesTable.PREFERENCES)));
|
||||
}
|
||||
}
|
@ -18,13 +18,16 @@ package com.djrapitops.plan.storage.database.sql.tables;
|
||||
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
|
||||
|
||||
/**
|
||||
* Table information about 'plan_security'
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @see com.djrapitops.plan.storage.database.transactions.patches.LinkedToSecurityTablePatch
|
||||
* @see com.djrapitops.plan.storage.database.transactions.patches.SecurityTableGroupPatch
|
||||
* @see com.djrapitops.plan.storage.database.transactions.patches.SecurityTableIdPatch
|
||||
*/
|
||||
public class SecurityTable {
|
||||
|
||||
@ -36,6 +39,7 @@ public class SecurityTable {
|
||||
public static final String SALT_PASSWORD_HASH = "salted_pass_hash";
|
||||
public static final String GROUP_ID = "group_id";
|
||||
|
||||
public static final String SELECT_ID_BY_USERNAME = SELECT + ID + FROM + TABLE_NAME + WHERE + USERNAME + "=?";
|
||||
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" +
|
||||
USERNAME + ',' +
|
||||
LINKED_TO + ',' +
|
||||
@ -48,11 +52,11 @@ public class SecurityTable {
|
||||
|
||||
public static String createTableSQL(DBType dbType) {
|
||||
return CreateTableBuilder.create(TABLE_NAME, dbType)
|
||||
.column(ID, Sql.INT).primaryKey()
|
||||
.column(USERNAME, Sql.varchar(100)).notNull().unique()
|
||||
.column(LINKED_TO, Sql.varchar(36)).defaultValue("''")
|
||||
.column(SALT_PASSWORD_HASH, Sql.varchar(100)).notNull().unique()
|
||||
.column(GROUP_ID, Sql.INT).notNull()
|
||||
.column(ID, INT).primaryKey()
|
||||
.column(USERNAME, varchar(100)).notNull().unique()
|
||||
.column(LINKED_TO, varchar(36)).defaultValue("''")
|
||||
.column(SALT_PASSWORD_HASH, varchar(100)).notNull().unique()
|
||||
.column(GROUP_ID, INT).notNull()
|
||||
.foreignKey(GROUP_ID, WebGroupTable.TABLE_NAME, WebGroupTable.ID)
|
||||
.toString();
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.storage.database.sql.tables;
|
||||
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
|
||||
|
||||
/**
|
||||
* Represents plan_web_user_preferences.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class WebUserPreferencesTable {
|
||||
|
||||
public static final String TABLE_NAME = "plan_web_user_preferences";
|
||||
|
||||
public static final String ID = "id";
|
||||
public static final String WEB_USER_ID = "web_user_id";
|
||||
public static final String PREFERENCES = "preferences";
|
||||
|
||||
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" + PREFERENCES + ',' + WEB_USER_ID +
|
||||
") VALUES (?, (" + SecurityTable.SELECT_ID_BY_USERNAME + "))";
|
||||
public static final String SELECT_BY_WEB_USERNAME = SELECT + PREFERENCES + FROM + TABLE_NAME +
|
||||
WHERE + WEB_USER_ID + "=(" + SecurityTable.SELECT_ID_BY_USERNAME + ")";
|
||||
public static final String DELETE_BY_WEB_USERNAME = DELETE_FROM + TABLE_NAME +
|
||||
WHERE + WEB_USER_ID + "=(" + SecurityTable.SELECT_ID_BY_USERNAME + ")";
|
||||
|
||||
private WebUserPreferencesTable() {
|
||||
/* Static information class */
|
||||
}
|
||||
|
||||
public static String createTableSQL(DBType dbType) {
|
||||
return CreateTableBuilder.create(TABLE_NAME, dbType)
|
||||
.column(ID, Sql.INT).primaryKey()
|
||||
.column(PREFERENCES, "TEXT").notNull()
|
||||
.column(WEB_USER_ID, Sql.INT)
|
||||
.foreignKey(WEB_USER_ID, SecurityTable.TABLE_NAME, SecurityTable.ID)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
@ -88,6 +88,7 @@ public class BackupCopyTransaction extends RemoveEverythingTransaction {
|
||||
|
||||
private void copyPlanWebUsers() {
|
||||
copy(LargeStoreQueries::storeAllPlanWebUsers, WebUserQueries.fetchAllUsers());
|
||||
copy(LargeStoreQueries::storeAllPreferences, WebUserQueries.fetchAllPreferences());
|
||||
}
|
||||
|
||||
private void copyPlanServerInformation() {
|
||||
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.storage.database.transactions;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.WebUserPreferencesTable;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Stores user preferences as text in database.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class StoreWebUserPreferencesTransaction extends Transaction {
|
||||
|
||||
@Untrusted
|
||||
private final String preferences;
|
||||
@Untrusted
|
||||
private final WebUser user;
|
||||
|
||||
public StoreWebUserPreferencesTransaction(String preferences, WebUser user) {
|
||||
this.preferences = preferences;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
execute(new ExecStatement(WebUserPreferencesTable.DELETE_BY_WEB_USERNAME) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setString(1, user.getUsername());
|
||||
}
|
||||
});
|
||||
commitMidTransaction();
|
||||
execute(new ExecStatement(WebUserPreferencesTable.INSERT_STATEMENT) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setString(1, preferences);
|
||||
statement.setString(2, user.getUsername());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ public class RemoveEverythingTransaction extends Patch {
|
||||
clearTable(WebGroupToPermissionTable.TABLE_NAME);
|
||||
clearTable(WebPermissionTable.TABLE_NAME);
|
||||
clearTable(WebGroupTable.TABLE_NAME);
|
||||
clearTable(WebUserPreferencesTable.TABLE_NAME);
|
||||
clearTable(SecurityTable.TABLE_NAME);
|
||||
clearTable(ServerTable.TABLE_NAME);
|
||||
clearTable(CookieTable.TABLE_NAME);
|
||||
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.storage.database.transactions.init;
|
||||
|
||||
import com.djrapitops.plan.storage.database.sql.tables.*;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.StoreJoinAddressTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.patches.SecurityTableIdPatch;
|
||||
|
||||
/**
|
||||
* Transaction that creates the table schema of Plan database.
|
||||
@ -52,6 +53,9 @@ public class CreateTablesTransaction extends OperationCriticalTransaction {
|
||||
execute(WebPermissionTable.createTableSQL(dbType));
|
||||
execute(WebGroupToPermissionTable.createTableSQL(dbType));
|
||||
execute(SecurityTable.createTableSQL(dbType));
|
||||
// Ensure plan_security has id column
|
||||
executeOther(new SecurityTableIdPatch());
|
||||
execute(WebUserPreferencesTable.createTableSQL(dbType));
|
||||
|
||||
// DataExtension tables
|
||||
execute(ExtensionIconTable.createTableSQL(dbType));
|
||||
|
@ -48,16 +48,26 @@ public class SecurityTableGroupPatch extends Patch {
|
||||
@Override
|
||||
protected void applyPatch() {
|
||||
try {
|
||||
boolean hasIdColumn = hasColumn(tableName, SecurityTable.ID)
|
||||
// Either temp table doesn't exist or it has id column so one can be selected.
|
||||
&& !hasTable(tempTableName) || hasColumn(tempTableName, SecurityTable.ID);
|
||||
if (hasIdColumn) {
|
||||
dropForeignKeys(tableName);
|
||||
ensureNoForeignKeyConstraints(tableName);
|
||||
}
|
||||
|
||||
tempOldTable();
|
||||
dropTable(tableName);
|
||||
execute(SecurityTable.createTableSQL(dbType));
|
||||
|
||||
execute("INSERT INTO " + tableName + " (" +
|
||||
(hasIdColumn ? SecurityTable.ID + ',' : "") +
|
||||
SecurityTable.USERNAME + ',' +
|
||||
SecurityTable.LINKED_TO + ',' +
|
||||
SecurityTable.SALT_PASSWORD_HASH + ',' +
|
||||
SecurityTable.GROUP_ID +
|
||||
") " + SELECT +
|
||||
(hasIdColumn ? SecurityTable.ID + ',' : "") +
|
||||
SecurityTable.USERNAME + ',' +
|
||||
SecurityTable.LINKED_TO + ',' +
|
||||
SecurityTable.SALT_PASSWORD_HASH + ',' +
|
||||
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.storage.database.transactions.patches;
|
||||
|
||||
import com.djrapitops.plan.exceptions.database.DBOpException;
|
||||
import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.SecurityTable;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
|
||||
|
||||
/**
|
||||
* Adds id to plan_security if it is not yet there.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class SecurityTableIdPatch extends Patch {
|
||||
|
||||
private static final String TEMP_TABLE_NAME = "temp_security_id_patch";
|
||||
private static final String TABLE_NAME = SecurityTable.TABLE_NAME;
|
||||
private static final String PERMISSION_LEVEL = "permission_level";
|
||||
|
||||
@Override
|
||||
public boolean hasBeenApplied() {
|
||||
return (hasColumn(TABLE_NAME, SecurityTable.ID))
|
||||
&& !hasTable(TEMP_TABLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyPatch() {
|
||||
boolean hasUuidLinks = hasColumn(TABLE_NAME, SecurityTable.LINKED_TO);
|
||||
if (hasUuidLinks) {
|
||||
patchWithLinkedUuids();
|
||||
} else {
|
||||
patchSimpleTable();
|
||||
}
|
||||
}
|
||||
|
||||
private void patchSimpleTable() {
|
||||
try {
|
||||
tempOldTable();
|
||||
dropTable(TABLE_NAME);
|
||||
execute(CreateTableBuilder.create(TABLE_NAME, dbType)
|
||||
.column(ID, INT).primaryKey()
|
||||
.column(SecurityTable.USERNAME, varchar(100)).notNull().unique()
|
||||
.column(SecurityTable.SALT_PASSWORD_HASH, varchar(100)).notNull().unique()
|
||||
.column(PERMISSION_LEVEL, INT)
|
||||
.toString());
|
||||
|
||||
execute("INSERT INTO " + TABLE_NAME + " (" +
|
||||
SecurityTable.USERNAME + ',' +
|
||||
SecurityTable.SALT_PASSWORD_HASH + ',' +
|
||||
PERMISSION_LEVEL +
|
||||
") " + SELECT +
|
||||
SecurityTable.USERNAME + ',' +
|
||||
SecurityTable.SALT_PASSWORD_HASH + ',' +
|
||||
PERMISSION_LEVEL +
|
||||
FROM + TEMP_TABLE_NAME
|
||||
);
|
||||
|
||||
dropTable(TEMP_TABLE_NAME);
|
||||
} catch (Exception e) {
|
||||
throw new DBOpException(SecurityTableGroupPatch.class.getSimpleName() + " failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void patchWithLinkedUuids() {
|
||||
try {
|
||||
tempOldTable();
|
||||
dropTable(TABLE_NAME);
|
||||
execute(CreateTableBuilder.create(TABLE_NAME, dbType)
|
||||
.column(ID, INT).primaryKey()
|
||||
.column(SecurityTable.USERNAME, varchar(100)).notNull().unique()
|
||||
.column(SecurityTable.SALT_PASSWORD_HASH, varchar(100)).notNull().unique()
|
||||
.column(SecurityTable.LINKED_TO, varchar(36)).defaultValue("''")
|
||||
.column(PERMISSION_LEVEL, INT)
|
||||
.toString());
|
||||
|
||||
execute("INSERT INTO " + TABLE_NAME + " (" +
|
||||
SecurityTable.USERNAME + ',' +
|
||||
SecurityTable.SALT_PASSWORD_HASH + ',' +
|
||||
SecurityTable.LINKED_TO + ',' +
|
||||
PERMISSION_LEVEL +
|
||||
") " + SELECT +
|
||||
SecurityTable.USERNAME + ',' +
|
||||
SecurityTable.SALT_PASSWORD_HASH + ',' +
|
||||
SecurityTable.LINKED_TO + ',' +
|
||||
PERMISSION_LEVEL +
|
||||
FROM + TEMP_TABLE_NAME
|
||||
);
|
||||
|
||||
dropTable(TEMP_TABLE_NAME);
|
||||
} catch (Exception e) {
|
||||
throw new DBOpException(SecurityTableGroupPatch.class.getSimpleName() + " failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void tempOldTable() {
|
||||
if (!hasTable(TEMP_TABLE_NAME)) {
|
||||
renameTable(TABLE_NAME, TEMP_TABLE_NAME);
|
||||
}
|
||||
}
|
||||
}
|
@ -60,7 +60,9 @@ public class Maps {
|
||||
}
|
||||
|
||||
public Builder<K, V> put(K key, V value) {
|
||||
map.put(key, value);
|
||||
if (value != null) {
|
||||
map.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -49,8 +49,6 @@ public class VersionChecker implements SubSystem {
|
||||
protected final RunnableFactory runnableFactory;
|
||||
protected final ErrorLogger errorLogger;
|
||||
|
||||
private static final String DOWNLOAD_ICON_HTML = "<i class=\"fa fa-fw fa-download\"></i> ";
|
||||
|
||||
protected VersionInfo newVersionAvailable;
|
||||
|
||||
@Inject
|
||||
@ -126,49 +124,6 @@ public class VersionChecker implements SubSystem {
|
||||
return Optional.ofNullable(newVersionAvailable);
|
||||
}
|
||||
|
||||
public Optional<String> getUpdateButton() {
|
||||
return getNewVersionAvailable().map(v -> {
|
||||
String reduceFontSize = v.getVersion().compareTo(new VersionNumber("5.2 build 999")) > 0 ?
|
||||
"font-size: 0.95rem;" : "";
|
||||
return "<button class=\"btn bg-white col-plan\" style=\"" + reduceFontSize +
|
||||
"\" data-bs-target=\"#updateModal\" data-bs-toggle=\"modal\" type=\"button\">" +
|
||||
DOWNLOAD_ICON_HTML + locale.getString(PluginLang.VERSION_UPDATE) + ": " + v.getVersion().asString() +
|
||||
"</button>";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public String getCurrentVersionButton() {
|
||||
return "<button class=\"btn bg-plan\" data-bs-target=\"#updateModal\" data-bs-toggle=\"modal\" type=\"button\">" +
|
||||
getCurrentVersion() +
|
||||
"</button>";
|
||||
}
|
||||
|
||||
public String getUpdateModal() {
|
||||
return getNewVersionAvailable()
|
||||
.map(v -> "<div class=\"modal-header\">" +
|
||||
"<h5 class=\"modal-title\" id=\"updateModalLabel\">" +
|
||||
DOWNLOAD_ICON_HTML + locale.getString(PluginLang.VERSION_UPDATE_AVAILABLE, v.getVersion().asString()) +
|
||||
"</h5><button aria-label=\"Close\" class=\"btn-close\" data-bs-dismiss=\"modal\" type=\"button\"></button>" +
|
||||
"</div>" + // Close modal-header
|
||||
"<div class=\"modal-body\">" +
|
||||
"<p>" + locale.getString(PluginLang.VERSION_CURRENT, getCurrentVersion()) + ". " + locale.getString(PluginLang.VERSION_UPDATE_INFO) +
|
||||
(v.isRelease() ? "" : "<br>" + locale.getString(PluginLang.VERSION_UPDATE_DEV)) + "</p>" +
|
||||
"<a class=\"btn col-plan\" href=\"" + v.getChangeLogUrl() + "\" rel=\"noopener noreferrer\" target=\"_blank\">" +
|
||||
"<i class=\"fa fa-fw fa-list\"></i> " + locale.getString(PluginLang.VERSION_CHANGE_LOG) + "</a>" +
|
||||
"<a class=\"btn col-plan\" href=\"" + v.getDownloadUrl() + "\" rel=\"noopener noreferrer\" target=\"_blank\">" +
|
||||
DOWNLOAD_ICON_HTML + locale.getString(PluginLang.VERSION_DOWNLOAD, v.getVersion().asString()) + "</a>" +
|
||||
"</div>") // Close modal-body
|
||||
.orElse("<div class=\"modal-header\">" +
|
||||
"<h5 class=\"modal-title\" id=\"updateModalLabel\">" +
|
||||
"<i class=\"far fa-fw fa-check-circle\"></i> " + locale.getString(PluginLang.VERSION_CURRENT, getCurrentVersion()) +
|
||||
"</h5><button aria-label=\"Close\" class=\"btn-close\" data-bs-dismiss=\"modal\" type=\"button\"></button>" +
|
||||
"</div>" + // Close modal-header
|
||||
"<div class=\"modal-body\">" +
|
||||
"<p>" + locale.getString(PluginLang.VERSION_NEWEST) + "</p>" +
|
||||
"</div>"); // Close modal-body
|
||||
}
|
||||
|
||||
public String getCurrentVersion() {
|
||||
return currentVersion.asString();
|
||||
}
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "会话"
|
||||
sortBy: "排序方式"
|
||||
stacked: "堆叠"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "主题选择"
|
||||
thirdDeadliestWeapon: "第三致命的 PVP 武器"
|
||||
thirtyDays: "30 天"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Relace"
|
||||
sortBy: "Seřadit podle"
|
||||
stacked: "Zaplnění"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Zvolené téma"
|
||||
thirdDeadliestWeapon: "3. PvP Zbraň"
|
||||
thirtyDays: "30 dní"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Sitzungen"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Thema ausgewählt"
|
||||
thirdDeadliestWeapon: "3. PvP Waffe"
|
||||
thirtyDays: "30 Tage"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Sessions"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Theme Select"
|
||||
thirdDeadliestWeapon: "3rd PvP Weapon"
|
||||
thirtyDays: "30 days"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Sesiones"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Selección de tema"
|
||||
thirdDeadliestWeapon: "3ª arma PvP"
|
||||
thirtyDays: "30 días"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Istunnot"
|
||||
sortBy: "Järjestä"
|
||||
stacked: "Päällekäin"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Teemavalikko"
|
||||
thirdDeadliestWeapon: "3. PvP Ase"
|
||||
thirtyDays: "30 päivää"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Sessions"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Sélection du Thème"
|
||||
thirdDeadliestWeapon: "3ᵉ Arme de Combat"
|
||||
thirtyDays: "30 jours"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Sessioni"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Selezione Tema"
|
||||
thirdDeadliestWeapon: "3° Arma PvP Preferita"
|
||||
thirtyDays: "30 giorni"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "接続履歴"
|
||||
sortBy: "並べ替え"
|
||||
stacked: "スタックビュー"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "テーマ選択"
|
||||
thirdDeadliestWeapon: "3番目にPvPで使用されている武器"
|
||||
thirtyDays: "1ヶ月"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "세션 목록"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "테마 선택"
|
||||
thirdDeadliestWeapon: "3rd PvP 무기"
|
||||
thirtyDays: "30일"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Sessies"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Thema selecteren"
|
||||
thirdDeadliestWeapon: "3e PvP-wapen"
|
||||
thirtyDays: "30 dagen"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Sessões"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Theme Select"
|
||||
thirdDeadliestWeapon: "3rd PvP Weapon"
|
||||
thirtyDays: "30 days"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Сессии"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Выбор темы"
|
||||
thirdDeadliestWeapon: "3-е PvP оружие"
|
||||
thirtyDays: "30 дней"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "Oturumlar"
|
||||
sortBy: "Sort By"
|
||||
stacked: "Stacked"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "Tema Seçimi"
|
||||
thirdDeadliestWeapon: "3. PvP Silahı"
|
||||
thirtyDays: "30 gün"
|
||||
|
@ -567,6 +567,10 @@ html:
|
||||
sessions: "會話"
|
||||
sortBy: "排序方式"
|
||||
stacked: "堆疊的"
|
||||
table:
|
||||
showNofM: "Showing {{n}} of {{mn}} entries"
|
||||
showPerPage: "Show per page"
|
||||
visibleColumns: "Visible columns"
|
||||
themeSelect: "主題色選擇"
|
||||
thirdDeadliestWeapon: "第三致命的 PvP 武器"
|
||||
thirtyDays: "30 天"
|
||||
|
@ -1,3 +0,0 @@
|
||||
#logout-button {
|
||||
display: none;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.query-buttons {
|
||||
display: inline-block !important;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -51,16 +51,10 @@
|
||||
<button class="btn bg-plan" data-bs-target="#colorChooserModal" data-bs-toggle="modal" type="button">
|
||||
<i class="fa fa-palette"></i>
|
||||
</button>
|
||||
<button class="btn bg-plan" data-bs-target="#informationModal" data-bs-toggle="modal" type="button">
|
||||
<i class="fa fa-fw fa-question-circle"></i>
|
||||
</button>
|
||||
<a class="btn bg-plan" href="auth/logout" id="logout-button">
|
||||
<i class="fa fa-fw fa-door-open"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
<div class="ms-md-3 text-center text-md-start">
|
||||
${versionButton}
|
||||
</div>
|
||||
</ul>
|
||||
<!-- End of Sidebar -->
|
||||
|
||||
@ -153,77 +147,6 @@
|
||||
</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 modal-lg" 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="btn-close" data-bs-dismiss="modal" type="button"></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/plan-player-analytics/Plan/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/plan-player-analytics/Plan/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 AuroraLS3.</p>
|
||||
<p>In addition following <span class="col-plan">awesome people</span> have contributed:</p>
|
||||
<ul class="row contributors">
|
||||
${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>
|
||||
<a class="btn col-plan" href="https://bstats.org/plugin/velocity/Plan/10326"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"><i class="fa fa-fw fa-chart-area"></i> Velocity</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn bg-plan" data-bs-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-bs-dismiss="modal" type="button">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- End of Page Wrapper -->
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
@ -1,498 +0,0 @@
|
||||
(function ($) {
|
||||
const bgElements = ['.sidebar', '.btn', 'body'];
|
||||
const textElements = ['a', 'button'];
|
||||
|
||||
const colors = ['plan',
|
||||
'red', 'pink', 'purple', 'deep-purple',
|
||||
'indigo', 'blue', 'light-blue', 'cyan',
|
||||
'teal', 'green', 'light-green', 'lime',
|
||||
'yellow', 'amber', 'orange', 'deep-orange',
|
||||
'brown', 'grey', 'blue-grey'];
|
||||
|
||||
const selectedColor = window.localStorage.getItem('themeColor');
|
||||
const themeDefaultColor = '${defaultTheme}';
|
||||
let currentColor = 'plan';
|
||||
|
||||
// Function for changing color
|
||||
function setColor(nextColor) {
|
||||
if (selectedColor === null) {
|
||||
window.localStorage.setItem('themeColor', currentColor);
|
||||
}
|
||||
const bodyElement = document.querySelector('body');
|
||||
bodyElement.classList.remove(`theme-${currentColor}`);
|
||||
bodyElement.classList.add(`theme-${nextColor}`);
|
||||
|
||||
if (!nextColor || nextColor == currentColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
bgElements.map(element => element + '.bg-' + currentColor + ":not(.color-chooser)")
|
||||
.forEach(selector => {
|
||||
document.querySelectorAll(selector).forEach(element => {
|
||||
element.classList.remove(`bg-${currentColor}`);
|
||||
element.classList.add(`bg-${nextColor}`);
|
||||
});
|
||||
});
|
||||
textElements.map(element => element + '.col-' + currentColor)
|
||||
.forEach(selector => {
|
||||
document.querySelectorAll(selector).forEach(element => {
|
||||
element.classList.remove(`col-${currentColor}`);
|
||||
element.classList.add(`col-${nextColor}`);
|
||||
});
|
||||
});
|
||||
if (nextColor != 'night') {
|
||||
window.localStorage.setItem('themeColor', nextColor);
|
||||
}
|
||||
currentColor = nextColor;
|
||||
}
|
||||
|
||||
// Set the color changing function for all color change buttons
|
||||
function enableColorSetters() {
|
||||
for (const color of colors) {
|
||||
const selector = document.getElementById(`choose-${color}`);
|
||||
selector.removeAttribute('disabled');
|
||||
selector.classList.remove('disabled');
|
||||
selector.classList.add(`bg-${color}`);
|
||||
selector.addEventListener('click', () => setColor(color));
|
||||
}
|
||||
}
|
||||
|
||||
enableColorSetters();
|
||||
|
||||
function disableColorSetters() {
|
||||
for (const color of colors) {
|
||||
const selector = document.getElementById(`choose-${color}`);
|
||||
selector.classList.add('disabled');
|
||||
selector.setAttribute('disabled', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
// Change the color of the theme
|
||||
setColor(selectedColor ? selectedColor : themeDefaultColor);
|
||||
|
||||
let nightMode = window.localStorage.getItem('nightMode') == 'true' || '${defaultTheme}' == 'night';
|
||||
|
||||
const saturationReduction = 0.70;
|
||||
|
||||
// From https://stackoverflow.com/a/3732187
|
||||
function withReducedSaturation(colorHex) {
|
||||
// To RGB
|
||||
let r = parseInt(colorHex.substr(1, 2), 16); // Grab the hex representation of red (chars 1-2) and convert to decimal (base 10).
|
||||
let g = parseInt(colorHex.substr(3, 2), 16);
|
||||
let b = parseInt(colorHex.substr(5, 2), 16);
|
||||
|
||||
// To HSL
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
let h, s;
|
||||
const l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0; // achromatic
|
||||
} else {
|
||||
const d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
// To css property
|
||||
return 'hsl(' + h * 360 + ',' + s * 100 * saturationReduction + '%,' + l * 95 + '%)';
|
||||
}
|
||||
|
||||
const red = withReducedSaturation('#F44336');
|
||||
const pink = withReducedSaturation('#E91E63');
|
||||
const purple = withReducedSaturation('#9C27B0');
|
||||
const deepPurple = withReducedSaturation('#673AB7');
|
||||
const indigo = withReducedSaturation('#3F51B5');
|
||||
const blue = withReducedSaturation('#2196F3');
|
||||
const lightBlue = withReducedSaturation('#03A9F4');
|
||||
const cyan = withReducedSaturation('#00BCD4');
|
||||
const teal = withReducedSaturation('#009688');
|
||||
const green = withReducedSaturation('#4CAF50');
|
||||
const lightGreen = withReducedSaturation('#8BC34A');
|
||||
const lime = withReducedSaturation('#CDDC39');
|
||||
const yellow = withReducedSaturation('#ffe821');
|
||||
const amber = withReducedSaturation('#FFC107');
|
||||
const warningColor = withReducedSaturation('#f6c23e');
|
||||
const orange = withReducedSaturation('#FF9800');
|
||||
const deepOrange = withReducedSaturation('#FF5722');
|
||||
const dangerColor = withReducedSaturation('#e74a3b');
|
||||
const brown = withReducedSaturation('#795548');
|
||||
const grey = withReducedSaturation('#9E9E9E');
|
||||
const blueGrey = withReducedSaturation('#607D8B');
|
||||
const black = withReducedSaturation('#555555');
|
||||
const planColor = withReducedSaturation('#368F17');
|
||||
const successColor = withReducedSaturation('#1cc88a');
|
||||
const nightModeColors = `.bg-red {background-color: ${red};color: #eee8d5;}` +
|
||||
`.bg-pink {background-color: ${pink};color: #eee8d5;}` +
|
||||
`.bg-purple {background-color: ${purple};color: #eee8d5;}` +
|
||||
`.bg-deep-purple {background-color: ${deepPurple};color: #eee8d5;}` +
|
||||
`.bg-indigo {background-color: ${indigo};color: #eee8d5;}` +
|
||||
`.bg-blue {background-color: ${blue};color: #eee8d5;}` +
|
||||
`.bg-light-blue {background-color: ${lightBlue};color: #eee8d5;}` +
|
||||
`.bg-cyan {background-color: ${cyan};color: #eee8d5;}` +
|
||||
`.bg-teal {background-color: ${teal};color: #eee8d5;}` +
|
||||
`.bg-green {background-color: ${green};color: #eee8d5;}` +
|
||||
`.bg-light-green {background-color: ${lightGreen};color: #eee8d5;}` +
|
||||
`.bg-lime {background-color: ${lime};color: #eee8d5;}` +
|
||||
`.bg-yellow {background-color: ${yellow};color: #eee8d5;}` +
|
||||
`.bg-amber {background-color: ${amber};color: #eee8d5;}` +
|
||||
`.bg-warning {background-color: ${warningColor};color: #eee8d5;}` +
|
||||
`.bg-orange {background-color: ${orange};color: #eee8d5;}` +
|
||||
`.bg-deep-orange {background-color: ${deepOrange};color: #eee8d5;}` +
|
||||
`.bg-danger {background-color: ${dangerColor};color: #eee8d5;}` +
|
||||
`.bg-brown {background-color: ${brown};color: #eee8d5;}` +
|
||||
`.bg-grey {background-color: ${grey};color: #eee8d5;}` +
|
||||
`.bg-blue-grey {background-color: ${blueGrey};color: #eee8d5;}` +
|
||||
`.bg-black {background-color: ${black};color: #eee8d5;}` +
|
||||
`.bg-plan,.page-item.active .page-link {background-color: ${planColor};color: #eee8d5;}` +
|
||||
`.bg-success {background-color: ${successColor};color: #eee8d5;}` +
|
||||
`.bg-night {background-color: #44475a;color: #eee8d5;}` +
|
||||
`.bg-red-outline {outline-color: ${red};border-color: ${red};}` +
|
||||
`.bg-pink-outline {outline-color: ${pink};border-color: ${pink};}` +
|
||||
`.bg-purple-outline {outline-color: ${purple};border-color: ${purple};}` +
|
||||
`.bg-deep-purple-outline {outline-color: ${deepPurple};border-color: ${deepPurple};}` +
|
||||
`.bg-indigo-outline {outline-color: ${indigo};border-color: ${indigo};}` +
|
||||
`.bg-blue-outline {outline-color: ${blue};border-color: ${blue};}` +
|
||||
`.bg-light-blue-outline {outline-color: ${lightBlue};border-color: ${lightBlue};}` +
|
||||
`.bg-cyan-outline {outline-color: ${cyan};border-color: ${cyan};}` +
|
||||
`.bg-teal-outline {outline-color: ${teal};border-color: ${teal};}` +
|
||||
`.bg-green-outline {outline-color: ${green};border-color: ${green};}` +
|
||||
`.bg-light-green-outline {outline-color: ${lightGreen};border-color: ${lightGreen};}` +
|
||||
`.bg-lime-outline {outline-color: ${lime};border-color: ${lime};}` +
|
||||
`.bg-yellow-outline {outline-color: ${yellow};border-color: ${yellow};}` +
|
||||
`.bg-amber-outline {outline-color: ${amber};border-color: ${amber};}` +
|
||||
`.bg-orange-outline {outline-color: ${orange};border-color: ${orange};}` +
|
||||
`.bg-deep-orange-outline {outline-color: ${deepOrange};border-color: ${deepOrange};}` +
|
||||
`.bg-brown-outline {outline-color: ${brown};border-color: ${brown};}` +
|
||||
`.bg-grey-outline {outline-color: ${grey};border-color: ${grey};}` +
|
||||
`.bg-blue-grey-outline {outline-color: ${blueGrey};border-color: ${blueGrey};}` +
|
||||
`.bg-black-outline {outline-color: ${black};border-color: ${black};}` +
|
||||
`.bg-plan-outline {outline-color: ${planColor};border-color: ${planColor};}` +
|
||||
`.col-red {color: ${red};}` +
|
||||
`.col-pink {color: ${pink};}` +
|
||||
`.col-purple {color: ${purple};}` +
|
||||
`.col-deep-purple {color: ${deepPurple};}` +
|
||||
`.col-indigo {color: ${indigo};}` +
|
||||
`.col-blue {color: ${blue};}` +
|
||||
`.col-light-blue {color: ${lightBlue};}` +
|
||||
`.col-cyan {color: ${cyan};}` +
|
||||
`.col-teal {color: ${teal};}` +
|
||||
`.col-green {color: ${green};}` +
|
||||
`.col-light-green {color: ${lightGreen};}` +
|
||||
`.col-lime {color: ${lime};}` +
|
||||
`.col-yellow {color: ${yellow};}` +
|
||||
`.col-amber {color: ${amber};}` +
|
||||
`.text-warning {color: ${warningColor};}` +
|
||||
`.col-orange {color: ${orange};}` +
|
||||
`.col-deep-orange {color: ${deepOrange};}` +
|
||||
`.text-danger {color: ${dangerColor};}` +
|
||||
`.col-brown {color: ${brown};}` +
|
||||
`.col-grey {color: ${grey};}` +
|
||||
`.col-blue-grey {color: ${blueGrey};}` +
|
||||
`.col-plan {color: ${planColor};}` +
|
||||
`.text-success {color: ${successColor};}`;
|
||||
|
||||
function changeNightModeCSS() {
|
||||
if (nightMode) {
|
||||
// Background colors from dracula theme
|
||||
$('head').append('<style id="nightmode">' +
|
||||
'#content {background-color:#282a36;}' +
|
||||
'.btn {color: #eee8d5;}' +
|
||||
'.card,.bg-white,.modal-content,.page-loader,.nav-tabs .nav-link:hover,.nav-tabs,hr,form .btn, .btn-outline-secondary{background-color:#44475a!important;border-color:#6272a4!important;}' +
|
||||
'.bg-white.collapse-inner {border:1px solid;}' +
|
||||
'.card-header {background-color:#44475a;border-color:#6272a4;}' +
|
||||
'#content,.col-black,.text-gray-900,.text-gray-800,.collapse-item,.modal-title,.modal-body,.page-loader,.fc-title,.fc-time,pre,.table-dark,input::placeholder{color:#eee8d5 !important;}' +
|
||||
'.collapse-item:hover,.nav-link.active {background-color: #606270 !important;}' +
|
||||
'.nav-tabs .nav-link.active {background-color: #44475a !important;border-color:#6272a4 #6272a4 #44475a !important;}' +
|
||||
'.fc-today {background:#646e8c !important}' +
|
||||
'.fc-popover-body,.fc-popover-header{background-color: #44475a !important;color: #eee8d5 !important;}' +
|
||||
'select,input,.dataTables_paginate .page-item:not(.active) a,.input-group-text,.input-group-text > * {background-color:#44475a !important;border-color:#6272a4 !important;color: #eee8d5 !important;}' +
|
||||
nightModeColors +
|
||||
'</style>');
|
||||
// Turn bright tables to dark
|
||||
$('.table').addClass('table-dark');
|
||||
// Turn modal close buttons light
|
||||
document.querySelectorAll('button.btn-close').forEach(element => { element.classList.add('btn-close-white'); })
|
||||
// Sidebar is grey when in night mode
|
||||
disableColorSetters();
|
||||
setColor('night');
|
||||
} else {
|
||||
// Remove night mode style sheet
|
||||
$('#nightmode').remove();
|
||||
// Turn dark tables bright again
|
||||
$('.table').removeClass('table-dark');
|
||||
// Turn modal close buttons dark
|
||||
document.querySelectorAll('button.btn-close').forEach(element => { element.classList.remove('btn-close-white'); })
|
||||
// Sidebar is colorful
|
||||
enableColorSetters();
|
||||
setColor(window.localStorage.getItem('themeColor'));
|
||||
}
|
||||
}
|
||||
|
||||
function changeHighChartsNightMode() {
|
||||
try {
|
||||
Highcharts.theme = nightMode ? {
|
||||
chart: {
|
||||
backgroundColor: null,
|
||||
plotBorderColor: '#606063'
|
||||
},
|
||||
title: {
|
||||
style: {color: '#eee8d5'}
|
||||
},
|
||||
subtitle: {
|
||||
style: {color: '#eee8d5'}
|
||||
},
|
||||
xAxis: {
|
||||
gridLineColor: '#707073',
|
||||
labels: {
|
||||
style: {color: '#eee8d5'}
|
||||
},
|
||||
lineColor: '#707073',
|
||||
minorGridLineColor: '#505053',
|
||||
tickColor: '#707073',
|
||||
title: {
|
||||
style: {color: '#eee8d5'}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
gridLineColor: '#707073',
|
||||
labels: {
|
||||
style: {color: '#eee8d5'}
|
||||
},
|
||||
lineColor: '#707073',
|
||||
minorGridLineColor: '#505053',
|
||||
tickColor: '#707073',
|
||||
tickWidth: 1,
|
||||
title: {
|
||||
style: {color: '#eee8d5'}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: '#44475a',
|
||||
style: {color: '#eee8d5'}
|
||||
},
|
||||
plotOptions: {
|
||||
series: {
|
||||
dataLabels: {color: '#B0B0B3'},
|
||||
marker: {lineColor: '#333'}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
itemStyle: {color: '#eee8d5'},
|
||||
itemHoverStyle: {color: '#eee8d5'},
|
||||
itemHiddenStyle: {color: '#606063'}
|
||||
},
|
||||
labels: {
|
||||
style: {color: '#eee8d5'}
|
||||
},
|
||||
drilldown: {
|
||||
activeAxisLabelStyle: {color: '#eee8d5'},
|
||||
activeDataLabelStyle: {color: '#eee8d5'}
|
||||
},
|
||||
navigation: {
|
||||
buttonOptions: {
|
||||
symbolStroke: '#eee8d5',
|
||||
theme: {fill: '#44475a'}
|
||||
}
|
||||
},
|
||||
// scroll charts
|
||||
rangeSelector: {
|
||||
buttonTheme: {
|
||||
fill: '#505053',
|
||||
stroke: '#646e8c',
|
||||
style: {color: '#CCC'},
|
||||
states: {
|
||||
hover: {
|
||||
fill: '#646e9d',
|
||||
stroke: '#646e8c',
|
||||
style: {color: 'white'}
|
||||
},
|
||||
select: {
|
||||
fill: '#646e9d',
|
||||
stroke: '#646e8c',
|
||||
style: {color: 'white'}
|
||||
}
|
||||
}
|
||||
},
|
||||
inputBoxBorderColor: '#505053',
|
||||
inputStyle: {
|
||||
backgroundColor: '#333',
|
||||
color: 'silver'
|
||||
},
|
||||
labelStyle: {color: 'silver'}
|
||||
},
|
||||
|
||||
navigator: {
|
||||
handles: {
|
||||
backgroundColor: '#666',
|
||||
borderColor: '#AAA'
|
||||
},
|
||||
outlineColor: '#CCC',
|
||||
maskFill: 'rgba(255,255,255,0.1)',
|
||||
series: {lineColor: '#A6C7ED'},
|
||||
xAxis: {gridLineColor: '#505053'}
|
||||
},
|
||||
|
||||
scrollbar: {
|
||||
barBackgroundColor: '#808083',
|
||||
barBorderColor: '#808083',
|
||||
buttonArrowColor: '#CCC',
|
||||
buttonBackgroundColor: '#606063',
|
||||
buttonBorderColor: '#606063',
|
||||
rifleColor: '#FFF',
|
||||
trackBackgroundColor: '#404043',
|
||||
trackBorderColor: '#404043'
|
||||
}
|
||||
} : { // Defaults
|
||||
chart: {
|
||||
backgroundColor: null,
|
||||
plotBorderColor: '#cccccc'
|
||||
},
|
||||
title: {
|
||||
style: {color: '#3E576F'}
|
||||
},
|
||||
subtitle: {
|
||||
style: {color: '#3E576F'}
|
||||
},
|
||||
xAxis: {
|
||||
gridLineColor: '#E6E6E6',
|
||||
labels: {
|
||||
style: {color: '#333333'}
|
||||
},
|
||||
lineColor: '#E6E6E6',
|
||||
minorGridLineColor: '#505053',
|
||||
tickColor: '#E6E6E6',
|
||||
title: {
|
||||
style: {color: '#333333'}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
gridLineColor: '#E6E6E6',
|
||||
labels: {
|
||||
style: {color: '#333333'}
|
||||
},
|
||||
lineColor: '#E6E6E6',
|
||||
minorGridLineColor: '#505053',
|
||||
tickColor: '#E6E6E6',
|
||||
tickWidth: 1,
|
||||
title: {
|
||||
style: {color: '#333333'}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(247,247,247,0.85)',
|
||||
style: {color: '#333333'}
|
||||
},
|
||||
plotOptions: {
|
||||
series: {
|
||||
dataLabels: {color: undefined},
|
||||
marker: {lineColor: undefined}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
itemStyle: {color: '#333333'},
|
||||
itemHoverStyle: {color: '#000000'},
|
||||
itemHiddenStyle: {color: '#cccccc'}
|
||||
},
|
||||
labels: {
|
||||
style: {color: '#333333'}
|
||||
},
|
||||
drilldown: {
|
||||
activeAxisLabelStyle: {color: '#333333'},
|
||||
activeDataLabelStyle: {color: '#333333'}
|
||||
},
|
||||
navigation: {
|
||||
buttonOptions: {
|
||||
symbolStroke: '#333333',
|
||||
theme: {fill: '#CCCCCC'}
|
||||
}
|
||||
},
|
||||
// scroll charts
|
||||
rangeSelector: {
|
||||
buttonTheme: {
|
||||
fill: '#F7F7F7',
|
||||
stroke: '#333',
|
||||
style: {color: '#4B336A'},
|
||||
states: {
|
||||
hover: {
|
||||
fill: '#E6EBF5',
|
||||
stroke: '#333',
|
||||
style: {color: 'black'}
|
||||
},
|
||||
select: {
|
||||
fill: '#E6EBF5',
|
||||
stroke: '#333',
|
||||
style: {color: 'black'}
|
||||
}
|
||||
}
|
||||
},
|
||||
inputBoxBorderColor: '#CCCCCC',
|
||||
inputStyle: {
|
||||
backgroundColor: '#333',
|
||||
color: '#666666'
|
||||
},
|
||||
labelStyle: {color: "#666666"}
|
||||
},
|
||||
|
||||
navigator: {
|
||||
handles: {
|
||||
backgroundColor: '#f2f2f2',
|
||||
borderColor: '#999999'
|
||||
},
|
||||
outlineColor: '#cccccc',
|
||||
maskFill: 'rgba(102,133,194,0.3)',
|
||||
series: {lineColor: "#3FA0FF"},
|
||||
xAxis: {gridLineColor: '#e6e6e6'}
|
||||
},
|
||||
|
||||
scrollbar: {
|
||||
barBackgroundColor: '#cccccc',
|
||||
barBorderColor: '#cccccc',
|
||||
buttonArrowColor: '#333333',
|
||||
buttonBackgroundColor: '#e6e6e6',
|
||||
buttonBorderColor: '#cccccc',
|
||||
rifleColor: '#333333',
|
||||
trackBackgroundColor: '#f2f2f2',
|
||||
trackBorderColor: '#f2f2f2'
|
||||
}
|
||||
};
|
||||
Highcharts.setOptions(Highcharts.theme);
|
||||
updateGraphs();
|
||||
} catch (e) {
|
||||
if ("Highcharts is not defined" === e.message || "updateGraphs is not defined" === e.message) {
|
||||
// Highcharts isn't loaded, can be ignored
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeNightModeCSS();
|
||||
changeHighChartsNightMode();
|
||||
|
||||
function toggleNightMode() {
|
||||
nightMode = !nightMode;
|
||||
setTimeout(function () {
|
||||
window.localStorage.setItem('nightMode', nightMode);
|
||||
changeNightModeCSS();
|
||||
changeHighChartsNightMode();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
$('#night-mode-toggle').on('click', toggleNightMode);
|
||||
|
||||
})(jQuery);
|
@ -1,38 +0,0 @@
|
||||
function insertElementBefore(elementSelector, createElementFunction) {
|
||||
const placeBefore = document.querySelector(elementSelector);
|
||||
insertElementBeforeElement(placeBefore, createElementFunction);
|
||||
}
|
||||
|
||||
function insertElementBeforeElement(placeBefore, createElementFunction) {
|
||||
const element = createElementFunction();
|
||||
placeBefore.insertAdjacentElement('beforebegin', element);
|
||||
}
|
||||
|
||||
function insertElementAfter(elementSelector, createElementFunction) {
|
||||
const placeBefore = document.querySelector(elementSelector);
|
||||
insertElementAfterElement(placeBefore, createElementFunction)
|
||||
}
|
||||
|
||||
function insertElementAfterElement(placeBefore, createElementFunction) {
|
||||
const element = createElementFunction();
|
||||
placeBefore.insertAdjacentElement('afterend', element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an error using the page loader.
|
||||
* @param {string} [message] The message to be shown.
|
||||
*/
|
||||
function loaderError(message) {
|
||||
let loader = document.querySelector(".page-loader");
|
||||
let loaderText = document.querySelector('.loader-text');
|
||||
|
||||
if (loader.style.display === "none") {
|
||||
loader.style.display = "block";
|
||||
}
|
||||
|
||||
if (message !== undefined) {
|
||||
loaderText.innerText = message;
|
||||
} else {
|
||||
loaderText.innerText = "Error occurred, see the Developer Console (Ctrl+Shift+I) for details."
|
||||
}
|
||||
}
|
@ -1,274 +0,0 @@
|
||||
class Filter {
|
||||
constructor(kind) {
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
render(filterCount) {
|
||||
return 'Unimplemented render function'
|
||||
}
|
||||
|
||||
toObject() {
|
||||
return {kind: this.kind}
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleChoiceFilter extends Filter {
|
||||
constructor(
|
||||
id, kind, label, options
|
||||
) {
|
||||
super(kind);
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
render(filterCount) {
|
||||
const select = filterCount === 0 ? "of Players who " : "and ";
|
||||
let html =
|
||||
`<div id="${this.id}" class="mt-2">
|
||||
<label class="form-label" for="${this.id}">${select}${this.label}:</label>
|
||||
<div class="row">
|
||||
<div class="col-11 flex-fill">
|
||||
<select class="form-control" multiple style="margin-bottom: 0.5rem;">`;
|
||||
|
||||
for (const option of this.options.options) {
|
||||
html += `<option>${option}</option>`;
|
||||
}
|
||||
|
||||
html +=
|
||||
` </select>
|
||||
</div>
|
||||
<div class="col-1 col-md-auto my-auto">
|
||||
<button class="filter-remover btn btn-outline-secondary float-end"
|
||||
onclick="removeFilter('${this.id}')"><i class="far fa-fw fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
toObject() {
|
||||
let selected = [];
|
||||
for (let option of document.querySelector(`#${this.id} select`).options) {
|
||||
if (option.selected) selected.push(option.text);
|
||||
}
|
||||
selected = JSON.stringify(selected);
|
||||
|
||||
return {
|
||||
kind: this.kind,
|
||||
parameters: {selected}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ActivityIndexFilter extends MultipleChoiceFilter {
|
||||
constructor(
|
||||
id, options
|
||||
) {
|
||||
super(id, "activityIndexNow", `are in Activity Groups`, options);
|
||||
}
|
||||
}
|
||||
|
||||
class BannedFilter extends MultipleChoiceFilter {
|
||||
constructor(
|
||||
id, options
|
||||
) {
|
||||
super(id, "banned", `are`, options);
|
||||
}
|
||||
}
|
||||
|
||||
class OperatorsFilter extends MultipleChoiceFilter {
|
||||
constructor(
|
||||
id, options
|
||||
) {
|
||||
super(id, "operators", `are`, options);
|
||||
}
|
||||
}
|
||||
|
||||
class JoinAddressFilter extends MultipleChoiceFilter {
|
||||
constructor(
|
||||
id, options
|
||||
) {
|
||||
super(id, "joinAddresses", `joined with address`, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Lowercase due to locale translation: Geolocations
|
||||
class geolocationsFilter extends MultipleChoiceFilter {
|
||||
constructor(
|
||||
id, options
|
||||
) {
|
||||
super(id, "geolocations", `have joined from country`, options);
|
||||
}
|
||||
}
|
||||
|
||||
class PluginBooleanGroupsFilter extends MultipleChoiceFilter {
|
||||
constructor(
|
||||
id, options
|
||||
) {
|
||||
super(id, "pluginsBooleanGroups", `have Plugin boolean value`, options);
|
||||
}
|
||||
}
|
||||
|
||||
class PluginGroupsFilter extends MultipleChoiceFilter {
|
||||
constructor(
|
||||
id, kind, options
|
||||
) {
|
||||
super(id, kind, `are in ${options.plugin}'s ${options.group} Groups`, options);
|
||||
}
|
||||
}
|
||||
|
||||
class BetweenDateFilter extends Filter {
|
||||
constructor(id, kind, label, options) {
|
||||
super(kind);
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
this.afterDate = options.after[0];
|
||||
this.afterTime = options.after[1];
|
||||
this.beforeDate = options.before[0];
|
||||
this.beforeTime = options.before[1];
|
||||
}
|
||||
|
||||
render(filterCount) {
|
||||
const id = this.id;
|
||||
const select = filterCount === 0 ? "of Players who " : "and ";
|
||||
return (
|
||||
`<div id="${id}">
|
||||
<label>${select}${this.label}:</label>
|
||||
<div class="my-2 row justify-content-start">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<i class="far fa-calendar"></i>
|
||||
</div>
|
||||
<input id="${id}-afterdate" class="form-control" placeholder="${this.afterDate}" type="text"
|
||||
onkeyup="setFilterOption('${id}', '${id}-afterdate', 'afterDate', isValidDate, correctDate)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<i class="far fa-clock"></i>
|
||||
</div>
|
||||
<input id="${id}-aftertime" class="form-control" placeholder="${this.afterTime}" type="text"
|
||||
onkeyup="setFilterOption('${id}', '${id}-aftertime', 'afterTime', isValidTime, correctTime)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-1 text-center my-1 my-md-2 flex-fill">
|
||||
<label for="inlineFormCustomSelectPref">&</label>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<i class="far fa-calendar"></i>
|
||||
</div>
|
||||
<input id="${id}-beforedate" class="form-control" placeholder="${this.beforeDate}" type="text"
|
||||
onkeyup="setFilterOption('${id}', '${id}-beforedate', 'beforeDate', isValidDate, correctDate)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-5 col-md-2">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<i class="far fa-clock"></i>
|
||||
</div>
|
||||
<input id="${id}-beforetime" class="form-control" placeholder="${this.beforeTime}" type="text"
|
||||
onkeyup="setFilterOption('${id}', '${id}-beforetime', 'beforeTime', isValidTime, correctTime)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1 col-md-auto">
|
||||
<button class="filter-remover btn btn-outline-secondary float-end"
|
||||
onclick="removeFilter('${this.id}')"><i class="far fa-fw fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
|
||||
toObject() {
|
||||
return {
|
||||
kind: this.kind,
|
||||
parameters: {
|
||||
afterDate: this.afterDate,
|
||||
afterTime: this.afterTime,
|
||||
beforeDate: this.beforeDate,
|
||||
beforeTime: this.beforeTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayedBetweenFilter extends BetweenDateFilter {
|
||||
constructor(id, options) {
|
||||
super(id, "playedBetween", "Played between", options);
|
||||
}
|
||||
}
|
||||
|
||||
class RegisteredBetweenFilter extends BetweenDateFilter {
|
||||
constructor(id, options) {
|
||||
super(id, "registeredBetween", "Registered between", options);
|
||||
}
|
||||
}
|
||||
|
||||
class PlayedOnServerFilter extends MultipleChoiceFilter {
|
||||
constructor(id, options) {
|
||||
super(id, "playedOnServer", "have played on at least one of", options);
|
||||
}
|
||||
}
|
||||
|
||||
function createFilter(filter, id) {
|
||||
if (filter.kind.startsWith("pluginGroups-")) {
|
||||
return new PluginGroupsFilter(id, filter.kind, filter.options);
|
||||
}
|
||||
switch (filter.kind) {
|
||||
case "activityIndexNow":
|
||||
return new ActivityIndexFilter(id, filter.options);
|
||||
case "banned":
|
||||
return new BannedFilter(id, filter.options);
|
||||
case "operators":
|
||||
return new OperatorsFilter(id, filter.options);
|
||||
case "joinAddresses":
|
||||
return new JoinAddressFilter(id, filter.options);
|
||||
case "geolocations":
|
||||
return new geolocationsFilter(id, filter.options);
|
||||
case "playedBetween":
|
||||
return new PlayedBetweenFilter(id, filter.options);
|
||||
case "registeredBetween":
|
||||
return new RegisteredBetweenFilter(id, filter.options);
|
||||
case "pluginsBooleanGroups":
|
||||
return new PluginBooleanGroupsFilter(id, filter.options);
|
||||
case "playedOnServer":
|
||||
return new PlayedOnServerFilter(id, filter.options);
|
||||
default:
|
||||
throw new Error("Unsupported filter kind: '" + filter.kind + "'");
|
||||
}
|
||||
}
|
||||
|
||||
function getReadableFilterName(filter) {
|
||||
if (filter.kind.startsWith("pluginGroups-")) {
|
||||
return "Group: " + filter.kind.substring(13);
|
||||
}
|
||||
switch (filter.kind) {
|
||||
case "allPlayers":
|
||||
return "All players"
|
||||
case "activityIndexNow":
|
||||
return "Current activity group";
|
||||
case "banned":
|
||||
return "Ban status";
|
||||
case "operators":
|
||||
return "Operator status";
|
||||
case "joinAddresses":
|
||||
return "Join Addresses";
|
||||
case "geolocations":
|
||||
return "Geolocations";
|
||||
case "playedBetween":
|
||||
return "Played between";
|
||||
case "registeredBetween":
|
||||
return "Registered between";
|
||||
case "pluginsBooleanGroups":
|
||||
return "Has plugin boolean value";
|
||||
case "playedOnServer":
|
||||
return "Has played on one of servers";
|
||||
default:
|
||||
return filter.kind;
|
||||
}
|
||||
}
|
@ -1,831 +0,0 @@
|
||||
const linegraphButtons = [{
|
||||
type: 'hour',
|
||||
count: 12,
|
||||
text: '12h'
|
||||
}, {
|
||||
type: 'hour',
|
||||
count: 24,
|
||||
text: '24h'
|
||||
}, {
|
||||
type: 'day',
|
||||
count: 7,
|
||||
text: '7d'
|
||||
}, {
|
||||
type: 'month',
|
||||
count: 1,
|
||||
text: '30d'
|
||||
}, {
|
||||
type: 'all',
|
||||
text: 'All'
|
||||
}];
|
||||
|
||||
const graphs = [];
|
||||
window.calendars = {};
|
||||
|
||||
function activityPie(id, activitySeries) {
|
||||
graphs.push(Highcharts.chart(id, {
|
||||
chart: {
|
||||
plotBackgroundColor: null,
|
||||
plotBorderWidth: null,
|
||||
plotShadow: false,
|
||||
type: 'pie'
|
||||
},
|
||||
title: {text: ''},
|
||||
tooltip: {
|
||||
pointFormat: '{series.name}: <b>{point.y}</b>'
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
showInLegend: true
|
||||
}
|
||||
},
|
||||
series: [activitySeries]
|
||||
}));
|
||||
}
|
||||
|
||||
function diskChart(id, series) {
|
||||
graphs.push(Highcharts.stockChart(id, {
|
||||
rangeSelector: {
|
||||
selected: 2,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
yAxis: {
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' MB';
|
||||
}
|
||||
},
|
||||
softMax: 2,
|
||||
softMin: 0
|
||||
},
|
||||
title: {text: ''},
|
||||
legend: {
|
||||
enabled: true
|
||||
},
|
||||
series: series
|
||||
}));
|
||||
}
|
||||
|
||||
function horizontalBarChart(id, categories, series, text) {
|
||||
graphs.push(Highcharts.chart(id, {
|
||||
chart: {
|
||||
type: 'bar'
|
||||
},
|
||||
title: {
|
||||
text: ''
|
||||
},
|
||||
xAxis: {
|
||||
categories: categories,
|
||||
title: {
|
||||
text: null
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
min: 0,
|
||||
title: {
|
||||
text: text,
|
||||
align: 'high'
|
||||
},
|
||||
labels: {
|
||||
overflow: 'justify'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
enabled: false
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
dataLabels: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
},
|
||||
credits: {
|
||||
enabled: true
|
||||
},
|
||||
series: series
|
||||
}));
|
||||
}
|
||||
|
||||
function lineChart(id, series) {
|
||||
graphs.push(Highcharts.stockChart(id, {
|
||||
rangeSelector: {
|
||||
selected: 2,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
yAxis: {
|
||||
softMax: 2,
|
||||
softMin: 0
|
||||
},
|
||||
title: {text: ''},
|
||||
legend: {
|
||||
enabled: true
|
||||
},
|
||||
series: series
|
||||
}));
|
||||
}
|
||||
|
||||
function dayByDay(id, series) {
|
||||
graphs.push(Highcharts.stockChart(id, {
|
||||
rangeSelector: {
|
||||
selected: 2,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
yAxis: {
|
||||
softMax: 2,
|
||||
softMin: 0
|
||||
},
|
||||
title: {text: ''},
|
||||
legend: {
|
||||
enabled: true
|
||||
},
|
||||
time: {timezoneOffset: 0},
|
||||
series: series
|
||||
}));
|
||||
}
|
||||
|
||||
function onlineActivityCalendar(id, event_data, firstDay) {
|
||||
window.calendars.online_activity = new FullCalendar.Calendar(document.querySelector(id), {
|
||||
timeZone: "UTC",
|
||||
themeSystem: 'bootstrap',
|
||||
eventColor: '#2196F3',
|
||||
firstDay: firstDay,
|
||||
initialView: 'dayGridMonth',
|
||||
|
||||
eventDidMount: function (info) {
|
||||
$(info.el).popover({
|
||||
content: info.event.title,
|
||||
trigger: 'hover',
|
||||
placement: 'top',
|
||||
container: 'body'
|
||||
});
|
||||
},
|
||||
|
||||
events: function (fetchInfo, successCallback, failureCallback) {
|
||||
successCallback(event_data)
|
||||
},
|
||||
|
||||
height: 800,
|
||||
contentHeight: 795,
|
||||
headerToolbar: {
|
||||
left: 'title',
|
||||
center: '',
|
||||
right: 'today prev,next'
|
||||
}
|
||||
});
|
||||
|
||||
window.calendars.online_activity.render();
|
||||
}
|
||||
|
||||
function mapToDataSeries(performanceData) {
|
||||
const playersOnline = [];
|
||||
const tps = [];
|
||||
const cpu = [];
|
||||
const ram = [];
|
||||
const entities = [];
|
||||
const chunks = [];
|
||||
const disk = [];
|
||||
|
||||
return new Promise((resolve => {
|
||||
let i = 0;
|
||||
const length = performanceData.length;
|
||||
|
||||
function processNextThousand() {
|
||||
const to = Math.min(i + 1000, length);
|
||||
for (i; i < to; i++) {
|
||||
const entry = performanceData[i];
|
||||
const date = entry[0];
|
||||
playersOnline[i] = [date, entry[1]];
|
||||
tps[i] = [date, entry[2]];
|
||||
cpu[i] = [date, entry[3]];
|
||||
ram[i] = [date, entry[4]];
|
||||
entities[i] = [date, entry[5]];
|
||||
chunks[i] = [date, entry[6]];
|
||||
disk[i] = [date, entry[7]];
|
||||
}
|
||||
if (i >= length) {
|
||||
resolve({playersOnline, tps, cpu, ram, entities, chunks, disk})
|
||||
} else {
|
||||
setTimeout(processNextThousand, 10);
|
||||
}
|
||||
}
|
||||
|
||||
processNextThousand();
|
||||
}))
|
||||
}
|
||||
|
||||
function performanceChart(id, playersOnlineSeries, tpsSeries, cpuSeries, ramSeries, entitySeries, chunkSeries) {
|
||||
const chart = Highcharts.stockChart(id, {
|
||||
rangeSelector: {
|
||||
selected: 2,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
title: {text: ''},
|
||||
yAxis: [{
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' P';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' TPS';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + '%';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' MB';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' E';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' C';
|
||||
}
|
||||
}
|
||||
}],
|
||||
legend: {
|
||||
enabled: true
|
||||
},
|
||||
series: [playersOnlineSeries, tpsSeries, cpuSeries, ramSeries, entitySeries, chunkSeries]
|
||||
});
|
||||
|
||||
function toggleLabels() {
|
||||
if (!chart || !chart.yAxis || !chart.yAxis.length) return;
|
||||
const newWidth = $(window).width();
|
||||
chart.yAxis[0].update({labels: {enabled: newWidth >= 900}});
|
||||
chart.yAxis[1].update({labels: {enabled: newWidth >= 900}});
|
||||
chart.yAxis[2].update({labels: {enabled: newWidth >= 1000}});
|
||||
chart.yAxis[3].update({labels: {enabled: newWidth >= 1000}});
|
||||
chart.yAxis[4].update({labels: {enabled: newWidth >= 1400}});
|
||||
chart.yAxis[5].update({labels: {enabled: newWidth >= 1400}});
|
||||
}
|
||||
|
||||
$(window).resize(toggleLabels);
|
||||
toggleLabels();
|
||||
|
||||
graphs.push(chart);
|
||||
}
|
||||
|
||||
function playersChart(id, playersOnlineSeries, sel) {
|
||||
function groupByIntervalStartingFrom(startDate, interval) {
|
||||
let previousGroupStart = startDate;
|
||||
const groupByInterval = [[]];
|
||||
|
||||
for (let point of playersOnlineSeries.data) {
|
||||
const date = point[0];
|
||||
if (date < startDate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (previousGroupStart + interval < date) {
|
||||
previousGroupStart = date;
|
||||
groupByInterval.push([]);
|
||||
}
|
||||
|
||||
const currentGroup = groupByInterval[groupByInterval.length - 1];
|
||||
currentGroup.push(point);
|
||||
}
|
||||
return groupByInterval;
|
||||
}
|
||||
|
||||
function averageGroupPoints(groupByInterval, minDate) {
|
||||
const averages = [];
|
||||
for (let group of groupByInterval) {
|
||||
let totalDate = 0;
|
||||
let total = 0;
|
||||
let count = group.length;
|
||||
for (let point of group) {
|
||||
totalDate += (point[0] - minDate); // Remove the minDate from dates to calculate a smaller total
|
||||
total += point[1];
|
||||
}
|
||||
|
||||
if (count !== 0) {
|
||||
const middleDate = Math.trunc((totalDate / count) + minDate);
|
||||
const average = Math.trunc(total / count);
|
||||
averages.push([middleDate, average]);
|
||||
}
|
||||
}
|
||||
return averages;
|
||||
}
|
||||
|
||||
function getAveragePlayersSeries(minDate, twentyPointInterval) {
|
||||
const groupByInterval = groupByIntervalStartingFrom(minDate, twentyPointInterval);
|
||||
|
||||
return {
|
||||
name: s.name.averagePlayersOnline,
|
||||
type: s.type.spline,
|
||||
tooltip: s.tooltip.zeroDecimals,
|
||||
data: averageGroupPoints(groupByInterval, minDate),
|
||||
color: "#02458d",
|
||||
yAxis: 0
|
||||
};
|
||||
}
|
||||
|
||||
function updateAveragePlayers(event) {
|
||||
const minDate = event.min;
|
||||
const maxDate = event.max;
|
||||
const twentyPointInterval = (maxDate - minDate) / 20;
|
||||
|
||||
const averagePlayersSeries = getAveragePlayersSeries(minDate, twentyPointInterval);
|
||||
|
||||
const playersOnlineGraph = graphs.find(graph => graph && graph.renderTo && graph.renderTo.id === id);
|
||||
playersOnlineGraph.series[1].update(averagePlayersSeries);
|
||||
}
|
||||
|
||||
const emptyAveragePlayersSeries = {
|
||||
name: s.name.averagePlayersOnline,
|
||||
type: s.type.spline,
|
||||
tooltip: s.tooltip.zeroDecimals,
|
||||
data: [],
|
||||
color: "#02458d",
|
||||
yAxis: 0
|
||||
};
|
||||
|
||||
graphs.push(Highcharts.stockChart(id, {
|
||||
rangeSelector: {
|
||||
selected: sel,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
yAxis: {
|
||||
softMax: 2,
|
||||
softMin: 0
|
||||
},
|
||||
/* Average online players graph Disabled
|
||||
xAxis: {
|
||||
events: {
|
||||
afterSetExtremes: updateAveragePlayers
|
||||
}
|
||||
},
|
||||
*/
|
||||
title: {text: ''},
|
||||
plotOptions: {
|
||||
areaspline: {
|
||||
fillOpacity: 0.4
|
||||
}
|
||||
},
|
||||
series: [playersOnlineSeries, /*emptyAveragePlayersSeries*/]
|
||||
}));
|
||||
}
|
||||
|
||||
function playersChartNoNav(id, playersOnlineSeries) {
|
||||
graphs.push(Highcharts.stockChart(id, {
|
||||
rangeSelector: {
|
||||
selected: 3,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
navigator: {
|
||||
enabled: false
|
||||
},
|
||||
yAxis: {
|
||||
softMax: 2,
|
||||
softMin: 0
|
||||
},
|
||||
title: {text: ''},
|
||||
plotOptions: {
|
||||
areaspline: {
|
||||
fillOpacity: 0.4
|
||||
}
|
||||
},
|
||||
series: [playersOnlineSeries]
|
||||
}));
|
||||
}
|
||||
|
||||
function punchCard(id, punchcardSeries) {
|
||||
graphs.push(Highcharts.chart(id, {
|
||||
chart: {
|
||||
defaultSeriesType: 'scatter'
|
||||
},
|
||||
title: {text: ''},
|
||||
xAxis: {
|
||||
type: 'datetime',
|
||||
dateTimeLabelFormats: {
|
||||
// https://www.php.net/manual/en/function.strftime.php
|
||||
hour: '%I %P',
|
||||
day: '%I %P'
|
||||
},
|
||||
tickInterval: 3600000
|
||||
},
|
||||
time: {
|
||||
timezoneOffset: 0
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: "Day of the Week"
|
||||
},
|
||||
reversed: true,
|
||||
categories: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
||||
},
|
||||
tooltip: {
|
||||
pointFormat: 'Activity: {point.z}'
|
||||
},
|
||||
series: [punchcardSeries]
|
||||
}));
|
||||
}
|
||||
|
||||
function resourceChart(id, cpuSeries, ramSeries, playersOnlineSeries) {
|
||||
graphs.push(Highcharts.stockChart(id, {
|
||||
rangeSelector: {
|
||||
selected: 1,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
tooltip: {
|
||||
split: true
|
||||
},
|
||||
title: {text: ''},
|
||||
plotOptions: {
|
||||
areaspline: {
|
||||
fillOpacity: 0.4
|
||||
}
|
||||
},
|
||||
yAxis: [{
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' Players';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + '%';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' MB';
|
||||
}
|
||||
}
|
||||
}],
|
||||
legend: {
|
||||
enabled: true
|
||||
},
|
||||
series: [cpuSeries, ramSeries, playersOnlineSeries]
|
||||
}));
|
||||
}
|
||||
|
||||
function serverPie(id, serverSeries) {
|
||||
graphs.push(Highcharts.chart(id, {
|
||||
chart: {
|
||||
plotBackgroundColor: null,
|
||||
plotBorderWidth: null,
|
||||
plotShadow: false,
|
||||
type: 'pie'
|
||||
},
|
||||
title: {text: ''},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
showInLegend: true
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
formatter: function () {
|
||||
return '<b>' + this.point.name + ':</b> ' + formatTimeAmount(this.y) + ' (' + this.percentage.toFixed(2) + '%)';
|
||||
}
|
||||
},
|
||||
series: [serverSeries]
|
||||
}));
|
||||
}
|
||||
|
||||
function joinAddressPie(id, joinAddresses) {
|
||||
if (joinAddresses.data.length < 2) {
|
||||
document.getElementById(id).innerHTML = '<div class="card-body"><p></p></div>'
|
||||
document.getElementById(id).classList.remove('chart-area');
|
||||
|
||||
// XSS danger appending join addresses directly, using innerText is safe.
|
||||
for (let slice of joinAddresses.data) {
|
||||
document.querySelector(`#${id} p`).innerText = `${slice.name}: ${slice.y}`;
|
||||
}
|
||||
} else {
|
||||
document.getElementById(id).innerHTML = '';
|
||||
document.getElementById(id).classList.add('chart-area');
|
||||
graphs.push(Highcharts.chart(id, {
|
||||
chart: {
|
||||
plotBackgroundColor: null,
|
||||
plotBorderWidth: null,
|
||||
plotShadow: false,
|
||||
type: 'pie'
|
||||
},
|
||||
title: {text: ''},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
showInLegend: true
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
formatter: function () {
|
||||
return '<b>' + this.point.name + ':</b> ' + this.y + ' (' + this.percentage.toFixed(2) + '%)';
|
||||
}
|
||||
},
|
||||
series: [joinAddresses]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function formatTimeAmount(ms) {
|
||||
let out = "";
|
||||
|
||||
let seconds = Math.floor(ms / 1000);
|
||||
|
||||
const dd = Math.floor(seconds / 86400);
|
||||
seconds -= (dd * 86400);
|
||||
const dh = Math.floor(seconds / 3600);
|
||||
seconds -= (dh * 3600);
|
||||
const dm = Math.floor(seconds / 60);
|
||||
seconds -= (dm * 60);
|
||||
seconds = Math.floor(seconds);
|
||||
if (dd !== 0) {
|
||||
out += dd.toString() + "d ";
|
||||
}
|
||||
if (dh !== 0) {
|
||||
out += dh.toString() + "h ";
|
||||
}
|
||||
if (dm !== 0) {
|
||||
out += dm.toString() + "m ";
|
||||
}
|
||||
out += seconds.toString() + "s ";
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function sessionCalendar(id, event_data, firstDay) {
|
||||
document.querySelector(id + " .loader").remove();
|
||||
window.calendars.sessions = new FullCalendar.Calendar(document.querySelector(id), {
|
||||
timeZone: "UTC",
|
||||
themeSystem: 'bootstrap',
|
||||
eventColor: '#009688',
|
||||
dayMaxEventRows: 4,
|
||||
firstDay: firstDay,
|
||||
initialView: 'dayGridMonth',
|
||||
|
||||
eventDidMount: function (info) {
|
||||
$(info.el).popover({
|
||||
content: info.event.title,
|
||||
trigger: 'hover',
|
||||
placement: 'top',
|
||||
container: 'body'
|
||||
});
|
||||
},
|
||||
|
||||
events: function (fetchInfo, successCallback, failureCallback) {
|
||||
successCallback(event_data)
|
||||
},
|
||||
|
||||
navLinks: true,
|
||||
height: 450,
|
||||
contentHeight: 445,
|
||||
headerToolbar: {
|
||||
left: 'title',
|
||||
center: '',
|
||||
right: 'dayGridMonth dayGridWeek dayGridDay today prev,next'
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
window.calendars.sessions.render();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function stackChart(id, categories, series, label) {
|
||||
graphs.push(Highcharts.chart(id, {
|
||||
chart: {
|
||||
type: 'area'
|
||||
},
|
||||
title: {
|
||||
text: ''
|
||||
},
|
||||
xAxis: {
|
||||
categories: categories,
|
||||
tickmarkPlacement: 'on',
|
||||
title: {
|
||||
enabled: false
|
||||
},
|
||||
ordinal: false
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: label
|
||||
},
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value;
|
||||
}
|
||||
},
|
||||
softMax: 2,
|
||||
softMin: 0
|
||||
},
|
||||
tooltip: {
|
||||
split: true,
|
||||
valueSuffix: ' ' + label
|
||||
},
|
||||
plotOptions: {
|
||||
area: {
|
||||
stacking: 'normal',
|
||||
lineWidth: 1
|
||||
}
|
||||
},
|
||||
series: series
|
||||
}));
|
||||
}
|
||||
|
||||
function tpsChart(id, tpsSeries, playersOnlineSeries) {
|
||||
graphs.push(Highcharts.stockChart(id, {
|
||||
rangeSelector: {
|
||||
selected: 1,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
tooltip: {
|
||||
split: true
|
||||
},
|
||||
title: {text: ''},
|
||||
plotOptions: {
|
||||
areaspline: {
|
||||
fillOpacity: 0.4
|
||||
}
|
||||
},
|
||||
yAxis: [{
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' Players';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' TPS';
|
||||
}
|
||||
}
|
||||
}],
|
||||
legend: {
|
||||
enabled: true
|
||||
},
|
||||
series: [tpsSeries, playersOnlineSeries]
|
||||
}));
|
||||
}
|
||||
|
||||
function worldChart(id, entitySeries, chunkSeries, playersOnlineSeries) {
|
||||
graphs.push(Highcharts.stockChart(id, {
|
||||
rangeSelector: {
|
||||
selected: 1,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
tooltip: {
|
||||
split: true
|
||||
},
|
||||
title: {text: ''},
|
||||
plotOptions: {
|
||||
areaspline: {
|
||||
fillOpacity: 0.4
|
||||
}
|
||||
},
|
||||
yAxis: [{
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' Players';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' Entities';
|
||||
}
|
||||
}
|
||||
}, {
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' Chunks';
|
||||
}
|
||||
}
|
||||
}],
|
||||
legend: {
|
||||
enabled: true
|
||||
},
|
||||
series: [entitySeries, chunkSeries, playersOnlineSeries]
|
||||
}));
|
||||
}
|
||||
|
||||
function worldMap(id, colorMin, colorMax, mapSeries) {
|
||||
graphs.push(Highcharts.mapChart(id, {
|
||||
chart: {
|
||||
animation: true
|
||||
},
|
||||
title: {text: ''},
|
||||
|
||||
mapNavigation: {
|
||||
enabled: true,
|
||||
enableDoubleClickZoomTo: true
|
||||
},
|
||||
|
||||
colorAxis: {
|
||||
min: 1,
|
||||
type: 'logarithmic',
|
||||
minColor: colorMin,
|
||||
maxColor: colorMax
|
||||
},
|
||||
series: [mapSeries]
|
||||
}));
|
||||
}
|
||||
|
||||
function worldPie(id, worldSeries, gmSeries) {
|
||||
const defaultTitle = '';
|
||||
const defaultSubtitle = 'Click to expand';
|
||||
const chart = Highcharts.chart(id, {
|
||||
chart: {
|
||||
plotBackgroundColor: null,
|
||||
plotBorderWidth: null,
|
||||
plotShadow: false,
|
||||
type: 'pie',
|
||||
events: {
|
||||
drilldown: function (e) {
|
||||
chart.setTitle({text: '' + e.point.name}, {text: ''});
|
||||
},
|
||||
drillup: function (e) {
|
||||
chart.setTitle({text: defaultTitle}, {text: defaultSubtitle});
|
||||
}
|
||||
}
|
||||
},
|
||||
title: {text: defaultTitle},
|
||||
subtitle: {
|
||||
text: defaultSubtitle
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
showInLegend: true
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
formatter: function () {
|
||||
return '<b>' + this.point.name + ':</b> ' + formatTimeAmount(this.y) + ' (' + this.percentage.toFixed(2) + '%)';
|
||||
}
|
||||
},
|
||||
series: [worldSeries],
|
||||
drilldown: {
|
||||
series: gmSeries.map(function (d) {
|
||||
return {name: d.name, id: d.id, colors: gmPieColors, data: d.data}
|
||||
})
|
||||
}
|
||||
});
|
||||
graphs.push(chart);
|
||||
}
|
||||
|
||||
function updateGraphs() {
|
||||
// HighCharts nukes the scrollbar variable from the given parameter
|
||||
// If the graph doesn't support srollbars (bar, pie and map charts for example)
|
||||
// This workaround stores a copy of the scrollbar so that it can be set
|
||||
const scrollbar = {...Highcharts.theme.scrollbar};
|
||||
|
||||
function updateGraph(graph, index, array) {
|
||||
// Empty objects can be left in the array if existing graph is re-rendered
|
||||
if (Object.keys(graph).length === 0) {
|
||||
array.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// scrollbar workaround
|
||||
if (!Highcharts.theme["scrollbar"]) Highcharts.theme["scrollbar"] = {...scrollbar};
|
||||
|
||||
graph.update(Highcharts.theme);
|
||||
}
|
||||
|
||||
graphs.forEach(updateGraph);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user