mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-09-07 04:18:26 +02:00
Playerbase overview numbers to network page
This commit is contained in:
parent
d2c70186ec
commit
986a753918
@ -0,0 +1,415 @@
|
|||||||
|
/*
|
||||||
|
* 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.db.access.queries.analysis;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
|
||||||
|
import com.djrapitops.plan.db.access.Query;
|
||||||
|
import com.djrapitops.plan.db.access.QueryStatement;
|
||||||
|
import com.djrapitops.plan.db.sql.tables.SessionsTable;
|
||||||
|
import com.djrapitops.plan.db.sql.tables.UsersTable;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static com.djrapitops.plan.db.sql.parsing.Sql.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries for Activity Index that attempts to gain insight into player activity levels.
|
||||||
|
* <p>
|
||||||
|
* Old formula for activity index was not linear and difficult to turn into a query due to conditional multipliers.
|
||||||
|
* Thus a new formula was written.
|
||||||
|
* <p>
|
||||||
|
* {@code T} - Time played after someone is considered active on a particular week
|
||||||
|
* {@code t1, t2, t3} - Time played that week
|
||||||
|
* <p>
|
||||||
|
* Activity index takes into account last 3 weeks.
|
||||||
|
* <p>
|
||||||
|
* Activity for a single week is calculated using {@code A(t) = (1 / (pi/2 * (t/T) + 1))}.
|
||||||
|
* A(t) is based on function f(x) = 1 / (x + 1), which has property f(0) = 1, decreasing from there, but not in a straight line.
|
||||||
|
* You can see the function plotted here https://www.wolframalpha.com/input/?i=1+%2F+(x%2B1)+from+-1+to+2
|
||||||
|
* <p>
|
||||||
|
* To fine tune the curve pi/2 is used since it felt like a good curve.
|
||||||
|
* <p>
|
||||||
|
* Activity index A is calculated by using the formula:
|
||||||
|
* {@code A = 5 - 5 * [A(t1) + A(t2) + A(t3)] / 3}
|
||||||
|
* <p>
|
||||||
|
* Plot for A and limits
|
||||||
|
* https://www.wolframalpha.com/input/?i=plot+y+%3D+5+-+5+*+(1+%2F+(pi%2F2+*+x%2B1))+and+y+%3D1+and+y+%3D+2+and+y+%3D+3+and+y+%3D+3.75+from+-0.5+to+3
|
||||||
|
* <p>
|
||||||
|
* New Limits for A would thus be
|
||||||
|
* {@code < 1: Inactive}
|
||||||
|
* {@code > 1: Irregular}
|
||||||
|
* {@code > 2: Regular}
|
||||||
|
* {@code > 3: Active}
|
||||||
|
* {@code > 3.75: Very Active}
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
public class NetworkActivityIndexQueries {
|
||||||
|
|
||||||
|
private NetworkActivityIndexQueries() {
|
||||||
|
// Static method class
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<Integer> fetchRegularPlayerCount(long date, long playtimeThreshold) {
|
||||||
|
return fetchActivityGroupCount(date, playtimeThreshold, ActivityIndex.REGULAR, 5.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String selectActivityIndexSQL() {
|
||||||
|
String selectActivePlaytimeSQL = SELECT +
|
||||||
|
SessionsTable.USER_UUID +
|
||||||
|
",SUM(" +
|
||||||
|
SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME +
|
||||||
|
") as active_playtime" +
|
||||||
|
FROM + SessionsTable.TABLE_NAME +
|
||||||
|
WHERE + SessionsTable.SESSION_START + ">=?" +
|
||||||
|
AND + SessionsTable.SESSION_END + "<=?" +
|
||||||
|
GROUP_BY + SessionsTable.USER_UUID;
|
||||||
|
|
||||||
|
String selectThreeWeeks = selectActivePlaytimeSQL + UNION + selectActivePlaytimeSQL + UNION + selectActivePlaytimeSQL;
|
||||||
|
|
||||||
|
return SELECT +
|
||||||
|
"5.0 - 5.0 * AVG(1 / (?/2 * (q1.active_playtime/?) +1)) as activity_index," +
|
||||||
|
"q1." + SessionsTable.USER_UUID +
|
||||||
|
FROM + '(' + selectThreeWeeks + ") q1" +
|
||||||
|
GROUP_BY + "q1." + SessionsTable.USER_UUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setSelectActivityIndexSQLParameters(PreparedStatement statement, int index, long playtimeThreshold, long date) throws SQLException {
|
||||||
|
statement.setDouble(index, Math.PI);
|
||||||
|
statement.setLong(index + 1, playtimeThreshold);
|
||||||
|
|
||||||
|
statement.setLong(index + 2, date - TimeUnit.DAYS.toMillis(7L));
|
||||||
|
statement.setLong(index + 3, date);
|
||||||
|
statement.setLong(index + 4, date - TimeUnit.DAYS.toMillis(14L));
|
||||||
|
statement.setLong(index + 5, date - TimeUnit.DAYS.toMillis(7L));
|
||||||
|
statement.setLong(index + 6, date - TimeUnit.DAYS.toMillis(21L));
|
||||||
|
statement.setLong(index + 7, date - TimeUnit.DAYS.toMillis(14L));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<Integer> fetchActivityGroupCount(long date, long playtimeThreshold, double above, double below) {
|
||||||
|
String selectActivityIndex = selectActivityIndexSQL();
|
||||||
|
|
||||||
|
String selectIndexes = SELECT + "COALESCE(activity_index, 0) as activity_index" +
|
||||||
|
FROM + UsersTable.TABLE_NAME + " u" +
|
||||||
|
LEFT_JOIN + '(' + selectActivityIndex + ") q2 on q2." + SessionsTable.USER_UUID + "=u." + UsersTable.USER_UUID +
|
||||||
|
WHERE + "u." + UsersTable.REGISTERED + "<=?";
|
||||||
|
|
||||||
|
String selectActivePlayerCount = SELECT + "COUNT(1) as count" +
|
||||||
|
FROM + '(' + selectIndexes + ") i" +
|
||||||
|
WHERE + "i.activity_index>=?" +
|
||||||
|
AND + "i.activity_index<?";
|
||||||
|
|
||||||
|
return new QueryStatement<Integer>(selectActivePlayerCount) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 1, playtimeThreshold, date);
|
||||||
|
statement.setLong(9, date);
|
||||||
|
statement.setDouble(10, above);
|
||||||
|
statement.setDouble(11, below);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? set.getInt("count") : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<Map<String, Integer>> fetchActivityIndexGroupingsOn(long date, long threshold) {
|
||||||
|
return db -> {
|
||||||
|
Map<String, Integer> groups = new HashMap<>();
|
||||||
|
groups.put("Very Active", db.query(fetchActivityGroupCount(date, threshold, ActivityIndex.VERY_ACTIVE, 5.1)));
|
||||||
|
groups.put("Active", db.query(fetchActivityGroupCount(date, threshold, ActivityIndex.ACTIVE, ActivityIndex.VERY_ACTIVE)));
|
||||||
|
groups.put("Regular", db.query(fetchActivityGroupCount(date, threshold, ActivityIndex.REGULAR, ActivityIndex.ACTIVE)));
|
||||||
|
groups.put("Irregular", db.query(fetchActivityGroupCount(date, threshold, ActivityIndex.IRREGULAR, ActivityIndex.REGULAR)));
|
||||||
|
groups.put("Inactive", db.query(fetchActivityGroupCount(date, threshold, -0.1, ActivityIndex.IRREGULAR)));
|
||||||
|
return groups;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<Integer> countNewPlayersTurnedRegular(long after, long before, Long threshold) {
|
||||||
|
String selectActivityIndex = selectActivityIndexSQL();
|
||||||
|
|
||||||
|
String selectActivePlayerCount = SELECT + "COUNT(1) as count" +
|
||||||
|
FROM + '(' + selectActivityIndex + ") q2" +
|
||||||
|
INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.USER_UUID + "=q2." + SessionsTable.USER_UUID +
|
||||||
|
WHERE + "u." + UsersTable.REGISTERED + ">=?" +
|
||||||
|
AND + "u." + UsersTable.REGISTERED + "<=?" +
|
||||||
|
AND + "q2.activity_index>=?" +
|
||||||
|
AND + "q2.activity_index<?";
|
||||||
|
|
||||||
|
return new QueryStatement<Integer>(selectActivePlayerCount) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 1, threshold, before);
|
||||||
|
statement.setLong(9, after);
|
||||||
|
statement.setLong(10, before);
|
||||||
|
statement.setDouble(11, ActivityIndex.REGULAR);
|
||||||
|
statement.setDouble(12, 5.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? set.getInt("count") : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param start Start of the tracking, those regular will be counted here.
|
||||||
|
* @param end End of the tracking, those inactive will be count here.
|
||||||
|
* @param threshold Playtime threshold
|
||||||
|
* @return Query how many players went from regular to inactive in a span of time.
|
||||||
|
*/
|
||||||
|
public static Query<Integer> countRegularPlayersTurnedInactive(long start, long end, Long threshold) {
|
||||||
|
String selectActivityIndex = selectActivityIndexSQL();
|
||||||
|
|
||||||
|
String selectActivePlayerCount = SELECT + "COUNT(1) as count" +
|
||||||
|
FROM + '(' + selectActivityIndex + ") q2" +
|
||||||
|
// Join two select activity index queries together to query Regular and Inactive players
|
||||||
|
INNER_JOIN + '(' + selectActivityIndex.replace("q1", "q3") + ") q4" +
|
||||||
|
" on q2." + SessionsTable.USER_UUID + "=q4." + SessionsTable.USER_UUID +
|
||||||
|
WHERE + "q2.activity_index>=?" +
|
||||||
|
AND + "q2.activity_index<?" +
|
||||||
|
AND + "q4.activity_index>=?" +
|
||||||
|
AND + "q4.activity_index<?";
|
||||||
|
|
||||||
|
return new QueryStatement<Integer>(selectActivePlayerCount) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 1, threshold, end);
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 9, threshold, start);
|
||||||
|
statement.setDouble(17, ActivityIndex.REGULAR);
|
||||||
|
statement.setDouble(18, 5.1);
|
||||||
|
statement.setDouble(19, -0.1);
|
||||||
|
statement.setDouble(20, ActivityIndex.IRREGULAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? set.getInt("count") : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<Long> averagePlaytimePerRegularPlayer(long after, long before, Long threshold) {
|
||||||
|
return database -> {
|
||||||
|
// INNER JOIN limits the users to only those that are regular
|
||||||
|
String selectPlaytimePerPlayer = SELECT +
|
||||||
|
"p." + SessionsTable.USER_UUID + "," +
|
||||||
|
"SUM(p." + SessionsTable.SESSION_END + "-p." + SessionsTable.SESSION_START + ") as playtime" +
|
||||||
|
FROM + SessionsTable.TABLE_NAME + " p" +
|
||||||
|
INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_UUID + "=p." + SessionsTable.USER_UUID +
|
||||||
|
WHERE + "p." + SessionsTable.SESSION_END + "<=?" +
|
||||||
|
AND + "p." + SessionsTable.SESSION_START + ">=?" +
|
||||||
|
AND + "q2.activity_index>=?" +
|
||||||
|
AND + "q2.activity_index<?" +
|
||||||
|
GROUP_BY + "p." + SessionsTable.USER_UUID;
|
||||||
|
String selectAverage = SELECT + "AVG(playtime) as average" + FROM + '(' + selectPlaytimePerPlayer + ") q1";
|
||||||
|
|
||||||
|
return database.query(new QueryStatement<Long>(selectAverage, 100) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 1, threshold, before);
|
||||||
|
statement.setLong(9, before);
|
||||||
|
statement.setLong(10, after);
|
||||||
|
statement.setDouble(11, ActivityIndex.REGULAR);
|
||||||
|
statement.setDouble(12, 5.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? set.getLong("average") : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<Long> averageSessionLengthPerRegularPlayer(long after, long before, Long threshold) {
|
||||||
|
return database -> {
|
||||||
|
// INNER JOIN limits the users to only those that are regular
|
||||||
|
String selectSessionLengthPerPlayer = SELECT +
|
||||||
|
"p." + SessionsTable.USER_UUID + "," +
|
||||||
|
"p." + SessionsTable.SESSION_END + "-p." + SessionsTable.SESSION_START + " as length" +
|
||||||
|
FROM + SessionsTable.TABLE_NAME + " p" +
|
||||||
|
INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_UUID + "=p." + SessionsTable.USER_UUID +
|
||||||
|
WHERE + "p." + SessionsTable.SESSION_END + "<=?" +
|
||||||
|
AND + "p." + SessionsTable.SESSION_START + ">=?" +
|
||||||
|
AND + "q2.activity_index>=?" +
|
||||||
|
AND + "q2.activity_index<?";
|
||||||
|
String selectAverage = SELECT + "AVG(length) as average" + FROM + '(' + selectSessionLengthPerPlayer + ") q1";
|
||||||
|
|
||||||
|
return database.query(new QueryStatement<Long>(selectAverage, 100) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 1, threshold, before);
|
||||||
|
statement.setLong(9, before);
|
||||||
|
statement.setLong(10, after);
|
||||||
|
statement.setDouble(11, ActivityIndex.REGULAR);
|
||||||
|
statement.setDouble(12, 5.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? set.getLong("average") : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<Long> averageAFKPerRegularPlayer(long after, long before, Long threshold) {
|
||||||
|
return database -> {
|
||||||
|
// INNER JOIN limits the users to only those that are regular
|
||||||
|
String selectPlaytimePerPlayer = SELECT +
|
||||||
|
"p." + SessionsTable.USER_UUID + "," +
|
||||||
|
"SUM(p." + SessionsTable.AFK_TIME + ") as afk" +
|
||||||
|
FROM + SessionsTable.TABLE_NAME + " p" +
|
||||||
|
INNER_JOIN + '(' + selectActivityIndexSQL() + ") q2 on q2." + SessionsTable.USER_UUID + "=p." + SessionsTable.USER_UUID +
|
||||||
|
WHERE + "p." + SessionsTable.SESSION_END + "<=?" +
|
||||||
|
AND + "p." + SessionsTable.SESSION_START + ">=?" +
|
||||||
|
AND + "q2.activity_index>=?" +
|
||||||
|
AND + "q2.activity_index<?" +
|
||||||
|
GROUP_BY + "p." + SessionsTable.USER_UUID;
|
||||||
|
String selectAverage = SELECT + "AVG(afk) as average" + FROM + '(' + selectPlaytimePerPlayer + ") q1";
|
||||||
|
|
||||||
|
return database.query(new QueryStatement<Long>(selectAverage, 100) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 1, threshold, before);
|
||||||
|
statement.setLong(9, before);
|
||||||
|
statement.setLong(10, after);
|
||||||
|
statement.setDouble(11, ActivityIndex.REGULAR);
|
||||||
|
statement.setDouble(12, 5.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? set.getLong("average") : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<Collection<ActivityIndex>> activityIndexForNewPlayers(long after, long before, Long threshold) {
|
||||||
|
String selectNewUUIDs = SELECT + UsersTable.USER_UUID +
|
||||||
|
FROM + UsersTable.TABLE_NAME +
|
||||||
|
WHERE + UsersTable.REGISTERED + "<=?" +
|
||||||
|
AND + UsersTable.REGISTERED + ">=?";
|
||||||
|
|
||||||
|
String sql = SELECT + "activity_index" +
|
||||||
|
FROM + '(' + selectNewUUIDs + ") n" +
|
||||||
|
INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID;
|
||||||
|
|
||||||
|
return new QueryStatement<Collection<ActivityIndex>>(sql) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setLong(1, before);
|
||||||
|
statement.setLong(2, after);
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 3, threshold, before);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<ActivityIndex> processResults(ResultSet set) throws SQLException {
|
||||||
|
Collection<ActivityIndex> indexes = new ArrayList<>();
|
||||||
|
while (set.next()) {
|
||||||
|
indexes.add(new ActivityIndex(set.getDouble("activity_index"), before));
|
||||||
|
}
|
||||||
|
return indexes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<ActivityIndex> averageActivityIndexForRetainedPlayers(long after, long before, Long threshold) {
|
||||||
|
String selectNewUUIDs = SELECT + UsersTable.USER_UUID +
|
||||||
|
FROM + UsersTable.TABLE_NAME +
|
||||||
|
WHERE + UsersTable.REGISTERED + "<=?" +
|
||||||
|
AND + UsersTable.REGISTERED + ">=?";
|
||||||
|
|
||||||
|
String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_UUID +
|
||||||
|
FROM + SessionsTable.TABLE_NAME +
|
||||||
|
WHERE + SessionsTable.SESSION_START + ">=?" +
|
||||||
|
AND + SessionsTable.SESSION_END + "<=?";
|
||||||
|
|
||||||
|
String sql = SELECT + "AVG(activity_index) as average" +
|
||||||
|
FROM + '(' + selectNewUUIDs + ") n" +
|
||||||
|
INNER_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_UUID + "=u." + SessionsTable.USER_UUID +
|
||||||
|
INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID;
|
||||||
|
|
||||||
|
return new QueryStatement<ActivityIndex>(sql) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setLong(1, before);
|
||||||
|
statement.setLong(2, after);
|
||||||
|
|
||||||
|
// Have played in the last half of the time frame
|
||||||
|
long half = before - (after - before) / 2;
|
||||||
|
statement.setLong(3, half);
|
||||||
|
statement.setLong(4, before);
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 5, threshold, before);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActivityIndex processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? new ActivityIndex(set.getDouble("average"), before) : new ActivityIndex(0.0, before);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<ActivityIndex> averageActivityIndexForNonRetainedPlayers(long after, long before, Long threshold) {
|
||||||
|
String selectNewUUIDs = SELECT + UsersTable.USER_UUID +
|
||||||
|
FROM + UsersTable.TABLE_NAME +
|
||||||
|
WHERE + UsersTable.REGISTERED + "<=?" +
|
||||||
|
AND + UsersTable.REGISTERED + ">=?";
|
||||||
|
|
||||||
|
String selectUniqueUUIDs = SELECT + "DISTINCT " + SessionsTable.USER_UUID +
|
||||||
|
FROM + SessionsTable.TABLE_NAME +
|
||||||
|
WHERE + SessionsTable.SESSION_START + ">=?" +
|
||||||
|
AND + SessionsTable.SESSION_END + "<=?";
|
||||||
|
|
||||||
|
String sql = SELECT + "AVG(activity_index) as average" +
|
||||||
|
FROM + '(' + selectNewUUIDs + ") n" +
|
||||||
|
LEFT_JOIN + '(' + selectUniqueUUIDs + ") u on n." + SessionsTable.USER_UUID + "=u." + SessionsTable.USER_UUID +
|
||||||
|
INNER_JOIN + '(' + selectActivityIndexSQL() + ") a on n." + SessionsTable.USER_UUID + "=a." + SessionsTable.USER_UUID +
|
||||||
|
WHERE + "n." + SessionsTable.USER_UUID + IS_NULL;
|
||||||
|
|
||||||
|
return new QueryStatement<ActivityIndex>(sql) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setLong(1, before);
|
||||||
|
statement.setLong(2, after);
|
||||||
|
|
||||||
|
// Have played in the last half of the time frame
|
||||||
|
long half = before - (after - before) / 2;
|
||||||
|
statement.setLong(3, half);
|
||||||
|
statement.setLong(4, before);
|
||||||
|
setSelectActivityIndexSQLParameters(statement, 5, threshold, before);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActivityIndex processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? new ActivityIndex(set.getDouble("average"), before) : new ActivityIndex(0.0, before);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,21 @@ public class PlayerCountQueries {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static QueryStatement<Integer> queryPlayerCount(String sql, long after, long before) {
|
||||||
|
return new QueryStatement<Integer>(sql) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setLong(1, before);
|
||||||
|
statement.setLong(2, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? set.getInt("player_count") : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static Query<Integer> uniquePlayerCount(long after, long before, UUID serverUUID) {
|
public static Query<Integer> uniquePlayerCount(long after, long before, UUID serverUUID) {
|
||||||
String sql = SELECT + "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" +
|
String sql = SELECT + "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" +
|
||||||
FROM + SessionsTable.TABLE_NAME +
|
FROM + SessionsTable.TABLE_NAME +
|
||||||
@ -68,6 +83,22 @@ public class PlayerCountQueries {
|
|||||||
return queryPlayerCount(sql, after, before, serverUUID);
|
return queryPlayerCount(sql, after, before, serverUUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch uniquePlayer count for ALL servers.
|
||||||
|
*
|
||||||
|
* @param after After epoch ms
|
||||||
|
* @param before Before epoch ms
|
||||||
|
* @return Unique player count (players who played within time frame)
|
||||||
|
*/
|
||||||
|
public static Query<Integer> uniquePlayerCount(long after, long before) {
|
||||||
|
String sql = SELECT + "COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" +
|
||||||
|
FROM + SessionsTable.TABLE_NAME +
|
||||||
|
WHERE + SessionsTable.SESSION_END + "<=?" +
|
||||||
|
AND + SessionsTable.SESSION_START + ">=?";
|
||||||
|
|
||||||
|
return queryPlayerCount(sql, after, before);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a EpochMs - Count map of unique players on a server.
|
* Fetch a EpochMs - Count map of unique players on a server.
|
||||||
*
|
*
|
||||||
|
@ -523,6 +523,39 @@ public class SessionQueries {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch average playtime per ALL players.
|
||||||
|
*
|
||||||
|
* @param after After epoch ms
|
||||||
|
* @param before Before epoch ms
|
||||||
|
* @return Average ms played / player, calculated with grouped sums from sessions table.
|
||||||
|
*/
|
||||||
|
public static Query<Long> averagePlaytimePerPlayer(long after, long before) {
|
||||||
|
return database -> {
|
||||||
|
String selectPlaytimePerPlayer = SELECT +
|
||||||
|
SessionsTable.USER_UUID + "," +
|
||||||
|
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" +
|
||||||
|
FROM + SessionsTable.TABLE_NAME +
|
||||||
|
WHERE + SessionsTable.SESSION_END + "<=?" +
|
||||||
|
AND + SessionsTable.SESSION_START + ">=?" +
|
||||||
|
GROUP_BY + SessionsTable.USER_UUID;
|
||||||
|
String selectAverage = SELECT + "AVG(playtime) as average" + FROM + '(' + selectPlaytimePerPlayer + ") q1";
|
||||||
|
|
||||||
|
return database.query(new QueryStatement<Long>(selectAverage, 100) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setLong(1, before);
|
||||||
|
statement.setLong(2, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? set.getLong("average") : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static Query<Long> averageAfkPerPlayer(long after, long before, UUID serverUUID) {
|
public static Query<Long> averageAfkPerPlayer(long after, long before, UUID serverUUID) {
|
||||||
return database -> {
|
return database -> {
|
||||||
String selectAfkPerPlayer = SELECT +
|
String selectAfkPerPlayer = SELECT +
|
||||||
@ -551,6 +584,39 @@ public class SessionQueries {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch average Afk per ALL players.
|
||||||
|
*
|
||||||
|
* @param after After epoch ms
|
||||||
|
* @param before Before epoch ms
|
||||||
|
* @return Average ms afk / player, calculated with grouped sums from sessions table.
|
||||||
|
*/
|
||||||
|
public static Query<Long> averageAfkPerPlayer(long after, long before) {
|
||||||
|
return database -> {
|
||||||
|
String selectAfkPerPlayer = SELECT +
|
||||||
|
SessionsTable.USER_UUID + "," +
|
||||||
|
"SUM(" + SessionsTable.AFK_TIME + ") as afk" +
|
||||||
|
FROM + SessionsTable.TABLE_NAME +
|
||||||
|
WHERE + SessionsTable.SESSION_END + "<=?" +
|
||||||
|
AND + SessionsTable.SESSION_START + ">=?" +
|
||||||
|
GROUP_BY + SessionsTable.USER_UUID;
|
||||||
|
String selectAverage = SELECT + "AVG(afk) as average" + FROM + '(' + selectAfkPerPlayer + ") q1";
|
||||||
|
|
||||||
|
return database.query(new QueryStatement<Long>(selectAverage, 100) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setLong(1, before);
|
||||||
|
statement.setLong(2, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long processResults(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? set.getLong("average") : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static Query<Long> afkTime(long after, long before, UUID serverUUID) {
|
public static Query<Long> afkTime(long after, long before, UUID serverUUID) {
|
||||||
String sql = SELECT + "SUM(" + SessionsTable.AFK_TIME + ") as afk_time" +
|
String sql = SELECT + "SUM(" + SessionsTable.AFK_TIME + ") as afk_time" +
|
||||||
FROM + SessionsTable.TABLE_NAME +
|
FROM + SessionsTable.TABLE_NAME +
|
||||||
|
@ -46,7 +46,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* @author Rsl1122
|
* @author Rsl1122
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class OnlineActivityOverviewJSONParser implements TabJSONParser<Map<String, Object>> {
|
public class OnlineActivityOverviewJSONParser implements ServerTabJSONParser<Map<String, Object>> {
|
||||||
|
|
||||||
private PlanConfig config;
|
private PlanConfig config;
|
||||||
private DBSystem dbSystem;
|
private DBSystem dbSystem;
|
||||||
|
@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* @author Rsl1122
|
* @author Rsl1122
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class PerformanceJSONParser implements TabJSONParser<Map<String, Object>> {
|
public class PerformanceJSONParser implements ServerTabJSONParser<Map<String, Object>> {
|
||||||
|
|
||||||
private final PlanConfig config;
|
private final PlanConfig config;
|
||||||
private final DBSystem dbSystem;
|
private final DBSystem dbSystem;
|
||||||
|
@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* @author Rsl1122
|
* @author Rsl1122
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class PlayerBaseOverviewJSONParser implements TabJSONParser<Map<String, Object>> {
|
public class PlayerBaseOverviewJSONParser implements ServerTabJSONParser<Map<String, Object>> {
|
||||||
|
|
||||||
private PlanConfig config;
|
private PlanConfig config;
|
||||||
private DBSystem dbSystem;
|
private DBSystem dbSystem;
|
||||||
|
@ -33,7 +33,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* @author Rsl1122
|
* @author Rsl1122
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class PvPPvEJSONParser implements TabJSONParser<Map<String, Object>> {
|
public class PvPPvEJSONParser implements ServerTabJSONParser<Map<String, Object>> {
|
||||||
|
|
||||||
private DBSystem dbSystem;
|
private DBSystem dbSystem;
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* @author Rsl1122
|
* @author Rsl1122
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ServerOverviewJSONParser implements TabJSONParser<Map<String, Object>> {
|
public class ServerOverviewJSONParser implements ServerTabJSONParser<Map<String, Object>> {
|
||||||
|
|
||||||
private final Formatter<Long> day;
|
private final Formatter<Long> day;
|
||||||
private PlanConfig config;
|
private PlanConfig config;
|
||||||
|
@ -24,7 +24,7 @@ import java.util.function.Function;
|
|||||||
*
|
*
|
||||||
* @author Rsl1122
|
* @author Rsl1122
|
||||||
*/
|
*/
|
||||||
public interface TabJSONParser<T> extends Function<UUID, T> {
|
public interface ServerTabJSONParser<T> extends Function<UUID, T> {
|
||||||
|
|
||||||
T createJSONAsMap(UUID serverUUID);
|
T createJSONAsMap(UUID serverUUID);
|
||||||
|
|
@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* @author Rsl1122
|
* @author Rsl1122
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class SessionsOverviewJSONParser implements TabJSONParser<Map<String, Object>> {
|
public class SessionsOverviewJSONParser implements ServerTabJSONParser<Map<String, Object>> {
|
||||||
|
|
||||||
private DBSystem dbSystem;
|
private DBSystem dbSystem;
|
||||||
|
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* 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.system.json.network;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.db.Database;
|
||||||
|
import com.djrapitops.plan.db.access.queries.analysis.NetworkActivityIndexQueries;
|
||||||
|
import com.djrapitops.plan.db.access.queries.analysis.PlayerCountQueries;
|
||||||
|
import com.djrapitops.plan.db.access.queries.objects.SessionQueries;
|
||||||
|
import com.djrapitops.plan.system.database.DBSystem;
|
||||||
|
import com.djrapitops.plan.system.json.Trend;
|
||||||
|
import com.djrapitops.plan.system.settings.config.PlanConfig;
|
||||||
|
import com.djrapitops.plan.system.settings.paths.TimeSettings;
|
||||||
|
import com.djrapitops.plan.utilities.formatting.Formatter;
|
||||||
|
import com.djrapitops.plan.utilities.formatting.Formatters;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses JSON payload for /server-page Playerbase Overview tab.
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class NetworkPlayerBaseOverviewJSONParser implements NetworkTabJSONParser<Map<String, Object>> {
|
||||||
|
|
||||||
|
private PlanConfig config;
|
||||||
|
private DBSystem dbSystem;
|
||||||
|
|
||||||
|
private Formatter<Long> timeAmount;
|
||||||
|
private Formatter<Double> percentage;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public NetworkPlayerBaseOverviewJSONParser(
|
||||||
|
PlanConfig config,
|
||||||
|
DBSystem dbSystem,
|
||||||
|
Formatters formatters
|
||||||
|
) {
|
||||||
|
this.config = config;
|
||||||
|
this.dbSystem = dbSystem;
|
||||||
|
|
||||||
|
timeAmount = formatters.timeAmount();
|
||||||
|
percentage = formatters.percentage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> createJSONAsMap() {
|
||||||
|
Map<String, Object> serverOverview = new HashMap<>();
|
||||||
|
serverOverview.put("trends", createTrendsMap());
|
||||||
|
serverOverview.put("insights", createInsightsMap());
|
||||||
|
return serverOverview;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> createTrendsMap() {
|
||||||
|
Database db = dbSystem.getDatabase();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long monthAgo = now - TimeUnit.DAYS.toMillis(30L);
|
||||||
|
long twoMonthsAgo = now - TimeUnit.DAYS.toMillis(60L);
|
||||||
|
Long playThreshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD);
|
||||||
|
|
||||||
|
Map<String, Object> trends = new HashMap<>();
|
||||||
|
|
||||||
|
Integer playersBefore = db.query(PlayerCountQueries.uniquePlayerCount(0L, monthAgo));
|
||||||
|
Integer playersAfter = db.query(PlayerCountQueries.uniquePlayerCount(0L, now));
|
||||||
|
trends.put("total_players_then", playersBefore);
|
||||||
|
trends.put("total_players_now", playersAfter);
|
||||||
|
trends.put("total_players_trend", new Trend(playersBefore, playersAfter, false));
|
||||||
|
|
||||||
|
Integer regularBefore = db.query(NetworkActivityIndexQueries.fetchRegularPlayerCount(monthAgo, playThreshold));
|
||||||
|
Integer regularAfter = db.query(NetworkActivityIndexQueries.fetchRegularPlayerCount(now, playThreshold));
|
||||||
|
trends.put("regular_players_then", regularBefore);
|
||||||
|
trends.put("regular_players_now", regularAfter);
|
||||||
|
trends.put("regular_players_trend", new Trend(regularBefore, regularAfter, false));
|
||||||
|
|
||||||
|
Long avgPlaytimeBefore = db.query(SessionQueries.averagePlaytimePerPlayer(twoMonthsAgo, monthAgo));
|
||||||
|
Long avgPlaytimeAfter = db.query(SessionQueries.averagePlaytimePerPlayer(monthAgo, now));
|
||||||
|
trends.put("playtime_avg_then", timeAmount.apply(avgPlaytimeBefore));
|
||||||
|
trends.put("playtime_avg_now", timeAmount.apply(avgPlaytimeAfter));
|
||||||
|
trends.put("playtime_avg_trend", new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false, timeAmount));
|
||||||
|
|
||||||
|
Long avgAfkBefore = db.query(SessionQueries.averageAfkPerPlayer(twoMonthsAgo, monthAgo));
|
||||||
|
Long avgAfkAfter = db.query(SessionQueries.averageAfkPerPlayer(monthAgo, now));
|
||||||
|
double afkPercBefore = avgPlaytimeBefore != 0 ? (double) avgAfkBefore / avgPlaytimeBefore : 0;
|
||||||
|
double afkPercAfter = avgPlaytimeAfter != 0 ? (double) avgAfkAfter / avgPlaytimeAfter : 0;
|
||||||
|
trends.put("afk_then", percentage.apply(afkPercBefore));
|
||||||
|
trends.put("afk_now", percentage.apply(afkPercAfter));
|
||||||
|
trends.put("afk_trend", new Trend(afkPercBefore, afkPercAfter, Trend.REVERSED, percentage));
|
||||||
|
|
||||||
|
Long avgRegularPlaytimeBefore = db.query(NetworkActivityIndexQueries.averagePlaytimePerRegularPlayer(twoMonthsAgo, monthAgo, playThreshold));
|
||||||
|
Long avgRegularPlaytimeAfter = db.query(NetworkActivityIndexQueries.averagePlaytimePerRegularPlayer(monthAgo, now, playThreshold));
|
||||||
|
trends.put("regular_playtime_avg_then", timeAmount.apply(avgRegularPlaytimeBefore));
|
||||||
|
trends.put("regular_playtime_avg_now", timeAmount.apply(avgRegularPlaytimeAfter));
|
||||||
|
trends.put("regular_playtime_avg_trend", new Trend(avgRegularPlaytimeBefore, avgRegularPlaytimeAfter, false, timeAmount));
|
||||||
|
|
||||||
|
Long avgRegularSessionLengthBefore = db.query(NetworkActivityIndexQueries.averageSessionLengthPerRegularPlayer(twoMonthsAgo, monthAgo, playThreshold));
|
||||||
|
Long avgRegularSessionLengthAfter = db.query(NetworkActivityIndexQueries.averageSessionLengthPerRegularPlayer(monthAgo, now, playThreshold));
|
||||||
|
trends.put("regular_session_avg_then", timeAmount.apply(avgRegularSessionLengthBefore));
|
||||||
|
trends.put("regular_session_avg_now", timeAmount.apply(avgRegularSessionLengthAfter));
|
||||||
|
trends.put("regular_session_avg_trend", new Trend(avgRegularSessionLengthBefore, avgRegularSessionLengthAfter, false, timeAmount));
|
||||||
|
|
||||||
|
Long avgRegularAfkBefore = db.query(NetworkActivityIndexQueries.averageAFKPerRegularPlayer(twoMonthsAgo, monthAgo, playThreshold));
|
||||||
|
Long avgRegularAfkAfter = db.query(NetworkActivityIndexQueries.averageAFKPerRegularPlayer(monthAgo, now, playThreshold));
|
||||||
|
double afkRegularPercBefore = avgRegularPlaytimeBefore != 0 ? (double) avgRegularAfkBefore / avgRegularPlaytimeBefore : 0;
|
||||||
|
double afkRegularPercAfter = avgRegularPlaytimeAfter != 0 ? (double) avgRegularAfkAfter / avgRegularPlaytimeAfter : 0;
|
||||||
|
trends.put("regular_afk_avg_then", percentage.apply(afkRegularPercBefore));
|
||||||
|
trends.put("regular_afk_avg_now", percentage.apply(afkRegularPercAfter));
|
||||||
|
trends.put("regular_afk_avg_trend", new Trend(afkRegularPercBefore, afkRegularPercAfter, Trend.REVERSED, percentage));
|
||||||
|
|
||||||
|
return trends;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> createInsightsMap() {
|
||||||
|
Database db = dbSystem.getDatabase();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long halfMonthAgo = now - TimeUnit.DAYS.toMillis(30L);
|
||||||
|
long monthAgo = now - TimeUnit.DAYS.toMillis(30L);
|
||||||
|
Long playThreshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD);
|
||||||
|
|
||||||
|
Map<String, Object> insights = new HashMap<>();
|
||||||
|
|
||||||
|
int newToRegular = db.query(NetworkActivityIndexQueries.countNewPlayersTurnedRegular(monthAgo, now, playThreshold));
|
||||||
|
Integer newToRegularBefore = db.query(NetworkActivityIndexQueries.countNewPlayersTurnedRegular(monthAgo, halfMonthAgo, playThreshold));
|
||||||
|
Integer newToRegularAfter = db.query(NetworkActivityIndexQueries.countNewPlayersTurnedRegular(halfMonthAgo, now, playThreshold));
|
||||||
|
insights.put("new_to_regular", newToRegular);
|
||||||
|
insights.put("new_to_regular_trend", new Trend(newToRegularBefore, newToRegularAfter, false));
|
||||||
|
|
||||||
|
Integer regularToInactive = db.query(NetworkActivityIndexQueries.countRegularPlayersTurnedInactive(monthAgo, now, playThreshold));
|
||||||
|
Integer regularToInactiveBefore = db.query(NetworkActivityIndexQueries.countRegularPlayersTurnedInactive(monthAgo, halfMonthAgo, playThreshold));
|
||||||
|
Integer regularToInactiveAfter = db.query(NetworkActivityIndexQueries.countRegularPlayersTurnedInactive(halfMonthAgo, now, playThreshold));
|
||||||
|
insights.put("regular_to_inactive", regularToInactive);
|
||||||
|
insights.put("regular_to_inactive_trend", new Trend(regularToInactiveBefore, regularToInactiveAfter, Trend.REVERSED));
|
||||||
|
|
||||||
|
return insights;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.system.json.network;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for different tab JSON parsers.
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
public interface NetworkTabJSONParser<T> extends Supplier<T> {
|
||||||
|
|
||||||
|
T createJSONAsMap();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default T get() {
|
||||||
|
return createJSONAsMap();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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.system.webserver.pages.json;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.api.exceptions.WebUserAuthException;
|
||||||
|
import com.djrapitops.plan.api.exceptions.connection.WebException;
|
||||||
|
import com.djrapitops.plan.system.json.network.NetworkTabJSONParser;
|
||||||
|
import com.djrapitops.plan.system.webserver.Request;
|
||||||
|
import com.djrapitops.plan.system.webserver.RequestTarget;
|
||||||
|
import com.djrapitops.plan.system.webserver.auth.Authentication;
|
||||||
|
import com.djrapitops.plan.system.webserver.pages.PageHandler;
|
||||||
|
import com.djrapitops.plan.system.webserver.response.Response;
|
||||||
|
import com.djrapitops.plan.system.webserver.response.data.JSONResponse;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic Tab JSON handler for any tab's data.
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
public class NetworkTabJSONHandler<T> implements PageHandler {
|
||||||
|
|
||||||
|
private final Supplier<T> jsonParser;
|
||||||
|
|
||||||
|
public NetworkTabJSONHandler(NetworkTabJSONParser<T> jsonParser) {
|
||||||
|
this.jsonParser = jsonParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getResponse(Request request, RequestTarget target) throws WebException {
|
||||||
|
return new JSONResponse(jsonParser.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthorized(Authentication auth, RequestTarget target) throws WebUserAuthException {
|
||||||
|
return auth.getWebUser().getPermLevel() <= 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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.system.webserver.pages.json;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.api.exceptions.WebUserAuthException;
|
||||||
|
import com.djrapitops.plan.system.json.network.NetworkPlayerBaseOverviewJSONParser;
|
||||||
|
import com.djrapitops.plan.system.json.network.NetworkTabJSONParser;
|
||||||
|
import com.djrapitops.plan.system.webserver.RequestTarget;
|
||||||
|
import com.djrapitops.plan.system.webserver.auth.Authentication;
|
||||||
|
import com.djrapitops.plan.system.webserver.pages.TreePageHandler;
|
||||||
|
import com.djrapitops.plan.system.webserver.response.ResponseFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root handler for different JSON end points.
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class NeworkJSONHandler extends TreePageHandler {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public NeworkJSONHandler(
|
||||||
|
ResponseFactory responseFactory,
|
||||||
|
NetworkPlayerBaseOverviewJSONParser playerBaseOverviewJSONParser
|
||||||
|
) {
|
||||||
|
super(responseFactory);
|
||||||
|
|
||||||
|
registerPage("playerbaseOverview", playerBaseOverviewJSONParser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void registerPage(String identifier, NetworkTabJSONParser<T> tabJSONParser) {
|
||||||
|
registerPage(identifier, new NetworkTabJSONHandler<>(tabJSONParser));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthorized(Authentication auth, RequestTarget target) throws WebUserAuthException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -41,7 +41,6 @@ public class RootJSONHandler extends TreePageHandler {
|
|||||||
public RootJSONHandler(
|
public RootJSONHandler(
|
||||||
ResponseFactory responseFactory,
|
ResponseFactory responseFactory,
|
||||||
Identifiers identifiers,
|
Identifiers identifiers,
|
||||||
JSONFactory jsonFactory,
|
|
||||||
GraphsJSONHandler graphsJSONHandler,
|
GraphsJSONHandler graphsJSONHandler,
|
||||||
SessionsJSONHandler sessionsJSONHandler,
|
SessionsJSONHandler sessionsJSONHandler,
|
||||||
PlayersTableJSONHandler playersTableJSONHandler,
|
PlayersTableJSONHandler playersTableJSONHandler,
|
||||||
@ -53,7 +52,8 @@ public class RootJSONHandler extends TreePageHandler {
|
|||||||
PvPPvEJSONParser pvPPvEJSONParser,
|
PvPPvEJSONParser pvPPvEJSONParser,
|
||||||
PlayerBaseOverviewJSONParser playerBaseOverviewJSONParser,
|
PlayerBaseOverviewJSONParser playerBaseOverviewJSONParser,
|
||||||
PerformanceJSONParser performanceJSONParser,
|
PerformanceJSONParser performanceJSONParser,
|
||||||
PlayerJSONHandler playerJSONHandler
|
PlayerJSONHandler playerJSONHandler,
|
||||||
|
NeworkJSONHandler neworkJSONHandler
|
||||||
) {
|
) {
|
||||||
super(responseFactory);
|
super(responseFactory);
|
||||||
|
|
||||||
@ -73,9 +73,10 @@ public class RootJSONHandler extends TreePageHandler {
|
|||||||
registerPage("performanceOverview", performanceJSONParser);
|
registerPage("performanceOverview", performanceJSONParser);
|
||||||
|
|
||||||
registerPage("player", playerJSONHandler);
|
registerPage("player", playerJSONHandler);
|
||||||
|
registerPage("network", neworkJSONHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> void registerPage(String identifier, TabJSONParser<T> tabJSONParser) {
|
private <T> void registerPage(String identifier, ServerTabJSONParser<T> tabJSONParser) {
|
||||||
registerPage(identifier, new ServerTabJSONHandler<>(identifiers, tabJSONParser));
|
registerPage(identifier, new ServerTabJSONHandler<>(identifiers, tabJSONParser));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ package com.djrapitops.plan.system.webserver.pages.json;
|
|||||||
import com.djrapitops.plan.api.exceptions.WebUserAuthException;
|
import com.djrapitops.plan.api.exceptions.WebUserAuthException;
|
||||||
import com.djrapitops.plan.api.exceptions.connection.WebException;
|
import com.djrapitops.plan.api.exceptions.connection.WebException;
|
||||||
import com.djrapitops.plan.system.Identifiers;
|
import com.djrapitops.plan.system.Identifiers;
|
||||||
import com.djrapitops.plan.system.json.TabJSONParser;
|
import com.djrapitops.plan.system.json.ServerTabJSONParser;
|
||||||
import com.djrapitops.plan.system.webserver.Request;
|
import com.djrapitops.plan.system.webserver.Request;
|
||||||
import com.djrapitops.plan.system.webserver.RequestTarget;
|
import com.djrapitops.plan.system.webserver.RequestTarget;
|
||||||
import com.djrapitops.plan.system.webserver.auth.Authentication;
|
import com.djrapitops.plan.system.webserver.auth.Authentication;
|
||||||
@ -40,7 +40,7 @@ public class ServerTabJSONHandler<T> implements PageHandler {
|
|||||||
private final Identifiers identifiers;
|
private final Identifiers identifiers;
|
||||||
private final Function<UUID, T> jsonParser;
|
private final Function<UUID, T> jsonParser;
|
||||||
|
|
||||||
public ServerTabJSONHandler(Identifiers identifiers, TabJSONParser<T> jsonParser) {
|
public ServerTabJSONHandler(Identifiers identifiers, ServerTabJSONParser<T> jsonParser) {
|
||||||
this.identifiers = identifiers;
|
this.identifiers = identifiers;
|
||||||
this.jsonParser = jsonParser;
|
this.jsonParser = jsonParser;
|
||||||
}
|
}
|
||||||
|
@ -17,18 +17,20 @@
|
|||||||
package com.djrapitops.plan.utilities.html.pages;
|
package com.djrapitops.plan.utilities.html.pages;
|
||||||
|
|
||||||
import com.djrapitops.plan.api.exceptions.ParseException;
|
import com.djrapitops.plan.api.exceptions.ParseException;
|
||||||
import com.djrapitops.plan.data.store.containers.NetworkContainer;
|
import com.djrapitops.plan.extension.implementation.results.server.ExtensionServerData;
|
||||||
import com.djrapitops.plan.data.store.keys.NetworkKeys;
|
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionServerDataQuery;
|
||||||
import com.djrapitops.plan.data.store.keys.ServerKeys;
|
import com.djrapitops.plan.system.database.DBSystem;
|
||||||
import com.djrapitops.plan.system.file.PlanFiles;
|
import com.djrapitops.plan.system.file.PlanFiles;
|
||||||
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
|
import com.djrapitops.plan.system.info.server.ServerInfo;
|
||||||
|
import com.djrapitops.plan.system.settings.config.PlanConfig;
|
||||||
|
import com.djrapitops.plan.system.settings.paths.ProxySettings;
|
||||||
|
import com.djrapitops.plan.system.settings.theme.Theme;
|
||||||
|
import com.djrapitops.plan.system.settings.theme.ThemeVal;
|
||||||
import com.djrapitops.plan.system.update.VersionCheckSystem;
|
import com.djrapitops.plan.system.update.VersionCheckSystem;
|
||||||
import com.djrapitops.plan.utilities.formatting.Formatters;
|
import com.djrapitops.plan.utilities.formatting.Formatters;
|
||||||
import com.djrapitops.plan.utilities.formatting.PlaceholderReplacer;
|
import com.djrapitops.plan.utilities.formatting.PlaceholderReplacer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.djrapitops.plan.data.store.keys.NetworkKeys.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Html String parser for /network page.
|
* Html String parser for /network page.
|
||||||
@ -37,57 +39,57 @@ import static com.djrapitops.plan.data.store.keys.NetworkKeys.*;
|
|||||||
*/
|
*/
|
||||||
public class NetworkPage implements Page {
|
public class NetworkPage implements Page {
|
||||||
|
|
||||||
private final NetworkContainer networkContainer;
|
private final DBSystem dbSystem;
|
||||||
|
|
||||||
private final VersionCheckSystem versionCheckSystem;
|
private final VersionCheckSystem versionCheckSystem;
|
||||||
private final PlanFiles files;
|
private final PlanFiles files;
|
||||||
private final ServerProperties serverProperties;
|
private final PlanConfig config;
|
||||||
|
private final Theme theme;
|
||||||
|
private final ServerInfo serverInfo;
|
||||||
private final Formatters formatters;
|
private final Formatters formatters;
|
||||||
|
|
||||||
NetworkPage(
|
NetworkPage(
|
||||||
NetworkContainer networkContainer,
|
DBSystem dbSystem,
|
||||||
VersionCheckSystem versionCheckSystem,
|
VersionCheckSystem versionCheckSystem,
|
||||||
PlanFiles files,
|
PlanFiles files,
|
||||||
ServerProperties serverProperties,
|
PlanConfig config,
|
||||||
|
Theme theme,
|
||||||
|
ServerInfo serverInfo,
|
||||||
Formatters formatters
|
Formatters formatters
|
||||||
) {
|
) {
|
||||||
this.networkContainer = networkContainer;
|
this.dbSystem = dbSystem;
|
||||||
this.versionCheckSystem = versionCheckSystem;
|
this.versionCheckSystem = versionCheckSystem;
|
||||||
this.files = files;
|
this.files = files;
|
||||||
this.serverProperties = serverProperties;
|
this.config = config;
|
||||||
|
this.theme = theme;
|
||||||
|
this.serverInfo = serverInfo;
|
||||||
this.formatters = formatters;
|
this.formatters = formatters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toHtml() throws ParseException {
|
public String toHtml() throws ParseException {
|
||||||
try {
|
try {
|
||||||
networkContainer.putSupplier(NetworkKeys.PLAYERS_ONLINE, serverProperties::getOnlinePlayers);
|
PlaceholderReplacer placeholders = new PlaceholderReplacer();
|
||||||
|
|
||||||
PlaceholderReplacer placeholderReplacer = new PlaceholderReplacer();
|
placeholders.put("networkDisplayName", config.get(ProxySettings.NETWORK_NAME));
|
||||||
placeholderReplacer.addAllPlaceholdersFrom(networkContainer,
|
|
||||||
VERSION, NETWORK_NAME, TIME_ZONE,
|
|
||||||
PLAYERS_ONLINE, PLAYERS_ONLINE_SERIES, PLAYERS_TOTAL, PLAYERS_GRAPH_COLOR,
|
|
||||||
REFRESH_TIME_F, RECENT_PEAK_TIME_F, ALL_TIME_PEAK_TIME_F,
|
|
||||||
PLAYERS_ALL_TIME_PEAK, PLAYERS_RECENT_PEAK,
|
|
||||||
PLAYERS_DAY, PLAYERS_WEEK, PLAYERS_MONTH,
|
|
||||||
PLAYERS_NEW_DAY, PLAYERS_NEW_WEEK, PLAYERS_NEW_MONTH,
|
|
||||||
WORLD_MAP_SERIES, WORLD_MAP_HIGH_COLOR, WORLD_MAP_LOW_COLOR,
|
|
||||||
COUNTRY_CATEGORIES, COUNTRY_SERIES,
|
|
||||||
HEALTH_INDEX, HEALTH_NOTES,
|
|
||||||
ACTIVITY_PIE_SERIES, ACTIVITY_STACK_SERIES, ACTIVITY_STACK_CATEGORIES,
|
|
||||||
SERVERS_TAB
|
|
||||||
);
|
|
||||||
placeholderReplacer.put("update", versionCheckSystem.getUpdateHtml().orElse(""));
|
|
||||||
|
|
||||||
ServerPluginTabs serverPluginTabs = new ServerPluginTabs(networkContainer.getBungeeContainer().getValue(ServerKeys.EXTENSION_DATA).orElse(new ArrayList<>()), formatters);
|
placeholders.put("gmPieColors", theme.getValue(ThemeVal.GRAPH_GM_PIE));
|
||||||
|
placeholders.put("playersGraphColor", theme.getValue(ThemeVal.GRAPH_PLAYERS_ONLINE));
|
||||||
|
placeholders.put("timeZone", config.getTimeZoneOffsetHours());
|
||||||
|
|
||||||
String nav = serverPluginTabs.getNav();
|
placeholders.put("update", versionCheckSystem.getUpdateHtml().orElse(""));
|
||||||
String tabs = serverPluginTabs.getTabs();
|
|
||||||
|
|
||||||
placeholderReplacer.put("navPluginsTabs", nav);
|
List<ExtensionServerData> extensionData = dbSystem.getDatabase()
|
||||||
placeholderReplacer.put("tabsPlugins", tabs);
|
.query(new ExtensionServerDataQuery(serverInfo.getServerUUID()));
|
||||||
|
ServerPluginTabs pluginTabs = new ServerPluginTabs(extensionData, formatters);
|
||||||
|
|
||||||
return placeholderReplacer.apply(files.getCustomizableResourceOrDefault("web/network.html").asString());
|
String nav = pluginTabs.getNav();
|
||||||
|
String tabs = pluginTabs.getTabs();
|
||||||
|
|
||||||
|
placeholders.put("navPluginsTabs", nav);
|
||||||
|
placeholders.put("tabsPlugins", tabs);
|
||||||
|
|
||||||
|
return placeholders.apply(files.getCustomizableResourceOrDefault("web/network.html").asString());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ParseException(e);
|
throw new ParseException(e);
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,7 @@ public class PageFactory {
|
|||||||
theme.get(),
|
theme.get(),
|
||||||
versionCheckSystem.get(),
|
versionCheckSystem.get(),
|
||||||
fileSystem.get(),
|
fileSystem.get(),
|
||||||
|
dbSystem.get(),
|
||||||
formatters.get()
|
formatters.get()
|
||||||
)).orElseThrow(() -> new NotFoundException("Server not found in the database"));
|
)).orElseThrow(() -> new NotFoundException("Server not found in the database"));
|
||||||
}
|
}
|
||||||
@ -161,7 +162,7 @@ public class PageFactory {
|
|||||||
public NetworkPage networkPage() {
|
public NetworkPage networkPage() {
|
||||||
NetworkContainer networkContainer = dbSystem.get().getDatabase()
|
NetworkContainer networkContainer = dbSystem.get().getDatabase()
|
||||||
.query(ContainerFetchQueries.fetchNetworkContainer()); // Not cached, big.
|
.query(ContainerFetchQueries.fetchNetworkContainer()); // Not cached, big.
|
||||||
return new NetworkPage(networkContainer,
|
return new NetworkPage(dbSystem.get(),
|
||||||
versionCheckSystem.get(), fileSystem.get(), serverInfo.get().getServerProperties(), formatters.get());
|
versionCheckSystem.get(), fileSystem.get(), config.get(), theme.get(), serverInfo.get(), formatters.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,6 +20,9 @@ import com.djrapitops.plan.api.exceptions.ParseException;
|
|||||||
import com.djrapitops.plan.data.store.containers.DataContainer;
|
import com.djrapitops.plan.data.store.containers.DataContainer;
|
||||||
import com.djrapitops.plan.data.store.containers.RawDataContainer;
|
import com.djrapitops.plan.data.store.containers.RawDataContainer;
|
||||||
import com.djrapitops.plan.data.store.keys.AnalysisKeys;
|
import com.djrapitops.plan.data.store.keys.AnalysisKeys;
|
||||||
|
import com.djrapitops.plan.extension.implementation.results.server.ExtensionServerData;
|
||||||
|
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionServerDataQuery;
|
||||||
|
import com.djrapitops.plan.system.database.DBSystem;
|
||||||
import com.djrapitops.plan.system.file.PlanFiles;
|
import com.djrapitops.plan.system.file.PlanFiles;
|
||||||
import com.djrapitops.plan.system.info.server.Server;
|
import com.djrapitops.plan.system.info.server.Server;
|
||||||
import com.djrapitops.plan.system.settings.config.PlanConfig;
|
import com.djrapitops.plan.system.settings.config.PlanConfig;
|
||||||
@ -31,6 +34,7 @@ import com.djrapitops.plan.utilities.formatting.Formatters;
|
|||||||
import com.djrapitops.plan.utilities.formatting.PlaceholderReplacer;
|
import com.djrapitops.plan.utilities.formatting.PlaceholderReplacer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static com.djrapitops.plan.data.store.keys.AnalysisKeys.*;
|
import static com.djrapitops.plan.data.store.keys.AnalysisKeys.*;
|
||||||
|
|
||||||
@ -46,6 +50,7 @@ public class ServerPage implements Page {
|
|||||||
private Theme theme;
|
private Theme theme;
|
||||||
private final VersionCheckSystem versionCheckSystem;
|
private final VersionCheckSystem versionCheckSystem;
|
||||||
private final PlanFiles files;
|
private final PlanFiles files;
|
||||||
|
private final DBSystem dbSystem;
|
||||||
private Formatters formatters;
|
private Formatters formatters;
|
||||||
|
|
||||||
ServerPage(
|
ServerPage(
|
||||||
@ -54,6 +59,7 @@ public class ServerPage implements Page {
|
|||||||
Theme theme,
|
Theme theme,
|
||||||
VersionCheckSystem versionCheckSystem,
|
VersionCheckSystem versionCheckSystem,
|
||||||
PlanFiles files,
|
PlanFiles files,
|
||||||
|
DBSystem dbSystem,
|
||||||
Formatters formatters
|
Formatters formatters
|
||||||
) {
|
) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
@ -61,20 +67,18 @@ public class ServerPage implements Page {
|
|||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
this.versionCheckSystem = versionCheckSystem;
|
this.versionCheckSystem = versionCheckSystem;
|
||||||
this.files = files;
|
this.files = files;
|
||||||
|
this.dbSystem = dbSystem;
|
||||||
this.formatters = formatters;
|
this.formatters = formatters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toHtml() throws ParseException {
|
public String toHtml() throws ParseException {
|
||||||
PlaceholderReplacer placeholderReplacer = new PlaceholderReplacer();
|
PlaceholderReplacer placeholders = new PlaceholderReplacer();
|
||||||
|
|
||||||
placeholderReplacer.put("serverName", server.getIdentifiableName());
|
placeholders.put("serverName", server.getIdentifiableName());
|
||||||
placeholderReplacer.put("serverDisplayName", server.getName());
|
placeholders.put("serverDisplayName", server.getName());
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
DataContainer constants = new RawDataContainer();
|
DataContainer constants = new RawDataContainer();
|
||||||
constants.putRawData(AnalysisKeys.REFRESH_TIME_F, formatters.clockLong().apply(now));
|
|
||||||
constants.putRawData(AnalysisKeys.REFRESH_TIME_FULL_F, formatters.secondLong().apply(now));
|
|
||||||
constants.putRawData(AnalysisKeys.VERSION, versionCheckSystem.getCurrentVersion());
|
constants.putRawData(AnalysisKeys.VERSION, versionCheckSystem.getCurrentVersion());
|
||||||
constants.putRawData(AnalysisKeys.TIME_ZONE, config.getTimeZoneOffsetHours());
|
constants.putRawData(AnalysisKeys.TIME_ZONE, config.getTimeZoneOffsetHours());
|
||||||
|
|
||||||
@ -97,7 +101,7 @@ public class ServerPage implements Page {
|
|||||||
constants.putRawData(AnalysisKeys.MAX_PING_COLOR, theme.getValue(ThemeVal.GRAPH_MAX_PING));
|
constants.putRawData(AnalysisKeys.MAX_PING_COLOR, theme.getValue(ThemeVal.GRAPH_MAX_PING));
|
||||||
constants.putRawData(AnalysisKeys.MIN_PING_COLOR, theme.getValue(ThemeVal.GRAPH_MIN_PING));
|
constants.putRawData(AnalysisKeys.MIN_PING_COLOR, theme.getValue(ThemeVal.GRAPH_MIN_PING));
|
||||||
|
|
||||||
placeholderReplacer.addAllPlaceholdersFrom(constants,
|
placeholders.addAllPlaceholdersFrom(constants,
|
||||||
VERSION, TIME_ZONE,
|
VERSION, TIME_ZONE,
|
||||||
FIRST_DAY, TPS_MEDIUM, TPS_HIGH,
|
FIRST_DAY, TPS_MEDIUM, TPS_HIGH,
|
||||||
DISK_MEDIUM, DISK_HIGH,
|
DISK_MEDIUM, DISK_HIGH,
|
||||||
@ -110,14 +114,24 @@ public class ServerPage implements Page {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (server.isProxy()) {
|
if (server.isProxy()) {
|
||||||
placeholderReplacer.put("backButton", "<li><a title=\"to Network page\" href=\"/network\"><i class=\"material-icons\">arrow_back</i><i class=\"material-icons\">cloud</i></a></li>");
|
placeholders.put("backButton", "<li><a title=\"to Network page\" href=\"/network\"><i class=\"material-icons\">arrow_back</i><i class=\"material-icons\">cloud</i></a></li>");
|
||||||
} else {
|
} else {
|
||||||
placeholderReplacer.put("backButton", "");
|
placeholders.put("backButton", "");
|
||||||
}
|
}
|
||||||
placeholderReplacer.put("update", versionCheckSystem.getUpdateHtml().orElse(""));
|
placeholders.put("update", versionCheckSystem.getUpdateHtml().orElse(""));
|
||||||
|
|
||||||
|
List<ExtensionServerData> extensionData = dbSystem.getDatabase()
|
||||||
|
.query(new ExtensionServerDataQuery(server.getUuid()));
|
||||||
|
ServerPluginTabs pluginTabs = new ServerPluginTabs(extensionData, formatters);
|
||||||
|
|
||||||
|
String nav = pluginTabs.getNav();
|
||||||
|
String tabs = pluginTabs.getTabs();
|
||||||
|
|
||||||
|
placeholders.put("navPluginsTabs", nav);
|
||||||
|
placeholders.put("tabsPlugins", tabs);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return placeholderReplacer.apply(files.getCustomizableResourceOrDefault("web/server.html").asString());
|
return placeholders.apply(files.getCustomizableResourceOrDefault("web/server.html").asString());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ParseException(e);
|
throw new ParseException(e);
|
||||||
}
|
}
|
||||||
|
@ -269,9 +269,9 @@ function loadPlayerbaseOverviewValues(json, error) {
|
|||||||
$(element).find('#data_regular_session_avg_then').text(data.regular_session_avg_then);
|
$(element).find('#data_regular_session_avg_then').text(data.regular_session_avg_then);
|
||||||
$(element).find('#data_regular_session_avg_now').text(data.regular_session_avg_now);
|
$(element).find('#data_regular_session_avg_now').text(data.regular_session_avg_now);
|
||||||
$(element).find('#data_regular_session_avg_trend').replaceWith(trend(data.regular_session_avg_trend));
|
$(element).find('#data_regular_session_avg_trend').replaceWith(trend(data.regular_session_avg_trend));
|
||||||
$(element).find('#data_regular_afk_then').text(data.regular_afk_then);
|
$(element).find('#data_regular_afk_then').text(data.regular_afk_avg_then);
|
||||||
$(element).find('#data_regular_afk_now').text(data.regular_afk_now);
|
$(element).find('#data_regular_afk_now').text(data.regular_afk_avg_now);
|
||||||
$(element).find('#data_regular_afk_trend').replaceWith(trend(data.regular_afk_trend));
|
$(element).find('#data_regular_afk_trend').replaceWith(trend(data.regular_afk_avg_trend));
|
||||||
|
|
||||||
// Insights
|
// Insights
|
||||||
data = json.insights;
|
data = json.insights;
|
||||||
|
@ -25,7 +25,9 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="page-top">
|
<body id="page-top">
|
||||||
|
<script>
|
||||||
|
var gmPieColors = [${gmPieColors}];
|
||||||
|
</script>
|
||||||
<div class="page-loader">
|
<div class="page-loader">
|
||||||
<span class="loader"></span>
|
<span class="loader"></span>
|
||||||
<p class="loader-text">Please wait..</span>
|
<p class="loader-text">Please wait..</span>
|
||||||
@ -796,16 +798,16 @@
|
|||||||
</div> <!-- /.container-fluid -->
|
</div> <!-- /.container-fluid -->
|
||||||
</div> <!-- End of Sessions tab -->
|
</div> <!-- End of Sessions tab -->
|
||||||
<!-- Begin Playerbase Overview Tab -->
|
<!-- Begin Playerbase Overview Tab -->
|
||||||
<div class="tab">
|
<div class="tab" id="playerbase-overview">
|
||||||
<div class="container-fluid mt-4">
|
<div class="container-fluid mt-4">
|
||||||
<!-- Page Heading -->
|
<!-- Page Heading -->
|
||||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||||
<h1 class="h3 mb-0 text-gray-800"><i class="sidebar-toggler fa fa-fw fa-bars"></i>Network Name
|
<h1 class="h3 mb-0 text-gray-800"><i class="sidebar-toggler fa fa-fw fa-bars"></i>${networkDisplayName}
|
||||||
· Playerbase Overview</h1>
|
· Playerbase Overview</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- New & Unique Players Chart -->
|
<!-- Playerbase Chart -->
|
||||||
<div class="col-xl-8 col-lg-8 col-sm-12">
|
<div class="col-xl-8 col-lg-8 col-sm-12">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div
|
<div
|
||||||
@ -814,13 +816,11 @@
|
|||||||
class="fas fa-fw fa-chart-line col-amber"></i>
|
class="fas fa-fw fa-chart-line col-amber"></i>
|
||||||
Playerbase development</h6>
|
Playerbase development</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-area">
|
<div class="chart-area" id="activityStackGraph"></div>
|
||||||
<canvas id="myAreaChart2"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Calendar -->
|
<!-- Current Playerbase -->
|
||||||
<div class="col-xl-4 col-lg-4 col-sm-12">
|
<div class="col-xl-4 col-lg-4 col-sm-12">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div
|
<div
|
||||||
@ -829,15 +829,13 @@
|
|||||||
class="fa fa-fw fa-users col-amber"></i>
|
class="fa fa-fw fa-users col-amber"></i>
|
||||||
Current Playerbase</h6>
|
Current Playerbase</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-area">
|
<div class="chart-area" id="activityPie"></div>
|
||||||
<canvas id="myAreaChart2"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Online Activity as Numbers -->
|
<!-- Trends for 30 days -->
|
||||||
<div class="col-lg-8 mb-8 col-sm-12">
|
<div class="col-lg-8 mb-8 col-sm-12">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div class="card-header py-3">
|
<div class="card-header py-3">
|
||||||
@ -845,71 +843,78 @@
|
|||||||
class="fa fa-fw fa-exchange-alt col-amber"></i>
|
class="fa fa-fw fa-exchange-alt col-amber"></i>
|
||||||
Trends for 30 days</h6>
|
Trends for 30 days</h6>
|
||||||
</div>
|
</div>
|
||||||
<table class="table">
|
<table class="table" id="data_trends">
|
||||||
<thead>
|
<thead>
|
||||||
<th><i class="text-success fa fa-caret-up"></i><i
|
<tr>
|
||||||
class="text-danger fa fa-caret-down"></i>
|
<th><i class="text-success fa fa-caret-up"></i><i
|
||||||
<small>Comparing 15 days</small>
|
class="text-danger fa fa-caret-down"></i>
|
||||||
</th>
|
<small>Comparing 30d ago to Now</small>
|
||||||
<th>30 days ago</th>
|
</th>
|
||||||
<th>Now</th>
|
<th>30 days ago</th>
|
||||||
<th>Trend</th>
|
<th>Now</th>
|
||||||
|
<th>Trend</th>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="fa fa-fw fa-users col-black"></i> Total Players</td>
|
<td><i class="fa fa-fw fa-users col-black"></i> Total Players</td>
|
||||||
<td>4532</td>
|
<td id="data_total_players_then"></td>
|
||||||
<td>5043</td>
|
<td id="data_total_players_now"></td>
|
||||||
<td><span class="badge badge-success"><i class="fa fa-caret-up"></i>
|
<td><span id="data_total_players_trend"></span></td>
|
||||||
511</span></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="fa fa-fw fa-users col-amber"></i> Regular Players</td>
|
<td><i class="fa fa-fw fa-users col-amber"></i> Regular Players</td>
|
||||||
<td>467</td>
|
<td id="data_regular_players_then"></td>
|
||||||
<td>483</td>
|
<td id="data_regular_players_now"></td>
|
||||||
<td><span class="badge badge-success"><i class="fa fa-caret-up"></i>
|
<td><span id="data_regular_players_trend"></span></td>
|
||||||
16</span></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="col-teal far fa-fw fa-clock"></i> Average Playtime / Player</td>
|
<td><i class="col-green far fa-fw fa-clock"></i> Average Playtime /
|
||||||
<td>8h 5m 56s</td>
|
|
||||||
<td>8h 53m 24s</td>
|
|
||||||
<td><span class="badge badge-success"><i class="fa fa-caret-up"></i> 47m
|
|
||||||
32s</span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><i class="col-teal far fa-fw fa-clock"></i> Average Playtime / Regular
|
|
||||||
Player
|
Player
|
||||||
</td>
|
</td>
|
||||||
<td>8h 5m 56s</td>
|
<td id="data_playtime_avg_then"></td>
|
||||||
<td>8h 53m 24s</td>
|
<td id="data_playtime_avg_now"></td>
|
||||||
<td><span class="badge badge-success"><i class="fa fa-caret-up"></i> 47m
|
<td><span id="data_playtime_avg_trend"></span></td>
|
||||||
32s</span></td>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><i class="col-grey far fa-fw fa-clock"></i> AFK /
|
||||||
|
Player
|
||||||
|
</td>
|
||||||
|
<td id="data_afk_then"></td>
|
||||||
|
<td id="data_afk_now"></td>
|
||||||
|
<td><span id="data_afk_trend"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><i class="col-green far fa-fw fa-clock"></i> Average Playtime /
|
||||||
|
Regular
|
||||||
|
Player
|
||||||
|
</td>
|
||||||
|
<td id="data_regular_playtime_avg_then"></td>
|
||||||
|
<td id="data_regular_playtime_avg_now"></td>
|
||||||
|
<td><span id="data_regular_playtime_avg_trend"></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="col-teal far fa-fw fa-clock"></i> Average Session Length /
|
<td><i class="col-teal far fa-fw fa-clock"></i> Average Session Length /
|
||||||
Regular
|
Regular
|
||||||
Player
|
Player
|
||||||
</td>
|
</td>
|
||||||
<td>8h 5m 56s</td>
|
<td id="data_regular_session_avg_then"></td>
|
||||||
<td>8h 53m 24s</td>
|
<td id="data_regular_session_avg_now"></td>
|
||||||
<td><span class="badge badge-success"><i class="fa fa-caret-up"></i> 47m
|
<td><span id="data_regular_session_avg_trend"></span></td>
|
||||||
32s</span></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="col-grey far fa-fw fa-clock"></i> AFK /
|
<td><i class="col-grey far fa-fw fa-clock"></i> AFK /
|
||||||
Regular
|
Regular
|
||||||
Player
|
Player
|
||||||
</td>
|
</td>
|
||||||
<td>34.5%</td>
|
<td id="data_regular_afk_then"></td>
|
||||||
<td>42.4%</td>
|
<td id="data_regular_afk_now"></td>
|
||||||
<td><span class="badge badge-danger"><i class="fa fa-caret-up"></i>
|
<td><span id="data_regular_afk_trend"></span></td>
|
||||||
7.9%</span></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- End of Online Activity as numbers-->
|
</div> <!-- Trends for 30 days-->
|
||||||
<!-- Insights -->
|
<!-- Insights -->
|
||||||
<div class="col-lg-4 mb-4 col-sm-12">
|
<div class="col-lg-4 mb-4 col-sm-12">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
@ -918,17 +923,17 @@
|
|||||||
class="far fa-fw fa-life-ring col-red"></i>
|
class="far fa-fw fa-life-ring col-red"></i>
|
||||||
Insights for 30 Days</h6>
|
Insights for 30 Days</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body" id="data_insights">
|
||||||
<p><i class="fa fa-fw fa-user col-light-green"></i> New <i
|
<p><i class="fa fa-fw fa-user col-light-green"></i> New <i
|
||||||
class="fa fa-fw fa-long-arrow-alt-right"></i> <i
|
class="fa fa-fw fa-long-arrow-alt-right"></i> <i
|
||||||
class="fa fa-fw fa-user col-amber"></i> Regular<span
|
class="fa fa-fw fa-user col-amber"></i> Regular<span
|
||||||
class="float-right"><b>32</b> <i
|
class="float-right"><b><span
|
||||||
class="text-success fa fa-caret-up"></i></span></p>
|
id="data_new_to_regular"></span></b></span></p>
|
||||||
<p><i class="fa fa-fw fa-user col-amber"></i> Regular <i
|
<p><i class="fa fa-fw fa-user col-amber"></i> Regular <i
|
||||||
class="fa fa-fw fa-long-arrow-alt-right"></i> <i
|
class="fa fa-fw fa-long-arrow-alt-right"></i> <i
|
||||||
class="fa fa-fw fa-user col-blue-grey"></i> Inactive<span
|
class="fa fa-fw fa-user col-blue-grey"></i> Inactive<span
|
||||||
class="float-right"><b>45</b> <i
|
class="float-right"><b><span
|
||||||
class="text-danger fa fa-caret-up"></i></span></p>
|
id="data_regular_to_inactive"></span></b></span></p>
|
||||||
<p class="float-right"><i class="text-success fa fa-caret-up"></i><i
|
<p class="float-right"><i class="text-success fa fa-caret-up"></i><i
|
||||||
class="text-danger fa fa-caret-down"></i>
|
class="text-danger fa fa-caret-down"></i>
|
||||||
<small>Comparing 30d ago to Now</small>
|
<small>Comparing 30d ago to Now</small>
|
||||||
@ -1265,10 +1270,23 @@
|
|||||||
<script src="js/color-selector.js"></script>
|
<script src="js/color-selector.js"></script>
|
||||||
|
|
||||||
<!-- Page level plugins -->
|
<!-- Page level plugins -->
|
||||||
|
<script src="vendor/datatables/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="vendor/datatables/dataTables.bootstrap4.min.js"></script>
|
||||||
|
<script src="vendor/highcharts/highstock.js"></script>
|
||||||
|
<script src="vendor/highcharts/map.js"></script>
|
||||||
|
<script src="vendor/highcharts/world.js"></script>
|
||||||
|
<script src="vendor/highcharts/drilldown.js"></script>
|
||||||
|
<script src="vendor/highcharts/highcharts-more.js"></script>
|
||||||
|
<script src="vendor/highcharts/no-data-to-display.js"></script>
|
||||||
|
|
||||||
<!-- Page level custom scripts -->
|
<!-- Page level custom scripts -->
|
||||||
|
<script src="js/network-values.js"></script>
|
||||||
|
<script src="js/graphs.js"></script>
|
||||||
|
<script src="js/sessionAccordion.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
setLoadingText('Calculating values..');
|
||||||
|
jsonRequest("../v1/network/playerbaseOverview", loadPlayerbaseOverviewValues);
|
||||||
setLoadingText('Rendering graphs..');
|
setLoadingText('Rendering graphs..');
|
||||||
setLoadingText('Sorting players table..');
|
setLoadingText('Sorting players table..');
|
||||||
setLoadingText('Almost done..');
|
setLoadingText('Almost done..');
|
||||||
|
@ -115,7 +115,7 @@
|
|||||||
Plugins
|
Plugins
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<li class="plugin-nav" style="display: none;"></li>
|
${navPluginsTabs}
|
||||||
|
|
||||||
<!-- Divider -->
|
<!-- Divider -->
|
||||||
<hr class="sidebar-divider">
|
<hr class="sidebar-divider">
|
||||||
@ -723,7 +723,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- New & Unique Players Chart -->
|
<!-- Playerbase Chart -->
|
||||||
<div class="col-xl-8 col-lg-8 col-sm-12">
|
<div class="col-xl-8 col-lg-8 col-sm-12">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div
|
<div
|
||||||
@ -736,7 +736,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Calendar -->
|
<!-- Current Playerbase -->
|
||||||
<div class="col-xl-4 col-lg-4 col-sm-12">
|
<div class="col-xl-4 col-lg-4 col-sm-12">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div
|
<div
|
||||||
@ -751,7 +751,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Online Activity as Numbers -->
|
<!-- Trends for 30 days -->
|
||||||
<div class="col-lg-8 mb-8 col-sm-12">
|
<div class="col-lg-8 mb-8 col-sm-12">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div class="card-header py-3">
|
<div class="card-header py-3">
|
||||||
@ -761,13 +761,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<table class="table" id="data_trends">
|
<table class="table" id="data_trends">
|
||||||
<thead>
|
<thead>
|
||||||
<th><i class="text-success fa fa-caret-up"></i><i
|
<tr>
|
||||||
class="text-danger fa fa-caret-down"></i>
|
<th><i class="text-success fa fa-caret-up"></i><i
|
||||||
<small>Comparing 30d ago to Now</small>
|
class="text-danger fa fa-caret-down"></i>
|
||||||
</th>
|
<small>Comparing 30d ago to Now</small>
|
||||||
<th>30 days ago</th>
|
</th>
|
||||||
<th>Now</th>
|
<th>30 days ago</th>
|
||||||
<th>Trend</th>
|
<th>Now</th>
|
||||||
|
<th>Trend</th>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@ -828,7 +830,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- End of Online Activity as numbers-->
|
</div> <!-- Trends for 30 days-->
|
||||||
<!-- Insights -->
|
<!-- Insights -->
|
||||||
<div class="col-lg-4 mb-4 col-sm-12">
|
<div class="col-lg-4 mb-4 col-sm-12">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
@ -842,7 +844,7 @@
|
|||||||
class="fa fa-fw fa-long-arrow-alt-right"></i> <i
|
class="fa fa-fw fa-long-arrow-alt-right"></i> <i
|
||||||
class="fa fa-fw fa-user col-amber"></i> Regular<span
|
class="fa fa-fw fa-user col-amber"></i> Regular<span
|
||||||
class="float-right"><b><span
|
class="float-right"><b><span
|
||||||
id="data_new_to_regular"></span></b></i></span></p>
|
id="data_new_to_regular"></span></b></span></p>
|
||||||
<p><i class="fa fa-fw fa-user col-amber"></i> Regular <i
|
<p><i class="fa fa-fw fa-user col-amber"></i> Regular <i
|
||||||
class="fa fa-fw fa-long-arrow-alt-right"></i> <i
|
class="fa fa-fw fa-long-arrow-alt-right"></i> <i
|
||||||
class="fa fa-fw fa-user col-blue-grey"></i> Inactive<span
|
class="fa fa-fw fa-user col-blue-grey"></i> Inactive<span
|
||||||
@ -1158,7 +1160,7 @@
|
|||||||
</div> <!-- /.container-fluid -->
|
</div> <!-- /.container-fluid -->
|
||||||
</div> <!-- End of Performance tab -->
|
</div> <!-- End of Performance tab -->
|
||||||
<!-- Begin Plugins Overview Tab -->
|
<!-- Begin Plugins Overview Tab -->
|
||||||
<div class="plugin-tabs"></div>
|
${tabsPlugins}
|
||||||
<div class="tab"></div>
|
<div class="tab"></div>
|
||||||
<div class="tab"></div>
|
<div class="tab"></div>
|
||||||
</div> <!-- End of Main Content -->
|
</div> <!-- End of Main Content -->
|
||||||
@ -1671,19 +1673,8 @@
|
|||||||
});
|
});
|
||||||
jsonRequest("../v1/kills?server=${serverName}", loadPlayerKills);
|
jsonRequest("../v1/kills?server=${serverName}", loadPlayerKills);
|
||||||
|
|
||||||
jsonRequest("../v1/extensions?server=${serverName}", function (json, error) {
|
$('.player-plugin-table').DataTable({
|
||||||
if (json) {
|
responsive: true
|
||||||
$('.plugin-nav').replaceWith(json.navigation);
|
|
||||||
$('.plugin-tabs').replaceWith(json.content);
|
|
||||||
openPageFunc();
|
|
||||||
} else if (error) {
|
|
||||||
$('.plugin-nav').remove();
|
|
||||||
$('.plugin-tabs').remove();
|
|
||||||
}
|
|
||||||
$('.server-name').text('${serverName}');
|
|
||||||
$('.player-plugin-table').DataTable({
|
|
||||||
responsive: true
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setLoadingText('Almost done..');
|
setLoadingText('Almost done..');
|
||||||
|
Loading…
Reference in New Issue
Block a user