Skip to content

Commit

Permalink
Various fixes to design
Browse files Browse the repository at this point in the history
- Re-register admin group on reload if missing
- Don't assume group with name 'admin' exists
- Add permissions for viewing network and server overview numbers separately
- Night mode fixes
- Help modal for group management
  • Loading branch information
AuroraLS3 committed Aug 12, 2023
1 parent 95024ac commit 04a5289
Show file tree
Hide file tree
Showing 15 changed files with 149 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -124,7 +125,8 @@ private Optional<String> getPermissionGroup(CMDSender sender, @Untrusted Argumen
}
}
} else if (arguments.contains("superuser")) {
return Optional.of("admin");
return dbSystem.getDatabase().query(WebUserQueries.fetchGroupNamesWithPermission(WebPermission.MANAGE_GROUPS.getPermission()))
.stream().findFirst();
}
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ 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"),
Expand Down Expand Up @@ -57,6 +58,7 @@ public enum WebPermission implements Supplier<String>, Lang {

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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public NetworkJSONResolver(
) {
this.asyncJSONResolverService = asyncJSONResolverService;
resolver = CompositeResolver.builder()
.add("overview", forJSON(DataID.SERVER_OVERVIEW, networkOverviewJSONCreator, WebPermission.PAGE_NETWORK_OVERVIEW))
.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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public RootJSONResolver(
.add("kills", playerKillsJSONResolver)
.add("graph", graphsJSONResolver)
.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))
.add("serverOverview", forJSON(DataID.SERVER_OVERVIEW, serverOverviewJSONCreator, WebPermission.PAGE_NETWORK_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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ Patch[] patches() {
new CorrectWrongCharacterEncodingPatch(logger, config),
new UpdateWebPermissionsPatch(),
new WebGroupDefaultGroupsPatch(),
new WebGroupAddMissingAdminGroupPatch(),
new LegacyPermissionLevelGroupsPatch(),
new SecurityTableGroupPatch()
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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()))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ static Stream<Arguments> testCases() {
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, 200, 403),
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),
Expand All @@ -108,7 +108,7 @@ static Stream<Arguments> testCases() {
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, 200, 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),
Expand Down
5 changes: 5 additions & 0 deletions Plan/react/dashboard/src/components/modal/HelpModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ActivityIndexHelp from "./help/ActivityIndexHelp";
import {faQuestionCircle} from "@fortawesome/free-regular-svg-icons";
import NewPlayerRetentionHelp from "./help/NewPlayerRetentionHelp";
import PlayerRetentionGraphHelp from "./help/PlayerRetentionGraphHelp";
import GroupPermissionHelp from "./help/GroupPermissionHelp";

const HelpModal = () => {
const {t} = useTranslation();
Expand All @@ -25,6 +26,10 @@ const HelpModal = () => {
"player-retention-graph": {
title: t('html.label.playerRetention'),
body: <PlayerRetentionGraphHelp/>
},
"group-permissions": {
title: t('html.label.managePage.groupHeader'),
body: <GroupPermissionHelp/>
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons";
import {useMetadata} from "../../../hooks/metadataHook";

const GroupPermissionHelp = () => {
const {mainCommand} = useMetadata();
return (
<div className={"group-help"}>
<p>This view allows you to modify web group permissions.</p>
<p>User's web group is determined during <code>/{mainCommand} register</code> by
checking if Player has <code>{"plan.webgroup.{group_name}"}</code> permission. First one that is found
is used.</p>
<p>You can use <code>/{mainCommand} setgroup {"{username} {group_name}"}</code> to change permission group
after
registering (Needs <code>plan.setgroup.other</code> permission).</p>
<p><FontAwesomeIcon icon={faExclamationTriangle}/> If you ever accidentally delete all groups
with <i>manage.groups</i> permission just <code>/{mainCommand} reload</code>.</p>
<h3>Permission inheritance</h3>
<p>Permissions follow inheritance model, where higher level permission grants all lower ones,
eg. <i>page.network</i> also gives <i>page.network.overview</i>, etc.</p>
<ul>
<li>
If given <i>page.network</i> the user can see everything on the network page.
</li>
<li>
If given <i>page.network.overview.graphs.online</i>, the user can
see just the Players Online Graph on Network Overview,
even if they don't have <i>page.network.overview</i> permission.
</li>
</ul>
<h3>Access vs Page -permissions</h3>
<p>You need to assign both access and page permissions for users.</p>
<ul>
<li>
<i>access</i> permissions allow user make the request to specific address,
eg. <i>access.network</i> allows request to /network.
</li>
<li>
<i>page</i> permissions determine what parts of the page are visible,
eg. <i>page.network.overview</i> allows viewing Network Overview.
These permissions also limit requests to the related data,
eg. <i>page.network.overview.numbers</i> allows request to /v1/network/overview.
</li>
<li>
<i>access</i> permissions are not required for data: <i>page.network.overview.numbers</i> allows
request to /v1/network/overview even without <i>access.network</i>.
</li>
</ul>
<h3>Adding or deleting groups</h3>
<p>Group names can be 100 characters long, and the Add group -form forces them to correct format. Only use
characters supported by your permission system so that it can
check <code>{"plan.webgroup.{group_name}"}</code> permission.</p>
<p>When you delete a group you need to choose where users of that group are moved. There is no undo button,
so choose carefully.</p>
<h3>Saving changes</h3>
<p>Group permissions are stored in the database.</p>
<p>When you add a group or delete a group that action is saved right away.</p>
<p>When you modify permissions those changes need to be saved by pressing the Save-button</p>
</div>
)
};

export default GroupPermissionHelp
4 changes: 4 additions & 0 deletions Plan/react/dashboard/src/style/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1474,3 +1474,7 @@ ul.filters {
width: 0
}
}

.group-help i {
color: var(--color-plan)
}
6 changes: 5 additions & 1 deletion Plan/react/dashboard/src/util/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ const createNightModeColorCss = () => {
export const createNightModeCss = () => {
return `#content-wrapper {background-color:var(--color-night-black)!important;}` +
`#wrapper {background-image: linear-gradient(to right, var(--color-night-dark-blue) 0%, var(--color-night-dark-blue) 14rem, var(--color-night-black) 14.01rem, var(--color-night-black) 100%);}` +
`body,.btn,.bg-transparent-light {color: var(--color-night-text-dark-bg);}` +
`body,.btn,.bg-transparent-light {color: var(--color-night-text-dark-bg) !important;}` +
`.card,.bg-white,.modal-content,.page-loader,.nav-tabs .nav-link:hover,.nav-tabs,hr,form .btn, .btn-outline-secondary{background-color:var(--color-night-dark-blue)!important;border-color:var(--color-night-blue)!important;}` +
`.bg-white.collapse-inner {border:1px solid;}` +
`.card-header {background-color:var(--color-night-dark-blue);border-color:var(--color-night-blue);}` +
Expand All @@ -284,5 +284,9 @@ export const createNightModeCss = () => {
`.fc a{color:var(--color-night-text-dark-bg) !important;}` +
`.fc-button{ background-color: ${withReducedSaturation(colorMap.PLAN.hex)} !important;}` +
`.loader{border: 4px solid var(--color-plan); background-color: var(--color-plan);}` +
`.dropdown-item,.dropdown-header{color: var(--color-night-text-dark-bg) !important;}` +
`.dropdown-item:hover{background-color: var(--color-night-blue) !important;}` +
`.dropdown-menu{border-color:var(--color-night-blue);color: var(--color-night-blue) !important;}` +
`:root {--bs-heading-color:var(--color-night-text-dark-bg); --bs-card-color:var(--color-night-text-dark-bg); --bs-body-color:var(--color-night-text-dark-bg); --bs-body-bg:var(--color-night-dark-grey-blue); --bs-btn-active-border-color:var(--color-night-blue);}` +
createNightModeColorCss()
}
2 changes: 1 addition & 1 deletion Plan/react/dashboard/src/views/SwaggerView.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const SwaggerView = () => {
url: baseAddress + "/docs/swagger.json"
});
}
}, []);
}, [seeDocs]);

return (
<>
Expand Down
8 changes: 8 additions & 0 deletions Plan/react/dashboard/src/views/manage/GroupsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import Scrollable from "../../components/Scrollable";
import OpaqueText from "../../components/layout/OpaqueText";
import {useAlertPopupContext} from "../../hooks/context/alertPopupContext";
import {DropdownStatusContextProvider, useDropdownStatusContext} from "../../hooks/context/dropdownStatusContextHook";
import {useNavigation} from "../../hooks/navigationHook";
import {faQuestionCircle} from "@fortawesome/free-regular-svg-icons";

const GroupsHeader = ({groupName, icon}) => {
return (
Expand Down Expand Up @@ -328,6 +330,8 @@ const AddGroupBody = ({groups, reloadGroupNames}) => {

const GroupsCard = ({groups, reloadGroupNames}) => {
const {t} = useTranslation();
const {setHelpModalTopic} = useNavigation();
const openHelp = useCallback(() => setHelpModalTopic('group-permissions'), [setHelpModalTopic]);

const slices = groups.map(group => {
return {
Expand All @@ -350,6 +354,10 @@ const GroupsCard = ({groups, reloadGroupNames}) => {
return (
<Card>
<CardHeader icon={faUsersGear} color="theme" label={t('html.label.managePage.groupHeader')}>
<button className={"btn bg-transparent col-blue"} style={{margin: "-0.5rem"}}
onClick={openHelp}>
<Fa icon={faQuestionCircle}/>
</button>
<UnsavedChangesText/>
<SaveButton/>
<DiscardButton/>
Expand Down
2 changes: 1 addition & 1 deletion Plan/react/dashboard/src/views/network/NetworkOverview.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const RecentPlayersCard = ({data}) => {

const NetworkOverview = () => {
const {hasPermission, hasChildPermission} = useAuth();
const seeOverview = hasPermission('page.network.overview');
const seeOverview = hasPermission('page.network.overview.numbers');
const seeGraphs = hasChildPermission('page.network.overview.graphs');
const {data, loadingError} = useDataRequest(fetchNetworkOverview, [], seeOverview)

Expand Down
2 changes: 1 addition & 1 deletion Plan/react/dashboard/src/views/server/ServerOverview.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const ServerOverview = () => {
const {hasPermission} = useAuth();
const {identifier} = useParams();

const seeOverview = hasPermission('page.server.overview');
const seeOverview = hasPermission('page.server.overview.numbers');
const seeOnlineGraph = hasPermission('page.server.overview.players.online.graph')
const {data, loadingError} = useDataRequest(
fetchServerOverview,
Expand Down

0 comments on commit 04a5289

Please sign in to comment.