From 47fb48fd06ceb3df27533f6eaad8aebb098b681b Mon Sep 17 00:00:00 2001 From: Etz Elia Date: Sat, 6 Jun 2020 19:48:46 +0200 Subject: [PATCH 01/13] Fix captcha and generify community invite (#52) Add validation for final question and some minor CSS Signed-off-by: Etzelia Change some docs Signed-off-by: Etzelia Fix captcha and generify community invite Signed-off-by: Etzelia Co-authored-by: Etzelia Reviewed-by: ZeroHD --- docs/source/django-settings.rst | 4 ++- external/views.py | 31 ++++++++++++------- static/minecraft_manager/css/external.css | 5 +++ .../minecraft_manager/external/apply.html | 2 +- .../minecraft_manager/external/base.html | 2 +- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/docs/source/django-settings.rst b/docs/source/django-settings.rst index 76a6469..a4ebbd8 100644 --- a/docs/source/django-settings.rst +++ b/docs/source/django-settings.rst @@ -43,7 +43,9 @@ Optional ``DISCORD_NOTIFICATION_CHANNEL`` - The ID for the channel used for notifications. -``DISCORD_INVITE`` - The invite code to your Discord, for after a player applies on the web form. +``INVITE_LINK`` - The invite link to your community. + +``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. diff --git a/external/views.py b/external/views.py index ce3f693..5d40f82 100644 --- a/external/views.py +++ b/external/views.py @@ -1,5 +1,5 @@ from django.views.generic import View -from django.shortcuts import render, reverse, redirect +from django.shortcuts import render from django.conf import settings from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt @@ -8,12 +8,13 @@ import minecraft_manager.api.api as mcm_api import minecraft_manager.utils as mcm_utils import minecraft_manager.external.stats as mcm_stats from minecraft_manager.models import Player -import random, yaml, os, json, datetime, pytz +import random, yaml, os def config(): data = {} - data['discord_invite'] = getattr(settings, "DISCORD_INVITE", "#") + data['invite_link'] = getattr(settings, "INVITE_LINK", "#") + data['invite_label'] = getattr(settings, "INVITE_LABEL", "community") dynmap_url = getattr(settings, "DYNMAP_URL", "") data['dynmap_url'] = dynmap_url @@ -50,7 +51,11 @@ def rules(): if cfg['rules']['application']['validate']: data.append("The answer to the final question is \"{}\"".format(cfg['rules']['application']['answer'])) - return data + return { + "rules": data, + "validate": cfg['rules']['application']['validate'], + "answer": cfg['rules']['application']['answer'] + } @method_decorator(csrf_exempt, name='dispatch') @@ -59,15 +64,17 @@ class Apply(View): def get(self, request): form = ApplicationForm() return render(request, 'minecraft_manager/external/apply.html', - {'form': form.as_p(), 'rules': rules(), 'valid': False, 'map': config(), - 'captcha': hasattr(settings, "CAPTCHA_SECRET")}) + {'form': form.as_p(), 'rules': rules()["rules"], 'valid': False, 'map': config(), + 'captcha': getattr(settings, "CAPTCHA_SITE", "")}) def post(self, request): form = ApplicationForm(request.POST) valid_username = mcm_utils.validate_username(form.data['username']) captcha = mcm_utils.Captcha(request.POST) valid = form.is_valid() - if valid and valid_username and captcha.success: + r = rules() + valid_answer = not r["validate"] or r["answer"] == form.data['read_rules'] + if valid and valid_username and valid_answer and captcha.success: app = form.save() msg = mcm_utils.build_application(app) mcm_api.discord_mcm(message='New Application!', embed=msg) @@ -77,9 +84,11 @@ class Apply(View): form.add_error(None, error) if not valid_username: 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', - {'form': form.as_p(), 'rules': rules(), 'valid': valid and valid_username and captcha.success, 'map': config(), - 'captcha': hasattr(settings, "CAPTCHA_SECRET")}) + {'form': form.as_p(), 'rules': r["rules"], 'valid': valid and valid_username and valid_answer and captcha.success, 'map': config(), + 'captcha': getattr(settings, "CAPTCHA_SITE", "")}) @method_decorator(csrf_exempt, name='dispatch') @@ -89,7 +98,7 @@ class Ticket(View): form = TicketForm() return render(request, 'minecraft_manager/external/ticket.html', {'form': form.as_p(), 'valid': False, 'map': config(), - 'captcha': hasattr(settings, "CAPTCHA_SECRET")}) + 'captcha': getattr(settings, "CAPTCHA_SITE", "")}) def post(self, request): post = request.POST.copy() @@ -120,7 +129,7 @@ class Ticket(View): form.data['player'] = username return render(request, 'minecraft_manager/external/ticket.html', {'form': form.as_p(), 'valid': valid and captcha.success, 'map': config(), - 'captcha': hasattr(settings, "CAPTCHA_SECRET")}) + 'captcha': getattr(settings, "CAPTCHA_SITE", "")}) @method_decorator(csrf_exempt, name='dispatch') diff --git a/static/minecraft_manager/css/external.css b/static/minecraft_manager/css/external.css index 0b2adc0..8f3436b 100644 --- a/static/minecraft_manager/css/external.css +++ b/static/minecraft_manager/css/external.css @@ -67,4 +67,9 @@ .rule { margin-bottom: .5em; +} + +.errorlist { + color: #D8000C; + background-color: #FFD2D2; } \ No newline at end of file diff --git a/templates/minecraft_manager/external/apply.html b/templates/minecraft_manager/external/apply.html index 072c6b2..d376524 100644 --- a/templates/minecraft_manager/external/apply.html +++ b/templates/minecraft_manager/external/apply.html @@ -17,7 +17,7 @@
We will get back to you soon.
- Consider joining our Discord + Consider joining our {{ map.invite_label }} {% endblock %} {% endif %} diff --git a/templates/minecraft_manager/external/base.html b/templates/minecraft_manager/external/base.html index 3819580..74197ad 100644 --- a/templates/minecraft_manager/external/base.html +++ b/templates/minecraft_manager/external/base.html @@ -21,7 +21,7 @@ {% else %}
{% block form %}{{ form }}{% endblock %}

- {% if captcha %}
{% endif %} + {% if captcha %}
{% endif %}
{% endif %} -- 2.41.0 From 58f611ee4547ddb2e62300b21aab023f8769a573 Mon Sep 17 00:00:00 2001 From: Etz Elia Date: Thu, 11 Jun 2020 22:03:54 +0200 Subject: [PATCH 02/13] Add 'LICENSE' (#53) Add 'LICENSE' Reviewed-by: ZeroHD --- LICENSE | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..82b4eb1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +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. \ No newline at end of file -- 2.41.0 From 83527fe4b57ca8bc512fbcc4107653d7aa7cb27b Mon Sep 17 00:00:00 2001 From: Etz Elia Date: Thu, 11 Jun 2020 22:18:51 +0200 Subject: [PATCH 03/13] Interim Patch (#54) Birb Patches (#1) Birb Patches Signed-off-by: Etzelia Co-authored-by: Etzelia Reviewed-by: ZeroHD --- external/views.py | 2 +- models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/views.py b/external/views.py index 5d40f82..6b5623a 100644 --- a/external/views.py +++ b/external/views.py @@ -73,7 +73,7 @@ class Apply(View): captcha = mcm_utils.Captcha(request.POST) valid = form.is_valid() r = rules() - valid_answer = not r["validate"] or r["answer"] == form.data['read_rules'] + 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() msg = mcm_utils.build_application(app) diff --git a/models.py b/models.py index 095bf16..db11175 100644 --- a/models.py +++ b/models.py @@ -61,7 +61,7 @@ class Application(models.Model): player_type = models.TextField("What type of player are you?", max_length=300) ever_banned = models.BooleanField("Have you ever been banned?", default=False) ever_banned_explanation = models.TextField("If you were previously banned, will you share why?", max_length=300, blank=True, null=True) - reference = models.CharField("Were you referred to our server?", max_length=50, blank=True, null=True) + reference = models.CharField("How did you find out about our server?", max_length=50, blank=True, null=True) read_rules = models.CharField("Have you read the rules?", max_length=10) accepted = models.NullBooleanField() date = models.DateTimeField(auto_now_add=True, blank=True, null=True) -- 2.41.0 From f55a1c45380c0675f438b7a4d66b9b9cbb8af26b Mon Sep 17 00:00:00 2001 From: Etzelia Date: Fri, 21 Aug 2020 16:28:53 +0200 Subject: [PATCH 04/13] Add requirements (not txt) (#57) Add requirements (not txt) Signed-off-by: Etzelia Reviewed-on: https://git.etztech.xyz/MMS/MinecraftManagerDjango/pulls/57 Reviewed-by: ZeroHD --- REQUIREMENTS.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 REQUIREMENTS.md diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md new file mode 100644 index 0000000..213692a --- /dev/null +++ b/REQUIREMENTS.md @@ -0,0 +1,15 @@ +# 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 + -- 2.41.0 From a40966aa0ec719bc6854ae18d693e1d9da645c8f Mon Sep 17 00:00:00 2001 From: Etzelia Date: Fri, 18 Sep 2020 01:38:19 +0200 Subject: [PATCH 05/13] Fix Discord embeds (#59) Fix Discord embeds Reviewed-on: https://git.etztech.xyz/MMS/MinecraftManagerDjango/pulls/59 Reviewed-by: ZeroHD --- utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils.py b/utils.py index 11d3716..37bab09 100644 --- a/utils.py +++ b/utils.py @@ -32,9 +32,10 @@ def build_application(application): embed.add_field(name="Age", value=application.age) embed.add_field(name="Type of Player", value=application.player_type) embed.add_field(name="Ever been banned", value=application.ever_banned) - if application.ever_banned: + if application.ever_banned and application.ever_banned_explanation: embed.add_field(name="Reason for being banned", value=application.ever_banned_explanation) - embed.add_field(name="Reference", value=application.reference) + if application.reference: + embed.add_field(name="Reference", value=application.reference) embed.add_field(name="Read the Rules", value=application.read_rules) embed.add_field(name="Date", value=application.date_display) embed.add_field(name="Status", value=application.status) -- 2.41.0 From cd369ca1a3e3e28a25d9c8b625d8fdadacf4c6ed Mon Sep 17 00:00:00 2001 From: Etzelia Date: Sun, 7 Feb 2021 22:08:36 -0600 Subject: [PATCH 06/13] Add attachments, clean up UI, etc. Signed-off-by: Etzelia --- __init__.py | 1 + admin.py | 11 ++- apps.py | 12 +++ bot/commands.py | 23 +++++ forms.py | 4 +- migrations/0015_auto_20210206_2140.py | 29 +++++++ models.py | 49 ++++++++++- signals/__init__.py | 0 signals/pre_delete.py | 2 + .../css/bootswatch-darkly.css | 5 ++ .../css/bootswatch-flatly.css | 5 ++ .../css/bootswatch-slate.css | 6 ++ .../css/bootswatch-solar.css | 5 ++ templates/minecraft_manager/alert.html | 12 ++- templates/minecraft_manager/application.html | 13 ++- templates/minecraft_manager/ban.html | 2 +- templates/minecraft_manager/bots.html | 16 ---- templates/minecraft_manager/dashboard.html | 6 +- .../modal/add_attachment.html | 47 ++++++++++ .../modal/delete_attachment.html | 48 +++++++++++ templates/minecraft_manager/note.html | 16 +++- templates/minecraft_manager/note_add.html | 16 ++-- templates/minecraft_manager/note_info.html | 35 ++++++-- templates/minecraft_manager/player.html | 14 ++- templates/minecraft_manager/player_info.html | 86 ++++++++++++++++--- templates/minecraft_manager/ticket.html | 18 +++- templates/minecraft_manager/ticket_info.html | 37 ++++++-- urls.py | 38 ++++---- utils.py | 1 + views.py | 81 ++++++++++++----- 30 files changed, 522 insertions(+), 116 deletions(-) create mode 100644 apps.py create mode 100644 migrations/0015_auto_20210206_2140.py create mode 100644 signals/__init__.py create mode 100644 signals/pre_delete.py delete mode 100644 templates/minecraft_manager/bots.html create mode 100644 templates/minecraft_manager/modal/add_attachment.html create mode 100644 templates/minecraft_manager/modal/delete_attachment.html diff --git a/__init__.py b/__init__.py index e69de29..4527f3e 100644 --- a/__init__.py +++ b/__init__.py @@ -0,0 +1 @@ +default_app_config = 'minecraft_manager.apps.MinecraftManagerAppConfig' diff --git a/admin.py b/admin.py index 6cc4532..5de50b1 100644 --- a/admin.py +++ b/admin.py @@ -4,7 +4,7 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ -from minecraft_manager.models import Application, Note, Ticket, TicketNote, Player, IP, UserSettings, Alert +from minecraft_manager.models import Application, Note, Ticket, TicketNote, Player, IP, UserSettings, Alert, Attachment from minecraft_manager.api.admin import register as api_register @@ -90,6 +90,14 @@ class IPAdmin(admin.ModelAdmin): 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: admin.site.unregister(User) admin.site.register(User, UserAdmin) @@ -101,6 +109,7 @@ try: admin.site.register(Player, PlayerAdmin) admin.site.register(IP, IPAdmin) admin.site.register(Alert) + admin.site.register(Attachment, AttachmentAdmin) api_register() except admin.sites.AlreadyRegistered: pass diff --git a/apps.py b/apps.py new file mode 100644 index 0000000..a5fc31c --- /dev/null +++ b/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig +from django.db.models.signals import pre_delete +from minecraft_manager.signals.pre_delete import attachment_delete + + +class MinecraftManagerAppConfig(AppConfig): + name = 'minecraft_manager' + verbose_name = "Minecraft Manager" + + def ready(self): + pre_delete.connect(attachment_delete) + diff --git a/bot/commands.py b/bot/commands.py index 9d6f4c1..dd7c2ec 100644 --- a/bot/commands.py +++ b/bot/commands.py @@ -153,6 +153,29 @@ class Commands(commands.Cog): 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?") + @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() async def search(self, ctx, search: str): await self._search(ctx, search) diff --git a/forms.py b/forms.py index d0a37b3..d16ab7c 100644 --- a/forms.py +++ b/forms.py @@ -30,7 +30,7 @@ class TicketForm(ModelForm): fields = ['player', 'message', 'priority', 'world', 'x', 'y', 'z'] widgets = { 'player': TextInput, - 'message': Textarea, + 'message': Textarea(attrs={'style': 'display: block;'}), } @@ -39,7 +39,7 @@ class NoteForm(ModelForm): model = Note fields = ['player', 'message', 'importance'] widgets = { - 'message': Textarea + 'message': Textarea(attrs={'style': 'display: block;'}) } diff --git a/migrations/0015_auto_20210206_2140.py b/migrations/0015_auto_20210206_2140.py new file mode 100644 index 0000000..fcb0e44 --- /dev/null +++ b/migrations/0015_auto_20210206_2140.py @@ -0,0 +1,29 @@ +# 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?'), + ), + ] diff --git a/models.py b/models.py index db11175..ad6ea38 100644 --- a/models.py +++ b/models.py @@ -1,13 +1,13 @@ from django.db import models from django.contrib.auth.models import User from django.db.models import Q +from os.path import basename import logging, yaml, pytz, json, os from django.conf import settings from datetime import datetime logger = logging.getLogger(__name__) - class MinecraftManagerUser(User): class Meta: @@ -252,6 +252,10 @@ class Ticket(models.Model): def date_display(self): return str(self.date).split(".")[0] + @property + def notes(self): + return TicketNote.objects.filter(ticket=self) + def __str__(self): return "{}: {}".format(self.issuer, self.snippet) @@ -263,6 +267,10 @@ class TicketNote(models.Model): last_update = models.DateTimeField(auto_now_add=True, blank=True, null=True) date = models.DateTimeField(auto_now_add=True, blank=True, null=True) + @property + def attachments(self): + return Attachment.objects.filter(ref_model=RefModels.TICKET_NOTE[0], ref_id=self.id) + class Meta: verbose_name = "Ticket Note" verbose_name_plural = "Ticket Notes" @@ -324,10 +332,49 @@ class Note(models.Model): def date_display(self): 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): 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 + + class IPManager(models.Manager): def get_queryset(self): users = User.objects.filter(is_active=True) diff --git a/signals/__init__.py b/signals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/signals/pre_delete.py b/signals/pre_delete.py new file mode 100644 index 0000000..cb46ea4 --- /dev/null +++ b/signals/pre_delete.py @@ -0,0 +1,2 @@ +def attachment_delete(sender, instance, **kwargs): + instance.file.delete(False) diff --git a/static/minecraft_manager/css/bootswatch-darkly.css b/static/minecraft_manager/css/bootswatch-darkly.css index 9a5e710..b886c76 100644 --- a/static/minecraft_manager/css/bootswatch-darkly.css +++ b/static/minecraft_manager/css/bootswatch-darkly.css @@ -1,4 +1,9 @@ @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 * Homepage: http://bootswatch.com diff --git a/static/minecraft_manager/css/bootswatch-flatly.css b/static/minecraft_manager/css/bootswatch-flatly.css index ba7e9e9..3e0f8f7 100644 --- a/static/minecraft_manager/css/bootswatch-flatly.css +++ b/static/minecraft_manager/css/bootswatch-flatly.css @@ -1,4 +1,9 @@ @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 * Homepage: http://bootswatch.com diff --git a/static/minecraft_manager/css/bootswatch-slate.css b/static/minecraft_manager/css/bootswatch-slate.css index eb90344..599a694 100644 --- a/static/minecraft_manager/css/bootswatch-slate.css +++ b/static/minecraft_manager/css/bootswatch-slate.css @@ -1,3 +1,9 @@ +@import url("https://fonts.googleapis.com/css?family=Roboto:400,700"); +/* Custom CSS */ +.nav-sidebar { + border-right: 1px solid #1c1e22; +} + /*! * bootswatch v3.3.7 * Homepage: http://bootswatch.com diff --git a/static/minecraft_manager/css/bootswatch-solar.css b/static/minecraft_manager/css/bootswatch-solar.css index 3beb663..b9b0b72 100644 --- a/static/minecraft_manager/css/bootswatch-solar.css +++ b/static/minecraft_manager/css/bootswatch-solar.css @@ -1,4 +1,9 @@ @import url("https://fonts.googleapis.com/css?family=Roboto:400,700"); +/* Custom CSS */ +.nav-sidebar { + border-right: 1px solid #282828; +} + /*! * bootswatch v3.3.7 * Homepage: http://bootswatch.com diff --git a/templates/minecraft_manager/alert.html b/templates/minecraft_manager/alert.html index 736900a..d84c66a 100644 --- a/templates/minecraft_manager/alert.html +++ b/templates/minecraft_manager/alert.html @@ -4,7 +4,7 @@ {% block title %}Alerts{% endblock %} {% block section %}