1623/access control (#3173)

* Add web authorization permission based on groups
  * Access and parts of website are limited by permissions
* Add group management in /manage page
  * Higher level permissions grant lower level permissions similar to Sponge
* Add command /plan setgroup, which uses plan.setgroup.other permission
* Add command /plan groups, which uses plan.setgroup.other permission
* Add more navigation based on permissions
* API modifications
  * User#hasPermission now returns true if user has parent permission in the tree
  * ResolverService#registerPermissions and ResolverService#registerPermission methods for adding new permissions
* Update locale with new lines
* Various unrelated fixes to CSS and code

Affects issues:
- Close #1623
This commit is contained in:
Aurora Lahtela 2023-08-20 11:56:13 +03:00 committed by GitHub
parent 4884cf28c2
commit 5061439d14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
204 changed files with 7567 additions and 1013 deletions

View File

@ -8,7 +8,7 @@ compileJava {
options.release = 8
}
ext.apiVersion = '5.5-R0.1'
ext.apiVersion = '5.6-R0.1'
publishing {
repositories {

View File

@ -90,7 +90,11 @@ enum Capability {
* {@link com.djrapitops.plan.delivery.web.ResourceService#addJavascriptToResource(String, String, ResourceService.Position, String, String)}
* {@link com.djrapitops.plan.delivery.web.ResourceService#addStyleToResource(String, String, ResourceService.Position, String, String)}
*/
PAGE_EXTENSION_RESOURCES_REGISTER_DIRECT_CUSTOMIZATION;
PAGE_EXTENSION_RESOURCES_REGISTER_DIRECT_CUSTOMIZATION,
/**
* {@link com.djrapitops.plan.delivery.web.ResolverService#registerPermissions(String...)}
*/
PAGE_EXTENSION_USER_PERMISSIONS;
static Optional<Capability> getByName(String name) {
if (name == null) {

View File

@ -17,9 +17,11 @@
package com.djrapitops.plan.delivery.web;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
@ -63,6 +65,26 @@ public interface ResolverService {
*/
void registerResolverForMatches(String pluginName, Pattern pattern, Resolver resolver);
/**
* Register a new permission that you are using in your {@link Resolver#canAccess(Request)} method.
* <p>
* The permissions are not given to any users by default, and need to be given by admin manually.
*
* @param webPermissions Permission strings, higher level permissions grant lower level automatically - eg. page.foo also grants page.foo.bar
* @return CompletableFuture that tells when the permissions have been stored.
*/
CompletableFuture<Void> registerPermissions(String... webPermissions);
/**
* Register a new permission that you are using in your {@link Resolver#canAccess(Request)} method.
* <p>
* The permission is granted to any groups with {@code whenHasPermission} parameter.
*
* @param webPermission Permission string, higher level permissions grant lower level automatically - eg. page.foo also grants page.foo.bar
* @param whenHasPermission Permission that a group already has that this permission should be granted to - eg. page.network.overview.numbers
*/
void registerPermission(String webPermission, String whenHasPermission);
/**
* Obtain a {@link Resolver} for a target.
* <p>

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.delivery.web.resolver.request;
import java.util.*;
import java.util.function.Supplier;
public final class WebUser {
@ -57,7 +58,16 @@ public final class WebUser {
}
public boolean hasPermission(String permission) {
return permissions.contains(permission);
for (String grant : permissions) {
String substitute = permission.replace(grant, "");
// Last character is . so it is a sub-permission of the parent, eg. page.player, page.player.thing -> .thing
if (substitute.isEmpty() || substitute.startsWith(".")) return true;
}
return false;
}
public boolean hasPermission(Supplier<String> permissionSupplier) {
return hasPermission(permissionSupplier.get());
}
public String getName() {

View File

@ -36,10 +36,10 @@ def buildVersion = determineBuildVersion()
allprojects {
group "com.djrapitops"
version "5.5-SNAPSHOT"
version "5.6-SNAPSHOT"
ext.majorVersion = '5'
ext.minorVersion = '5'
ext.minorVersion = '6'
ext.buildVersion = buildVersion
ext.fullVersion = project.ext.majorVersion + '.' + project.ext.minorVersion + ' build ' + project.ext.buildVersion

View File

@ -180,12 +180,12 @@ public class PlanSystem implements SubSystem {
queryService.register();
enableSystems(
processing,
files,
localeSystem,
versionChecker,
databaseSystem,
webServerSystem,
processing,
serverInfo,
importSystem,
exportSystem,

View File

@ -115,6 +115,8 @@ public class PlanCommand {
.subcommand(unregisterCommand())
.subcommand(logoutCommand())
.subcommand(webUsersCommand())
.subcommand(groups())
.subcommand(setGroup())
.subcommand(acceptCommand())
.subcommand(cancelCommand())
@ -261,7 +263,7 @@ public class PlanCommand {
.requiredArgument(locale.getString(HelpLang.ARG_USERNAME), locale.getString(HelpLang.DESC_ARG_USERNAME))
.description(locale.getString(HelpLang.LOGOUT))
.inDepthDescription(locale.getString(DeepHelpLang.LOGOUT))
.onCommand(registrationCommands::onLogoutCommand)
.onArgsOnlyCommand(registrationCommands::onLogoutCommand)
.onTabComplete(this::webUserNames)
.build();
}
@ -514,4 +516,36 @@ public class PlanCommand {
.onTabComplete(this::playerNames)
.build();
}
private Subcommand setGroup() {
return Subcommand.builder()
.aliases("setgroup")
.requirePermission(Permissions.SET_GROUP)
.requiredArgument(locale.getString(HelpLang.ARG_USERNAME), locale.getString(HelpLang.DESC_ARG_USERNAME))
.requiredArgument(locale.getString(HelpLang.ARG_GROUP), locale.getString(HelpLang.DESC_ARG_GROUP))
.description(locale.getString(HelpLang.SET_GROUP))
.inDepthDescription(locale.getString(DeepHelpLang.SET_GROUP))
.onCommand(registrationCommands::onChangePermissionGroup)
.onTabComplete(this::webGroupTabComplete)
.build();
}
private List<String> webGroupTabComplete(CMDSender sender, @Untrusted Arguments arguments) {
Optional<String> groupArgument = arguments.get(1);
if (groupArgument.isPresent()) {
return tabCompleteCache.getMatchingWebGroupNames(groupArgument.get());
}
String usernameArgument = arguments.get(0).orElse(null);
return tabCompleteCache.getMatchingUserIdentifiers(usernameArgument);
}
private Subcommand groups() {
return Subcommand.builder()
.aliases("groups")
.requirePermission(Permissions.SET_GROUP)
.description(locale.getString(HelpLang.GROUPS))
.inDepthDescription(locale.getString(DeepHelpLang.GROUPS))
.onCommand(registrationCommands::onListWebGroups)
.build();
}
}

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.commands;
import com.djrapitops.plan.SubSystem;
import com.djrapitops.plan.delivery.domain.auth.User;
import com.djrapitops.plan.gathering.ServerSensor;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
@ -52,6 +51,7 @@ public class TabCompleteCache implements SubSystem {
private final Set<String> serverIdentifiers;
private final Set<String> userIdentifiers;
private final Set<String> backupFileNames;
private final Set<String> webGroupIdentifiers;
@Inject
public TabCompleteCache(
@ -68,6 +68,7 @@ public class TabCompleteCache implements SubSystem {
serverIdentifiers = new HashSet<>();
userIdentifiers = new HashSet<>();
backupFileNames = new HashSet<>();
webGroupIdentifiers = new HashSet<>();
}
@Override
@ -77,9 +78,14 @@ public class TabCompleteCache implements SubSystem {
refreshServerIdentifiers();
refreshUserIdentifiers();
refreshBackupFileNames();
refreshWebGroupIdentifiers();
});
}
private void refreshWebGroupIdentifiers() {
webGroupIdentifiers.addAll(dbSystem.getDatabase().query(WebUserQueries.fetchGroupNames()));
}
private void refreshServerIdentifiers() {
Map<ServerUUID, Server> serverNames = dbSystem.getDatabase().query(ServerQueries.fetchPlanServerInformation());
for (Map.Entry<ServerUUID, Server> server : serverNames.entrySet()) {
@ -94,9 +100,7 @@ public class TabCompleteCache implements SubSystem {
}
private void refreshUserIdentifiers() {
dbSystem.getDatabase().query(WebUserQueries.fetchAllUsers()).stream()
.map(User::getUsername)
.forEach(userIdentifiers::add);
userIdentifiers.addAll(dbSystem.getDatabase().query(WebUserQueries.fetchAllUsernames()));
}
private void refreshBackupFileNames() {
@ -133,6 +137,10 @@ public class TabCompleteCache implements SubSystem {
return findMatches(backupFileNames, searchFor);
}
public List<String> getMatchingWebGroupNames(@Untrusted String searchFor) {
return findMatches(webGroupIdentifiers, searchFor);
}
@NotNull
List<String> findMatches(Collection<String> searchList, @Untrusted String searchFor) {
List<String> filtered = searchList.stream()

View File

@ -217,7 +217,7 @@ public class LinkCommands {
sender.send(t + locale.getString(CommandLang.HEADER_WEB_USERS, 0));
} else {
String usersListed = users.stream().sorted()
.map(user -> m + user.getUsername() + "::" + t + user.getLinkedTo() + "::" + s + user.getPermissionLevel() + "\n")
.map(user -> m + user.getUsername() + "::" + t + user.getLinkedTo() + "::" + s + user.getPermissionGroup() + "\n")
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
.toString();
sender.buildMessage()

View File

@ -20,6 +20,7 @@ import com.djrapitops.plan.commands.use.Arguments;
import com.djrapitops.plan.commands.use.CMDSender;
import com.djrapitops.plan.commands.use.ColorScheme;
import com.djrapitops.plan.delivery.domain.auth.User;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieStore;
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
import com.djrapitops.plan.delivery.webserver.auth.RegistrationBin;
@ -33,7 +34,6 @@ import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.transactions.commands.RemoveWebUserTransaction;
import com.djrapitops.plan.storage.database.transactions.commands.StoreWebUserTransaction;
import com.djrapitops.plan.utilities.PassEncryptUtil;
import com.djrapitops.plan.utilities.dev.Untrusted;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
@ -41,7 +41,7 @@ import net.playeranalytics.plugin.server.PluginLogger;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@ -99,61 +99,41 @@ public class RegistrationCommands {
} else {
@Untrusted Optional<String> code = arguments.getAfter("--code");
if (code.isPresent()) {
registerUsingCode(sender, code.get());
registerUsingCode(sender, code.get(), arguments);
} else {
registerUsingLegacy(sender, arguments);
sender.send(locale.getString(CommandLang.FAIL_REQ_ARGS, "--code", "/plan register --code 81cc5b17"));
}
}
}
public void registerUsingCode(CMDSender sender, @Untrusted String code) {
public void registerUsingCode(CMDSender sender, @Untrusted String code, @Untrusted Arguments arguments) {
UUID linkedToUUID = sender.getUUID().orElse(null);
Optional<User> user = RegistrationBin.register(code, linkedToUUID);
if (user.isPresent()) {
registerUser(user.get(), sender, getPermissionLevel(sender));
} else {
throw new IllegalArgumentException(locale.getString(FailReason.USER_INFORMATION_NOT_FOUND));
}
User user = RegistrationBin.register(code, linkedToUUID)
.orElseThrow(() -> new IllegalArgumentException(locale.getString(FailReason.USER_INFORMATION_NOT_FOUND)));
String permissionGroup = getPermissionGroup(sender, arguments)
.orElseThrow(() -> new IllegalArgumentException(locale.getString(FailReason.NO_PERMISSION_GROUP)));
user.setPermissionGroup(permissionGroup);
registerUser(user, sender);
}
public void registerUsingLegacy(CMDSender sender, @Untrusted Arguments arguments) {
@Untrusted String password = arguments.get(0)
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ARGS, 1, "<password>")));
String passwordHash = PassEncryptUtil.createHash(password);
int permissionLevel = arguments.getInteger(2)
.filter(arg -> sender.hasPermission(Permissions.REGISTER_OTHER)) // argument only allowed with register other permission
.orElseGet(() -> getPermissionLevel(sender));
Optional<UUID> senderUUID = sender.getUUID();
Optional<String> senderName = sender.getPlayerName();
if (senderUUID.isPresent() && senderName.isPresent()) {
String playerName = senderName.get();
UUID linkedToUUID = senderUUID.get();
@Untrusted String username = arguments.get(1).orElse(playerName);
registerUser(new User(username, playerName, linkedToUUID, passwordHash, permissionLevel, Collections.emptyList()), sender, permissionLevel);
} else {
@Untrusted String username = arguments.get(1)
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ARGS, 3, "<password> <name> <level>")));
registerUser(new User(username, "console", null, passwordHash, permissionLevel, Collections.emptyList()), sender, permissionLevel);
private Optional<String> getPermissionGroup(CMDSender sender, @Untrusted Arguments arguments) {
List<String> groups = dbSystem.getDatabase().query(WebUserQueries.fetchGroupNames());
if (sender.isPlayer()) {
for (String group : groups) {
if (sender.hasPermission("plan.webgroup." + group)) {
return Optional.of(group);
}
}
} else if (arguments.contains("superuser")) {
return dbSystem.getDatabase().query(WebUserQueries.fetchGroupNamesWithPermission(WebPermission.MANAGE_GROUPS.getPermission()))
.stream().findFirst();
}
return Optional.empty();
}
private int getPermissionLevel(CMDSender sender) {
if (sender.hasPermission(Permissions.SERVER)) {
return 0;
}
if (sender.hasPermission(Permissions.PLAYER_OTHER)) {
return 1;
}
if (sender.hasPermission(Permissions.PLAYER_SELF)) {
return 2;
}
return 100;
}
private void registerUser(User user, CMDSender sender, int permissionLevel) {
private void registerUser(User user, CMDSender sender) {
String username = user.getUsername();
user.setPermissionLevel(permissionLevel);
try {
Database database = dbSystem.getDatabase();
boolean userExists = database.query(WebUserQueries.fetchUser(username)).isPresent();
@ -163,11 +143,11 @@ public class RegistrationCommands {
.get(); // Wait for completion
sender.send(locale.getString(CommandLang.WEB_USER_REGISTER_SUCCESS, username));
logger.info(locale.getString(CommandLang.WEB_USER_REGISTER_NOTIFY, username, permissionLevel));
logger.info(locale.getString(CommandLang.WEB_USER_REGISTER_NOTIFY, username, user.getPermissionGroup()));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (DBOpException | ExecutionException e) {
errorLogger.warn(e, ErrorContext.builder().related(sender, user, permissionLevel).build());
errorLogger.warn(e, ErrorContext.builder().related(sender, user).build());
}
}
@ -217,7 +197,7 @@ public class RegistrationCommands {
}
public void onLogoutCommand(CMDSender sender, @Untrusted Arguments arguments) {
public void onLogoutCommand(@Untrusted Arguments arguments) {
@Untrusted String loggingOut = arguments.get(0)
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ONE_ARG, locale.getString(HelpLang.ARG_USERNAME) + "/*")));
@ -227,4 +207,32 @@ public class RegistrationCommands {
ActiveCookieStore.removeUserCookie(loggingOut);
}
}
public void onChangePermissionGroup(CMDSender sender, @Untrusted Arguments arguments) {
String username = arguments.get(0)
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ARGS, locale.getString(HelpLang.ARG_USERNAME))));
String group = arguments.get(1)
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ARGS, locale.getString(HelpLang.ARG_GROUP))));
Database database = dbSystem.getDatabase();
User user = database.query(WebUserQueries.fetchUser(username))
.orElseThrow(() -> new IllegalArgumentException(locale.getString(FailReason.USER_DOES_NOT_EXIST)));
Optional<Integer> groupId = database.query(WebUserQueries.fetchGroupId(group));
if (groupId.isEmpty()) {
throw new IllegalArgumentException(locale.getString(FailReason.GROUP_DOES_NOT_EXIST));
}
user.setPermissionGroup(group);
database.executeTransaction(new StoreWebUserTransaction(user))
.thenRun(() -> sender.send(locale.getString(CommandLang.PROGRESS_SUCCESS)));
}
public void onListWebGroups(CMDSender sender) {
Database database = dbSystem.getDatabase();
List<String> groupNames = database.query(WebUserQueries.fetchGroupNames());
sender.send(String.join(", ", groupNames));
}
}

View File

@ -54,6 +54,10 @@ public interface SubcommandBuilder {
return onCommand((sender, arguments) -> executor.accept(sender));
}
default SubcommandBuilder onArgsOnlyCommand(Consumer<Arguments> executor) {
return onCommand((sender, arguments) -> executor.accept(arguments));
}
SubcommandBuilder onTabComplete(BiFunction<CMDSender, Arguments, List<String>> resolver);
Subcommand build();

View File

@ -1,93 +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.domain;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Object containing webserver security user information.
*
* @author AuroraLS3
* @deprecated Use {@link com.djrapitops.plan.delivery.domain.auth.User} instead
* TODO Rewrite Authentication stuff
*/
@Deprecated(since = "2022-02-12, User.java")
public class WebUser {
private final String username;
private final String saltedPassHash;
private final int permLevel;
public WebUser(String username, String saltedPassHash, int permLevel) {
this.username = username;
this.saltedPassHash = saltedPassHash;
this.permLevel = permLevel;
}
public static List<String> getPermissionsForLevel(int level) {
List<String> permissions = new ArrayList<>();
if (level <= 0) {
permissions.add("page.network");
permissions.add("page.server");
permissions.add("page.debug");
// TODO Add JSON Permissions
}
if (level <= 1) {
permissions.add("page.players");
permissions.add("page.player.other");
}
if (level <= 2) {
permissions.add("page.player.self");
}
return permissions;
}
public String getSaltedPassHash() {
return saltedPassHash;
}
public int getPermLevel() {
return permLevel;
}
public String getName() {
return username;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WebUser webUser = (WebUser) o;
return permLevel == webUser.permLevel &&
Objects.equals(username, webUser.username) &&
Objects.equals(saltedPassHash, webUser.saltedPassHash);
}
@Override
public int hashCode() {
return Objects.hash(username, saltedPassHash, permLevel);
}
public com.djrapitops.plan.delivery.web.resolver.request.WebUser toNewWebUser() {
return new com.djrapitops.plan.delivery.web.resolver.request.WebUser(
username, getPermissionsForLevel(permLevel).toArray(new String[0])
);
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.auth;
import java.util.Objects;
/**
* Represents Plan web permission group without permissions or users.
* <p>
* This object is used instead of a String in case more attributes are added in the future.
*
* @author AuroraLS3
*/
public class Group {
private final String name;
public Group(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Group group = (Group) o;
return Objects.equals(getName(), group.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName());
}
@Override
public String toString() {
return "WebGroup{" +
"name='" + name + '\'' +
'}';
}
}

View File

@ -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.delivery.domain.auth;
import java.util.List;
import java.util.Objects;
/**
* Represents Plan web permission group listing without associated permissions.
*
* @author AuroraLS3
*/
public class GroupList {
private final List<Group> groups;
public GroupList(List<Group> groups) {
this.groups = groups;
}
public List<Group> getGroups() {
return groups;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GroupList that = (GroupList) o;
return Objects.equals(getGroups(), that.getGroups());
}
@Override
public int hashCode() {
return Objects.hash(getGroups());
}
@Override
public String toString() {
return "WebGroupList{" +
"groups=" + groups +
'}';
}
}

View File

@ -36,15 +36,15 @@ public class User implements Comparable<User> {
private final String linkedTo;
private final UUID linkedToUUID; // null for 'console'
private final String passwordHash;
private int permissionLevel;
private String permissionGroup;
private final Collection<String> permissions;
public User(@Untrusted String username, String linkedTo, UUID linkedToUUID, String passwordHash, int permissionLevel, Collection<String> permissions) {
public User(@Untrusted String username, String linkedTo, UUID linkedToUUID, String passwordHash, String permissionGroup, Collection<String> permissions) {
this.username = username;
this.linkedTo = linkedTo;
this.linkedToUUID = linkedToUUID;
this.passwordHash = passwordHash;
this.permissionLevel = permissionLevel;
this.permissionGroup = permissionGroup;
this.permissions = permissions;
}
@ -73,20 +73,16 @@ public class User implements Comparable<User> {
return passwordHash;
}
/**
* @deprecated Permission list should be used instead.
*/
@Deprecated(since = "2022-05-04", forRemoval = true)
public int getPermissionLevel() {
return permissionLevel;
public String getPermissionGroup() {
return permissionGroup;
}
/**
* @deprecated Permission list should be used instead.
*/
@Deprecated(since = "2022-05-04", forRemoval = true)
public void setPermissionLevel(int permissionLevel) {
this.permissionLevel = permissionLevel;
public void setPermissionGroup(String permissionGroup) {
this.permissionGroup = permissionGroup;
}
public Collection<String> getPermissions() {
return permissions;
}
@Override
@ -96,7 +92,7 @@ public class User implements Comparable<User> {
", linkedTo='" + linkedTo + '\'' +
", linkedToUUID=" + linkedToUUID +
", passwordHash='" + passwordHash + '\'' +
", permissionLevel=" + permissionLevel +
", permissionGroup=" + permissionGroup +
", permissions=" + permissions +
'}';
}
@ -106,22 +102,22 @@ public class User implements Comparable<User> {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return permissionLevel == user.permissionLevel &&
Objects.equals(username, user.username) &&
return Objects.equals(username, user.username) &&
Objects.equals(linkedTo, user.linkedTo) &&
Objects.equals(linkedToUUID, user.linkedToUUID) &&
Objects.equals(passwordHash, user.passwordHash) &&
Objects.equals(permissionGroup, user.permissionGroup) &&
Objects.equals(permissions, user.permissions);
}
@Override
public int hashCode() {
return Objects.hash(username, linkedTo, linkedToUUID, passwordHash, permissionLevel, permissions);
return Objects.hash(username, linkedTo, linkedToUUID, passwordHash, permissionGroup, permissions);
}
@Override
public int compareTo(User other) {
int comparison = Integer.compare(this.permissionLevel, other.permissionLevel);
int comparison = String.CASE_INSENSITIVE_ORDER.compare(this.permissionGroup, other.permissionGroup);
if (comparison == 0) comparison = String.CASE_INSENSITIVE_ORDER.compare(this.username, other.username);
return comparison;
}

View File

@ -0,0 +1,155 @@
/*
* 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.auth;
import com.djrapitops.plan.settings.locale.lang.Lang;
import org.apache.commons.lang3.StringUtils;
import java.util.function.Supplier;
/**
* List of web permissions.
*
* @author AuroraLS3
*/
public enum WebPermission implements Supplier<String>, Lang {
PAGE("Controls what is visible on pages"),
PAGE_NETWORK("See all of network page"),
PAGE_NETWORK_OVERVIEW("See Network Overview -tab"),
PAGE_NETWORK_OVERVIEW_NUMBERS("See Network Overview numbers"),
PAGE_NETWORK_OVERVIEW_GRAPHS("See Network Overview graphs"),
PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE("See Players Online graph"),
PAGE_NETWORK_OVERVIEW_GRAPHS_DAY_BY_DAY("See Day by Day graph"),
PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR("See Hour by Hour graph"),
PAGE_NETWORK_SERVER_LIST("See list of servers"),
PAGE_NETWORK_PLAYERBASE("See Playerbase Overview -tab"),
PAGE_NETWORK_PLAYERBASE_OVERVIEW("See Playerbase Overview numbers"),
PAGE_NETWORK_PLAYERBASE_GRAPHS("See Playerbase Overview graphs"),
PAGE_NETWORK_SESSIONS("See Sessions tab"),
PAGE_NETWORK_SESSIONS_OVERVIEW("See Session insights"),
PAGE_NETWORK_SESSIONS_WORLD_PIE("See World Pie graph"),
PAGE_NETWORK_SESSIONS_SERVER_PIE("See Server Pie graph"),
PAGE_NETWORK_SESSIONS_LIST("See list of sessions"),
PAGE_NETWORK_JOIN_ADDRESSES("See Join Addresses -tab"),
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"),
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
PAGE_NETWORK_RETENTION("See Player Retention -tab"),
PAGE_NETWORK_GEOLOCATIONS("See Geolocations tab"),
PAGE_NETWORK_GEOLOCATIONS_MAP("See Geolocations Map"),
PAGE_NETWORK_GEOLOCATIONS_PING_PER_COUNTRY("See Ping Per Country table"),
PAGE_NETWORK_PLAYERS("See Player list -tab"),
PAGE_NETWORK_PERFORMANCE("See network Performance tab"),
PAGE_NETWORK_PLUGINS("See Plugins tab of Proxy"),
PAGE_SERVER("See all of server page"),
PAGE_SERVER_OVERVIEW("See Server Overview -tab"),
PAGE_SERVER_OVERVIEW_NUMBERS("See Server Overview numbers"),
PAGE_SERVER_OVERVIEW_PLAYERS_ONLINE_GRAPH("See Players Online graph"),
PAGE_SERVER_ONLINE_ACTIVITY("See Online Activity -tab"),
PAGE_SERVER_ONLINE_ACTIVITY_OVERVIEW("See Online Activity numbers"),
PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS("See Online Activity graphs"),
PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_DAY_BY_DAY("See Day by Day graph"),
PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_HOUR_BY_HOUR("See Hour by Hour graph"),
PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_PUNCHCARD("See Punchcard graph"),
PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_CALENDAR("See Server calendar"),
PAGE_SERVER_PLAYERBASE("See Playerbase Overview -tab"),
PAGE_SERVER_PLAYERBASE_OVERVIEW("See Playerbase Overview numbers"),
PAGE_SERVER_PLAYERBASE_GRAPHS("See Playerbase Overview graphs"),
PAGE_SERVER_PLAYER_VERSUS("See PvP & PvE -tab"),
PAGE_SERVER_PLAYER_VERSUS_OVERVIEW("See PvP & PvE numbers"),
PAGE_SERVER_PLAYER_VERSUS_KILL_LIST("See Player kill and death lists"),
PAGE_SERVER_PLAYERS("See Player list -tab"),
PAGE_SERVER_SESSIONS("See Sessions tab"),
PAGE_SERVER_SESSIONS_OVERVIEW("See Session insights"),
PAGE_SERVER_SESSIONS_WORLD_PIE("See World Pie graph"),
PAGE_SERVER_SESSIONS_LIST("See list of sessions"),
PAGE_SERVER_JOIN_ADDRESSES("See Join Addresses -tab"),
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"),
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
PAGE_SERVER_RETENTION("See Player Retention -tab"),
PAGE_SERVER_GEOLOCATIONS("See Geolocations tab"),
PAGE_SERVER_GEOLOCATIONS_MAP("See Geolocations Map"),
PAGE_SERVER_GEOLOCATIONS_PING_PER_COUNTRY("See Ping Per Country table"),
PAGE_SERVER_PERFORMANCE("See Performance tab"),
PAGE_SERVER_PERFORMANCE_GRAPHS("See Performance graphs"),
PAGE_SERVER_PERFORMANCE_OVERVIEW("See Performance numbers"),
PAGE_SERVER_PLUGINS("See Plugins -tabs of servers"),
PAGE_PLAYER("See all of player page"),
PAGE_PLAYER_OVERVIEW("See Player Overview -tab"),
PAGE_PLAYER_SESSIONS("See Player Sessions -tab"),
PAGE_PLAYER_VERSUS("See PvP & PvE -tab"),
PAGE_PLAYER_SERVERS("See Servers -tab"),
PAGE_PLAYER_PLUGINS("See Plugins -tabs"),
ACCESS("Controls access to pages"),
ACCESS_PLAYER("Allows accessing any /player pages"),
ACCESS_PLAYER_SELF("Allows accessing own /player page"),
ACCESS_RAW_PLAYER_DATA("Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."),
// Restricting to specific servers: access.server.uuid
ACCESS_SERVER("Allows accessing all /server pages"),
ACCESS_NETWORK("Allows accessing /network page"),
ACCESS_PLAYERS("Allows accessing /players page"),
ACCESS_QUERY("Allows accessing /query and Query results pages"),
ACCESS_ERRORS("Allows accessing /errors page"),
ACCESS_DOCS("Allows accessing /docs page"),
MANAGE_GROUPS("Allows modifying group permissions & Access to /manage/groups page"),
MANAGE_USERS("Allows modifying what users belong to what group");
private final String description;
private final boolean deprecated;
WebPermission(String description) {
this(description, false);
}
WebPermission(String description, boolean deprecated) {
this.description = description;
this.deprecated = deprecated;
}
public String getPermission() {
return StringUtils.lowerCase(name()).replace('_', '.');
}
public boolean isDeprecated() {
return deprecated;
}
@Override
public String get() {
return getPermission();
}
@Override
public String getIdentifier() {
return "HTML - Permission " + name();
}
@Override
public String getKey() {
return "html.manage.permission.description." + name().toLowerCase();
}
@Override
public String getDefault() {
return description;
}
}

View File

@ -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.delivery.domain.auth;
import java.util.List;
import java.util.Objects;
/**
* Represents a list of web permissions.
*
* @author AuroraLS3
*/
public class WebPermissionList {
private final List<String> permissions;
public WebPermissionList(List<String> permissions) {
this.permissions = permissions;
}
public List<String> getPermissions() {
return permissions;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WebPermissionList that = (WebPermissionList) o;
return Objects.equals(getPermissions(), that.getPermissions());
}
@Override
public int hashCode() {
return Objects.hash(getPermissions());
}
@Override
public String toString() {
return "PermissionList{" +
"permissions=" + permissions +
'}';
}
}

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.rendering.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.domain.container.PlayerContainer;
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionsDto;
import com.djrapitops.plan.delivery.domain.keys.PlayerKeys;
@ -56,6 +57,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
@Singleton
public class PlayerJSONCreator {
@ -96,18 +98,12 @@ public class PlayerJSONCreator {
return dbSystem.getDatabase().query(SessionQueries.lastSeen(playerUUID));
}
public Map<String, Object> createJSONAsMap(UUID playerUUID) {
public Map<String, Object> createJSONAsMap(UUID playerUUID, Predicate<WebPermission> hasPermission) {
Database db = dbSystem.getDatabase();
Map<ServerUUID, String> serverNames = db.query(ServerQueries.fetchServerNames());
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
PlayerContainer player = db.query(new PlayerContainerQuery(playerUUID));
SessionsMutator sessionsMutator = SessionsMutator.forContainer(player);
Map<ServerUUID, WorldTimes> worldTimesPerServer = PerServerMutator.forContainer(player).worldTimesPerServer();
List<Map<String, Object>> serverAccordion = new ServerAccordion(player, serverNames, graphs, year, timeAmount, locale.getString(GenericLang.UNKNOWN)).asMaps();
List<PlayerKill> kills = player.getValue(PlayerKeys.PLAYER_KILLS).orElse(Collections.emptyList());
List<PlayerKill> deaths = player.getValue(PlayerKeys.PLAYER_DEATHS_KILLS).orElse(Collections.emptyList());
PingMutator.forContainer(player).addPingToSessions(sessionsMutator.all());
@ -117,31 +113,52 @@ public class PlayerJSONCreator {
data.put("timestamp", now);
data.put("timestamp_f", year.apply(now));
data.put("info", createInfoJSONMap(player, serverNames));
data.put("online_activity", createOnlineActivityJSONMap(sessionsMutator));
data.put("kill_data", createPvPPvEMap(player));
if (hasPermission.test(WebPermission.PAGE_PLAYER_OVERVIEW)) {
data.put("info", createInfoJSONMap(player, serverNames));
data.put("online_activity", createOnlineActivityJSONMap(sessionsMutator));
data.put("nicknames", player.getValue(PlayerKeys.NICKNAMES)
.map(nicks -> Nickname.fromDataNicknames(nicks, serverNames, year))
.orElse(Collections.emptyList()));
data.put("connections", player.getValue(PlayerKeys.GEO_INFO)
.map(geoInfo -> ConnectionInfo.fromGeoInfo(geoInfo, year))
.orElse(Collections.emptyList()));
data.put("punchcard_series", graphs.special().punchCard(sessionsMutator).getDots());
} else {
data.put("info", createLimitedInfoMap(player));
}
if (hasPermission.test(WebPermission.PAGE_PLAYER_SESSIONS)) {
data.put("sessions", sessionsMutator.sort(new DateHolderRecentComparator()).toServerNameJSONMaps(graphs, config.getWorldAliasSettings(), formatters));
data.put("sessions_per_page", config.get(DisplaySettings.SESSIONS_PER_PAGE));
WorldPie worldPie = graphs.pie().worldPie(player.getValue(PlayerKeys.WORLD_TIMES).orElse(new WorldTimes()));
data.put("world_pie_series", worldPie.getSlices());
data.put("gm_series", worldPie.toHighChartsDrillDownMaps());
data.put("first_day", 1); // Monday
data.put("calendar_series", graphs.calendar().playerCalendar(player).getEntries());
}
if (hasPermission.test(WebPermission.PAGE_PLAYER_VERSUS)) {
List<PlayerKill> kills = player.getValue(PlayerKeys.PLAYER_KILLS).orElse(Collections.emptyList());
List<PlayerKill> deaths = player.getValue(PlayerKeys.PLAYER_DEATHS_KILLS).orElse(Collections.emptyList());
data.put("kill_data", createPvPPvEMap(player));
data.put("player_kills", new PlayerKillMutator(kills).filterNonSelfKills().toJSONAsMap(formatters));
data.put("player_deaths", new PlayerKillMutator(deaths).toJSONAsMap(formatters));
}
if (hasPermission.test(WebPermission.PAGE_PLAYER_SERVERS)) {
List<Map<String, Object>> serverAccordion = new ServerAccordion(player, serverNames, graphs, year, timeAmount, locale.getString(GenericLang.UNKNOWN)).asMaps();
Map<ServerUUID, WorldTimes> worldTimesPerServer = PerServerMutator.forContainer(player).worldTimesPerServer();
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
data.put("ping_graph", createPingGraphJson(player));
data.put("servers", serverAccordion);
data.put("server_pie_series", graphs.pie().serverPreferencePie(serverNames, worldTimesPerServer).getSlices());
data.put("server_pie_colors", pieColors);
}
if (hasPermission.test(WebPermission.PAGE_PLAYER_PLUGINS)) {
data.put("extensions", playerExtensionData(playerUUID));
} else {
data.put("extensions", List.of());
}
data.put("nicknames", player.getValue(PlayerKeys.NICKNAMES)
.map(nicks -> Nickname.fromDataNicknames(nicks, serverNames, year))
.orElse(Collections.emptyList()));
data.put("connections", player.getValue(PlayerKeys.GEO_INFO)
.map(geoInfo -> ConnectionInfo.fromGeoInfo(geoInfo, year))
.orElse(Collections.emptyList()));
data.put("player_kills", new PlayerKillMutator(kills).filterNonSelfKills().toJSONAsMap(formatters));
data.put("player_deaths", new PlayerKillMutator(deaths).toJSONAsMap(formatters));
data.put("sessions", sessionsMutator.sort(new DateHolderRecentComparator()).toServerNameJSONMaps(graphs, config.getWorldAliasSettings(), formatters));
data.put("sessions_per_page", config.get(DisplaySettings.SESSIONS_PER_PAGE));
data.put("servers", serverAccordion);
data.put("punchcard_series", graphs.special().punchCard(sessionsMutator).getDots());
WorldPie worldPie = graphs.pie().worldPie(player.getValue(PlayerKeys.WORLD_TIMES).orElse(new WorldTimes()));
data.put("world_pie_series", worldPie.getSlices());
data.put("gm_series", worldPie.toHighChartsDrillDownMaps());
data.put("calendar_series", graphs.calendar().playerCalendar(player).getEntries());
data.put("server_pie_series", graphs.pie().serverPreferencePie(serverNames, worldTimesPerServer).getSlices());
data.put("server_pie_colors", pieColors);
data.put("ping_graph", createPingGraphJson(player));
data.put("first_day", 1); // Monday
data.put("extensions", playerExtensionData(playerUUID));
return data;
}
@ -236,6 +253,15 @@ public class PlayerJSONCreator {
return info;
}
private Map<String, Object> createLimitedInfoMap(PlayerContainer player) {
Map<String, Object> info = new HashMap<>();
info.put("name", player.getValue(PlayerKeys.NAME).orElse(player.getUnsafe(PlayerKeys.UUID).toString()));
info.put("uuid", player.getUnsafe(PlayerKeys.UUID).toString());
return info;
}
private Map<String, Object> createPvPPvEMap(PlayerContainer playerContainer) {
long now = System.currentTimeMillis();
long weekAgo = now - TimeUnit.DAYS.toMillis(7L);

View File

@ -19,12 +19,16 @@ package com.djrapitops.plan.delivery.web;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.PluginSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.GrantWebPermissionToGroupsWithPermissionTransaction;
import com.djrapitops.plan.storage.database.transactions.StoreMissingWebPermissionsTransaction;
import com.djrapitops.plan.utilities.dev.Untrusted;
import net.playeranalytics.plugin.server.PluginLogger;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@ -38,14 +42,16 @@ public class ResolverSvc implements ResolverService {
private final PlanConfig config;
private final PluginLogger logger;
private final DBSystem dbSystem;
private final List<Container> basicResolvers;
private final List<Container> regexResolvers;
@Inject
public ResolverSvc(PlanConfig config, PluginLogger logger) {
public ResolverSvc(PlanConfig config, PluginLogger logger, DBSystem dbSystem) {
this.config = config;
this.logger = logger;
this.dbSystem = dbSystem;
basicResolvers = new ArrayList<>();
regexResolvers = new ArrayList<>();
}
@ -72,6 +78,20 @@ public class ResolverSvc implements ResolverService {
}
}
@Override
public CompletableFuture<Void> registerPermissions(String... webPermissions) {
return dbSystem.getDatabase().executeTransaction(new StoreMissingWebPermissionsTransaction(Arrays.asList(webPermissions)))
.thenRun(() -> {});
}
@Override
public void registerPermission(String webPermission, String whenHasPermission) {
registerPermissions(webPermission)
.thenRun(() -> dbSystem.getDatabase().executeTransaction(
new GrantWebPermissionToGroupsWithPermissionTransaction(webPermission, whenHasPermission)
));
}
@Override
public Optional<Resolver> getResolver(String target) {
for (Container container : basicResolvers) {

View File

@ -60,7 +60,7 @@ import java.util.regex.Pattern;
*/
@Singleton
@OpenAPIDefinition(info = @Info(
title = "Plan API endpoints",
title = "Swagger Docs",
description = "If authentication is enabled (see response of /v1/whoami) logging in is required for endpoints (/auth/login). Pass 'Cookie' header in the requests after login.",
contact = @Contact(name = "Github Discussions", url = "https://github.com/plan-player-analytics/Plan/discussions/categories/apis-and-development"),
license = @License(name = "GNU Lesser General Public License v3.0 (LGPLv3.0)", url = "https://github.com/plan-player-analytics/Plan/blob/master/LICENSE")
@ -82,6 +82,7 @@ public class ResponseResolver {
private final ErrorsPageResolver errorsPageResolver;
private final SwaggerJsonResolver swaggerJsonResolver;
private final SwaggerPageResolver swaggerPageResolver;
private final ManagePageResolver managePageResolver;
private final ErrorLogger errorLogger;
private final ResolverService resolverService;
@ -116,7 +117,7 @@ public class ResponseResolver {
SwaggerJsonResolver swaggerJsonResolver,
SwaggerPageResolver swaggerPageResolver,
ErrorLogger errorLogger
ManagePageResolver managePageResolver, ErrorLogger errorLogger
) {
this.resolverService = resolverService;
this.responseFactory = responseFactory;
@ -138,6 +139,7 @@ public class ResponseResolver {
this.errorsPageResolver = errorsPageResolver;
this.swaggerJsonResolver = swaggerJsonResolver;
this.swaggerPageResolver = swaggerPageResolver;
this.managePageResolver = managePageResolver;
this.errorLogger = errorLogger;
}
@ -154,7 +156,7 @@ public class ResponseResolver {
resolverService.registerResolver(plugin, "/player", playerPageResolver);
resolverService.registerResolver(plugin, "/network", serverPageResolver);
resolverService.registerResolver(plugin, "/server", serverPageResolver);
if (webserverConfiguration.isAuthenticationEnabled()) {
if (webServer.get().isAuthRequired()) {
resolverService.registerResolver(plugin, "/login", loginPageResolver);
resolverService.registerResolver(plugin, "/register", registerPageResolver);
resolverService.registerResolver(plugin, "/auth/login", loginResolver);
@ -162,6 +164,7 @@ public class ResponseResolver {
if (webserverConfiguration.isRegistrationEnabled()) {
resolverService.registerResolver(plugin, "/auth/register", registerResolver);
}
resolverService.registerResolver(plugin, "/manage", managePageResolver);
}
resolverService.registerResolver(plugin, "/errors", errorsPageResolver);

View File

@ -89,13 +89,14 @@ public class ActiveCookieStore implements SubSystem {
@Override
public void enable() {
ActiveCookieStore.setCookiesExpireAfter(config.get(WebserverSettings.COOKIES_EXPIRE_AFTER));
processing.submitNonCritical(this::loadActiveCookies);
processing.submitNonCritical(this::reloadActiveCookies);
}
private void loadActiveCookies() {
USERS_BY_COOKIE.clear();
public void reloadActiveCookies() {
try {
USERS_BY_COOKIE.putAll(dbSystem.getDatabase().query(WebUserQueries.fetchActiveCookies()));
Map<String, User> cookies = dbSystem.getDatabase().query(WebUserQueries.fetchActiveCookies());
USERS_BY_COOKIE.clear();
USERS_BY_COOKIE.putAll(cookies);
for (Map.Entry<String, Long> entry : dbSystem.getDatabase().query(WebUserQueries.getCookieExpiryTimes()).entrySet()) {
long timeToExpiry = Math.max(entry.getValue() - System.currentTimeMillis(), 0L);
activeCookieExpiryCleanupTask.addExpiry(entry.getKey(), System.currentTimeMillis() + timeToExpiry);

View File

@ -29,7 +29,9 @@ public enum FailReason implements Lang {
EXPIRED_COOKIE("html.error.auth.expiredCookie", "User cookie has expired"),
USER_AND_PASS_NOT_SPECIFIED("html.error.auth.emptyForm", "User and Password not specified"),
USER_DOES_NOT_EXIST("html.error.auth.userNotFound", "User does not exist"),
GROUP_DOES_NOT_EXIST("html.error.auth.groupNotFound", "Web Permission Group does not exist"),
USER_INFORMATION_NOT_FOUND("html.error.auth.registrationFailed", "Registration failed, try again (The code expires after 15 minutes)"),
NO_PERMISSION_GROUP("html.error.auth.noPermissionGroup", "Registration failed, player did not have any 'plan.webgroup.{name}' permission"),
USER_PASS_MISMATCH("html.error.auth.loginFailed", "User and Password did not match"),
DATABASE_NOT_OPEN("html.error.auth.dbClosed", "Database is not open, check db status with /plan info"),
ERROR("html.error.auth.generic", "Authentication failed due to error");

View File

@ -72,7 +72,7 @@ public class RegistrationBin {
}
public User toUser(UUID linkedToUUID) {
return new User(username, null, linkedToUUID, passwordHash, 100, Collections.emptyList());
return new User(username, null, linkedToUUID, passwordHash, null, Collections.emptyList());
}
}
}

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
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;
@ -35,7 +36,7 @@ public class ErrorsPageResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission("page.server")).orElse(false);
return request.getUser().map(user -> user.hasPermission(WebPermission.ACCESS_ERRORS)).orElse(false);
}
@Override

View File

@ -0,0 +1,48 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.webserver.resolver;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
import javax.inject.Inject;
import java.util.Optional;
public class ManagePageResolver implements Resolver {
private final ResponseFactory responseFactory;
@Inject
public ManagePageResolver(
ResponseFactory responseFactory
) {
this.responseFactory = responseFactory;
}
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission(WebPermission.MANAGE_GROUPS) || user.hasPermission(WebPermission.MANAGE_USERS)).orElse(false);
}
@Override
public Optional<Response> resolve(Request request) {
return Optional.of(responseFactory.reactPageResponse(request));
}
}

View File

@ -16,6 +16,7 @@
*/
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;
@ -63,7 +64,10 @@ public class PlayerPageResolver implements Resolver {
URIPath path = request.getPath();
WebUser user = request.getUser().orElse(new WebUser(""));
boolean isOwnPage = isOwnPage(path, user);
return user.hasPermission("page.player.other") || user.hasPermission("page.player.self") && isOwnPage;
boolean raw = path.getPart(2).map("raw"::equalsIgnoreCase).orElse(false);
boolean canSeeNormalPage = user.hasPermission(WebPermission.ACCESS_PLAYER)
|| user.hasPermission(WebPermission.ACCESS_PLAYER_SELF) && isOwnPage;
return canSeeNormalPage && !raw || user.hasPermission(WebPermission.ACCESS_RAW_PLAYER_DATA);
}
@NotNull

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
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;
@ -44,7 +45,7 @@ public class PlayersPageResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission("page.players")).orElse(false);
return request.getUser().map(user -> user.hasPermission(WebPermission.ACCESS_PLAYERS)).orElse(false);
}
@Override

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
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;
@ -37,7 +38,7 @@ public class QueryPageResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission("page.players")).orElse(false);
return request.getUser().map(user -> user.hasPermission(WebPermission.ACCESS_QUERY)).orElse(false);
}
@Override

View File

@ -16,6 +16,7 @@
*/
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.NoAuthResolver;
import com.djrapitops.plan.delivery.web.resolver.Response;
@ -42,6 +43,8 @@ import java.util.UUID;
@Singleton
public class RootPageResolver implements NoAuthResolver {
private static final String NETWORK_PAGE = "network";
private final ResponseFactory responseFactory;
private final Lazy<WebServer> webServer;
private final ServerInfo serverInfo;
@ -61,21 +64,31 @@ public class RootPageResolver implements NoAuthResolver {
private Response getResponse(Request request) {
Server server = serverInfo.getServer();
if (!webServer.get().isAuthRequired()) {
String redirectTo = server.isProxy() ? "network" : "server/" + Html.encodeToURL(server.getIdentifiableName());
String redirectTo = server.isProxy() ? NETWORK_PAGE : "server/" + Html.encodeToURL(server.getIdentifiableName());
return responseFactory.redirectResponse(redirectTo);
}
WebUser user = request.getUser()
.orElseThrow(() -> new WebUserAuthException(FailReason.EXPIRED_COOKIE));
if (user.hasPermission("page.server")) {
return responseFactory.redirectResponse(server.isProxy() ? "network" : "server/" + Html.encodeToURL(server.getIdentifiableName()));
} else if (user.hasPermission("page.players")) {
if (server.isProxy() && user.hasPermission(WebPermission.ACCESS_NETWORK)) {
return responseFactory.redirectResponse(NETWORK_PAGE);
} else if (user.hasPermission(WebPermission.ACCESS_SERVER)) {
return responseFactory.redirectResponse(server.isProxy() ? NETWORK_PAGE : "server/" + Html.encodeToURL(server.getIdentifiableName()));
} else if (user.hasPermission(WebPermission.ACCESS_PLAYERS)) {
return responseFactory.redirectResponse("players");
} else if (user.hasPermission("page.player.self")) {
} else if (user.hasPermission(WebPermission.ACCESS_PLAYER_SELF)) {
return responseFactory.redirectResponse("player/" + user.getUUID().map(UUID::toString).orElseGet(user::getName));
} else if (user.hasPermission(WebPermission.ACCESS_QUERY)) {
return responseFactory.redirectResponse("query");
} else if (user.hasPermission(WebPermission.MANAGE_GROUPS)) {
return responseFactory.redirectResponse("manage");
} else if (user.hasPermission(WebPermission.ACCESS_DOCS)) {
return responseFactory.redirectResponse("docs");
} else if (user.hasPermission(WebPermission.ACCESS_ERRORS)) {
return responseFactory.redirectResponse("errors");
} else {
return responseFactory.forbidden403(user.getName() + " has insufficient permissions to be redirected to any page. Needs one of: 'page.server', 'page.players' or 'page.player.self'");
return responseFactory.forbidden403("User has insufficient permissions to be redirected to any page.");
}
}
}

View File

@ -16,6 +16,7 @@
*/
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;
@ -42,6 +43,8 @@ import java.util.Optional;
@Singleton
public class ServerPageResolver implements Resolver {
private static final String NETWORK_PAGE = "network";
private final ResponseFactory responseFactory;
private final DBSystem dbSystem;
private final ServerInfo serverInfo;
@ -60,9 +63,9 @@ public class ServerPageResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
@Untrusted String firstPart = request.getPath().getPart(0).orElse("");
WebUser permissions = request.getUser().orElse(new WebUser(""));
boolean forServerPage = "server".equalsIgnoreCase(firstPart) && permissions.hasPermission("page.server");
boolean forNetworkPage = "network".equalsIgnoreCase(firstPart) && permissions.hasPermission("page.network");
WebUser user = request.getUser().orElse(new WebUser(""));
boolean forServerPage = "server".equalsIgnoreCase(firstPart) && user.hasPermission(WebPermission.ACCESS_SERVER);
boolean forNetworkPage = NETWORK_PAGE.equalsIgnoreCase(firstPart) && user.hasPermission(WebPermission.ACCESS_NETWORK);
return forServerPage || forNetworkPage;
}
@ -75,7 +78,7 @@ public class ServerPageResolver implements Resolver {
private Optional<Response> redirectToCurrentServer() {
String directTo = serverInfo.getServer().isProxy()
? "/network"
? "/" + NETWORK_PAGE
: "/server/" + Html.encodeToURL(serverInfo.getServer().getIdentifiableName());
return Optional.of(responseFactory.redirectResponse(directTo));
}
@ -83,7 +86,7 @@ public class ServerPageResolver implements Resolver {
private Optional<Response> getServerPage(ServerUUID serverUUID, @Untrusted Request request) {
boolean toNetworkPage = serverInfo.getServer().isProxy() && serverInfo.getServerUUID().equals(serverUUID);
if (toNetworkPage) {
if (request.getPath().getPart(0).map("network"::equals).orElse(false)) {
if (request.getPath().getPart(0).map(NETWORK_PAGE::equals).orElse(false)) {
return Optional.of(responseFactory.networkPageResponse(request));
} else {
// Accessing /server/Server <Bungee ID> which should be redirected to /network
@ -95,7 +98,7 @@ public class ServerPageResolver implements Resolver {
private Optional<ServerUUID> getServerUUID(@Untrusted URIPath path) {
if (serverInfo.getServer().isProxy()
&& path.getPart(0).map("network"::equals).orElse(false)
&& path.getPart(0).map(NETWORK_PAGE::equals).orElse(false)
) {
return Optional.of(serverInfo.getServerUUID());
}

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.Response;
@ -57,7 +58,7 @@ public class ErrorsJSONResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
return request.getUser().orElse(new WebUser("")).hasPermission(WebPermission.ACCESS_ERRORS);
}
@GET

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionDataDto;
import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
@ -71,8 +72,8 @@ public class ExtensionJSONResolver extends JSONResolver {
@Override
public boolean canAccess(Request request) {
WebUser permissions = request.getUser().orElse(new WebUser(""));
return permissions.hasPermission("page.server") || permissions.hasPermission("page.network");
WebUser user = request.getUser().orElse(new WebUser(""));
return user.hasPermission(WebPermission.PAGE_NETWORK_PLUGINS) || user.hasPermission(WebPermission.PAGE_SERVER_PLUGINS);
}
@GET

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.domain.datatransfer.FilterDto;
import com.djrapitops.plan.delivery.domain.datatransfer.ViewDto;
import com.djrapitops.plan.delivery.formatting.Formatters;
@ -87,7 +88,7 @@ public class FiltersJSONResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
WebUser user = request.getUser().orElse(new WebUser(""));
return user.hasPermission("page.players");
return user.hasPermission(WebPermission.ACCESS_QUERY);
}
@GET

View File

@ -16,6 +16,7 @@
*/
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.graphs.GraphJSONCreator;
import com.djrapitops.plan.delivery.web.resolver.Response;
@ -41,6 +42,7 @@ import jakarta.ws.rs.Path;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Optional;
/**
@ -72,7 +74,21 @@ public class GraphsJSONResolver extends JSONResolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
@Untrusted String type = request.getQuery().get("type")
.orElseThrow(() -> new BadRequestException("'type' parameter was not defined."));
DataID dataID = getDataID(type);
boolean forServer = request.getQuery().get("server").isPresent();
List<WebPermission> requiredPermissionOptions = forServer
? getRequiredPermission(dataID)
: getRequiredNetworkPermission(dataID);
if (requiredPermissionOptions.isEmpty()) return true;
WebUser user = request.getUser().orElse(new WebUser(""));
for (WebPermission permissionOption : requiredPermissionOptions) {
if (user.hasPermission(permissionOption)) return true;
}
return false;
}
/**
@ -190,6 +206,65 @@ public class GraphsJSONResolver extends JSONResolver {
}
}
private List<WebPermission> getRequiredPermission(DataID dataID) {
switch (dataID) {
case GRAPH_PERFORMANCE:
return List.of(WebPermission.PAGE_SERVER_PERFORMANCE_GRAPHS);
case GRAPH_PING:
case GRAPH_OPTIMIZED_PERFORMANCE:
return List.of(WebPermission.PAGE_SERVER_PERFORMANCE_GRAPHS, WebPermission.PAGE_NETWORK_PERFORMANCE);
case GRAPH_ONLINE:
return List.of(WebPermission.PAGE_SERVER_OVERVIEW_PLAYERS_ONLINE_GRAPH);
case GRAPH_UNIQUE_NEW:
return List.of(WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_DAY_BY_DAY);
case GRAPH_HOURLY_UNIQUE_NEW:
return List.of(WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_HOUR_BY_HOUR);
case GRAPH_CALENDAR:
return List.of(WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_CALENDAR);
case GRAPH_PUNCHCARD:
return List.of(WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_PUNCHCARD);
case GRAPH_WORLD_PIE:
return List.of(WebPermission.PAGE_SERVER_SESSIONS_WORLD_PIE);
case GRAPH_ACTIVITY:
return List.of(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS);
case GRAPH_WORLD_MAP:
return List.of(WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP);
case GRAPH_HOSTNAME_PIE:
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE);
case JOIN_ADDRESSES_BY_DAY:
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME);
default:
return List.of();
}
}
private List<WebPermission> getRequiredNetworkPermission(DataID dataID) {
switch (dataID) {
case GRAPH_PERFORMANCE:
case GRAPH_OPTIMIZED_PERFORMANCE:
case GRAPH_PING:
return List.of(WebPermission.PAGE_NETWORK_PERFORMANCE);
case GRAPH_ACTIVITY:
return List.of(WebPermission.PAGE_NETWORK_PLAYERBASE_GRAPHS);
case GRAPH_UNIQUE_NEW:
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_DAY_BY_DAY);
case GRAPH_HOURLY_UNIQUE_NEW:
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR);
case GRAPH_SERVER_PIE:
return List.of(WebPermission.PAGE_NETWORK_SESSIONS_SERVER_PIE);
case GRAPH_WORLD_MAP:
return List.of(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP);
case GRAPH_ONLINE_PROXIES:
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE);
case GRAPH_HOSTNAME_PIE:
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE);
case JOIN_ADDRESSES_BY_DAY:
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME);
default:
return List.of();
}
}
private Object generateGraphDataJSONOfType(DataID id, ServerUUID serverUUID, @Untrusted URIQuery query) {
switch (id) {
case GRAPH_PERFORMANCE:

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
import com.djrapitops.plan.delivery.rendering.json.network.NetworkOverviewJSONCreator;
import com.djrapitops.plan.delivery.rendering.json.network.NetworkPlayerBaseOverviewJSONCreator;
@ -49,19 +50,19 @@ public class NetworkJSONResolver {
) {
this.asyncJSONResolverService = asyncJSONResolverService;
resolver = CompositeResolver.builder()
.add("overview", forJSON(DataID.SERVER_OVERVIEW, networkOverviewJSONCreator))
.add("playerbaseOverview", forJSON(DataID.PLAYERBASE_OVERVIEW, networkPlayerBaseOverviewJSONCreator))
.add("sessionsOverview", forJSON(DataID.SESSIONS_OVERVIEW, networkSessionsOverviewJSONCreator))
.add("servers", forJSON(DataID.SERVERS, jsonFactory::serversAsJSONMaps))
.add("pingTable", forJSON(DataID.PING_TABLE, jsonFactory::pingPerGeolocation))
.add("listServers", forJSON(DataID.LIST_SERVERS, jsonFactory::listServers))
.add("serverOptions", forJSON(DataID.LIST_SERVERS, jsonFactory::listServers))
.add("overview", forJSON(DataID.SERVER_OVERVIEW, networkOverviewJSONCreator, WebPermission.PAGE_NETWORK_OVERVIEW_NUMBERS))
.add("playerbaseOverview", forJSON(DataID.PLAYERBASE_OVERVIEW, networkPlayerBaseOverviewJSONCreator, WebPermission.PAGE_NETWORK_PLAYERBASE_OVERVIEW))
.add("sessionsOverview", forJSON(DataID.SESSIONS_OVERVIEW, networkSessionsOverviewJSONCreator, WebPermission.PAGE_NETWORK_SESSIONS_OVERVIEW))
.add("servers", forJSON(DataID.SERVERS, jsonFactory::serversAsJSONMaps, WebPermission.PAGE_NETWORK_SERVER_LIST))
.add("pingTable", forJSON(DataID.PING_TABLE, jsonFactory::pingPerGeolocation, WebPermission.PAGE_NETWORK_GEOLOCATIONS_PING_PER_COUNTRY))
.add("listServers", forJSON(DataID.LIST_SERVERS, jsonFactory::listServers, WebPermission.PAGE_NETWORK_PERFORMANCE))
.add("serverOptions", forJSON(DataID.LIST_SERVERS, jsonFactory::listServers, WebPermission.PAGE_NETWORK_PERFORMANCE))
.add("performanceOverview", networkPerformanceJSONResolver)
.build();
}
private <T> NetworkTabJSONResolver<T> forJSON(DataID dataID, NetworkTabJSONCreator<T> tabJSONCreator) {
return new NetworkTabJSONResolver<>(dataID, tabJSONCreator, asyncJSONResolverService);
private <T> NetworkTabJSONResolver<T> forJSON(DataID dataID, NetworkTabJSONCreator<T> tabJSONCreator, WebPermission permission) {
return new NetworkTabJSONResolver<>(dataID, permission, tabJSONCreator, asyncJSONResolverService);
}
public CompositeResolver getResolver() {

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.domain.mutators.TPSMutator;
import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.formatting.Formatters;
@ -92,7 +93,7 @@ public class NetworkPerformanceJSONResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().orElse(new WebUser("")).hasPermission("page.network");
return request.getUser().orElse(new WebUser("")).hasPermission(WebPermission.PAGE_NETWORK_PERFORMANCE);
}
@GET

View File

@ -16,6 +16,7 @@
*/
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.network.NetworkTabJSONCreator;
import com.djrapitops.plan.delivery.web.resolver.Response;
@ -37,14 +38,16 @@ import java.util.function.Supplier;
public class NetworkTabJSONResolver<T> extends JSONResolver {
private final DataID dataID;
private final WebPermission permission;
private final Supplier<T> jsonCreator;
private final AsyncJSONResolverService asyncJSONResolverService;
public NetworkTabJSONResolver(
DataID dataID, NetworkTabJSONCreator<T> jsonCreator,
DataID dataID, WebPermission permission, NetworkTabJSONCreator<T> jsonCreator,
AsyncJSONResolverService asyncJSONResolverService
) {
this.dataID = dataID;
this.permission = permission;
this.jsonCreator = jsonCreator;
this.asyncJSONResolverService = asyncJSONResolverService;
}
@ -54,7 +57,7 @@ public class NetworkTabJSONResolver<T> extends JSONResolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().orElse(new WebUser("")).hasPermission("page.network");
return request.getUser().orElse(new WebUser("")).hasPermission(permission);
}
@Override

View File

@ -16,6 +16,7 @@
*/
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.formatting.Formatters;
import com.djrapitops.plan.delivery.rendering.json.PlayerJSONCreator;
@ -43,6 +44,7 @@ import javax.inject.Singleton;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
@Singleton
@Path("/v1/player")
@ -63,8 +65,8 @@ public class PlayerJSONResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
WebUser user = request.getUser().orElse(new WebUser(""));
if (user.hasPermission("page.player.other")) return true;
if (user.hasPermission("page.player.self")) {
if (user.hasPermission(WebPermission.ACCESS_PLAYER)) return true;
if (user.hasPermission(WebPermission.ACCESS_PLAYER_SELF)) {
try {
UUID webUserUUID = identifiers.getPlayerUUID(user.getName());
UUID playerUUID = identifiers.getPlayerUUID(request);
@ -97,10 +99,12 @@ public class PlayerJSONResolver implements Resolver {
private Response getResponse(Request request) {
UUID playerUUID = identifiers.getPlayerUUID(request); // Can throw BadRequestException
Optional<Long> etag = Identifiers.getEtag(request);
// User needs to be taken into account due to permissions.
String userSpecific = request.getUser().map(WebUser::getUsername).orElse("");
Optional<String> etag = Identifiers.getStringEtag(request);
if (etag.isPresent()) {
long lastSeen = jsonCreator.getLastSeen(playerUUID);
if (etag.get() == lastSeen) {
if (etag.get().equals(lastSeen + userSpecific)) {
return Response.builder()
.setStatus(304)
.setContent(new byte[0])
@ -108,7 +112,10 @@ public class PlayerJSONResolver implements Resolver {
}
}
Map<String, Object> jsonAsMap = jsonCreator.createJSONAsMap(playerUUID);
Predicate<WebPermission> hasPermission = request.getUser()
.map(user -> (Predicate<WebPermission>) user::hasPermission)
.orElse(permission -> true); // No user means auth disabled inside resolve
Map<String, Object> jsonAsMap = jsonCreator.createJSONAsMap(playerUUID, hasPermission);
long lastSeenRawValue = Optional.ofNullable(jsonAsMap.get("info"))
.map(Map.class::cast)
.map(info -> info.get("last_seen_raw_value"))
@ -119,7 +126,7 @@ public class PlayerJSONResolver implements Resolver {
.setJSONContent(jsonAsMap)
.setHeader(HttpHeader.CACHE_CONTROL.asString(), CacheStrategy.CHECK_ETAG_USER_SPECIFIC)
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastSeenRawValue))
.setHeader(HttpHeader.ETAG.asString(), lastSeenRawValue)
.setHeader(HttpHeader.ETAG.asString(), lastSeenRawValue + userSpecific)
.build();
}
}

View File

@ -16,6 +16,7 @@
*/
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;
@ -67,7 +68,11 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
@Override
public boolean canAccess(@Untrusted Request request) {
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
WebUser user = request.getUser().orElse(new WebUser(""));
if (request.getQuery().get("server").isPresent()) {
return user.hasPermission(WebPermission.PAGE_SERVER_RETENTION);
}
return user.hasPermission(WebPermission.PAGE_NETWORK_RETENTION);
}
@GET

View File

@ -16,6 +16,7 @@
*/
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;
@ -71,7 +72,7 @@ public class PlayerKillsJSONResolver extends JSONResolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
return request.getUser().orElse(new WebUser("")).hasPermission(WebPermission.PAGE_SERVER_PLAYER_VERSUS_KILL_LIST);
}
@GET

View File

@ -16,6 +16,7 @@
*/
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;
@ -73,10 +74,11 @@ public class PlayersTableJSONResolver extends JSONResolver {
public boolean canAccess(Request request) {
WebUser user = request.getUser().orElse(new WebUser(""));
if (request.getQuery().get("server").isPresent()) {
return user.hasPermission("page.server");
return user.hasPermission(WebPermission.PAGE_SERVER_PLAYERS);
}
// Assume players page
return user.hasPermission("page.players");
return user.hasPermission(WebPermission.ACCESS_PLAYERS)
|| user.hasPermission(WebPermission.ACCESS_NETWORK) && user.hasPermission(WebPermission.PAGE_NETWORK_PLAYERS);
}
@GET

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.delivery.webserver.resolver.json;
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.ViewDto;
@ -109,7 +110,7 @@ public class QueryJSONResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
WebUser user = request.getUser().orElse(new WebUser(""));
return user.hasPermission("page.players");
return user.hasPermission(WebPermission.ACCESS_QUERY);
}
@GET

View File

@ -16,6 +16,7 @@
*/
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;
@ -67,7 +68,11 @@ public class RetentionJSONResolver extends JSONResolver {
@Override
public boolean canAccess(@Untrusted Request request) {
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
WebUser user = request.getUser().orElse(new WebUser(""));
if (request.getQuery().get("server").isPresent()) {
return user.hasPermission(WebPermission.PAGE_SERVER_RETENTION);
}
return user.hasPermission(WebPermission.PAGE_NETWORK_RETENTION);
}
@GET

View File

@ -16,11 +16,14 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.rendering.json.*;
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.identification.Identifiers;
import dagger.Lazy;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -35,13 +38,22 @@ public class RootJSONResolver {
private final Identifiers identifiers;
private final AsyncJSONResolverService asyncJSONResolverService;
private final CompositeResolver resolver;
private final Lazy<WebServer> webServer;
private final WebGroupJSONResolver webGroupJSONResolver;
private final WebGroupPermissionJSONResolver webGroupPermissionJSONResolver;
private final WebPermissionJSONResolver webPermissionJSONResolver;
private final WebGroupSaveJSONResolver webGroupSaveJSONResolver;
private final WebGroupDeleteJSONResolver webGroupDeleteJSONResolver;
private final CompositeResolver.Builder readOnlyResourcesBuilder;
private CompositeResolver resolver;
@Inject
public RootJSONResolver(
Identifiers identifiers,
AsyncJSONResolverService asyncJSONResolverService,
JSONFactory jsonFactory,
Lazy<WebServer> webServer,
GraphsJSONResolver graphsJSONResolver,
SessionsJSONResolver sessionsJSONResolver,
@ -67,23 +79,29 @@ public class RootJSONResolver {
ServerIdentityJSONResolver serverIdentityJSONResolver,
ExtensionJSONResolver extensionJSONResolver,
RetentionJSONResolver retentionJSONResolver,
PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver
PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver,
WebGroupJSONResolver webGroupJSONResolver,
WebGroupPermissionJSONResolver webGroupPermissionJSONResolver,
WebPermissionJSONResolver webPermissionJSONResolver,
WebGroupSaveJSONResolver webGroupSaveJSONResolver,
WebGroupDeleteJSONResolver webGroupDeleteJSONResolver
) {
this.identifiers = identifiers;
this.asyncJSONResolverService = asyncJSONResolverService;
resolver = CompositeResolver.builder()
readOnlyResourcesBuilder = CompositeResolver.builder()
.add("players", playersTableJSONResolver)
.add("sessions", sessionsJSONResolver)
.add("kills", playerKillsJSONResolver)
.add("graph", graphsJSONResolver)
.add("pingTable", forJSON(DataID.PING_TABLE, jsonFactory::pingPerGeolocation))
.add("serverOverview", forJSON(DataID.SERVER_OVERVIEW, serverOverviewJSONCreator))
.add("onlineOverview", forJSON(DataID.ONLINE_OVERVIEW, onlineActivityOverviewJSONCreator))
.add("sessionsOverview", forJSON(DataID.SESSIONS_OVERVIEW, sessionsOverviewJSONCreator))
.add("playerVersus", forJSON(DataID.PVP_PVE, pvPPvEJSONCreator))
.add("playerbaseOverview", forJSON(DataID.PLAYERBASE_OVERVIEW, playerBaseOverviewJSONCreator))
.add("performanceOverview", forJSON(DataID.PERFORMANCE_OVERVIEW, performanceJSONCreator))
.add("pingTable", forJSON(DataID.PING_TABLE, jsonFactory::pingPerGeolocation, WebPermission.PAGE_SERVER_GEOLOCATIONS_PING_PER_COUNTRY))
.add("serverOverview", forJSON(DataID.SERVER_OVERVIEW, serverOverviewJSONCreator, WebPermission.PAGE_SERVER_OVERVIEW_NUMBERS))
.add("onlineOverview", forJSON(DataID.ONLINE_OVERVIEW, onlineActivityOverviewJSONCreator, WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_OVERVIEW))
.add("sessionsOverview", forJSON(DataID.SESSIONS_OVERVIEW, sessionsOverviewJSONCreator, WebPermission.PAGE_SERVER_SESSIONS_OVERVIEW))
.add("playerVersus", forJSON(DataID.PVP_PVE, pvPPvEJSONCreator, WebPermission.PAGE_SERVER_PLAYER_VERSUS_OVERVIEW))
.add("playerbaseOverview", forJSON(DataID.PLAYERBASE_OVERVIEW, playerBaseOverviewJSONCreator, WebPermission.PAGE_SERVER_PLAYERBASE_OVERVIEW))
.add("performanceOverview", forJSON(DataID.PERFORMANCE_OVERVIEW, performanceJSONCreator, WebPermission.PAGE_SERVER_PERFORMANCE_OVERVIEW))
.add("player", playerJSONResolver)
.add("network", networkJSONResolver.getResolver())
.add("filters", filtersJSONResolver)
@ -97,15 +115,36 @@ public class RootJSONResolver {
.add("whoami", whoAmIJSONResolver)
.add("extensionData", extensionJSONResolver)
.add("retention", retentionJSONResolver)
.add("joinAddresses", playerJoinAddressJSONResolver)
.build();
.add("joinAddresses", playerJoinAddressJSONResolver);
this.webServer = webServer;
// These endpoints require authentication to be enabled.
this.webGroupJSONResolver = webGroupJSONResolver;
this.webGroupPermissionJSONResolver = webGroupPermissionJSONResolver;
this.webPermissionJSONResolver = webPermissionJSONResolver;
this.webGroupSaveJSONResolver = webGroupSaveJSONResolver;
this.webGroupDeleteJSONResolver = webGroupDeleteJSONResolver;
}
private <T> ServerTabJSONResolver<T> forJSON(DataID dataID, ServerTabJSONCreator<T> tabJSONCreator) {
return new ServerTabJSONResolver<>(dataID, identifiers, tabJSONCreator, asyncJSONResolverService);
private <T> ServerTabJSONResolver<T> forJSON(DataID dataID, ServerTabJSONCreator<T> tabJSONCreator, WebPermission permission) {
return new ServerTabJSONResolver<>(dataID, permission, identifiers, tabJSONCreator, asyncJSONResolverService);
}
public CompositeResolver getResolver() {
if (resolver == null) {
if (webServer.get().isAuthRequired()) {
resolver = readOnlyResourcesBuilder
.add("webGroups", webGroupJSONResolver)
.add("groupPermissions", webGroupPermissionJSONResolver)
.add("permissions", webPermissionJSONResolver)
.add("saveGroupPermissions", webGroupSaveJSONResolver)
.add("deleteGroup", webGroupDeleteJSONResolver)
.build();
} else {
resolver = readOnlyResourcesBuilder.build();
}
}
return resolver;
}
}

View File

@ -16,14 +16,16 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver;
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.exception.NotFoundException;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
import com.djrapitops.plan.utilities.dev.Untrusted;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -43,7 +45,7 @@ import java.util.Optional;
* @author AuroraLS3
*/
@Singleton
public class ServerIdentityJSONResolver implements NoAuthResolver {
public class ServerIdentityJSONResolver implements Resolver {
private final JSONFactory jsonFactory;
@ -52,6 +54,12 @@ public class ServerIdentityJSONResolver implements NoAuthResolver {
this.jsonFactory = jsonFactory;
}
@Override
public boolean canAccess(Request request) {
WebUser user = request.getUser().orElse(new WebUser(""));
return user.hasPermission(WebPermission.ACCESS_SERVER);
}
@GET
@Operation(
description = "Get server identity for an identifier",

View File

@ -16,6 +16,7 @@
*/
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.ServerTabJSONCreator;
import com.djrapitops.plan.delivery.web.resolver.Response;
@ -39,15 +40,17 @@ import java.util.function.Function;
public class ServerTabJSONResolver<T> extends JSONResolver {
private final DataID dataID;
private final WebPermission permission;
private final Identifiers identifiers;
private final Function<ServerUUID, T> jsonCreator;
private final AsyncJSONResolverService asyncJSONResolverService;
public ServerTabJSONResolver(
DataID dataID, Identifiers identifiers, ServerTabJSONCreator<T> jsonCreator,
DataID dataID, WebPermission permission, Identifiers identifiers, ServerTabJSONCreator<T> jsonCreator,
AsyncJSONResolverService asyncJSONResolverService
) {
this.dataID = dataID;
this.permission = permission;
this.identifiers = identifiers;
this.jsonCreator = jsonCreator;
this.asyncJSONResolverService = asyncJSONResolverService;
@ -58,7 +61,7 @@ public class ServerTabJSONResolver<T> extends JSONResolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
return request.getUser().orElse(new WebUser("")).hasPermission(permission);
}
@Override

View File

@ -16,6 +16,7 @@
*/
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;
@ -72,7 +73,11 @@ public class SessionsJSONResolver extends JSONResolver {
@Override
public boolean canAccess(Request request) {
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
WebUser user = request.getUser().orElse(new WebUser(""));
if (request.getQuery().get("server").isPresent()) {
return user.hasPermission(WebPermission.PAGE_SERVER_SESSIONS_LIST);
}
return user.hasPermission(WebPermission.PAGE_NETWORK_SESSIONS_LIST);
}
@GET

View File

@ -17,7 +17,7 @@
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
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.version.VersionChecker;
@ -42,7 +42,7 @@ import java.util.Optional;
* @author Kopo942
*/
@Path("/v1/version")
public class VersionJSONResolver implements Resolver {
public class VersionJSONResolver implements NoAuthResolver {
private final VersionChecker versionChecker;
private final String currentVersion;
@ -56,11 +56,6 @@ public class VersionJSONResolver implements Resolver {
this.versionChecker = versionChecker;
}
@Override
public boolean canAccess(Request request) {
return true;
}
@GET
@Operation(
description = "Get Plan version and update information",

View File

@ -0,0 +1,108 @@
/*
* 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.web.resolver.MimeType;
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.webserver.auth.ActiveCookieStore;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.DeleteWebGroupTransaction;
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.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.DELETE;
import jakarta.ws.rs.Path;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
/**
* Endpoint for adding a group.
*
* @author AuroraLS3
*/
@Singleton
@Path("/v1/deleteGroup")
public class WebGroupDeleteJSONResolver implements Resolver {
private final DBSystem dbSystem;
private final ActiveCookieStore activeCookieStore;
@Inject
public WebGroupDeleteJSONResolver(DBSystem dbSystem, ActiveCookieStore activeCookieStore) {
this.dbSystem = dbSystem;
this.activeCookieStore = activeCookieStore;
}
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission(WebPermission.MANAGE_GROUPS)).orElse(false);
}
@DELETE
@Operation(
description = "Delete a group",
parameters = {
@Parameter(name = "group", description = "Name of the group to delete", required = true),
@Parameter(name = "moveTo", description = "Name of the group to move users of deleted group to", required = true)
},
responses = {
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON,
examples = @ExampleObject("{\"success\": true}"))),
},
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
)
@Override
public Optional<Response> resolve(Request request) {
if (!request.getMethod().equals("DELETE")) {
throw new BadRequestException("Endpoint needs to be sent a DELETE request.");
}
@Untrusted String groupName = request.getQuery().get("group")
.orElseThrow(() -> new BadRequestException("'group' parameter not given."));
@Untrusted String moveTo = request.getQuery().get("moveTo")
.orElseThrow(() -> new BadRequestException("'moveTo' parameter not given."));
return Optional.of(getResponse(groupName, moveTo));
}
private Response getResponse(@Untrusted String groupName, @Untrusted String moveTo) {
try {
dbSystem.getDatabase().executeTransaction(new DeleteWebGroupTransaction(groupName, moveTo))
.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
throw new IllegalStateException(e);
}
activeCookieStore.reloadActiveCookies();
return Response.builder()
.setStatus(200)
.setJSONContent("{\"success\": true}")
.build();
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.Group;
import com.djrapitops.plan.delivery.domain.auth.GroupList;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
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.media.Schema;
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.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Endpoint for getting list of Plan web permission groups.
*
* @author AuroraLS3
*/
@Singleton
@Path("/v1/webGroups")
public class WebGroupJSONResolver implements Resolver {
private final DBSystem dbSystem;
@Inject
public WebGroupJSONResolver(DBSystem dbSystem) {
this.dbSystem = dbSystem;
}
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission(WebPermission.MANAGE_GROUPS)).orElse(false);
}
@GET
@Operation(
description = "Get list of web permission groups",
responses = {
@ApiResponse(responseCode = "200", content = @Content(
mediaType = MimeType.JSON,
schema = @Schema(implementation = GroupList.class))),
},
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
)
@Override
public Optional<Response> resolve(Request request) {
return Optional.of(getResponse());
}
private Response getResponse() {
List<String> groupNames = dbSystem.getDatabase().query(WebUserQueries.fetchGroupNames());
GroupList groupList = new GroupList(groupNames.stream()
.map(Group::new)
.collect(Collectors.toList()));
return Response.builder()
.setMimeType(MimeType.JSON)
.setJSONContent(groupList)
.build();
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.domain.auth.WebPermissionList;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
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.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
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.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;
import jakarta.ws.rs.Path;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Optional;
/**
* Endpoint for getting list of Plan web permissions of specific group.
*
* @author AuroraLS3
*/
@Singleton
@Path("/v1/groupPermissions")
public class WebGroupPermissionJSONResolver implements Resolver {
private final DBSystem dbSystem;
@Inject
public WebGroupPermissionJSONResolver(DBSystem dbSystem) {
this.dbSystem = dbSystem;
}
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission(WebPermission.MANAGE_GROUPS)).orElse(false);
}
@GET
@Operation(
description = "Get list of web permissions that have been granted to specific group",
parameters = {
@Parameter(name = "group", description = "Name of the group", required = true),
},
responses = {
@ApiResponse(responseCode = "200", content = @Content(
mediaType = MimeType.JSON,
schema = @Schema(implementation = WebPermissionList.class))),
},
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
)
@Override
public Optional<Response> resolve(Request request) {
return Optional.of(getResponse(request));
}
private Response getResponse(@Untrusted Request request) {
@Untrusted String group = request.getQuery().get("group")
.orElseThrow(() -> new BadRequestException("Parameter 'group' not given."));
List<String> permissions = dbSystem.getDatabase().query(WebUserQueries.fetchGroupPermissions(group));
WebPermissionList permissionList = new WebPermissionList(permissions);
return Response.builder()
.setMimeType(MimeType.JSON)
.setJSONContent(permissionList)
.build();
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.GroupList;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
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.webserver.auth.ActiveCookieStore;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.StoreWebGroupTransaction;
import com.google.gson.Gson;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.POST;
import jakarta.ws.rs.Path;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
/**
* Endpoint for storing new permissions for a group.
*
* @author AuroraLS3
*/
@Singleton
@Path("/v1/saveGroupPermissions")
public class WebGroupSaveJSONResolver implements Resolver {
private final DBSystem dbSystem;
private final ActiveCookieStore activeCookieStore;
@Inject
public WebGroupSaveJSONResolver(DBSystem dbSystem, ActiveCookieStore activeCookieStore) {
this.dbSystem = dbSystem;
this.activeCookieStore = activeCookieStore;
}
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission(WebPermission.MANAGE_GROUPS)).orElse(false);
}
@POST
@Operation(
description = "Update list of permissions for a group",
parameters = {
@Parameter(name = "group", description = "Name of the group", required = true),
},
responses = {
@ApiResponse(responseCode = "200", content = @Content(
mediaType = MimeType.JSON,
schema = @Schema(implementation = GroupList.class))),
},
requestBody = @RequestBody(content = @Content(examples = @ExampleObject("[\"page\"]")))
)
@Override
public Optional<Response> resolve(Request request) {
if (!request.getMethod().equals("POST")) {
throw new BadRequestException("Endpoint needs to be sent a POST request.");
}
String groupName = request.getQuery().get("group")
.orElseThrow(() -> new BadRequestException("'group' parameter not given."));
String requestBody = new String(request.getRequestBody(), StandardCharsets.UTF_8);
List<String> permissions = Arrays.asList(new Gson().fromJson(requestBody, String[].class));
return Optional.of(getResponse(groupName, permissions));
}
private Response getResponse(String groupName, List<String> permissions) {
try {
dbSystem.getDatabase().executeTransaction(new StoreWebGroupTransaction(groupName, permissions))
.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
throw new IllegalStateException(e);
}
activeCookieStore.reloadActiveCookies();
return Response.builder()
.setStatus(200)
.setJSONContent("{\"success\": true}")
.build();
}
}

View File

@ -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;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.domain.auth.WebPermissionList;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
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.media.Schema;
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.List;
import java.util.Optional;
/**
* Endpoint for getting list of available Plan web permissions.
*
* @author AuroraLS3
*/
@Singleton
@Path("/v1/permissions")
public class WebPermissionJSONResolver implements Resolver {
private final DBSystem dbSystem;
@Inject
public WebPermissionJSONResolver(DBSystem dbSystem) {
this.dbSystem = dbSystem;
}
@Override
public boolean canAccess(Request request) {
return request.getUser().map(user -> user.hasPermission(WebPermission.MANAGE_GROUPS)).orElse(false);
}
@GET
@Operation(
description = "Get list of web permissions that can be granted",
responses = {
@ApiResponse(responseCode = "200", content = @Content(
mediaType = MimeType.JSON,
schema = @Schema(implementation = WebPermissionList.class))),
},
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
)
@Override
public Optional<Response> resolve(Request request) {
return Optional.of(getResponse());
}
private Response getResponse() {
List<String> permissions = dbSystem.getDatabase().query(WebUserQueries.fetchAvailablePermissions());
WebPermissionList permissionList = new WebPermissionList(permissions);
return Response.builder()
.setMimeType(MimeType.JSON)
.setJSONContent(permissionList)
.build();
}
}

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver.swagger;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
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;
@ -38,7 +39,7 @@ public class SwaggerJsonResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
return request.getUser()
.filter(user -> user.hasPermission("page.server"))
.filter(user -> user.hasPermission(WebPermission.ACCESS_DOCS))
.isPresent();
}

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.delivery.webserver.resolver.swagger;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
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;
@ -38,7 +39,7 @@ public class SwaggerPageResolver implements Resolver {
@Override
public boolean canAccess(Request request) {
return request.getUser()
.filter(user -> user.hasPermission("page.server"))
.filter(user -> user.hasPermission(WebPermission.ACCESS_DOCS))
.isPresent();
}

View File

@ -93,6 +93,10 @@ public class Identifiers {
});
}
public static Optional<String> getStringEtag(Request request) {
return request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
}
/**
* Obtain UUID of the server.
*

View File

@ -34,6 +34,7 @@ public enum Permissions {
REGISTER_OTHER("plan.register.other"),
UNREGISTER_SELF("plan.unregister.self"),
UNREGISTER_OTHER("plan.unregister.other"),
SET_GROUP("plan.setgroup.other"),
LOGOUT_OTHER("plan.logout.other"),
INFO("plan.info"),
RELOAD("plan.reload"),

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.settings.locale;
import com.djrapitops.plan.SubSystem;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.web.AssetVersions;
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
import com.djrapitops.plan.settings.config.PlanConfig;
@ -104,6 +105,7 @@ public class LocaleSystem implements SubSystem {
HtmlLang.values(),
JSLang.values(),
PluginLang.values(),
WebPermission.values(),
};
}

View File

@ -46,7 +46,9 @@ public enum DeepHelpLang implements Lang {
DB_UNINSTALLED("command.help.dbUninstalled.inDepth", "In Depth Help - /plan db uninstalled", "Marks a server in Plan database as uninstalled so that it will not show up in server queries."),
EXPORT("command.help.export.inDepth", "In Depth Help - /plan export", "Performs an export to export location defined in the config."),
IMPORT("command.help.import.inDepth", "In Depth Help - /plan import", "Performs an import to load data into the database."),
JSON("command.help.json.inDepth", "In Depth Help - /plan json", "Allows you to download a player's data in json format. All of it.");
JSON("command.help.json.inDepth", "In Depth Help - /plan json", "Allows you to download a player's data in json format. All of it."),
SET_GROUP("command.help.setgroup.inDepth", "In Depth Help - /plan setgroup", "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."),
GROUPS("command.help.groups.inDepth", "In Depth Help - /plan groups", "List available web permission groups that are managed on the web interface.");
private final String key;
private final String identifier;

View File

@ -26,6 +26,7 @@ public enum HelpLang implements Lang {
ARG_NAME_UUID("command.argument.nameOrUUID.name", "CMD Arg Name - name or uuid", "name/uuid"),
ARG_CODE("command.argument.code.name", "CMD Arg Name - code", "${code}"),
ARG_USERNAME("command.argument.username.name", "CMD Arg Name - username", "username"),
ARG_GROUP("command.argument.group.name", "CMD Arg Name - group", "group"),
ARG_FEATURE("command.argument.feature.name", "CMD Arg Name - feature", "feature"),
ARG_SUBCOMMAND("command.argument.subcommand.name", "CMD Arg Name - subcommand", "subcommand"),
ARG_BACKUP_FILE("command.argument.backupFile.name", "CMD Arg Name - backup-file", "backup-file"),
@ -36,6 +37,7 @@ public enum HelpLang implements Lang {
DESC_ARG_PLAYER_IDENTIFIER_REMOVE("command.argument.nameOrUUID.removeDescription", "CMD Arg - player identifier remove", "Identifier for a player that will be removed from current database."),
DESC_ARG_CODE("command.argument.code.description", "CMD Arg - code", "Code used to finalize registration."),
DESC_ARG_USERNAME("command.argument.username.description", "CMD Arg - username", "Username of another user. If not specified player linked user is used."),
DESC_ARG_GROUP("command.argument.group.description", "CMD Arg - group", "Web Permission Group, case sensitive."),
DESC_ARG_FEATURE("command.argument.feature.description", "CMD Arg - feature", "Name of the feature to disable: ${0}"),
DESC_ARG_SUBCOMMAND("command.argument.subcommand.description", "CMD Arg - subcommand", "Use the command without subcommand to see help."),
DESC_ARG_BACKUP_FILE("command.argument.backupFile.description", "CMD Arg - backup-file", "Name of the backup file (case sensitive)"),
@ -70,6 +72,8 @@ public enum HelpLang implements Lang {
EXPORT("command.help.export.description", "Command Help - /plan export", "Export html or json files manually"),
IMPORT("command.help.import.description", "Command Help - /plan import", "Import data"),
JSON("command.help.json.description", "Command Help - /plan json", "View json of Player's raw data."),
SET_GROUP("command.help.setgroup.description", "Command Help - /plan setgroup", "Change users web permission group."),
GROUPS("command.help.groups.description", "Command Help - /plan groups", "List web permission groups."),
LOGOUT("command.help.logout.description", "Command Help - /plan logout", "Log out other users from the panel."),
JOIN_ADDRESS_REMOVAL("command.help.removejoinaddresses.description", "Command Help - /plan db removejoinaddresses", "Remove join addresses of a specified server"),
ONLINE_UUID_MIGRATION("command.help.migrateToOnlineUuids.description", "Command Help - /plan db migratetoonlineuuids", "Migrate offline uuid data to online uuids");

View File

@ -36,6 +36,12 @@ public enum HtmlLang implements Lang {
SIDE_LINKS("html.label.links", "LINKS"),
SIDE_PERFORMANCE("html.label.performance", "Performance"),
SIDE_PLUGINS_OVERVIEW("html.label.pluginsOverview", "Plugins Overview"),
SIDE_MANAGE("html.label.manage", "Manage"),
SIDE_MANAGE_GROUPS("html.label.groupPermissions", "Manage Groups"),
SIDE_MANAGE_GROUP_USERS("html.label.groupUsers", "Manage Group Users"),
SIDE_MANAGE_USER_GROUPS("html.label.users", "Manage Users"),
SIDE_ERRORS("html.label.errors", "Plan Error Logs"),
SIDE_DOCS("html.label.docs", "Swagger Docs"),
QUERY_MAKE("html.label.query", "Make a query"),
UNIT_NO_DATA("generic.noData", "No Data"), // Generic
GRAPH_NO_DATA("html.label.noDataToDisplay", "No Data to Display"),
@ -319,6 +325,22 @@ public enum HtmlLang implements Lang {
HELP_ACTIVITY_INDEX_EXAMPLE_3("html.label.help.activityIndexExample3", "The index approaches 5 indefinitely."),
HELP_ACTIVITY_INDEX_VISUALIZATION("html.label.help.activityIndexVisual", "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."),
HELP_GROUPS_1("html.label.help.manage.groups.line-1", "This view allows you to modify web group permissions."),
HELP_GROUPS_2("html.label.help.manage.groups.line-2", "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."),
HELP_GROUPS_3("html.label.help.manage.groups.line-3", "You can use {{command}} to change permission group after registering."),
HELP_GROUPS_4("html.label.help.manage.groups.line-4", "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."),
HELP_GROUPS_5("html.label.help.manage.groups.line-5", "Permission inheritance"),
HELP_GROUPS_6("html.label.help.manage.groups.line-6", "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."),
HELP_GROUPS_7("html.label.help.manage.groups.line-7", "Access vs Page -permissions"),
HELP_GROUPS_8("html.label.help.manage.groups.line-8", "You need to assign both access and page permissions for users."),
HELP_GROUPS_9("html.label.help.manage.groups.line-9", "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."),
HELP_GROUPS_10("html.label.help.manage.groups.line-10", "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."),
HELP_GROUPS_11("html.label.help.manage.groups.line-11", "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."),
HELP_GROUPS_12("html.label.help.manage.groups.line-12", "Saving changes"),
HELP_GROUPS_13("html.label.help.manage.groups.line-13", "When you add a group or delete a group that action is saved immediately after confirm (no undo)."),
HELP_GROUPS_14("html.label.help.manage.groups.line-14", "When you modify permissions those changes need to be saved by pressing the Save-button"),
HELP_GROUPS_15("html.label.help.manage.groups.line-15", "Documentation can be found from {{link}}"),
HELP_GRAPH_ZOOM("html.label.help.graph.zoom", "You can Zoom in by click + dragging on the graph."),
HELP_GRAPH_TITLE("html.label.help.graph.title", "Graph"),
HELP_GRAPH_LABEL("html.label.help.graph.labels", "You can hide/show a group by clicking on the label at the bottom."),
@ -372,6 +394,24 @@ public enum HtmlLang implements Lang {
Y_AXIS("html.label.yAxis", "Y Axis"),
PERCENTAGE("html.label.unit.percentage", "Percentage"),
PLAYER_COUNT("html.label.unit.playerCount", "Player Count"),
MANAGE_GROUP_HEADER("html.label.managePage.groupHeader", "Manage Group Permissions"),
MANAGE_ADD_GROUP("html.label.managePage.addGroup.header", "Add group"),
MANAGE_ADD_GROUP_NAME("html.label.managePage.addGroup.name", "Name of the group"),
MANAGE_ADD_GROUP_NAME_INVALID("html.label.managePage.addGroup.invalidName", "Group name can be 100 characters maximum."),
MANAGE_GROUP_PERMISSION_LIST("html.label.managePage.groupPermissions", "Permissions of {{groupName}}"),
MANAGE_SAVE_CHANGES("html.label.managePage.changes.save", "Save"),
MANAGE_DISCARD_CHANGES("html.label.managePage.changes.discard", "Discard Changes"),
MANAGE_UNSAVED_CHANGES("html.label.managePage.changes.unsaved", "Unsaved changes"),
MANAGE_DELETE_GROUP_HEADER("html.label.managePage.deleteGroup.header", "Delete '{{groupName}}'"),
MANAGE_DELETE_GROUP_CONFIRM("html.label.managePage.deleteGroup.confirm", "Confirm & Delete {{groupName}}"),
MANAGE_DELETE_GROUP_MOVE_TO("html.label.managePage.deleteGroup.moveToSelect", "Move remaining users to group"),
MANAGE_DELETE_CONFIRM_DESCRIPTION("html.label.managePage.deleteGroup.confirmDescription", "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"),
MANAGE_ALERT_GROUP_DELETE_FAIL("html.label.managePage.alert.groupDeleteFail", "Failed to delete group: {{error}}"),
MANAGE_ALERT_GROUP_DELETE_SUCCESS("html.label.managePage.alert.groupDeleteSuccess", "Deleted group '{{groupName}}'"),
MANAGE_ALERT_GROUP_ADD_FAIL("html.label.managePage.alert.groupAddFail", "Failed to add group: {{error}}"),
MANAGE_ALERT_GROUP_ADD_SUCCESS("html.label.managePage.alert.groupAddSuccess", "Added group '{{groupName}}'"),
MANAGE_ALERT_SAVE_FAIL("html.label.managePage.alert.saveFail", "Failed to save changes: {{error}}"),
MANAGE_ALERT_SAVE_SUCCESS("html.label.managePage.alert.saveSuccess", "Changes saved successfully!"),
WARNING_NO_GAME_SERVERS("html.description.noGameServers", "Some data requires Plan to be installed on game servers."),
WARNING_PERFORMANCE_NO_GAME_SERVERS("html.description.performanceNoGameServers", "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."),

View File

@ -243,6 +243,11 @@ public abstract class SQLDB extends AbstractDatabase {
new BadJoinAddressDataCorrectionPatch(),
new AfterBadJoinAddressDataCorrectionPatch(),
new CorrectWrongCharacterEncodingPatch(logger, config),
new UpdateWebPermissionsPatch(),
new WebGroupDefaultGroupsPatch(),
new WebGroupAddMissingAdminGroupPatch(),
new LegacyPermissionLevelGroupsPatch(),
new SecurityTableGroupPatch()
};
}

View File

@ -30,6 +30,7 @@ import com.djrapitops.plan.storage.database.sql.tables.*;
import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement;
import com.djrapitops.plan.storage.database.transactions.Executable;
import org.apache.commons.lang3.StringUtils;
import org.intellij.lang.annotations.Language;
import java.sql.Connection;
import java.sql.PreparedStatement;
@ -127,7 +128,7 @@ public class LargeStoreQueries {
statement.setString(2, user.getLinkedToUUID().toString());
}
statement.setString(3, user.getPasswordHash());
statement.setInt(4, user.getPermissionLevel());
statement.setString(4, user.getPermissionGroup());
statement.addBatch();
}
}
@ -428,4 +429,58 @@ public class LargeStoreQueries {
}
};
}
public static Executable storeGroupNames(List<String> groups) {
if (groups == null || groups.isEmpty()) return Executable.empty();
return new ExecBatchStatement(WebGroupTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (String group : groups) {
statement.setString(1, group);
statement.addBatch();
}
}
};
}
public static Executable storePermissions(List<String> permissions) {
if (permissions == null || permissions.isEmpty()) return Executable.empty();
return new ExecBatchStatement(WebPermissionTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (String permission : permissions) {
statement.setString(1, permission);
statement.addBatch();
}
}
};
}
public static Executable storeGroupPermissionRelations(Map<String, List<String>> groupPermissions) {
if (groupPermissions == null || groupPermissions.isEmpty()) return Executable.empty();
@Language("SQL")
String sql = "INSERT INTO " + WebGroupToPermissionTable.TABLE_NAME + " (" +
WebGroupToPermissionTable.GROUP_ID + ',' + WebGroupToPermissionTable.PERMISSION_ID +
") VALUES ((" +
WebGroupTable.SELECT_GROUP_ID + "),(" + WebPermissionTable.SELECT_PERMISSION_ID +
"))";
return new ExecBatchStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (var permissionsOfGroup : groupPermissions.entrySet()) {
String group = permissionsOfGroup.getKey();
for (String permission : permissionsOfGroup.getValue()) {
statement.setString(1, group);
statement.setString(2, permission);
statement.addBatch();
}
}
}
};
}
}

View File

@ -16,20 +16,19 @@
*/
package com.djrapitops.plan.storage.database.queries.objects;
import com.djrapitops.plan.delivery.domain.WebUser;
import com.djrapitops.plan.delivery.domain.auth.User;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.sql.tables.CookieTable;
import com.djrapitops.plan.storage.database.sql.tables.SecurityTable;
import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
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 org.intellij.lang.annotations.Language;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
@ -45,45 +44,98 @@ public class WebUserQueries {
}
public static Query<Optional<User>> fetchUser(@Untrusted String username) {
String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME +
String sql = SELECT +
SecurityTable.USERNAME + ',' +
UsersTable.USER_NAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
WebGroupTable.NAME + ',' +
"GROUP_CONCAT(" + WebPermissionTable.PERMISSION + ",',') as user_permissions" +
FROM + SecurityTable.TABLE_NAME + " s" +
INNER_JOIN + WebGroupTable.TABLE_NAME + " g on g." + WebGroupTable.ID + "=s." + SecurityTable.GROUP_ID +
LEFT_JOIN + WebGroupToPermissionTable.TABLE_NAME + " gtp on gtp." + WebGroupToPermissionTable.GROUP_ID + "=s." + SecurityTable.GROUP_ID +
LEFT_JOIN + WebPermissionTable.TABLE_NAME + " p on gtp." + WebGroupToPermissionTable.PERMISSION_ID + "=p." + WebPermissionTable.ID +
LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID +
WHERE + SecurityTable.USERNAME + "=?" + LIMIT + "1";
WHERE + SecurityTable.USERNAME + "=?" +
GROUP_BY +
SecurityTable.USERNAME + ',' +
UsersTable.USER_NAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
WebGroupTable.NAME
+ LIMIT + "1";
return db -> db.queryOptional(sql, WebUserQueries::extractUser, username);
}
public static Query<Optional<User>> fetchUserLinkedTo(String playerName) {
String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME +
LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID +
WHERE + UsersTable.USER_NAME + "=?" + LIMIT + "1";
return db -> db.queryOptional(sql, WebUserQueries::extractUser, playerName);
}
public static Query<Optional<User>> fetchUser(UUID linkedToUUID) {
String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME +
String sql = SELECT +
SecurityTable.USERNAME + ',' +
UsersTable.USER_NAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
WebGroupTable.NAME + ',' +
"GROUP_CONCAT(" + WebPermissionTable.PERMISSION + ",',') as user_permissions" +
FROM + SecurityTable.TABLE_NAME + " s" +
INNER_JOIN + WebGroupTable.TABLE_NAME + " g on g." + WebGroupTable.ID + "=s." + SecurityTable.GROUP_ID +
LEFT_JOIN + WebGroupToPermissionTable.TABLE_NAME + " gtp on gtp." + WebGroupToPermissionTable.GROUP_ID + "=s." + SecurityTable.GROUP_ID +
LEFT_JOIN + WebPermissionTable.TABLE_NAME + " p on gtp." + WebGroupToPermissionTable.PERMISSION_ID + "=p." + WebPermissionTable.ID +
LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID +
WHERE + SecurityTable.LINKED_TO + "=?" + LIMIT + "1";
WHERE + SecurityTable.LINKED_TO + "=?" +
GROUP_BY +
SecurityTable.USERNAME + ',' +
UsersTable.USER_NAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
WebGroupTable.NAME
+ LIMIT + "1";
return db -> db.queryOptional(sql, WebUserQueries::extractUser, linkedToUUID);
}
public static Query<List<User>> fetchAllUsers() {
String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME +
LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID;
String sql = SELECT +
SecurityTable.USERNAME + ',' +
UsersTable.USER_NAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
WebGroupTable.NAME + ',' +
"GROUP_CONCAT(" + WebPermissionTable.PERMISSION + ",',') as user_permissions" +
FROM + SecurityTable.TABLE_NAME + " s" +
INNER_JOIN + WebGroupTable.TABLE_NAME + " g on g." + WebGroupTable.ID + "=s." + SecurityTable.GROUP_ID +
LEFT_JOIN + WebGroupToPermissionTable.TABLE_NAME + " gtp on gtp." + WebGroupToPermissionTable.GROUP_ID + "=s." + SecurityTable.GROUP_ID +
LEFT_JOIN + WebPermissionTable.TABLE_NAME + " p on gtp." + WebGroupToPermissionTable.PERMISSION_ID + "=p." + WebPermissionTable.ID +
LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID +
GROUP_BY +
SecurityTable.USERNAME + ',' +
UsersTable.USER_NAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
WebGroupTable.NAME;
return db -> db.queryList(sql, WebUserQueries::extractUser);
}
public static Query<List<User>> matchUsers(String partOfUsername) {
String sql = SELECT + '*' + FROM + SecurityTable.TABLE_NAME +
LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID +
WHERE + "LOWER(" + SecurityTable.USERNAME + ") LIKE LOWER(?)";
return db -> db.queryList(sql, WebUserQueries::extractUser, '%' + partOfUsername + '%');
}
public static Query<Map<String, User>> fetchActiveCookies() {
String sql = SELECT + '*' + FROM + CookieTable.TABLE_NAME +
INNER_JOIN + SecurityTable.TABLE_NAME + " on " + CookieTable.TABLE_NAME + '.' + CookieTable.WEB_USERNAME + '=' + SecurityTable.TABLE_NAME + '.' + SecurityTable.USERNAME +
String sql = SELECT +
SecurityTable.USERNAME + ',' +
UsersTable.USER_NAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
WebGroupTable.NAME + ',' +
CookieTable.COOKIE + ',' +
"GROUP_CONCAT(" + WebPermissionTable.PERMISSION + ",',') as user_permissions" +
FROM + CookieTable.TABLE_NAME + " c" +
INNER_JOIN + SecurityTable.TABLE_NAME + " s on c." + CookieTable.WEB_USERNAME + "=s." + SecurityTable.USERNAME +
INNER_JOIN + WebGroupTable.TABLE_NAME + " g on g." + WebGroupTable.ID + "=s." + SecurityTable.GROUP_ID +
LEFT_JOIN + WebGroupToPermissionTable.TABLE_NAME + " gtp on gtp." + WebGroupToPermissionTable.GROUP_ID + "=s." + SecurityTable.GROUP_ID +
LEFT_JOIN + WebPermissionTable.TABLE_NAME + " p on gtp." + WebGroupToPermissionTable.PERMISSION_ID + "=p." + WebPermissionTable.ID +
LEFT_JOIN + UsersTable.TABLE_NAME + " on " + SecurityTable.LINKED_TO + "=" + UsersTable.USER_UUID +
WHERE + CookieTable.EXPIRES + ">?";
WHERE + CookieTable.EXPIRES + ">?" +
GROUP_BY +
SecurityTable.USERNAME + ',' +
UsersTable.USER_NAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
WebGroupTable.NAME + ',' +
CookieTable.COOKIE;
return db -> db.queryMap(sql, (set, byCookie) -> byCookie.put(set.getString(CookieTable.COOKIE), extractUser(set)),
System.currentTimeMillis());
@ -94,13 +146,97 @@ public class WebUserQueries {
String linkedTo = set.getString(UsersTable.USER_NAME);
UUID linkedToUUID = linkedTo != null ? UUID.fromString(set.getString(SecurityTable.LINKED_TO)) : null;
String passwordHash = set.getString(SecurityTable.SALT_PASSWORD_HASH);
int permissionLevel = set.getInt(SecurityTable.PERMISSION_LEVEL);
List<String> permissions = WebUser.getPermissionsForLevel(permissionLevel);
return new User(username, linkedTo != null ? linkedTo : "console", linkedToUUID, passwordHash, permissionLevel, permissions);
String permissionGroup = set.getString(WebGroupTable.NAME);
String userPermissions = set.getString("user_permissions");
List<String> permissions = userPermissions != null ? Arrays.stream(userPermissions.split(","))
.filter(permission -> !permission.isEmpty())
.collect(Collectors.toList()) : List.of();
return new User(username, linkedTo != null ? linkedTo : "console", linkedToUUID, passwordHash, permissionGroup, new HashSet<>(permissions));
}
public static Query<Map<String, Long>> getCookieExpiryTimes() {
String sql = SELECT + CookieTable.COOKIE + ',' + CookieTable.EXPIRES + FROM + CookieTable.TABLE_NAME;
return db -> db.queryMap(sql, (set, expiryTimes) -> expiryTimes.put(set.getString(CookieTable.COOKIE), set.getLong(CookieTable.EXPIRES)));
}
public static Query<List<String>> fetchGroupNames() {
String sql = SELECT + WebGroupTable.NAME + FROM + WebGroupTable.TABLE_NAME;
return db -> db.queryList(sql, row -> row.getString(WebGroupTable.NAME));
}
public static Query<List<String>> fetchGroupPermissions(@Untrusted String group) {
String sql = SELECT + WebPermissionTable.PERMISSION +
FROM + WebGroupTable.TABLE_NAME + " g" +
INNER_JOIN + WebGroupToPermissionTable.TABLE_NAME + " gtp ON g." + WebGroupTable.ID + "=gtp." + WebGroupToPermissionTable.GROUP_ID +
INNER_JOIN + WebPermissionTable.TABLE_NAME + " p ON p." + WebPermissionTable.ID + "=gtp." + WebGroupToPermissionTable.PERMISSION_ID +
WHERE + WebGroupTable.NAME + "=?";
return db -> db.queryList(sql, row -> row.getString(WebPermissionTable.PERMISSION), group);
}
public static Query<List<String>> fetchAvailablePermissions() {
String sql = SELECT + WebPermissionTable.PERMISSION + FROM + WebPermissionTable.TABLE_NAME;
return db -> db.queryList(sql, row -> row.getString(WebPermissionTable.PERMISSION));
}
public static Query<Optional<Integer>> fetchGroupId(@Untrusted String name) {
return db -> db.queryOptional(WebGroupTable.SELECT_GROUP_ID, row -> row.getInt(WebGroupTable.ID), name);
}
public static Query<List<Integer>> fetchPermissionIds(@Untrusted Collection<String> permissions) {
String sql = SELECT + WebPermissionTable.ID +
FROM + WebPermissionTable.TABLE_NAME +
WHERE + WebPermissionTable.PERMISSION + " IN (" + Sql.nParameters(permissions.size()) + ")";
return db -> {
if (permissions.isEmpty()) return Collections.emptyList();
return db.queryList(sql, row -> row.getInt(WebPermissionTable.ID), permissions);
};
}
public static Query<List<String>> fetchAllUsernames() {
return db -> db.queryList(SELECT + SecurityTable.USERNAME + FROM + SecurityTable.TABLE_NAME, row -> row.getString(SecurityTable.USERNAME));
}
public static Query<List<String>> fetchGroupNamesWithPermission(String permission) {
@Language("SQL")
String sql = SELECT + WebGroupTable.NAME +
FROM + WebGroupTable.TABLE_NAME + " g" +
INNER_JOIN + WebGroupToPermissionTable.TABLE_NAME + " gp ON gp." + WebGroupToPermissionTable.GROUP_ID + "=g." + WebGroupTable.ID +
INNER_JOIN + WebPermissionTable.TABLE_NAME + " p ON gp." + WebGroupToPermissionTable.PERMISSION_ID + "=p." + WebPermissionTable.ID +
WHERE + WebPermissionTable.PERMISSION + "=?";
return db -> db.queryList(sql, row -> row.getString(WebGroupTable.NAME), permission);
}
public static Query<Optional<Integer>> fetchPermissionId(String permission) {
String sql = SELECT + WebPermissionTable.ID + FROM + WebPermissionTable.TABLE_NAME + WHERE + WebPermissionTable.PERMISSION + "=?";
return db -> db.queryOptional(sql, row -> row.getInt(WebPermissionTable.ID), permission);
}
public static Query<List<Integer>> fetchGroupIds(List<String> groups) {
String sql = SELECT + WebGroupTable.ID +
FROM + WebGroupTable.TABLE_NAME +
WHERE + WebGroupTable.NAME + " IN (" + Sql.nParameters(groups.size()) + ')';
return db -> db.queryList(sql, row -> row.getInt(WebGroupTable.ID), groups);
}
public static Query<Map<String, List<String>>> fetchAllGroupPermissions() {
String sql = SELECT + WebGroupTable.NAME + ',' + WebPermissionTable.PERMISSION +
FROM + WebGroupTable.TABLE_NAME + " g" +
INNER_JOIN + WebGroupToPermissionTable.TABLE_NAME + " gtp ON g." + WebGroupTable.ID + "=gtp." + WebGroupToPermissionTable.GROUP_ID +
INNER_JOIN + WebPermissionTable.TABLE_NAME + " p ON p." + WebPermissionTable.ID + "=gtp." + WebGroupToPermissionTable.PERMISSION_ID;
return new QueryAllStatement<>(sql, 100) {
@Override
public Map<String, List<String>> processResults(ResultSet set) throws SQLException {
Map<String, List<String>> groupMap = new HashMap<>();
while (set.next()) {
String group = set.getString(WebGroupTable.NAME);
String permission = set.getString(WebPermissionTable.PERMISSION);
List<String> permissionList = groupMap.computeIfAbsent(group, Lists::create);
permissionList.add(permission);
}
return groupMap;
}
};
}
}

View File

@ -24,6 +24,7 @@ import com.djrapitops.plan.storage.database.sql.building.Sql;
* Table information about 'plan_security'
*
* @author AuroraLS3
* @see com.djrapitops.plan.storage.database.transactions.patches.SecurityTableGroupPatch
*/
public class SecurityTable {
@ -33,13 +34,13 @@ public class SecurityTable {
public static final String USERNAME = "username";
public static final String LINKED_TO = "linked_to_uuid";
public static final String SALT_PASSWORD_HASH = "salted_pass_hash";
public static final String PERMISSION_LEVEL = "permission_level";
public static final String GROUP_ID = "group_id";
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" +
USERNAME + ',' +
LINKED_TO + ',' +
SALT_PASSWORD_HASH + ',' +
PERMISSION_LEVEL + ") VALUES (?,?,?,?)";
GROUP_ID + ") VALUES (?,?,?,(" + WebGroupTable.SELECT_GROUP_ID + "))";
private SecurityTable() {
/* Static information class */
@ -51,7 +52,8 @@ public class SecurityTable {
.column(USERNAME, Sql.varchar(100)).notNull().unique()
.column(LINKED_TO, Sql.varchar(36)).defaultValue("''")
.column(SALT_PASSWORD_HASH, Sql.varchar(100)).notNull().unique()
.column(PERMISSION_LEVEL, Sql.INT).notNull()
.column(GROUP_ID, Sql.INT).notNull()
.foreignKey(GROUP_ID, WebGroupTable.TABLE_NAME, WebGroupTable.ID)
.toString();
}
}

View File

@ -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.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 the plan_web_group table.
*
* @author AuroraLS3
*/
public class WebGroupTable {
public static final String TABLE_NAME = "plan_web_group";
public static final String ID = "id";
public static final String NAME = "group_name";
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" + NAME + ") VALUES (?)";
public static final String SELECT_GROUP_ID = SELECT + ID + FROM + TABLE_NAME + WHERE + NAME + "=?";
private WebGroupTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableBuilder.create(TABLE_NAME, dbType)
.column(ID, Sql.INT).primaryKey()
.column(NAME, Sql.varchar(100)).notNull().unique()
.toString();
}
}

View File

@ -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.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;
/**
* Represents the plan_web_permission table.
*
* @author AuroraLS3
*/
public class WebGroupToPermissionTable {
public static final String TABLE_NAME = "plan_web_group_to_permission";
public static final String ID = "id";
public static final String GROUP_ID = "group_id";
public static final String PERMISSION_ID = "permission_id";
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" + GROUP_ID + ',' + PERMISSION_ID + ") VALUES (?,?)";
private WebGroupToPermissionTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableBuilder.create(TABLE_NAME, dbType)
.column(ID, Sql.INT).primaryKey()
.column(GROUP_ID, Sql.INT).notNull()
.column(PERMISSION_ID, Sql.INT).notNull()
.foreignKey(GROUP_ID, WebGroupTable.TABLE_NAME, WebGroupTable.ID)
.foreignKey(PERMISSION_ID, WebPermissionTable.TABLE_NAME, WebPermissionTable.ID)
.toString();
}
}

View File

@ -0,0 +1,50 @@
/*
* 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 the plan_web_permission table.
*
* @author AuroraLS3
*/
public class WebPermissionTable {
public static final String TABLE_NAME = "plan_web_permission";
public static final String ID = "id";
public static final String PERMISSION = "permission";
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" + PERMISSION + ") VALUES (?)";
public static final String SELECT_PERMISSION_ID = SELECT + ID + FROM + TABLE_NAME + WHERE + PERMISSION + "=?";
private WebPermissionTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableBuilder.create(TABLE_NAME, dbType)
.column(ID, Sql.INT).primaryKey()
.column(PERMISSION, Sql.varchar(100)).notNull().unique()
.toString();
}
}

View File

@ -54,6 +54,7 @@ public class BackupCopyTransaction extends RemoveEverythingTransaction {
copyCommonUserInformation();
copyWorldNames();
copyTPSData();
copyWebGroups();
copyPlanWebUsers();
copyGeoInformation();
copyNicknameData();
@ -62,6 +63,12 @@ public class BackupCopyTransaction extends RemoveEverythingTransaction {
copyPingData();
}
private void copyWebGroups() {
copy(LargeStoreQueries::storeGroupNames, WebUserQueries.fetchGroupNames());
copy(LargeStoreQueries::storePermissions, WebUserQueries.fetchAvailablePermissions());
copy(LargeStoreQueries::storeGroupPermissionRelations, WebUserQueries.fetchAllGroupPermissions());
}
private <T> void copy(Function<T, Executable> executableCreator, Query<T> dataQuery) {
// Creates a new Executable from the queried data of the source database
execute(executableCreator.apply(sourceDB.query(dataQuery)));

View File

@ -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.storage.database.transactions;
import com.djrapitops.plan.exceptions.database.DBOpException;
import com.djrapitops.plan.storage.database.sql.tables.SecurityTable;
import com.djrapitops.plan.storage.database.sql.tables.WebGroupTable;
import com.djrapitops.plan.storage.database.sql.tables.WebGroupToPermissionTable;
import com.djrapitops.plan.utilities.dev.Untrusted;
import org.intellij.lang.annotations.Language;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static com.djrapitops.plan.storage.database.sql.building.Sql.DELETE_FROM;
import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
/**
* Removes a web group from the database.
*
* @author AuroraLS3
*/
public class DeleteWebGroupTransaction extends Transaction {
private final String name;
private final String moveTo;
public DeleteWebGroupTransaction(@Untrusted String name, @Untrusted String moveTo) {
this.name = name;
this.moveTo = moveTo;
}
@Override
protected void performOperations() {
String selectIdSql = WebGroupTable.SELECT_GROUP_ID;
Integer moveToId = query(db -> db.queryOptional(selectIdSql, row -> row.getInt(WebGroupTable.ID), moveTo))
.orElseThrow(() -> new DBOpException("Group not found for given name"));
query(db -> db.queryOptional(selectIdSql, row -> row.getInt(WebGroupTable.ID), name))
.ifPresent(groupId -> {
@Language("SQL")
String deletePermissionLinks = DELETE_FROM + WebGroupToPermissionTable.TABLE_NAME + WHERE + WebGroupToPermissionTable.GROUP_ID + "=?";
execute(new ExecStatement(deletePermissionLinks) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setInt(1, groupId);
}
});
@Language("SQL")
String moveUsersSql = "UPDATE " + SecurityTable.TABLE_NAME + " SET " + SecurityTable.GROUP_ID + "=?" +
WHERE + SecurityTable.GROUP_ID + "=?";
execute(new ExecStatement(moveUsersSql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setInt(1, moveToId);
statement.setInt(2, groupId);
}
});
@Language("SQL")
String deleteGroup = DELETE_FROM + WebGroupTable.TABLE_NAME + WHERE + WebGroupTable.ID + "=?";
execute(new ExecStatement(deleteGroup) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setInt(1, groupId);
}
});
}
);
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.exceptions.database.DBOpException;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.sql.tables.WebGroupToPermissionTable;
import org.intellij.lang.annotations.Language;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
/**
* Adds a permission to any group that already has the other given permission.
*
* @author AuroraLS3
*/
public class GrantWebPermissionToGroupsWithPermissionTransaction extends Transaction {
private final String permissionToGive;
private final String whenHasPermission;
public GrantWebPermissionToGroupsWithPermissionTransaction(String permissionToGive, String whenHasPermission) {
this.permissionToGive = permissionToGive;
this.whenHasPermission = whenHasPermission;
}
@Override
protected void performOperations() {
List<String> groupsWithPermission = query(WebUserQueries.fetchGroupNamesWithPermission(whenHasPermission));
List<String> groupsWithPermissionGiven = query(WebUserQueries.fetchGroupNamesWithPermission(permissionToGive));
groupsWithPermission.removeAll(groupsWithPermissionGiven);
List<Integer> groupIds = query(WebUserQueries.fetchGroupIds(groupsWithPermission));
if (groupIds.isEmpty()) return;
Integer permissionId = query(WebUserQueries.fetchPermissionId(permissionToGive))
.orElseThrow(() -> new DBOpException("Permission called '" + permissionToGive + "' not found in database."));
@Language("SQL")
String sql = "INSERT INTO " + WebGroupToPermissionTable.TABLE_NAME + '(' +
WebGroupToPermissionTable.GROUP_ID + ',' + WebGroupToPermissionTable.PERMISSION_ID +
") VALUES (?, ?)";
execute(new ExecBatchStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (Integer groupId : groupIds) {
statement.setInt(1, groupId);
statement.setInt(2, permissionId);
statement.addBatch();
}
}
});
}
}

View File

@ -0,0 +1,61 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.storage.database.transactions;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.sql.tables.WebPermissionTable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Adds missing permissions to the permission table.
*
* @author AuroraLS3
*/
public class StoreMissingWebPermissionsTransaction extends Transaction {
private final Collection<String> permissions;
public StoreMissingWebPermissionsTransaction(Collection<String> permissions) {
this.permissions = permissions;
}
@Override
protected void performOperations() {
List<String> storedPermissions = query(WebUserQueries.fetchAvailablePermissions());
Set<String> missingPermissions = new HashSet<>();
for (String permission : permissions) {
if (!storedPermissions.contains(permission)) {
missingPermissions.add(permission);
}
}
execute(new ExecBatchStatement(WebPermissionTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (String permission : missingPermissions) {
statement.setString(1, permission);
statement.addBatch();
}
}
});
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.sql.tables.WebGroupTable;
import com.djrapitops.plan.storage.database.sql.tables.WebGroupToPermissionTable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static com.djrapitops.plan.storage.database.sql.building.Sql.DELETE_FROM;
import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
/**
* Adds or updates a web permission group with specific permissions.
*
* @author AuroraLS3
*/
public class StoreWebGroupTransaction extends Transaction {
private final String name;
private final Collection<String> permissions;
public StoreWebGroupTransaction(String name, Collection<String> permissions) {
this.name = name;
this.permissions = permissions;
}
@Override
protected void performOperations() {
executeOther(new StoreMissingWebPermissionsTransaction(permissions));
commitMidTransaction();
Optional<Integer> id = query(WebUserQueries.fetchGroupId(name));
if (id.isPresent()) {
updateGroup(id.get());
} else {
insertGroup();
}
}
private void insertGroup() {
int id = executeReturningId(new ExecStatement(WebGroupTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, name);
}
});
updateGroup(id);
}
private void deletePermissionsOfGroup(int id) {
String sql = DELETE_FROM + WebGroupToPermissionTable.TABLE_NAME + WHERE + WebGroupToPermissionTable.GROUP_ID + "=?";
execute(new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setInt(1, id);
}
});
}
private void insertPermissionIdsOfGroup(int id, List<Integer> permissionIds) {
if (permissionIds.isEmpty()) return;
execute(new ExecBatchStatement(WebGroupToPermissionTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (Integer permissionId : permissionIds) {
statement.setInt(1, id);
statement.setInt(2, permissionId);
statement.addBatch();
}
}
});
}
private void updateGroup(Integer id) {
List<Integer> permissionIds = query(WebUserQueries.fetchPermissionIds(permissions));
deletePermissionsOfGroup(id);
insertPermissionIdsOfGroup(id, permissionIds);
}
}

View File

@ -148,6 +148,15 @@ public abstract class Transaction {
return rollbackStatusMsg;
}
protected void commitMidTransaction() {
try {
connection.commit();
initializeTransaction();
} catch (SQLException e) {
manageFailure(e);
}
}
/**
* Override this method for conditional execution.
* <p>

View File

@ -46,6 +46,9 @@ public class RemoveEverythingTransaction extends Patch {
clearTable(UserInfoTable.TABLE_NAME);
clearTable(UsersTable.TABLE_NAME);
clearTable(TPSTable.TABLE_NAME);
clearTable(WebGroupToPermissionTable.TABLE_NAME);
clearTable(WebPermissionTable.TABLE_NAME);
clearTable(WebGroupTable.TABLE_NAME);
clearTable(SecurityTable.TABLE_NAME);
clearTable(ServerTable.TABLE_NAME);
clearTable(CookieTable.TABLE_NAME);

View File

@ -17,13 +17,19 @@
package com.djrapitops.plan.storage.database.transactions.commands;
import com.djrapitops.plan.delivery.domain.auth.User;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.sql.tables.SecurityTable;
import com.djrapitops.plan.storage.database.sql.tables.WebGroupTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import com.djrapitops.plan.storage.database.transactions.StoreWebGroupTransaction;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Optional;
import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
/**
* Transaction to save a new Plan {@link User} to the database.
@ -40,18 +46,34 @@ public class StoreWebUserTransaction extends Transaction {
@Override
protected void performOperations() {
execute(new ExecStatement(SecurityTable.INSERT_STATEMENT) {
Optional<Integer> groupId = query(WebUserQueries.fetchGroupId(user.getPermissionGroup()));
if (groupId.isEmpty()) {
executeOther(new StoreWebGroupTransaction(user.getPermissionGroup(), user.getPermissions()));
}
String sql = "UPDATE " + SecurityTable.TABLE_NAME + " SET " + SecurityTable.GROUP_ID + "=(" + WebGroupTable.SELECT_GROUP_ID + ')' +
WHERE + SecurityTable.USERNAME + "=?";
boolean updated = execute(new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, user.getUsername());
if (user.getLinkedToUUID() == null) {
statement.setNull(2, Types.VARCHAR);
} else {
statement.setString(2, user.getLinkedToUUID().toString());
}
statement.setString(3, user.getPasswordHash());
statement.setInt(4, user.getPermissionLevel());
statement.setString(1, user.getPermissionGroup());
statement.setString(2, user.getUsername());
}
});
if (!updated) {
execute(new ExecStatement(SecurityTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, user.getUsername());
if (user.getLinkedToUUID() == null) {
statement.setNull(2, Types.VARCHAR);
} else {
statement.setString(2, user.getLinkedToUUID().toString());
}
statement.setString(3, user.getPasswordHash());
statement.setString(4, user.getPermissionGroup());
}
});
}
}
}

View File

@ -45,10 +45,13 @@ public class CreateTablesTransaction extends OperationCriticalTransaction {
execute(TPSTable.createTableSQL(dbType));
execute(WorldTable.createTableSQL(dbType));
execute(WorldTimesTable.createTableSQL(dbType));
execute(SecurityTable.createTableSQL(dbType));
execute(SettingsTable.createTableSQL(dbType));
execute(CookieTable.createTableSQL(dbType));
execute(AccessLogTable.createTableSql(dbType));
execute(WebGroupTable.createTableSQL(dbType));
execute(WebPermissionTable.createTableSQL(dbType));
execute(WebGroupToPermissionTable.createTableSQL(dbType));
execute(SecurityTable.createTableSQL(dbType));
// DataExtension tables
execute(ExtensionIconTable.createTableSQL(dbType));

View File

@ -0,0 +1,79 @@
/*
* 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.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.sql.tables.SecurityTable;
import com.djrapitops.plan.storage.database.transactions.StoreWebGroupTransaction;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;
/**
* Adds permission level based permission groups to group table.
* <ul>
* <li>permission level 0 = access to any pages (no manage)</li>
* <li>permission level 1 = access to /players, /query and /player pages</li>
* <li>permission level 2 = access to /player/{linked uuid} page</li>
* </ul>
*
* @author AuroraLS3
*/
public class LegacyPermissionLevelGroupsPatch extends Patch {
@Override
public boolean hasBeenApplied() {
return !hasColumn(SecurityTable.TABLE_NAME, "permission_level")
|| query(WebUserQueries.fetchGroupId("legacy_level_100")).isPresent();
}
@Override
protected void applyPatch() {
executeOther(new StoreWebGroupTransaction("legacy_level_0", Arrays.stream(new WebPermission[]{
WebPermission.PAGE_NETWORK,
WebPermission.PAGE_SERVER,
WebPermission.ACCESS_QUERY,
WebPermission.ACCESS_PLAYERS,
WebPermission.PAGE_PLAYER,
WebPermission.ACCESS_PLAYER,
WebPermission.ACCESS_SERVER,
WebPermission.ACCESS_NETWORK
})
.map(WebPermission::getPermission)
.collect(Collectors.toList()))
);
executeOther(new StoreWebGroupTransaction("legacy_level_1", Arrays.stream(new WebPermission[]{
WebPermission.ACCESS_QUERY,
WebPermission.ACCESS_PLAYERS,
WebPermission.PAGE_PLAYER,
WebPermission.ACCESS_PLAYER
})
.map(WebPermission::getPermission)
.collect(Collectors.toList()))
);
executeOther(new StoreWebGroupTransaction("legacy_level_2", Arrays.stream(new WebPermission[]{
WebPermission.PAGE_PLAYER,
WebPermission.ACCESS_PLAYER_SELF
})
.map(WebPermission::getPermission)
.collect(Collectors.toList()))
);
executeOther(new StoreWebGroupTransaction("legacy_level_100", Collections.emptyList()));
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.DBType;
import com.djrapitops.plan.storage.database.sql.tables.SecurityTable;
import com.djrapitops.plan.storage.database.sql.tables.WebGroupTable;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
/**
* Replaces permission_level with group_id to plan_security table.
*
* @author AuroraLS3
*/
public class SecurityTableGroupPatch extends Patch {
private final String tempTableName;
private final String tableName;
public SecurityTableGroupPatch() {
tableName = SecurityTable.TABLE_NAME;
tempTableName = "temp_security";
}
@Override
public boolean hasBeenApplied() {
return hasColumn(tableName, SecurityTable.GROUP_ID)
&& !hasColumn(tableName, "permission_level")
&& !hasTable(tempTableName); // If this table exists the patch has failed to finish.
}
@Override
protected void applyPatch() {
try {
tempOldTable();
dropTable(tableName);
execute(SecurityTable.createTableSQL(dbType));
execute("INSERT INTO " + tableName + " (" +
SecurityTable.USERNAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
SecurityTable.GROUP_ID +
") " + SELECT +
SecurityTable.USERNAME + ',' +
SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' +
"(" + SELECT + WebGroupTable.ID + FROM + WebGroupTable.TABLE_NAME + WHERE + WebGroupTable.NAME + "=" + (dbType == DBType.SQLITE ? "'legacy_level_' || permission_level" : "CONCAT('legacy_level_', permission_level)") + ")" +
FROM + tempTableName
);
dropTable(tempTableName);
} catch (Exception e) {
throw new DBOpException(SecurityTableGroupPatch.class.getSimpleName() + " failed.", e);
}
}
private void tempOldTable() {
if (!hasTable(tempTableName)) {
renameTable(tableName, tempTableName);
}
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.sql.tables.WebPermissionTable;
import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Adds missing web permissions to the permission table.
*
* @author AuroraLS3
*/
public class UpdateWebPermissionsPatch extends Patch {
private List<String> missingPermissions;
@Override
public boolean hasBeenApplied() {
List<String> defaultPermissions = Arrays.stream(WebPermission.values())
.map(WebPermission::getPermission)
.collect(Collectors.toList());
List<String> storedPermissions = query(WebUserQueries.fetchAvailablePermissions());
missingPermissions = new ArrayList<>();
for (String permission : defaultPermissions) {
if (!storedPermissions.contains(permission)) {
missingPermissions.add(permission);
}
}
return missingPermissions.isEmpty();
}
@Override
protected void applyPatch() {
execute(new ExecBatchStatement(WebPermissionTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (String permission : missingPermissions) {
statement.setString(1, permission);
statement.addBatch();
}
}
});
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.transactions.StoreWebGroupTransaction;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* Adds admin group back to the database if user accidentally deletes it.
*
* @author AuroraLS3
*/
public class WebGroupAddMissingAdminGroupPatch extends Patch {
@Override
public boolean hasBeenApplied() {
return !query(WebUserQueries.fetchGroupNamesWithPermission(WebPermission.MANAGE_GROUPS.getPermission())).isEmpty();
}
@Override
protected void applyPatch() {
executeOther(new StoreWebGroupTransaction("admin", Arrays.stream(new WebPermission[]{
WebPermission.PAGE,
WebPermission.ACCESS,
WebPermission.MANAGE_GROUPS,
WebPermission.MANAGE_USERS
})
.map(WebPermission::getPermission)
.collect(Collectors.toList()))
);
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.transactions.StoreWebGroupTransaction;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;
/**
* Adds default groups to plan_web_group table.
*
* @author AuroraLS3
*/
public class WebGroupDefaultGroupsPatch extends Patch {
@Override
public boolean hasBeenApplied() {
return query(WebUserQueries.fetchGroupId("no_access")).isPresent();
}
@Override
protected void applyPatch() {
executeOther(new StoreWebGroupTransaction("admin", Arrays.stream(new WebPermission[]{
WebPermission.PAGE,
WebPermission.ACCESS,
WebPermission.MANAGE_GROUPS,
WebPermission.MANAGE_USERS
})
.map(WebPermission::getPermission)
.collect(Collectors.toList()))
);
executeOther(new StoreWebGroupTransaction("read_all", Arrays.stream(new WebPermission[]{
WebPermission.PAGE_NETWORK,
WebPermission.PAGE_SERVER,
WebPermission.ACCESS_QUERY,
WebPermission.ACCESS_PLAYERS,
WebPermission.PAGE_PLAYER,
WebPermission.ACCESS_PLAYER,
WebPermission.ACCESS_RAW_PLAYER_DATA,
WebPermission.ACCESS_SERVER,
WebPermission.ACCESS_NETWORK
})
.map(WebPermission::getPermission)
.collect(Collectors.toList()))
);
executeOther(new StoreWebGroupTransaction("player_analyst", Arrays.stream(new WebPermission[]{
WebPermission.ACCESS_QUERY,
WebPermission.ACCESS_PLAYERS,
WebPermission.PAGE_PLAYER,
WebPermission.ACCESS_PLAYER,
WebPermission.ACCESS_RAW_PLAYER_DATA
})
.map(WebPermission::getPermission)
.collect(Collectors.toList()))
);
executeOther(new StoreWebGroupTransaction("player", Arrays.stream(new WebPermission[]{
WebPermission.PAGE_PLAYER,
WebPermission.ACCESS_PLAYER_SELF,
WebPermission.ACCESS_RAW_PLAYER_DATA
})
.map(WebPermission::getPermission)
.collect(Collectors.toList()))
);
executeOther(new StoreWebGroupTransaction("no_access", Collections.emptyList()));
}
}

View File

@ -23,6 +23,9 @@ command:
feature:
description: "要禁用的功能名称:${0}"
name: "功能"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "导入类型"
nameOrUUID:
description: "玩家的名称或 UUID"
@ -150,6 +153,9 @@ command:
export:
description: "手动导出 html 或 json 文件"
inDepth: "把数据导出到配置文件中指定的导出位置。"
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "导入数据"
inDepth: "执行导入,将数据加载到数据库。"
@ -193,6 +199,9 @@ command:
servers:
description: "列出数据库中的服务器"
inDepth: "列出数据库中所有服务器的ID、名称和UUID。"
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "注销一个 Plan 网页账户"
inDepth: "不含参数使用会注销当前绑定的账户,使用用户名作为参数能注销另一个用户。"
@ -226,6 +235,7 @@ command:
info:
database: " §2当前数据库§f${0}"
proxy: " §2连接至代理§f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2有可用更新§f${0}"
version: " §2版本§f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "独立:"
description:
newPlayerRetention: "这个数值是基于之前的玩家数据预测的。"
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "要获取某些数据,你需要将 Plan 安装在游戏服务器上。"
noGeolocations: "需要在配置文件中启用地理位置收集(Accept GeoLite2 EULA)。"
noServerOnlinActivity: "没有可显示在线活动的服务器"
noServers: "数据库中找不到服务器"
noServersLong: '看起来 Plan 没有安装在任何游戏服务器上或者游戏服务器未连接到相同的数据库。 群组网络教程请参见:<a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a>'
noSpongeChunks: "区块数据在 Sponge 服务端不可用"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "这个数值是基于之前的玩家数据预测的"
error:
401Unauthorized: "未认证"
@ -257,8 +271,10 @@ html:
emptyForm: "未指定用户名与密码"
expiredCookie: "用户 Cookie 已过期"
generic: "认证时发生错误"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "用户名和密码不匹配"
noCookie: "用户 cookie 不存在"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "注册失败,请重试(注册代码有效期 15 分钟)"
userNotFound: "用户名不存在"
authFailed: "认证失败。"
@ -315,9 +331,11 @@ html:
deaths: "死亡数"
disk: "硬盘空间"
diskSpace: "剩余硬盘空间"
docs: "Swagger Docs"
downtime: "停机时间"
duringLowTps: "持续低 TPS 时间"
entities: "实体"
errors: "Plan Error Logs"
exported: "数据导出时间"
favoriteServer: "最喜爱的服务器"
firstSession: "第一次会话"
@ -331,6 +349,8 @@ html:
miller: "米勒投影"
ortographic: "正射投影"
geolocations: "地理位置"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "活跃指数基于过去3周21天内的非AFK游戏时间。每周单独考虑。"
activityIndexExample1: "如果有人每周玩的时间达到阈值他们将获得活跃指数约为3。"
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "小时"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "最长会话时间"
lowTpsSpikes: "低 TPS 时间"
lowTpsSpikes7days: "低 TPS 时间7天"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "最大可用硬盘空间"
medianSessionLength: "会话长度中位数"
minFreeDisk: "最小可用硬盘空间"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "非常活跃"
weekComparison: "每周对比"
weekdays: "'星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'"
@ -557,6 +619,89 @@ html:
password: "密码"
register: "创建一个账户!"
username: "用户名"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "报告问题"
@ -648,6 +793,7 @@ html:
completion3: "在游戏中使用以下命令完成注册:"
completion4: "或使用控制台:"
createNewUser: "创建一个新用户"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "检查注册状态失败:"
failed: "注册失败:"

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Název funkce k vypnutí: ${0}"
name: "funkce"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "druh importu"
nameOrUUID:
description: "Jméno či UUID hráče"
@ -150,6 +153,9 @@ command:
export:
description: "Export html či json souborů manuálně"
inDepth: "Vykoná export k lokaci definované v configu."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Import dat"
inDepth: "Vykoná import k načtení dat z databáze."
@ -193,6 +199,9 @@ command:
servers:
description: "Seznam serverů v databázi"
inDepth: "Seznam id, jmen a uuid serverů v databázi."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Odregistrovat uživatele z Plan webu"
inDepth: "Použijte bez argumentů k odregistraci sebe, nebo zadejte jméno jiného uživatele."
@ -226,6 +235,7 @@ command:
info:
database: " §2Aktivní databáze: §f${0}"
proxy: " §2Připojen na Proxy: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Dostupná aktualizace: §f${0}"
version: " §2Verze: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Unikátní:"
description:
newPlayerRetention: "Tato hodnota je odhad dle předchozích hráčů."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Některá data vyžadují, aby byla na herní servery nainstalována aplikace Plan."
noGeolocations: "V konfiguraci je třeba povolit shromažďování geolokací (Přijměte GeoLite2 EULA)."
noServerOnlinActivity: "Žádný server pro který lze ukázat online aktivitu"
noServers: "Žádné servery nenalezeny v databázi"
noServersLong: 'Vypadá to, že plugin není nainstalován na žádném serveru nebo není propojen s databází. Podívejte se na tutoriál na <a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a>.'
noSpongeChunks: "Chunky nejsou na Sponge dostupné"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "Tato hodnota je odhad založený na předchozích hráčích"
error:
401Unauthorized: "Neautorizováno"
@ -257,8 +271,10 @@ html:
emptyForm: "Uživatel a heslo nespecifikováno"
expiredCookie: "Uživatelské cookie expirovalo"
generic: "Autentifikace neuspěšná kvůli chybě"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "Uživatel nebo heslo nesouhlasí"
noCookie: "Cookies uživatele nejsou k dispozici."
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Registrace selhala, zkuste to znovu. (Kód expiruje za 15 minut)"
userNotFound: "Uživatel neexistuje"
authFailed: "Ověření selhalo."
@ -315,9 +331,11 @@ html:
deaths: "Smrti"
disk: "Místo na disku"
diskSpace: "Volné místo na disku"
docs: "Swagger Docs"
downtime: "Offline doba"
duringLowTps: "Při nízkých TPS:"
entities: "Entity"
errors: "Plan Error Logs"
exported: "Doba exportu dat"
favoriteServer: "Oblíbený server"
firstSession: "První relace"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortografie"
geolocations: "Geolokace"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Index aktivity vychází z času stráveného hraním mimo AFK za poslední 3 týdny (21 dní). Každý týden se posuzuje zvlášť."
activityIndexExample1: "Pokud někdo hraje každý týden tolik, kolik je limit, má index aktivity ~3."
@ -343,6 +363,23 @@ html:
labels: "Skupinu můžete skrýt/zobrazit kliknutím na štítek v dolní části."
title: "Graf"
zoom: "Graf si můžete přiblížit kliknutím a táhnutím."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hodiny"
retention:
calculationStep1: "Nejprve se data filtrují pomocí možnosti '<>'. Hráči s 'registerDate' mimo časový rozsah jsou ignorováni."
@ -402,6 +439,30 @@ html:
longestSession: "Nejdelší relace"
lowTpsSpikes: "Nejnižší TPS"
lowTpsSpikes7days: "Nízké hodnoty TPS (7 dní)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Max. volného disku"
medianSessionLength: "Medián délky relace"
minFreeDisk: "Min. volného disku"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Procento"
playerCount: "Počet hráčů"
users: "Manage Users"
veryActive: "Velmi aktivní"
weekComparison: "Týdenní srovnání"
weekdays: "'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota', 'Neděle'"
@ -557,6 +619,89 @@ html:
password: "Heslo"
register: "Vytvořte si účet!"
username: "Uživatelské jméno"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Nahlásit potíže"
@ -648,6 +793,7 @@ html:
completion3: "Pro dokončení registrace použijte následující příkaz ve hře:"
completion4: "Nebo použijte příkaz v konzoli:"
createNewUser: "Vytvořit nového uživatele"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Kontrola registrace neúspěšná: "
failed: "Registrace se nezdařila: "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Name des zu deaktivierenden Features: ${0}"
name: "Feature"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "Import-Art"
nameOrUUID:
description: "Name oder UUID eines Spieler"
@ -150,6 +153,9 @@ command:
export:
description: "Exportiere JSON oder HTMl Dateien manuell"
inDepth: "Führt einen Export zum in der Config bestimmten Exportstandort aus."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Importiere Daten"
inDepth: "Führt einen Import durch, um Daten in die Datenbank zu laden."
@ -193,6 +199,9 @@ command:
servers:
description: "Liste die Server in der Datenbank auf"
inDepth: "List ids, names and uuids of servers in the database."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Registrierung eines Benutzers der Plan-Website aufheben"
inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user."
@ -226,6 +235,7 @@ command:
info:
database: " §2Aktuelle Datenbank: §f${0}"
proxy: " §2Verbunden mit Bungee: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Update verfügbar: §f${0}"
version: " §2Version: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Einzigartig:"
description:
newPlayerRetention: "Dieser Wert ist eine Vorhersage, die auf früheren Spielern basiert."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Some data requires Plan to be installed on game servers."
noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)."
noServerOnlinActivity: "Keine Server gefunden, um die Online Aktivität anzuzeigen"
noServers: "Keine Server in der Datenbank gefunden"
noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See <a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a> for Network tutorial.'
noSpongeChunks: "Chunks unavailable on Sponge"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "Dieser Wert ist eine Vorraussage, der sich auf, der auf den Spielern basiert"
error:
401Unauthorized: "Unautorisiert"
@ -257,8 +271,10 @@ html:
emptyForm: "User und Passwort nicht spezifiziert"
expiredCookie: "Benutzer-Cookie ist abgelaufen"
generic: "Authentifizierung fehlgeschlagen"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "User und Password stimmen nicht überein"
noCookie: "Benutzer-Cookie nicht vorhanden"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Registration failed, try again (The code expires after 15 minutes)"
userNotFound: "User existiert nicht"
authFailed: "Authentifizierung fehlgeschlagen."
@ -315,9 +331,11 @@ html:
deaths: "Tode"
disk: "Festplattenspeicher"
diskSpace: "Freier Festplattenspeicher"
docs: "Swagger Docs"
downtime: "Downtime"
duringLowTps: "Während niedriger TPS-Spitzen:"
entities: "Entitäten"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "Lieblingsserver"
firstSession: "Erste Sitzung"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "Geolocations"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "Längste Sitzung"
lowTpsSpikes: "Low TPS Spitzen"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Max Freier Speicher"
medianSessionLength: "Median Session Length"
minFreeDisk: "Min Freier Speicher"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "Sehr aktiv"
weekComparison: "Wochenvergleich"
weekdays: "'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'"
@ -557,6 +619,89 @@ html:
password: "Password"
register: "Create an Account!"
username: "Username"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Melde einen Bug"
@ -648,6 +793,7 @@ html:
completion3: "Use the following command in game to finish registration:"
completion4: "Or using console:"
createNewUser: "Create a new user"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Checking registration status failed: "
failed: "Registration failed: "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Name of the feature to disable: ${0}"
name: "feature"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "import kind"
nameOrUUID:
description: "Name or UUID of a player"
@ -150,6 +153,9 @@ command:
export:
description: "Export html or json files manually"
inDepth: "Performs an export to export location defined in the config."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Import data"
inDepth: "Performs an import to load data into the database."
@ -193,6 +199,9 @@ command:
servers:
description: "List servers in Database"
inDepth: "List ids, names and uuids of servers in the database."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Unregister a user of Plan website"
inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user."
@ -226,6 +235,7 @@ command:
info:
database: " §2Current Database: §f${0}"
proxy: " §2Connected to Proxy: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Update Available: §f${0}"
version: " §2Version: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Unique:"
description:
newPlayerRetention: "This value is a prediction based on previous players."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Some data requires Plan to be installed on game servers."
noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)."
noServerOnlinActivity: "No server to display online activity for"
noServers: "No servers found in the database"
noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See <a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a> for Network tutorial.'
noSpongeChunks: "Chunks unavailable on Sponge"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "This value is a prediction based on previous players"
error:
401Unauthorized: "Unauthorized"
@ -257,8 +271,10 @@ html:
emptyForm: "User and Password not specified"
expiredCookie: "User cookie has expired"
generic: "Authentication failed due to error"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "User and Password did not match"
noCookie: "User cookie not present"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Registration failed, try again (The code expires after 15 minutes)"
userNotFound: "User does not exist"
authFailed: "Authentication Failed."
@ -315,9 +331,11 @@ html:
deaths: "Deaths"
disk: "Disk space"
diskSpace: "Free Disk Space"
docs: "Swagger Docs"
downtime: "Downtime"
duringLowTps: "During Low TPS Spikes:"
entities: "Entities"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "Favorite Server"
firstSession: "First session"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "Geolocations"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "Longest Session"
lowTpsSpikes: "Low TPS Spikes"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Max Free Disk"
medianSessionLength: "Median Session Length"
minFreeDisk: "Min Free Disk"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "Very Active"
weekComparison: "Week Comparison"
weekdays: "'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'"
@ -557,6 +619,89 @@ html:
password: "Password"
register: "Create an Account!"
username: "Username"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Report Issues"
@ -648,6 +793,7 @@ html:
completion3: "Use the following command in game to finish registration:"
completion4: "Or using console:"
createNewUser: "Create a new user"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Checking registration status failed: "
failed: "Registration failed: "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Nombre de la característica a deshabilitar: ${0}"
name: "caracteristica"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "importar tipo"
nameOrUUID:
description: "Nombre o UUID de un jugador"
@ -150,6 +153,9 @@ command:
export:
description: "Exportar archivos html o json manualmente"
inDepth: "Realiza una exportación al directorio de exportación definido en la configuración."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Importar datos"
inDepth: "Realiza un importe de los datos hacia la base de datos."
@ -193,6 +199,9 @@ command:
servers:
description: "Listar los servidores en la base de datos"
inDepth: "Lista de ids, nombres y uuids de los servidores en la base de datos."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Quitar el registro de un usuario en la página web de Plan"
inDepth: "Usa sin argumentos para eliminar del registro al jugador vinculado, o con un argumento de nombre de usuario para eliminar su registro."
@ -226,6 +235,7 @@ command:
info:
database: " §2Base de datos actual: §f${0}"
proxy: " §2Conectado al Proxy: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Actualización disponible: §f${0}"
version: " §2Versión: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Únicos:"
description:
newPlayerRetention: "Este valor es una predicción basada en jugadores anteriores."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Some data requires Plan to be installed on game servers."
noGeolocations: "La recopilación de la Geolocalización necesita ser habilitada en la configuración (Y aceptar el GeoLite2 EULA)."
noServerOnlinActivity: "Sin un servidor donde registrar la actividad online."
noServers: "Ningun servidor encontrado en la base de datos."
noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See <a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a> for Network tutorial.'
noSpongeChunks: "Chunks no disponibles en Sponge"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "Este valor es una predicción que viene dado por jugadores anteriores."
error:
401Unauthorized: "No autorizado"
@ -257,8 +271,10 @@ html:
emptyForm: "Usuario y contraseña no especificados"
expiredCookie: "Las cookies del jugador han expirado"
generic: "La autenticación ha llevado a error"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "El usuario y la contraseña no coincide"
noCookie: "Las cookies del jugador no esta presentes"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Registro fallido, vuelve a intentar (El código expirara en 15 minutos)"
userNotFound: "El usuario no existe"
authFailed: "Autenticación fallada."
@ -315,9 +331,11 @@ html:
deaths: "Muertes"
disk: "Espacio del disco"
diskSpace: "Espacio libre en el disco duro"
docs: "Swagger Docs"
downtime: "Falta de tiempo"
duringLowTps: "Durante picos bajos de TPS:"
entities: "Entidades"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "Servidor favorito"
firstSession: "Primera sesión"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "Geolocalizaciones"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "Sesión más larga"
lowTpsSpikes: "Picos bajos TPS"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Máximo espacio libre en el disco duro"
medianSessionLength: "Median Session Length"
minFreeDisk: "Mínimo espacio libre en el disco duro"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "Muy activo"
weekComparison: "Comparación semanal"
weekdays: "'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'"
@ -557,6 +619,89 @@ html:
password: "Contraseña"
register: "Crear una cuenta!"
username: "Usuario"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Reportar errores"
@ -648,6 +793,7 @@ html:
completion3: "Usa el siguiente comando en el juego para finalizar el registro:"
completion4: "O usando la consola:"
createNewUser: "Crear un nuevo usuario"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Checking registration status failed: "
failed: "Registración fallida: "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Ominaisuuden nimi joka poistetaan käytöstä: ${0}"
name: "ominaisuus"
group:
description: "Käyttöoikeus Ryhmä, huomioi kirjainkoon"
name: "ryhmä"
importKind: "tuonnin muoto"
nameOrUUID:
description: "Pelaajan UUID tai nimi."
@ -100,7 +103,7 @@ command:
featureDisabled: "§aSammutettiin '${0}' toistaiseksi, kunnes Plan ladataan uudelleen."
noAddress: "§eOsoitetta ei ollut saatavilla - käytetään localhost:ia sen sijasta. Aseta 'Alternative_IP' asetukset."
noWebuser: "Sinulla ei ehkä ole Verkkokäyttäjää, käytä /plan register <salasana>-komentoa"
notifyWebUserRegister: "Rekisteröitiin uusi Verkkokäyttäjä: '${0}' Lupa taso: ${1}"
notifyWebUserRegister: "Rekisteröitiin uusi Verkkokäyttäjä: '${0}' Käyttäjäryhmä: ${1}"
pluginDisabled: "§aPlan on nyt poissa päältä. Voit käyttää reload komentoa uudelleenkäynnistykseen."
reloadComplete: "§aUudelleenlataus onnistui!"
reloadFailed: "§cUudelleenlatauksessa esiintyi ongelmia. Käynnistystä uudelleen suositellaan."
@ -117,7 +120,7 @@ command:
search: "> §2${0} Tulosta haulle §f${1}§2:"
serverList: "id::nimi::uuid::versio"
servers: "> §2Palvelimet"
webUserList: "käyttäjänimi::linkitetty pelaajaan::lupa taso"
webUserList: "käyttäjänimi::linkitetty pelaajaan::käyttäjäryhmä"
webUsers: "> §2${0} Verkkokäyttäjät"
help:
database:
@ -150,6 +153,9 @@ command:
export:
description: "Vie html tai json tietoja manuaalisesti"
inDepth: "Toimittaa viennin asetuksissa olevaan sijaintiin"
groups:
description: "Listaa käyttäjäryhmät."
inDepth: "Listaa saatavilla olevat käyttäjäryhmät joita hallitaan verkkonäkymästä."
import:
description: "Tuo tietoja"
inDepth: "Tuo tietoja tietokantaan"
@ -193,6 +199,9 @@ command:
servers:
description: "Listaa tietokannassa olevat palvelimet"
inDepth: "Listaa id:t, nimet ja uuid:t tietokannassa olevista palvelimista."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Poista Plan sivuston käyttäjä rekisteristä"
inDepth: "Käytä ilman argumentteja poistaaksesi nykyiseen pelaajaan linkitetty käyttäjä, tai anna käyttäjänimi joka poistaa"
@ -226,6 +235,7 @@ command:
info:
database: " §2Nykyinen Tietokanta: §f${0}"
proxy: " §2Yhdistetty Proxyyn: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Päivitys saatavilla: §f${0}"
version: " §2Versio: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Uniikit:"
description:
newPlayerRetention: "Tämä arvo on ennuste, joka perustuu edellisiin pelaajiin"
noData24h: "Palvelin ei ole lähettänyt tietoja yli 24 tuntiin."
noData30d: "Palvelin ei ole lähettänyt tietoja yli 30 päivään."
noData7d: "Palvelin ei ole lähettänyt tietoja yli 7 päivään."
noGameServers: "Osaa tietoja varten Plan täytyy asentaa peli palvelimille."
noGeolocations: "Maa-tietojen keräys täytyy laittaa päälle asetustiedostosta (Hyväksy Geolite2 EULA)"
noServerOnlinActivity: "Ei palvelinta jolle näyttää aktiivisuutta"
noServers: "Palvelimia ei löytynyt tietokannasta"
noServersLong: 'Vaikuttaa että Plan peli-palvelimia ei ole asennettu tai yhdistetty samaan tietokantaan. Katso <a href="https://github.com/plan-player-analytics/Plan/wiki">wikiin</a> lisätietoja varten.'
noSpongeChunks: "Alueiden määrää ei voi laskea Sponge palvelimilla"
performanceNoGameServers: "TPS, Entiteetti tai Chunkki tietoja ei kerätä proxy palvelimilta, koska niillä ei ole peli-askel sykliä."
predictedNewPlayerRetention: "Tämä arvo on arvattu ennustus edellisten pelaajien perusteella"
error:
401Unauthorized: "Todennusta ei suoritettu loppuun."
@ -257,8 +271,10 @@ html:
emptyForm: "Käyttäjää ja salasana vaaditaan."
expiredCookie: "Käyttäjän kirjautumiseväste vanheni"
generic: "Todennus epäonnistui virheen vuoksi"
groupNotFound: "Käyttäjäryhmää ei ole olemassa"
loginFailed: "Käyttäjä ja salasana ei täsmää"
noCookie: "Käyttäjän kirjautumisevästettä ei annettu"
noPermissionGroup: "Rekisteröityminen epäonnistui, pelaajalla ei ollut mitään 'plan.webgroup.{nimi}' lupaa"
registrationFailed: "Rekisteröityminen epäonnistui, yritä uudestaan (Koodi vanhenee 15 minuutin jälkeen)"
userNotFound: "Käyttäjää ei ole olemassa"
authFailed: "Todennus ei onnistunut."
@ -268,7 +284,7 @@ html:
serverNotExported: "Palvelinta ei löytynyt, sen tietoja ei ole välttämättä viety tiedostoon vielä"
serverNotSeen: "Palvelinta ei löytynyt"
generic:
none: "None"
none: "Ei mitään"
label:
active: "Aktiivinen"
activePlaytime: "Aktiivinen peliaika"
@ -315,9 +331,11 @@ html:
deaths: "Kuolemat"
disk: "Levytila"
diskSpace: "Vapaa Levytila"
docs: "Swagger Docs"
downtime: "Poissa päältä"
duringLowTps: "Matalan TPS:n aikana:"
entities: "Entiteetit"
errors: "Plan Error Logs"
exported: "Tietojen vientiaika"
favoriteServer: "Lempipalvelin"
firstSession: "Ensimmäinen sessio"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortografinen"
geolocations: "Sijainnit"
groupPermissions: "Hallitse ryhmiä"
groupUsers: "Hallitse ryhmien käyttäjiä"
help:
activityIndexBasis: "Aktiivisuus indeksi perustuu ei-AFK peliaikaan viimeiseltä kolmelta viikolta (21 päivää). Jokaista viikkoa katsotaan erikseen"
activityIndexExample1: "Jos joku pelaa kynnyksen määrän joka viikko, on aktiivisuus indeksi noin ~3."
@ -343,6 +363,23 @@ html:
labels: "Voit piilottaa/näyttää ryhmän klikkaamalla nimeä käyrän alapuolella"
title: "Käyrä"
zoom: "Voit katsoa tietoja tarkemmin klikkaamalla ja vetämällä käyrän päällä"
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "tuntia"
retention:
calculationStep1: "Ensin tiedot rajataan käyttämällä '<>' valintaa. Pelaajat joiden 'registerDate' osuu aikarajauksen ulkopuolelle eivät tule mukaan laskuihin"
@ -402,6 +439,30 @@ html:
longestSession: "Pisin istunto"
lowTpsSpikes: "Matalan TPS:n piikit"
lowTpsSpikes7days: "Matalan TPS:n piikit (7 päivää)"
manage: "Hallitse"
managePage:
addGroup:
header: "Lisää ryhmä"
invalidName: "Ryhmän nimi voi olla maksimissaan 100 kirjainta"
name: "Ryhmän nimi"
alert:
groupAddFail: "Ryhmän nimen lisäys epäonnistui: {{error}}"
groupAddSuccess: "Lisättiin ryhmä '{{groupName}}'"
groupDeleteFail: "Ryhmän poisto epäonnistui: {{error}}"
groupDeleteSuccess: "Poistettiin ryhmä '{{groupName}}'"
saveFail: "Muutosten talletus epäonnistui: {{error}}"
saveSuccess: "Muutokset tallennettu!"
changes:
discard: "Hylkää Muutokset"
save: "Tallenna"
unsaved: "Tallentamattomia muutoksia"
deleteGroup:
confirm: "Vahvista & Poista {{groupName}}"
confirmDescription: "Tämä siirtää kaikki '{{groupName}}':n käyttäjät ryhmään '{{moveTo}}'. Tätä ei voi peruuttaa!"
header: "Poista '{{groupName}}'"
moveToSelect: "Siirrä loput käyttäjät ryhmään"
groupHeader: "Hallitse ryhmän oikeuksia"
groupPermissions: "Ryhmän {{groupName}} oikeudet"
maxFreeDisk: "Maksimi vapaa levytila"
medianSessionLength: "Istuntopituuksien mediaani"
minFreeDisk: "Minimi vapaa levytila"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Prosentti"
playerCount: "Pelaajamäärä"
users: "Manage Users"
veryActive: "Todella Aktiivinen"
weekComparison: "Viikkojen vertaus"
weekdays: "'Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai', 'Sunnuntai'"
@ -557,6 +619,89 @@ html:
password: "Salasana"
register: "Luo käyttäjä!"
username: "Käyttäjänimi"
manage:
permission:
description:
access: "Ohjaa pääsyä eri sivuille"
access_docs: "Antaa pääsyn /docs sivulle"
access_errors: "Antaa pääsyn /errors sivulle"
access_network: "Antaa pääsyn /network sivulle"
access_player: "Antaa pääsyn any /player sivuille"
access_player_self: "Antaa pääsyn own /player sivulle"
access_players: "Antaa pääsyn /players sivulle"
access_query: "Antaa pääsyn /query ja kysely tulos sivuille"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Antaa pääsyn all /server sivuille"
manage_groups: "Antaa muuttaa ryhmien oikeuksia & Antaa pääsyn /manage/groups sivulle"
manage_users: "Antaa muuttaa mitkä käyttäjät kuuluvat mihinkin ryhmään"
page: "Ohjaa mitä milläkin sivulla näkyy"
page_network: "Näkee koko verkosto sivun"
page_network_geolocations: "Näkee Geolokaatio osion"
page_network_geolocations_map: "Näkee Geolokaatio kartan"
page_network_geolocations_ping_per_country: "Näkee Viive per Maa -taulun"
page_network_join_addresses: "Näkee Liittymäosoitteet osion"
page_network_join_addresses_graphs: "Näkee Liittymisosoite kaaviot"
page_network_join_addresses_graphs_pie: "Näkee kaavion Viimeisimmistä liittymisosoitteista"
page_network_join_addresses_graphs_time: "Näkee kaavion Liittymisosoitteista ajan yli"
page_network_overview: "Näkee Verkoston katsaus osion"
page_network_overview_graphs: "Näkee Verkoston katsaus kaaviot"
page_network_overview_graphs_day_by_day: "Näkee Päivä Kerrallaan -kaavion"
page_network_overview_graphs_hour_by_hour: "Näkee Tunti Kerrallaan -kaavion"
page_network_overview_graphs_online: "Näkee Pelaajia paikalla -kaavion"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "Näkee Verkoston suorituskyky osion"
page_network_playerbase: "Näkee Pelaajakunnan katsaus osion"
page_network_playerbase_graphs: "Näkee Pelaajakunnan katsaus kaaviot"
page_network_playerbase_overview: "Näkee Pelaajakunnan katsauksen numeroina"
page_network_players: "Näkee Pelaajalista osion"
page_network_plugins: "Näkee Proxy palvelimen Lisäosat osion"
page_network_retention: "Näkee Pelaajien pysyvyys osion"
page_network_server_list: "Näkee listan palvelimista"
page_network_sessions: "Näkee Istunnot osion"
page_network_sessions_list: "Näkee listan istunnoista"
page_network_sessions_overview: "Näkee havaintoja istunnoista"
page_network_sessions_server_pie: "Näkee palvelin piirakkakaavion"
page_network_sessions_world_pie: "Näkee maailma piirakkakaavion"
page_player: "Näkee koko pelaaja sivun"
page_player_overview: "Näkee Pelaajan katsaus osion"
page_player_plugins: "Näkee Lisäosat osiot"
page_player_servers: "Näkee Palvelimet osion"
page_player_sessions: "Näkee Pelaajan Istunnot osion"
page_player_versus: "Näkee PvP & PvE osion"
page_server: "Näkee koko palvelin sivun"
page_server_geolocations: "Näkee Geolokaatio osion"
page_server_geolocations_map: "Näkee Geolokaatio kartan"
page_server_geolocations_ping_per_country: "Näkee Viive per Maa -taulun"
page_server_join_addresses: "Näkee Liittymäosoitteet osion"
page_server_join_addresses_graphs: "Näkee Liittymisosoite kaaviot"
page_server_join_addresses_graphs_pie: "Näkee kaavion Viimeisimmistä liittymisosoitteista"
page_server_join_addresses_graphs_time: "Näkee kaavion Liittymisosoitteista ajan yli"
page_server_online_activity: "Näkee Online Aktiivisuus osion"
page_server_online_activity_graphs: "Näkee Online Aktiivisuus kaaviot"
page_server_online_activity_graphs_calendar: "Näkee Palvelimen Kalenterin"
page_server_online_activity_graphs_day_by_day: "Näkee Päivä Kerrallaan -kaavion"
page_server_online_activity_graphs_hour_by_hour: "Näkee Tunti Kerrallaan -kaavion"
page_server_online_activity_graphs_punchcard: "Näkee Reikäkortti kaavion"
page_server_online_activity_overview: "Näkee Online Aktiivisuuden numeroina"
page_server_overview: "Näkee Palvelimen katsaus osion"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "Näkee Pelaajia paikalla -kaavion"
page_server_performance: "Näkee Suorituskyky osion"
page_server_performance_graphs: "Näkee Suorituskyky kaaviot"
page_server_performance_overview: "Näkee Suorituskyky numerot"
page_server_player_versus: "Näkee PvP & PvE osion"
page_server_player_versus_kill_list: "Näkee pelaajien tappo- ja kuolemalistat"
page_server_player_versus_overview: "Näkee PvP & PvE numerot"
page_server_playerbase: "Näkee Pelaajakunnan katsaus osion"
page_server_playerbase_graphs: "Näkee Pelaajakunnan katsaus kaaviot"
page_server_playerbase_overview: "Näkee Pelaajakunnan katsauksen numeroina"
page_server_players: "Näkee Pelaajalista osion"
page_server_plugins: "Näkee palvelinten Lisäosat osiot"
page_server_retention: "Näkee Pelaajien pysyvyys osion"
page_server_sessions: "Näkee Istunnot osion"
page_server_sessions_list: "Näkee listan istunnoista"
page_server_sessions_overview: "Näkee havaintoja istunnoista"
page_server_sessions_world_pie: "Näkee maailma piirakkakaavion"
modal:
info:
bugs: "Ilmoita ongelmista"
@ -648,6 +793,7 @@ html:
completion3: "Käytä seuraavaa komentoa pelissä viimeistelläksesi rekisteröinnin:"
completion4: "Tai konsolia:"
createNewUser: "Luo uusi käyttäjä"
disabled: "Uusien käyttäjien rekisteröiminen on poistettu käytöstä asetuksista."
error:
checkFailed: "Rekisteröitymisen tilan tarkistus epäonnistui:"
failed: "Rekisteröinti epäonnistui:"
@ -657,7 +803,7 @@ html:
login: "Aiempi käyttäjä? Kirjaudu sisään!"
passwordTip: "Salasana kannattaa olla yli 8 merkkiä, mutta ei ole rajoituksia."
register: "Rekisteröidy"
success: "Registered a new user successfully! You can now login."
success: "Käyttäjä rekisteröitiin onnistuneesti! Voit nyt kirjautua."
usernameTip: "Käyttäjänimi voi olla enintään 50 merkkiä."
text:
clickToExpand: "Klikkaa laajentaaksesi"

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Nom de la fonctionnalité à désactiver : ${0}"
name: "fonctionnalité"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "type d'importation"
nameOrUUID:
description: "Nom ou UUID d'un joueur"
@ -150,6 +153,9 @@ command:
export:
description: "Exporter les fichiers HTML ou JSON manuellement"
inDepth: "Effectue une exportation vers un emplacement défini dans la configuration."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Importer des données"
inDepth: "Effectue une importation pour charger des données dans la base de données."
@ -193,6 +199,9 @@ command:
servers:
description: "Obtenir la liste des serveurs dans la base de données"
inDepth: "Liste les ids, noms et uuids des serveurs de la base de données."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Désenregistrer un utilisateur du site de Plan"
inDepth: "Utilisez sans argument pour désenregistrer un utilisateur lié à un joueur, ou avec un nom d'utilisateur pour désenregistrer un autre utilisateur."
@ -226,6 +235,7 @@ command:
info:
database: " §2Base de données actuelle : §f${0}"
proxy: " §2Connecté : §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Mise à jour disponible : §f${0}"
version: " §2Version : §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Unique :"
description:
newPlayerRetention: "Cette valeur est une prédiction basée sur les joueurs précédents."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Certaines données nécessitent l'installation de Plan sur les serveurs de jeu."
noGeolocations: "La collecte de la géolocalisation doit être activée dans la configuration (Accepter l'EULA de GeoLite2)."
noServerOnlinActivity: "Aucun serveur pour afficher l'activité en ligne."
noServers: "Il n'y a pas de serveur dans la base de données."
noServersLong: 'Il semblerait que Plan ne soit installé sur aucun des serveurs de jeu ou qu'il ne soit pas connecté à la même base de données. Voir <a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a> pour un tutoriel sur la mise en place d'un Réseau.'
noSpongeChunks: "Chunks indisponibles sur Sponge"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "Cette valeur est une prédiction basée sur les anciennes données du joueur."
error:
401Unauthorized: "Non autorisé."
@ -257,8 +271,10 @@ html:
emptyForm: "Utilisateur et mot de passe non spécifiés"
expiredCookie: "Le cookie de l'utilisateur a expiré"
generic: "Authentification échouée en raison d'une erreur"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "L'utilisateur et le mot de passe ne correspondent pas"
noCookie: "Cookie de l'utilisateur non présent"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Enregistrement échoué, veuillez réessayer (Le code expire au bout de 15 minutes)"
userNotFound: "Cet utilisateur n'existe pas"
authFailed: "Authentification échouée."
@ -315,9 +331,11 @@ html:
deaths: "Morts"
disk: "Espace Disque"
diskSpace: "Espace Disque disponible"
docs: "Swagger Docs"
downtime: "Temps Hors-Ligne"
duringLowTps: "Pendant les pics de TPS bas :"
entities: "Entités"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "Serveur Favori"
firstSession: "Première session"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "Géolocalisation"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "Session la plus Longue"
lowTpsSpikes: "Pics de TPS bas"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Espace Disque MAX disponible"
medianSessionLength: "Median Session Length"
minFreeDisk: "Espace Disque MIN disponible"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "Très Actif"
weekComparison: "Comparaison Hebdomadaire"
weekdays: "'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'"
@ -557,6 +619,89 @@ html:
password: "Mot de Passe"
register: "Créer un compte !"
username: "Nom d'Utilisateur"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Rapport de bugs"
@ -648,6 +793,7 @@ html:
completion3: "Utilisez la commande suivante en jeu pour terminer l'enregistrement :"
completion4: "Ou en utilisant la console :"
createNewUser: "Créer un nouvel utilisateur"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "La vérification de l'état de l'enregistrement a échoué : "
failed: "Enregistrement échoué : "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Name of the feature to disable: ${0}"
name: "feature"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "import kind"
nameOrUUID:
description: "Name or UUID of a player"
@ -150,6 +153,9 @@ command:
export:
description: "Export html or json files manually"
inDepth: "Performs an export to export location defined in the config."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Import data"
inDepth: "Performs an import to load data into the database."
@ -193,6 +199,9 @@ command:
servers:
description: "Elenca i server nel Database"
inDepth: "List ids, names and uuids of servers in the database."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Unregister a user of Plan website"
inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user."
@ -226,6 +235,7 @@ command:
info:
database: " §2Database corrente: §f${0}"
proxy: " §2Connesso al Proxy: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Aggiornamento Disponibile: §f${0}"
version: " §2Versione: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Unico:"
description:
newPlayerRetention: "This value is a prediction based on previous players."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Some data requires Plan to be installed on game servers."
noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)."
noServerOnlinActivity: "Nessun server con cui mostare attività"
noServers: "Nessun server trovato in questo database"
noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See <a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a> for Network tutorial.'
noSpongeChunks: "Chunks unavailable on Sponge"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "Questo valore è una previsione basata sui giocatori precedenti"
error:
401Unauthorized: "Non Autorizzato"
@ -257,8 +271,10 @@ html:
emptyForm: "Utente e Password non specificati"
expiredCookie: "User cookie has expired"
generic: "Autenticazione fallita a causa di un errore"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "Utente e Password non corrispondono"
noCookie: "User cookie not present"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Registration failed, try again (The code expires after 15 minutes)"
userNotFound: "Utente non esistente"
authFailed: "Autenticazione Fallita."
@ -315,9 +331,11 @@ html:
deaths: "Morti"
disk: "Spazio sul Disco"
diskSpace: "Spazio Disco Disponibile"
docs: "Swagger Docs"
downtime: "Downtime"
duringLowTps: "Durante Spicchi TPS bassi:"
entities: "Entità"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "Server Preferito"
firstSession: "Prima sessione"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "Geolocalizazione"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "Sessione più lunga"
lowTpsSpikes: "Spicchi TPS bassi"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Spazio Disco libero Max"
medianSessionLength: "Median Session Length"
minFreeDisk: "Spazio Disco libero Min"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "Molto Attivo"
weekComparison: "Confronto settimanale"
weekdays: "'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica'"
@ -557,6 +619,89 @@ html:
password: "Password"
register: "Create an Account!"
username: "Username"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Segnala Problemi"
@ -648,6 +793,7 @@ html:
completion3: "Use the following command in game to finish registration:"
completion4: "Or using console:"
createNewUser: "Create a new user"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Checking registration status failed: "
failed: "Registration failed: "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "「${0}」を無効化する機能の名前"
name: "機能"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "インポート系"
nameOrUUID:
description: "プレイヤーの名前もしくはUUID"
@ -150,6 +153,9 @@ command:
export:
description: "手動でHTMLかJsonファイルにエクスポートします"
inDepth: "コンフィグで指定した出力先にデータをエクスポートします"
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "データをインポートします"
inDepth: "データベースにデータをロードするためのインポートを行います"
@ -193,6 +199,9 @@ command:
servers:
description: "データベース内のBukkit/Spigotサーバー一覧を表示します"
inDepth: "データベース内に存在する全サーバーのID、名前、UUIDを表示します"
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "ウェブページからユーザーを未登録にします"
inDepth: "別のユーザーの登録を解除します。引数が未指定の場合、ユーザーにリンクされたのプレイヤーの登録を解除します。"
@ -262,8 +271,10 @@ html:
emptyForm: "ユーザーとパスワードが入力されてません"
expiredCookie: "ユーザーのクッキーの有効期限切れです"
generic: "エラーが発生したため認証に失敗しました"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "入力されたユーザー名とパスワードが間違っています"
noCookie: "ユーザーのクッキーが存在しません"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "登録に失敗しました。もう一度お試しください (コードの有効期限は 15 分後に切れます)"
userNotFound: "入力されたユーザーは存在しません"
authFailed: "認証に失敗しました"
@ -320,9 +331,11 @@ html:
deaths: "死亡回数"
disk: "ドライブの容量"
diskSpace: "ドライブの空き容量"
docs: "Swagger Docs"
downtime: "ダウンタイム"
duringLowTps: "TPSの低下までの時間:"
entities: "エンティティ数"
errors: "Plan Error Logs"
exported: "データエクスポート時間"
favoriteServer: "お気に入りのサーバー"
firstSession: "初参加"
@ -336,6 +349,8 @@ html:
miller: "ミラー図法"
ortographic: "正射図法"
geolocations: "地域"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "アクティビティ インデックスは、過去 3 週間 (21 日間) の非 AFK プレイ時間に基づいています。各週は個別に考慮されます"
activityIndexExample1: "誰かが毎週しきい値に達するほどプレイした場合、その人にはアクティビティインデックス~3が与えられます"
@ -348,6 +363,23 @@ html:
labels: "下部のラベルをクリックすることで、グループの表示/非表示を切り替えられます"
title: "グラフ"
zoom: "グラフをクリック+ドラッグで拡大できます"
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "時間"
retention:
calculationStep1: "まず、 '<>' を使ってデータをフィルタリングします。範囲外の登録日を持つプレイヤーは無視されます"
@ -407,6 +439,30 @@ html:
longestSession: "最長接続時間"
lowTpsSpikes: "TPSの低下値"
lowTpsSpikes7days: "TPSの低下値(7日)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "ディスクの最大空き容量"
medianSessionLength: "セッションの長さの中央値"
minFreeDisk: "ディスクの最低空き容量"
@ -542,6 +598,7 @@ html:
unit:
percentage: "パーセンテージ"
playerCount: "プレイヤー数"
users: "Manage Users"
veryActive: "とてもログインしている"
weekComparison: "直近1週間での比較"
weekdays: "'月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日'"
@ -562,6 +619,89 @@ html:
password: "パスワード"
register: "アカウントを作ろう!"
username: "ユーザー名"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "バグ報告"

View File

@ -23,6 +23,9 @@ command:
feature:
description: "비활성화할 기능의 이름: ${0}"
name: "기능"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "로드 종류"
nameOrUUID:
description: "Name or UUID of a player"
@ -150,6 +153,9 @@ command:
export:
description: "HTML 또는 JSON 형식 파일로 내보냅니다."
inDepth: "Performs an export to export location defined in the config."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "저장된 데이터를 불러옵니다."
inDepth: "Performs an import to load data into the database."
@ -193,6 +199,9 @@ command:
servers:
description: "서버 목록 열람하기"
inDepth: "List ids, names and uuids of servers in the database."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Plan 페이지 계정 탈퇴합니다."
inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user."
@ -226,6 +235,7 @@ command:
info:
database: " §2현재 데이터베이스: §f${0}"
proxy: " §2프록시에 연결됨: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2최신 버전: §f${0}"
version: " §2버전: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Unique:"
description:
newPlayerRetention: "This value is a prediction based on previous players."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Some data requires Plan to be installed on game servers."
noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)."
noServerOnlinActivity: "온라인 활동을 표시 할 서버가 없습니다."
noServers: "데이터베이스에 서버가 없습니다."
noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See <a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a> for Network tutorial.'
noSpongeChunks: "Chunks unavailable on Sponge"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "이 값은 기존 플레이어를 기반으로 한 예측입니다."
error:
401Unauthorized: "Unauthorized 401"
@ -257,8 +271,10 @@ html:
emptyForm: "사용자 및 암호가 지정되지 않았습니다."
expiredCookie: "사용자 쿠키가 만료되었습니다."
generic: "오류로 인해 인증에 실패했습니다."
groupNotFound: "Web Permission Group does not exist"
loginFailed: "사용자와 비밀번호가 일치하지 않습니다"
noCookie: "사용자 쿠키가 없습니다."
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "등록 실패, 다시 시도 (코드는 15분 후에 만료 됨)"
userNotFound: "사용자가 존재하지 않습니다"
authFailed: "Authentication Failed. 401"
@ -315,9 +331,11 @@ html:
deaths: "죽은 횟수"
disk: "디스크 공간"
diskSpace: "여유 디스크 공간"
docs: "Swagger Docs"
downtime: "다운타임"
duringLowTps: "낮은 TPS 스파이크 동안:"
entities: "엔티티"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "즐겨찾는 서버"
firstSession: "첫 번째 세션"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "지리적 위치"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "가장 긴 접속 시간"
lowTpsSpikes: "낮은 TPS 스파이크"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "최대 여유 디스크용량"
medianSessionLength: "Median Session Length"
minFreeDisk: "최소 여유 디스크용량"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "매우 활성화된"
weekComparison: "주 비교"
weekdays: "'월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일'"
@ -557,6 +619,89 @@ html:
password: "Password"
register: "Create an Account!"
username: "Username"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "문제 보고"
@ -648,6 +793,7 @@ html:
completion3: "Use the following command in game to finish registration:"
completion4: "Or using console:"
createNewUser: "Create a new user"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Checking registration status failed: "
failed: "Registration failed: "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Naam van de functie die moet worden uitgeschakeld: ${0}"
name: "feature"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "soort invoer"
nameOrUUID:
description: "Naam of UUID van een speler"
@ -150,6 +153,9 @@ command:
export:
description: "Exporteer html- of json-bestanden handmatig"
inDepth: "Voer een export uit naar de exportlocatie gedefinieerd in de configuratie."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Gegevens importeren"
inDepth: "Voer een import uit om gegevens naar de database te laden."
@ -193,6 +199,9 @@ command:
servers:
description: "Servers in database weergeven"
inDepth: "Geef een lijst met id's, namen en uuids van servers weer uit de database."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Een gebruiker van de Plan-website uitschrijven"
inDepth: "Gebruik zonder argumenten om de aan een speler gekoppelde gebruiker uit te schrijven, of met gebruikersnaamargument om een andere gebruiker uit te schrijven."
@ -226,6 +235,7 @@ command:
info:
database: " §2Huidige database: §f${0}"
proxy: " §2Verbonden met proxy: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Update Beschikbaar: §f${0}"
version: " §2Versie: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Uniek:"
description:
newPlayerRetention: "Deze waarde is een voorspelling op basis van eerdere spelers."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Voor sommige gegevens moet Plan op gameservers zijn geïnstalleerd."
noGeolocations: "Het verzamelen van geolocatie moet worden ingeschakeld in de configuratie (Accept GeoLite2 EULA)."
noServerOnlinActivity: "Er is geen server om online activiteit voor weer te geven"
noServers: "Er zijn geen servers gevonden in de database"
noServersLong: 'Het lijkt erop dat Plan op geen enkele gameserver is geïnstalleerd of niet is verbonden met dezelfde database. Zie <a href="https://github.com/plan-player-analytics/Plan/wiki">de wiki</a> voor een netwerkhandleiding.'
noSpongeChunks: "Chunks niet beschikbaar op Sponge"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "Deze waarde is een voorspelling op basis van eerdere spelers"
error:
401Unauthorized: "Onbevoegd"
@ -257,8 +271,10 @@ html:
emptyForm: "Gebruiker en wachtwoord niet gespecificeerd"
expiredCookie: "Gebruikerscookie is verlopen"
generic: "Authenticatie mislukt vanwege fout"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "Gebruiker en wachtwoord kwamen niet overeen"
noCookie: "Gebruikerscookie niet aanwezig"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Registratie mislukt, probeer het opnieuw (de code verloopt na 15 minuten)"
userNotFound: "Gebruiker bestaat niet"
authFailed: "Authenticatie mislukt."
@ -315,9 +331,11 @@ html:
deaths: "Sterfgevallen"
disk: "Schijfruimte"
diskSpace: "Vrije schijfruimte"
docs: "Swagger Docs"
downtime: "Uitvaltijd"
duringLowTps: "Tijdens lage TPS-pieken:"
entities: "Entiteiten"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "Favoeriete Server"
firstSession: "Eerste sessie"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "Geolocaties"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "Langste sessie"
lowTpsSpikes: "Lage TPS-pieken"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Max Vrije schijfruimte"
medianSessionLength: "Median Session Length"
minFreeDisk: "Min Vrije schijfruimte"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "Heel Actief"
weekComparison: "Weekvergelijking"
weekdays: "'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag'"
@ -557,6 +619,89 @@ html:
password: "Wachtwoord"
register: "Maak een account!"
username: "Gebruikersnaam"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Problemen melden"
@ -648,6 +793,7 @@ html:
completion3: "Gebruik de volgende opdracht in het spel om de registratie te voltooien::"
completion4: "Of via console:"
createNewUser: "Nieuwe gebruiker aanmaken"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Het controleren van de registratiestatus is mislukt: "
failed: "Registratie mislukt: "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Name of the feature to disable: ${0}"
name: "feature"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "import kind"
nameOrUUID:
description: "Name or UUID of a player"
@ -150,6 +153,9 @@ command:
export:
description: "Export html or json files manually"
inDepth: "Performs an export to export location defined in the config."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Import data"
inDepth: "Performs an import to load data into the database."
@ -193,6 +199,9 @@ command:
servers:
description: "Listar servidores do banco de dados"
inDepth: "List ids, names and uuids of servers in the database."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Unregister a user of Plan website"
inDepth: "Use without arguments to unregister player linked user, or with username argument to unregister another user."
@ -226,6 +235,7 @@ command:
info:
database: " §2Banco de dados atual: §f${0}"
proxy: " §2Conectados ao Bungee: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Atualização Disponível: §f${0}"
version: " §2Versão: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Únicos:"
description:
newPlayerRetention: "This value is a prediction based on previous players."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Some data requires Plan to be installed on game servers."
noGeolocations: "Geolocation gathering needs to be enabled in the config (Accept GeoLite2 EULA)."
noServerOnlinActivity: "No server to display online activity for"
noServers: "No servers found in the database"
noServersLong: 'It appears that Plan is not installed on any game servers or not connected to the same database. See <a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a> for Network tutorial.'
noSpongeChunks: "Chunks unavailable on Sponge"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "This value is a prediction based on previous players"
error:
401Unauthorized: "Acesso não autorizado"
@ -257,8 +271,10 @@ html:
emptyForm: "Usuário e Senha não específicado"
expiredCookie: "User cookie has expired"
generic: "Falha ao autenticar"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "Usuário e Senha não coincidem"
noCookie: "User cookie not present"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Registration failed, try again (The code expires after 15 minutes)"
userNotFound: "Usuário não existe"
authFailed: "Falha na Autenticação."
@ -315,9 +331,11 @@ html:
deaths: "Mortes"
disk: "Espaço de disco"
diskSpace: "Espaço de Disco Livre"
docs: "Swagger Docs"
downtime: "Downtime"
duringLowTps: "During Low TPS Spikes:"
entities: "Entidades"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "Servidor Favorito"
firstSession: "First session"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "Geolocalizações"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "Longest Session"
lowTpsSpikes: "Low TPS Spikes"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Max Free Disk"
medianSessionLength: "Median Session Length"
minFreeDisk: "Min Free Disk"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "Muito Ativo"
weekComparison: "Week Comparison"
weekdays: "'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'"
@ -557,6 +619,89 @@ html:
password: "Password"
register: "Create an Account!"
username: "Username"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Report Issues"
@ -648,6 +793,7 @@ html:
completion3: "Use the following command in game to finish registration:"
completion4: "Or using console:"
createNewUser: "Create a new user"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Checking registration status failed: "
failed: "Registration failed: "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Тип датабазы для отключения: ${0}"
name: "функция"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "вид импорта"
nameOrUUID:
description: "Ник или UUID игрока"
@ -150,6 +153,9 @@ command:
export:
description: "Экспортировать HTML или JSON файлы самостоятельно"
inDepth: "Выполняет экспорт в место экспорта, указанное в конфигурации."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Импортировать дату"
inDepth: "Выполняет импорт для загрузки данных в дата базу."
@ -193,6 +199,9 @@ command:
servers:
description: "Список серверов в базе данных"
inDepth: "Список айди, ников и UUID серверов в базе данных."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Удалить веб-пользователя"
inDepth: "Используйте без аргументов, чтобы отменить регистрацию пользователя."
@ -226,6 +235,7 @@ command:
info:
database: " §2Текущая база данных: §f${0}"
proxy: " §2Подключен к прокси: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Доступно обновление: §f${0}"
version: " §2Версия: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Уникальных:"
description:
newPlayerRetention: "Это значение является прогнозом, основанным на предыдущих игроках.."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Для некоторых данных требуется установка Plan на игровые сервера."
noGeolocations: "Сбор геолокации должен быть включен в конфигурации (ПРОЧИТАЙТЕ и примите GeoLite2 EULA)."
noServerOnlinActivity: "Нет сервера для отображения онлайн активности"
noServers: "В базе данных не найдено ни одного сервера"
noServersLong: 'Похоже, что Plan не установлен ни на одном игровом сервере или не подключен к той же базе данных. Смотрите <a href="https://github.com/plan-player-analytics/Plan/wiki">вики</a> для помощи.'
noSpongeChunks: "Чанки не доступны на Sponge"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "Это значение является прогнозом на основе предыдущих игроков"
error:
401Unauthorized: "Не авторизован"
@ -257,8 +271,10 @@ html:
emptyForm: "Имя пользователя и пароль не указаны"
expiredCookie: "Срок действия файла cookie пользователя истек"
generic: "Ошибка при авторизации"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "Пользователь и пароль не совпадают"
noCookie: "Пользовательский файл cookie отсутствует"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Регистрация не удалась, попробуйте ещё раз (Код перестанет действовать через 15 минут)"
userNotFound: "Пользователь не существует"
authFailed: "Ошибка аутентификации."
@ -315,9 +331,11 @@ html:
deaths: "Смерти"
disk: "Дисковое пространство"
diskSpace: "Свободное дисковое пространство"
docs: "Swagger Docs"
downtime: "Время простоя"
duringLowTps: "Во время низкого TPS:"
entities: "Объекты"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "Любимый сервер"
firstSession: "Первая сессия"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "Геолокация"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "Самая длинная сессия"
lowTpsSpikes: "Низкий TPS"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Макс. свободный диск"
medianSessionLength: "Средняя продолжительность сеанса"
minFreeDisk: "Мин. свободный диск"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "Очень активный"
weekComparison: "Сравнение за неделю"
weekdays: "'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'"
@ -557,6 +619,89 @@ html:
password: "Пароль"
register: "Создайте аккаунт!"
username: "Имя пользователя"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Сообщить о проблемах"
@ -648,6 +793,7 @@ html:
completion3: "Используйте следующую команду для окончания регистрации:"
completion4: "Или используйте консоль:"
createNewUser: "Создаём нового пользователя"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Не удалось проверить статус регистрации: "
failed: "Регистрация не удалась: "

View File

@ -23,6 +23,9 @@ command:
feature:
description: "Devre dışı bırakılacak özelliğin adı: ${0}"
name: "özellik"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "türü içe aktar"
nameOrUUID:
description: "Bir oyuncunun adı veya UUID'si"
@ -150,6 +153,9 @@ command:
export:
description: "Html veya json dosyalarını manuel olarak dışa aktarın"
inDepth: "Yapılandırmada tanımlanan dışa aktarma için bir dışa aktarma gerçekleştirir."
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "Verileri içe aktar"
inDepth: "Veritabanına veri yüklemek için bir içe aktarma gerçekleştirir."
@ -193,6 +199,9 @@ command:
servers:
description: "Sunucun tüm veritabanını listeler"
inDepth: "Veritabanındaki sunucuların kimliklerini, adlarını ve kullanıcılarını listeleyin."
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "Plan web sitesinin bir kullanıcısının kaydını iptal edin"
inDepth: "Oyuncu bağlantılı kullanıcının kaydını silmek için bağımsız değişken olmadan veya başka bir kullanıcının kaydını silmek için kullanıcı adı bağımsız değişkeniyle kullanın."
@ -226,6 +235,7 @@ command:
info:
database: " §2Mevcut veritabanı: §f${0}"
proxy: " §2Bungee ye bağlan: §f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2Güncelleme mevcut: §f${0}"
version: " §2Versiyon: §f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "Benzersiz:"
description:
newPlayerRetention: "Bu değer, önceki oyunculara dayalı bir tahmindir."
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "Bazı veriler, Plan'ın oyun sunucularına yüklenmesini gerektirir."
noGeolocations: "Konum belirleme toplama yapılandırmada etkinleştirilmelidir (GeoLite2 EULA'yı Kabul Et)."
noServerOnlinActivity: "Çevrimiçi etkinliği gösterecek sunucu yok"
noServers: "Veritabanında sunucu bulunamadı"
noServersLong: 'Plan'ın herhangi bir oyun sunucusuna yüklenmediği veya aynı veritabanına bağlı olmadığı anlaşılıyor. Ağ eğitimi için <a href="https://github.com/plan-player-analytics/Plan/wiki"> wiki </a> 'ye bakın.'
noSpongeChunks: "Chunklar Spongeda mevcut değil"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "Bu değer, önceki oyunculara dayalı bir tahmindir"
error:
401Unauthorized: "Yetkisiz"
@ -257,8 +271,10 @@ html:
emptyForm: "Kullanıcı ve Şifre belirtilmedi"
expiredCookie: "Kullanıcı çerezinin süresi doldu"
generic: "Kimlik doğrulama hata nedeniyle başarısız oldu"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "Kullanıcı adı ve şifre uyuşmuyor"
noCookie: "Kullanıcı çerezi mevcut değil"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "Kayıt başarısız oldu, tekrar deneyin (Kodun süresi 15 dakika sonra dolar)"
userNotFound: "Böyle Bir Kullanıcı Yok"
authFailed: "Kimlik doğrulama başarısız oldu."
@ -315,9 +331,11 @@ html:
deaths: "Ölümler"
disk: "Disk Alanı"
diskSpace: "Boş Disk Alanı"
docs: "Swagger Docs"
downtime: "Arıza Süresi"
duringLowTps: "Düşük TPS Artışları Sırasında:"
entities: "Varlıklar"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "Favori Sunucu"
firstSession: "İlk seans"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "Coğrafi Konumlar"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "En Uzun Oturum"
lowTpsSpikes: "Low TPS Spikes"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "Maksimum Boş Disk"
medianSessionLength: "Median Session Length"
minFreeDisk: "Minimum Boş Disk"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "Çok Aktif"
weekComparison: "Hafta Karşılaştırması"
weekdays: "'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'"
@ -557,6 +619,89 @@ html:
password: "Parola"
register: "Hesap oluştur!"
username: "Kullanıcı adı"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "Sorunları Bildir"
@ -648,6 +793,7 @@ html:
completion3: "Kaydı bitirmek için oyunda aşağıdaki komutu kullanın:"
completion4: "Veya konsol kullanarak:"
createNewUser: "Yeni bir kullanıcı oluşturun"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "Kayıt durumu kontrol edilemedi:"
failed: "Kayıt başarısız:"

View File

@ -23,6 +23,9 @@ command:
feature:
description: "要關閉的功能名稱:${0}"
name: "功能"
group:
description: "Web Permission Group, case sensitive."
name: "group"
importKind: "匯入類型"
nameOrUUID:
description: "玩家的名稱或 UUID"
@ -150,6 +153,9 @@ command:
export:
description: "手動匯出 html 或 json 檔案"
inDepth: "把資料匯出到設定檔案中指定的匯出位置。"
groups:
description: "List web permission groups."
inDepth: "List available web permission groups that are managed on the web interface."
import:
description: "匯入資料"
inDepth: "執行匯入,將資料載入到資料庫。"
@ -193,6 +199,9 @@ command:
servers:
description: "列出資料庫中的伺服器"
inDepth: "列出資料庫中所有伺服器的ID、名稱和UUID。"
setgroup:
description: "Change users web permission group."
inDepth: "Allows you to change a users web permission group to an existing web group. Use /plan groups for list of available groups."
unregister:
description: "註冊一個 Plan 網頁帳戶"
inDepth: "不含參數使用會註冊目前綁定的帳戶,使用使用者名稱作為參數能註冊另一個使用者。"
@ -226,6 +235,7 @@ command:
info:
database: " §2目前資料庫§f${0}"
proxy: " §2連接至代理§f${0}"
serverUUID: " §2Server UUID: §f${0}"
update: " §2有可用更新§f${0}"
version: " §2版本§f${0}"
generic:
@ -238,12 +248,16 @@ html:
unique: "獨立:"
description:
newPlayerRetention: "這個數值是基於之前的玩家資料預測的。"
noData24h: "Server has not sent data for over 24 hours."
noData30d: "Server has not sent data for over 30 days."
noData7d: "Server has not sent data for over 7 days."
noGameServers: "要獲得某些資料,你需要將 Plan 安裝在遊戲伺服器上。"
noGeolocations: "需要在設定檔案中啟用地理位置收集(Accept GeoLite2 EULA)。"
noServerOnlinActivity: "沒有可顯示線上活動的伺服器"
noServers: "資料庫中找不到伺服器"
noServersLong: '看起來 Plan 沒有安裝在任何遊戲伺服器上或者遊戲伺服器未連接到相同的資料庫。 群組網路教程請參見:<a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a>'
noSpongeChunks: "區塊資料在 Sponge 伺服端無法使用"
performanceNoGameServers: "TPS, Entity or Chunk data is not gathered from proxy servers since they don't have game tick loop."
predictedNewPlayerRetention: "這個數值是基於之前的玩家資料預測的"
error:
401Unauthorized: "未認證"
@ -257,8 +271,10 @@ html:
emptyForm: "未指定使用者名稱與密碼"
expiredCookie: "使用者 Cookie 已過期"
generic: "認證時發生錯誤"
groupNotFound: "Web Permission Group does not exist"
loginFailed: "使用者名稱和密碼無法配合"
noCookie: "使用者 cookie 不存在"
noPermissionGroup: "Registration failed, player did not have any 'plan.webgroup.{name}' permission"
registrationFailed: "註冊失敗,請重試(註冊代碼有效期 15 分鐘)"
userNotFound: "使用者不存在"
authFailed: "認證失敗。"
@ -315,9 +331,11 @@ html:
deaths: "死亡數"
disk: "硬碟空間"
diskSpace: "剩餘硬碟空間"
docs: "Swagger Docs"
downtime: "關機時間"
duringLowTps: "持續低 TPS 時間"
entities: "實體"
errors: "Plan Error Logs"
exported: "Data export time"
favoriteServer: "最喜愛的伺服器"
firstSession: "第一此會話"
@ -331,6 +349,8 @@ html:
miller: "Miller"
ortographic: "Ortographic"
geolocations: "地理位置"
groupPermissions: "Manage Groups"
groupUsers: "Manage Group Users"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
@ -343,6 +363,23 @@ html:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
manage:
groups:
line-1: "This view allows you to modify web group permissions."
line-10: "{{permission}} permissions determine what parts of the page are visible. These permissions also limit requests to the related data endpoints."
line-11: "{{permission1}} permissions are not required for data: {{permission2}} allows request to /v1/network/overview even without {{permission3}}."
line-12: "Saving changes"
line-13: "When you add a group or delete a group that action is saved immediately after confirm (no undo)."
line-14: "When you modify permissions those changes need to be saved by pressing the Save-button"
line-15: "Documentation can be found from {{link}}"
line-2: "User's web group is determined during {{command}} by checking if Player has {{permission}} permission."
line-3: "You can use {{command}} to change permission group after registering."
line-4: "{{icon}} If you ever accidentally delete all groups with {{permission}}} permission just {{command}}."
line-5: "Permission inheritance"
line-6: "Permissions follow inheritance model, where higher level permission grants all lower ones, eg. {{permission1}} also gives {{permission2}}, etc."
line-7: "Access vs Page -permissions"
line-8: "You need to assign both access and page permissions for users."
line-9: "{{permission1}} permissions allow user make the request to specific address, eg. {{permission2}} allows request to /network."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
@ -402,6 +439,30 @@ html:
longestSession: "最長會話時間"
lowTpsSpikes: "最低TPS"
lowTpsSpikes7days: "最低TPS 7 天)"
manage: "Manage"
managePage:
addGroup:
header: "Add group"
invalidName: "Group name can be 100 characters maximum."
name: "Name of the group"
alert:
groupAddFail: "Failed to add group: {{error}}"
groupAddSuccess: "Added group '{{groupName}}'"
groupDeleteFail: "Failed to delete group: {{error}}"
groupDeleteSuccess: "Deleted group '{{groupName}}'"
saveFail: "Failed to save changes: {{error}}"
saveSuccess: "Changes saved successfully!"
changes:
discard: "Discard Changes"
save: "Save"
unsaved: "Unsaved changes"
deleteGroup:
confirm: "Confirm & Delete {{groupName}}"
confirmDescription: "This will move all users of '{{groupName}}' to group '{{moveTo}}'. There is no undo!"
header: "Delete '{{groupName}}'"
moveToSelect: "Move remaining users to group"
groupHeader: "Manage Group Permissions"
groupPermissions: "Permissions of {{groupName}}"
maxFreeDisk: "最大可用硬碟空間"
medianSessionLength: "Median Session Length"
minFreeDisk: "最小可用硬碟空間"
@ -537,6 +598,7 @@ html:
unit:
percentage: "Percentage"
playerCount: "Player Count"
users: "Manage Users"
veryActive: "非常活躍"
weekComparison: "每週對比"
weekdays: "'星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'"
@ -557,6 +619,89 @@ html:
password: "密碼"
register: "建立一個帳戶!"
username: "使用者名稱"
manage:
permission:
description:
access: "Controls access to pages"
access_docs: "Allows accessing /docs page"
access_errors: "Allows accessing /errors page"
access_network: "Allows accessing /network page"
access_player: "Allows accessing any /player pages"
access_player_self: "Allows accessing own /player page"
access_players: "Allows accessing /players page"
access_query: "Allows accessing /query and Query results pages"
access_raw_player_data: "Allows accessing /player/{uuid}/raw json data. Follows 'access.player' permissions."
access_server: "Allows accessing all /server pages"
manage_groups: "Allows modifying group permissions & Access to /manage/groups page"
manage_users: "Allows modifying what users belong to what group"
page: "Controls what is visible on pages"
page_network: "See all of network page"
page_network_geolocations: "See Geolocations tab"
page_network_geolocations_map: "See Geolocations Map"
page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs"
page_network_overview_graphs_day_by_day: "See Day by Day graph"
page_network_overview_graphs_hour_by_hour: "See Hour by Hour graph"
page_network_overview_graphs_online: "See Players Online graph"
page_network_overview_numbers: "See Network Overview numbers"
page_network_performance: "See network Performance tab"
page_network_playerbase: "See Playerbase Overview -tab"
page_network_playerbase_graphs: "See Playerbase Overview graphs"
page_network_playerbase_overview: "See Playerbase Overview numbers"
page_network_players: "See Player list -tab"
page_network_plugins: "See Plugins tab of Proxy"
page_network_retention: "See Player Retention -tab"
page_network_server_list: "See list of servers"
page_network_sessions: "See Sessions tab"
page_network_sessions_list: "See list of sessions"
page_network_sessions_overview: "See Session insights"
page_network_sessions_server_pie: "See Server Pie graph"
page_network_sessions_world_pie: "See World Pie graph"
page_player: "See all of player page"
page_player_overview: "See Player Overview -tab"
page_player_plugins: "See Plugins -tabs"
page_player_servers: "See Servers -tab"
page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page"
page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs"
page_server_online_activity_graphs_calendar: "See Server calendar"
page_server_online_activity_graphs_day_by_day: "See Day by Day graph"
page_server_online_activity_graphs_hour_by_hour: "See Hour by Hour graph"
page_server_online_activity_graphs_punchcard: "See Punchcard graph"
page_server_online_activity_overview: "See Online Activity numbers"
page_server_overview: "See Server Overview -tab"
page_server_overview_numbers: "See Server Overview numbers"
page_server_overview_players_online_graph: "See Players Online graph"
page_server_performance: "See Performance tab"
page_server_performance_graphs: "See Performance graphs"
page_server_performance_overview: "See Performance numbers"
page_server_player_versus: "See PvP & PvE -tab"
page_server_player_versus_kill_list: "See Player kill and death lists"
page_server_player_versus_overview: "See PvP & PvE numbers"
page_server_playerbase: "See Playerbase Overview -tab"
page_server_playerbase_graphs: "See Playerbase Overview graphs"
page_server_playerbase_overview: "See Playerbase Overview numbers"
page_server_players: "See Player list -tab"
page_server_plugins: "See Plugins -tabs of servers"
page_server_retention: "See Player Retention -tab"
page_server_sessions: "See Sessions tab"
page_server_sessions_list: "See list of sessions"
page_server_sessions_overview: "See Session insights"
page_server_sessions_world_pie: "See World Pie graph"
modal:
info:
bugs: "報告問題"
@ -648,6 +793,7 @@ html:
completion3: "在遊戲中使用以下指令完成註冊:"
completion4: "或使用控制台:"
createNewUser: "建立一個新使用者"
disabled: "Registering new users has been disabled in the config."
error:
checkFailed: "檢查註冊狀態失敗:"
failed: "註冊失敗:"

View File

@ -45,6 +45,7 @@ import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -154,11 +155,11 @@ class PlanCommandTest {
}
@Test
void networkCommandSendsLink(Database database) {
void networkCommandSendsLink(Database database) throws ExecutionException, InterruptedException {
try {
Server server = new Server(ServerUUID.randomUUID(), "Serve", "", "");
server.setProxy(true);
database.executeTransaction(new StoreServerInformationTransaction(server));
database.executeTransaction(new StoreServerInformationTransaction(server)).get();
CMDSender sender = runCommand("network", "plan.network");

View File

@ -0,0 +1,190 @@
/*
* 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.commands.subcommands;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.commands.PlanCommand;
import com.djrapitops.plan.commands.use.Arguments;
import com.djrapitops.plan.commands.use.CMDSender;
import com.djrapitops.plan.commands.use.CommandWithSubcommands;
import com.djrapitops.plan.delivery.domain.auth.User;
import com.djrapitops.plan.delivery.webserver.Addresses;
import com.djrapitops.plan.settings.Permissions;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.changes.ConfigUpdater;
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
import com.djrapitops.plan.storage.database.transactions.commands.StoreWebUserTransaction;
import com.djrapitops.plan.utilities.PassEncryptUtil;
import com.google.gson.Gson;
import extension.FullSystemExtension;
import org.apache.commons.compress.utils.IOUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.openqa.selenium.json.TypeToken;
import utilities.HTTPConnector;
import utilities.TestConstants;
import utilities.TestResources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author AuroraLS3
*/
@ExtendWith(FullSystemExtension.class)
class RegistrationCommandsTest {
@BeforeAll
static void beforeAll(@TempDir Path tempDir, PlanSystem system) throws Exception {
File file = tempDir.resolve("TestCert.p12").toFile();
File testCert = TestResources.getTestResourceFile("TestCert.p12", ConfigUpdater.class);
Files.copy(testCert.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
String absolutePath = file.getAbsolutePath();
PlanConfig config = system.getConfigSystem().getConfig();
config.set(WebserverSettings.CERTIFICATE_PATH, absolutePath);
config.set(WebserverSettings.CERTIFICATE_KEYPASS, "test");
config.set(WebserverSettings.CERTIFICATE_STOREPASS, "test");
config.set(WebserverSettings.CERTIFICATE_ALIAS, "test");
system.enable();
}
@AfterAll
static void afterAll(PlanSystem system) {
system.disable();
}
@Test
@DisplayName("User is registered with 'admin' group with 'plan.webgroup.admin' permission")
void normalRegistrationFlow(Addresses addresses, PlanCommand command, Database database) throws Exception {
String username = "normalRegistrationFlow";
String code = registerUser(username, addresses);
CMDSender sender = mock(CMDSender.class);
when(sender.isPlayer()).thenReturn(true);
when(sender.hasPermission(Permissions.REGISTER_SELF.getPermission())).thenReturn(true);
when(sender.hasPermission("plan.webgroup.admin")).thenReturn(true);
when(sender.getUUID()).thenReturn(Optional.of(TestConstants.PLAYER_ONE_UUID));
when(sender.getPlayerName()).thenReturn(Optional.of(TestConstants.PLAYER_ONE_NAME));
command.build().executeCommand(sender, new Arguments(List.of("register", "--code", code)));
User user = database.query(WebUserQueries.fetchUser(username)).orElseThrow(AssertionError::new);
assertEquals("admin", user.getPermissionGroup());
}
@Test
@DisplayName("User registration fails without any plan.webgroup.{name} permission.")
void noPermissionFlow(Addresses addresses, PlanCommand command, Database database) throws Exception {
String username = "noPermissionFlow";
String code = registerUser(username, addresses);
CMDSender sender = mock(CMDSender.class);
when(sender.isPlayer()).thenReturn(true);
when(sender.hasPermission(Permissions.REGISTER_SELF.getPermission())).thenReturn(true);
when(sender.getUUID()).thenReturn(Optional.of(TestConstants.PLAYER_ONE_UUID));
when(sender.getPlayerName()).thenReturn(Optional.of(TestConstants.PLAYER_ONE_NAME));
CommandWithSubcommands cmd = command.build();
Arguments arguments = new Arguments(List.of("register", "--code", code));
assertThrows(IllegalArgumentException.class, () -> cmd.executeCommand(sender, arguments));
assertTrue(database.query(WebUserQueries.fetchUser(username)).isEmpty());
}
@Test
@DisplayName("User registration fails without any plan.webgroup.{name} permission attempting to bypass.")
void noPermissionFlowBypassAttempt(Addresses addresses, PlanCommand command, Database database) throws Exception {
String username = "noPermissionFlowBypassAttempt";
String code = registerUser(username, addresses);
CMDSender sender = mock(CMDSender.class);
when(sender.isPlayer()).thenReturn(true);
when(sender.hasPermission(Permissions.REGISTER_SELF.getPermission())).thenReturn(true);
when(sender.getUUID()).thenReturn(Optional.of(TestConstants.PLAYER_ONE_UUID));
when(sender.getPlayerName()).thenReturn(Optional.of(TestConstants.PLAYER_ONE_NAME));
CommandWithSubcommands cmd = command.build();
Arguments arguments = new Arguments(List.of("register", "--code", code, "superuser"));
assertThrows(IllegalArgumentException.class, () -> cmd.executeCommand(sender, arguments));
assertTrue(database.query(WebUserQueries.fetchUser(username)).isEmpty());
}
@Test
@DisplayName("User group is changed")
void setGroupCommandTest(PlanCommand command, Database database) throws Exception {
String username = "setGroupCommandTest";
User user = new User(username, "console", null, PassEncryptUtil.createHash("testPass"), "admin", Collections.emptyList());
database.executeTransaction(new StoreWebUserTransaction(user)).get();
CMDSender sender = mock(CMDSender.class);
when(sender.isPlayer()).thenReturn(true);
when(sender.hasPermission(Permissions.SET_GROUP.getPermission())).thenReturn(true);
when(sender.getUUID()).thenReturn(Optional.of(TestConstants.PLAYER_ONE_UUID));
when(sender.getPlayerName()).thenReturn(Optional.of(TestConstants.PLAYER_ONE_NAME));
command.build().executeCommand(sender, new Arguments(List.of("setgroup", username, "no_access")));
User modifiedUser = database.query(WebUserQueries.fetchUser(username)).orElseThrow(AssertionError::new);
assertEquals("no_access", modifiedUser.getPermissionGroup());
}
private static String registerUser(String username, Addresses addresses) throws IOException, KeyManagementException, NoSuchAlgorithmException {
HTTPConnector connector = new HTTPConnector();
HttpURLConnection connection = null;
String code;
try {
String address = addresses.getFallbackLocalhostAddress();
connection = connector.getConnection("POST", address + "/auth/register");
connection.setDoOutput(true);
connection.getOutputStream().write(("user=" + username + "&password=testPass").getBytes());
try (InputStream in = connection.getInputStream()) {
String responseBody = new String(IOUtils.toByteArray(in));
assertTrue(responseBody.contains("\"code\":"), () -> "Not successful: " + responseBody);
Map<String, Object> read = new Gson().fromJson(responseBody, new TypeToken<Map<String, Object>>() {}.getType());
code = (String) read.get("code");
System.out.println("Got registration code: " + code);
}
} finally {
if (connection != null) connection.disconnect();
}
return code;
}
}

View File

@ -18,24 +18,29 @@ package com.djrapitops.plan.delivery.webserver;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.delivery.domain.auth.User;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.extension.Caller;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.changes.ConfigUpdater;
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.ExtensionsDatabaseTest;
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
import com.djrapitops.plan.storage.database.transactions.StoreWebGroupTransaction;
import com.djrapitops.plan.storage.database.transactions.commands.StoreWebUserTransaction;
import com.djrapitops.plan.storage.database.transactions.events.PlayerRegisterTransaction;
import com.djrapitops.plan.utilities.PassEncryptUtil;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import utilities.HTTPConnector;
import utilities.RandomData;
import utilities.TestConstants;
@ -51,10 +56,13 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for limiting user access control based on permissions.
@ -67,11 +75,87 @@ class AccessControlTest {
private static PlanSystem system;
private static String address;
private static String cookieLevel0;
private static String cookieLevel1;
private static String cookieLevel2;
private static ServerUUID serverUUID;
private static String cookieLevel100;
private static String cookieNoAccess;
static Stream<Arguments> testCases() {
return Stream.of(
Arguments.of("/", WebPermission.ACCESS, 302, 403),
Arguments.of("/server", WebPermission.ACCESS_SERVER, 302, 403),
Arguments.of("/server/" + TestConstants.SERVER_UUID_STRING + "", WebPermission.ACCESS_SERVER, 200, 403),
Arguments.of("/css/style.css", WebPermission.ACCESS, 200, 200),
Arguments.of("/js/color-selector.js", WebPermission.ACCESS, 200, 200),
Arguments.of("/v1/serverOverview?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_OVERVIEW_NUMBERS, 200, 403),
Arguments.of("/v1/onlineOverview?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_OVERVIEW, 200, 403),
Arguments.of("/v1/sessionsOverview?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_SESSIONS, 200, 403),
Arguments.of("/v1/playerVersus?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PLAYER_VERSUS, 200, 403),
Arguments.of("/v1/playerbaseOverview?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PLAYERBASE_OVERVIEW, 200, 403),
Arguments.of("/v1/performanceOverview?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PERFORMANCE_OVERVIEW, 200, 403),
Arguments.of("/v1/graph?type=optimizedPerformance&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PERFORMANCE_GRAPHS, 200, 403),
Arguments.of("/v1/graph?type=aggregatedPing&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PERFORMANCE_GRAPHS, 200, 403),
Arguments.of("/v1/graph?type=worldPie&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_SESSIONS_WORLD_PIE, 200, 403),
Arguments.of("/v1/graph?type=activity&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS, 200, 403),
Arguments.of("/v1/graph?type=joinAddressPie&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE, 200, 403),
Arguments.of("/v1/graph?type=geolocation&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP, 200, 403),
Arguments.of("/v1/graph?type=uniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_DAY_BY_DAY, 200, 403),
Arguments.of("/v1/graph?type=hourlyUniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_HOUR_BY_HOUR, 200, 403),
Arguments.of("/v1/graph?type=serverCalendar&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_CALENDAR, 200, 403),
Arguments.of("/v1/graph?type=punchCard&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_PUNCHCARD, 200, 403),
Arguments.of("/v1/graph?type=joinAddressByDay&server=" + TestConstants.SERVER_UUID_STRING + "&after=0&before=" + 123456L + "", WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME, 200, 403),
Arguments.of("/v1/players?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PLAYERS, 200, 403),
Arguments.of("/v1/kills?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PLAYER_VERSUS_KILL_LIST, 200, 403),
Arguments.of("/v1/pingTable?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_GEOLOCATIONS_PING_PER_COUNTRY, 200, 403),
Arguments.of("/v1/sessions?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_SESSIONS_LIST, 200, 403),
Arguments.of("/v1/retention?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_RETENTION, 200, 403),
Arguments.of("/v1/joinAddresses?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_RETENTION, 200, 403),
Arguments.of("/network", WebPermission.ACCESS_NETWORK, 302, 403),
Arguments.of("/v1/network/overview", WebPermission.PAGE_NETWORK_OVERVIEW_NUMBERS, 200, 403),
Arguments.of("/v1/network/servers", WebPermission.PAGE_NETWORK_SERVER_LIST, 200, 403),
Arguments.of("/v1/network/sessionsOverview", WebPermission.PAGE_NETWORK_SESSIONS_OVERVIEW, 200, 403),
Arguments.of("/v1/network/playerbaseOverview", WebPermission.PAGE_NETWORK_PLAYERBASE_OVERVIEW, 200, 403),
Arguments.of("/v1/sessions", WebPermission.PAGE_NETWORK_SESSIONS_LIST, 200, 403),
Arguments.of("/v1/graph?type=playersOnline&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_OVERVIEW_PLAYERS_ONLINE_GRAPH, 200, 403),
Arguments.of("/v1/graph?type=uniqueAndNew", WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_DAY_BY_DAY, 200, 403),
Arguments.of("/v1/graph?type=hourlyUniqueAndNew", WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR, 200, 403),
Arguments.of("/v1/graph?type=serverPie", WebPermission.PAGE_NETWORK_SESSIONS_SERVER_PIE, 200, 403),
Arguments.of("/v1/graph?type=joinAddressPie", WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE, 200, 403),
Arguments.of("/v1/graph?type=activity", WebPermission.PAGE_NETWORK_PLAYERBASE_GRAPHS, 200, 403),
Arguments.of("/v1/graph?type=geolocation", WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP, 200, 403),
Arguments.of("/v1/network/pingTable", WebPermission.PAGE_NETWORK_GEOLOCATIONS_PING_PER_COUNTRY, 200, 403),
Arguments.of("/player/" + TestConstants.PLAYER_ONE_NAME + "", WebPermission.ACCESS_PLAYER, 200, 403),
Arguments.of("/player/" + TestConstants.PLAYER_TWO_NAME + "", WebPermission.ACCESS_PLAYER, 404, 403),
Arguments.of("/player/" + TestConstants.PLAYER_ONE_UUID_STRING + "", WebPermission.ACCESS_PLAYER, 200, 403),
Arguments.of("/player/" + TestConstants.PLAYER_TWO_UUID_STRING + "", WebPermission.ACCESS_PLAYER, 404, 403),
Arguments.of("/v1/player?player=" + TestConstants.PLAYER_ONE_NAME + "", WebPermission.ACCESS_PLAYER, 200, 403),
Arguments.of("/v1/player?player=" + TestConstants.PLAYER_TWO_NAME + "", WebPermission.ACCESS_PLAYER, 400, 403),
Arguments.of("/players", WebPermission.ACCESS_PLAYERS, 200, 403),
Arguments.of("/v1/players", WebPermission.ACCESS_PLAYERS, 200, 403),
Arguments.of("/query", WebPermission.ACCESS_QUERY, 200, 403),
Arguments.of("/v1/filters", WebPermission.ACCESS_QUERY, 200, 403),
Arguments.of("/v1/query", WebPermission.ACCESS_QUERY, 400, 403),
Arguments.of("/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D", WebPermission.ACCESS_QUERY, 200, 403),
Arguments.of("/v1/errors", WebPermission.ACCESS_ERRORS, 200, 403),
Arguments.of("/errors", WebPermission.ACCESS_ERRORS, 200, 403),
Arguments.of("/v1/network/listServers", WebPermission.PAGE_NETWORK_PERFORMANCE, 200, 403),
Arguments.of("/v1/network/serverOptions", WebPermission.PAGE_NETWORK_PERFORMANCE, 200, 403),
Arguments.of("/v1/network/performanceOverview?servers=[" + TestConstants.SERVER_UUID_STRING + "]", WebPermission.PAGE_NETWORK_PERFORMANCE, 200, 403),
Arguments.of("/v1/version", WebPermission.ACCESS, 200, 200),
Arguments.of("/v1/whoami", WebPermission.ACCESS, 200, 200),
Arguments.of("/v1/metadata", WebPermission.ACCESS, 200, 200),
Arguments.of("/v1/networkMetadata", WebPermission.ACCESS, 200, 200),
Arguments.of("/v1/serverIdentity?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.ACCESS_SERVER, 200, 403),
Arguments.of("/v1/locale", WebPermission.ACCESS, 200, 200),
Arguments.of("/v1/locale/EN", WebPermission.ACCESS, 200, 200),
Arguments.of("/v1/locale/NonexistingLanguage", WebPermission.ACCESS, 404, 404),
Arguments.of("/docs/swagger.json", WebPermission.ACCESS_DOCS, 500, 403), // swagger.json not available during tests
Arguments.of("/docs", WebPermission.ACCESS_DOCS, 200, 403),
Arguments.of("/pageExtensionApi.js", WebPermission.ACCESS, 200, 200),
Arguments.of("/manage", WebPermission.MANAGE_GROUPS, 200, 403),
Arguments.of("/v1/groupPermissions?group=admin", WebPermission.MANAGE_GROUPS, 200, 403),
Arguments.of("/v1/webGroups", WebPermission.MANAGE_GROUPS, 200, 403),
Arguments.of("/v1/deleteGroup?group=admin&moveTo=no_access", WebPermission.MANAGE_GROUPS, 400, 403),
Arguments.of("/v1/saveGroupPermissions?group=admin", WebPermission.MANAGE_GROUPS, 400, 403)
);
}
@BeforeAll
static void setUpClass(@TempDir Path tempDir) throws Exception {
@ -95,17 +179,13 @@ class AccessControlTest {
system.enable();
User userLevel0 = new User("test0", "console", null, PassEncryptUtil.createHash("testPass"), 0, Collections.emptyList());
User userLevel1 = new User("test1", "console", null, PassEncryptUtil.createHash("testPass"), 1, Collections.emptyList());
User userLevel2 = new User("test2", TestConstants.PLAYER_ONE_NAME, TestConstants.PLAYER_ONE_UUID, PassEncryptUtil.createHash("testPass"), 2, Collections.emptyList());
User userLevel100 = new User("test100", "console", null, PassEncryptUtil.createHash("testPass"), 100, Collections.emptyList());
system.getDatabaseSystem().getDatabase().executeTransaction(new StoreWebUserTransaction(userLevel0));
system.getDatabaseSystem().getDatabase().executeTransaction(new StoreWebUserTransaction(userLevel1));
system.getDatabaseSystem().getDatabase().executeTransaction(new StoreWebUserTransaction(userLevel2));
system.getDatabaseSystem().getDatabase().executeTransaction(new StoreWebUserTransaction(userLevel100));
User userNoAccess = new User("test0", "console", null, PassEncryptUtil.createHash("testPass"), "no_access", Collections.emptyList());
system.getDatabaseSystem().getDatabase().executeTransaction(new PlayerRegisterTransaction(TestConstants.PLAYER_ONE_UUID, () -> 0L, TestConstants.PLAYER_ONE_NAME));
system.getDatabaseSystem().getDatabase().executeTransaction(new StoreServerInformationTransaction(new Server(
Database database = system.getDatabaseSystem().getDatabase();
database.executeTransaction(new StoreWebUserTransaction(userNoAccess));
database.executeTransaction(new PlayerRegisterTransaction(TestConstants.PLAYER_ONE_UUID, () -> 0L, TestConstants.PLAYER_ONE_NAME));
database.executeTransaction(new StoreServerInformationTransaction(new Server(
TestConstants.SERVER_UUID,
TestConstants.SERVER_NAME,
address,
@ -119,10 +199,7 @@ class AccessControlTest {
assertTrue(system.getWebServerSystem().getWebServer().isAuthRequired());
address = "https://localhost:" + TEST_PORT_NUMBER;
cookieLevel0 = login(address, userLevel0.getUsername());
cookieLevel1 = login(address, userLevel1.getUsername());
cookieLevel2 = login(address, userLevel2.getUsername());
cookieLevel100 = login(address, userLevel100.getUsername());
cookieNoAccess = login(address, userNoAccess.getUsername());
}
@AfterAll
@ -132,7 +209,7 @@ class AccessControlTest {
}
}
static String login(String address, String username) throws IOException, KeyManagementException, NoSuchAlgorithmException {
public static String login(String address, String username) throws IOException, KeyManagementException, NoSuchAlgorithmException {
HttpURLConnection loginConnection = null;
String cookie;
try {
@ -151,318 +228,56 @@ class AccessControlTest {
return cookie;
}
@DisplayName("Access control test, level 0:")
@ParameterizedTest(name = "{0}: expecting {1}")
@CsvSource({
"/,302",
"/server,302",
"/server/" + TestConstants.SERVER_UUID_STRING + ",200",
"/css/style.css,200",
"/js/color-selector.js,200",
"/v1/serverOverview?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/onlineOverview?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/sessionsOverview?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/playerVersus?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/playerbaseOverview?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/performanceOverview?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=optimizedPerformance&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=aggregatedPing&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=worldPie&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=activity&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=joinAddressPie&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=geolocation&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=uniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=hourlyUniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=serverCalendar&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=punchCard&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=joinAddressByDay&server=" + TestConstants.SERVER_UUID_STRING + "&after=0&before=" + 123456L + ",200",
"/v1/players?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/kills?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/pingTable?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/sessions?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/retention?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/joinAddresses?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/network,302",
"/v1/network/overview,200",
"/v1/network/servers,200",
"/v1/network/sessionsOverview,200",
"/v1/network/playerbaseOverview,200",
"/v1/sessions,200",
"/v1/graph?type=playersOnline&server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/graph?type=uniqueAndNew,200",
"/v1/graph?type=hourlyUniqueAndNew,200",
"/v1/graph?type=serverPie,200",
"/v1/graph?type=joinAddressPie,200",
"/v1/graph?type=activity,200",
"/v1/graph?type=geolocation,200",
"/v1/network/pingTable,200",
"/player/" + TestConstants.PLAYER_ONE_NAME + ",200",
"/player/" + TestConstants.PLAYER_TWO_NAME + ",404",
"/player/" + TestConstants.PLAYER_ONE_UUID_STRING + ",200",
"/player/" + TestConstants.PLAYER_TWO_UUID_STRING + ",404",
"/v1/player?player=" + TestConstants.PLAYER_ONE_NAME + ",200",
"/v1/player?player=" + TestConstants.PLAYER_TWO_NAME + ",400",
"/players,200",
"/v1/players,200",
"/query,200",
"/v1/filters,200",
"/v1/query,400",
"/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D,200",
"/v1/errors,200",
"/errors,200",
"/v1/network/listServers,200",
"/v1/network/serverOptions,200",
"/v1/network/performanceOverview?servers=[" + TestConstants.SERVER_UUID_STRING + "],200",
"/v1/version,200",
"/v1/whoami,200",
"/v1/metadata,200",
"/v1/networkMetadata,200",
"/v1/serverIdentity?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/locale,200",
"/v1/locale/EN,200",
"/v1/locale/NonexistingLanguage,404",
"/docs/swagger.json,500", // swagger.json not available during tests
"/docs,200",
"/pageExtensionApi.js,200",
})
void levelZeroCanAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException {
int responseCode = access(resource, cookieLevel0);
assertEquals(Integer.parseInt(expectedResponseCode), responseCode, () -> "User level 0, Wrong response code for " + resource + ", expected " + expectedResponseCode + " but was " + responseCode);
@DisplayName("Access control test")
@ParameterizedTest(name = "{0}: Permission {1}, expecting {2} with & {3} without permission")
@MethodSource("testCases")
void accessControlTest(String resource, WebPermission permission, int expectedWithPermission, int expectedWithout) throws Exception {
String cookie = login(address, createUserWithPermissions(resource, permission).getUsername());
int responseCodeWithPermission = access(resource, cookie);
int responseCodeWithout = access(resource, cookieNoAccess);
assertAll(
() -> assertEquals(expectedWithPermission, responseCodeWithPermission,
() -> "Permission '" + permission.getPermission() + "', Wrong response code for " + resource + ", expected " + expectedWithPermission + " but was " + responseCodeWithPermission),
() -> assertEquals(expectedWithout, responseCodeWithout,
() -> "No Permissions, Wrong response code for " + resource + ", expected " + expectedWithout + " but was " + responseCodeWithout)
);
}
@DisplayName("Access control test, level 1:")
@ParameterizedTest(name = "{0}: expecting {1}")
@CsvSource({
"/,302",
"/server,403",
"/server/" + TestConstants.SERVER_UUID_STRING + ",403",
"/css/style.css,200",
"/js/color-selector.js,200",
"/v1/serverOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/onlineOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/sessionsOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/playerVersus?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/playerbaseOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/performanceOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=optimizedPerformance&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=aggregatedPing&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=worldPie&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=activity&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=joinAddressPie&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=geolocation&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=uniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=hourlyUniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=serverCalendar&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=punchCard&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/players?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/kills?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/pingTable?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/sessions?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/retention?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/joinAddresses?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=joinAddressByDay&server=" + TestConstants.SERVER_UUID_STRING + "&after=0&before=" + 123456L + ",403",
"/network,403",
"/v1/network/overview,403",
"/v1/network/servers,403",
"/v1/network/sessionsOverview,403",
"/v1/network/playerbaseOverview,403",
"/v1/sessions,403",
"/v1/graph?type=playersOnline&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=uniqueAndNew,403",
"/v1/graph?type=hourlyUniqueAndNew,403",
"/v1/graph?type=serverPie,403",
"/v1/graph?type=joinAddressPie,403",
"/v1/graph?type=activity,403",
"/v1/graph?type=geolocation,403",
"/v1/network/pingTable,403",
"/player/" + TestConstants.PLAYER_ONE_NAME + ",200",
"/player/" + TestConstants.PLAYER_TWO_NAME + ",404",
"/player/" + TestConstants.PLAYER_ONE_UUID_STRING + ",200",
"/player/" + TestConstants.PLAYER_TWO_UUID_STRING + ",404",
"/v1/player?player=" + TestConstants.PLAYER_ONE_NAME + ",200",
"/v1/player?player=" + TestConstants.PLAYER_TWO_NAME + ",400",
"/players,200",
"/v1/players,200",
"/query,200",
"/v1/filters,200",
"/v1/query,400",
"/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D,200",
"/v1/errors,403",
"/errors,403",
"/v1/network/listServers,403",
"/v1/network/serverOptions,403",
"/v1/network/performanceOverview?servers=[" + TestConstants.SERVER_UUID_STRING + "],403",
"/v1/version,200",
"/v1/whoami,200",
"/v1/metadata,200",
"/v1/networkMetadata,200",
"/v1/serverIdentity?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/locale,200",
"/v1/locale/EN,200",
"/v1/locale/NonexistingLanguage,404",
"/docs/swagger.json,403",
"/docs,403",
"/pageExtensionApi.js,200",
})
void levelOneCanAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException {
int responseCode = access(resource, cookieLevel1);
assertEquals(Integer.parseInt(expectedResponseCode), responseCode, () -> "User level 1, Wrong response code for " + resource + ", expected " + expectedResponseCode + " but was " + responseCode);
@DisplayName("Access test player/uuid/raw")
@Test
void playerRawAccess() throws Exception {
String resource = "/player/" + TestConstants.PLAYER_ONE_UUID + "/raw";
int expectedWithPermission = 200;
int expectedWithout = 403;
String cookie = login(address, createUserWithPermissions(resource, WebPermission.ACCESS_PLAYER, WebPermission.ACCESS_RAW_PLAYER_DATA).getUsername());
String cookieJustPage = login(address, createUserWithPermissions(resource, WebPermission.ACCESS_PLAYER).getUsername());
int responseCodeWithPermission = access(resource, cookie);
int responseCodeJustPage = access(resource, cookieJustPage);
int responseCodeWithout = access(resource, cookieNoAccess);
assertAll(
() -> assertEquals(expectedWithPermission, responseCodeWithPermission,
() -> "Permission 'access.player', 'access.raw.player.data', Wrong response code for " + resource + ", expected " + expectedWithPermission + " but was " + responseCodeWithPermission),
() -> assertEquals(expectedWithout, responseCodeJustPage,
() -> "Just page visibility permissions, Wrong response code for " + resource + ", expected " + expectedWithout + " but was " + responseCodeJustPage),
() -> assertEquals(expectedWithout, responseCodeWithout,
() -> "No Permissions, Wrong response code for " + resource + ", expected " + expectedWithout + " but was " + responseCodeWithout)
);
}
@DisplayName("Access control test, level 2:")
@ParameterizedTest(name = "{0}: expecting {1}")
@CsvSource({
"/,302",
"/server,403",
"/server/" + TestConstants.SERVER_UUID_STRING + ",403",
"/css/style.css,200",
"/js/color-selector.js,200",
"/v1/serverOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/onlineOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/sessionsOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/playerVersus?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/playerbaseOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/performanceOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=optimizedPerformance&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=aggregatedPing&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=worldPie&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=activity&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=joinAddressPie&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=geolocation&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=uniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=hourlyUniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=serverCalendar&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=punchCard&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=joinAddressByDay&server=" + TestConstants.SERVER_UUID_STRING + "&after=0&before=" + 123456L + ",403",
"/v1/players?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/kills?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/pingTable?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/sessions?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/retention?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/joinAddresses?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/network,403",
"/v1/network/overview,403",
"/v1/network/servers,403",
"/v1/network/sessionsOverview,403",
"/v1/network/playerbaseOverview,403",
"/v1/sessions,403",
"/v1/graph?type=playersOnline&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=uniqueAndNew,403",
"/v1/graph?type=hourlyUniqueAndNew,403",
"/v1/graph?type=serverPie,403",
"/v1/graph?type=joinAddressPie,403",
"/v1/graph?type=activity,403",
"/v1/graph?type=geolocation,403",
"/v1/network/pingTable,403",
"/player/" + TestConstants.PLAYER_ONE_NAME + ",200",
"/player/" + TestConstants.PLAYER_TWO_NAME + ",403",
"/player/" + TestConstants.PLAYER_ONE_UUID_STRING + ",200",
"/player/" + TestConstants.PLAYER_TWO_UUID_STRING + ",403",
"/v1/player?player=" + TestConstants.PLAYER_ONE_NAME + ",200",
"/v1/player?player=" + TestConstants.PLAYER_TWO_NAME + ",403",
"/players,403",
"/v1/players,403",
"/query,403",
"/v1/filters,403",
"/v1/query,403",
"/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D,403",
"/v1/errors,403",
"/errors,403",
"/v1/network/listServers,403",
"/v1/network/serverOptions,403",
"/v1/network/performanceOverview?servers=[" + TestConstants.SERVER_UUID_STRING + "],403",
"/v1/version,200",
"/v1/whoami,200",
"/v1/metadata,200",
"/v1/networkMetadata,200",
"/v1/serverIdentity?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/locale,200",
"/v1/locale/EN,200",
"/v1/locale/NonexistingLanguage,404",
"/docs/swagger.json,403",
"/docs,403",
"/pageExtensionApi.js,200",
})
void levelTwoCanAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException {
int responseCode = access(resource, cookieLevel2);
assertEquals(Integer.parseInt(expectedResponseCode), responseCode, () -> "User level 2, Wrong response code for " + resource + ", expected " + expectedResponseCode + " but was " + responseCode);
}
private User createUserWithPermissions(String resource, WebPermission... permissions) throws ExecutionException, InterruptedException {
Database db = system.getDatabaseSystem().getDatabase();
@DisplayName("Access control test, level 100:")
@ParameterizedTest(name = "{0}: expecting {1}")
@CsvSource({
"/,403",
"/server,403",
"/server/" + TestConstants.SERVER_UUID_STRING + ",403",
"/css/style.css,200",
"/js/color-selector.js,200",
"/v1/serverOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/onlineOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/sessionsOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/playerVersus?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/playerbaseOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/performanceOverview?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=optimizedPerformance&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=aggregatedPing&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=worldPie&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=activity&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=joinAddressPie&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=geolocation&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=uniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=hourlyUniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=serverCalendar&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=punchCard&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=joinAddressByDay&server=" + TestConstants.SERVER_UUID_STRING + "&after=0&before=" + 123456L + ",403",
"/v1/players?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/kills?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/pingTable?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/sessions?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/retention?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/joinAddresses?server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/network,403",
"/v1/network/overview,403",
"/v1/network/servers,403",
"/v1/network/sessionsOverview,403",
"/v1/network/playerbaseOverview,403",
"/v1/sessions,403",
"/v1/graph?type=playersOnline&server=" + TestConstants.SERVER_UUID_STRING + ",403",
"/v1/graph?type=uniqueAndNew,403",
"/v1/graph?type=hourlyUniqueAndNew,403",
"/v1/graph?type=serverPie,403",
"/v1/graph?type=joinAddressPie,403",
"/v1/graph?type=activity,403",
"/v1/graph?type=geolocation,403",
"/v1/network/pingTable,403",
"/player/" + TestConstants.PLAYER_ONE_NAME + ",403",
"/player/" + TestConstants.PLAYER_TWO_NAME + ",403",
"/player/" + TestConstants.PLAYER_ONE_UUID_STRING + ",403",
"/player/" + TestConstants.PLAYER_TWO_UUID_STRING + ",403",
"/v1/player?player=" + TestConstants.PLAYER_ONE_NAME + ",403",
"/v1/player?player=" + TestConstants.PLAYER_TWO_NAME + ",403",
"/players,403",
"/v1/players,403",
"/query,403",
"/v1/filters,403",
"/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D,403",
"/v1/query,403",
"/v1/network/listServers,403",
"/v1/network/serverOptions,403",
"/v1/network/performanceOverview?servers=[" + TestConstants.SERVER_UUID_STRING + "],403",
"/v1/version,200",
"/v1/whoami,200",
"/v1/metadata,200",
"/v1/networkMetadata,200",
"/v1/serverIdentity?server=" + TestConstants.SERVER_UUID_STRING + ",200",
"/v1/locale,200",
"/v1/locale/EN,200",
"/v1/locale/NonexistingLanguage,404",
"/docs/swagger.json,403",
"/docs,403",
"/pageExtensionApi.js,200",
})
void levelHundredCanNotAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException {
int responseCode = access(resource, cookieLevel100);
assertEquals(Integer.parseInt(expectedResponseCode), responseCode, () -> "User level 100, Wrong response code for " + resource + ", expected " + expectedResponseCode + " but was " + responseCode);
String groupName = StringUtils.truncate(resource, 75);
db.executeTransaction(
new StoreWebGroupTransaction(groupName, Arrays.stream(permissions).map(WebPermission::getPermission).collect(Collectors.toList()))
).get();
User user = new User(RandomData.randomString(45), "console", null, PassEncryptUtil.createHash("testPass"), groupName, Collections.emptyList());
db.executeTransaction(new StoreWebUserTransaction(user)).get();
return user;
}
private int access(String resource, String cookie) throws IOException, KeyManagementException, NoSuchAlgorithmException {

Some files were not shown because too many files have changed in this diff Show More