mirror of
				https://github.com/DarkflameUniverse/NexusDashboard.git
				synced 2025-11-04 14:11:54 +00:00 
			
		
		
		
	@@ -13,7 +13,7 @@ from flask_apscheduler import APScheduler
 | 
			
		||||
from app.luclient import query_cdclient, register_luclient_jinja_helpers
 | 
			
		||||
 | 
			
		||||
from app.commands import init_db, init_accounts, load_property, gen_image_cache, gen_model_cache
 | 
			
		||||
from app.models import Account, AccountInvitation
 | 
			
		||||
from app.models import Account, AccountInvitation, AuditLog
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from logging.handlers import RotatingFileHandler
 | 
			
		||||
@@ -245,3 +245,9 @@ def gm_level(gm_level):
 | 
			
		||||
            return func(*args, **kwargs)
 | 
			
		||||
        return wrapper
 | 
			
		||||
    return decorator
 | 
			
		||||
 | 
			
		||||
def log_audit(message):
 | 
			
		||||
    AuditLog(
 | 
			
		||||
        account_id=current_user.id,
 | 
			
		||||
        action=message
 | 
			
		||||
    ).save()
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ import datetime
 | 
			
		||||
import time
 | 
			
		||||
from app.models import Account, AccountInvitation, db
 | 
			
		||||
from app.schemas import AccountSchema
 | 
			
		||||
from app import gm_level
 | 
			
		||||
from app import gm_level, log_audit
 | 
			
		||||
from app.forms import EditGMLevelForm
 | 
			
		||||
 | 
			
		||||
accounts_blueprint = Blueprint('accounts', __name__)
 | 
			
		||||
@@ -46,8 +46,10 @@ def edit_gm_level(id):
 | 
			
		||||
    form = EditGMLevelForm()
 | 
			
		||||
 | 
			
		||||
    if form.validate_on_submit():
 | 
			
		||||
        log_audit(f"Changed ({account_data.id}){account_data.username}'s GM Level from {account_data.gm_level} to {form.gm_level.data}")
 | 
			
		||||
        account_data.gm_level = form.gm_level.data
 | 
			
		||||
        account_data.save()
 | 
			
		||||
 | 
			
		||||
        return redirect(url_for('accounts.view', id=account_data.id))
 | 
			
		||||
 | 
			
		||||
    form.gm_level.data = account_data.gm_level
 | 
			
		||||
@@ -63,8 +65,10 @@ def lock(id):
 | 
			
		||||
    account.locked = not account.locked
 | 
			
		||||
    account.save()
 | 
			
		||||
    if account.locked:
 | 
			
		||||
        log_audit(f"Locked ({account.id}){account.username}")
 | 
			
		||||
        flash("Locked Account", "danger")
 | 
			
		||||
    else:
 | 
			
		||||
        log_audit(f"Unlocked ({account.id}){account.username}")
 | 
			
		||||
        flash("Unlocked account", "success")
 | 
			
		||||
    return redirect(request.referrer if request.referrer else url_for("main.index"))
 | 
			
		||||
 | 
			
		||||
@@ -77,8 +81,10 @@ def ban(id):
 | 
			
		||||
    account.banned = not account.banned
 | 
			
		||||
    account.save()
 | 
			
		||||
    if account.banned:
 | 
			
		||||
        log_audit(f"Banned ({account.id}){account.username}")
 | 
			
		||||
        flash("Banned Account", "danger")
 | 
			
		||||
    else:
 | 
			
		||||
        log_audit(f"Unbanned ({account.id}){account.username}")
 | 
			
		||||
        flash("Unbanned account", "success")
 | 
			
		||||
    return redirect(request.referrer if request.referrer else url_for("main.index"))
 | 
			
		||||
 | 
			
		||||
@@ -90,10 +96,12 @@ def mute(id, days=0):
 | 
			
		||||
    account = Account.query.filter(Account.id == id).first()
 | 
			
		||||
    if days == "0":
 | 
			
		||||
        account.mute_expire = 0
 | 
			
		||||
        log_audit(f"Unmuted ({account.id}){account.username}")
 | 
			
		||||
        flash("Unmuted Account", "success")
 | 
			
		||||
    else:
 | 
			
		||||
        muted_intil = datetime.datetime.now() + datetime.timedelta(days=int(days))
 | 
			
		||||
        account.mute_expire = muted_intil.timestamp()
 | 
			
		||||
        log_audit(f"Muted ({account.id}){account.username} for {days} days")
 | 
			
		||||
        flash(f"Muted account for {days} days", "danger")
 | 
			
		||||
    account.save()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ from datatables import ColumnDT, DataTables
 | 
			
		||||
import datetime, time
 | 
			
		||||
from app.models import CharacterInfo, CharacterXML, Account, db
 | 
			
		||||
from app.schemas import CharacterInfoSchema
 | 
			
		||||
from app import gm_level
 | 
			
		||||
from app import gm_level, log_audit
 | 
			
		||||
import xmltodict
 | 
			
		||||
 | 
			
		||||
character_blueprint = Blueprint('characters', __name__)
 | 
			
		||||
@@ -26,16 +26,19 @@ def approve_name(id, action):
 | 
			
		||||
    character =  CharacterInfo.query.filter(CharacterInfo.id == id).first()
 | 
			
		||||
 | 
			
		||||
    if action == "approve":
 | 
			
		||||
        log_audit(f"Approved ({character.id}){character.pending_name} from {character.name}")
 | 
			
		||||
        flash(
 | 
			
		||||
            f"Approved ({character.id}){character.pending_name} from {character.name}",
 | 
			
		||||
            "success"
 | 
			
		||||
        )
 | 
			
		||||
        if character.pending_name:
 | 
			
		||||
            character.name = character.pending_name
 | 
			
		||||
            character.pending_name = ""
 | 
			
		||||
        character.needs_rename = False
 | 
			
		||||
        flash(
 | 
			
		||||
            f"Approved name {character.name}",
 | 
			
		||||
            "success"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    elif action == "rename":
 | 
			
		||||
        character.needs_rename = True
 | 
			
		||||
        log_audit(f"Marked character ({character.id}){character.name} (Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename")
 | 
			
		||||
        flash(
 | 
			
		||||
            f"Marked character {character.name} (Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename",
 | 
			
		||||
            "danger"
 | 
			
		||||
@@ -146,6 +149,9 @@ def restrict(id, bit):
 | 
			
		||||
        abort(404)
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    log_audit(f"Updated ({character_data.id}){character_data.name}'s permission map to \
 | 
			
		||||
        {character_data.permission_map ^ (1 << int(bit))} from {character_data.permission_map}")
 | 
			
		||||
 | 
			
		||||
    character_data.permission_map ^= (1 << int(bit))
 | 
			
		||||
    character_data.save()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ from flask_user import login_required, current_user
 | 
			
		||||
from app.models import db, Mail, CharacterInfo
 | 
			
		||||
from datatables import ColumnDT, DataTables
 | 
			
		||||
from app.forms import SendMailForm
 | 
			
		||||
from app import gm_level
 | 
			
		||||
from app import gm_level, log_audit
 | 
			
		||||
from app.luclient import translate_from_locale, query_cdclient
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
@@ -29,6 +29,7 @@ def send():
 | 
			
		||||
        if form.attachment.data != "0" and form.attachment_count.data == 0:
 | 
			
		||||
            form.attachment_count.data = 1
 | 
			
		||||
        if form.recipient.data == "0":
 | 
			
		||||
            log_audit(f"Sending {form.subject.data}: {form.body.data} to All Characters with {form.attachment_count.data} of item {form.attachment.data}")
 | 
			
		||||
            for character in CharacterInfo.query.all():
 | 
			
		||||
                Mail(
 | 
			
		||||
                    sender_id = 0,
 | 
			
		||||
@@ -42,6 +43,7 @@ def send():
 | 
			
		||||
                    attachment_lot = form.attachment.data,
 | 
			
		||||
                    attachment_count = form.attachment_count.data
 | 
			
		||||
                ).save()
 | 
			
		||||
                log_audit(f"Sent {form.subject.data}: {form.body.data} to ({character.id}){character.name} with {form.attachment_count.data} of item {form.attachment.data}")
 | 
			
		||||
        else:
 | 
			
		||||
            Mail(
 | 
			
		||||
                sender_id = 0,
 | 
			
		||||
@@ -55,6 +57,7 @@ def send():
 | 
			
		||||
                attachment_lot = form.attachment.data,
 | 
			
		||||
                attachment_count = form.attachment_count.data
 | 
			
		||||
            ).save()
 | 
			
		||||
            log_audit(f"Sent {form.subject.data}: {form.body.data} to ({form.recipient.data}){CharacterInfo.query.filter(CharacterInfo.id == form.recipient.data).first().name} with {form.attachment_count.data} of item {form.attachment.data}")
 | 
			
		||||
 | 
			
		||||
        flash("Sent Mail", "success")
 | 
			
		||||
        return redirect(url_for('mail.send'))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
from flask_sqlalchemy import SQLAlchemy
 | 
			
		||||
from flask_migrate import Migrate
 | 
			
		||||
from flask_user import UserMixin
 | 
			
		||||
from flask_user import UserMixin, current_user
 | 
			
		||||
from wtforms import ValidationError
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
@@ -1010,3 +1010,42 @@ class Reports(db.Model):
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        db.session.delete(self)
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
class AuditLog(db.Model):
 | 
			
		||||
    __tablename__ = 'audit_logs'
 | 
			
		||||
    id = db.Column(
 | 
			
		||||
        mysql.INTEGER,
 | 
			
		||||
        primary_key=True
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    account_id = db.Column(
 | 
			
		||||
        db.Integer(),
 | 
			
		||||
        db.ForeignKey(Account.id, ondelete='CASCADE'),
 | 
			
		||||
        nullable=False,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    account = db.relationship(
 | 
			
		||||
        'Account',
 | 
			
		||||
        backref="audit_logs",
 | 
			
		||||
        passive_deletes=True
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    action = db.Column(
 | 
			
		||||
        mysql.TEXT,
 | 
			
		||||
        nullable=False
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    date = db.Column(
 | 
			
		||||
        mysql.TIMESTAMP,
 | 
			
		||||
        nullable=False,
 | 
			
		||||
        server_default=db.func.now()
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        db.session.add(self)
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
        db.session.refresh(self)
 | 
			
		||||
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        db.session.delete(self)
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ from flask_user import login_required
 | 
			
		||||
from app.models import PetNames, db
 | 
			
		||||
from datatables import ColumnDT, DataTables
 | 
			
		||||
from app.forms import CreatePlayKeyForm, EditPlayKeyForm
 | 
			
		||||
from app import gm_level
 | 
			
		||||
from app import gm_level, log_audit
 | 
			
		||||
 | 
			
		||||
moderation_blueprint = Blueprint('moderation', __name__)
 | 
			
		||||
 | 
			
		||||
@@ -23,6 +23,7 @@ def approve_pet(id):
 | 
			
		||||
    pet_data =  PetNames.query.filter(PetNames.id == id).first()
 | 
			
		||||
 | 
			
		||||
    pet_data.approved = 2
 | 
			
		||||
    log_audit(f"Approved pet name {pet_data.pet_name}")
 | 
			
		||||
    flash(f"Approved pet name {pet_data.pet_name}", "success")
 | 
			
		||||
    pet_data.save()
 | 
			
		||||
    return redirect(request.referrer if request.referrer else url_for("main.index"))
 | 
			
		||||
@@ -36,6 +37,7 @@ def reject_pet(id):
 | 
			
		||||
    pet_data =  PetNames.query.filter(PetNames.id == id).first()
 | 
			
		||||
 | 
			
		||||
    pet_data.approved = 0
 | 
			
		||||
    log_audit(f"Rejected pet name {pet_data.pet_name}")
 | 
			
		||||
    flash(f"Rejected pet name {pet_data.pet_name}", "danger")
 | 
			
		||||
    pet_data.save()
 | 
			
		||||
    return redirect(request.referrer if request.referrer else url_for("main.index"))
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ from flask_user import login_required, current_user
 | 
			
		||||
from app.models import Account, AccountInvitation, PlayKey, db
 | 
			
		||||
from datatables import ColumnDT, DataTables
 | 
			
		||||
from app.forms import CreatePlayKeyForm, EditPlayKeyForm
 | 
			
		||||
from app import gm_level
 | 
			
		||||
from app import gm_level, log_audit
 | 
			
		||||
 | 
			
		||||
play_keys_blueprint = Blueprint('play_keys', __name__)
 | 
			
		||||
 | 
			
		||||
@@ -21,6 +21,7 @@ def index():
 | 
			
		||||
@gm_level(9)
 | 
			
		||||
def create(count=1, uses=1):
 | 
			
		||||
    PlayKey.create(count=count, uses=uses)
 | 
			
		||||
    log_audit(f"Created {count} Play Key(s) with {uses} uses!")
 | 
			
		||||
    flash(f"Created {count} Play Key(s) with {uses} uses!", "success")
 | 
			
		||||
    return redirect(url_for('play_keys.index'))
 | 
			
		||||
 | 
			
		||||
@@ -32,6 +33,8 @@ def bulk_create():
 | 
			
		||||
    form = CreatePlayKeyForm()
 | 
			
		||||
    if form.validate_on_submit():
 | 
			
		||||
        PlayKey.create(count=form.count.data, uses=form.uses.data)
 | 
			
		||||
        log_audit(f"Created {form.count.data} Play Key(s) with {form.uses.data} uses!")
 | 
			
		||||
        flash(f"Created {form.count.data} Play Key(s) with {form.uses.data} uses!", "success")
 | 
			
		||||
        return redirect(url_for('play_keys.index'))
 | 
			
		||||
 | 
			
		||||
    return render_template('play_keys/bulk.html.j2', form=form)
 | 
			
		||||
@@ -43,6 +46,7 @@ def bulk_create():
 | 
			
		||||
def delete(id):
 | 
			
		||||
    key = PlayKey.query.filter(PlayKey.id == id).first()
 | 
			
		||||
    associated_accounts = Account.query.filter(Account.play_key_id==id).all()
 | 
			
		||||
    log_audit(f"Deleted Play Key {key.key_string}")
 | 
			
		||||
    flash(f"Deleted Play Key {key.key_string}", "danger")
 | 
			
		||||
    key.delete()
 | 
			
		||||
    return redirect(url_for('play_keys.index'))
 | 
			
		||||
@@ -56,10 +60,16 @@ def edit(id):
 | 
			
		||||
    form = EditPlayKeyForm()
 | 
			
		||||
 | 
			
		||||
    if form.validate_on_submit():
 | 
			
		||||
        log_audit(f"Updated Play key {key.id} \
 | 
			
		||||
            Uses: {key.key_uses}:{form.uses.data} \
 | 
			
		||||
            Active: {key.active}:{form.active.data} \
 | 
			
		||||
            Notes: {key.notes}:{form.notes.data} \
 | 
			
		||||
        ")
 | 
			
		||||
        key.key_uses = form.uses.data
 | 
			
		||||
        key.active = form.active.data
 | 
			
		||||
        key.notes = form.notes.data
 | 
			
		||||
        key.save()
 | 
			
		||||
 | 
			
		||||
        return redirect(url_for('play_keys.index'))
 | 
			
		||||
 | 
			
		||||
    form.uses.data = key.key_uses
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ from datatables import ColumnDT, DataTables
 | 
			
		||||
import time
 | 
			
		||||
from app.models import Property, db, UGC, CharacterInfo, PropertyContent, Account
 | 
			
		||||
from app.schemas import PropertySchema
 | 
			
		||||
from app import gm_level
 | 
			
		||||
from app import gm_level, log_audit
 | 
			
		||||
from app.luclient import query_cdclient
 | 
			
		||||
 | 
			
		||||
import zlib
 | 
			
		||||
@@ -50,25 +50,29 @@ def approve(id):
 | 
			
		||||
        property_data.rejection_reason = ""
 | 
			
		||||
 | 
			
		||||
    if property_data.mod_approved:
 | 
			
		||||
        flash(
 | 
			
		||||
            f"""Approved Property
 | 
			
		||||
        message = f"""Approved Property
 | 
			
		||||
            {property_data.name if property_data.name else query_cdclient(
 | 
			
		||||
                'select DisplayDescription from ZoneTable where zoneID = ?',
 | 
			
		||||
                [property_data.zone_id],
 | 
			
		||||
                one=True
 | 
			
		||||
            )[0]}
 | 
			
		||||
            from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}""",
 | 
			
		||||
            from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}"""
 | 
			
		||||
        log_audit(message)
 | 
			
		||||
        flash(
 | 
			
		||||
            message,
 | 
			
		||||
            "success"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        flash(
 | 
			
		||||
            f"""Unapproved Property
 | 
			
		||||
        message = f"""Unapproved Property
 | 
			
		||||
            {property_data.name if property_data.name else query_cdclient(
 | 
			
		||||
                'select DisplayDescription from ZoneTable where zoneID = ?',
 | 
			
		||||
                [property_data.zone_id],
 | 
			
		||||
                one=True
 | 
			
		||||
            )[0]}
 | 
			
		||||
            from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}""",
 | 
			
		||||
            from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}"""
 | 
			
		||||
        log_audit(message)
 | 
			
		||||
        flash(
 | 
			
		||||
            message,
 | 
			
		||||
            "danger"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								migrations/versions/3132aaef7413_fix_nullables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								migrations/versions/3132aaef7413_fix_nullables.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
"""fix nullables
 | 
			
		||||
 | 
			
		||||
Revision ID: 3132aaef7413
 | 
			
		||||
Revises: bd908969d8fe
 | 
			
		||||
Create Date: 2022-02-11 21:51:58.479066
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from alembic import op
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
from sqlalchemy.dialects import mysql
 | 
			
		||||
 | 
			
		||||
# revision identifiers, used by Alembic.
 | 
			
		||||
revision = '3132aaef7413'
 | 
			
		||||
down_revision = 'bd908969d8fe'
 | 
			
		||||
branch_labels = None
 | 
			
		||||
depends_on = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upgrade():
 | 
			
		||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
			
		||||
    op.alter_column('audit_logs', 'account_id',
 | 
			
		||||
               existing_type=mysql.INTEGER(display_width=11),
 | 
			
		||||
               nullable=False)
 | 
			
		||||
    op.alter_column('audit_logs', 'action',
 | 
			
		||||
               existing_type=mysql.TEXT(),
 | 
			
		||||
               nullable=False)
 | 
			
		||||
    # ### end Alembic commands ###
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def downgrade():
 | 
			
		||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
			
		||||
    op.alter_column('audit_logs', 'action',
 | 
			
		||||
               existing_type=mysql.TEXT(),
 | 
			
		||||
               nullable=True)
 | 
			
		||||
    op.alter_column('audit_logs', 'account_id',
 | 
			
		||||
               existing_type=mysql.INTEGER(display_width=11),
 | 
			
		||||
               nullable=True)
 | 
			
		||||
    # ### end Alembic commands ###
 | 
			
		||||
							
								
								
									
										35
									
								
								migrations/versions/bd908969d8fe_add_audit_log_table.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								migrations/versions/bd908969d8fe_add_audit_log_table.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
"""Add audit_log table
 | 
			
		||||
 | 
			
		||||
Revision ID: bd908969d8fe
 | 
			
		||||
Revises: aee4c6c24811
 | 
			
		||||
Create Date: 2022-02-11 21:48:03.798474
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from alembic import op
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
from sqlalchemy.dialects import mysql
 | 
			
		||||
 | 
			
		||||
# revision identifiers, used by Alembic.
 | 
			
		||||
revision = 'bd908969d8fe'
 | 
			
		||||
down_revision = 'aee4c6c24811'
 | 
			
		||||
branch_labels = None
 | 
			
		||||
depends_on = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upgrade():
 | 
			
		||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
			
		||||
    op.create_table('audit_logs',
 | 
			
		||||
    sa.Column('id', mysql.INTEGER(), nullable=False),
 | 
			
		||||
    sa.Column('account_id', sa.Integer(), nullable=True),
 | 
			
		||||
    sa.Column('action', mysql.TEXT(), nullable=True),
 | 
			
		||||
    sa.Column('date', mysql.TIMESTAMP(), server_default=sa.text('now()'), nullable=False),
 | 
			
		||||
    sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], ondelete='CASCADE'),
 | 
			
		||||
    sa.PrimaryKeyConstraint('id')
 | 
			
		||||
    )
 | 
			
		||||
    # ### end Alembic commands ###
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def downgrade():
 | 
			
		||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
			
		||||
    op.drop_table('audit_logs')
 | 
			
		||||
    # ### end Alembic commands ###
 | 
			
		||||
		Reference in New Issue
	
	Block a user