Initial commit for Gitea

reminder
Etzelia 2018-09-19 21:56:17 -05:00
commit da44791186
83 changed files with 26363 additions and 0 deletions

3
.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
*.pyc
__pycache__/*
docs/build/*

0
__init__.py 100644
View File

106
admin.py 100644
View File

@ -0,0 +1,106 @@
from __future__ import absolute_import
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from minecraft_manager.models import Application, Warning, Ticket, Player, IP, UserSettings, Alert, Note
class PlayerInline(admin.StackedInline):
model = Player
can_delete = False
verbose_name_plural = 'player'
class UserAdmin(BaseUserAdmin):
inlines = (PlayerInline, )
actions = ['deactivate_account']
def deactivate_account(self, request, queryset):
rows_updated = queryset.update(is_active=False)
if rows_updated == 1:
message_bit = "1 user was"
else:
message_bit = "%s users were" % rows_updated
self.message_user(request, "%s successfully deactivated." % message_bit)
deactivate_account.short_description = "Deactivate selected accounts"
class PlayerAdmin(admin.ModelAdmin):
search_fields = ["username", "uuid"]
class ApplicationAdmin(admin.ModelAdmin):
search_fields = ["username"]
#date_hierarchy = 'date'
class TicketPriorityFilter(admin.SimpleListFilter):
title = _('Priority')
parameter_name = 'priority'
def lookups(self, request, model_admin):
return (
('0', _('Low')),
('1', _('Medium')),
('2', _('High'))
)
def queryset(self, request, queryset):
if self.value() == '0':
return queryset.filter(priority='L')
if self.value() == '1':
return queryset.filter(priority='M')
if self.value() == '2':
return queryset.filter(priority='H')
class TicketAdmin(admin.ModelAdmin):
search_fields = ["player__username", "staff__username"]
#date_hierarchy = 'date'
list_filter = (TicketPriorityFilter,)
class WarningSeverityFilter(admin.SimpleListFilter):
title = _('Severity')
parameter_name = 'severity'
def lookups(self, request, model_admin):
return (
('0', _('Low')),
('1', _('Medium')),
('2', _('High'))
)
def queryset(self, request, queryset):
if self.value() == '0':
return queryset.filter(priority='L')
if self.value() == '1':
return queryset.filter(priority='M')
if self.value() == '2':
return queryset.filter(priority='H')
class WarningAdmin(admin.ModelAdmin):
search_fields = ["player__username", "staff__username"]
#date_hierarchy = 'date'
list_filter = (WarningSeverityFilter,)
try:
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
admin.site.register(UserSettings)
admin.site.register(Application, ApplicationAdmin)
admin.site.register(Warning, WarningAdmin)
admin.site.register(Ticket, TicketAdmin)
admin.site.register(Player, PlayerAdmin)
admin.site.register(IP)
admin.site.register(Alert)
admin.site.register(Note)
except admin.sites.AlreadyRegistered:
pass

0
api/__init__.py 100644
View File

142
api/api.py 100644
View File

@ -0,0 +1,142 @@
import socket, requests, logging, os, datetime, pytz, mcstatus, discord
from minecraft_manager.models import Alert
from django.contrib.auth.models import User
from django.conf import settings
logger = logging.getLogger(__name__)
def create_alert(message):
users = User.objects.filter(is_active=True)
for user in users:
alert = Alert(user=user, message=message)
alert.save()
PLUGIN_APPLICATION = 'application'
PLUGIN_ACCEPT = 'accept'
PLUGIN_DENY = 'deny'
PLUGIN_GLOBAL_CHAT = 'global'
PLUGIN_STAFF_CHAT = 'staff'
def plugin(key, command):
host = '127.0.0.1'
port = getattr(settings, 'PLUGIN_PORT', None)
full_command = "{0} {1}".format(key, command)
if port and plugin_exists():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
sock.sendall(full_command.encode('utf-8'))
sock.close()
def discord_mcm(message='', embeds=None, ping=False):
discord_mcm_webhook = getattr(settings, 'DISCORD_MCM_WEBHOOK', None)
if discord_mcm_webhook:
ping_list = getattr(settings, 'DISCORD_PING_LIST', [])
if ping and ping_list:
ping_list = ["<@&{0}>".format(ping) for ping in ping_list]
message = "{0}\n{1}".format(" ".join(ping_list), message)
data = {}
if message:
data['content'] = message
if embeds:
data['embeds'] = embeds
return requests.post(discord_mcm_webhook, json=data)
return None
def discord_notification(message='', embeds=None, ping=False):
discord_notification_webhook = getattr(settings, 'DISCORD_NOTIFICATION_WEBHOOK', None)
if discord_notification_webhook:
ping_list = getattr(settings, 'DISCORD_PING_LIST', [])
if ping and ping_list:
ping_list = ["<@&{0}>".format(ping) for ping in ping_list]
message = "{0}\n{1}".format(" ".join(ping_list), message)
data = {}
if message:
data['content'] = message
if embeds:
data['embeds'] = embeds
return requests.post(discord_notification_webhook, json=data)
return None
def discord_webhook(webhook_name=None, message=None, embed=None, ping=False):
webhook = getattr(settings, 'DISCORD_{}_WEBHOOK'.format(webhook_name.upper()), getattr(settings, 'DISCORD_WEBHOOK', None))
if webhook and (message or embed):
ping_list = getattr(settings, 'DISCORD_PING_LIST', [])
if ping and ping_list:
ping_list = ["<@&{0}>".format(ping) for ping in ping_list]
message = "{0}\n{1}".format(" ".join(ping_list), message)
data = {}
if message:
data['content'] = message
if embed:
data['embeds'] = embed
return requests.post(webhook, json=data)
return None
def strip_format(message):
return message.replace("§0", "").replace("§1", "").replace("§2", "").replace("§3", "").replace("§4", "") \
.replace("§5", "").replace("§6", "").replace("§7", "").replace("§8", "").replace("§9", "").replace("§a", "") \
.replace("§b", "").replace("§c", "").replace("§d", "").replace("§e", "").replace("§f", "").replace("§r", "") \
.replace("§k", "").replace("§l", "").replace("§m", "").replace("§n", "").replace("§o", "")
def plugin_exists():
return os.path.exists(os.path.join(getattr(settings, 'MINECRAFT_BASE_DIR', ''), 'plugins/MinecraftManager'))
def get_chats(as_timezone):
global_log = getattr(settings, 'GLOBAL_LOG', "")
staff_log = getattr(settings, 'STAFF_LOG', "")
log_time_format = getattr(settings, 'LOG_TIME_FORMAT', '%m/%d/%y %I:%M %p')
if plugin_exists():
try:
with open(global_log, encoding='utf-8') as g:
g_chat = g.readlines()[-100:]
except OSError:
g_chat = []
for idx, gc in enumerate(g_chat):
try:
date_str = gc.split("] ")[0].replace("[", "")
msg = gc.split("] ", 1)[1]
dt = datetime.datetime.strptime(date_str, log_time_format)
dt = pytz.timezone('US/Central').localize(dt)
dt = dt.astimezone(pytz.timezone(as_timezone))
g_chat[idx] = {"date": dt.strftime(log_time_format), "text": msg}
except:
g_chat[idx] = {"date": "01/01/17 00:00 AM", "name": "Error", "text": "Parsing Line"}
try:
with open(staff_log, encoding='utf-8') as s:
s_chat = s.readlines()[-100:]
except OSError:
s_chat = []
for idx, sc in enumerate(s_chat):
try:
date_str = sc.split("] ")[0].replace("[", "")
msg = sc.split("] ", 1)[1]
dt = datetime.datetime.strptime(date_str, log_time_format)
dt = pytz.timezone('US/Central').localize(dt)
dt = dt.astimezone(pytz.timezone(as_timezone))
s_chat[idx] = {"date": dt.strftime(log_time_format), "text": msg}
except:
s_chat[idx] = {"date": "01/01/17 00:00 AM", "name": "Error", "text": "Parsing Line"}
return {'global': g_chat, 'staff': s_chat}
return None
def get_query():
try:
server = mcstatus.MinecraftServer.lookup(getattr(settings, 'SERVER_QUERY_IP', None))
query = server.query()
return {'max': str(query.players.max), 'online': str(query.players.online),
'players': sorted(query.players.names)}
except:
return {'max': 0, 'online': 0,
'players': []}

296
api/bot.py 100644
View File

@ -0,0 +1,296 @@
import discord, logging, re, sys, traceback, asyncio, datetime, os, time
from minecraft_manager.models import Application, Player
from minecraft_manager.api import api
from django.conf import settings
from django.db import close_old_connections
from threading import Thread
logger = logging.getLogger(__name__)
class Discord(discord.Client):
discord_game = 'MCM'
prefix = getattr(settings, 'DISCORD_BOT_PREFIX', '!')
auth_roles = getattr(settings, 'DISCORD_BOT_ROLES', [])
error_users = getattr(settings, 'DISCORD_ERROR_USERS', [])
token = None
def __init__(self, token, **kwargs):
super().__init__(**kwargs)
self.token = token
@asyncio.coroutine
def on_ready(self):
print('Logged in as')
print(self.user.name)
print(self.user.id)
print(discord.__version__)
print('Voice Loaded: {0}'.format(discord.opus.is_loaded()))
print('OAuth URL: https://discordapp.com/api/oauth2/authorize?client_id={0}&permissions=0&scope=bot'.format(self.user.id))
print('------')
logger.info('Logged in as {0} ({1}) with discord.py v{2}'.format(self.user.name, self.user.id, discord.__version__))
yield from self.change_presence(game=discord.Game(name=self.discord_game))
@asyncio.coroutine
def discord_message(self, channel, message):
if isinstance(message, discord.Embed):
for idx, field in enumerate(message.fields):
if not field.value:
message.set_field_at(idx, name=field.name, value="N/A")
yield from self.send_message(channel, embed=message)
else:
yield from self.send_message(channel, message)
@asyncio.coroutine
def on_message(self, message):
# IGNORE DM AND BOTS
if message.author.bot is True or message.channel.is_private is True:
return
member_roles = [role.id for role in message.author.roles]
# IF MEMBER IS NOT AUTHORIZED, IGNORE
if not any(role in self.auth_roles for role in member_roles):
return
# FIX STALE DB CONNECTIONS
close_old_connections()
# HELP
match = re.match("[{0}]help$".format(self.prefix), message.content)
if match:
embed = discord.Embed(colour=discord.Colour(0x417505))
embed.set_thumbnail(url="https://cdn.discordapp.com/avatars/454457830918062081/b5792489bc43d9e17b8f657880a17dd4.png")
embed.add_field(name="Minecraft Manager Help", value="-----------------------------")
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 ]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")
yield from self.discord_message(message.channel, embed)
# APP COMMANDS WITH APP ID
match = re.match("[{0}](?:app )?(i|info|a|accept|d|deny) (\d+)$".format(self.prefix), message.content)
if match:
if match.group(1) and match.group(2):
action = match.group(1)[:1]
action_display = "accept" if action == "a" else "deny" if action == "d" else ""
yield from self.discord_message(message.channel, "{0} App ID **{1}**".format(
"Retrieving info for" if action == "i" else "{0}ing".format(action_display.capitalize()),
match.group(2)))
application = None
try:
application = Application.objects.get(id=match.group(2))
except:
yield from self.discord_message(message.channel, "An Application with that ID doesn't exist.")
return
if action == "i":
# Info
msg = self.build_info(application)
else:
# Action
accept = True if action == "a" else False
if not application.accepted:
application.accepted = accept
application.save()
msg = "App ID **{0}** was successfully {1}.".format(match.group(2), "accepted" if accept else "denied")
api.plugin("accept" if accept else "deny", application.username)
else:
msg = "App ID **{0}** was already {1}.".format(match.group(2), "accepted" if application.accepted else "denied")
yield from self.discord_message(message.channel, msg)
return
# APP INFO WITH PARTIAL NAME SEARCH
match = re.match("[{0}](?:app )?(?:search|info) (\S+)?$".format(self.prefix), message.content)
if match:
search = match.group(1)
yield from self.discord_message(message.channel,"Searching for applications whose username contains '{0}'".format(search))
applications = Application.objects.filter(username__icontains=search)[:10]
count = Application.objects.filter(username__icontains=search).count()
if count > 0:
if count == 1:
info = self.build_info(applications[0])
else:
info = "**Found the following applications**"
for app in applications:
info += "\n{0} - {1} ({2})".format(app.id, app.username.replace("_", "\\_"), app.status)
if count > 10:
info += "\n**This is only 10 applications out of {0} found. Please narrow your search if possible.**".format(len(applications))
else:
info = "No applications matched that search."
yield from self.discord_message(message.channel, info)
# COMPARE DISCORD USERS TO WHITELIST
match = re.match("[{0}]compare".format(self.prefix), message.content)
if match:
yield from self.delete_message(message)
yield from self.send_typing(message.channel)
no_player = []
no_application = []
for member in message.server.members:
if member.bot:
continue
name = member.nick if member.nick else member.name
try:
Player.objects.get(username__iexact=name)
except:
no_player.append(name)
try:
Application.objects.get(username__iexact=name)
except:
no_player = no_player[:-1]
no_application.append(name)
header = "**The following users have an application match, but no player match on the whitelist:**\n"
yield from self.discord_message(message.author, "{}```{}```".format(header, "\n".join(no_player)))
header = "**The following users do not have an application or player match on the whitelist:**\n"
yield from self.discord_message(message.author, "{}```{}```".format(header, "\n".join(no_application)))
def build_info(self, application):
embed = discord.Embed(colour=discord.Colour(0x417505))
embed.set_thumbnail(
url="https://minotar.net/helm/{0}/100.png".format(application.username))
embed.add_field(name="Application ID", value=application.id)
embed.add_field(name="Username", value=application.username.replace("_", "\\_"))
embed.add_field(name="Age", value=application.age)
embed.add_field(name="Type of Player", value=application.player_type)
embed.add_field(name="Ever been banned", value=application.ever_banned)
if application.ever_banned:
embed.add_field(name="Reason for being banned", value=application.ever_banned_explanation)
embed.add_field(name="Reference", value=application.reference)
embed.add_field(name="Read the Rules", value=application.read_rules)
embed.add_field(name="Date", value=application.date_display)
embed.add_field(name="Status", value=application.status)
return embed
@asyncio.coroutine
def on_error(self, event, *args, **kwargs):
print(sys.exc_info())
print("Exception raised by " + event)
error = '{0}\n{1}'.format(sys.exc_info()[1], ''.join(traceback.format_tb(sys.exc_info()[2])))
logger.error(error)
for user in self.error_users:
try:
user = discord.User(id=user)
yield from self.discord_message(user, '```python\n{}```'.format(error))
except:
pass
def run_bot(self):
self.run(self.token)
class OreAlert:
# Options
log = os.path.join(settings.MINECRAFT_BASE_DIR, 'logs/latest.log')
purge = 30 # How long until we purge, in minutes
rotate = 5 # How long without input before we assume the log has rotated
notify_start = 5 # How many veins found within the above purge minutes to notify
notify_each = 1 # After the initial alert, how many should be found in addition before more alerts?
notify_ping = 5 # After the initial alert, how many should be found in addition before more pings?
playerList = []
class Player:
def __init__(self, name, time, prev=[]):
self.name = name
self.time = time
self.prev = prev
def compare(self, player):
if self.name == player.name:
return True
else:
return False
def to_string(self):
return str(self.name) + ": " + str(self.time) + ", previous: " + self.prev_to_string()
def prev_to_string(self):
ret = ""
if len(self.prev) > 0:
for p in self.prev:
ret += str(p) + " "
return ret.rstrip()
else:
return ret
def minute_interval(self, start, end):
reverse = False
if start > end:
start, end = end, start
reverse = True
delta = (end.hour - start.hour) * 60 + end.minute - start.minute + (end.second - start.second) / 60.0
if reverse:
delta = 24 * 60 - delta
return delta
def follow(self, filename):
thefile = open(filename, 'r', encoding='utf-8')
thefile.seek(0, 2) # Go to the end of the file
start = datetime.datetime.now()
end = datetime.datetime.now()
api.discord_notification('OreAlert has started successfully.')
while True:
line = thefile.readline()
if not line:
if self.minute_interval(start, end) > self.rotate:
thefile.close()
time.sleep(5)
thefile = open(filename, 'r', encoding='utf-8')
thefile.seek(0, 2) # Go to the end of the file
start = datetime.datetime.now()
end = datetime.datetime.now()
# api.discord_notification('OreAlert has closed and re-opened the log...hopefully it just rotated.')
continue
end = end + datetime.timedelta(milliseconds=100)
time.sleep(0.1) # Sleep briefly
continue
start = datetime.datetime.now()
end = datetime.datetime.now()
yield line
def run_bot(self):
cur_line = ""
try:
loglines = self.follow(self.log)
for line in loglines:
# [00: 03:46] [Server thread / INFO]: [MinecraftManager]: [OreAlert]: Etzelia
if "MinecraftManager" in line and "OreAlert" in line: # Filter out non-OreAlert log statements
cur_time = line.split()[0].replace("[", "").replace("]", "")
if ":" in cur_time: # Make sure we have a time.
time_array = cur_time.split(":")
name = line.split()[-1]
dt = datetime.time(int(time_array[0]), int(time_array[1]), int(time_array[2]))
p = self.Player(name, dt)
new_player = True
for player in self.playerList:
if p.compare(player):
new_player = False
player.prev[:] = [x for x in player.prev if not self.minute_interval(x,
dt) > self.purge] # First, purge any times older than our configured amount
player.prev.append(dt) # Add the new time
if len(player.prev) >= self.notify_start:
# MCM Alert
if len(player.prev) == self.notify_start:
api.create_alert("OreAlert: {0}".format(player.name))
if len(player.prev) % self.notify_each == 0:
# In-game Notification
api.plugin(api.PLUGIN_STAFF_CHAT,
"OreAlert {0} has found {1} diamond ore veins within {2} minutes.".format(
player.name, len(player.prev), self.purge))
# Discord Notification
ping = True if len(player.prev) % self.notify_ping == 0 else False
api.discord_notification(
'{0} has found {1} diamond ore veins within {2} minutes.'.format(
player.name.replace("_", "\\_"), len(player.prev), self.purge), ping=ping)
if new_player:
p.prev = [p.time]
self.playerList.append(p)
except KeyboardInterrupt:
api.discord_notification("OreAlert has been stopped manually.")
except:
api.discord_notification("OreAlert has crashed!", ping=True)

12
api/urls.py 100644
View File

@ -0,0 +1,12 @@
from django.conf.urls import url
from django.views.decorators.csrf import csrf_exempt
import minecraft_manager.api.views as api
urlpatterns = [
#API - Password protected
url(r'^web/(?P<keyword>\w{1,20})/$', csrf_exempt(api.WebAPI.as_view()), name="api-web"),
url(r'^plugin/(?P<keyword>\w{1,20})/$', csrf_exempt(api.PluginAPI.as_view()), name="api-plugin"),
url(r'^form/(?P<request_model>\w{1,20})/$', csrf_exempt(api.FormAPI.as_view()), name="api-form"),
url(r'^model/(?P<request_model>\w{1,20})/$', csrf_exempt(api.ModelAPI.as_view()), name="api-model"),
url(r'^stats/$', csrf_exempt(api.StatsAPI.as_view()), name="api-stats"),
]

423
api/views.py 100644
View File

@ -0,0 +1,423 @@
from __future__ import absolute_import
import logging, random, string, discord
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth import update_session_auth_hash
from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import User
from django.http import JsonResponse, HttpResponse
from django.urls import reverse
from django.utils import timezone
from django.views.generic import View
from django.forms import modelform_factory
import minecraft_manager.forms as MCMForms
from minecraft_manager.models import Player, UserSettings, Application, IP, Ticket, Warning
import minecraft_manager.api.api as mcm_api
import minecraft_manager.utils as mcm_utils
import minecraft_manager.external.stats as mcm_stats
logger = logging.getLogger(__name__)
def request_allowed(request):
is_authenticated = False
if hasattr(request, 'user'):
if hasattr(request.user, 'is_authenticated'):
is_authenticated = request.user.is_authenticated
password = getattr(settings, 'API_PASSWORD', None)
get = request.GET
post= request.POST
request_password = None
if 'api' in get:
request_password = get['api']
elif 'api' in post:
request_password = post['api']
correct_password = False
if password and request_password:
correct_password = request_password == password
return is_authenticated or correct_password
def clean(model, data):
cleaned = {}
for d in data:
attr = d
if '__' in d:
attr = d.split('__')[0]
if hasattr(model, attr):
cleaned[d] = data[d]
return cleaned
def generate_password():
return "".join([random.choice(string.ascii_letters + string.digits) for idx in range(0, 20)])
class WebAPI(View):
def get(self, request, keyword):
get = request.GET
data = {'success': False, 'message': 'API failed'}
if request_allowed(request):
keyword = keyword.lower()
if keyword == 'log':
html_global = ""
html_staff = ""
chats = mcm_api.get_chats(request.user.usersettings.default_timezone)
if chats:
for g in chats['global']:
g['text'] = g['text'].replace("<", "&lt;").replace(">", "&gt;")
if request.user.usersettings.show_timestamp_chat:
html_global += "<div>[{0}] {1}</div>".format(g['date'], g['text'])
else:
html_global += "<div data-toggle='tooltip' title='{0}' data-placement='left'>{1}</div>".format(g['date'], g['text'])
for s in chats['staff']:
s['text'] = s['text'].replace("<", "&lt;").replace(">", "&gt;")
if request.user.usersettings.show_timestamp_chat:
html_staff += "<div>[{0}] {1}</div>".format(s['date'], s['text'])
else:
html_staff += "<div data-toggle='tooltip' title='{0}' data-placement='left'>{1}</div>".format(s['date'], s['text'])
html = {'global': html_global, 'staff': html_staff}
data = {'chats': chats, 'html': html}
elif keyword == 'online':
query = mcm_api.get_query()
html = ""
for p in query['players']:
html += "<div class='label label-primary'>{0}</div>".format(p)
html += "<br/>"
data = {'query': query, 'html': html}
elif keyword == "coreprotect":
if 'username' in get and 'ip' in get:
user = User.objects.get(username=get['username'])
if user.is_active and user.usersettings.last_ip == get['ip']:
data = {'success': True, 'message': self.access_level(user)}
else:
data = {'success': False, 'message': 'Parameters not set'}
else:
data = {'message': 'Not Authorized', 'success': False}
return JsonResponse(data)
def post(self, request, keyword):
post = request.POST
data = {}
if request_allowed(request):
keyword = keyword.lower()
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))]:
if s in post:
setattr(request.user.usersettings, s, post[s])
request.user.usersettings.save()
data = {'success': True, 'message': 'User Settings saved'}
elif keyword == 'password':
form = PasswordChangeForm(request.user, post)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user)
return HttpResponse("success")
else:
return HttpResponse(form.as_p())
elif keyword == 'alert':
form = MCMForms.AlertForm(request.POST)
if form.is_valid():
if mcm_api.create_alert(form.cleaned_data['message']):
data = {'success': True}
else:
data = {'success': False, 'message': 'Could not create Alerts'}
else:
data = {'success': False, 'message': 'Invalid Message'}
elif keyword == "discord":
if 'message' in post:
ping = post['ping'] if 'ping' in post else False
mcm_api.discord_notification(post['message'], ping=ping)
data = {'success': True, 'message': "Success", 'extra': {'message': post['message'], 'ping': ping}}
else:
data = {'success': False, 'message': 'No message supplied'}
else:
data = {'success': True, 'message': 'Model set to "{0}"'.format(keyword)}
else:
data = {'message': 'Not Authorized', 'success': False}
return JsonResponse(data)
def access_level(self, user):
access = {'cpp': False, 'cpf': False, 'cpa': False}
if user.has_perm('auth.coreprotect_partial'):
access['cpp'] = True
if user.has_perm('auth.coreprotect_full'):
access['cpf'] = True
if user.has_perm('auth.coreprotect_activity'):
access['cpa'] = True
return access
class PluginAPI(View):
def get(self, request, keyword):
json = {'status': True, 'message': '', 'extra': ''}
if request_allowed(request):
get = request.GET
keyword = keyword.lower()
return JsonResponse(json)
def post(self, request, keyword):
json = {'status': True, 'message': '', 'extra': ''}
if request_allowed(request):
post = request.POST
keyword = keyword.lower()
if "application" == keyword:
if Application.objects.filter(username=post['username']).exists():
application = Application.objects.get(username=post['username'])
if application.accepted is not None:
json['status'] = False
json['message'] = "An application for {0} has already been acted on. Please contact Staff.".format(
post['username'])
return JsonResponse(json)
else:
application.accepted = None
application.age = post['age']
application.player_type = post['player_type']
application.ever_banned = False if post['ever_banned'] == "no" else True
application.ever_banned_explanation = post['ever_banned_explanation']
application.reference = post['reference']
application.read_rules = post['read_rules']
else:
application = Application(username=post['username'], age=post['age'],
player_type=post['player_type'],
ever_banned=False if post['ever_banned'] == "no" else True,
ever_banned_explanation= post['ever_banned_explanation'],
reference=post['reference'], read_rules=post['read_rules'])
application.save()
if Player.objects.filter(username__iexact=post['username']).exists():
player = Player.objects.get(username__iexact=post['username'])
player.application_id = application.id
player.save()
json['message'] = "{0}'s application was submitted.".format(application.username)
json['extra'] = application.id
msg = mcm_utils.build_application(application)
mcm_api.discord_mcm(message='New Application!', embeds=msg)
elif "application_action" == keyword:
if Application.objects.filter(id=post['application_id']).exists():
application = Application.objects.get(id=post['application_id'])
if application.accepted is not None:
json['status'] = False
json['message'] = "Application was already {0}.".format(
"accepted" if post['action'] == "True" else "denied")
else:
application.accepted = True if post['action'] == "True" else False
application.save()
json['message'] = "Application was successfully {0}.".format(
"accepted" if post['action'] == "True" else "denied")
mcm_api.discord_mcm("{0}'s application (#{1}) was {2} by {3}".format(application.username,
application.id,
"accepted" if post['action'] == "True" else "denied",
post['username']))
mcm_api.plugin("accept" if post['action'] == "True" else "deny", application.username)
else:
json['status'] = False
json['message'] = "No application found."
return JsonResponse(json)
elif "application_clear" == keyword:
if Application.objects.filter(id=post['application_id']).exists():
application = Application.objects.get(id=post['application_id'])
if application.accepted is True:
json['status'] = False
json['message'] = "An accepted application can't be cleared."
else:
application.accepted = None
application.save()
json['message'] = "Application was successfully cleared."
else:
json['status'] = False
json['message'] = "No application found."
return JsonResponse(json)
elif "login" == keyword:
player = Player.objects.filter(uuid=post['uuid']).exists()
new_player = False
if not player:
player = Player(uuid=post['uuid'], username=post['username'])
new_player = True
player.first_seen = timezone.now().strftime("%Y-%m-%d")
test_app = Application.objects.filter(username__iexact=post['username']).exists()
if test_app:
test_app = Application.objects.get(username__iexact=post['username'])
player.application = test_app
player.save()
else:
player = Player.objects.get(uuid=post['uuid'])
if player.username != post['username']:
user = User.objects.filter(username__iexact=player.username).exists()
if user:
user = User.objects.get(username__iexact=player.username)
user.username = post['username'].lower()
user.save()
mcm_api.create_alert("Name Change: {0} to {1}.".format(player.username, post['username']))
player.username = post['username']
if not player.application:
test_app = Application.objects.filter(username__iexact=post['username']).exists()
if test_app:
test_app = Application.objects.get(username__iexact=player.username)
player.application = test_app
player.save()
test_ip = IP.objects.filter(ip=post['ip'], player=player).exists()
if not test_ip:
ip = IP(ip=post['ip'], player=player)
ip.save()
else:
ip = IP.objects.get(ip=post['ip'], player=player)
player = Player.objects.get(uuid=post['uuid'])
player.last_seen = timezone.now().strftime("%Y-%m-%d")
player.save()
if new_player and ip.associated:
for assoc in ip.associated:
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))
mcm_api.discord_notification("{0}'s IP matches the banned player {1}".format(player.username, assoc.username), ping=True)
json['status'] = True
json['message'] = "Updated {0}".format(post['username'])
elif "register" == keyword:
player = Player.objects.get(uuid=post['uuid'])
if player.auth_user:
json['status'] = False
json['message'] = "You are already registered. To change your password, contact an Admin."
else:
password = generate_password()
user = User.objects.create_user(username=player.username.lower(), password=password)
user.save()
player.auth_user = user
player.save()
json['message'] = password
elif "ticket" == keyword:
player = Player.objects.get(uuid=post['uuid'])
try:
ticket = Ticket(player=player, message=post['message'], x=post['x'], y=post['y'], z=post['z'], world=post['world'])
ticket.save()
json['message'] = "Ticket submitted."
link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard/ticket', ticket.id))
msg = mcm_utils.build_ticket(ticket, link)
json['extra'] = {'id': ticket.id, 'link': link}
mcm_api.discord_mcm(embeds=msg, ping=True)
except:
json['status'] = False
json['message'] = "Error while submitting ticket."
elif "warning" == keyword:
player = Player.objects.get(uuid=post['player'])
staff = Player.objects.get(uuid=post['staff'])
try:
warning = Warning(player=player, message=post['message'], severity=post['severity'], staff=staff.auth_user)
warning.save()
json['message'] = "Warning issued."
link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard/warning', warning.id))
msg = mcm_utils.build_warning(warning, link)
mcm_api.discord_mcm(embeds=msg)
except Exception as ex:
json['status'] = False
json['message'] = "Error while issuing warning."
return JsonResponse(json)
class FormAPI(View):
def get(self, request, request_model):
html = ""
if request_allowed(request):
get = request.GET
model = None
for m in apps.get_app_config('minecraft_manager').get_models():
if m._meta.model_name.upper() == request_model.upper():
model = m
break
if model:
form = None
for modelform in MCMForms.__all__():
if modelform.Meta.model == model:
form = modelform()
break
if not form:
form = modelform_factory(model, exclude=('id',))()
if 'as' in get:
html = getattr(form, 'as_{0}'.format(get['as']))()
else:
html = form.as_p()
return HttpResponse(html)
def post(self, request, request_model):
html = ""
if request_allowed(request):
post = request.POST
model = None
for m in apps.get_app_config('minecraft_manager').get_models():
if m._meta.model_name.upper() == request_model.upper():
model = m
break
if model:
form = None
for modelform in MCMForms.__all__():
if modelform.Meta.model == model:
form = modelform(post)
break
if not form:
form = modelform_factory(model, exclude=('id',))(post)
if form.is_valid():
form.save()
html = "Saved"
else:
if 'as' in post:
html = getattr(form, 'as_{0}'.format(post['as']))()
else:
html = form.as_p()
return HttpResponse(html)
class ModelAPI(View):
def get(self, request, request_model):
json = []
if request_allowed(request):
get = request.GET
model = None
for m in apps.get_app_config('minecraft_manager').get_models():
if m._meta.model_name.upper() == request_model.upper():
model = m
break
if model:
keywords = clean(model, get)
objects = model.objects.filter(**keywords).values()
json = []
for value in objects:
try:
link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard', request_model, value['id']))
value['link'] = link
except:
pass
json.append(value)
return JsonResponse(json, safe=False)
def post(self, request, request_model):
pass
class StatsAPI(View):
def get(self, request):
json = []
if request_allowed(request):
get = request.GET
if 'stat' in get:
if 'uuid' in get:
json = mcm_stats.one_single(get['uuid'], get['stat'])
elif 'username' in get and Player.objects.filter(username__iexact=get['username']).exists():
uuid = Player.objects.get(username__iexact=get['username']).uuid
json = mcm_stats.one_single(uuid, get['stat'])
else:
json = mcm_stats.top_ten_stat(get['stat'])
elif 'uuid' in get:
json = mcm_stats.top_ten_player(get['uuid'])
elif 'username' in get:
uuid = Player.objects.get(username__iexact=get['username']).uuid
json = mcm_stats.top_ten_player(uuid)
return JsonResponse(json, safe=False)
def post(self, request):
pass

View File

@ -0,0 +1,18 @@
import os, sys, django
sep = os.sep
path = os.path.dirname(os.path.abspath(__file__))
path = path.split(sep)[:-3]
project = path[-1]
path = sep.join(path)
sys.path.append(path)
print("Setting path for {0}: {1}".format(project, path))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{}.settings".format(project))
django.setup()
from django.conf import settings
from minecraft_manager.api.bot import Discord
token = getattr(settings, 'DISCORD_BOT_TOKEN', None)
bot = Discord(token)
bot.run_bot()

View File

@ -0,0 +1,16 @@
import os, sys, django
sep = os.sep
path = os.path.dirname(os.path.abspath(__file__))
path = path.split(sep)[:-3]
project = path[-1]
path = sep.join(path)
sys.path.append(path)
print("Setting path for {0}: {1}".format(project, path))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{}.settings".format(project))
django.setup()
from minecraft_manager.api.bot import OreAlert
bot = OreAlert()
bot.run_bot()

View File

@ -0,0 +1,9 @@
You will need to get this from your CP MySQL tables
NOTE: CoreProtect does not match up these IDs with the block's in-game ID, so air is not 0, stone is not 1, etc.
1. mysql -p <coreprotect database> -e "SELECT id, material FROM co_material_map" > blocks.txt
2. Open blocks.txt
3. Remove the header row
4. Remove spaces/tabs and delimit with a ','
e.g. 1,minecraft:air

55
assets/migrate.sql 100644
View File

@ -0,0 +1,55 @@
-- Alerts
insert into minecraft_manager_alert (select * from whitelist_alert);
-- Applications
insert into minecraft_manager_application (select * from whitelist_application);
-- Players
insert into minecraft_manager_player (uuid, username, application_id, auth_user_id, last_seen, first_seen)
select wp.uuid, wp.username, (
select mma.id from minecraft_manager_application mma where mma.username = (
select wa.username from whitelist_application wa where wp.application_id = wa.id
)
), wp.auth_user_id, wp.last_seen, wp.first_seen from whitelist_player wp
;
-- Tickets
insert into minecraft_manager_ticket (message, priority, resolved, world, x, y, z, date, player_id, staff_id)
select wt.message, wt.priority, wt.resolved, wt.world, wt.x, wt.y, wt.z, wt.date, (
select mmp.id from minecraft_manager_player mmp where mmp.uuid = (
select wp.uuid from whitelist_player wp where wp.id = wt.player_id
)
), (
select au.id from auth_user au where au.username = (
select wp2.username from whitelist_player wp2 where wp2.id = wt.staff_id
)
) from whitelist_ticket wt
;
-- Warnings
insert into minecraft_manager_warning (message, severity, date, player_id, staff_id)
select ww.message, ww.severity, ww.date, (
select mmp.id from minecraft_manager_player mmp where mmp.uuid = (
select wp.uuid from whitelist_player wp where wp.id = ww.player_id
)
), (
select au.id from auth_user au where au.username = (
select wp2.username from whitelist_player wp2 where wp2.id = ww.staff_id
)
) from whitelist_warning ww
;
-- User Settings
insert into minecraft_manager_usersettings (default_results, default_theme, default_timezone, search_player_ip, show_timestamp_chat, last_ip, auth_user_id)
select default_results, default_theme, default_timezone, search_player_ip, show_timestamp_chat, last_ip, auth_user_id from whitelist_usersettings wu
;
-- Notes (This migration ONLY works if you are using standard whitelist app, aka only Tickets had notes)
-- The ignore is because there were some incorrectly encoded characters giving MySQL a hard time
insert ignore into minecraft_manager_note (ref_table, ref_id, message, last_update, author_id)
select wn.ref_table, (
select mmt.id from minecraft_manager_ticket mmt where mmt.message = (
select wt.message from whitelist_ticket wt where wt.id = wn.ref_id
)
), wn.message, wn.last_update, wn.author_id from whitelist_note wn where (select count(*) from whitelist_ticket wt2 where wt2.id = wn.ref_id) > 0
;

0
external/__init__.py vendored 100644
View File

85
external/stats.py vendored 100644
View File

@ -0,0 +1,85 @@
import os, json
from minecraft_manager.models import Player
from django.conf import settings
stats_dir = os.path.join(settings.MINECRAFT_BASE_DIR, getattr(settings, "WORLD", "world"), "stats")
def get_score(data):
return data['score']
def get_stats():
stats = {}
for filename in os.listdir(stats_dir):
with open(stats_dir + "/" + filename) as json_file:
j = json.load(json_file)['stats']
uuid = filename.replace(".json", "")
stats[uuid] = j
return stats
def get_names(stats=None, sort=False):
names = []
if not stats:
stats = get_stats()
for uuid in stats:
for base in stats[uuid]:
for stat in stats[uuid][base]:
name = "{}.{}".format(base, stat)
if name not in names:
names.append(name)
if sort:
names = sorted(names)
return names
def top_ten_stat(stat):
stats = get_stats()
top_ten = []
parts = stat.split('.')
for s in stats:
if parts[0] in stats[s]:
top_ten.append({'uuid': s, 'score': stats[s][parts[0]][parts[1]] if parts[1] in stats[s][parts[0]] else 0})
top_ten = sorted(top_ten, key=get_score, reverse=True)[:10]
for idx, tt in enumerate(top_ten):
uuid = tt['uuid']
try:
top_ten[idx]['username'] = Player.objects.get(uuid=uuid).username
except:
top_ten[idx]['username'] = uuid
return top_ten
def top_ten_player(uuid):
stats = get_stats()
names = get_names(stats)
top_ten_list = {}
for name in names:
top_ten_list[name] = []
parts = name.split('.')
for stat in stats:
bases = stats[stat]
if parts[0] in bases:
top_ten_list[name].append({'uuid': stat, 'score': bases[parts[0]][parts[1]] if parts[1] in bases[parts[0]] else 0})
top_ten_list[name] = sorted(top_ten_list[name], key=get_score, reverse=True)
top_ten = []
for tt in top_ten_list:
idx = 0
for ttl in top_ten_list[tt]:
if idx > 10:
break
if ttl['uuid'] == uuid:
ttl['rank'] = idx+1
ttl['stat'] = tt
top_ten.append(ttl)
break
idx += 1
return top_ten
def one_single(uuid, stat):
stats = get_stats()
if uuid in stats:
return [{'uuid': uuid, 'stat': stat, 'score': stats[uuid][stat]}]
return 0

10
external/urls.py vendored 100644
View File

@ -0,0 +1,10 @@
from django.conf.urls import url
from django.contrib.auth.decorators import login_required
import minecraft_manager.external.views as external
urlpatterns = [
url(r'^apply/$', external.Apply.as_view(), name="external-apply"),
url(r'^ticket/$', external.Ticket.as_view(), name="external-ticket"),
url(r'^stats/$', external.Stats.as_view(), name="external-stats"),
url(r'^stats/player/$', external.StatsPlayer.as_view(), name="external-stats-player"),
]

158
external/views.py vendored 100644
View File

@ -0,0 +1,158 @@
from django.views.generic import View
from django.shortcuts import render, reverse, redirect
from django.conf import settings
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from minecraft_manager.forms import ApplicationForm, TicketForm
import minecraft_manager.api.api as mcm_api
import minecraft_manager.utils as mcm_utils
import minecraft_manager.external.stats as mcm_stats
from minecraft_manager.models import Player
import random, yaml, os, json, datetime, pytz
def config():
data = {}
data['discord_invite'] = getattr(settings, "DISCORD_INVITE", "#")
dynmap_url = getattr(settings, "DYNMAP_URL", "")
data['dynmap_url'] = dynmap_url
dynmap_static_url = getattr(settings, "DYNMAP_STATIC_URL", dynmap_url)
data['dynmap_static_url'] = dynmap_static_url
dynmap_world = getattr(settings, "WORLD", "world")
data['dynmap_world'] = dynmap_world
world_border = getattr(settings, "WORLD_BORDER", 100)
data['world_border'] = world_border
data['target'] = "_blank" if dynmap_url else "_self"
x = random.randint(-world_border, world_border)
data['x'] = x
z = random.randint(-world_border, world_border)
data['z'] = z
uri = "?worldname={}&mapname=surface&zoom=5&x={}&y=64&z={}".format(dynmap_world, x, z)
data['map_url'] = "{}{}".format(dynmap_url, uri)
data['static_url'] = "{}{}".format(dynmap_static_url, uri)
return data
def rules():
path = os.path.join(settings.MINECRAFT_BASE_DIR, "plugins/MinecraftManager/config.yml")
with open(path) as config_file:
config = yaml.load(config_file)
data = config['rules']['rules']
if config['rules']['application']['validate']:
data.append("The answer to the final question is \"{}\"".format(config['rules']['application']['answer']))
return data
@method_decorator(csrf_exempt, name='dispatch')
class Apply(View):
def get(self, request):
form = ApplicationForm()
return render(request, 'minecraft_manager/external/apply.html',
{'form': form.as_p(), 'rules': rules(), 'valid': False, 'map': config(),
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
def post(self, request):
form = ApplicationForm(request.POST)
valid_username = mcm_utils.validate_username(form.data['username'])
captcha = mcm_utils.Captcha(request.POST)
valid = form.is_valid()
if valid and valid_username and captcha.success:
app = form.save()
msg = mcm_utils.build_application(app)
mcm_api.discord_mcm(message='New Application!', embeds=msg)
mcm_api.plugin("application", "{0} {1}".format(form.data['username'], app.id))
else:
for error in captcha.errors:
form.add_error(None, error)
if not valid_username:
form.add_error(None, "That username is not a premium Minecraft account")
return render(request, 'minecraft_manager/external/apply.html',
{'form': form.as_p(), 'rules': rules(), 'valid': valid and valid_username and captcha.success, 'map': config(),
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
@method_decorator(csrf_exempt, name='dispatch')
class Ticket(View):
def get(self, request):
form = TicketForm()
return render(request, 'minecraft_manager/external/ticket.html',
{'form': form.as_p(), 'valid': False, 'map': config(),
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
def post(self, request):
post = request.POST.copy()
username = post['player']
if 'player' in post and Player.objects.filter(username__iexact=post['player']).exists():
player = Player.objects.get(username__iexact=post['player'])
post.update({'player': player.id})
form = TicketForm(post)
captcha = mcm_utils.Captcha(request.POST)
valid = form.is_valid()
if 'player' not in post:
# Why did I make Player nullable???
valid = False
form.add_error(None, "You must enter your IGN")
if valid and captcha.success:
ticket = form.save()
# Create the message to send to Discord
link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard/ticket', ticket.id))
msg = mcm_utils.build_ticket(ticket, link)
mcm_api.discord_mcm(message="New Ticket", embeds=msg, ping=True)
mcm_api.plugin("ticket", "{0} {1} {2}".format(username, ticket.id, link))
else:
for error in captcha.errors:
form.add_error(None, error)
if 'player' in form.errors:
form.errors['player'][0] = 'You must enter your IGN.'
form.data = form.data.copy()
form.data['player'] = username
return render(request, 'minecraft_manager/external/ticket.html',
{'form': form.as_p(), 'valid': valid and captcha.success, 'map': config(),
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
@method_decorator(csrf_exempt, name='dispatch')
class Stats(View):
def get(self, request):
get = request.GET
results = None
if 'stat' in get and get['stat']:
results = mcm_stats.top_ten_stat(get['stat'])
return render(request, 'minecraft_manager/external/stats_all.html', {'map': config(), 'stats': mcm_stats.get_names(sort=True), 'results': results})
def post(self, request):
pass
@method_decorator(csrf_exempt, name='dispatch')
class StatsPlayer(View):
def get(self, request):
get = request.GET
results = None
if 'username' in get and get['username']:
if Player.objects.filter(username__iexact=get['username']).exists():
uuid = Player.objects.get(username__iexact=get['username']).uuid
results = mcm_stats.top_ten_player(uuid)
if 'uuid' in get and get['uuid']:
results = mcm_stats.top_ten_player(get['uuid'])
return render(request, 'minecraft_manager/external/stats_single.html', {'map': config(), 'results': results})
def post(self, request):
pass

53
forms.py 100644
View File

@ -0,0 +1,53 @@
from django.forms import ModelForm, Textarea, HiddenInput, TextInput
from minecraft_manager.models import UserSettings, Application, Alert, Ticket, Warning, Note
def __all__():
return [UserSettingsForm, ApplicationForm, AlertForm, TicketForm, WarningForm, NoteForm]
class UserSettingsForm(ModelForm):
class Meta:
model = UserSettings
fields = ['default_results', 'default_theme', 'default_timezone', 'search_player_ip', 'show_timestamp_chat']
class ApplicationForm(ModelForm):
class Meta:
model = Application
fields = ['username', 'age', 'player_type', 'ever_banned', 'ever_banned_explanation', 'reference', 'read_rules']
class AlertForm(ModelForm):
class Meta:
model = Alert
fields = ['message']
class TicketForm(ModelForm):
class Meta:
model = Ticket
fields = ['player', 'message', 'priority', 'world', 'x', 'y', 'z']
widgets = {
'player': TextInput,
'message': Textarea,
}
class WarningForm(ModelForm):
class Meta:
model = Warning
fields = ['player', 'message', 'severity']
widgets = {
'message': Textarea
}
class NoteForm(ModelForm):
class Meta:
model = Note
fields = ['ref_id', 'ref_table', 'message']
widgets = {
'ref_id': HiddenInput,
'ref_table': HiddenInput
}

16
middleware.py 100644
View File

@ -0,0 +1,16 @@
import pytz
from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin
class TimezoneMiddleware(MiddlewareMixin):
def process_request(self, request):
tzname = request.session.get('django_timezone')
if tzname:
timezone.activate(pytz.timezone(tzname))
else:
try:
timezone.activate(pytz.timezone(request.user.usersettings.default_timezone))
except:
timezone.deactivate()

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,21 @@
# Generated by Django 2.0.5 on 2018-05-31 03:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('minecraft_manager', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='player',
name='rank',
),
migrations.RemoveField(
model_name='ticket',
name='type',
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 2.0.5 on 2018-05-31 03:35
import django.contrib.auth.models
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
('minecraft_manager', '0002_auto_20180530_2223'),
]
operations = [
migrations.CreateModel(
name='MinecraftManagerUser',
fields=[
],
options={
'permissions': (('coreprotect_partial', 'Can use CoreProtect GUI except Command/Chat searches'), ('coreprotect_full', 'Can use full CoreProtect GUI'), ('bots', 'Can use the bot control page'), ('chat', 'Can use chat page')),
'proxy': True,
'indexes': [],
},
bases=('auth.user',),
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.0.5 on 2018-05-31 03:41
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('minecraft_manager', '0003_minecraftmanageruser'),
]
operations = [
migrations.AlterModelOptions(
name='minecraftmanageruser',
options={'permissions': (('coreprotect_partial', 'Can use CoreProtect GUI except Command/Chat searches'), ('coreprotect_full', 'Can use full CoreProtect GUI'), ('coreprotect_activity', 'Can use CoreProtect Activity Monitor'), ('bots', 'Can use the bot control page'), ('chat', 'Can use chat page'))},
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.0.5 on 2018-06-02 04:11
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('minecraft_manager', '0004_auto_20180530_2241'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='staff',
field=models.ForeignKey(blank=True, limit_choices_to=models.Q(is_active=True), null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_staff', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.0.5 on 2018-06-02 04:12
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('minecraft_manager', '0005_auto_20180601_2311'),
]
operations = [
migrations.AlterField(
model_name='warning',
name='staff',
field=models.ForeignKey(blank=True, limit_choices_to=models.Q(is_active=True), null=True, on_delete=django.db.models.deletion.CASCADE, related_name='warning_staff', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.5 on 2018-07-25 03:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('minecraft_manager', '0006_auto_20180601_2312'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='priority',
field=models.CharField(blank=True, choices=[('L', 'Low'), ('M', 'Medium'), ('H', 'High')], default='L', max_length=1),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.0.5 on 2018-08-20 19:06
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('minecraft_manager', '0007_auto_20180724_2218'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='staff',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_staff', to=settings.AUTH_USER_MODEL),
),
]

View File

438
models.py 100644
View File

@ -0,0 +1,438 @@
from django.db import models
from django.contrib.auth.models import User
from django.db.models import Q
import logging, yaml, pytz, json, os
from django.conf import settings
from datetime import datetime
logger = logging.getLogger(__name__)
class MinecraftManagerUser(User):
class Meta:
proxy = True
permissions = (
('coreprotect_partial', 'Can use CoreProtect GUI except Command/Chat searches'),
('coreprotect_full', 'Can use full CoreProtect GUI'),
('coreprotect_activity', 'Can use CoreProtect Activity Monitor'),
('bots', 'Can use the bot control page'),
('chat', 'Can use chat page'),
)
class UserSettings(models.Model):
RESULT_OPTIONS = (
(10, '10'),
(25, '25'),
(50, '50'),
(100, '100'),
(-1, 'All')
)
THEME_OPTIONS = (
('DE', 'Default'),
('DA', 'Dark'),
('SO', 'Solar'),
('SL', 'Slate')
)
tzs = []
for tz in pytz.common_timezones:
tzs.append((tz, tz))
TIMEZONES = tuple(tzs)
auth_user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
default_results = models.SmallIntegerField("Default Results", default=10, choices=RESULT_OPTIONS)
default_theme = models.CharField("Theme", max_length=2, default='DE', choices=THEME_OPTIONS)
default_timezone = models.CharField("Timezone", max_length=20, default='UTC', choices=TIMEZONES)
search_player_ip = models.BooleanField("Include IP in Player search", default=False)
show_timestamp_chat = models.BooleanField("Show Timestamp By Chat", default=False)
last_ip = models.CharField(max_length=30, default="127.0.0.1", editable=False)
class Meta:
verbose_name = "User Settings"
verbose_name_plural = "User Settings"
def __str__(self):
return "User Settings : %s" % self.auth_user.username
def __repr__(self):
return self.auth_user.username
class UnansweredManager(models.Manager):
def get_queryset(self):
return super(UnansweredManager, self).get_queryset().filter(accepted=None)
class Application(models.Model):
username = models.CharField("Minecraft Username", max_length=20, unique=True)
age = models.PositiveSmallIntegerField()
player_type = models.TextField("What type of player are you?", max_length=300)
ever_banned = models.BooleanField("Have you ever been banned?", default=False)
ever_banned_explanation = models.TextField("If you were previously banned, will you share why?", max_length=300, blank=True, null=True)
reference = models.CharField("Were you referred to our server?", max_length=50, blank=True, null=True)
read_rules = models.CharField("Have you read the rules?", max_length=10)
accepted = models.NullBooleanField()
date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
objects = models.Manager()
unanswered = UnansweredManager()
@property
def status(self):
if self.accepted is not None:
if self.accepted is True:
return "Accepted"
elif self.accepted is False:
return "Denied"
else:
return "Unknown"
else:
return "Unanswered"
@property
def date_display(self):
return str(self.date).split(".")[0]
@property
def json(self):
return {'id': self.id, 'username': self.username, 'age': self.age, 'player_type': self.player_type,
'ever_banned': self.ever_banned, 'ever_banned_explanation': self.ever_banned_explanation,
'reference': self.reference, 'read_rules': self.read_rules, 'status': self.status,
'date': self.date_display}
@property
def skript(self):
return "{0}<:>{1}<:>{2}<:>{3}<:>{4}<:>{5}<:>{6}<:>{7}<:>{8}<:>{9}<:>{10}".format(
"success", self.id, self.username, self.age, self.player_type, self.ever_banned,
self.ever_banned_explanation, self.reference, self.read_rules, self.status, self.date_display
)
def __str__(self):
return "Application: %s" % self.username
def __repr__(self):
return self.username
class Player(models.Model):
auth_user = models.OneToOneField(User, on_delete=models.DO_NOTHING, null=True, blank=True)
uuid = models.CharField(max_length=36, unique=True)
username = models.CharField(max_length=20)
application = models.ForeignKey(Application, on_delete=models.CASCADE, null=True, blank=True)
first_seen = models.DateField(null=True, blank=True)
last_seen = models.DateField(null=True, blank=True)
@property
def is_banned(self):
ban_file = os.path.join(settings.MINECRAFT_BASE_DIR, 'banned-players.json')
with open(ban_file, encoding='utf-8') as f:
bans = json.load(f)
if bans:
for ban in bans:
if self.uuid == ban['uuid']:
return True
return False
else:
return False
@property
def donor_status(self):
def format_time(timestamp):
return datetime.utcfromtimestamp(timestamp).strftime('%m/%d/%Y')
try:
from django_luckperms.models import LuckPermsPlayerPermissions as lppp
donator = lppp.objects.filter(permission='group.donator', uuid=self.uuid)
if donator.exists():
expiry = donator.first().expiry
if expiry > 0:
return "Until {}".format(format_time(expiry))
else:
return "Permanent"
else:
return "None"
except:
pass
try:
pex_file = os.path.join(getattr(settings, 'MINECRAFT_BASE_DIR', ''), 'plugins/PermissionsEx/permissions.yml')
with open(pex_file) as f:
yml = yaml.load(f)
for user in yml['users']:
if user == self.uuid:
if 'donator' in yml['users'][user]['group']:
u = yml['users'][user]
if 'group-donator-until' in u['options']:
unix_time = float(u['options']['group-donator-until'])
return 'Until {0}'.format(format_time(unix_time))
else:
return 'Permanent'
else:
return 'None'
except:
pass
return "N/A"
@property
def ips(self):
ips = []
query = IP.objects.filter(player=self)
for q in query:
ips.append(q.ip)
return " ".join(ips)
def __str__(self):
return self.username + " (" + self.uuid + ")"
class UnclaimedManager(models.Manager):
def get_queryset(self):
return super(UnclaimedManager, self).get_queryset().filter(staff=None, resolved=False)
class ClaimedManager(models.Manager):
def get_queryset(self):
return super(ClaimedManager, self).get_queryset().filter(staff__isnull=False, resolved=False)
class Ticket(models.Model):
PRIORITY = (
('L', 'Low'),
('M', 'Medium'),
('H', 'High')
)
WORLDS = (
('O', 'Overworld'),
('N', 'Nether'),
('E', 'The End')
)
player = models.ForeignKey(Player, related_name="ticket_player", on_delete=models.CASCADE, blank=True, null=True)
message = models.CharField(max_length=500)
priority = models.CharField(max_length=1, choices=PRIORITY, blank=True, default='L')
staff = models.ForeignKey(User, related_name="ticket_staff", on_delete=models.CASCADE, null=True, blank=True)
resolved = models.BooleanField(default=False)
world = models.CharField(max_length=1, choices=WORLDS, blank=True, null=True)
x = models.CharField(max_length=20, blank=True, null=True)
y = models.CharField(max_length=20, blank=True, null=True)
z = models.CharField(max_length=20, blank=True, null=True)
date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
objects = models.Manager()
unclaimed = UnclaimedManager()
claimed = ClaimedManager()
@property
def location(self):
return "{0}, {1}, {2} in {3}".format(self.x, self.y, self.z, self.world_label)
@property
def world_label(self):
for W in self.WORLDS:
if W[0] == self.world:
return W[1]
return ""
@property
def resolved_label(self):
if self.resolved:
return "Resolved"
else:
return "Unresolved"
@property
def snippet(self):
if len(self.message) > 50:
return self.message[:50] + "..."
else:
return self.message
@property
def issuer(self):
if self.player:
pl = Player.objects.get(id=self.player_id)
return pl.username
else:
return "Unknown"
@property
def claimed_by(self):
if self.staff:
pl = User.objects.get(id=self.staff_id)
return pl.username
else:
return "Unclaimed"
@property
def is_claimed(self):
if self.staff:
return True
else:
return False
@property
def priority_display(self):
for P in self.PRIORITY:
if P[0] == self.priority:
return P[1]
return "Unknown"
@staticmethod
def priority_code_to_display(piority_code):
for P in Ticket.PRIORITY:
if P[0] == piority_code:
return P[1]
return "Unknown"
@property
def date_display(self):
return str(self.date).split(".")[0]
def __str__(self):
username = "Unknown"
try:
pl = Player.objects.get(id=self.player_id)
username = pl.username
except:
pass
return "Ticket from %s" % username
class Warning(models.Model):
SEVERITY = (
('L', 'Low'),
('M', 'Medium'),
('H', 'High')
)
player = models.ForeignKey(Player, related_name="warning_player", on_delete=models.CASCADE)
message = models.CharField(max_length=200)
severity = models.CharField(max_length=1, choices=SEVERITY)
staff = models.ForeignKey(User, related_name="warning_staff", on_delete=models.CASCADE, null=True, blank=True, limit_choices_to=Q(is_active=True))
date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
@property
def snippet(self):
if len(self.message) > 50:
return self.message[:50] + "..."
else:
return self.message
@property
def severity_display(self):
for S in self.SEVERITY:
if S[0] == self.severity:
return S[1]
return "Unknown"
@staticmethod
def severity_code_to_display(severity_code):
for S in Warning.SEVERITY:
if S[0] == severity_code:
return S[1]
return "Unknown"
@staticmethod
def resolve(severity):
for S in Warning.SEVERITY:
if severity.lower() in (S[0].lower(), S[1].lower()):
return S[0]
return 'L'
@property
def issuer(self):
if self.staff:
pl = User.objects.get(id=self.staff_id)
return pl.username
else:
return "System"
@property
def issuee(self):
if self.player:
pl = Player.objects.get(id=self.player_id)
return pl.username
else:
return "Unknown"
@property
def date_display(self):
return str(self.date).split(".")[0]
def __str__(self):
username = "Unknown"
try:
pl = Player.objects.get(id=self.player_id)
username = pl.username
except:
pass
return "Warning for %s" % username
class IP(models.Model):
player = models.ForeignKey(Player, on_delete=models.CASCADE)
ip = models.CharField(max_length=30)
class Meta:
verbose_name = "IP"
verbose_name_plural = "IPs"
@property
def associated(self):
ips = IP.objects.filter(ip=self.ip)
players = []
for ip in ips:
if self.player != ip.player:
players.append(ip.player)
if players:
return players
else:
return None
def __str__(self):
player = Player.objects.get(id=self.player_id)
return player.username + " - " + self.ip
class Alert(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
message = models.TextField(max_length=1000)
seen = models.BooleanField(default=False)
date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
@property
def snippet(self):
if len(self.message) > 50:
return self.message[:50] + "..."
else:
return self.message
def __str__(self):
return "Alert for %s" % self.user.username
class Note(models.Model):
NOTABLE = (
('PL', 'Player'),
('TI', 'Ticket'),
('WA', 'Warning')
)
author = models.ForeignKey(User, on_delete=models.CASCADE)
ref_table = models.CharField(max_length=2, choices=NOTABLE)
ref_id = models.CharField(max_length=4)
message = models.TextField(max_length=1000)
last_update = models.DateTimeField(auto_now_add=True, blank=True, null=True)
date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
@property
def ref_model(self):
if self.ref_table == 'PL':
return Player
elif self.ref_table == 'TI':
return Ticket
elif self.ref_table == 'WA':
return Warning
def __str__(self):
ref = self.ref_model.objects.get(id=self.ref_id)
return "Note: {0}".format(ref)

View File

@ -0,0 +1,25 @@
jQuery.fn.filterByText = function(textbox) {
return this.each(function() {
var select = this;
var options = [];
$(select).find('option').each(function() {
options.push({value: $(this).val(), text: $(this).text()});
});
$(select).data('options', options);
$(textbox).bind('change keyup', function() {
var options = $(select).empty().data('options');
var search = $.trim($(this).val());
var regex = new RegExp(search,"gi");
$.each(options, function(i) {
var option = options[i];
if(option.text.match(regex) !== null) {
$(select).append(
$('<option>').text(option.text).val(option.value)
);
}
});
});
});
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

14
static/chart.min.js vendored 100644

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,138 @@
/*
* Base structure
*/
/* Move down content because we have a fixed navbar that is 50px tall */
body {
padding-top: 50px;
}
.popup {
opacity: .9;
position: absolute;
right: 3em;
width: 20em;
}
/*
* Global add-ons
*/
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
/*
* Top navigation
* Hide default border to remove 1px line.
*/
.navbar-fixed-top {
border: 0;
}
.navbar-brand {
cursor: default !important;
}
.navbar-brand:hover {
cursor: default !important;
color: #ffffff !important;
}
/*
* Sidebar
*/
/* Hide for mobile, show later */
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #222222;
}
}
/* Sidebar navigation */
.nav-sidebar {
margin-right: -21px; /* 20px padding + 1px border */
margin-bottom: 20px;
margin-left: -20px;
}
.nav-sidebar > li > a {
padding-right: 20px;
padding-left: 20px;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
color: #fff;
background-color: #428bca;
}
/*
* Main content
*/
.main {
padding: 20px;
}
@media (min-width: 768px) {
.main {
padding-right: 40px;
padding-left: 40px;
}
}
.main .page-header {
margin-top: 0;
}
tr[data-id] {
cursor: pointer;
}
/*
* Placeholder dashboard ideas
*/
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder {
margin-bottom: 20px;
}
.placeholder img {
display: inline-block;
border-radius: 50%;
}
.center {
text-align: center;
}
select {
background-color: grey;
}
option {
background-color: grey;
}
.well {
color: black;
}

View File

@ -0,0 +1,138 @@
/*
* Base structure
*/
/* Move down content because we have a fixed navbar that is 50px tall */
body {
padding-top: 50px;
}
.popup {
opacity: .9;
position: absolute;
right: 3em;
width: 20em;
}
/*
* Global add-ons
*/
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
/*
* Top navigation
* Hide default border to remove 1px line.
*/
.navbar-fixed-top {
border: 0;
}
.navbar-brand {
cursor: default !important;
}
.navbar-brand:hover {
cursor: default !important;
color: #ffffff !important;
}
/*
* Sidebar
*/
/* Hide for mobile, show later */
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #272b30;
}
}
/* Sidebar navigation */
.nav-sidebar {
margin-right: -21px; /* 20px padding + 1px border */
margin-bottom: 20px;
margin-left: -20px;
}
.nav-sidebar > li > a {
padding-right: 20px;
padding-left: 20px;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
color: #fff;
background-color: #428bca;
}
/*
* Main content
*/
.main {
padding: 20px;
}
@media (min-width: 768px) {
.main {
padding-right: 40px;
padding-left: 40px;
}
}
.main .page-header {
margin-top: 0;
}
tr[data-id] {
cursor: pointer;
}
/*
* Placeholder dashboard ideas
*/
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder {
margin-bottom: 20px;
}
.placeholder img {
display: inline-block;
border-radius: 50%;
}
.center {
text-align: center;
}
select {
background-color: grey;
}
option {
background-color: grey;
}
.well {
color: black;
}

View File

@ -0,0 +1,130 @@
/*
* Base structure
*/
/* Move down content because we have a fixed navbar that is 50px tall */
body {
padding-top: 50px;
}
.popup {
opacity: .9;
position: absolute;
right: 3em;
width: 20em;
}
/*
* Global add-ons
*/
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
/*
* Top navigation
* Hide default border to remove 1px line.
*/
.navbar-fixed-top {
border: 0;
}
.navbar-brand {
cursor: default !important;
}
.navbar-brand:hover {
cursor: default !important;
color: #ffffff !important;
}
/*
* Sidebar
*/
/* Hide for mobile, show later */
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #002b36;
}
}
/* Sidebar navigation */
.nav-sidebar {
margin-right: -21px; /* 20px padding + 1px border */
margin-bottom: 20px;
margin-left: -20px;
}
.nav-sidebar > li > a {
padding-right: 20px;
padding-left: 20px;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
color: #fff;
background-color: #428bca;
}
/*
* Main content
*/
.main {
padding: 20px;
}
@media (min-width: 768px) {
.main {
padding-right: 40px;
padding-left: 40px;
}
}
.main .page-header {
margin-top: 0;
}
tr[data-id] {
cursor: pointer;
}
/*
* Placeholder dashboard ideas
*/
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder {
margin-bottom: 20px;
}
.placeholder img {
display: inline-block;
border-radius: 50%;
}
.center {
text-align: center;
}
.well {
color: black;
}

View File

@ -0,0 +1,127 @@
/*
* Base structure
*/
/* Move down content because we have a fixed navbar that is 50px tall */
body {
padding-top: 50px;
}
.popup {
opacity: .9;
position: absolute;
right: 3em;
width: 20em;
}
/*
* Global add-ons
*/
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
/*
* Top navigation
* Hide default border to remove 1px line.
*/
.navbar-fixed-top {
border: 0;
}
.navbar-brand {
cursor: default !important;
}
.navbar-brand:hover {
cursor: default !important;
color: #9d9d9d !important;
}
/*
* Sidebar
*/
/* Hide for mobile, show later */
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #f5f5f5;
border-right: 1px solid #eee;
}
}
/* Sidebar navigation */
.nav-sidebar {
margin-right: -21px; /* 20px padding + 1px border */
margin-bottom: 20px;
margin-left: -20px;
}
.nav-sidebar > li > a {
padding-right: 20px;
padding-left: 20px;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
color: #fff;
background-color: #428bca;
}
/*
* Main content
*/
.main {
padding: 20px;
}
@media (min-width: 768px) {
.main {
padding-right: 40px;
padding-left: 40px;
}
}
.main .page-header {
margin-top: 0;
}
tr[data-id] {
cursor: pointer;
}
/*
* Placeholder dashboard ideas
*/
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder {
margin-bottom: 20px;
}
.placeholder img {
display: inline-block;
border-radius: 50%;
}
.center {
text-align: center;
}

View File

@ -0,0 +1,70 @@
#bg {
width:100%;
min-height:100%;
height:auto!important;
position:fixed;
top:0px;
left:0px;
overflow:hidden;
border:0px;
z-index:-9;
float:left;
}
#form {
background: rgba(255, 255, 255, .9);
margin: auto;
display: block;
width: 35%;
padding-top: 1em;
padding-bottom: 1em;
padding-left: 5em;
border-radius: 1em;
top: 50%;
left: 50%;
}
.dataTables_wrapper {
width: 95%;
}
#appFormOld {
margin: auto;
display: block;
width: 40%;
height: 98%;
border-radius: 1em;
}
#viewMap {
float: right;
border: 0px;
margin: 5px 5px 0 0;
position: absolute;
top: 0px;
right: 21px;
}
#goMap {
float: right;
border: 0px;
margin: 5px 5px 0 0;
position: absolute;
top: 0px;
right: 0px;
}
#display {
color: white;
background: rgba(0,0,0,.5);
border-radius: 1em;
padding: 1em;
}
#display input {
color: black;
}
.rule {
margin-bottom: .5em;
}

BIN
static/favicon.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

2
static/jquery-3.3.1.min.js vendored 100644

File diff suppressed because one or more lines are too long

1
static/load.svg 100644
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg width='200px' height='200px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-gears"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><g transform="translate(-20,-20)"><path d="M79.9,52.6C80,51.8,80,50.9,80,50s0-1.8-0.1-2.6l-5.1-0.4c-0.3-2.4-0.9-4.6-1.8-6.7l4.2-2.9c-0.7-1.6-1.6-3.1-2.6-4.5 L70,35c-1.4-1.9-3.1-3.5-4.9-4.9l2.2-4.6c-1.4-1-2.9-1.9-4.5-2.6L59.8,27c-2.1-0.9-4.4-1.5-6.7-1.8l-0.4-5.1C51.8,20,50.9,20,50,20 s-1.8,0-2.6,0.1l-0.4,5.1c-2.4,0.3-4.6,0.9-6.7,1.8l-2.9-4.1c-1.6,0.7-3.1,1.6-4.5,2.6l2.1,4.6c-1.9,1.4-3.5,3.1-5,4.9l-4.5-2.1 c-1,1.4-1.9,2.9-2.6,4.5l4.1,2.9c-0.9,2.1-1.5,4.4-1.8,6.8l-5,0.4C20,48.2,20,49.1,20,50s0,1.8,0.1,2.6l5,0.4 c0.3,2.4,0.9,4.7,1.8,6.8l-4.1,2.9c0.7,1.6,1.6,3.1,2.6,4.5l4.5-2.1c1.4,1.9,3.1,3.5,5,4.9l-2.1,4.6c1.4,1,2.9,1.9,4.5,2.6l2.9-4.1 c2.1,0.9,4.4,1.5,6.7,1.8l0.4,5.1C48.2,80,49.1,80,50,80s1.8,0,2.6-0.1l0.4-5.1c2.3-0.3,4.6-0.9,6.7-1.8l2.9,4.2 c1.6-0.7,3.1-1.6,4.5-2.6L65,69.9c1.9-1.4,3.5-3,4.9-4.9l4.6,2.2c1-1.4,1.9-2.9,2.6-4.5L73,59.8c0.9-2.1,1.5-4.4,1.8-6.7L79.9,52.6 z M50,65c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15s15,6.7,15,15C65,58.3,58.3,65,50,65z" fill="#ffd444"><animateTransform attributeName="transform" type="rotate" from="90 50 50" to="0 50 50" dur="1s" repeatCount="indefinite"></animateTransform></path></g><g transform="translate(20,20) rotate(15 50 50)"><path d="M79.9,52.6C80,51.8,80,50.9,80,50s0-1.8-0.1-2.6l-5.1-0.4c-0.3-2.4-0.9-4.6-1.8-6.7l4.2-2.9c-0.7-1.6-1.6-3.1-2.6-4.5 L70,35c-1.4-1.9-3.1-3.5-4.9-4.9l2.2-4.6c-1.4-1-2.9-1.9-4.5-2.6L59.8,27c-2.1-0.9-4.4-1.5-6.7-1.8l-0.4-5.1C51.8,20,50.9,20,50,20 s-1.8,0-2.6,0.1l-0.4,5.1c-2.4,0.3-4.6,0.9-6.7,1.8l-2.9-4.1c-1.6,0.7-3.1,1.6-4.5,2.6l2.1,4.6c-1.9,1.4-3.5,3.1-5,4.9l-4.5-2.1 c-1,1.4-1.9,2.9-2.6,4.5l4.1,2.9c-0.9,2.1-1.5,4.4-1.8,6.8l-5,0.4C20,48.2,20,49.1,20,50s0,1.8,0.1,2.6l5,0.4 c0.3,2.4,0.9,4.7,1.8,6.8l-4.1,2.9c0.7,1.6,1.6,3.1,2.6,4.5l4.5-2.1c1.4,1.9,3.1,3.5,5,4.9l-2.1,4.6c1.4,1,2.9,1.9,4.5,2.6l2.9-4.1 c2.1,0.9,4.4,1.5,6.7,1.8l0.4,5.1C48.2,80,49.1,80,50,80s1.8,0,2.6-0.1l0.4-5.1c2.3-0.3,4.6-0.9,6.7-1.8l2.9,4.2 c1.6-0.7,3.1-1.6,4.5-2.6L65,69.9c1.9-1.4,3.5-3,4.9-4.9l4.6,2.2c1-1.4,1.9-2.9,2.6-4.5L73,59.8c0.9-2.1,1.5-4.4,1.8-6.7L79.9,52.6 z M50,65c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15s15,6.7,15,15C65,58.3,58.3,65,50,65z" fill="#3771a2"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="90 50 50" dur="1s" repeatCount="indefinite"></animateTransform></path></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

48
static/signin.css 100644
View File

@ -0,0 +1,48 @@
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-control-top {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-control-middle {
border-radius: 0;
}
.form-control-bottom {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.form-control-alone {
border-radius: 10px;
}
.form-control-button {
margin-top: 10px;
}

BIN
static/world.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

BIN
static/world_go.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

View File

@ -0,0 +1,11 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load template_settings %}
{% block section %}
<a href="{% template_settings 'COREPROTECT_ACTIVITY_URL' %}?username={{ user.username }}" target="_blank">Fullscreen</a>
<iframe id="cpgui" width="100%" height="750px" src="{% template_settings 'COREPROTECT_ACTIVITY_URL' %}?username={{ user.username }}">Loading...</iframe>
<script>
var height = $(window).height();
//$("#cpgui").height(height * .8);
</script>
{% endblock section %}

View File

@ -0,0 +1,45 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% load static %}
{% block section %}
<div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table">
<thead>
<tr>
<th>Message</th>
<th>Seen</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{% for alert in alerts %}
<tr {% if alert.seen is True %}class="success"{% endif %} data-id="{{ alert.id }}">
<td>{{ alert.snippet }}</td>
<td>{{ alert.seen }}</td>
<td>{{ alert.date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if unseen > 0 %}
<form method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<button type="submit" class="btn btn-primary" name="action" value="mar">Mark all as read</button>
</form>
{% endif %}
</div>
<script>
$(document).ready(function() {
$("#model-table").DataTable({
'lengthMenu': [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, "All" ] ],
'initComplete': function(settings, json) {
$("#content").show();
},
'order': [],
{% if user.usersettings %}
'pageLength': {{ user.usersettings.default_results }},
{% endif %}
});
});
</script>
{% endblock section %}

View File

@ -0,0 +1,23 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% block section %}
<div id="content">
<h2 class="sub-header">Alert Info</h2>
<div class="row">
<div class="col-xs-6 col-md-4">
<p>Message: </p>
<p class="well">{{ alert.message }}</p>
</div>
<div class="col-xs-12 col-md-8">
<p>Date: {{ alert.date }}</p>
</div>
</div>
{% if alert.seen is True %}
<div class="row">
<form method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<button type="submit" class="btn btn-primary" name="action" value="mu">Mark Unread</button>
</form>
</div>
{% endif %}
</div>
{% endblock section %}

View File

@ -0,0 +1,46 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load static %}
{% block section %}
<div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table">
<thead>
<tr>
<th>App ID</th>
<th>Username</th>
<th>Age</th>
<th>Past Ban</th>
<th>Status</th>
<th>Date Applied</th>
</tr>
</thead>
<tbody>
{% for app in applications %}
<tr {% if app.accepted is True %}class="success"{% endif %}{% if app.accepted is False %}class="danger"{% endif %} data-id="{{ app.id }}">
<td>{{ app.id }}</td>
<td>{{ app.username }}</td>
<td>{{ app.age }}</td>
<td>{{ app.ever_banned }}</td>
<td>{{ app.status }}</td>
<td>{{ app.date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{% url 'reference' %}">Reference Report</a>
</div>
<script>
$(document).ready(function() {
$("#model-table").DataTable({
'lengthMenu': [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, "All" ] ],
'initComplete': function(settings, json) {
$("#content").show();
},
'order': [],
{% if request.user.usersettings %}
'pageLength': {{ request.user.usersettings.default_results }},
{% endif %}
});
});
</script>
{% endblock section %}

View File

@ -0,0 +1,43 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% block section %}
<div id="content">
<h2 class="sub-header">Viewing application for {{ application.username }}</h2>
<div class="row">
<div class="col-xs-6 col-md-4">
<!-- <p>Username: {{ application.username }}</p> -->
<p>Age: {{ application.age }}</p>
<p>Application Date: {{ application.date }}</p>
<p>Type Of Player:</p>
<p class="well">{{ application.player_type }}</p>
{% if application.accepted is not null %}
<p>Status: {{ application.status }}</p>
{% else %}
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<button class="btn btn-primary" type="submit" name="accept" value="accept">Accept</button>
<button class="btn btn-danger" type="submit" name="accept" value="deny">Deny</button>
</form>
{% endif %}
</div>
<div class="col-xs-12 col-md-8">
<p>Ever Been Banned: {{ application.ever_banned }}</p>
{% if application.ever_banned %}
<p>Explanation: {{ application.ever_banned_explanation }}</p>
{% endif %}
{% if application.reference %}
<p>Reference: {{ application.reference }}</p>
{% endif %}
<p>Read The Rules: {{ application.read_rules }}</p>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-md-4">
</div>
<div class="col-xs-12 col-md-8">
</div>
</div>
</div>
{% endblock section %}

View File

@ -0,0 +1,45 @@
{% extends "minecraft_manager/dashboard.html" %}
{% block section %}
<div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table">
<thead>
<tr>
<th>Player</th>
<th>Message</th>
<td>Issued By</td>
<th>Expires</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{% for ban in bans %}
<tr {% if ban.id > 0 %} data-url="{% url 'player' %}" data-id="{{ ban.id }}" {% endif %}>
<!-- <td>{{ ban.id }}</td> -->
<td>{{ ban.name }}</td>
<td>{{ ban.reason }}</td>
<td>{{ ban.source }}</td>
<td>{{ ban.expires }}</td>
<td>{{ ban.created }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
$(document).ready(function() {
$("#model-table").DataTable({
'lengthMenu': [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, "All" ] ],
'initComplete': function(settings, json) {
$("#content").show();
$(this).css('width', '');
},
'order': [],
{% if request.user.usersettings %}
'pageLength': {{ request.user.usersettings.default_results }},
{% endif %}
});
});
</script>
{% endblock section %}

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
{% load static %}
{% load template_settings %}
<html>
<head>
<!-- favicon -->
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.png' %}"/>
<!-- Latest compiled and minified JQuery -->
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- Latest compiled and minified CSS -->
{% block bootstrap %}{% endblock bootstrap %}
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!-- DataTables -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.13/css/dataTables.bootstrap.min.css"></link>
<script src="https://cdn.datatables.net/1.10.13/js/jquery.dataTables.min.js"></script>
<script src="{% static "FilterByText.js" %}"></script>
{% block head %}{% endblock head %}
{% template_settings 'WINDOW_TITLE' as WINDOW_TITLE %}
<title>{% if WINDOW_TITLE %}{{ WINDOW_TITLE }}{% else %}MCM{% endif %}</title>
</head>
<body>
{% block content %}{% endblock content %}
</body>
<script>
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
});
String.prototype.htmlEscape = function() {
return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
</script>
</html>

View File

@ -0,0 +1,17 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% load getattribute %}
{% block section %}
<div id="content">
<!-- {{ form.status }} -->
{% for bot in form.bots %}
<p>{{ bot.name }}: <span class="label {% if form|getattribute:bot.name is True %}label-success{% else %}label-danger{% endif %}">{% if form|getattribute:bot.name is True %}Started{% else %}Stopped{% endif %}</span></p>
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<button class="btn btn-primary{% if form|getattribute:bot.name is True %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="start" {% if form|getattribute:bot.name is True %} disabled="disabled"{% endif %}>Start</button>
<button class="btn btn-primary{% if form|getattribute:bot.name is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="stop" {% if form|getattribute:bot.name is False %}disabled="disabled"{% endif %}>Stop</button>
<button class="btn btn-primary{% if form|getattribute:bot.name is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="restart" {% if form|getattribute:bot.name is False %}disabled="disabled"{% endif %}>Restart</button>
</form>
<br/>
{% endfor %}
</div>
{% endblock section %}

View File

@ -0,0 +1,121 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% block section %}
<div id="content">
<div class="row">
<div class="col-xs-8 col-md-5">
<h3>Global Chat</h3>
<div id="global_chat" style="overflow:scroll; height:40em;">
Loading...
</div>
<br/>
<div class="input-group">
<span class="input-group-addon">Auto-Scroll</span>
<span class="input-group-addon">
<input id="global_scroll" type="checkbox" aria-label="Auto-Scroll" checked="checked">
</span>
<input id="global_input" type="text" class="form-control" placeholder="Global Chat...">
<span class="input-group-btn">
<button id="global_button" onClick="chat('global');" class="btn btn-secondary" type="button">Send</button>
</span>
</div>
</div>
<div class="col-xs-2 col-md-2">
<h3>Players <span id="player_online">N</span>/<span id="player_max">A</span></h3>
<div id="player_list">
<div>Loading...</div>
</div>
</div>
{% if staff %}
<div class="col-xs-8 col-md-5">
<h3>Staff Chat</h3>
<div id="staff_chat" style="overflow:scroll; height:40em;">
Loading...
</div>
<br/>
<div class="input-group">
<span class="input-group-addon">Auto-Scroll</span>
<span class="input-group-addon">
<input id="staff_scroll" type="checkbox" aria-label="Auto-Scroll" checked="checked">
</span>
<input id="staff_input" type="text" class="form-control" placeholder="Staff Chat...">
<span class="input-group-btn">
<button id="staff_button" onClick="chat('staff');" class="btn btn-secondary" type="button">Send</button>
</span>
</div>
</div>
{% endif %}
</div>
</div>
<script>
$(document).ready(function() {
$("#global_input").keypress(function(e) {
if (e.keyCode === 13) {
$("#global_button").click();
}
});
scrollGlobal();
{% if staff %}
$("#staff_input").keypress(function(e) {
if (e.keyCode === 13) {
$("#staff_button").click();
}
});
scrollStaff();
{% endif %}
setInterval(function() {
$.ajax({
url: "{% url "api-web" 'log' %}?api={{ api }}",
success: function(res, status, xhr) {
$("#global_chat").html(res.html.global);
if ($("#global_scroll").prop("checked")) {
scrollGlobal();
}
{% if staff %}
$("#staff_chat").html(res.html.staff);
if ($("#staff_scroll").prop("checked")) {
scrollStaff();
}
{% endif %}
$('[data-toggle="tooltip"]').tooltip();
}
});
$.ajax({
url: "{% url "api-web" 'online' %}?api={{ api }}",
success: function(res, status, xhr) {
$("#player_list").html(res.html);
$("#player_online").html(res.query.online);
$("#player_max").html(res.query.max);
}
});
}, 1000);
});
function chat(type) {
var msg = $("#" + type + "_input").val();
if (msg != null && msg != "") {
$.ajax({
type: 'POST',
data: {'csrfmiddlewaretoken': '{% get_csrf_token request %}','chat': type, 'message': msg}
});
$("#" + type + "_input").val('');
}
}
function scrollGlobal() {
var global_chat = document.getElementById("global_chat");
global_chat.scrollTop = global_chat.scrollHeight;
}
{% if staff %}
function scrollStaff() {
var staff_chat = document.getElementById("staff_chat");
staff_chat.scrollTop = staff_chat.scrollHeight;
}
{% endif %}
</script>
{% endblock section %}

View File

@ -0,0 +1,11 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load template_settings %}
{% block section %}
<a href="{% template_settings 'COREPROTECT_WEB_URL' %}?username={{ user.username }}" target="_blank">Fullscreen</a>
<iframe id="cpgui" width="100%" height="750px" src="{% template_settings 'COREPROTECT_WEB_URL' %}?username={{ user.username }}">Loading...</iframe>
<script>
var height = $(window).height();
//$("#cpgui").height(height * .8);
</script>
{% endblock section %}

View File

@ -0,0 +1,214 @@
{% extends "minecraft_manager/base.html" %}
{% load static %}
{% load csrf_html %}
{% load sidebar %}
{% load template_settings %}
{% block bootstrap %}
{% if user.usersettings.default_theme == 'DA' %}
<link rel="stylesheet" href="{% static "bootstrap-dark.css" %}">
{% elif user.usersettings.default_theme == 'SO' %}
<link rel="stylesheet" href="{% static "bootstrap-solar.css" %}">
{% elif user.usersettings.default_theme == 'SL' %}
<link rel="stylesheet" href="{% static "bootstrap-slate.css" %}">
{% else %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
{% endif %}
{% endblock bootstrap %}
{% block head %}
{% if user.usersettings.default_theme == 'DA' %}
<link rel="stylesheet" href="{% static "dashboard-dark.css" %}">
{% elif user.usersettings.default_theme == 'SO' %}
<link rel="stylesheet" href="{% static "dashboard-solar.css" %}">
{% elif user.usersettings.default_theme == 'SL' %}
<link rel="stylesheet" href="{% static "dashboard-slate.css" %}">
{% else %}
<link rel="stylesheet" href="{% static "dashboard.css" %}">
{% endif %}
<script src="{% static "chart.min.js" %}"></script>
{% endblock head %}
{% block content %}
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
{% template_settings 'DASHBOARD_TITLE' as DASHBOARD_TITLE %}
<a class="navbar-brand">{% if DASHBOARD_TITLE %}{{ DASHBOARD_TITLE }}{% else %}Minecraft Manager{% endif %}</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
{% template_settings 'COREPROTECT_WEB_URL' as CPW %}
{% template_settings 'COREPROTECT_ACTIVITY_URL' as CPA %}
{% if CPW %}
{% if perms.auth.coreprotect_activity and CPA %}
<li class="dropdown">
<a class="dropdown-toggle " href="#" role="button" data-toggle="dropdown" id="cpDropdown">CoreProtect</a>
<ul class="dropdown-menu" aria-labelledby="cpDropdown">
<li><a href="{% url 'coreprotect' %}">Web GUI</a></li>
<li><a href="{% url 'activity' %}">Activity Monitor</a></li>
</ul>
</li>
{% elif perms.auth.coreprotect_partial or perms.auth.coreprotect_full %}
<li><a class="dropdown-item btn" href="{% url 'coreprotect' %}">CoreProtect</a></li>
{% endif %}
{% endif %}
<li class="dropdown">
<a class="dropdown-toggle " href="#" role="button" data-toggle="dropdown" id="accountDropdown">{{ user.username }}</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="accountDropdown">
<li><a style="cursor:pointer;" data-toggle="modal" data-target="#settingsModal">Settings</a></li>
<li><a style="cursor:pointer;" data-toggle="modal" data-target="#passwordModal">Change Password</a></li>
<li><a href="{% url "logout" %}">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
{% autoescape off %}{% get_sidebar current_app|safe request %}{% endautoescape %}
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">{{ current_app|capfirst }} {% block after_h1 %}{% endblock %}</h1>
<div id="alert" class="popup"></div>
{% block section %}{% endblock section %}
</div>
</div>
</div>
<div id="settingsModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">User Settings</h4>
</div>
<form id="settingsForm" method="POST" action="{% url 'api-web' keyword='settings' %}">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<div class="modal-body">
{% get_form 'usersettings' request %}
{% if user.is_staff %}
<br/><a target="_blank" href="{% url "admin:index" %}">Admin Site</a>
{% endif %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="saveSettings();">Save</button>
</div>
</form>
</div>
</div>
</div>
<div id="passwordModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Change Password</h4>
</div>
<form id="passwordForm" method="POST" action="{% url 'api-web' keyword='password' %}">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<div id="passwordDiv" class="modal-body">
{% get_form 'password' request %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="savePassword();">Save</button>
</div>
</form>
</div>
</div>
</div>
<script>
$('.table.link-table > tbody > tr').click(function() {
var base_url = $(location).attr('href').split("?")[0];
var id = $(this).attr('data-id');
var url = $(this).attr('data-url');
if (url != null && id != null) {
if (url.endsWith("/")) {
window.location.href = url + id
} else {
window.location.href = url + "/" + id
}
} else if (id != null) {
if (base_url.endsWith("/")) {
window.location.href = base_url + id
} else {
window.location.href = base_url + "/" + id
}
}
});
function showAlert(message, flavor) {
var $alert = $("#alert");
$alert.hide();
$alert.html(message);
$alert.removeClass();
$alert.addClass("popup alert alert-" + flavor);
$alert.fadeIn().delay(1000).fadeOut();
}
function saveSettings() {
var form = $("#settingsForm").serialize();
form = form.replace("search_player_ip=on", "search_player_ip=True");
if (!form.includes("search_player_ip")) {
form += "&search_player_ip=False";
}
form = form.replace("show_timestamp_chat=on", "show_timestamp_chat=True");
if (!form.includes("show_timestamp_chat")) {
form += "&show_timestamp_chat=False";
}
$.ajax({
type: 'POST',
url: '{% url 'api-web' keyword='settings' %}',
data: form,
success: function() {
location.reload()
}
});
}
function savePassword() {
var form = $("#passwordForm").serialize();
$.ajax({
type: 'POST',
url: '{% url 'api-web' keyword='password' %}',
data: form,
success: function(data) {
if (data === "success") {
alert("Password Changed!");
location.reload();
} else {
$("#passwordDiv").html(data);
}
}
});
}
$(document).ready(function() {
$(".paginate_button").each(function() {
$(this).addClass("btn");
});
$("#model-table").on('draw.dt', function() {
$(".paginate_button").each(function() {
$(this).addClass("btn");
});
});
});
</script>
{% endblock content %}

View File

@ -0,0 +1,25 @@
{% extends 'minecraft_manager/external/base.html' %}
{% block title %}Application Form{% endblock %}
{% block h1 %}Application{% endblock %}
{% block form_top %}
<h3>Rules</h3>
{% for rule in rules %}
<div class="rule">{{ rule }}</div>
{% endfor %}
{% endblock %}
{% if valid is True %}
{% block valid %}
<h2>Thanks for applying!
<br/>
We will get back to you soon.
<br/>
Consider joining our <a href="https://discord.gg/{{ map.discord_invite }}">Discord</a></h2>
{% endblock %}
{% endif %}
{% block submit %}Apply{% endblock %}

View File

@ -0,0 +1,50 @@
{% load static %}
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.png' %}"/>
<script src="{% static 'jquery-3.3.1.min.js' %}"></script>
<script src="{% static 'FilterByText.js' %}"></script>
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
{% if captcha %}
<script src='https://www.google.com/recaptcha/api.js'></script>
{% endif %}
<link rel="stylesheet" href="{% static 'external.css' %}">
<link rel="stylesheet" href="//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
</head>
<body>
{% include 'minecraft_manager/external/map.html' %}
<div id="form"><h1>{% block h1 %}{% endblock %}</h1>
{% block form_top %}{% endblock %}
{% if valid is True %}
{% block valid %}{% endblock %}
{% else %}
<form method="{% block method %}POST{% endblock %}">
{% block form %}{{ form }}{% endblock %}<br/><br/>
{% if captcha %}<div class="g-recaptcha" data-sitekey="6LeLcGEUAAAAAMMpHR-7VL6Q3nNV5v03S5sq1Ddz"></div>{% endif %}
<button type="submit">{% block submit %}Submit{% endblock %}</button>
</form>
{% endif %}
{% block form_bottom %}{% endblock %}
</div>
</body>
<script>
$(document).ready(function() {
var $dataTable = $("#dataTable");
if ($dataTable.length) {
$dataTable.hide();
$dataTable.DataTable({
'initComplete': function(settings, json) {
$dataTable.show();
}
});
}
var $filter = $("#filter");
if ($filter.length) {
$("#stats").filterByText($filter);
}
});
</script>
</html>

View File

@ -0,0 +1,26 @@
{% load static %}
{% if map.dynmap_static_url %}
<iframe id='bg' scrolling='no' allowtransparency='true' src='{{ map.static_url }}' frameborder='0'>Loading...</iframe>
{% elif map.dynmap_url %}
<iframe id='bg' scrolling='no' allowtransparency='true' src='{{ map.map_url }}' frameborder='0'>Loading...</iframe>
{% else %}
<img id='bg' src="{% static 'background.png' %}" />
{% endif %}
{% if map.dynmap_static_url or map.dynmap_url %}
<!-- Images -->
<a id='viewMap' href='javascript:;' hidden><img src="{% static 'world.png' %}" /></a>
<a id='goMap' href='{% if map.dynmap_url %}{{ map.map_url }}{% else %}#{% endif %}' target='{{ map.target }}' hidden><img src="{% static 'world_go.png' %}" /></a>
<script>
$(document).ready(function() {
var $viewMap = $("#viewMap");
$viewMap.show();
$("#goMap").show();
$viewMap.click(function() {
$("#form").toggle();
});
});
</script>
{% endif %}

View File

@ -0,0 +1,46 @@
{% extends 'minecraft_manager/external/base.html' %}
{% load pretty_print %}
{% block title %}Stats Search{% endblock %}
{% block h1 %}Stats{% endblock %}
{% block method %}GET{% endblock %}
{% block form %}
<label>Filter: <input id="filter" /></label>
<br/>
<label>Stat: <br/>
<select id="stats" name="stat">
{% for stat in stats %}
<option {% if request.GET.stat == stat %}selected{% endif %} value="{{ stat }}">{{ stat|pretty_stat }}</option>
{% endfor %}
</select>
</label>
{% endblock %}
{% block submit %}Search{% endblock %}
{% block form_bottom %}
{% if results %}
<table id="dataTable" class="stripe row-border compact">
<thead>
<tr>
<th>Rank</th>
<th>Username</th>
<th>Score</th>
</tr>
</thead>
<tbody>
{% for result in results %}
<tr>
<td>{{ forloop.counter }}</td>
<td><a href="{% url 'external-stats-player' %}?uuid={{ result.uuid }}">{{ result.username }}</a></td>
<td>{{ result.score|pretty_score }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,44 @@
{% extends 'minecraft_manager/external/base.html' %}
{% load pretty_print %}
{% block title %}Stats Search{% endblock %}
{% block h1 %}Stats{% endblock %}
{% block method %}GET{% endblock %}
{% block form %}
<label data-toggle="tooltip" title="If you recently updated your username, try searching via UUID">Username:
<input type="text" name="username" value="{{ request.GET.username }}" />
</label>
<br/><br/>----- or -----<br/><br/>
<label>UUID:
<input type="text" name="uuid" size="36" value="{{ request.GET.uuid }}" />
</label>
{% endblock %}
{% block submit %}Search{% endblock %}
{% block form_bottom %}
{% if results %}
<table id="dataTable" class="stripe row-border compact">
<thead>
<tr>
<th>Stat</th>
<th>Rank</th>
<th>Score</th>
</tr>
</thead>
<tbody>
{% for result in results %}
<tr>
<td><a href="{% url 'external-stats' %}?stat={{ result.stat }}">{{ result.stat|pretty_stat }}</a></td>
<td>{{ result.rank }}</td>
<td>{{ result.score|pretty_score }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends 'minecraft_manager/external/base.html' %}
{% block title %}Ticket Form{% endblock %}
{% block h1 %}Ticket Form{% endblock %}
{% if valid is True %}
{% block valid %}
<h2>Ticket Submitted
<br/>
We will get back to you soon.</h2>
{% endblock %}
{% endif %}

View File

@ -0,0 +1,9 @@
{% extends "minecraft_manager/base.html" %}
{% block content %}
<h1>Logged out</h1>
<p>You have logged out 24CarrotCraft.</p><p>
</p><p><a href="{% url 'login' %}">Log back in</a>. </p>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends "minecraft_manager/base.html" %}
{% load static %}
{% block bootstrap %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
{% endblock bootstrap %}
{% block head %}
<link rel="stylesheet" href="{% static "signin.css" %}">
{% endblock head %}
{% block content %}
<div class="container">
<form class="form-signin" method="post" action="{% url 'login' %}">{% csrf_token %}
{% if form.errors %}
<p class="alert alert-error">Username and Password are invalid. Please try again.</p>
{% endif %}
<label for="inputUsername" class="sr-only">Username</label>
<input type="text" id="inputUsername" class="form-control form-control-top" name="username" placeholder="Username" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control form-control-bottom" name="password" placeholder="Password" required>
<button class="btn btn-lg btn-primary btn-block form-control-button" type="submit">Sign in</button>
<input type="hidden" name="next" value="{{ next }}" />
</form>
</div> <!-- /container -->
<script>
$(document).ready(function() {
});
</script>
{% endblock content %}

View File

@ -0,0 +1,141 @@
{% extends "minecraft_manager/dashboard.html" %}
{% block section %}
<div id="content">
<div class="row">
<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>
<canvas id="appChart" style="width:30em;height:15em" ></canvas>
</div>
<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>
<h4></h4>
<canvas id="ticketChart" style="width:30em;height:15em" ></canvas>
</div>
</div>
<div class="row">
<div class="col-xs-18 col-md-12">
<h3 class="center">Totals</h3>
<canvas id="totalChart" style="width:30em;height:15em" ></canvas>
</div>
</div>
</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 = {
labels: [
"Unclaimed",
"Claimed",
"Resolved"
],
datasets: [
{
data: [{{ form.tickets.unclaimed }}, {{ form.tickets.claimed }}, {{ form.tickets.resolved }}],
backgroundColor: [
"#8080ff",
"#ff3333",
"#33cc33"
],
hoverBackgroundColor: [
"#8080ff",
"#ff3333",
"#33cc33"
]
}
]
};
var ticket_options = {
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 %}

View File

@ -0,0 +1,40 @@
{% extends "minecraft_manager/dashboard.html" %}
{% block section %}
<div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table">
<thead>
<tr>
<th>Username</th>
<th>UUID</th>
<th>Banned</th>
<th>Last Seen</th>
</tr>
</thead>
<tbody>
{% for player in players %}
<tr {% if player.uuid in bans %}class="danger"{% endif %}{% if player.uuid not in bans %}class="success"{% endif %} data-id="{{ player.id }}">
<td>{{ player.username }}{% if user.usersettings.search_player_ip is True %}<span hidden="hidden">{{ player.ips }}</span>{% endif %}</td>
<td>{{ player.uuid }}</td>
<td>{% if player.uuid in bans %}True {% else %}False{% endif %}</td>
<td>{{ player.last_seen }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
$(document).ready(function() {
$("#model-table").DataTable({
'lengthMenu': [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, "All" ] ],
'initComplete': function(settings, json) {
$("#content").show();
},
'order': [],
{% if user.usersettings %}
'pageLength': {{ user.usersettings.default_results }},
{% endif %}
});
});
</script>
{% endblock section %}

View File

@ -0,0 +1,90 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% block section %}
<div id="content">
<h2 class="sub-header">Player Info ({{ player.username }})</h2>
<div class="row">
<div class="col-xs-9 col-md-6">
<p>UUID: {{ player.uuid }}</p>
{% if player.auth_user %}
<p>Connected User: {{ player.auth_user.username }}</p>
{% endif %}
{% if player.application %}
<p>Application: <a href="{% url "application" %}{{ player.application.id }}">Here</a></p>
{% endif %}
<p>Donor Status: {{ player.donor_status }}</p>
<p>First Seen: {{ player.first_seen }}</p>
<p>Last Seen: {{ player.last_seen }}</p>
<p>Banned: {% if player.is_banned %}<span class="label label-danger">Yes</span>{% else %}<span class="label label-success">No</span>{% endif %}</p>
</div>
<div class="col-xs-9 col-md-6">
<h4>Tickets</h4>
<table class="table table-hover link-table">
<tbody>
{% if form.tickets %}
{% for ticket in form.tickets %}
<tr {% if ticket.resolved is True %}class="success"{% else %}class="danger"{% endif %} data-id="{{ ticket.id }}" data-url="{% url "ticket" %}">
<td>{{ ticket.id }}</td>
<td>{{ ticket.snippet }}</td>
<td>{{ ticket.resolved }}</td>
</tr>
{% endfor %}
{% else %}
<tr><td colspan="3">No Tickets Found</td></tr>
{% endif %}
</tbody>
</table>
<br/>
<table class="table table-hover link-table">
<h4>Warnings</h4>
<tbody>
{% if form.warnings %}
{% for warning in form.warnings %}
<tr {% if warning.severity == 'L' %}class="info"{% endif %}{% if warning.severity == 'M' %}class="warning"{% endif %}{% if warning.severity == 'H' %}class="danger"{% endif %} data-id="{{ warning.id }}" data-url="{% url "warning" %}">
<!-- {{ warning.id }} -->
<td>{{ warning.snippet }}</td>
<td>{{ warning.severity_display }}</td>
</tr>
{% endfor %}
{% else %}
<tr><td colspan="3">No Warnings Found</td></tr>
{% endif %}
</tbody>
</table>
<br/>
<table class="table table-striped">
<h4>IPs</h4>
<tbody>
{% if form.ips %}
{% for ip in form.ips %}
<tr class="default">
<!-- {{ ip.id }} -->
<td>{{ ip.ip }}</td>
{% if ip.associated %}
<td>
{% for assoc in ip.associated %}
<a href="{% url "player" %}{{ assoc.id }}">{{ assoc.username }}</a>{% if assoc.is_banned %} <span class="label label-danger">Banned</span>{% endif %}<br/>
{% endfor %}
</td>
{% else %}
<td>None</td>
{% endif %}
</tr>
{% endfor %}
{% else %}
<tr><td colspan="3">No IPs Found</td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-md-4">
</div>
<div class="col-xs-12 col-md-8">
</div>
</div>
</div>
{% endblock section %}

View File

@ -0,0 +1,24 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load static %}
{% block section %}
<div id="content">
<table id="model-table" class="table table-striped">
<thead>
<tr>
<th>Username</th>
<th>Age</th>
<th>Reference</th>
</tr>
</thead>
<tbody>
{% for app in applications %}
<tr>
<td>{{ app.username }}</td>
<td>{{ app.age }}</td>
<td>{{ app.reference }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock section %}

View File

@ -0,0 +1,84 @@
{% extends "minecraft_manager/dashboard.html" %}
{% block after_h1 %}{% if timestamp %}- {{ timestamp }}{% endif %}{% endblock %}
{% block section %}
<form>
Report: <br/>
<label for="player">
<input type="radio" name="report" id="player" value="players" {% if report == 'players' %}checked{% endif %}>Players
</label>
<label for="entity">
<input type="radio" name="report" id="entity" value="entities" {% if report == 'entities' %}checked{% endif %}>Entities
</label>
<label for="count">
<input type="radio" name="report" id="count" value="counts" {% if report == 'counts' %}checked{% endif %}>Counts
</label>
<br/><br/>
World: <br/>
<label for="overworld">
<input type="radio" name="world" id="overworld" value="overworld" {% if world == 'overworld' %}checked{% endif %}>Overworld
</label>
<label for="nether">
<input type="radio" name="world" id="nether" value="nether" {% if world == 'nether' %}checked{% endif %}>Nether
</label>
<label for="end">
<input type="radio" name="world" id="end" value="end" {% if world == 'end' %}checked{% endif %}>The End
</label>
<br/><br/>
<button type="submit" class="btn btn-primary">Load</button>
</form>
<br/>
{% if results %}
{% if results == 'NONE' %}
<h2>No Report Found. Use "/mcm report" in-game to generate one.</h2>
{% elif results == 'BOTH' %}
<h2>Choose One Of Each Option</h2>
{% elif results == 'EMPTY' %}
<h2>No Data</h2>
{% else %}
<table id="dataTable" class="table table-hover link-table">
<thead>
<tr>
<th>{% if report == 'players' %}Name{% else %}Entity{% endif %}</th>
<th>{% if report == 'counts' %}Count{% else %}X{% endif %}</th>
{% if report != 'counts' %}<th>Y</th>{% endif %}
{% if report != 'counts' %}<th>Z</th>{% endif %}
</tr>
</thead>
<tbody>
{% for row in results %}
<tr>
<td>{{ row.name }}</td>
<td>{% if report == 'counts' %}{{ row.count }}{% else %}{{ row.x }}{% endif %}</td>
{% if report != 'counts' %}<td>{{ row.y }}</td>{% endif %}
{% if report != 'counts' %}<td>{{ row.z }}</td>{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endif %}
<script>
$(document).ready(function() {
$("#dataTable").DataTable({
'lengthMenu': [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, "All" ] ],
'initComplete': function(settings, json) {
$("#content").show();
$(this).css('width', '');
},
'order': [],
{% if user.usersettings %}
'pageLength': {{ user.usersettings.default_results }},
{% endif %}
});
});
</script>
{% endblock %}
{% block form_bottom %}
{% endblock %}

View File

@ -0,0 +1,46 @@
{% extends "minecraft_manager/dashboard.html" %}
{% block section %}
<div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table">
<thead>
<tr>
<th>ID</th>
<th>Player</th>
<th>Message</th>
<th>Priority</th>
<th>Claimed</th>
<th>Resolved</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{% for ticket in tickets %}
<tr {% if ticket.resolved is True %}class="info"{% elif ticket.priority == 'L' %}class="success"{% elif ticket.priority == 'M' %}class="warning"{% elif ticket.priority == 'H' %}class="danger"{% endif %} data-id="{{ ticket.id }}">
<td>{{ ticket.id }}</td>
<td>{{ ticket.issuer }}</td>
<td>{{ ticket.snippet }}</td>
<td>{{ ticket.priority_display }}</td>
<td>{{ ticket.claimed_by }}</td>
<td>{{ ticket.resolved }}</td>
<td>{{ ticket.date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
$(document).ready(function() {
$("#model-table").DataTable({
'lengthMenu': [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, "All" ] ],
'initComplete': function(settings, json) {
$("#content").show();
},
'order': [],
{% if user.usersettings %}
'pageLength': {{ user.usersettings.default_results }},
{% endif %}
});
});
</script>
{% endblock section %}

View File

@ -0,0 +1,129 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% block section %}
<div id="content">
<h2 class="sub-header">Ticket Info ({{ ticket.issuer }}) <span {% if ticket.resolved is True %}class="label label-success"{% else %}class="label label-danger"{% endif %}>{{ ticket.resolved_label }}</span></h2>
<div class="row">
<div class="col-xs-6 col-md-4">
<p>Message: </p>
<p class="well">{{ ticket.message }}</p>
<p>Location: {{ ticket.location }}</p>
<p>Sent In Date: {{ ticket.date }}</p>
</div>
<div class="col-xs-12 col-md-8">
{% if ticket.resolved is False %}
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<p>Claimed:
{% if user.is_staff %}
<select id="ticketStaff" name="staff">
<option value=""></option>
{% if form.active_staff %}
<optgroup label="Active">
{% for staff in form.active_staff %}
<option value="{{ staff.id }}" {% if staff == ticket.staff %}selected="selected"{% endif %}>{{ staff.username }}</option>
{% endfor %}
</optgroup>
{% endif %}
{% if form.inactive_staff %}
<optgroup label="Inactive">
{% for staff in form.inactive_staff %}
<option value="{{ staff.id }}" {% if staff == ticket.staff %}selected="selected"{% endif %}>{{ staff.username }}</option>
{% endfor %}
</optgroup>
{% endif %}
</select>
{% elif ticket.staff %}
{{ ticket.staff.username }}
{% else %}
<button type="submit" name="staff" value="{{ user.id}}">Claim</button>
{% endif %}
</p>
<p>Priority:
{% if user.is_staff or user == ticket.staff %}
<select id="ticketPriority" name="priority">
{% for p in form.priority %}
<option value="{{ p.0 }}" {% if ticket.priority == p.0 %}selected="selected"{% endif %}>{{ p.1 }}</option>
{% endfor %}
</select>
{% else %}
{{ ticket.priority_display }}
{% endif %}
</p>
{% if user.is_staff or ticket.staff and user == ticket.staff %}
<button id="resolveButton" class="btn btn-primary" type="submit" name="resolved" value="1">Set as Resolved</button>
<button id="saveButton" class="btn btn-primary" type="submit">Save</button>
{% endif %}
</form>
{% else %}
<p>Claimed: {{ ticket.staff.username }}</p>
<p>Priority: {{ ticket.priority_display }}</p>
{% endif %}
</div>
</div>
<hr/>
<div class="row">
{% for note in form.notes %}
<div class="col-xs-9 col-md-6">
<h3>Note by {{ note.author.username }}</h3>
<p>Message:</p>
<p class="well">{{ note.message }}</p>
{% if note.author == user %}
<button id="editBtn" class="btn btn-primary" onClick="showNote();">Edit</button>
{% endif %}
<p>Created: {{ note.date }}</p>
<p>Last Update: {{ note.last_update }}</p>
</div>
{% endfor %}
</div>
{% if not form.has_note and not form.show_note %}
<div id="createDiv" class="row">
<button class="btn btn-primary" onClick="showNote()">Create Note</button>
</div>
{% endif %}
<div id="noteDiv" class="row" {% if not form.show_note %}style="display: none;"{% endif %}>
<br/>
<h3>Note</h3>
<form action="" method="POST">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
{{ form.note_form }}
<br/>
<button type="submit" class="btn btn-primary" name="note" value="{% if form.has_note %}edit{% else %}create{% endif %}">Save</button>
</form>
</div>
</div>
<script>
$("#saveButton").hide();
$("#ticketStaff").change(function() {
var $priority = $("#ticketPriority");
if ($(this).val() != "{{ ticket.staff.id }}" || $priority.val() != "{{ ticket.priority }}") {
toggleSave(true);
} else {
toggleSave(false);
}
});
$("#ticketPriority").change(function() {
var $staff = $("#ticketStaff");
if ($(this).val() != "{{ ticket.priority }}" || $staff.val() != "{{ ticket.staff.id }}") {
toggleSave(true);
} else {
toggleSave(false);
}
});
function toggleSave(toggle) {
if (toggle == true) {
$("#resolveButton").hide();
$("#saveButton").show();
} else if (toggle == false) {
$("#resolveButton").show();
$("#saveButton").hide();
}
}
function showNote() {
$("#noteDiv").show();
$("#createDiv").hide();
$("#editBtn").hide();
}
</script>
{% endblock section %}

View File

@ -0,0 +1,46 @@
{% extends "minecraft_manager/dashboard.html" %}
{% block section %}
<div id="content" hidden="hidden">
<table id="model-table" class="table table-hover link-table">
<thead>
<tr>
<th>Player</th>
<th>Message</th>
<th>Severity</th>
<td>Issued By</td>
<th>Date</th>
</tr>
</thead>
<tbody>
{% for warning in warnings %}
<tr {% if warning.severity == 'L' %}class="info"{% endif %}{% if warning.severity == 'M' %}class="warning"{% endif %}{% if warning.severity == 'H' %}class="danger"{% endif %} data-id="{{ warning.id }}">
<!-- <td>{{ warning.id }}</td> -->
<td>{{ warning.issuee }}</td>
<td>{{ warning.snippet }}</td>
<td>{{ warning.severity_display }}</td>
<td>{{ warning.issuer }}</td>
<td>{{ warning.date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a class="btn btn-primary" href="{% url "warning_add" %}">Add Warning</a>
</div>
<script>
$(document).ready(function() {
$("#model-table").DataTable({
'lengthMenu': [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, "All" ] ],
'initComplete': function(settings, json) {
$("#content").show();
$(this).css('width', '');
},
'order': [],
{% if user.usersettings %}
'pageLength': {{ user.usersettings.default_results }},
{% endif %}
});
});
</script>
{% endblock section %}

View File

@ -0,0 +1,29 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% block section %}
<div id="content">
<h2 class="sub-header">New Warning</h2>
<div class="row">
<div class="col-xs-18 col-md-12">
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<p><label for="">Player Filter:</label> <input id="id_filter" type="text"/></p>
{{ form }}
<button id="saveButton" class="btn btn-primary" type="submit">Save</button>
</form>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-md-4">
</div>
<div class="col-xs-12 col-md-8">
</div>
</div>
</div>
<script>
$(document).ready(function() {
$("#id_player").filterByText($("#id_filter"));
});
</script>
{% endblock section %}

View File

@ -0,0 +1,52 @@
{% extends "minecraft_manager/dashboard.html" %}
{% load csrf_html %}
{% block section %}
<div id="content">
<h2 class="sub-header">Warning Info ({{ warning.player.username }})</h2>
<div class="row">
<div class="col-xs-6 col-md-4">
<!-- <p>Username: {{ application.username }}</p> -->
<p>Message: </p>
<p class="well">{{ warning.message }}</p>
<p>Warning Date: {{ warning.date }}</p>
</div>
<div class="col-xs-12 col-md-8">
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
<p>Issuer: {{ warning.staff.username }} </p>
<p>Severity:
{% if user.is_staff or user == warning.staff %}
<select id="warningSeverity" name="severity">
{% for p in form.severity %}
<option value="{{ p.0 }}" {% if warning.severity == p.0 %}selected="selected"{% endif %}>{{ p.1 }}</option>
{% endfor %}
</select>
{% else %}
{{ warning.severity_display }}
{% endif %}
</p>
{% if user.is_staff or warning.staff and user == warning.staff %}
<button id="saveButton" class="btn btn-primary" type="submit">Save</button>
{% endif %}
</form>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-md-4">
</div>
<div class="col-xs-12 col-md-8">
</div>
</div>
</div>
<script>
$("#saveButton").hide();
$("#warningSeverity").change(function() {
if (("{{ user.username }}" == "{{ warning.staff.username }}" || "{{ user.is_staff }}" == "True") && $(this).val() != "{{ warning.severity }}") {
$("#saveButton").show();
} else {
$("#saveButton").hide();
}
});
</script>
{% endblock section %}

View File

View File

@ -0,0 +1,36 @@
from django.template import Library
from minecraft_manager.models import UserSettings
from minecraft_manager.forms import UserSettingsForm
from django.contrib.auth.forms import PasswordChangeForm
register = Library()
@register.simple_tag
def get_csrf_html(request):
if request.COOKIES and request.COOKIES['csrftoken']:
return "<input type='hidden' name='csrfmiddlewaretoken' value='%s' />" % request.COOKIES['csrftoken']
return ""
@register.simple_tag
def get_csrf_token(request):
if request.COOKIES and request.COOKIES['csrftoken']:
return request.COOKIES['csrftoken']
return ""
@register.simple_tag
def get_form(form, request):
if form.lower() == 'usersettings' and request:
try:
return UserSettingsForm(instance=request.user.usersettings).as_p()
except UserSettings.DoesNotExist:
user_settings = UserSettings(auth_user=request.user)
user_settings.save()
return UserSettingsForm(instance=request.user.usersettings).as_p()
elif form.lower() == "password":
return PasswordChangeForm(request.user).as_p()

View File

@ -0,0 +1,22 @@
import re
from django import template
from django.conf import settings
numeric_test = re.compile("^\d+$")
register = template.Library()
def getattribute(value, arg):
"""Gets an attribute of an object dynamically from a string name"""
if hasattr(value, str(arg)):
return getattr(value, arg)
elif hasattr(value, 'has_key') and value.has_key(arg):
return value[arg]
elif arg in value:
return value[arg]
elif numeric_test.match(str(arg)) and len(value) > int(arg):
return value[int(arg)]
else:
return settings.TEMPLATE_STRING_IF_INVALID
register.filter('getattribute', getattribute)

View File

@ -0,0 +1,23 @@
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
@register.filter
@stringfilter
def pretty_stat(value):
stat = value.split('.')
parts = []
for s in stat:
s = s.replace('minecraft:', '')
for part in s.split('_'):
if part.lower() == 'custom':
continue
parts.append(part.title())
return ' '.join(parts)
@register.filter
def pretty_score(value):
return '{:,}'.format(value)

View File

@ -0,0 +1,69 @@
from django.template import Library
from django.urls import reverse
from minecraft_manager.models import Alert
from django.conf import settings
register = Library()
@register.simple_tag
def get_sidebar(current_app, request):
# Get unseen Alerts
unseen_alerts = Alert.objects.filter(user=request.user, seen=False)
unseen_html = ""
if len(unseen_alerts) > 0:
unseen_html = " <span class='badge badge-light'>" + str(len(unseen_alerts)) + "</span>"
ret = ""
if current_app == 'overview':
ret += "<li class=\"active\"><a href=\"" + reverse('overview') + "\">Overview</a></li>"
else:
ret += "<li><a href=\"" + reverse('overview') + "\">Overview</a></li>"
if current_app == 'ban':
ret += "<li class=\"active\"><a href=\"" + reverse('ban') + "\">Bans</a></li>"
else:
ret += "<li><a href=\"" + reverse('ban') + "\">Bans</a></li>"
if current_app == 'alert':
ret += "<li class=\"active\"><a href=\"" + reverse('alert') + "\">Alerts{0}</a></li>".format(unseen_html)
else:
ret += "<li><a href=\"" + reverse('alert') + "\">Alerts{0}</a></li>".format(unseen_html)
# Models
if current_app == 'application':
ret += "<li class=\"active\"><a href=\"" + reverse('application') + "\">Applications</a></li>"
else:
ret += "<li><a href=\"" + reverse('application') + "\">Applications</a></li>"
if current_app == 'player':
ret += "<li class=\"active\"><a href=\"" + reverse('player') + "\">Players</a></li>"
else:
ret += "<li><a href=\"" + reverse('player') + "\">Players</a></li>"
if current_app == 'ticket':
ret += "<li class=\"active\"><a href=\"" + reverse('ticket') + "\">Tickets</a></li>"
else:
ret += "<li><a href=\"" + reverse('ticket') + "\">Tickets</a></li>"
if current_app == 'warning':
ret += "<li class=\"active\"><a href=\"" + reverse('warning') + "\">Warnings</a></li>"
else:
ret += "<li><a href=\"" + reverse('warning') + "\">Warnings</a></li>"
# Split up MCM and "other"
ret += "<hr/>"
if current_app == 'report':
ret += "<li class=\"active\"><a href=\"" + reverse("report") + "\">Report</a></li>"
else:
ret += "<li><a href=\"" + reverse("report") + "\">Report</a></li>"
show_chat = True if getattr(settings, 'GLOBAL_LOG', None) is not None else False
if show_chat and request.user.has_perm('auth.chat'):
if current_app == 'chat':
ret += "<li class=\"active\"><a href=\"" + reverse("chat") + "\">Chat</a></li>"
else:
ret += "<li><a href=\"" + reverse("chat") + "\">Chat</a></li>"
if request.user.has_perm('auth.bots'):
if current_app == 'bots':
ret += "<li class=\"active\"><a href=\"" + reverse("bots") + "\">Bots</a></li>"
else:
ret += "<li><a href=\"" + reverse("bots") + "\">Bots</a></li>"
return ret

View File

@ -0,0 +1,10 @@
from django.template import Library
from django.conf import settings
register = Library()
@register.simple_tag
def template_settings(prop):
return getattr(settings, prop, "")

72
urls.py 100644
View File

@ -0,0 +1,72 @@
from django.conf.urls import url
from django.views.generic import RedirectView
from django.contrib.auth.decorators import login_required
import minecraft_manager.views as mcm
urlpatterns = [
url(r'^$', RedirectView.as_view(pattern_name='overview')),
#Dashboard
url(r'^dashboard/overview/$', login_required(mcm.Overview.as_view()), name="overview"),
url(r'^dashboard/ban/$', login_required(mcm.Ban.as_view()), name="ban"),
#CoreProtect
url(r'^dashboard/coreprotect/$', login_required(mcm.CoreProtect.as_view()), name="coreprotect"),
url(r'^dashboard/activity/$', login_required(mcm.Activity.as_view()), name="activity"),
#Alerts
url(r'^dashboard/alert/$', login_required(mcm.Alert.as_view()), name="alert"),
url(r'^dashboard/alert/(?P<alert_id>[0-9]{1,5})/$', login_required(mcm.AlertInfo.as_view())),
#Applications
url(r'^dashboard/application/$', login_required(mcm.Application.as_view()), name="application"),
url(r'^dashboard/reference/$', login_required(mcm.Reference.as_view()), name="reference"),
url(r'^dashboard/application/(?P<application_id>[0-9]{1,5})/$', login_required(mcm.ApplicationInfo.as_view())),
#Players
url(r'^dashboard/player/$', login_required(mcm.Player.as_view()), name="player"),
url(r'^dashboard/player/(?P<player_id>[0-9]{1,5})/$', login_required(mcm.PlayerInfo.as_view())),
#Tickets
url(r'^dashboard/ticket/$', login_required(mcm.Ticket.as_view()), name="ticket"),
url(r'^dashboard/ticket/(?P<ticket_id>[0-9]{1,5})/$', login_required(mcm.TicketInfo.as_view())),
#Warnings
url(r'^dashboard/warning/$', login_required(mcm.Warning.as_view()), name="warning"),
url(r'^dashboard/warning/(?P<warning_id>[0-9]{1,5})/$', login_required(mcm.WarningInfo.as_view())),
url(r'^dashboard/warning/add$', login_required(mcm.WarningAdd.as_view()), name="warning_add"),
#Report
url(r'^report/$', login_required(mcm.Report.as_view()), name="report"),
#Chat
url(r'^dashboard/chat/$', login_required(mcm.Chat.as_view()), name="chat"),
#Bots
url(r'^dashboard/bots/$', login_required(mcm.Bots.as_view()), name="bots"),
]
# Possible future feature
# from django.conf import settings
#
# LOGIN_REQUIRED = settings.LOGIN_REQUIRED if hasattr(settings, 'LOGIN_REQUIRED') else False
#
# urlpatterns = [
# #Dashboard
# url(r'^dashboard/overview/$', login_required(mcm.Overview.as_view()) if LOGIN_REQUIRED else mcm.Overview.as_view(), name="overview"),
# url(r'^dashboard/coreprotect/$', login_required(mcm.CoreProtect.as_view()) if LOGIN_REQUIRED else mcm.CoreProtect.as_view(), name="coreprotect"),
# url(r'^dashboard/activity/$', login_required(mcm.Activity.as_view()) if LOGIN_REQUIRED else mcm.Activity.as_view(), name="activity"),
# url(r'^dashboard/ban/$', login_required(mcm.Ban.as_view()) if LOGIN_REQUIRED else mcm.Ban.as_view(), name="ban"),
# #Alerts
# url(r'^dashboard/alert/$', login_required(mcm.Alert.as_view()) if LOGIN_REQUIRED else mcm.Alert.as_view(), name="alert"),
# url(r'^dashboard/alert/(?P<alert_id>[0-9]{1,5})/$', login_required(mcm.AlertInfo.as_view()) if LOGIN_REQUIRED else mcm.AlertInfo.as_view()),
# #Applications
# url(r'^dashboard/application/$', login_required(mcm.Application.as_view()) if LOGIN_REQUIRED else mcm.Application.as_view(), name="application"),
# url(r'^dashboard/application/(?P<application_id>[0-9]{1,5})/$', login_required(mcm.ApplicationInfo.as_view()) if LOGIN_REQUIRED else mcm.ApplicationInfo.as_view()),
# #Players
# url(r'^dashboard/player/$', login_required(mcm.Player.as_view()) if LOGIN_REQUIRED else mcm.Player.as_view(), name="player"),
# url(r'^dashboard/player/(?P<player_id>[0-9]{1,5})/$', login_required(mcm.PlayerInfo.as_view()) if LOGIN_REQUIRED else mcm.PlayerInfo.as_view()),
# #Tickets
# url(r'^dashboard/ticket/$', login_required(mcm.Ticket.as_view()) if LOGIN_REQUIRED else mcm.Ticket.as_view(), name="ticket"),
# url(r'^dashboard/ticket/(?P<ticket_id>[0-9]{1,5})/$', login_required(mcm.TicketInfo.as_view()) if LOGIN_REQUIRED else mcm.TicketInfo.as_view()),
# #Warnings
# url(r'^dashboard/warning/$', login_required(mcm.Warning.as_view()) if LOGIN_REQUIRED else mcm.Warning.as_view(), name="warning"),
# url(r'^dashboard/warning/(?P<warning_id>[0-9]{1,5})/$', login_required(mcm.WarningInfo.as_view()) if LOGIN_REQUIRED else mcm.WarningInfo.as_view()),
# url(r'^dashboard/warning/add$', login_required(mcm.WarningAdd.as_view()) if LOGIN_REQUIRED else mcm.WarningAdd.as_view(), name="warning_add"),
# #Chat
# url(r'^dashboard/chat/$', login_required(mcm.Chat.as_view()) if LOGIN_REQUIRED else mcm.Chat.as_view(), name="chat"),
# #Bots
# url(r'^dashboard/bots/$', login_required(mcm.Bots.as_view()) if LOGIN_REQUIRED else mcm.Bots.as_view(), name="bots"),
# ]

103
utils.py 100644
View File

@ -0,0 +1,103 @@
import discord, requests
from django.conf import settings
def build_application(application):
embed = discord.Embed(colour=discord.Colour(0x417505))
embed.title = "Application"
embed.set_thumbnail(
url="https://minotar.net/helm/{0}/100.png".format(application.username))
embed.add_field(name="Application ID", value=application.id)
embed.add_field(name="Username", value=application.username.replace("_", "\\_"))
embed.add_field(name="Age", value=application.age)
embed.add_field(name="Type of Player", value=application.player_type)
embed.add_field(name="Ever been banned", value=application.ever_banned)
if application.ever_banned:
embed.add_field(name="Reason for being banned", value=application.ever_banned_explanation)
embed.add_field(name="Reference", value=application.reference)
embed.add_field(name="Read the Rules", value=application.read_rules)
embed.add_field(name="Date", value=application.date_display)
embed.add_field(name="Status", value=application.status)
return [embed.to_dict()]
def build_ticket(ticket, link):
embed = discord.Embed(colour=discord.Colour(0x417505))
embed.title = "Ticket"
embed.set_thumbnail(
url="https://cdn.discordapp.com/avatars/454457830918062081/b5792489bc43d9e17b8f657880a17dd4.png")
embed.add_field(name="Date", value=ticket.date_display)
embed.add_field(name="Player", value=ticket.player.username.replace("_", "\\_"))
embed.add_field(name="Priority", value=ticket.priority_display)
if ticket.x and ticket.y and ticket.z and ticket.world:
embed.add_field(name="Location", value=ticket.location)
embed.add_field(name="Message", value=ticket.message)
embed.add_field(name="Link", value=link)
return [embed.to_dict()]
def build_warning(warning, link):
embed = discord.Embed(colour=discord.Colour(0x417505))
embed.title = "Warning"
embed.set_thumbnail(
url="https://cdn.discordapp.com/avatars/454457830918062081/b5792489bc43d9e17b8f657880a17dd4.png")
embed.add_field(name="Date", value=warning.date_display)
embed.add_field(name="Player", value=warning.player.username.replace("_", "\\_"))
embed.add_field(name="Severity", value=warning.severity_display)
embed.add_field(name="Message", value=warning.message)
embed.add_field(name="Link", value=link)
return [embed.to_dict()]
def validate_username(username):
response = requests.get("https://api.mojang.com/users/profiles/minecraft/{}".format(username))
if response.status_code == 200:
return True
return False
def url_path(*args):
value = []
for arg in args:
arg = str(arg)
if arg.startswith('/'):
arg = arg[1:]
if arg.endswith('/'):
arg = arg[:-1]
value.append(arg)
return '/'.join(value)
class Captcha:
success = False
challenge_ts = None
hostname = None
errors = []
def __init__(self, post):
if not hasattr(settings, 'CAPTCHA_SECRET'):
self.success = True
if 'g-recaptcha-response' in post:
response = requests.post("https://www.google.com/recaptcha/api/siteverify",
{"secret": settings.CAPTCHA_SECRET, "response": post['g-recaptcha-response']})
json = response.json()
self.success = json['success']
self.challenge_ts = json['challenge_ts'] if 'challenge_ts' in json else None
self.hostname = json['hostname'] if 'hostname' in json else None
if 'error-codes' in json:
codes = json['error-codes']
if 'missing-input-secret' in codes:
self.add_error('The secret parameter is missing.')
if 'invalid-input-secret' in codes:
self.add_error('The secret parameter is invalid or malformed.')
if 'missing-input-response' in codes:
self.add_error('The reCAPTCHA must be filled out.')
if 'invalid-input-response' in codes:
self.add_error('The response parameter is invalid or malformed.')
if 'bad-request' in codes:
self.add_error('The request is invalid or malformed.')
def add_error(self, error):
if error not in self.errors:
self.errors.append(error)

497
views.py 100644
View File

@ -0,0 +1,497 @@
#create your views here.
# https://api.mojang.com/users/profiles/minecraft/<username>
from __future__ import absolute_import
import json, datetime, pytz, os, sys
from django.utils import timezone
from itertools import chain
from django.http import JsonResponse
from django.shortcuts import render, reverse, redirect
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator
from django.conf import settings
from django.views.generic import View
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.forms import WarningForm, NoteForm
import minecraft_manager.api.api as API
import subprocess
class Overview(View):
def get(self, request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
user_ip = x_forwarded_for.split(',')[0]
else:
user_ip = request.META.get('REMOTE_ADDR')
try:
request.user.usersettings.last_ip = user_ip
except:
request.user.usersettings = UserSettingsModel(auth_user=request.user)
request.user.usersettings.last_ip = user_ip
request.user.usersettings.save()
unanswered_apps = AppModel.unanswered.count()
accepted_apps = AppModel.objects.filter(accepted=True).count()
denied_apps = AppModel.objects.filter(accepted=False).count()
unclaimed_tickets = TicketModel.unclaimed.count()
claimed_tickets = TicketModel.claimed.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):
def get(self, request):
#http://www.24carrotcraft.com/assets/cp/index.php?username=etzelia
return render(request, 'minecraft_manager/coreprotect.html', {'current_app': 'coreprotect'})
class Activity(View):
def get(self, request):
#http://www.24carrotcraft.com/assets/cp/activity.php?username=etzelia
return render(request, 'minecraft_manager/activity.html', {'current_app': 'activity'})
class Ban(View):
def get(self, request):
ban_file = os.path.join(settings.MINECRAFT_BASE_DIR, 'banned-players.json')
with open(ban_file, encoding='utf-8') as f:
bans = json.load(f)
for idx, ban in enumerate(bans):
ban['source'] = API.strip_format(ban['source'])
if PlayerModel.objects.filter(uuid=ban['uuid']).exists():
ban['id'] = PlayerModel.objects.get(uuid=ban['uuid']).id
else:
ban['id'] = 0
ban['source'] = API.strip_format(ban['source'])
if ban['expires'] == "forever":
ban['expires'] = "Permanent"
else:
dt = datetime.datetime.strptime(ban['expires'], "%Y-%m-%d %H:%M:%S %z")
dt = dt.astimezone(pytz.timezone(request.user.usersettings.default_timezone))
ban['expires'] = dt.strftime("%m/%d/%y %I:%M %p")
dt = datetime.datetime.strptime(ban['created'], "%Y-%m-%d %H:%M:%S %z")
dt = dt.astimezone(pytz.timezone(request.user.usersettings.default_timezone))
ban['created'] = dt.strftime("%m/%d/%y %I:%M %p")
bans[idx] = ban
bans = sorted(bans, key=lambda ban: ban['created'], reverse=True)
return render(request, 'minecraft_manager/ban.html', {'current_app': 'ban', 'bans': bans})
class Alert(View):
def get(self, request):
alerts = AlertModel.objects.filter(user=request.user).order_by('seen', '-id')
unseen_alerts = AlertModel.objects.filter(user=request.user, seen=False)
return render(request, 'minecraft_manager/alert.html',
{'current_app': 'alert', 'alerts': alerts, 'unseen': len(unseen_alerts)})
def post(self, request):
post = request.POST
if 'action' in post:
if post['action'] == 'mar':
AlertModel.objects.filter(user=request.user).update(seen=True)
alerts = AlertModel.objects.filter(user=request.user).order_by('seen', '-id')
return render(request, 'minecraft_manager/alert.html',
{'current_app': 'alert', 'alerts': alerts, 'unseen': 0})
class AlertInfo(View):
def get(self, request, alert_id):
alert = AlertModel.objects.get(id=alert_id)
alert.seen = True
alert.save()
return render(request, 'minecraft_manager/alert_info.html',
{'current_app': 'alert', 'alert': alert})
def post(self, request, alert_id):
post = request.POST
alert = AlertModel.objects.get(id=alert_id)
if 'action' in post:
if post['action'] == 'mu':
alert.seen = False
alert.save()
return render(request, 'minecraft_manager/alert_info.html',
{'current_app': 'alert', 'alert': alert})
class Application(View):
def get(self, request):
get = request.GET
if 'accepted' in get:
if get['accepted'].lower() == 'true':
applications = AppModel.objects.filter(accepted=True)
elif get['accepted'].lower() == 'false':
applications = AppModel.objects.filter(accepted=False)
else:
applications = AppModel.objects.filter(accepted__isnull=True)
else:
applications1 = AppModel.objects.filter(accepted__isnull=True)
applications2 = AppModel.objects.filter(accepted__isnull=False)
applications = list(chain(applications1, applications2))
return render(request, 'minecraft_manager/application.html', {'current_app': 'application', 'applications': applications})
class Reference(View):
def get(self, request):
get = request.GET
applications = AppModel.objects.all()
return render(request, 'minecraft_manager/reference.html', {'current_app': 'application', 'applications': applications})
class ApplicationInfo(View):
@method_decorator(csrf_protect)
def get(self, request, application_id):
application = AppModel.objects.get(id=application_id)
return render(request, 'minecraft_manager/application_info.html', {'current_app': 'application', 'application': application})
def post(self, request, application_id):
post = request.POST
application = AppModel.objects.get(id=application_id)
if post['accept']:
if post['accept'] == 'accept':
application.accepted = True
if post['accept'] == 'deny':
application.accepted = False
application.save()
API.plugin(post['accept'], application.username)
API.discord_mcm("Application #**{0}** was **{1}** by **{2}**".format(application.id, "Accepted" if application.accepted else "Denied", request.user.player.username))
return render(request, 'minecraft_manager/application_info.html',
{'current_app': 'application', 'application': application})
class Player(View):
def get(self, request):
players = PlayerModel.objects.all()
ban_file = os.path.join(settings.MINECRAFT_BASE_DIR, 'banned-players.json')
with open(ban_file, encoding='utf-8') as f:
ban_list = json.load(f)
bans = []
for ban in ban_list:
bans.append(ban['uuid'])
return render(request, 'minecraft_manager/player.html', {'current_app': 'player', 'players': players, 'bans': bans})
class PlayerInfo(View):
def get(self, request, player_id):
player = PlayerModel.objects.get(id=player_id)
ips = IPModel.objects.filter(player=player)
tickets = TicketModel.objects.filter(player=player)
warnings = WarningModel.objects.filter(player=player)
form = {'ips': ips, 'tickets': tickets, 'warnings': warnings}
return render(request, 'minecraft_manager/player_info.html', {'current_app': 'player', 'player': player, 'form': form})
def post(self, request, player_id):
player = PlayerModel.objects.get(id=player_id)
return render(request, 'minecraft_manager/player_info.html', {'current_app': 'player', 'player': player})
class Ticket(View):
def get(self, request):
get = request.GET
if 'claimed' in get:
if get['claimed'].lower() == 'true':
tickets = TicketModel.claimed.all()
elif get['claimed'].lower() == 'false':
tickets = TicketModel.unclaimed.all()
else:
tickets = TicketModel.objects.filter(resolved=True)
else:
tickets1 = TicketModel.objects.filter(resolved=False).order_by('-id')
tickets2 = TicketModel.objects.filter(resolved=True).order_by('-id')
tickets = list(chain(tickets1, tickets2))
return render(request, 'minecraft_manager/ticket.html', {'current_app': 'ticket', 'tickets': tickets})
class TicketInfo(View):
@method_decorator(csrf_protect)
def get(self, request, ticket_id):
ticket = TicketModel.objects.get(id=ticket_id)
active_staff = User.objects.filter(is_active=True)
inactive_staff = User.objects.filter(is_active=False)
notes = NoteModel.objects.filter(ref_id=ticket_id, ref_table='TI')
note_form = NoteForm(instance=request.user)
has_note = False
for note in notes:
if note.author == request.user:
note_form = NoteForm(instance=note)
has_note = True
if not has_note:
note_form.fields['ref_table'].initial = 'TI'
note_form.fields['ref_id'].initial = ticket_id
form = {'active_staff': active_staff, 'inactive_staff': inactive_staff, 'priority': TicketModel.PRIORITY,
'resolved': ticket.resolved, 'note_form': note_form.as_p(), 'notes': notes, 'has_note': has_note,
'show_note': False}
return render(request, 'minecraft_manager/ticket_info.html', {'current_app': 'ticket', 'ticket': ticket, 'form': form})
def post(self, request, ticket_id):
post = request.POST
ticket = TicketModel.objects.get(id=ticket_id)
if 'priority' in post:
if post['priority'] != ticket.priority:
API.discord_mcm(
"Ticket #**{0}**'s priority was changed from **{1}** to **{2}** by **{3}**".format(ticket.id,
ticket.priority_display,
TicketModel.priority_code_to_display(
post['priority']),
request.user.username))
ticket.priority = post['priority']
if 'staff' in post and 'resolved' not in post:
if post['staff']:
staff = User.objects.get(id=post['staff'])
if post['staff'] != str(getattr(ticket.staff, 'id', '-1')):
if post['staff'] == str(request.user.id):
API.discord_mcm(
"Ticket #**{0}** was claimed by **{1}**".format(ticket.id, request.user.username))
else:
API.discord_mcm(
"Ticket #**{0}** was given to **{1}** by **{2}**".format(ticket.id, staff.username, request.user.username))
else:
staff = None
API.discord_mcm(
"Ticket #**{0}** was unclaimed by **{1}**".format(ticket.id, request.user.username))
ticket.staff = staff
if 'resolved' in post:
API.discord_mcm("Ticket #**{0}** was resolved by **{1}**".format(ticket.id, request.user.username))
ticket.resolved = True
ticket.save()
show_note = False
if 'note' in post:
note_form = NoteForm(post)
if note_form.is_valid():
n = note_form.save(commit=False)
if post['note'] == 'create':
n.author = request.user
n.save()
elif post['note'] == 'edit':
db = NoteModel.objects.get(ref_id=ticket_id, ref_table='TI', author=request.user)
db.message = n.message
db.last_update = timezone.now()
db.save()
else:
show_note = True
else:
note_form = NoteForm(instance=request.user)
notes = NoteModel.objects.filter(ref_id=ticket_id, ref_table='TI')
has_note = False
for note in notes:
if note.author == request.user:
note_form = NoteForm(instance=note)
has_note = True
if not has_note:
note_form.fields['ref_table'].initial = 'TI'
note_form.fields['ref_id'].initial = ticket_id
active_staff = User.objects.filter(is_active=True)
inactive_staff = User.objects.filter(is_active=False)
form = {'active_staff': active_staff, 'inactive_staff': inactive_staff, 'priority': TicketModel.PRIORITY,
'resolved': ticket.resolved, 'note_form': note_form.as_p(), 'notes': notes, 'has_note': has_note,
'show_note': show_note}
return render(request, 'minecraft_manager/ticket_info.html',
{'current_app': 'ticket', 'ticket': ticket, 'form': form})
class Warning(View):
def get(self, request):
warnings = WarningModel.objects.order_by('-id')
return render(request, 'minecraft_manager/warning.html', {'current_app': 'warning', 'warnings': warnings})
class WarningInfo(View):
@method_decorator(csrf_protect)
def get(self, request, warning_id):
warning = WarningModel.objects.get(id=warning_id)
form = {'severity': WarningModel.SEVERITY}
return render(request, 'minecraft_manager/warning_info.html', {'current_app': 'warning', 'form': form, 'warning': warning})
def post(self, request, warning_id):
post = request.POST
warning = WarningModel.objects.get(id=warning_id)
if 'severity' in post:
API.discord_mcm("Warning #**{0}**'s severity was changed from {1} to {2} by {3}".format(warning.id,
warning.severity_display,
WarningModel.severity_code_to_display(
post[
'severity']),
request.user.player.username))
warning.severity = post['severity']
warning.save()
form = {'severity': WarningModel.SEVERITY}
return render(request, 'minecraft_manager/warning_info.html',
{'current_app': 'warning', 'form': form, 'warning': warning})
class WarningAdd(View):
@method_decorator(csrf_protect)
def get(self, request):
form = WarningForm().as_p()
return render(request, 'minecraft_manager/warning_add.html',
{'current_app': 'warning', 'form': form})
def post(self, request):
post = request.POST
form = WarningForm(post)
if form.is_valid():
warning = form.save()
warning.staff = request.user
warning.save()
API.discord_mcm(
"**{0}** issued a **{1}** severity warning to **{2}**\nPreview: {3}".format(warning.staff.player.username,
warning.severity_display,
warning.player.username,
warning.snippet))
return redirect("{0}{1}".format(reverse('warning'), warning.id))
else:
return render(request, 'minecraft_manager/warning_add.html',
{'current_app': 'warning', 'form': form})
class Report(View):
def get(self, request):
get = request.GET
results = []
timestamp = None
report = get.get('report')
world = get.get('world')
if report and world:
# Get some results
report_dir = os.path.join(settings.MINECRAFT_BASE_DIR, 'plugins', 'MinecraftManager', 'report.json')
if os.path.exists(report_dir):
with open(report_dir) as rpt:
raw = json.load(rpt)
time = raw.get('time')
timestamp = datetime.datetime.utcfromtimestamp(float(time))
timestamp = pytz.timezone('UTC').localize(timestamp)
timestamp = timestamp.astimezone(pytz.timezone(request.user.usersettings.default_timezone))
for w in raw:
if w.endswith('_nether') and world == 'nether':
world_raw = raw[w]
elif w.endswith('_the_end') and world == 'end':
world_raw = raw[w]
elif w != 'time' and not (w.endswith('_nether') or w.endswith('_the_end')) and world == 'overworld': # Need to think of a way to clean this up...
world_raw = raw[w]
break
results_raw = world_raw.get(report)
for result in results_raw:
if report == 'players':
results.append({'name': result.get('name'), 'x': result.get('x'), 'y': result.get('y'),
'z': result.get('z')})
else:
type_formatted = ' '.join([part.title() for part in result.split('_')])
if report == 'entities':
for location in results_raw[result]:
results.append({'name': type_formatted, 'x': location.get('x'), 'y': location.get('y'),
'z': location.get('z')})
else:
results.append({'name': type_formatted, 'count': results_raw.get(result)})
else:
results = 'NONE'
elif report or world:
results = 'BOTH'
if len(results) == 0:
results = 'EMPTY'
return render(request, 'minecraft_manager/report.html', {'current_app': 'report', 'report': report, 'world': world, 'timestamp': timestamp, 'results': results})
def post(self, request):
pass
class Chat(View):
@staticmethod
def replace_ascii(message):
return message.replace(" ", "\\040").replace("\"", "\\042").replace("#", "\\043").replace("$", "\\044")\
.replace("%", "\\045").replace("&", "\\046").replace("(", "\\050").replace(")", "\\051").replace("*", "\\052")\
.replace("+", "\\053").replace("-", "\\055")
def get(self, request):
staff = hasattr(settings, 'STAFF_LOG')
return render(request, 'minecraft_manager/chat.html', {'current_app': 'chat', 'staff': staff})
def post(self, request):
post = request.POST
if 'chat' in post and 'message' in post:
API.plugin(post['chat'], "{0} {1}".format(request.user.player.username, post['message']))
data = {'success': True, 'message': 'Message sent successfully.'}
else:
data = {'success': False, 'message': 'No chat type or message set.'}
return JsonResponse(data)
class Bots(View):
def assets(self):
path = os.path.abspath(os.path.dirname(__file__))
bot_dir = os.path.join(path, 'assets/bots')
return bot_dir
def get_bots(self):
bot_dir = getattr(settings, 'BOT_DIR', None)
bots = []
if bot_dir:
for file in os.listdir(bot_dir):
if file.endswith('.bot.py'):
ve = file.replace('.bot.py', '')
py = os.path.join(bot_dir, ve, 'bin/python')
if os.path.isfile(py):
bots.append({'name': file.replace('.bot.py', ''), 'asset': False, 'executable': py})
else:
bots.append({'name': file.replace('.bot.py', ''), 'asset': False, 'executable': sys.executable})
# Also get packaged MCM bots
for file in os.listdir(self.assets()):
if file.endswith('.bot.py'):
bots.append({'name': file.replace('.bot.py', ''), 'asset': True, 'executable': sys.executable})
return bots
def get_form(self):
bots = self.get_bots()
plugin_port = getattr(settings, 'PLUGIN_PORT', None)
screens = subprocess.getoutput("screen -ls")
form = {'screens': screens, 'bots': bots}
for bot in bots:
form[bot['name']] = True if "{0}_{1}".format(plugin_port, bot['name']) in screens else False
return form
def get(self, request):
return render(request, 'minecraft_manager/bots.html', {'current_app': 'bots', 'form': self.get_form()})
def post(self, request):
post = request.POST
plugin_port = getattr(settings, 'PLUGIN_PORT', None)
for bot in self.get_bots():
if bot['name'] in post:
if post[bot['name']] == "stop":
subprocess.run('screen -X -S "{0}_{1}" quit'.format(plugin_port, bot['name']), shell=True)
elif post[bot['name']] in ('start', 'restart'):
subprocess.run('screen -X -S "{0}_{1}" quit'.format(plugin_port, bot['name']), shell=True)
path = self.assets() if bot['asset'] else getattr(settings, 'BOT_DIR', "")
if not path.endswith("/"):
path += "/"
print('screen -S {0}_{1} -d -m {2} {3}{1}.bot.py'.format(plugin_port, bot['name'], bot['executable'], path))
subprocess.run(
'screen -S {0}_{1} -d -m {2} {3}{1}.bot.py'.format(plugin_port, bot['name'], bot['executable'], path),
shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return render(request, 'minecraft_manager/bots.html', {'current_app': 'bots', 'form': self.get_form()})