Joey Hines 2018-12-08 22:10:12 -06:00
commit 938ec65422
13 changed files with 275 additions and 184 deletions

View File

@ -5,6 +5,7 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from minecraft_manager.models import Application, Warning, Ticket, Player, IP, UserSettings, Alert, Note from minecraft_manager.models import Application, Warning, Ticket, Player, IP, UserSettings, Alert, Note
from minecraft_manager.api.admin import register as api_register
class PlayerInline(admin.StackedInline): class PlayerInline(admin.StackedInline):
@ -100,6 +101,7 @@ try:
admin.site.register(IP, IPAdmin) admin.site.register(IP, IPAdmin)
admin.site.register(Alert) admin.site.register(Alert)
admin.site.register(Note) admin.site.register(Note)
api_register()
except admin.sites.AlreadyRegistered: except admin.sites.AlreadyRegistered:
pass pass

45
api/admin.py 100644
View File

@ -0,0 +1,45 @@
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from minecraft_manager.api.models import Token
class TokenActiveFilter(admin.SimpleListFilter):
title = _('Active')
parameter_name = 'active'
def lookups(self, request, model_admin):
return (
('0', _('Active')),
('1', _('Inactive')),
)
def queryset(self, request, queryset):
if self.value() == '0':
return queryset.filter(active=True)
if self.value() == '1':
return queryset.filter(active=False)
class TokenAdmin(admin.ModelAdmin):
list_filter = (TokenActiveFilter,)
list_display = ('display', 'active', 'web_get_permission', 'web_post_permission', 'plugin_get_permission',
'plugin_post_permission', 'form_get_permission', 'form_post_permission', 'model_get_permission',
'model_post_permission', 'stats_get_permission', 'stats_post_permission')
fieldsets = (
(None, {
'fields': ('key', 'active', 'description')
}),
('Permissions', {
'fields': ('web_get_permission', 'web_post_permission', 'plugin_get_permission', 'plugin_post_permission',
'form_get_permission', 'form_post_permission', 'model_get_permission', 'model_post_permission',
'stats_get_permission', 'stats_post_permission')
})
)
def register():
try:
admin.site.register(Token, TokenAdmin)
except admin.sites.AlreadyRegistered:
pass

View File

@ -1,4 +1,4 @@
import socket, requests, logging, os, datetime, pytz, mcstatus, discord import socket, requests, logging, os, datetime, pytz, mcstatus, random, string
from minecraft_manager.models import Alert from minecraft_manager.models import Alert
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
@ -18,6 +18,7 @@ PLUGIN_ACCEPT = 'accept'
PLUGIN_DENY = 'deny' PLUGIN_DENY = 'deny'
PLUGIN_GLOBAL_CHAT = 'global' PLUGIN_GLOBAL_CHAT = 'global'
PLUGIN_STAFF_CHAT = 'staff' PLUGIN_STAFF_CHAT = 'staff'
PLUGIN_DEMOTE = 'demote'
def plugin(key, command): def plugin(key, command):
@ -140,3 +141,7 @@ def get_query():
except: except:
return {'max': 0, 'online': 0, return {'max': 0, 'online': 0,
'players': []} 'players': []}
def generate_password(size=20):
return "".join([random.choice(string.ascii_letters + string.digits) for idx in range(0, size)])

View File

@ -1,6 +1,7 @@
import discord, logging, re, sys, traceback, asyncio, datetime, os, time import discord, logging, re, sys, traceback, asyncio, datetime, os, time
from minecraft_manager.models import Application, Player from minecraft_manager.models import Application, Player
from minecraft_manager.api import api from minecraft_manager.api import api
from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django.db import close_old_connections from django.db import close_old_connections
from threading import Thread from threading import Thread
@ -67,7 +68,8 @@ class Discord(discord.Client):
embed.add_field(name="{}[app ]search <username>".format(self.prefix), value="Search for applications by partial or exact username.") embed.add_field(name="{}[app ]search <username>".format(self.prefix), value="Search for applications by partial or exact username.")
embed.add_field(name="{}[app ]info <app ID>".format(self.prefix), value="Get detailed information about a specific application.") embed.add_field(name="{}[app ]info <app ID>".format(self.prefix), value="Get detailed information about a specific application.")
embed.add_field(name="{}[app ]accept|deny <app ID>".format(self.prefix), value="Take action on an application.") embed.add_field(name="{}[app ]accept|deny <app ID>".format(self.prefix), value="Take action on an application.")
embed.add_field(name="{}compare".format(self.prefix), value="Compare Discord users to the Whitelist") embed.add_field(name="{}demote <username>".format(self.prefix), value="Demote a player to the role given to accepted applications.")
embed.add_field(name="{}compare".format(self.prefix), value="Compare Discord users to the Whitelist.")
yield from self.discord_message(message.channel, embed) yield from self.discord_message(message.channel, embed)
# APP COMMANDS WITH APP ID # APP COMMANDS WITH APP ID
match = re.match("[{0}](?:app )?(i|info|a|accept|d|deny) (\d+)$".format(self.prefix), message.content) match = re.match("[{0}](?:app )?(i|info|a|accept|d|deny) (\d+)$".format(self.prefix), message.content)
@ -118,6 +120,19 @@ class Discord(discord.Client):
else: else:
info = "No applications matched that search." info = "No applications matched that search."
yield from self.discord_message(message.channel, info) yield from self.discord_message(message.channel, info)
# DEMOTE A PLAYER TO MEMBER
match = re.match("[{0}]demote (\w+)$".format(self.prefix), message.content)
if match:
yield from self.delete_message(message)
username = match.group(1)
api.plugin(api.PLUGIN_DEMOTE, username)
deactivated = ""
if User.objects.filter(username__iexact=username).exists():
user = User.objects.get(username__iexact=username)
user.is_active = False
user.save()
deactivated = " and de-activated"
yield from self.discord_message(message.channel, "{} has been demoted{}.".format(username, deactivated))
# COMPARE DISCORD USERS TO WHITELIST # COMPARE DISCORD USERS TO WHITELIST
match = re.match("[{0}]compare".format(self.prefix), message.content) match = re.match("[{0}]compare".format(self.prefix), message.content)
if match: if match:

25
api/models.py 100644
View File

@ -0,0 +1,25 @@
from django.db import models
from minecraft_manager.api.api import generate_password
class Token(models.Model):
key = models.CharField("Key", default=generate_password, max_length=50, unique=True)
description = models.CharField("Description", max_length=200, blank=True)
active = models.BooleanField("Active", default=True)
web_get_permission = models.BooleanField("Web GET", default=False)
web_post_permission = models.BooleanField("Web POST", default=False)
plugin_get_permission = models.BooleanField("Plugin GET", default=False)
plugin_post_permission = models.BooleanField("Plugin POST", default=False)
form_get_permission = models.BooleanField("Form GET", default=False)
form_post_permission = models.BooleanField("Form POST", default=False)
model_get_permission = models.BooleanField("Model GET", default=False)
model_post_permission = models.BooleanField("Model POST", default=False)
stats_get_permission = models.BooleanField("Stats GET", default=False)
stats_post_permission = models.BooleanField("Stats POST", default=False)
@property
def display(self):
return self.description if self.description else self.key
def __str__(self):
return self.key

View File

@ -1,32 +1,31 @@
from __future__ import absolute_import from __future__ import absolute_import
import logging, random, string, datetime import logging, datetime
from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.http import JsonResponse, HttpResponse from django.http import JsonResponse, HttpResponse
from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.views.generic import View from django.views.generic import View
from django.forms import modelform_factory from django.forms import modelform_factory
import minecraft_manager.forms as MCMForms import minecraft_manager.forms as mcm_forms
from minecraft_manager.models import Player, UserSettings, Application, IP, Ticket, Warning from minecraft_manager.models import Player, UserSettings, Application, IP, Ticket, Warning
import minecraft_manager.api.api as mcm_api import minecraft_manager.api.api as mcm_api
from minecraft_manager.api.models import Token
import minecraft_manager.utils as mcm_utils import minecraft_manager.utils as mcm_utils
import minecraft_manager.external.stats as mcm_stats import minecraft_manager.external.stats as mcm_stats
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def request_allowed(request): def request_allowed(request, permission):
is_authenticated = False is_authenticated = False
if hasattr(request, 'user'): if hasattr(request, 'user'):
if hasattr(request.user, 'is_authenticated'): if hasattr(request.user, 'is_authenticated'):
is_authenticated = request.user.is_authenticated is_authenticated = request.user.is_authenticated
password = getattr(settings, 'API_PASSWORD', None)
get = request.GET get = request.GET
post = request.POST post = request.POST
request_password = None request_password = None
@ -34,10 +33,11 @@ def request_allowed(request):
request_password = get['api'] request_password = get['api']
elif 'api' in post: elif 'api' in post:
request_password = post['api'] request_password = post['api']
correct_password = False token_permission = False
if password and request_password: if Token.objects.filter(active=True, key=request_password).exists():
correct_password = request_password == password token = Token.objects.get(active=True, key=request_password)
return is_authenticated or correct_password token_permission = getattr(token, permission, False)
return is_authenticated or token_permission
def clean(model, data): def clean(model, data):
@ -51,16 +51,12 @@ def clean(model, data):
return cleaned return cleaned
def generate_password():
return "".join([random.choice(string.ascii_letters + string.digits) for idx in range(0, 20)])
class WebAPI(View): class WebAPI(View):
def get(self, request, keyword): def get(self, request, keyword):
get = request.GET get = request.GET
data = {'success': False, 'message': 'API failed'} data = {'success': False, 'message': 'API failed'}
if request_allowed(request): if request_allowed(request, 'web_get_permission'):
keyword = keyword.lower() keyword = keyword.lower()
if keyword == 'log': if keyword == 'log':
html_global = "" html_global = ""
@ -102,7 +98,7 @@ class WebAPI(View):
def post(self, request, keyword): def post(self, request, keyword):
post = request.POST post = request.POST
data = {} data = {}
if request_allowed(request): if request_allowed(request, 'web_post_permission'):
keyword = keyword.lower() keyword = keyword.lower()
if keyword == 'settings' and request.user.usersettings: if keyword == 'settings' and request.user.usersettings:
for s in [a for a in dir(UserSettings) if not a.startswith('__') and not callable(getattr(UserSettings,a))]: for s in [a for a in dir(UserSettings) if not a.startswith('__') and not callable(getattr(UserSettings,a))]:
@ -119,7 +115,7 @@ class WebAPI(View):
else: else:
return HttpResponse(form.as_p()) return HttpResponse(form.as_p())
elif keyword == 'alert': elif keyword == 'alert':
form = MCMForms.AlertForm(request.POST) form = mcm_forms.AlertForm(request.POST)
if form.is_valid(): if form.is_valid():
if mcm_api.create_alert(form.cleaned_data['message']): if mcm_api.create_alert(form.cleaned_data['message']):
data = {'success': True} data = {'success': True}
@ -155,7 +151,7 @@ class PluginAPI(View):
def get(self, request, keyword): def get(self, request, keyword):
json = {'status': True, 'message': '', 'extra': ''} json = {'status': True, 'message': '', 'extra': ''}
if request_allowed(request): if request_allowed(request, 'plugin_get_permission'):
get = request.GET get = request.GET
keyword = keyword.lower() keyword = keyword.lower()
@ -163,7 +159,7 @@ class PluginAPI(View):
def post(self, request, keyword): def post(self, request, keyword):
json = {'status': True, 'message': '', 'extra': ''} json = {'status': True, 'message': '', 'extra': ''}
if request_allowed(request): if request_allowed(request, 'plugin_post_permission'):
post = request.POST post = request.POST
keyword = keyword.lower() keyword = keyword.lower()
if "application" == keyword: if "application" == keyword:
@ -272,10 +268,13 @@ class PluginAPI(View):
player.last_seen = timezone.now().strftime("%Y-%m-%d") player.last_seen = timezone.now().strftime("%Y-%m-%d")
player.save() player.save()
if new_player and ip.associated: if new_player and ip.associated:
associated = []
for assoc in ip.associated: for assoc in ip.associated:
if assoc.uuid is not player.uuid and assoc.is_banned: if assoc.uuid is not player.uuid and assoc.is_banned:
mcm_api.plugin("staff", "Server {0}'s IP matches the banned player {1}".format(player.username, assoc.username)) associated.append(assoc)
mcm_api.discord_notification("{0}'s IP matches the banned player {1}".format(player.username, assoc.username), ping=True) if associated:
mcm_api.plugin("staff", "Server {0}'s IP matches the banned player(s) {1}".format(player.username, ", ".join([assoc.username for assoc in associated])))
mcm_api.discord_notification("{0}'s IP matches the banned player(s) {1}".format(player.username, ", ".join([assoc.username for assoc in associated])), ping=True)
json['status'] = True json['status'] = True
json['message'] = "Updated {0}".format(post['username']) json['message'] = "Updated {0}".format(post['username'])
elif "register" == keyword: elif "register" == keyword:
@ -284,7 +283,7 @@ class PluginAPI(View):
json['status'] = False json['status'] = False
json['message'] = "You are already registered. To change your password, contact an Admin." json['message'] = "You are already registered. To change your password, contact an Admin."
else: else:
password = generate_password() password = mcm_api.generate_password()
user = User.objects.create_user(username=player.username.lower(), password=password) user = User.objects.create_user(username=player.username.lower(), password=password)
user.save() user.save()
player.auth_user = user player.auth_user = user
@ -323,7 +322,7 @@ class FormAPI(View):
def get(self, request, request_model): def get(self, request, request_model):
html = "" html = ""
if request_allowed(request): if request_allowed(request, 'form_get_permission'):
get = request.GET get = request.GET
model = None model = None
for m in apps.get_app_config('minecraft_manager').get_models(): for m in apps.get_app_config('minecraft_manager').get_models():
@ -332,7 +331,7 @@ class FormAPI(View):
break break
if model: if model:
form = None form = None
for modelform in MCMForms.__all__(): for modelform in mcm_forms.__all__():
if modelform.Meta.model == model: if modelform.Meta.model == model:
form = modelform() form = modelform()
break break
@ -346,7 +345,7 @@ class FormAPI(View):
def post(self, request, request_model): def post(self, request, request_model):
html = "" html = ""
if request_allowed(request): if request_allowed(request, 'form_post_permission'):
post = request.POST post = request.POST
model = None model = None
for m in apps.get_app_config('minecraft_manager').get_models(): for m in apps.get_app_config('minecraft_manager').get_models():
@ -355,7 +354,7 @@ class FormAPI(View):
break break
if model: if model:
form = None form = None
for modelform in MCMForms.__all__(): for modelform in mcm_forms.__all__():
if modelform.Meta.model == model: if modelform.Meta.model == model:
form = modelform(post) form = modelform(post)
break break
@ -376,7 +375,7 @@ class ModelAPI(View):
def get(self, request, request_model): def get(self, request, request_model):
json = [] json = []
if request_allowed(request): if request_allowed(request, 'model_get_permission'):
get = request.GET get = request.GET
model = None model = None
for m in apps.get_app_config('minecraft_manager').get_models(): for m in apps.get_app_config('minecraft_manager').get_models():
@ -404,7 +403,7 @@ class StatsAPI(View):
def get(self, request): def get(self, request):
json = [] json = []
if request_allowed(request): if request_allowed(request, 'stats_get_permission'):
get = request.GET get = request.GET
if 'stat' in get: if 'stat' in get:
if 'uuid' in get: if 'uuid' in get:

View File

@ -47,8 +47,6 @@ Optional
``SERVER_QUERY_IP`` - The full IP (and port) used to query your server. (This is used to get a player list) ``SERVER_QUERY_IP`` - The full IP (and port) used to query your server. (This is used to get a player list)
``API_PASSWORD`` - The password used to validate API requests from unauthenticated sources.
``COREPROTECT_WEB_URL`` - The URL to your CoreProtect Web UI, if it exists. ``COREPROTECT_WEB_URL`` - The URL to your CoreProtect Web UI, if it exists.
``COREPROTECT_ACTIVITY_URL`` - The URL to your CoreProtect Activity Web UI, if it exists. ``COREPROTECT_ACTIVITY_URL`` - The URL to your CoreProtect Activity Web UI, if it exists.

View File

@ -47,6 +47,7 @@ Add MCM urls to your ``urls.py``
Django doesn't provide login/logout templates by default, so MCM has some generic ones if needed. Django doesn't provide login/logout templates by default, so MCM has some generic ones if needed.
:: ::
from django.contrib.auth import views as auth_views
path('accounts/login/', auth_views.LoginView.as_view(template_name='minecraft_manager/login.html'), name='login'), path('accounts/login/', auth_views.LoginView.as_view(template_name='minecraft_manager/login.html'), name='login'),
path('accounts/logout/', auth_views.LogoutView.as_view(template_name='minecraft_manager/logged_out.html'), name='logout'), path('accounts/logout/', auth_views.LogoutView.as_view(template_name='minecraft_manager/logged_out.html'), name='logout'),

12
external/stats.py vendored
View File

@ -1,4 +1,4 @@
import os, json import os, json, copy
from minecraft_manager.models import Player from minecraft_manager.models import Player
from django.conf import settings from django.conf import settings
@ -16,9 +16,13 @@ def get_stats():
with open(stats_dir + "/" + filename) as json_file: with open(stats_dir + "/" + filename) as json_file:
raw = json.load(json_file)['stats'] raw = json.load(json_file)['stats']
clean = {} clean = {}
for r in raw: raw_copy = copy.deepcopy(raw)
if not any(sf.lower() in r.lower() for sf in stats_filter): for ra in raw_copy:
clean[r] = raw[r] if not any(sf.lower() in ra.lower() for sf in stats_filter):
for r in raw_copy[ra]:
if any(sf.lower() in r.lower() for sf in stats_filter):
del raw[ra][r]
clean[ra] = raw[ra]
uuid = filename.replace(".json", "") uuid = filename.replace(".json", "")
stats[uuid] = clean stats[uuid] = clean
return stats return stats

75
overview.py 100644
View File

@ -0,0 +1,75 @@
import os
import json
from datetime import datetime, timedelta
from django.conf import settings
from minecraft_manager.models import Application, Player, Ticket, Warning, IP
from django.contrib.auth.models import User
def overview_data():
data = {}
# Setup
with open(os.path.join(settings.MINECRAFT_BASE_DIR, 'banned-players.json'), encoding='utf-8') as f:
bans = json.load(f)
# Totals
data['total'] = {
'application': {
'accepted': Application.objects.filter(accepted=True).count(),
'denied': Application.objects.filter(accepted=False).count(),
'all': Application.objects.count()
},
'player': {
'banned': len(bans),
'unbanned': Player.objects.count() - len(bans),
'all': Player.objects.count()
},
'ticket': {
'claimed': Ticket.objects.filter(staff__isnull=False).count(),
'unclaimed': Ticket.objects.filter(staff__isnull=True).count(),
'resolved': Ticket.objects.filter(resolved=True).count(),
'unresolved': Ticket.objects.filter(resolved=False).count(),
'all': Ticket.objects.count()
},
'warning': Warning.objects.count(),
'ip': IP.objects.count()
}
# Averages
data['average'] = {
'age': round(sum([application.age for application in Application.objects.filter(accepted=True)]) /
data['total']['application']['accepted'] if data['total']['application']['accepted'] != 0 else 1, 2)
}
# Percentage
data['percentage'] = {
'accepted': round((data['total']['application']['accepted'] /
data['total']['application']['all'] if data['total']['application']['all'] != 0 else 1) * 100, 2),
'banned': round((data['total']['player']['banned'] /
data['total']['player']['all'] if data['total']['player']['all'] != 0 else 1) * 100, 2),
'applied': round((data['total']['application']['all'] / data['total']['player']['all']) * 100, 2)
}
# Unique logins
now = datetime.now()
day = now - timedelta(days=1)
week = now - timedelta(weeks=1)
month = now - timedelta(weeks=4)
data['unique'] = {
'day': Player.objects.filter(last_seen__range=[day, now]).count(),
'week': Player.objects.filter(last_seen__range=[week, now]).count(),
'month': Player.objects.filter(last_seen__range=[month, now]).count()
}
# Admin
data['resolved'] = []
for user in User.objects.all().order_by('username'):
data['resolved'].append({
'active': user.is_active,
'username': user.username,
'tickets': Ticket.objects.filter(staff=user).count()
})
return data

View File

@ -6,141 +6,72 @@
{% endblock %} {% endblock %}
{% block section %} {% block section %}
<div id="content"> <div id="content">
{% if request.user.is_staff %}
<div class="panel panel-danger">
<div class="panel-body">
<h4><span class="label label-danger">Admin Area</span></h4>
<h3>Resolved Tickets</h3>
<div class="row">
{% for staff in data.resolved %}
<div class="col-xs-6 col-md-4">
<p><span class="label label-{% if staff.active %}success{% else %}danger{% endif %}">{% if staff.active %}Active{% else %}Inactive{% endif %}</span> {{ staff.username }}: {{ staff.tickets }}</p>
</div>
{% endfor %}
</div>
</div>
</div>
<hr/>
{% endif %}
<div class="row"> <div class="row">
<div class="col-xs-9 col-md-6"> <div class="col-xs-9 col-md-6">
<h3 class="center">Applications{% if form.apps.unanswered > 0 %} <a href="{% url "application" %}?accepted=">({{ form.apps.unanswered }} Unanswered)</a>{% endif %}</h3> <h3>Applications: {{ data.total.application.all }}</h3>
<canvas id="appChart" style="width:30em;height:15em" ></canvas> <p>Accepted: {{ data.total.application.accepted }}</p>
<p>Denied: {{ data.total.application.denied }}</p>
<br/>
<h3>Tickets: {{ data.total.ticket.all }}</h3>
<p>Claimed: {{ data.total.ticket.claimed }}</p>
<p>Unclaimed: {{ data.total.ticket.unclaimed }}</p>
<p>Resolved: {{ data.total.ticket.resolved }}</p>
<p>Unresolved: {{ data.total.ticket.unresolved }}</p>
</div> </div>
<div class="col-xs-9 col-md-6"> <div class="col-xs-9 col-md-6">
<h3 class="center">Tickets{% if form.tickets.unclaimed > 0 %} <a href="{% url "ticket" %}?claimed=false">({{ form.tickets.unclaimed }} Unclaimed)</a>{% endif %}</h3> <h3>Players: {{ data.total.player.all }}</h3>
<h4></h4> <p>Not Banned: {{ data.total.player.unbanned }}</p>
<canvas id="ticketChart" style="width:30em;height:15em" ></canvas> <p>Banned: {{ data.total.player.banned }}</p>
<br/>
<h3>Warnings: {{ data.total.warning }}</h3>
<br/>
<h3>IPs: {{ data.total.ip }}</h3>
</div> </div>
</div> </div>
<hr/>
<div class="row"> <div class="row">
<div class="col-xs-18 col-md-12"> <div class="col-xs-9 col-md-6">
<h3 class="center">Totals</h3> <h3>Average Age: {{ data.average.age }}</h3>
<canvas id="totalChart" style="width:30em;height:15em" ></canvas>
</div> </div>
</div> <div class="col-xs-9 col-md-6">
</div>
<script>
$(document).ready(function() {
var app_data = {
labels: [
"Unanswered",
"Denied",
"Accepted"
],
datasets: [
{
data: [{{ form.apps.unanswered }}, {{ form.apps.denied }}, {{ form.apps.accepted }}],
backgroundColor: [
"#8080ff",
"#ff3333",
"#33cc33"
],
hoverBackgroundColor: [
"#8080ff",
"#ff3333",
"#33cc33"
]
}
]
};
var app_options = {
animation: {
animateRotate: true
}
}
var app_ctx = $("#appChart");
var app_chart = new Chart(app_ctx,
{
type: 'pie',
data: app_data,
options: app_options
}
);
var ticket_data = { </div>
labels: [ </div>
"Unclaimed", <hr/>
"Claimed", <div class="row">
"Resolved" <div class="col-xs-9 col-md-6">
], <h3>Acceptance Rate: {{ data.percentage.accepted }}%</h3>
datasets: [ <h3>Application/Player Rate: {{ data.percentage.applied }}%</h3>
{ </div>
data: [{{ form.tickets.unclaimed }}, {{ form.tickets.claimed }}, {{ form.tickets.resolved }}], <div class="col-xs-9 col-md-6">
backgroundColor: [ <h3>Ban Rate: {{ data.percentage.banned }}%</h3>
"#8080ff", </div>
"#ff3333", </div>
"#33cc33" <hr/>
], <div class="row">
hoverBackgroundColor: [ <div class="col-xs-9 col-md-6">
"#8080ff", <h3>Logins Today: {{ data.unique.day }}</h3>
"#ff3333", <h3>Logins Last Month: {{ data.unique.month }}</h3>
"#33cc33" </div>
] <div class="col-xs-9 col-md-6">
} <h3>Logins Last Week: {{ data.unique.week }}</h3>
] </div>
}; </div>
var ticket_options = { </div>
animation: {
animateRotate: true
}
}
var ticket_ctx = $("#ticketChart");
var ticket_chart = new Chart(ticket_ctx,
{
type: 'pie',
data: ticket_data,
options: ticket_options
}
);
var total_data = {
labels: [
"Applications",
"Players",
"IPs",
"Tickets",
"Warnings"
],
datasets: [
{
label: "Totals",
data: [
{{ form.counts.applications }},
{{ form.counts.players }},
{{ form.counts.ips }},
{{ form.counts.tickets }},
{{ form.counts.warnings }}
],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)'
],
borderWidth: 1
}
]
}
var total_ctx = $("#totalChart");
var total_chart = new Chart(total_ctx,
{
type: 'bar',
data: total_data
}
);
});
</script>
{% endblock section %} {% endblock section %}

View File

@ -17,7 +17,7 @@ def get_sidebar(current_app, request):
ret = '<li {}><a href="{}"><span class="glyphicon glyphicon-home"></span>&nbsp;&nbsp;Overview</a></li>'.format('class="active"' if current_app == 'overview' else "", reverse('overview')) ret = '<li {}><a href="{}"><span class="glyphicon glyphicon-home"></span>&nbsp;&nbsp;Overview</a></li>'.format('class="active"' if current_app == 'overview' else "", reverse('overview'))
ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-ban-circle"></span>&nbsp;&nbsp;Bans</a></li>'.format('class="active"' if current_app == 'ban' else '', reverse('ban')) ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-ban-circle"></span>&nbsp;&nbsp;Bans</a></li>'.format('class="active"' if current_app == 'ban' else '', reverse('ban'))
ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-inbox"></span>&nbsp;&nbsp;Alerts{}</a></li>'.format('class="active"' if current_app == 'alert' else '', reverse('alert'), unseen_html) ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-bell"></span>&nbsp;&nbsp;Alerts{}</a></li>'.format('class="active"' if current_app == 'alert' else '', reverse('alert'), unseen_html)
# Models # Models
ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-file"></span>&nbsp;&nbsp;Applications</a></li>'.format('class="active"' if current_app == 'application' else '', reverse('application')) ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-file"></span>&nbsp;&nbsp;Applications</a></li>'.format('class="active"' if current_app == 'application' else '', reverse('application'))

View File

@ -14,6 +14,7 @@ from django.views.generic import View
from django.contrib.auth.models import User from django.contrib.auth.models import User
from minecraft_manager.models import Application as AppModel, Player as PlayerModel, Ticket as TicketModel, Warning as WarningModel, IP as IPModel, Alert as AlertModel, Note as NoteModel, UserSettings as UserSettingsModel from minecraft_manager.models import Application as AppModel, Player as PlayerModel, Ticket as TicketModel, Warning as WarningModel, IP as IPModel, Alert as AlertModel, Note as NoteModel, UserSettings as UserSettingsModel
from minecraft_manager.forms import WarningForm, NoteForm from minecraft_manager.forms import WarningForm, NoteForm
from minecraft_manager.overview import overview_data
import minecraft_manager.api.api as API import minecraft_manager.api.api as API
import subprocess import subprocess
@ -33,19 +34,9 @@ class Overview(View):
request.user.usersettings = UserSettingsModel(auth_user=request.user) request.user.usersettings = UserSettingsModel(auth_user=request.user)
request.user.usersettings.last_ip = user_ip request.user.usersettings.last_ip = user_ip
request.user.usersettings.save() request.user.usersettings.save()
unanswered_apps = AppModel.objects.filter(accepted=None).count()
accepted_apps = AppModel.objects.filter(accepted=True).count()
denied_apps = AppModel.objects.filter(accepted=False).count() return render(request, 'minecraft_manager/overview.html', {'current_app': 'overview', 'data': overview_data()})
unclaimed_tickets = TicketModel.objects.filter(staff=None, resolved=False).count()
claimed_tickets = TicketModel.objects.filter(staff__isnull=False, resolved=False).count()
resolved_tickets = TicketModel.objects.filter(resolved=True).count()
counts = {'applications': AppModel.objects.count(), 'players': PlayerModel.objects.count(),
"ips": IPModel.objects.count(), "tickets": TicketModel.objects.count(),
"warnings": WarningModel.objects.count()}
form = {'apps': {'unanswered': unanswered_apps, 'accepted': accepted_apps, 'denied': denied_apps},
'tickets': {'unclaimed': unclaimed_tickets, 'claimed': claimed_tickets, 'resolved': resolved_tickets},
'counts': counts}
return render(request, 'minecraft_manager/overview.html', {'current_app': 'overview', 'form': form})
class CoreProtect(View): class CoreProtect(View):