Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Etzelia | d95f937f7a |
7
LICENSE
7
LICENSE
|
@ -1,7 +0,0 @@
|
||||||
Copyright 2020 Etzelia
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
@ -1,15 +0,0 @@
|
||||||
# Requirements
|
|
||||||
|
|
||||||
This project was built with (and should work with non-breaking upgrades of):
|
|
||||||
|
|
||||||
| Package | Version |
|
|
||||||
|-------------------------------------------------------|---------|
|
|
||||||
| [discord.py](https://pypi.org/project/discord.py/) | 1.3.4 |
|
|
||||||
| [Django](https://pypi.org/project/Django/) | 2.2 |
|
|
||||||
| [mcstatus](https://pypi.org/project/mcstatus/) | 3.1.1 |
|
|
||||||
| [mysqlclient*](https://pypi.org/project/mysqlclient/) | 1.4.6 |
|
|
||||||
| [PyYAML](https://pypi.org/project/PyYAML/) | 5.3.1 |
|
|
||||||
|
|
||||||
|
|
||||||
*or any other preferred DB
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
default_app_config = 'minecraft_manager.apps.MinecraftManagerAppConfig'
|
|
13
admin.py
13
admin.py
|
@ -3,8 +3,8 @@ from __future__ import absolute_import
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from minecraft_manager.models import Application, Note, Ticket, TicketNote, Player, IP, UserSettings, Alert, Attachment
|
from minecraft_manager.models import Application, Note, Ticket, TicketNote, Player, IP, UserSettings, Alert
|
||||||
from minecraft_manager.api.admin import register as api_register
|
from minecraft_manager.api.admin import register as api_register
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,14 +90,6 @@ class IPAdmin(admin.ModelAdmin):
|
||||||
search_fields = ["player__username", "ip"]
|
search_fields = ["player__username", "ip"]
|
||||||
|
|
||||||
|
|
||||||
class AttachmentAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ["file_name", "model_name"]
|
|
||||||
|
|
||||||
def model_name(self, attachment):
|
|
||||||
return attachment.ref_name
|
|
||||||
model_name.short_description = "Model"
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
admin.site.unregister(User)
|
admin.site.unregister(User)
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
|
@ -109,7 +101,6 @@ try:
|
||||||
admin.site.register(Player, PlayerAdmin)
|
admin.site.register(Player, PlayerAdmin)
|
||||||
admin.site.register(IP, IPAdmin)
|
admin.site.register(IP, IPAdmin)
|
||||||
admin.site.register(Alert)
|
admin.site.register(Alert)
|
||||||
admin.site.register(Attachment, AttachmentAdmin)
|
|
||||||
api_register()
|
api_register()
|
||||||
except admin.sites.AlreadyRegistered:
|
except admin.sites.AlreadyRegistered:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from minecraft_manager.api.models import Token
|
from minecraft_manager.api.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
|
37
api/api.py
37
api/api.py
|
@ -1,7 +1,8 @@
|
||||||
import socket, requests, logging, os, datetime, pytz, mcstatus, random, string
|
import socket, logging, os, datetime, pytz, mcstatus, random, string
|
||||||
from minecraft_manager.models import Alert
|
from minecraft_manager.models import Alert
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from minecraft_manager.bot.discord import send as discord_send, DestType
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -38,31 +39,25 @@ def plugin(key, command):
|
||||||
|
|
||||||
|
|
||||||
def discord_mcm(message='', embed=None, ping=False):
|
def discord_mcm(message='', embed=None, ping=False):
|
||||||
discord_mcm_webhook = getattr(settings, 'DISCORD_MCM_WEBHOOK', None)
|
channel_id = getattr(settings, 'DISCORD_MCM_CHANNEL', None)
|
||||||
if discord_mcm_webhook:
|
if channel_id:
|
||||||
return post_webhook(discord_mcm_webhook, message, embed, ping)
|
ping_list = getattr(settings, 'DISCORD_PING_LIST', [])
|
||||||
return None
|
if ping and ping_list:
|
||||||
|
ping_list = ["<@&{0}>".format(ping) for ping in ping_list]
|
||||||
|
message = "{0}\n{1}".format(" ".join(ping_list), message)
|
||||||
|
discord_send(DestType.CHANNEL, channel_id, message, embed)
|
||||||
|
|
||||||
|
|
||||||
def discord_notification(message='', embed=None, ping=False):
|
def discord_notification(message='', embed=None, ping=False):
|
||||||
discord_notification_webhook = getattr(settings, 'DISCORD_NOTIFICATION_WEBHOOK', None)
|
channel_id = getattr(settings, 'DISCORD_NOTIFICATION_CHANNEL', None)
|
||||||
if discord_notification_webhook:
|
if channel_id:
|
||||||
return post_webhook(discord_notification_webhook, message, embed, ping)
|
ping_list = getattr(settings, 'DISCORD_PING_LIST', [])
|
||||||
return None
|
if ping and ping_list:
|
||||||
|
ping_list = ["<@&{0}>".format(ping) for ping in ping_list]
|
||||||
|
message = "{0}\n{1}".format(" ".join(ping_list), message)
|
||||||
|
discord_send(DestType.CHANNEL, channel_id, message, embed)
|
||||||
|
|
||||||
|
|
||||||
def post_webhook(webhook_url, message, embed, ping):
|
|
||||||
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.to_dict()]
|
|
||||||
return requests.post(webhook_url, json=data)
|
|
||||||
|
|
||||||
|
|
||||||
def strip_format(message):
|
def strip_format(message):
|
||||||
return message.replace("§0", "").replace("§1", "").replace("§2", "").replace("§3", "").replace("§4", "") \
|
return message.replace("§0", "").replace("§1", "").replace("§2", "").replace("§3", "").replace("§4", "") \
|
||||||
|
|
32
api/views.py
32
api/views.py
|
@ -1,23 +1,22 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import datetime
|
import logging, datetime
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
from django.contrib.auth import update_session_auth_hash
|
|
||||||
from django.contrib.auth.forms import PasswordChangeForm
|
from django.contrib.auth.forms import PasswordChangeForm
|
||||||
|
from django.contrib.auth import update_session_auth_hash
|
||||||
|
from django.apps import apps
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.forms import modelform_factory
|
|
||||||
from django.http import JsonResponse, HttpResponse
|
from django.http import JsonResponse, HttpResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from django.forms import modelform_factory
|
||||||
|
|
||||||
import minecraft_manager.api.api as mcm_api
|
|
||||||
import minecraft_manager.external.stats as mcm_stats
|
|
||||||
import minecraft_manager.forms as mcm_forms
|
import minecraft_manager.forms as mcm_forms
|
||||||
import minecraft_manager.utils as mcm_utils
|
|
||||||
from minecraft_manager.api.models import Token
|
|
||||||
from minecraft_manager.models import Player, UserSettings, Application, IP, Ticket, Note
|
from minecraft_manager.models import Player, UserSettings, Application, IP, Ticket, Note
|
||||||
|
import minecraft_manager.api.api as mcm_api
|
||||||
|
from minecraft_manager.api.models import Token
|
||||||
|
import minecraft_manager.utils as mcm_utils
|
||||||
|
import minecraft_manager.external.stats as mcm_stats
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -206,11 +205,10 @@ class PluginAPI(View):
|
||||||
application.save()
|
application.save()
|
||||||
json['message'] = "Application was successfully {0}.".format(
|
json['message'] = "Application was successfully {0}.".format(
|
||||||
"accepted" if post['action'] == "True" else "denied")
|
"accepted" if post['action'] == "True" else "denied")
|
||||||
link = mcm_utils.full_reverse('application_info', application.id)
|
mcm_api.discord_mcm("{0}'s application (#{1}) was {2} by {3}".format(application.username,
|
||||||
mcm_api.discord_mcm("{0}'s application ([#{1}]({4})) was {2} by {3}".format(application.username,
|
|
||||||
application.id,
|
application.id,
|
||||||
"accepted" if post['action'] == "True" else "denied",
|
"accepted" if post['action'] == "True" else "denied",
|
||||||
post['username'], link))
|
post['username']))
|
||||||
mcm_api.plugin("accept" if post['action'] == "True" else "deny", application.username)
|
mcm_api.plugin("accept" if post['action'] == "True" else "deny", application.username)
|
||||||
else:
|
else:
|
||||||
json['status'] = False
|
json['status'] = False
|
||||||
|
@ -299,7 +297,7 @@ class PluginAPI(View):
|
||||||
ticket = Ticket(player=player, message=post['message'], x=post['x'], y=post['y'], z=post['z'], world=post['world'])
|
ticket = Ticket(player=player, message=post['message'], x=post['x'], y=post['y'], z=post['z'], world=post['world'])
|
||||||
ticket.save()
|
ticket.save()
|
||||||
json['message'] = "Ticket submitted."
|
json['message'] = "Ticket submitted."
|
||||||
link = mcm_utils.full_reverse('ticket_info', ticket.id)
|
link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard/ticket', ticket.id))
|
||||||
msg = mcm_utils.build_ticket(ticket, link)
|
msg = mcm_utils.build_ticket(ticket, link)
|
||||||
json['extra'] = {'id': ticket.id, 'link': link}
|
json['extra'] = {'id': ticket.id, 'link': link}
|
||||||
mcm_api.discord_mcm(embed=msg, ping=True)
|
mcm_api.discord_mcm(embed=msg, ping=True)
|
||||||
|
@ -313,8 +311,8 @@ class PluginAPI(View):
|
||||||
warning = Note(player=player, message=post['message'], importance=post['severity'], staff=staff.auth_user)
|
warning = Note(player=player, message=post['message'], importance=post['severity'], staff=staff.auth_user)
|
||||||
warning.save()
|
warning.save()
|
||||||
json['message'] = "Warning issued."
|
json['message'] = "Warning issued."
|
||||||
link = mcm_utils.full_reverse('note_info', warning.id)
|
link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard/note', warning.id))
|
||||||
msg = mcm_utils.build_note(warning, link)
|
msg = mcm_utils.build_warning(warning, link)
|
||||||
mcm_api.discord_mcm(embed=msg)
|
mcm_api.discord_mcm(embed=msg)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
json['status'] = False
|
json['status'] = False
|
||||||
|
@ -392,7 +390,7 @@ class ModelAPI(View):
|
||||||
json = []
|
json = []
|
||||||
for value in objects:
|
for value in objects:
|
||||||
try:
|
try:
|
||||||
link = mcm_utils.full_reverse(f"{request_model}_info", value['id'])
|
link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard', request_model, value['id']))
|
||||||
value['link'] = link
|
value['link'] = link
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
7
apps.py
7
apps.py
|
@ -1,7 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class MinecraftManagerAppConfig(AppConfig):
|
|
||||||
name = 'minecraft_manager'
|
|
||||||
verbose_name = "Minecraft Manager"
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
from django.conf import settings
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class Bot:
|
||||||
|
plugin_port = getattr(settings, 'PLUGIN_PORT', None)
|
||||||
|
bot_dir = getattr(settings, 'BOT_DIR', "")
|
||||||
|
if not bot_dir.endswith("/"):
|
||||||
|
bot_dir += "/"
|
||||||
|
|
||||||
|
def __init__(self, name, asset, executable=None, start=None, stop=None, restart=None, status=None, display=None):
|
||||||
|
self.name = name
|
||||||
|
self.asset = asset
|
||||||
|
self.executable = executable
|
||||||
|
self.start = start if start else self._start
|
||||||
|
self.stop = stop if stop else self._stop
|
||||||
|
self.restart = restart if restart else self._restart
|
||||||
|
self.status = status if status else self._status
|
||||||
|
self.display = display if display else self._display
|
||||||
|
|
||||||
|
def _start(self):
|
||||||
|
screen = 'screen -S {0}_{1} -d -m {2} {3}{1}.bot.py'
|
||||||
|
subprocess.run(screen.format(self.plugin_port, self.name, self.executable, self.bot_dir),
|
||||||
|
shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
|
||||||
|
def _stop(self):
|
||||||
|
subprocess.run('screen -X -S "{0}_{1}" quit'.format(self.plugin_port, self.name), shell=True)
|
||||||
|
|
||||||
|
def _restart(self):
|
||||||
|
self.stop()
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def _status(self):
|
||||||
|
screens = subprocess.getoutput("screen -ls")
|
||||||
|
return True if "{0}_{1}".format(self.plugin_port, self.name) in screens else False
|
||||||
|
|
||||||
|
def _display(self):
|
||||||
|
return "Started" if self.status() else "Stopped"
|
|
@ -1,29 +0,0 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import django
|
|
||||||
|
|
||||||
# This block is assuming you will use this exact file
|
|
||||||
sep = os.sep
|
|
||||||
path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
path = path.split(sep)[:-3]
|
|
||||||
project = path[-1]
|
|
||||||
path = sep.join(path)
|
|
||||||
|
|
||||||
# What you need here is
|
|
||||||
# project = name of your main django project
|
|
||||||
# path = path to the root of your django project
|
|
||||||
# e.g. If your project is at /home/mcm/django1 and settings.py is at /home/mcm/django1/django2/settings.py
|
|
||||||
# project = django2
|
|
||||||
# path = /home/mcm/django1
|
|
||||||
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.bot.discord import Discord
|
|
||||||
|
|
||||||
token = getattr(settings, 'DISCORD_BOT_TOKEN', None)
|
|
||||||
bot = Discord(token)
|
|
||||||
|
|
||||||
bot.run_bot()
|
|
|
@ -1,13 +1,12 @@
|
||||||
import discord
|
import discord
|
||||||
from io import StringIO
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import close_old_connections
|
from django.db import close_old_connections
|
||||||
|
|
||||||
from minecraft_manager.api import api
|
from minecraft_manager.api import api
|
||||||
from minecraft_manager.bot.utils import get_application
|
from minecraft_manager.bot.utils import get_application
|
||||||
|
from minecraft_manager.utils import build_application
|
||||||
from minecraft_manager.models import Application, Player
|
from minecraft_manager.models import Application, Player
|
||||||
from minecraft_manager.utils import build_application, full_static
|
|
||||||
|
|
||||||
|
|
||||||
class Commands(commands.Cog):
|
class Commands(commands.Cog):
|
||||||
|
@ -45,8 +44,8 @@ class Commands(commands.Cog):
|
||||||
async def help(self, ctx):
|
async def help(self, ctx):
|
||||||
embed = discord.Embed(colour=discord.Colour(0x417505))
|
embed = discord.Embed(colour=discord.Colour(0x417505))
|
||||||
embed.set_thumbnail(
|
embed.set_thumbnail(
|
||||||
url=full_static('favicon.png'))
|
url="https://cdn.discordapp.com/avatars/454457830918062081/b5792489bc43d9e17b8f657880a17dd4.png")
|
||||||
embed.title = "Minecraft Manager Help"
|
embed.add_field(name="Minecraft Manager Help", value="-----------------------------")
|
||||||
embed.add_field(name="{}app search <username>".format(self.bot.prefix),
|
embed.add_field(name="{}app search <username>".format(self.bot.prefix),
|
||||||
value="Search for applications by partial or exact username.")
|
value="Search for applications by partial or exact username.")
|
||||||
embed.add_field(name="{}app info <app ID>".format(self.bot.prefix),
|
embed.add_field(name="{}app info <app ID>".format(self.bot.prefix),
|
||||||
|
@ -55,7 +54,6 @@ class Commands(commands.Cog):
|
||||||
embed.add_field(name="{}demote <username>".format(self.bot.prefix),
|
embed.add_field(name="{}demote <username>".format(self.bot.prefix),
|
||||||
value="Demote a player to the role given to accepted applications.")
|
value="Demote a player to the role given to accepted applications.")
|
||||||
embed.add_field(name="{}compare".format(self.bot.prefix), value="Compare Discord users to the Whitelist.")
|
embed.add_field(name="{}compare".format(self.bot.prefix), value="Compare Discord users to the Whitelist.")
|
||||||
embed.add_field(name="{}sync".format(self.bot.prefix), value="Sync Discord users with Player records.")
|
|
||||||
await self.bot.discord_message(ctx.message.channel, embed)
|
await self.bot.discord_message(ctx.message.channel, embed)
|
||||||
|
|
||||||
@commands.group("app", aliases=["application"])
|
@commands.group("app", aliases=["application"])
|
||||||
|
@ -155,29 +153,6 @@ class Commands(commands.Cog):
|
||||||
if not api.plugin(api.PLUGIN_DENY, application.username):
|
if not api.plugin(api.PLUGIN_DENY, application.username):
|
||||||
await self.bot.discord_message(ctx.message.channel, "Could not deny in-game, is the server running?")
|
await self.bot.discord_message(ctx.message.channel, "Could not deny in-game, is the server running?")
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def clear(self, ctx, app_id: int):
|
|
||||||
await self._clear(ctx, app_id)
|
|
||||||
|
|
||||||
@app.command("clear")
|
|
||||||
async def app_clear(self, ctx, app_id: int):
|
|
||||||
await self._clear(ctx, app_id)
|
|
||||||
|
|
||||||
async def _clear(self, ctx, app_id: int):
|
|
||||||
application = get_application(app_id)
|
|
||||||
if not application:
|
|
||||||
await self.bot.discord_message(ctx.message.channel, "An Application with that ID doesn't exist.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not application.accepted:
|
|
||||||
application.accepted = None
|
|
||||||
application.save()
|
|
||||||
if Player.objects.filter(username__iexact=application.username).exists():
|
|
||||||
player = Player.objects.get(username__iexact=application.username)
|
|
||||||
player.application_id = application.id
|
|
||||||
player.save()
|
|
||||||
await self.bot.discord_message(ctx.message.channel, "App ID **{0}** was successfully cleared.".format(app_id))
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def search(self, ctx, search: str):
|
async def search(self, ctx, search: str):
|
||||||
await self._search(ctx, search)
|
await self._search(ctx, search)
|
||||||
|
@ -260,31 +235,6 @@ class Commands(commands.Cog):
|
||||||
header = "**The following users do not have an application or player match on the whitelist:**\n"
|
header = "**The following users do not have an application or player match on the whitelist:**\n"
|
||||||
await self.bot.discord_message(ctx.author, "{}```{}```".format(header, "\n".join(no_application)))
|
await self.bot.discord_message(ctx.author, "{}```{}```".format(header, "\n".join(no_application)))
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def sync(self, ctx):
|
|
||||||
def sync_player(player):
|
|
||||||
for member in ctx.guild.members:
|
|
||||||
name = member.nick if member.nick else member.name
|
|
||||||
if player.username.lower() == name.lower():
|
|
||||||
player.discord_id = member.id
|
|
||||||
player.save()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
# Attempt to sync users
|
|
||||||
need_sync = Player.objects.filter(discord_id__isnull=True)
|
|
||||||
need_sync_count = need_sync.count()
|
|
||||||
synced = []
|
|
||||||
not_synced = []
|
|
||||||
if need_sync_count > 0:
|
|
||||||
for ns in need_sync:
|
|
||||||
if sync_player(ns):
|
|
||||||
synced.append(ns.username)
|
|
||||||
else:
|
|
||||||
not_synced.append(ns.username)
|
|
||||||
txt = "Synced\n\t{}\n\nNot Synced\n\t{}".format('\n\t'.join(synced), '\n\t'.join(not_synced))
|
|
||||||
attach = discord.File(fp=StringIO(txt), filename="sync.txt")
|
|
||||||
await ctx.channel.send(content="Successfully synced {}/{} players.".format(len(synced), need_sync_count), file=attach)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_cog(Commands(bot))
|
bot.add_cog(Commands(bot))
|
||||||
|
|
130
bot/discord.py
130
bot/discord.py
|
@ -1,21 +1,41 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
|
import threading
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.shortcuts import reverse
|
||||||
from minecraft_manager.models import Application, Ticket
|
from minecraft_manager.models import Application, Ticket
|
||||||
from minecraft_manager.utils import full_reverse
|
from minecraft_manager.utils import url_path
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
discord_loop = None
|
||||||
|
discord_bot = None
|
||||||
description = '''
|
description = '''
|
||||||
A Discord bot connected to an MCM instance.
|
A Discord bot connected to an MCM instance.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordStatus(Enum):
|
||||||
|
STOPPED = 0
|
||||||
|
STARTING = 1
|
||||||
|
STARTED = 2
|
||||||
|
STOPPING = 3
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name.title()
|
||||||
|
|
||||||
|
def is_running(self):
|
||||||
|
return self != DiscordStatus.STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
discord_status = DiscordStatus.STOPPED
|
||||||
|
|
||||||
|
|
||||||
class Discord(commands.Bot):
|
class Discord(commands.Bot):
|
||||||
discord_game = 'MCM'
|
discord_game = 'MCM'
|
||||||
prefix = getattr(settings, 'DISCORD_BOT_PREFIX', '!')
|
prefix = getattr(settings, 'DISCORD_BOT_PREFIX', '!')
|
||||||
|
@ -23,15 +43,14 @@ class Discord(commands.Bot):
|
||||||
superuser_roles = getattr(settings, 'DISCORD_SUPERUSER_ROLES', [])
|
superuser_roles = getattr(settings, 'DISCORD_SUPERUSER_ROLES', [])
|
||||||
error_users = getattr(settings, 'DISCORD_ERROR_USERS', [])
|
error_users = getattr(settings, 'DISCORD_ERROR_USERS', [])
|
||||||
|
|
||||||
def __init__(self, token):
|
def __init__(self, token, loop):
|
||||||
intents = discord.Intents.default()
|
super().__init__(command_prefix=self.prefix, description=description, case_insensitive=True, help_command=None, activity=discord.Game(name=self.discord_game), loop=loop)
|
||||||
intents.members = True
|
|
||||||
super().__init__(command_prefix=self.prefix, description=description, case_insensitive=True, help_command=None,
|
|
||||||
activity=discord.Game(name=self.discord_game), intents=intents)
|
|
||||||
self.token = token
|
self.token = token
|
||||||
self.load_extension("minecraft_manager.bot.commands")
|
self.load_extension("minecraft_manager.bot.commands")
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
|
global discord_status
|
||||||
|
discord_status = DiscordStatus.STARTED
|
||||||
print('Logged in as')
|
print('Logged in as')
|
||||||
print(self.user.name)
|
print(self.user.name)
|
||||||
print(self.user.id)
|
print(self.user.id)
|
||||||
|
@ -48,11 +67,11 @@ class Discord(commands.Bot):
|
||||||
content = ""
|
content = ""
|
||||||
unanswered_applications = Application.objects.filter(accepted=None)
|
unanswered_applications = Application.objects.filter(accepted=None)
|
||||||
if len(unanswered_applications) > 0:
|
if len(unanswered_applications) > 0:
|
||||||
link = full_reverse('application')
|
link = url_path(settings.MCM_BASE_LINK, 'dashboard/application')
|
||||||
content += "[Unanswered Applications: {}]({})".format(len(unanswered_applications), link)
|
content += "[Unanswered Applications: {}]({})".format(len(unanswered_applications), link)
|
||||||
unclaimed_tickets = Ticket.objects.filter(staff=None, resolved=False)
|
unclaimed_tickets = Ticket.objects.filter(staff=None, resolved=False)
|
||||||
if len(unclaimed_tickets) > 0:
|
if len(unclaimed_tickets) > 0:
|
||||||
link = full_reverse('ticket')
|
link = url_path(settings.MCM_BASE_LINK, 'dashboard/ticket')
|
||||||
if content:
|
if content:
|
||||||
content += "\n\n"
|
content += "\n\n"
|
||||||
content += "[Unclaimed Tickets: {}]({})".format(len(unclaimed_tickets), link)
|
content += "[Unclaimed Tickets: {}]({})".format(len(unclaimed_tickets), link)
|
||||||
|
@ -61,6 +80,10 @@ class Discord(commands.Bot):
|
||||||
embed.description = content
|
embed.description = content
|
||||||
await self.discord_message(channel, embed)
|
await self.discord_message(channel, embed)
|
||||||
|
|
||||||
|
async def on_disconnect(self):
|
||||||
|
global discord_status
|
||||||
|
discord_status = DiscordStatus.STOPPED
|
||||||
|
|
||||||
async def discord_message(self, dest, message):
|
async def discord_message(self, dest, message):
|
||||||
if isinstance(message, discord.Embed):
|
if isinstance(message, discord.Embed):
|
||||||
for idx, field in enumerate(message.fields):
|
for idx, field in enumerate(message.fields):
|
||||||
|
@ -84,14 +107,81 @@ class Discord(commands.Bot):
|
||||||
await self.discord_message(user, '```python\n{}```'.format(error))
|
await self.discord_message(user, '```python\n{}```'.format(error))
|
||||||
|
|
||||||
def run_bot(self):
|
def run_bot(self):
|
||||||
loop = asyncio.get_event_loop()
|
global discord_loop
|
||||||
try:
|
loop = True
|
||||||
loop.run_until_complete(self.start(self.token))
|
while loop:
|
||||||
except KeyboardInterrupt:
|
try:
|
||||||
logger.info("Bot received keyboard interrupt")
|
discord_loop.run_until_complete(self.start(self.token))
|
||||||
except Exception as e:
|
except KeyboardInterrupt:
|
||||||
print(e)
|
logger.info("Bot received keyboard interrupt")
|
||||||
logger.info('Bot encountered the following unhandled exception %s', e)
|
loop = False
|
||||||
finally:
|
except Exception as e:
|
||||||
loop.run_until_complete(self.close())
|
print(e)
|
||||||
logger.info("Bot shutting down...")
|
logger.info('Bot encountered the following unhandled exception %s', e)
|
||||||
|
loop = False
|
||||||
|
|
||||||
|
|
||||||
|
def start():
|
||||||
|
global discord_loop, discord_bot, discord_status
|
||||||
|
if discord_status != DiscordStatus.STOPPED:
|
||||||
|
return
|
||||||
|
token = getattr(settings, 'DISCORD_BOT_TOKEN', None)
|
||||||
|
discord_loop = asyncio.new_event_loop()
|
||||||
|
discord_bot = Discord(token, discord_loop)
|
||||||
|
thread = threading.Thread(target=discord_bot.run_bot)
|
||||||
|
thread.start()
|
||||||
|
discord_status = DiscordStatus.STARTING
|
||||||
|
|
||||||
|
|
||||||
|
def stop():
|
||||||
|
global discord_loop, discord_bot, discord_status
|
||||||
|
if discord_status == DiscordStatus.STARTED:
|
||||||
|
discord_loop.create_task(discord_bot.close())
|
||||||
|
discord_status = DiscordStatus.STOPPING
|
||||||
|
discord_loop = None
|
||||||
|
discord_bot = None
|
||||||
|
|
||||||
|
|
||||||
|
def restart():
|
||||||
|
def _restart():
|
||||||
|
stop()
|
||||||
|
while discord_status.is_running():
|
||||||
|
pass
|
||||||
|
start()
|
||||||
|
if discord_status != DiscordStatus.STARTED:
|
||||||
|
return
|
||||||
|
thread = threading.Thread(target=_restart)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
def status():
|
||||||
|
return discord_status.is_running()
|
||||||
|
|
||||||
|
|
||||||
|
def display():
|
||||||
|
return str(discord_status)
|
||||||
|
|
||||||
|
|
||||||
|
class DestType(Enum):
|
||||||
|
CHANNEL = 1
|
||||||
|
USER = 2
|
||||||
|
|
||||||
|
|
||||||
|
def send(dest_type: DestType, dest_id: int, message: str = "", embed: discord.Embed = None):
|
||||||
|
async def _send():
|
||||||
|
if dest_type == DestType.CHANNEL:
|
||||||
|
dest = discord_bot.get_channel(dest_id)
|
||||||
|
elif dest_type == DestType.USER:
|
||||||
|
dest = discord_bot.get_user(dest_id)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
if message is not None:
|
||||||
|
await dest.send(message)
|
||||||
|
if embed is not None:
|
||||||
|
for idx, field in enumerate(embed.fields):
|
||||||
|
if not field.value:
|
||||||
|
embed.set_field_at(idx, name=field.name, value="N/A")
|
||||||
|
await dest.send(embed=embed)
|
||||||
|
global discord_loop, discord_bot
|
||||||
|
if discord_loop:
|
||||||
|
discord_loop.create_task(_send())
|
||||||
|
|
29
discord.py
29
discord.py
|
@ -1,29 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
|
|
||||||
class TimestampStyle(Enum):
|
|
||||||
SHORT_TIME = "t"
|
|
||||||
"""16:20"""
|
|
||||||
LONG_TIME = "T"
|
|
||||||
"""16:20:30"""
|
|
||||||
SHORT_DATE = "d"
|
|
||||||
"""20/04/2021"""
|
|
||||||
LONG_DATE = "D"
|
|
||||||
"""20 April 2021"""
|
|
||||||
SHORT_DATETIME = "f"
|
|
||||||
"""20 April 2021 16:20"""
|
|
||||||
LONG_DATETIME = "F"
|
|
||||||
"""Tuesday, 20 April 2021 16:20"""
|
|
||||||
RELATIVE = "R"
|
|
||||||
"""2 months ago"""
|
|
||||||
|
|
||||||
|
|
||||||
def format_timestamp(unix: Union[int, datetime], style: TimestampStyle = TimestampStyle.SHORT_DATETIME) -> str:
|
|
||||||
t = 0
|
|
||||||
if isinstance(unix, int):
|
|
||||||
t = unix
|
|
||||||
elif isinstance(unix, datetime):
|
|
||||||
t = unix.timestamp()
|
|
||||||
return f"<t:{t}:{style}>"
|
|
|
@ -23,10 +23,6 @@ Optional
|
||||||
|
|
||||||
``BOT_DIR`` - The path to your bot directory.
|
``BOT_DIR`` - The path to your bot directory.
|
||||||
|
|
||||||
``DISCORD_NOTIFICATION_WEBHOOK`` - The URL for the webhook used for notifications.
|
|
||||||
|
|
||||||
``DISCORD_MCM_WEBHOOK`` - The URL for the webhook used for Applications, Tickets, and Warnings.
|
|
||||||
|
|
||||||
``DISCORD_BOT_TOKEN`` - The token to use to run the Discord bot. This must be generated by you in the Discord developer area.
|
``DISCORD_BOT_TOKEN`` - The token to use to run the Discord bot. This must be generated by you in the Discord developer area.
|
||||||
|
|
||||||
``DISCORD_PING_LIST`` - A list of Discord Role IDs to ping whenever certain messages are sent.
|
``DISCORD_PING_LIST`` - A list of Discord Role IDs to ping whenever certain messages are sent.
|
||||||
|
@ -43,9 +39,7 @@ Optional
|
||||||
|
|
||||||
``DISCORD_NOTIFICATION_CHANNEL`` - The ID for the channel used for notifications.
|
``DISCORD_NOTIFICATION_CHANNEL`` - The ID for the channel used for notifications.
|
||||||
|
|
||||||
``INVITE_LINK`` - The invite link to your community.
|
``DISCORD_INVITE`` - The invite code to your Discord, for after a player applies on the web form.
|
||||||
|
|
||||||
``INVITE_LABEL`` - The invite label for your community.
|
|
||||||
|
|
||||||
``DYNMAP_URL`` - The URL to your dynmap if you have one. Leave blank if you'd rather use a static background for web forms.
|
``DYNMAP_URL`` - The URL to your dynmap if you have one. Leave blank if you'd rather use a static background for web forms.
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from django.urls import path
|
from django.conf.urls import url
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
import minecraft_manager.external.views as external
|
import minecraft_manager.external.views as external
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('apply/', external.Apply.as_view(), name="external-apply"),
|
url(r'^apply/$', external.Apply.as_view(), name="external-apply"),
|
||||||
path('ticket/', external.Ticket.as_view(), name="external-ticket"),
|
url(r'^ticket/$', external.Ticket.as_view(), name="external-ticket"),
|
||||||
path('stats/', external.Stats.as_view(), name="external-stats"),
|
url(r'^stats/$', external.Stats.as_view(), name="external-stats"),
|
||||||
path('stats/player/', external.StatsPlayer.as_view(), name="external-stats-player"),
|
url(r'^stats/player/$', external.StatsPlayer.as_view(), name="external-stats-player"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.shortcuts import render, reverse
|
from django.shortcuts import render, reverse, redirect
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
@ -8,13 +8,12 @@ import minecraft_manager.api.api as mcm_api
|
||||||
import minecraft_manager.utils as mcm_utils
|
import minecraft_manager.utils as mcm_utils
|
||||||
import minecraft_manager.external.stats as mcm_stats
|
import minecraft_manager.external.stats as mcm_stats
|
||||||
from minecraft_manager.models import Player
|
from minecraft_manager.models import Player
|
||||||
import random, yaml, os
|
import random, yaml, os, json, datetime, pytz
|
||||||
|
|
||||||
|
|
||||||
def config():
|
def config():
|
||||||
data = {}
|
data = {}
|
||||||
data['invite_link'] = getattr(settings, "INVITE_LINK", "#")
|
data['discord_invite'] = getattr(settings, "DISCORD_INVITE", "#")
|
||||||
data['invite_label'] = getattr(settings, "INVITE_LABEL", "community")
|
|
||||||
|
|
||||||
dynmap_url = getattr(settings, "DYNMAP_URL", "")
|
dynmap_url = getattr(settings, "DYNMAP_URL", "")
|
||||||
data['dynmap_url'] = dynmap_url
|
data['dynmap_url'] = dynmap_url
|
||||||
|
@ -46,16 +45,12 @@ def config():
|
||||||
def rules():
|
def rules():
|
||||||
path = os.path.join(settings.MINECRAFT_BASE_DIR, "plugins/MinecraftManager/config.yml")
|
path = os.path.join(settings.MINECRAFT_BASE_DIR, "plugins/MinecraftManager/config.yml")
|
||||||
with open(path) as config_file:
|
with open(path) as config_file:
|
||||||
cfg = yaml.safe_load(config_file)
|
config = yaml.load(config_file)
|
||||||
data = cfg['rules']['rules']
|
data = config['rules']['rules']
|
||||||
if cfg['rules']['application']['validate']:
|
if config['rules']['application']['validate']:
|
||||||
data.append("The answer to the final question is \"{}\"".format(cfg['rules']['application']['answer']))
|
data.append("The answer to the final question is \"{}\"".format(config['rules']['application']['answer']))
|
||||||
|
|
||||||
return {
|
return data
|
||||||
"rules": data,
|
|
||||||
"validate": cfg['rules']['application']['validate'],
|
|
||||||
"answer": cfg['rules']['application']['answer']
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
|
@ -64,17 +59,15 @@ class Apply(View):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
form = ApplicationForm()
|
form = ApplicationForm()
|
||||||
return render(request, 'minecraft_manager/external/apply.html',
|
return render(request, 'minecraft_manager/external/apply.html',
|
||||||
{'form': form.as_p(), 'rules': rules()["rules"], 'valid': False, 'map': config(),
|
{'form': form.as_p(), 'rules': rules(), 'valid': False, 'map': config(),
|
||||||
'captcha': getattr(settings, "CAPTCHA_SITE", "")})
|
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
form = ApplicationForm(request.POST)
|
form = ApplicationForm(request.POST)
|
||||||
valid_username = mcm_utils.validate_username(form.data['username'])
|
valid_username = mcm_utils.validate_username(form.data['username'])
|
||||||
captcha = mcm_utils.Captcha(request.POST)
|
captcha = mcm_utils.Captcha(request.POST)
|
||||||
valid = form.is_valid()
|
valid = form.is_valid()
|
||||||
r = rules()
|
if valid and valid_username and captcha.success:
|
||||||
valid_answer = not r["validate"] or r["answer"].casefold() == form.data['read_rules'].casefold()
|
|
||||||
if valid and valid_username and valid_answer and captcha.success:
|
|
||||||
app = form.save()
|
app = form.save()
|
||||||
msg = mcm_utils.build_application(app)
|
msg = mcm_utils.build_application(app)
|
||||||
mcm_api.discord_mcm(message='New Application!', embed=msg)
|
mcm_api.discord_mcm(message='New Application!', embed=msg)
|
||||||
|
@ -84,11 +77,9 @@ class Apply(View):
|
||||||
form.add_error(None, error)
|
form.add_error(None, error)
|
||||||
if not valid_username:
|
if not valid_username:
|
||||||
form.add_error(None, "That username is not a premium Minecraft account")
|
form.add_error(None, "That username is not a premium Minecraft account")
|
||||||
if not valid_answer:
|
|
||||||
form.add_error(None, "Please read the rules again")
|
|
||||||
return render(request, 'minecraft_manager/external/apply.html',
|
return render(request, 'minecraft_manager/external/apply.html',
|
||||||
{'form': form.as_p(), 'rules': r["rules"], 'valid': valid and valid_username and valid_answer and captcha.success, 'map': config(),
|
{'form': form.as_p(), 'rules': rules(), 'valid': valid and valid_username and captcha.success, 'map': config(),
|
||||||
'captcha': getattr(settings, "CAPTCHA_SITE", "")})
|
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
|
@ -98,7 +89,7 @@ class Ticket(View):
|
||||||
form = TicketForm()
|
form = TicketForm()
|
||||||
return render(request, 'minecraft_manager/external/ticket.html',
|
return render(request, 'minecraft_manager/external/ticket.html',
|
||||||
{'form': form.as_p(), 'valid': False, 'map': config(),
|
{'form': form.as_p(), 'valid': False, 'map': config(),
|
||||||
'captcha': getattr(settings, "CAPTCHA_SITE", "")})
|
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
post = request.POST.copy()
|
post = request.POST.copy()
|
||||||
|
@ -116,7 +107,7 @@ class Ticket(View):
|
||||||
if valid and captcha.success:
|
if valid and captcha.success:
|
||||||
ticket = form.save()
|
ticket = form.save()
|
||||||
# Create the message to send to Discord
|
# Create the message to send to Discord
|
||||||
link = mcm_utils.full_reverse('ticket_info', ticket.id)
|
link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard/ticket', ticket.id))
|
||||||
msg = mcm_utils.build_ticket(ticket, link)
|
msg = mcm_utils.build_ticket(ticket, link)
|
||||||
mcm_api.discord_mcm(message="New Ticket", embed=msg, ping=True)
|
mcm_api.discord_mcm(message="New Ticket", embed=msg, ping=True)
|
||||||
mcm_api.plugin("ticket", "{0} {1} {2}".format(username, ticket.id, link))
|
mcm_api.plugin("ticket", "{0} {1} {2}".format(username, ticket.id, link))
|
||||||
|
@ -129,7 +120,7 @@ class Ticket(View):
|
||||||
form.data['player'] = username
|
form.data['player'] = username
|
||||||
return render(request, 'minecraft_manager/external/ticket.html',
|
return render(request, 'minecraft_manager/external/ticket.html',
|
||||||
{'form': form.as_p(), 'valid': valid and captcha.success, 'map': config(),
|
{'form': form.as_p(), 'valid': valid and captcha.success, 'map': config(),
|
||||||
'captcha': getattr(settings, "CAPTCHA_SITE", "")})
|
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
|
|
4
forms.py
4
forms.py
|
@ -30,7 +30,7 @@ class TicketForm(ModelForm):
|
||||||
fields = ['player', 'message', 'priority', 'world', 'x', 'y', 'z']
|
fields = ['player', 'message', 'priority', 'world', 'x', 'y', 'z']
|
||||||
widgets = {
|
widgets = {
|
||||||
'player': TextInput,
|
'player': TextInput,
|
||||||
'message': Textarea(attrs={'style': 'display: block;'}),
|
'message': Textarea,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class NoteForm(ModelForm):
|
||||||
model = Note
|
model = Note
|
||||||
fields = ['player', 'message', 'importance']
|
fields = ['player', 'message', 'importance']
|
||||||
widgets = {
|
widgets = {
|
||||||
'message': Textarea(attrs={'style': 'display: block;'})
|
'message': Textarea
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
# Generated by Django 2.2.3 on 2021-02-06 21:40
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import minecraft_manager.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('minecraft_manager', '0014_auto_20190930_1103'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Attachment',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('ref_model', models.CharField(choices=[('T', 'Ticket Note'), ('N', 'Note')], max_length=1)),
|
|
||||||
('ref_id', models.IntegerField()),
|
|
||||||
('file', models.FileField(upload_to=minecraft_manager.models._attachment_upload_to)),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='application',
|
|
||||||
name='reference',
|
|
||||||
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='How did you find out about our server?'),
|
|
||||||
),
|
|
||||||
]
|
|
File diff suppressed because one or more lines are too long
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.2.3 on 2021-07-20 23:29
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('minecraft_manager', '0016_auto_20210328_0131'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='player',
|
|
||||||
name='discord_id',
|
|
||||||
field=models.CharField(blank=True, max_length=30, null=True, unique=True),
|
|
||||||
),
|
|
||||||
]
|
|
78
models.py
78
models.py
|
@ -1,17 +1,9 @@
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from datetime import datetime
|
|
||||||
from os.path import basename
|
|
||||||
|
|
||||||
import pytz
|
|
||||||
import yaml
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.signals import pre_delete
|
import logging, yaml, pytz, json, os
|
||||||
from django.dispatch import receiver
|
from django.conf import settings
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -47,7 +39,7 @@ class UserSettings(models.Model):
|
||||||
auth_user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
|
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_results = models.SmallIntegerField("Default Results", default=10, choices=RESULT_OPTIONS)
|
||||||
default_theme = models.CharField("Theme", max_length=2, default='DE', choices=THEME_OPTIONS)
|
default_theme = models.CharField("Theme", max_length=2, default='DE', choices=THEME_OPTIONS)
|
||||||
default_timezone = models.CharField("Timezone", max_length=50, default='UTC', choices=TIMEZONES)
|
default_timezone = models.CharField("Timezone", max_length=20, default='UTC', choices=TIMEZONES)
|
||||||
search_player_ip = models.BooleanField("Include IP in Player search", default=False)
|
search_player_ip = models.BooleanField("Include IP in Player search", default=False)
|
||||||
show_timestamp_chat = models.BooleanField("Show Timestamp By Chat", 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)
|
last_ip = models.CharField(max_length=30, default="127.0.0.1", editable=False)
|
||||||
|
@ -66,12 +58,12 @@ class UserSettings(models.Model):
|
||||||
class Application(models.Model):
|
class Application(models.Model):
|
||||||
username = models.CharField("Minecraft Username", max_length=20, unique=True)
|
username = models.CharField("Minecraft Username", max_length=20, unique=True)
|
||||||
age = models.PositiveSmallIntegerField()
|
age = models.PositiveSmallIntegerField()
|
||||||
player_type = models.TextField("What's your favorite thing to do in Minecraft?", max_length=300)
|
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 = 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)
|
ever_banned_explanation = models.TextField("If you were previously banned, will you share why?", max_length=300, blank=True, null=True)
|
||||||
reference = models.CharField("How did you find out about us? Please give a website or player name, if applicable.", max_length=50, 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)
|
read_rules = models.CharField("Have you read the rules?", max_length=10)
|
||||||
accepted = models.BooleanField(null=True)
|
accepted = models.NullBooleanField()
|
||||||
date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
|
date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
|
||||||
objects = models.Manager()
|
objects = models.Manager()
|
||||||
|
|
||||||
|
@ -105,7 +97,7 @@ class Player(models.Model):
|
||||||
application = models.ForeignKey(Application, on_delete=models.SET_NULL, null=True, blank=True)
|
application = models.ForeignKey(Application, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
first_seen = models.DateField(null=True, blank=True)
|
first_seen = models.DateField(null=True, blank=True)
|
||||||
last_seen = models.DateField(null=True, blank=True)
|
last_seen = models.DateField(null=True, blank=True)
|
||||||
discord_id = models.CharField(max_length=30, unique=True, null=True, blank=True)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_banned(self):
|
def is_banned(self):
|
||||||
|
@ -260,10 +252,6 @@ class Ticket(models.Model):
|
||||||
def date_display(self):
|
def date_display(self):
|
||||||
return str(self.date).split(".")[0]
|
return str(self.date).split(".")[0]
|
||||||
|
|
||||||
@property
|
|
||||||
def notes(self):
|
|
||||||
return TicketNote.objects.filter(ticket=self)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}: {}".format(self.issuer, self.snippet)
|
return "{}: {}".format(self.issuer, self.snippet)
|
||||||
|
|
||||||
|
@ -275,10 +263,6 @@ class TicketNote(models.Model):
|
||||||
last_update = models.DateTimeField(auto_now_add=True, blank=True, null=True)
|
last_update = models.DateTimeField(auto_now_add=True, blank=True, null=True)
|
||||||
date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
|
date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
|
||||||
|
|
||||||
@property
|
|
||||||
def attachments(self):
|
|
||||||
return Attachment.objects.filter(ref_model=RefModels.TICKET_NOTE[0], ref_id=self.id)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Ticket Note"
|
verbose_name = "Ticket Note"
|
||||||
verbose_name_plural = "Ticket Notes"
|
verbose_name_plural = "Ticket Notes"
|
||||||
|
@ -340,54 +324,10 @@ class Note(models.Model):
|
||||||
def date_display(self):
|
def date_display(self):
|
||||||
return str(self.date).split(".")[0]
|
return str(self.date).split(".")[0]
|
||||||
|
|
||||||
@property
|
|
||||||
def attachments(self):
|
|
||||||
return Attachment.objects.filter(ref_model=RefModels.NOTE[0], ref_id=self.id)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}: {}".format(self.issuee, self.snippet)
|
return "{}: {}".format(self.issuee, self.snippet)
|
||||||
|
|
||||||
|
|
||||||
class RefModels:
|
|
||||||
TICKET_NOTE = 'T', 'Ticket Note'
|
|
||||||
NOTE = 'N', 'Note'
|
|
||||||
choices = TICKET_NOTE, NOTE
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def label(ref: str):
|
|
||||||
for c in RefModels.choices:
|
|
||||||
if c[0] == ref:
|
|
||||||
return c[1]
|
|
||||||
return "None"
|
|
||||||
|
|
||||||
|
|
||||||
def _attachment_upload_to(instance, filename):
|
|
||||||
return f"attachments/{instance.ref_name.lower().replace(' ', '_')}s/{instance.ref_id}/{filename}"
|
|
||||||
|
|
||||||
|
|
||||||
class Attachment(models.Model):
|
|
||||||
ref_model = models.CharField(max_length=1, choices=RefModels.choices)
|
|
||||||
ref_id = models.IntegerField()
|
|
||||||
file = models.FileField(upload_to=_attachment_upload_to)
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ref_name(self):
|
|
||||||
return RefModels.label(self.ref_model)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def file_name(self):
|
|
||||||
return basename(self.file.name)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.file.name
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=Attachment, dispatch_uid="delete_attachments")
|
|
||||||
def attachment_delete(sender, instance, **kwargs):
|
|
||||||
instance.file.delete(False)
|
|
||||||
|
|
||||||
|
|
||||||
class IPManager(models.Manager):
|
class IPManager(models.Manager):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
users = User.objects.filter(is_active=True)
|
users = User.objects.filter(is_active=True)
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
@import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic");
|
@import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic");
|
||||||
/* Custom CSS */
|
|
||||||
.nav-sidebar {
|
|
||||||
border-right: 1px solid #464545;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* bootswatch v3.3.7
|
* bootswatch v3.3.7
|
||||||
* Homepage: http://bootswatch.com
|
* Homepage: http://bootswatch.com
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
@import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic");
|
@import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic");
|
||||||
/* Custom CSS */
|
|
||||||
.nav-sidebar {
|
|
||||||
border-right: 1px solid #ecf0f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* bootswatch v3.3.7
|
* bootswatch v3.3.7
|
||||||
* Homepage: http://bootswatch.com
|
* Homepage: http://bootswatch.com
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
@import url("https://fonts.googleapis.com/css?family=Roboto:400,700");
|
|
||||||
/* Custom CSS */
|
|
||||||
.nav-sidebar {
|
|
||||||
border-right: 1px solid #1c1e22;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* bootswatch v3.3.7
|
* bootswatch v3.3.7
|
||||||
* Homepage: http://bootswatch.com
|
* Homepage: http://bootswatch.com
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
@import url("https://fonts.googleapis.com/css?family=Roboto:400,700");
|
@import url("https://fonts.googleapis.com/css?family=Roboto:400,700");
|
||||||
/* Custom CSS */
|
|
||||||
.nav-sidebar {
|
|
||||||
border-right: 1px solid #282828;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* bootswatch v3.3.7
|
* bootswatch v3.3.7
|
||||||
* Homepage: http://bootswatch.com
|
* Homepage: http://bootswatch.com
|
||||||
|
|
|
@ -3,19 +3,22 @@
|
||||||
min-height:100%;
|
min-height:100%;
|
||||||
height:auto!important;
|
height:auto!important;
|
||||||
position:fixed;
|
position:fixed;
|
||||||
top:0;
|
top:0px;
|
||||||
left:0;
|
left:0px;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
border:0;
|
border:0px;
|
||||||
z-index:-9;
|
z-index:-9;
|
||||||
float:left;
|
float:left;
|
||||||
}
|
}
|
||||||
|
|
||||||
#form {
|
#form {
|
||||||
background: rgba(0, 0, 0, .75);
|
background: rgba(255, 255, 255, .9);
|
||||||
|
margin: auto;
|
||||||
display: block;
|
display: block;
|
||||||
width: 75%;
|
width: 35%;
|
||||||
padding: 1em 5em;
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
padding-left: 5em;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
@ -62,15 +65,6 @@
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rule {
|
.rule {
|
||||||
margin-bottom: .5em;
|
margin-bottom: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.errorlist {
|
|
||||||
color: #D8000C;
|
|
||||||
background-color: #FFD2D2;
|
|
||||||
}
|
|
|
@ -1,249 +0,0 @@
|
||||||
/* Sakura.css v1.3.1
|
|
||||||
* ================
|
|
||||||
* Minimal css theme.
|
|
||||||
* Project: https://github.com/oxalorg/sakura/
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Default Sakura Theme */
|
|
||||||
:root {
|
|
||||||
--color-blossom: #1d7484;
|
|
||||||
--color-fade: #982c61;
|
|
||||||
--color-bg: #f9f9f9;
|
|
||||||
--color-bg-alt: #f1f1f1;
|
|
||||||
--color-text: #4a4a4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sakura Dark Theme */
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--color-blossom: #ffffff;
|
|
||||||
--color-fade: #c9c9c9;
|
|
||||||
--color-bg: #222222;
|
|
||||||
--color-bg-alt: #4a4a4a;
|
|
||||||
--color-text: #c9c9c9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 62.5%;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
line-height: 1.618;
|
|
||||||
max-width: 38em;
|
|
||||||
margin: auto;
|
|
||||||
color: var(--color-text);
|
|
||||||
background-color: var(--color-bg);
|
|
||||||
padding: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 684px) {
|
|
||||||
body {
|
|
||||||
font-size: 1.53rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 382px) {
|
|
||||||
body {
|
|
||||||
font-size: 1.35rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
line-height: 1.1;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-top: 3rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
word-wrap: break-word;
|
|
||||||
-ms-word-break: break-all;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2.35em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 2.00em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: 1.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6 {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
small, sub, sup {
|
|
||||||
font-size: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border-color: var(--color-blossom);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--color-blossom);
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--color-fade);
|
|
||||||
border-bottom: 2px solid var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited {
|
|
||||||
color: var(--color-blossom);
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding-left: 1.4em;
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
margin-left: 0px;
|
|
||||||
margin-right: 0px;
|
|
||||||
padding-left: 1em;
|
|
||||||
padding-top: 0.8em;
|
|
||||||
padding-bottom: 0.8em;
|
|
||||||
padding-right: 0.8em;
|
|
||||||
border-left: 5px solid var(--color-blossom);
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
background-color: var(--color-bg-alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
img, video {
|
|
||||||
height: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pre and Code */
|
|
||||||
pre {
|
|
||||||
background-color: var(--color-bg-alt);
|
|
||||||
display: block;
|
|
||||||
padding: 1em;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-size: 0.9em;
|
|
||||||
padding: 0 0.5em;
|
|
||||||
background-color: var(--color-bg-alt);
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre > code {
|
|
||||||
padding: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tables */
|
|
||||||
table {
|
|
||||||
text-align: justify;
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
td, th {
|
|
||||||
padding: 0.5em;
|
|
||||||
border-bottom: 1px solid var(--color-bg-alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons, forms and input */
|
|
||||||
input, textarea {
|
|
||||||
border: 1px solid var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus, textarea:focus {
|
|
||||||
border: 1px solid var(--color-blossom);
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button, button, input[type="submit"], input[type="reset"], input[type="button"] {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 5px 10px;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
background-color: var(--color-blossom);
|
|
||||||
color: var(--color-bg);
|
|
||||||
border-radius: 1px;
|
|
||||||
border: 1px solid var(--color-blossom);
|
|
||||||
cursor: pointer;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button[disabled], button[disabled], input[type="submit"][disabled], input[type="reset"][disabled], input[type="button"][disabled] {
|
|
||||||
cursor: default;
|
|
||||||
opacity: .5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:focus:enabled, .button:hover:enabled, button:focus:enabled, button:hover:enabled, input[type="submit"]:focus:enabled, input[type="submit"]:hover:enabled, input[type="reset"]:focus:enabled, input[type="reset"]:hover:enabled, input[type="button"]:focus:enabled, input[type="button"]:hover:enabled {
|
|
||||||
background-color: var(--color-fade);
|
|
||||||
border-color: var(--color-fade);
|
|
||||||
color: var(--color-bg);
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea, select, input {
|
|
||||||
color: var(--color-text);
|
|
||||||
padding: 6px 10px;
|
|
||||||
/* The 6px vertically centers text on FF, ignored by Webkit */
|
|
||||||
margin-bottom: 10px;
|
|
||||||
background-color: var(--color-bg-alt);
|
|
||||||
border: 1px solid var(--color-bg-alt);
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea:focus, select:focus, input:focus {
|
|
||||||
border: 1px solid var(--color-blossom);
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"]:focus {
|
|
||||||
outline: 1px dotted var(--color-blossom);
|
|
||||||
}
|
|
||||||
|
|
||||||
label, legend, fieldset {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% block title %}Alerts{% endblock %}
|
{% block title %}Alerts{% endblock %}
|
||||||
{% block section %}
|
{% block section %}
|
||||||
<div id="content" hidden="hidden">
|
<div id="content" hidden="hidden">
|
||||||
<table id="model-table" class="table table-striped table-hover link-table">
|
<table id="model-table" class="table table-hover link-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Message</th>
|
<th>Message</th>
|
||||||
|
@ -14,15 +14,9 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for alert in alerts %}
|
{% for alert in alerts %}
|
||||||
<tr data-id="{{ alert.id }}">
|
<tr {% if alert.seen is True %}class="success"{% endif %} data-id="{{ alert.id }}">
|
||||||
<td>{{ alert.snippet }}</td>
|
<td>{{ alert.snippet }}</td>
|
||||||
<td>
|
<td>{{ alert.seen }}</td>
|
||||||
{% if alert.seen %}
|
|
||||||
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
|
|
||||||
{% else %}
|
|
||||||
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ alert.date }}</td>
|
<td>{{ alert.date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{% block title %}Applications{% endblock %}
|
{% block title %}Applications{% endblock %}
|
||||||
{% block section %}
|
{% block section %}
|
||||||
<div id="content" hidden="hidden">
|
<div id="content" hidden="hidden">
|
||||||
<table id="model-table" class="table table-striped table-hover link-table">
|
<table id="model-table" class="table table-hover link-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>App ID</th>
|
<th>App ID</th>
|
||||||
|
@ -16,25 +16,18 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for app in applications %}
|
{% for app in applications %}
|
||||||
<tr data-id="{{ app.id }}">
|
<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.id }}</td>
|
||||||
<td>{{ app.username }}</td>
|
<td>{{ app.username }}</td>
|
||||||
<td>{{ app.age }}</td>
|
<td>{{ app.age }}</td>
|
||||||
<td>{{ app.ever_banned }}</td>
|
<td>{{ app.ever_banned }}</td>
|
||||||
<td>
|
<td>{{ app.status }}</td>
|
||||||
{{ app.status }}
|
|
||||||
{% if app.accepted is True %}
|
|
||||||
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
|
|
||||||
{% elif app.accepted is False %}
|
|
||||||
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ app.date }}</td>
|
<td>{{ app.date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<a class="btn btn-primary" href="{% url 'reference' %}">Reference Report</a>
|
<a href="{% url 'reference' %}">Reference Report</a>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<!-- <p>Username: {{ application.username }}</p> -->
|
<!-- <p>Username: {{ application.username }}</p> -->
|
||||||
<p>Age: {{ application.age }}</p>
|
<p>Age: {{ application.age }}</p>
|
||||||
<p>Application Date: {{ application.date }}</p>
|
<p>Application Date: {{ application.date }}</p>
|
||||||
<p>Favorite Activity:</p>
|
<p>Type Of Player:</p>
|
||||||
<p class="well">{{ application.player_type }}</p>
|
<p class="well">{{ application.player_type }}</p>
|
||||||
{% if application.accepted is not null %}
|
{% if application.accepted is not null %}
|
||||||
<p>Status: {{ application.status }}</p>
|
<p>Status: {{ application.status }}</p>
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
<p>Explanation: {{ application.ever_banned_explanation }}</p>
|
<p>Explanation: {{ application.ever_banned_explanation }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if application.reference %}
|
{% if application.reference %}
|
||||||
<p>Referral: {{ application.reference }}</p>
|
<p>Reference: {{ application.reference }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>Read The Rules: {{ application.read_rules }}</p>
|
<p>Read The Rules: {{ application.read_rules }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{% block title %}Bans{% endblock %}
|
{% block title %}Bans{% endblock %}
|
||||||
{% block section %}
|
{% block section %}
|
||||||
<div id="content" hidden="hidden">
|
<div id="content" hidden="hidden">
|
||||||
<table id="model-table" class="table table-striped table-hover link-table">
|
<table id="model-table" class="table table-hover link-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Player</th>
|
<th>Player</th>
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends "minecraft_manager/dashboard.html" %}
|
||||||
|
{% load csrf_html %}
|
||||||
|
{% block title %}Bots{% endblock %}
|
||||||
|
{% block section %}
|
||||||
|
<div id="content">
|
||||||
|
{% for bot in bots %}
|
||||||
|
<p>{{ bot.name }}: <span class="label {% if bot.status is True %}label-success{% else %}label-danger{% endif %}">{{ bot.display }}</span></p>
|
||||||
|
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
||||||
|
<button class="btn btn-primary{% if bot.status is True %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="start" {% if bot.status is True %} disabled="disabled"{% endif %}>Start</button>
|
||||||
|
<button class="btn btn-primary{% if bot.status is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="stop" {% if bot.status is False %}disabled="disabled"{% endif %}>Stop</button>
|
||||||
|
<button class="btn btn-primary{% if bot.status is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="restart" {% if bot.status is False %}disabled="disabled"{% endif %}>Restart</button>
|
||||||
|
</form>
|
||||||
|
<br/>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock section %}
|
|
@ -28,9 +28,6 @@
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a class="dropdown-toggle " href="#" role="button" data-toggle="dropdown" id="accountDropdown">{{ user.username }}</a>
|
<a class="dropdown-toggle " href="#" role="button" data-toggle="dropdown" id="accountDropdown">{{ user.username }}</a>
|
||||||
<ul class="dropdown-menu" role="menu" aria-labelledby="accountDropdown">
|
<ul class="dropdown-menu" role="menu" aria-labelledby="accountDropdown">
|
||||||
{% if user.is_staff %}
|
|
||||||
<li><a target="_blank" href="{% url "admin:index" %}">Admin Site</a></li>
|
|
||||||
{% endif %}
|
|
||||||
<li><a style="cursor:pointer;" data-toggle="modal" data-target="#settingsModal">Settings</a></li>
|
<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 style="cursor:pointer;" data-toggle="modal" data-target="#passwordModal">Change Password</a></li>
|
||||||
<li><a href="{% url "logout" %}">Logout</a></li>
|
<li><a href="{% url "logout" %}">Logout</a></li>
|
||||||
|
@ -72,6 +69,9 @@
|
||||||
<form id="settingsForm" method="POST" action="{% url 'api-web' keyword='settings' %}">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
<form id="settingsForm" method="POST" action="{% url 'api-web' keyword='settings' %}">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
{% get_form 'usersettings' request %}
|
{% get_form 'usersettings' request %}
|
||||||
|
{% if user.is_staff %}
|
||||||
|
<br/><a target="_blank" href="{% url "admin:index" %}">Admin Site</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
{% extends 'minecraft_manager/external/base.html' %}
|
{% extends 'minecraft_manager/external/base.html' %}
|
||||||
{% load str_utils %}
|
|
||||||
|
|
||||||
{% block title %}Application Form{% endblock %}
|
{% block title %}Application Form{% endblock %}
|
||||||
|
|
||||||
|
@ -8,7 +7,7 @@
|
||||||
{% block form_top %}
|
{% block form_top %}
|
||||||
<h3>Rules</h3>
|
<h3>Rules</h3>
|
||||||
{% for rule in rules %}
|
{% for rule in rules %}
|
||||||
<div class="{% if rule|has_prefix:'*' %}sub {% endif %}rule">{{ rule }}</div>
|
<div class="rule">{{ rule }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@
|
||||||
<br/>
|
<br/>
|
||||||
We will get back to you soon.
|
We will get back to you soon.
|
||||||
<br/>
|
<br/>
|
||||||
Consider joining our <a href="{{ map.invite_link }}">{{ map.invite_label }}</a></h2>
|
Consider joining our <a href="https://discord.gg/{{ map.discord_invite }}">Discord</a></h2>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<link rel="stylesheet" href="{% static 'minecraft_manager/css/external.css' %}">
|
<link rel="stylesheet" href="{% static 'minecraft_manager/css/external.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'minecraft_manager/css/sakura.css' %}">
|
|
||||||
<link rel="stylesheet" href="//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
|
<link rel="stylesheet" href="//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -22,7 +21,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<form method="{% block method %}POST{% endblock %}">
|
<form method="{% block method %}POST{% endblock %}">
|
||||||
{% block form %}{{ form }}{% endblock %}<br/><br/>
|
{% block form %}{{ form }}{% endblock %}<br/><br/>
|
||||||
{% if captcha %}<div class="g-recaptcha" data-sitekey="{{ captcha }}"></div>{% endif %}
|
{% if captcha %}<div class="g-recaptcha" data-sitekey="6LeLcGEUAAAAAMMpHR-7VL6Q3nNV5v03S5sq1Ddz"></div>{% endif %}
|
||||||
<button type="submit">{% block submit %}Submit{% endblock %}</button>
|
<button type="submit">{% block submit %}Submit{% endblock %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h1>Logged out</h1>
|
<h1>Logged out</h1>
|
||||||
<p>You have logged out.</p><p>
|
<p>You have logged out 24CarrotCraft.</p><p>
|
||||||
</p><p><a href="{% url 'login' %}">Log back in</a>. </p>
|
</p><p><a href="{% url 'login' %}">Log back in</a>. </p>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,47 +0,0 @@
|
||||||
<div id="addAttachmentModal" 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">×</button>
|
|
||||||
<h4 class="modal-title">Add Attachment(s)</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" id="add-attachment-form" enctype="multipart/form-data">{% csrf_token %}
|
|
||||||
<input type="file" multiple name="attachments"/>
|
|
||||||
<input type="hidden" name="next" value="{% url 'note_info' note.id %}"/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
||||||
<button type="button" class="btn btn-primary" id="add-attachment-save">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(document).ready(() => {
|
|
||||||
const $modal = $('#addAttachmentModal');
|
|
||||||
const addAttachmentSave = document.querySelector('#add-attachment-save');
|
|
||||||
const addAttachmentForm = document.querySelector('#add-attachment-form');
|
|
||||||
document.querySelectorAll('.add-attachment[data-model][data-ref]').forEach((elem) => {
|
|
||||||
elem.addEventListener('click', () => {
|
|
||||||
addAttachmentModal(elem.dataset.model, elem.dataset.ref);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
$modal.on('hidden.bs.modal', () => {
|
|
||||||
addAttachmentForm.reset();
|
|
||||||
});
|
|
||||||
addAttachmentSave.addEventListener('click', () => {
|
|
||||||
addAttachmentForm.submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
function addAttachmentModal(model, ref) {
|
|
||||||
addAttachmentForm.action = '{% url 'attachment_add' 'X' 0 %}'
|
|
||||||
.replace('X', model)
|
|
||||||
.replace('0', ref);
|
|
||||||
$modal.modal('show');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,48 +0,0 @@
|
||||||
<div id="deleteAttachmentModal" 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">×</button>
|
|
||||||
<h4 class="modal-title">Delete Attachment</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Are you sure you want to delete <code id="delete-attachment-name"></code>?</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
||||||
<button type="button" class="btn btn-primary" id="delete-attachment-confirm">Confirm</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$(document).ready(() => {
|
|
||||||
const $modal = $('#deleteAttachmentModal');
|
|
||||||
const deleteAttachmentName = document.querySelector('#delete-attachment-name');
|
|
||||||
const deleteAttachmentConfirm = document.querySelector('#delete-attachment-confirm');
|
|
||||||
document.querySelectorAll('.delete-attachment[data-name][data-id]').forEach((elem) => {
|
|
||||||
elem.addEventListener('click', () => {
|
|
||||||
deleteAttachmentModal(elem.dataset.name, elem.dataset.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
deleteAttachmentConfirm.addEventListener('click', () => {
|
|
||||||
const attachmentURL = '{% url 'attachment' 0 %}'.replace('0', deleteAttachmentConfirm.dataset.id);
|
|
||||||
fetch(attachmentURL, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'X-CSRFToken': '{{ csrf_token }}'
|
|
||||||
}
|
|
||||||
}).then(() => {
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function deleteAttachmentModal(name, id) {
|
|
||||||
deleteAttachmentName.innerHTML = name;
|
|
||||||
deleteAttachmentConfirm.dataset.id = id;
|
|
||||||
$modal.modal('show');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -2,34 +2,24 @@
|
||||||
{% block title %}Notes{% endblock %}
|
{% block title %}Notes{% endblock %}
|
||||||
{% block section %}
|
{% block section %}
|
||||||
<div id="content" hidden="hidden">
|
<div id="content" hidden="hidden">
|
||||||
<table id="model-table" class="table table-striped table-hover link-table">
|
<table id="model-table" class="table table-hover link-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Player</th>
|
<th>Player</th>
|
||||||
<th>Message</th>
|
<th>Message</th>
|
||||||
<th>Importance</th>
|
<th>Importance</th>
|
||||||
<td>Issued By</td>
|
<td>Issued By</td>
|
||||||
<td>Attachments</td>
|
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for note in notes %}
|
{% for note in notes %}
|
||||||
<tr data-id="{{ note.id }}">
|
<tr {% if note.importance == 'L' %}class="info"{% endif %}{% if note.importance == 'M' %}class="warning"{% endif %}{% if note.importance == 'H' %}class="danger"{% endif %} data-id="{{ note.id }}">
|
||||||
<!-- <td>{{ note.id }}</td> -->
|
<!-- <td>{{ note.id }}</td> -->
|
||||||
<td>{{ note.issuee }}</td>
|
<td>{{ note.issuee }}</td>
|
||||||
<td>{{ note.snippet }}</td>
|
<td>{{ note.snippet }}</td>
|
||||||
<td>
|
<td>{{ note.importance_display }}</td>
|
||||||
<span class="label label-{% if note.importance == 'L' %}info{% elif note.importance == 'M' %}warning{% elif note.importance == 'H' %}danger{% endif %}">
|
|
||||||
{{ note.importance_display }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ note.issuer }}</td>
|
<td>{{ note.issuer }}</td>
|
||||||
<td>
|
|
||||||
{% for attachment in note.attachments %}
|
|
||||||
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a><br/>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>{{ note.date }}</td>
|
<td>{{ note.date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -6,17 +6,21 @@
|
||||||
<h2 class="sub-header">New Note</h2>
|
<h2 class="sub-header">New Note</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-18 col-md-12">
|
<div class="col-xs-18 col-md-12">
|
||||||
<form action="" method="post" enctype="multipart/form-data">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
||||||
<p><label for="id_filter">Player Filter:</label> <input id="id_filter" type="text"/></p>
|
<p><label for="">Player Filter:</label> <input id="id_filter" type="text"/></p>
|
||||||
{{ form }}
|
{{ form }}
|
||||||
<p>
|
|
||||||
<label for="attachments">Attachments:</label>
|
|
||||||
<input id="attachments" name="attachments" type="file" multiple/>
|
|
||||||
</p>
|
|
||||||
<button id="saveButton" class="btn btn-primary" type="submit">Save</button>
|
<button id="saveButton" class="btn btn-primary" type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<div class="col-xs-12 col-md-8">
|
<div class="col-xs-12 col-md-8">
|
||||||
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
||||||
<p>Issuer: {{ note.staff.username }} </p>
|
<p>Issuer: {{ note.staff.username }} </p>
|
||||||
<p><label for="noteImportance">Importance:</label>
|
<p>Importance:
|
||||||
{% if user.is_staff or user == note.staff %}
|
{% if user.is_staff or user == note.staff %}
|
||||||
<select id="noteImportance" name="importance">
|
<select id="noteImportance" name="importance">
|
||||||
{% for p in form.importance %}
|
{% for p in form.importance %}
|
||||||
|
@ -32,44 +32,23 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if note.attachments.all|length > 0 %}
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-9 col-md-6">
|
<div class="col-xs-6 col-md-4">
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr><th>Attachments</th></tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for attachment in note.attachments %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a>
|
|
||||||
<span class="delete-attachment" data-name="{{ attachment.file_name }}" data-id="{{ attachment.id }}">
|
|
||||||
<i class="glyphicon glyphicon-remove-circle text-danger"></i>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<br/>
|
|
||||||
<button type="button" class="btn btn-sm btn-primary add-attachment" data-model="N" data-ref="{{ note.id }}">Add Attachment(s)</button>
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-8">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$("#saveButton").hide();
|
$("#saveButton").hide();
|
||||||
$("#noteImportance").change(function() {
|
$("#noteImportance").change(function() {
|
||||||
if (("{{ user.username }}" === "{{ note.staff.username }}" || "{{ user.is_staff }}" === "True") && $(this).val() !== "{{ note.importance }}") {
|
if (("{{ user.username }}" == "{{ note.staff.username }}" || "{{ user.is_staff }}" == "True") && $(this).val() != "{{ note.importance }}") {
|
||||||
$("#saveButton").show();
|
$("#saveButton").show();
|
||||||
} else {
|
} else {
|
||||||
$("#saveButton").hide();
|
$("#saveButton").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% include 'minecraft_manager/modal/add_attachment.html' %}
|
|
||||||
{% include 'minecraft_manager/modal/delete_attachment.html' %}
|
|
||||||
{% endblock section %}
|
{% endblock section %}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{% block title %}Players{% endblock %}
|
{% block title %}Players{% endblock %}
|
||||||
{% block section %}
|
{% block section %}
|
||||||
<div id="content" hidden="hidden">
|
<div id="content" hidden="hidden">
|
||||||
<table id="model-table" class="table table-striped table-hover link-table">
|
<table id="model-table" class="table table-hover link-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Username</th>
|
<th>Username</th>
|
||||||
|
@ -13,18 +13,10 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for player in players %}
|
{% for player in players %}
|
||||||
<tr data-id="{{ player.id }}">
|
<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.username }}{% if user.usersettings.search_player_ip is True %}<span hidden="hidden">{{ player.ips }}</span>{% endif %}</td>
|
||||||
<td>{{ player.uuid }}</td>
|
<td>{{ player.uuid }}</td>
|
||||||
<td>
|
<td>{% if player.uuid in bans %}True {% else %}False{% endif %}</td>
|
||||||
{% if player.uuid in bans %}
|
|
||||||
Yes
|
|
||||||
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
|
|
||||||
{% else %}
|
|
||||||
No
|
|
||||||
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ player.last_seen }}</td>
|
<td>{{ player.last_seen }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
<h2 class="sub-header">Player Info ({{ player.username }})</h2>
|
<h2 class="sub-header">Player Info ({{ player.username }})</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-9 col-md-6">
|
<div class="col-xs-9 col-md-6">
|
||||||
<p>UUID: <code>{{ player.uuid }}</code> <button data-copy="{{ player.uuid }}" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-copy"></i></button></p>
|
<p>UUID: {{ player.uuid }}</p>
|
||||||
<p>Discord ID: {% if player.discord_id %}<code>{{ player.discord_id }}</code> <button data-copy="{{ player.discord_id }}" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-copy"></i></button>{% else %}N/A{% endif %}</p>
|
|
||||||
{% if player.auth_user %}
|
{% if player.auth_user %}
|
||||||
<p>Connected User: {{ player.auth_user.username }}</p>
|
<p>Connected User: {{ player.auth_user.username }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if player.application %}
|
{% if player.application %}
|
||||||
<p>Application: <a href="{% url "application" %}{{ player.application.id }}">{{ player.application.username }}</a></p>
|
<p>Application: <a href="{% url "application" %}{{ player.application.id }}">Here</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>Donor Status: {{ player.donor_status }}</p>
|
<p>Donor Status: {{ player.donor_status }}</p>
|
||||||
<p>First Seen: {{ player.first_seen }}</p>
|
<p>First Seen: {{ player.first_seen }}</p>
|
||||||
|
@ -21,35 +20,14 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-9 col-md-6">
|
<div class="col-xs-9 col-md-6">
|
||||||
<h4>Tickets</h4>
|
<h4>Tickets</h4>
|
||||||
<table class="table table-striped table-hover link-table">
|
<table class="table table-hover link-table">
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>ID</td>
|
|
||||||
<td>Message</td>
|
|
||||||
<td>Resolved</td>
|
|
||||||
<td>Attachments</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% if form.tickets %}
|
{% if form.tickets %}
|
||||||
{% for ticket in form.tickets %}
|
{% for ticket in form.tickets %}
|
||||||
<tr data-id="{{ ticket.id }}" data-url="{% url "ticket" %}">
|
<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.id }}</td>
|
||||||
<td>{{ ticket.snippet }}</td>
|
<td>{{ ticket.snippet }}</td>
|
||||||
<td>
|
<td>{{ ticket.resolved }}</td>
|
||||||
{% if ticket.resolved is True %}
|
|
||||||
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
|
|
||||||
{% else %}
|
|
||||||
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% for note in ticket.notes %}
|
|
||||||
{% for attachment in note.attachments %}
|
|
||||||
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a> ({{ note.author.username }})<br/>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -58,33 +36,15 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<br/>
|
<br/>
|
||||||
<h4>Notes</h4>
|
<table class="table table-hover link-table">
|
||||||
<table class="table table-striped table-hover link-table">
|
<h4>Notes</h4>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>Message</td>
|
|
||||||
<td>Importance</td>
|
|
||||||
<td>Attachments</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% if form.notes %}
|
{% if form.notes %}
|
||||||
{% for note in form.notes %}
|
{% for note in form.notes %}
|
||||||
<tr data-id="{{ note.id }}" data-url="{% url "note" %}">
|
<tr {% if note.importance == 'L' %}class="info"{% endif %}{% if note.importance == 'M' %}class="warning"{% endif %}{% if note.importance == 'H' %}class="danger"{% endif %} data-id="{{ note.id }}" data-url="{% url "note" %}">
|
||||||
<!-- {{ note.id }} -->
|
<!-- {{ note.id }} -->
|
||||||
<td>{{ note.snippet }}</td>
|
<td>{{ note.snippet }}</td>
|
||||||
<td>
|
<td>{{ note.importance_display }}</td>
|
||||||
<span class="label label-{% if note.importance == 'L' %}info{% elif note.importance == 'M' %}warning{% elif note.importance == 'H' %}danger{% endif %}">
|
|
||||||
{{ note.importance_display }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% for attachment in note.attachments %}
|
|
||||||
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a>
|
|
||||||
{% if note.staff %}({{ note.staff.username }}){% endif %}
|
|
||||||
<br/>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -94,22 +54,14 @@
|
||||||
</table>
|
</table>
|
||||||
<a class="btn btn-primary" href="{% url 'note_add' %}?player={{ player.id }}">Add Note</a>
|
<a class="btn btn-primary" href="{% url 'note_add' %}?player={{ player.id }}">Add Note</a>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<h4>IPs</h4>
|
|
||||||
<table class="table table-striped table-hover link-table">
|
<table class="table table-striped table-hover link-table">
|
||||||
<thead>
|
<h4>IPs</h4>
|
||||||
<tr>
|
|
||||||
<th>IP</th>
|
|
||||||
<th>Last Used</th>
|
|
||||||
<th>Associated Users</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% if form.ips %}
|
{% if form.ips %}
|
||||||
{% for ip in form.ips %}
|
{% for ip in form.ips %}
|
||||||
<tr class="default" data-id="" data-url="{% url 'ip' ip.id %}">
|
<tr class="default" data-id="" data-url="{% url 'ip' ip.id %}">
|
||||||
<!-- {{ ip.id }} -->
|
<!-- {{ ip.id }} -->
|
||||||
<td>{{ ip.ip }}</td>
|
<td>{{ ip.ip }} ({{ ip.last_used_formatted }})</td>
|
||||||
<td>{{ ip.last_used_formatted }}</td>
|
|
||||||
{% if ip.associated %}
|
{% if ip.associated %}
|
||||||
<td>
|
<td>
|
||||||
{% for assoc in ip.associated %}
|
{% for assoc in ip.associated %}
|
||||||
|
@ -129,21 +81,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
document.querySelectorAll('[data-copy]').forEach((elem) => {
|
|
||||||
elem.addEventListener('click', () => {
|
|
||||||
const text = document.createElement('textarea');
|
|
||||||
text.value = elem.dataset.copy;
|
|
||||||
text.style.top = "0";
|
|
||||||
text.style.left = "0";
|
|
||||||
text.style.position = "fixed";
|
|
||||||
document.body.appendChild(text);
|
|
||||||
text.focus();
|
|
||||||
text.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
text.remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock section %}
|
{% endblock section %}
|
||||||
|
|
|
@ -2,14 +2,13 @@
|
||||||
{% block title %}Tickets{% endblock %}
|
{% block title %}Tickets{% endblock %}
|
||||||
{% block section %}
|
{% block section %}
|
||||||
<div id="content" hidden="hidden">
|
<div id="content" hidden="hidden">
|
||||||
<table id="model-table" class="table table-striped table-hover link-table">
|
<table id="model-table" class="table table-hover link-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>Player</th>
|
<th>Player</th>
|
||||||
<th>Message</th>
|
<th>Message</th>
|
||||||
<th>Priority</th>
|
<th>Priority</th>
|
||||||
<th>Attachments</th>
|
|
||||||
<th>Claimed</th>
|
<th>Claimed</th>
|
||||||
<th>Resolved</th>
|
<th>Resolved</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
|
@ -17,30 +16,13 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for ticket in tickets %}
|
{% for ticket in tickets %}
|
||||||
<tr data-id="{{ ticket.id }}">
|
<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.id }}</td>
|
||||||
<td>{{ ticket.issuer }}</td>
|
<td>{{ ticket.issuer }}</td>
|
||||||
<td>{{ ticket.snippet }}</td>
|
<td>{{ ticket.snippet }}</td>
|
||||||
<td>
|
<td>{{ ticket.priority_display }}</td>
|
||||||
<span class="label label-{% if ticket.priority == 'L' %}info{% elif ticket.priority == 'M' %}warning{% elif ticket.priority == 'H' %}danger{% endif %}">
|
|
||||||
{{ ticket.priority_display }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% for note in ticket.notes %}
|
|
||||||
{% for attachment in note.attachments %}
|
|
||||||
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a><br/>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>{{ ticket.claimed_by }}</td>
|
<td>{{ ticket.claimed_by }}</td>
|
||||||
<td>
|
<td>{{ ticket.resolved }}</td>
|
||||||
{% if ticket.resolved %}
|
|
||||||
<span><i class="glyphicon glyphicon-ok-circle text-success"></i></span>
|
|
||||||
{% else %}
|
|
||||||
<span><i class="glyphicon glyphicon-remove-circle text-danger"></i></span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ ticket.date }}</td>
|
<td>{{ ticket.date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -74,42 +74,20 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>Created: {{ ticket_note.date }}</p>
|
<p>Created: {{ ticket_note.date }}</p>
|
||||||
<p>Last Update: {{ ticket_note.last_update }}</p>
|
<p>Last Update: {{ ticket_note.last_update }}</p>
|
||||||
{% if ticket_note.attachments|length > 0 %}
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr><th>Attachments</th></tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for attachment in ticket_note.attachments %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'attachment' attachment.id %}">{{ attachment.file_name }}</a>
|
|
||||||
<span class="delete-attachment" data-name="{{ attachment.file_name }}" data-id="{{ attachment.id }}">
|
|
||||||
<i class="glyphicon glyphicon-remove-circle text-danger"></i>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% if not form.has_ticket_note and not form.show_ticket_note %}
|
{% if not form.has_ticket_note and not form.show_ticket_note %}
|
||||||
<div id="createDiv" class="row">
|
<div id="createDiv" class="row">
|
||||||
<button class="btn btn-primary" onClick="showNote();">Create Note</button>
|
<button class="btn btn-primary" onClick="showNote()">Create Note</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="ticket_noteDiv" class="row" {% if not form.show_ticket_note %}style="display: none;"{% endif %}>
|
<div id="ticket_noteDiv" class="row" {% if not form.show_ticket_note %}style="display: none;"{% endif %}>
|
||||||
<br/>
|
<br/>
|
||||||
<h3>Note</h3>
|
<h3>Note</h3>
|
||||||
<form action="" method="POST" enctype="multipart/form-data">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
<form action="" method="POST">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
||||||
{{ form.ticket_note_form }}
|
{{ form.ticket_note_form }}
|
||||||
<p>
|
<br/>
|
||||||
<label for="attachments">Attachments:</label>
|
|
||||||
<input id="attachments" name="attachments" type="file" multiple/>
|
|
||||||
</p>
|
|
||||||
<button type="submit" class="btn btn-primary" name="ticket_note" value="{% if form.has_ticket_note %}edit{% else %}create{% endif %}">Save</button>
|
<button type="submit" class="btn btn-primary" name="ticket_note" value="{% if form.has_ticket_note %}edit{% else %}create{% endif %}">Save</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -119,7 +97,7 @@
|
||||||
$("#saveButton").hide();
|
$("#saveButton").hide();
|
||||||
$("#ticketStaff").change(function() {
|
$("#ticketStaff").change(function() {
|
||||||
var $priority = $("#ticketPriority");
|
var $priority = $("#ticketPriority");
|
||||||
if ($(this).val() !== "{{ ticket.staff.id }}" || $priority.val() !== "{{ ticket.priority }}") {
|
if ($(this).val() != "{{ ticket.staff.id }}" || $priority.val() != "{{ ticket.priority }}") {
|
||||||
toggleSave(true);
|
toggleSave(true);
|
||||||
} else {
|
} else {
|
||||||
toggleSave(false);
|
toggleSave(false);
|
||||||
|
@ -127,7 +105,7 @@
|
||||||
});
|
});
|
||||||
$("#ticketPriority").change(function() {
|
$("#ticketPriority").change(function() {
|
||||||
var $staff = $("#ticketStaff");
|
var $staff = $("#ticketStaff");
|
||||||
if ($(this).val() !== "{{ ticket.priority }}" || $staff.val() !== "{{ ticket.staff.id }}") {
|
if ($(this).val() != "{{ ticket.priority }}" || $staff.val() != "{{ ticket.staff.id }}") {
|
||||||
toggleSave(true);
|
toggleSave(true);
|
||||||
} else {
|
} else {
|
||||||
toggleSave(false);
|
toggleSave(false);
|
||||||
|
@ -135,10 +113,10 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
function toggleSave(toggle) {
|
function toggleSave(toggle) {
|
||||||
if (toggle === true) {
|
if (toggle == true) {
|
||||||
$("#resolveButton").hide();
|
$("#resolveButton").hide();
|
||||||
$("#saveButton").show();
|
$("#saveButton").show();
|
||||||
} else if (toggle === false) {
|
} else if (toggle == false) {
|
||||||
$("#resolveButton").show();
|
$("#resolveButton").show();
|
||||||
$("#saveButton").hide();
|
$("#saveButton").hide();
|
||||||
}
|
}
|
||||||
|
@ -150,5 +128,4 @@
|
||||||
$("#editBtn").hide();
|
$("#editBtn").hide();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% include 'minecraft_manager/modal/delete_attachment.html' %}
|
|
||||||
{% endblock section %}
|
{% endblock section %}
|
||||||
|
|
|
@ -33,5 +33,6 @@ def get_sidebar(current_app, request):
|
||||||
show_chat = True if getattr(settings, 'GLOBAL_LOG', None) is not None else False
|
show_chat = True if getattr(settings, 'GLOBAL_LOG', None) is not None else False
|
||||||
if show_chat and request.user.has_perm('minecraft_manager.chat'):
|
if show_chat and request.user.has_perm('minecraft_manager.chat'):
|
||||||
ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-comment"></span> Chat</a></li>'.format('class="active"' if current_app == 'chat' else '', reverse('chat'))
|
ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-comment"></span> Chat</a></li>'.format('class="active"' if current_app == 'chat' else '', reverse('chat'))
|
||||||
|
if request.user.has_perm('minecraft_manager.bots'):
|
||||||
|
ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-flash"></span> Bots</a></li>'.format('class="active"' if current_app == 'bots' else '', reverse('bots'))
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
from django import template
|
|
||||||
|
|
||||||
register = template.Library()
|
|
||||||
|
|
||||||
|
|
||||||
def has_prefix(value: str, arg: str) -> bool:
|
|
||||||
return value.startswith(arg)
|
|
||||||
|
|
||||||
|
|
||||||
register.filter('has_prefix', has_prefix)
|
|
106
urls.py
106
urls.py
|
@ -1,47 +1,71 @@
|
||||||
from django.urls import path
|
from django.conf.urls import url
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
import minecraft_manager.views as mcm
|
import minecraft_manager.views as mcm
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', RedirectView.as_view(pattern_name='overview')),
|
url(r'^$', RedirectView.as_view(pattern_name='overview')),
|
||||||
|
#Dashboard
|
||||||
# Dashboard
|
url(r'^dashboard/overview/$', login_required(mcm.Overview.as_view()), name="overview"),
|
||||||
path('overview/', login_required(mcm.Overview.as_view()), name="overview"),
|
url(r'^dashboard/ban/$', login_required(mcm.Ban.as_view()), name="ban"),
|
||||||
path('ban/', login_required(mcm.Ban.as_view()), name="ban"),
|
#Alerts
|
||||||
|
url(r'^dashboard/alert/$', login_required(mcm.Alert.as_view()), name="alert"),
|
||||||
# Alerts
|
url(r'^dashboard/alert/(?P<alert_id>[0-9]{1,5})/$', login_required(mcm.AlertInfo.as_view())),
|
||||||
path('alert/', login_required(mcm.Alert.as_view()), name="alert"),
|
#Applications
|
||||||
path('alert/<int:alert_id>/', login_required(mcm.AlertInfo.as_view()), name="alert_info"),
|
url(r'^dashboard/application/$', login_required(mcm.Application.as_view()), name="application"),
|
||||||
|
url(r'^dashboard/reference/$', login_required(mcm.Reference.as_view()), name="reference"),
|
||||||
# Applications
|
url(r'^dashboard/application/(?P<application_id>[0-9]{1,5})/$', login_required(mcm.ApplicationInfo.as_view())),
|
||||||
path('application/', login_required(mcm.Application.as_view()), name="application"),
|
#Players
|
||||||
path('reference/', login_required(mcm.Reference.as_view()), name="reference"),
|
url(r'^dashboard/player/$', login_required(mcm.Player.as_view()), name="player"),
|
||||||
path('application/<int:application_id>/', login_required(mcm.ApplicationInfo.as_view()), name="application_info"),
|
url(r'^dashboard/player/(?P<player_id>[0-9]{1,5})/$', login_required(mcm.PlayerInfo.as_view())),
|
||||||
|
#Tickets
|
||||||
# Players
|
url(r'^dashboard/ticket/$', login_required(mcm.Ticket.as_view()), name="ticket"),
|
||||||
path('player/', login_required(mcm.Player.as_view()), name="player"),
|
url(r'^dashboard/ticket/(?P<ticket_id>[0-9]{1,5})/$', login_required(mcm.TicketInfo.as_view())),
|
||||||
path('player/<int:player_id>/', login_required(mcm.PlayerInfo.as_view()), name="player_info"),
|
#Warnings
|
||||||
|
url(r'^dashboard/note/$', login_required(mcm.Note.as_view()), name="note"),
|
||||||
# Tickets
|
url(r'^dashboard/note/(?P<note_id>[0-9]{1,5})/$', login_required(mcm.NoteInfo.as_view())),
|
||||||
path('ticket/', login_required(mcm.Ticket.as_view()), name="ticket"),
|
url(r'^dashboard/note/add$', login_required(mcm.NoteAdd.as_view()), name="note_add"),
|
||||||
path('ticket/<int:ticket_id>/', login_required(mcm.TicketInfo.as_view()), name="ticket_info"),
|
#IP
|
||||||
|
url(r'^dashboard/ip/(?P<ip_id>[0-9]{1,5})/$', login_required(mcm.IP.as_view()), name="ip"),
|
||||||
# Notes
|
#Report
|
||||||
path('note/', login_required(mcm.Note.as_view()), name="note"),
|
url(r'^report/$', login_required(mcm.Report.as_view()), name="report"),
|
||||||
path('note/<int:note_id>/', login_required(mcm.NoteInfo.as_view()), name='note_info'),
|
#Chat
|
||||||
path('note/add', login_required(mcm.NoteAdd.as_view()), name="note_add"),
|
url(r'^dashboard/chat/$', permission_required('minecraft_manager.chat')(mcm.Chat.as_view()), name="chat"),
|
||||||
|
#Bots
|
||||||
# Attachments
|
url(r'^dashboard/bots/$', permission_required('minecraft_manager.bots')(mcm.Bots.as_view()), name="bots"),
|
||||||
path('attachment/<int:attachment_id>/', login_required(mcm.Attachment.as_view()), name="attachment"),
|
|
||||||
path('attachment/<str:ref_model>/<int:ref_id>/', login_required(mcm.AddAttachment.as_view()), name='attachment_add'),
|
|
||||||
|
|
||||||
# IP
|
|
||||||
path('ip/<int:ip_id>/', login_required(mcm.IP.as_view()), name="ip"),
|
|
||||||
|
|
||||||
# Report
|
|
||||||
path('report/', login_required(mcm.Report.as_view()), name="report"),
|
|
||||||
|
|
||||||
# Chat
|
|
||||||
path('chat/', permission_required('minecraft_manager.chat')(mcm.Chat.as_view()), name="chat"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 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"),
|
||||||
|
# ]
|
||||||
|
|
65
utils.py
65
utils.py
|
@ -1,9 +1,5 @@
|
||||||
import discord
|
import discord, requests
|
||||||
import requests
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.shortcuts import reverse
|
|
||||||
from django.templatetags.static import static
|
|
||||||
|
|
||||||
from minecraft_manager.models import Player, Application
|
from minecraft_manager.models import Player, Application
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,66 +26,67 @@ def build_application(application):
|
||||||
embed = discord.Embed(colour=discord.Colour(0x417505))
|
embed = discord.Embed(colour=discord.Colour(0x417505))
|
||||||
embed.title = "Application"
|
embed.title = "Application"
|
||||||
embed.set_thumbnail(
|
embed.set_thumbnail(
|
||||||
url=f"https://minotar.net/helm/{application.username}/100.png")
|
url="https://minotar.net/helm/{0}/100.png".format(application.username))
|
||||||
embed.add_field(name="Application ID", value=application.id)
|
embed.add_field(name="Application ID", value=application.id)
|
||||||
embed.add_field(name="Username", value=application.username.replace("_", "\\_"))
|
embed.add_field(name="Username", value=application.username.replace("_", "\\_"))
|
||||||
embed.add_field(name="Age", value=application.age)
|
embed.add_field(name="Age", value=application.age)
|
||||||
embed.add_field(name="Favorite Activity", value=application.player_type)
|
embed.add_field(name="Type of Player", value=application.player_type)
|
||||||
embed.add_field(name="Ever been banned", value=application.ever_banned)
|
embed.add_field(name="Ever been banned", value=application.ever_banned)
|
||||||
if application.ever_banned and application.ever_banned_explanation:
|
if application.ever_banned:
|
||||||
embed.add_field(name="Reason for being banned", value=application.ever_banned_explanation)
|
embed.add_field(name="Reason for being banned", value=application.ever_banned_explanation)
|
||||||
if application.reference:
|
embed.add_field(name="Reference", value=application.reference)
|
||||||
embed.add_field(name="Referral", value=application.reference)
|
|
||||||
embed.add_field(name="Read the Rules", value=application.read_rules)
|
embed.add_field(name="Read the Rules", value=application.read_rules)
|
||||||
embed.timestamp = application.date
|
embed.add_field(name="Date", value=application.date_display)
|
||||||
embed.add_field(name="Status", value=application.status)
|
embed.add_field(name="Status", value=application.status)
|
||||||
embed.add_field(name="Link", value=full_reverse('application_info', application.id), inline=False)
|
embed.add_field(name="Link", value="{}".format(url_path(settings.MCM_BASE_LINK, 'dashboard/application', application.id)))
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
def build_ticket(ticket, link):
|
def build_ticket(ticket, link):
|
||||||
embed = discord.Embed(colour=discord.Colour(0x417505))
|
embed = discord.Embed(colour=discord.Colour(0x417505))
|
||||||
embed.title = "Ticket"
|
embed.title = "Ticket"
|
||||||
embed.set_thumbnail(url=full_static("favicon.png"))
|
embed.set_thumbnail(
|
||||||
embed.timestamp = ticket.date
|
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="Player", value=ticket.player.username.replace("_", "\\_"))
|
||||||
embed.add_field(name="Priority", value=ticket.priority_display)
|
embed.add_field(name="Priority", value=ticket.priority_display)
|
||||||
if ticket.x and ticket.y and ticket.z and ticket.world:
|
if ticket.x and ticket.y and ticket.z and ticket.world:
|
||||||
embed.add_field(name="Location", value=ticket.location)
|
embed.add_field(name="Location", value=ticket.location)
|
||||||
embed.add_field(name="Message", value=ticket.message)
|
embed.add_field(name="Message", value=ticket.message)
|
||||||
embed.add_field(name="Link", value=link, inline=False)
|
embed.add_field(name="Link", value=link)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
def build_note(note, link):
|
def build_warning(warning, link):
|
||||||
embed = discord.Embed(colour=discord.Colour(0x417505))
|
embed = discord.Embed(colour=discord.Colour(0x417505))
|
||||||
embed.title = "Note"
|
embed.title = "Warning"
|
||||||
embed.set_thumbnail(url=full_static("favicon.png"))
|
embed.set_thumbnail(
|
||||||
embed.timestamp = note.date
|
url="https://cdn.discordapp.com/avatars/454457830918062081/b5792489bc43d9e17b8f657880a17dd4.png")
|
||||||
embed.add_field(name="Player", value=note.player.username.replace("_", "\\_"))
|
embed.add_field(name="Date", value=warning.date_display)
|
||||||
embed.add_field(name="Importance", value=note.importance_display)
|
embed.add_field(name="Player", value=warning.player.username.replace("_", "\\_"))
|
||||||
embed.add_field(name="Message", value=note.message)
|
embed.add_field(name="Importance", value=warning.importance_display)
|
||||||
embed.add_field(name="Link", value=link, inline=False)
|
embed.add_field(name="Message", value=warning.message)
|
||||||
|
embed.add_field(name="Link", value=link)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
def validate_username(username):
|
def validate_username(username):
|
||||||
response = requests.get(f"https://api.mojang.com/users/profiles/minecraft/{username}")
|
response = requests.get("https://api.mojang.com/users/profiles/minecraft/{}".format(username))
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def full_reverse(viewname, *args):
|
def url_path(*args):
|
||||||
base = settings.MCM_DOMAIN.rstrip('/')
|
value = []
|
||||||
view = reverse(viewname, args=args)
|
for arg in args:
|
||||||
return f"{base}{view}"
|
arg = str(arg)
|
||||||
|
if arg.startswith('/'):
|
||||||
|
arg = arg[1:]
|
||||||
def full_static(assetname):
|
if arg.endswith('/'):
|
||||||
base = settings.MCM_DOMAIN.rstrip('/')
|
arg = arg[:-1]
|
||||||
asset = static(assetname)
|
value.append(arg)
|
||||||
return f"{base}{asset}"
|
return '/'.join(value)
|
||||||
|
|
||||||
|
|
||||||
class Captcha:
|
class Captcha:
|
||||||
|
|
153
views.py
153
views.py
|
@ -1,20 +1,25 @@
|
||||||
|
#create your views here.
|
||||||
|
# https://api.mojang.com/users/profiles/minecraft/<username>
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import json, datetime, pytz, os
|
import json, datetime, pytz, os, sys
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from django.http import JsonResponse, HttpResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import render, reverse, redirect
|
from django.shortcuts import render, reverse, redirect
|
||||||
from django.views.decorators.csrf import csrf_protect
|
from django.views.decorators.csrf import csrf_protect
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from minecraft_manager.models import Application as AppModel, Player as PlayerModel, Ticket as TicketModel, TicketNote as TicketNoteModel, Note as NoteModel, IP as IPModel, Alert as AlertModel, UserSettings as UserSettingsModel, Attachment as AttachmentModel, RefModels
|
from minecraft_manager.models import Application as AppModel, Player as PlayerModel, Ticket as TicketModel, TicketNote as TicketNoteModel, Note as NoteModel, IP as IPModel, Alert as AlertModel, UserSettings as UserSettingsModel
|
||||||
from minecraft_manager.forms import TicketNoteForm, NoteForm
|
from minecraft_manager.forms import TicketNoteForm, NoteForm
|
||||||
from minecraft_manager.overview import overview_data
|
from minecraft_manager.overview import overview_data
|
||||||
from minecraft_manager.utils import resolve_player, build_note, full_reverse
|
from minecraft_manager.utils import resolve_player
|
||||||
import minecraft_manager.api.api as API
|
import minecraft_manager.api.api as API
|
||||||
|
from minecraft_manager.bot import Bot
|
||||||
|
from minecraft_manager.bot.discord import start as discord_start, stop as discord_stop, restart as discord_restart, \
|
||||||
|
status as discord_status, display as discord_display
|
||||||
|
|
||||||
|
|
||||||
class Overview(View):
|
class Overview(View):
|
||||||
|
@ -39,12 +44,14 @@ class Overview(View):
|
||||||
class CoreProtect(View):
|
class CoreProtect(View):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
#http://www.24carrotcraft.com/assets/cp/index.php?username=etzelia
|
||||||
return render(request, 'minecraft_manager/coreprotect.html', {'current_app': 'coreprotect'})
|
return render(request, 'minecraft_manager/coreprotect.html', {'current_app': 'coreprotect'})
|
||||||
|
|
||||||
|
|
||||||
class Activity(View):
|
class Activity(View):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
#http://www.24carrotcraft.com/assets/cp/activity.php?username=etzelia
|
||||||
return render(request, 'minecraft_manager/activity.html', {'current_app': 'activity'})
|
return render(request, 'minecraft_manager/activity.html', {'current_app': 'activity'})
|
||||||
|
|
||||||
|
|
||||||
|
@ -134,6 +141,7 @@ class Application(View):
|
||||||
class Reference(View):
|
class Reference(View):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
get = request.GET
|
||||||
applications = AppModel.objects.all()
|
applications = AppModel.objects.all()
|
||||||
return render(request, 'minecraft_manager/reference.html', {'current_app': 'application', 'applications': applications})
|
return render(request, 'minecraft_manager/reference.html', {'current_app': 'application', 'applications': applications})
|
||||||
|
|
||||||
|
@ -155,8 +163,7 @@ class ApplicationInfo(View):
|
||||||
application.accepted = False
|
application.accepted = False
|
||||||
application.save()
|
application.save()
|
||||||
API.plugin(post['accept'], application.username)
|
API.plugin(post['accept'], application.username)
|
||||||
link = full_reverse('application_info', application_id)
|
API.discord_mcm("Application #**{0}** was **{1}** by **{2}**".format(application.id, "Accepted" if application.accepted else "Denied", request.user.player.username))
|
||||||
API.discord_mcm("[Application #**{0}**]({3}) was **{1}** by **{2}**".format(application.id, "Accepted" if application.accepted else "Denied", request.user.player.username, link))
|
|
||||||
return render(request, 'minecraft_manager/application_info.html',
|
return render(request, 'minecraft_manager/application_info.html',
|
||||||
{'current_app': 'application', 'application': application})
|
{'current_app': 'application', 'application': application})
|
||||||
|
|
||||||
|
@ -185,7 +192,7 @@ class Player(View):
|
||||||
|
|
||||||
class PlayerInfo(View):
|
class PlayerInfo(View):
|
||||||
|
|
||||||
def _info(self, request, player_id):
|
def get(self, request, player_id):
|
||||||
player = PlayerModel.objects.get(id=player_id)
|
player = PlayerModel.objects.get(id=player_id)
|
||||||
ips = IPModel.api.filter(player=player)
|
ips = IPModel.api.filter(player=player)
|
||||||
tickets = TicketModel.objects.filter(player=player)
|
tickets = TicketModel.objects.filter(player=player)
|
||||||
|
@ -194,16 +201,20 @@ class PlayerInfo(View):
|
||||||
return render(request, 'minecraft_manager/player_info.html',
|
return render(request, 'minecraft_manager/player_info.html',
|
||||||
{'current_app': 'player', 'player': player, 'form': form})
|
{'current_app': 'player', 'player': player, 'form': form})
|
||||||
|
|
||||||
def get(self, request, player_id):
|
|
||||||
return self._info(request, player_id)
|
|
||||||
|
|
||||||
def post(self, request, player_id):
|
def post(self, request, player_id):
|
||||||
return self._info(request, player_id)
|
player = PlayerModel.objects.get(id=player_id)
|
||||||
|
ips = IPModel.api.filter(player=player)
|
||||||
|
tickets = TicketModel.objects.filter(player=player)
|
||||||
|
notes = NoteModel.objects.filter(player=player)
|
||||||
|
form = {'ips': ips, 'tickets': tickets, 'notes': notes}
|
||||||
|
return render(request, 'minecraft_manager/player_info.html',
|
||||||
|
{'current_app': 'player', 'player': player, 'form': form})
|
||||||
|
|
||||||
|
|
||||||
class Ticket(View):
|
class Ticket(View):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
get = request.GET
|
||||||
tickets1 = TicketModel.objects.filter(resolved=False).order_by('-id')
|
tickets1 = TicketModel.objects.filter(resolved=False).order_by('-id')
|
||||||
tickets2 = TicketModel.objects.filter(resolved=True).order_by('-id')
|
tickets2 = TicketModel.objects.filter(resolved=True).order_by('-id')
|
||||||
tickets = list(chain(tickets1, tickets2))
|
tickets = list(chain(tickets1, tickets2))
|
||||||
|
@ -234,17 +245,14 @@ class TicketInfo(View):
|
||||||
def post(self, request, ticket_id):
|
def post(self, request, ticket_id):
|
||||||
post = request.POST
|
post = request.POST
|
||||||
ticket = TicketModel.objects.get(id=ticket_id)
|
ticket = TicketModel.objects.get(id=ticket_id)
|
||||||
link = full_reverse('ticket_info', ticket_id)
|
|
||||||
if 'priority' in post:
|
if 'priority' in post:
|
||||||
if post['priority'] != ticket.priority:
|
if post['priority'] != ticket.priority:
|
||||||
API.discord_mcm(
|
API.discord_mcm(
|
||||||
"[Ticket #**{0}**]({4})'s priority was changed from **{1}** to **{2}** by **{3}**".format(ticket.id,
|
"Ticket #**{0}**'s priority was changed from **{1}** to **{2}** by **{3}**".format(ticket.id,
|
||||||
ticket.priority_display,
|
ticket.priority_display,
|
||||||
TicketModel.priority_code_to_display(
|
TicketModel.priority_code_to_display(
|
||||||
post['priority']),
|
post['priority']),
|
||||||
request.user.username,
|
request.user.username))
|
||||||
link)
|
|
||||||
)
|
|
||||||
ticket.priority = post['priority']
|
ticket.priority = post['priority']
|
||||||
if 'staff' in post and 'resolved' not in post:
|
if 'staff' in post and 'resolved' not in post:
|
||||||
if not ticket.staff or request.user.is_staff:
|
if not ticket.staff or request.user.is_staff:
|
||||||
|
@ -252,13 +260,13 @@ class TicketInfo(View):
|
||||||
if post['staff'] != str(getattr(ticket.staff, 'id', '-1')):
|
if post['staff'] != str(getattr(ticket.staff, 'id', '-1')):
|
||||||
if post['staff'] == str(request.user.id):
|
if post['staff'] == str(request.user.id):
|
||||||
API.discord_mcm(
|
API.discord_mcm(
|
||||||
"[Ticket #**{0}**]({2}) was claimed by **{1}**".format(ticket.id, request.user.username, link))
|
"Ticket #**{0}** was claimed by **{1}**".format(ticket.id, request.user.username))
|
||||||
else:
|
else:
|
||||||
API.discord_mcm(
|
API.discord_mcm(
|
||||||
"[Ticket #**{0}**]({3}) was given to **{1}** by **{2}**".format(ticket.id, staff.username, request.user.username, link))
|
"Ticket #**{0}** was given to **{1}** by **{2}**".format(ticket.id, staff.username, request.user.username))
|
||||||
ticket.staff = staff
|
ticket.staff = staff
|
||||||
if 'resolved' in post:
|
if 'resolved' in post:
|
||||||
API.discord_mcm("[Ticket #**{0}**]({2}) was resolved by **{1}**".format(ticket.id, request.user.username, link))
|
API.discord_mcm("Ticket #**{0}** was resolved by **{1}**".format(ticket.id, request.user.username))
|
||||||
ticket.resolved = True
|
ticket.resolved = True
|
||||||
ticket.save()
|
ticket.save()
|
||||||
|
|
||||||
|
@ -275,11 +283,6 @@ class TicketInfo(View):
|
||||||
db.message = n.message
|
db.message = n.message
|
||||||
db.last_update = timezone.now()
|
db.last_update = timezone.now()
|
||||||
db.save()
|
db.save()
|
||||||
# Refresh to get the ID for attachments
|
|
||||||
note = TicketNoteModel.objects.get(ticket=ticket, author=request.user)
|
|
||||||
for file in request.FILES.getlist('attachments', []):
|
|
||||||
attachment = AttachmentModel(ref_model=RefModels.TICKET_NOTE[0], ref_id=note.id, file=file)
|
|
||||||
attachment.save()
|
|
||||||
else:
|
else:
|
||||||
show_ticket_note = True
|
show_ticket_note = True
|
||||||
else:
|
else:
|
||||||
|
@ -314,31 +317,23 @@ class NoteInfo(View):
|
||||||
def get(self, request, note_id):
|
def get(self, request, note_id):
|
||||||
note = NoteModel.objects.get(id=note_id)
|
note = NoteModel.objects.get(id=note_id)
|
||||||
form = {'importance': NoteModel.IMPORTANCE}
|
form = {'importance': NoteModel.IMPORTANCE}
|
||||||
return render(request, 'minecraft_manager/note_info.html', {
|
return render(request, 'minecraft_manager/note_info.html', {'current_app': 'note', 'form': form, 'note': note})
|
||||||
'current_app': 'note',
|
|
||||||
'form': form,
|
|
||||||
'note': note
|
|
||||||
})
|
|
||||||
|
|
||||||
def post(self, request, note_id):
|
def post(self, request, note_id):
|
||||||
post = request.POST
|
post = request.POST
|
||||||
note = NoteModel.objects.get(id=note_id)
|
note = NoteModel.objects.get(id=note_id)
|
||||||
if 'importance' in post:
|
if 'importance' in post:
|
||||||
API.discord_mcm("[Note #**{0}**]({4})'s importance was changed from **{1}** to **{2}** by **{3}**".format(
|
API.discord_mcm("Note #**{0}**'s importance was changed from {1} to {2} by {3}".format(note.id,
|
||||||
note.id,
|
note.importance_display,
|
||||||
note.importance_display,
|
NoteModel.importance_code_to_display(
|
||||||
NoteModel.importance_code_to_display(post['importance']),
|
post[
|
||||||
request.user.username,
|
'importance']),
|
||||||
full_reverse('note_info', note_id))
|
request.user.player.username))
|
||||||
)
|
|
||||||
note.importance = post['importance']
|
note.importance = post['importance']
|
||||||
note.save()
|
note.save()
|
||||||
form = {'importance': NoteModel.IMPORTANCE}
|
form = {'importance': NoteModel.IMPORTANCE}
|
||||||
return render(request, 'minecraft_manager/note_info.html', context={
|
return render(request, 'minecraft_manager/note_info.html',
|
||||||
'current_app': 'note',
|
{'current_app': 'note', 'form': form, 'note': note})
|
||||||
'form': form,
|
|
||||||
'note': note
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class NoteAdd(View):
|
class NoteAdd(View):
|
||||||
|
@ -349,7 +344,8 @@ class NoteAdd(View):
|
||||||
form = NoteForm()
|
form = NoteForm()
|
||||||
if 'player' in get:
|
if 'player' in get:
|
||||||
form.initial = {'player': get['player']}
|
form.initial = {'player': get['player']}
|
||||||
return render(request, 'minecraft_manager/note_add.html', context={'current_app': 'note', 'form': form.as_p()})
|
return render(request, 'minecraft_manager/note_add.html',
|
||||||
|
{'current_app': 'note', 'form': form.as_p()})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
post = request.POST
|
post = request.POST
|
||||||
|
@ -358,36 +354,15 @@ class NoteAdd(View):
|
||||||
note = form.save()
|
note = form.save()
|
||||||
note.staff = request.user
|
note.staff = request.user
|
||||||
note.save()
|
note.save()
|
||||||
API.discord_mcm(embed=build_note(note, full_reverse('note_info', note.id)))
|
API.discord_mcm(
|
||||||
for file in request.FILES.getlist('attachments', []):
|
"**{0}** made a **{1}** importance note for **{2}**\nPreview: {3}".format(note.staff.player.username,
|
||||||
attachment = AttachmentModel(ref_model=RefModels.NOTE[0], ref_id=note.id, file=file)
|
note.importance_display,
|
||||||
attachment.save()
|
note.player.username,
|
||||||
return redirect("{0}{1}".format(full_reverse('note'), note.id))
|
note.snippet))
|
||||||
|
return redirect("{0}{1}".format(reverse('note'), note.id))
|
||||||
else:
|
else:
|
||||||
return render(request, 'minecraft_manager/note_add.html', context={'current_app': 'note', 'form': form})
|
return render(request, 'minecraft_manager/note_add.html',
|
||||||
|
{'current_app': 'note', 'form': form})
|
||||||
|
|
||||||
class Attachment(View):
|
|
||||||
|
|
||||||
def get(self, request, attachment_id):
|
|
||||||
attachment = AttachmentModel.objects.get(id=attachment_id)
|
|
||||||
resp = HttpResponse(attachment.file)
|
|
||||||
resp['Content-Disposition'] = f"attachment; filename={attachment.file_name}"
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def delete(self, request, attachment_id):
|
|
||||||
attachment = AttachmentModel.objects.get(id=attachment_id)
|
|
||||||
attachment.delete()
|
|
||||||
return HttpResponse(status=204)
|
|
||||||
|
|
||||||
|
|
||||||
class AddAttachment(View):
|
|
||||||
|
|
||||||
def post(self, request, ref_model, ref_id):
|
|
||||||
for file in request.FILES.getlist('attachments', []):
|
|
||||||
attachment = AttachmentModel(ref_model=ref_model, ref_id=ref_id, file=file)
|
|
||||||
attachment.save()
|
|
||||||
return redirect(request.POST.get('next', reverse('overview')))
|
|
||||||
|
|
||||||
|
|
||||||
class IP(View):
|
class IP(View):
|
||||||
|
@ -455,6 +430,7 @@ class Report(View):
|
||||||
|
|
||||||
class Chat(View):
|
class Chat(View):
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def replace_ascii(message):
|
def replace_ascii(message):
|
||||||
return message.replace(" ", "\\040").replace("\"", "\\042").replace("#", "\\043").replace("$", "\\044")\
|
return message.replace(" ", "\\040").replace("\"", "\\042").replace("#", "\\043").replace("$", "\\044")\
|
||||||
|
@ -473,3 +449,38 @@ class Chat(View):
|
||||||
else:
|
else:
|
||||||
data = {'success': False, 'message': 'No chat type or message set.'}
|
data = {'success': False, 'message': 'No chat type or message set.'}
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
|
||||||
|
class Bots(View):
|
||||||
|
|
||||||
|
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(Bot(file.replace('.bot.py', ''), False, py))
|
||||||
|
else:
|
||||||
|
bots.append(Bot(file.replace('.bot.py', ''), False, sys.executable))
|
||||||
|
# Also get packaged MCM bots
|
||||||
|
if getattr(settings, 'DISCORD_BOT_TOKEN', None):
|
||||||
|
bots.append(Bot("Discord-MCM", True, None, discord_start, discord_stop, discord_restart, discord_status, discord_display))
|
||||||
|
return bots
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
return render(request, 'minecraft_manager/bots.html', {'current_app': 'bots', 'bots': self.get_bots()})
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
post = request.POST
|
||||||
|
for bot in self.get_bots():
|
||||||
|
if bot.name in post:
|
||||||
|
if post[bot.name] == "stop":
|
||||||
|
bot.stop()
|
||||||
|
elif post[bot.name] == "start":
|
||||||
|
bot.start()
|
||||||
|
elif post[bot.name] == "restart":
|
||||||
|
bot.restart()
|
||||||
|
return render(request, 'minecraft_manager/bots.html', {'current_app': 'bots', 'bots': self.get_bots()})
|
||||||
|
|
Loading…
Reference in New Issue