This commit is contained in:
copyrighttxt
2026-07-06 13:28:00 -04:00
commit 05211f73ef
441 changed files with 94128 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 800px;">
<a class="mb-2" href="/admin/manage-users/{{userObj.id}}"> < Return to User Page</a>
<div class="d-flex align-items-center">
<img height="100px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{userObj.id}}&x=100&y=100">
<div>
<h1 class="m-0 text-secondary">Ban <span class="text-white">{{userObj.username}}</span></h1>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{userObj.id}}</span></p>
</div>
</div>
{% if LastestUserBanObj != None: %}
<div class="alert alert-danger">
This user currently has a ongoing ban, please revoke the previous ban before banning again.
</div>
{% endif %}
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{% for message in messages %}
<div class="alert border border-danger p-2 text-center messagealerts">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="linebreak"></div>
<form method="post">
<div class="form-floating">
<select name="ban_type" class="form-control" id="ban-type-select">
<option value="0" selected>Warning</option>
<option value="1">1 Day Ban</option>
<option value="2">3 Day Ban</option>
<option value="3">7 Day Ban</option>
<option value="4">14 Day Ban</option>
<option value="5">30 Day Ban</option>
<option value="6">Account Deleted</option>
</select>
<label for="ban-type-select">Ban Type</label>
</div>
<div class="form-floating mt-2">
<input type="text" class="form-control" name="ban_reason" id="ban-reason-input" placeholder="Reason" required>
<label for="ban-reason-input">Reason</label>
</div>
<div class="form-floating mt-2">
<input type="text" class="form-control" name="ban_notes" id="ban-notes-input" placeholder="Notes" required>
<label for="ban-notes-input">Moderator Note ( Internal )</label>
</div>
<p class="m-0 text-secondary" style="font-size: 13px;">Please try to include links to evidence that lead to this ban</p>
<div class="form-floating mt-2">
<input type="password" class="form-control" name="totp_code" id="totp_code-input" placeholder="2FA Code" required>
<label for="ban-notes-public-input">2FA Code</label>
</div>
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
<div class="linebreak"></div>
{% if not LastestUserBanObj != None: %}
<button type="submit" class="btn btn-danger mt-2 w-100 btn-sm">Ban User</button>
{% else%}
<button class="btn btn-danger mt-2 w-100 btn-sm" disabled>Ban User</button>
{% endif %}
</form>
{% if LastestUserBanObj != None: %}
<form method="post" action="/admin/manage-users/{{userObj.id}}/ban-user/revoke-ban?banid={{LastestUserBanObj.id}}">
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
<button class="btn btn-warning mt-2 w-100 btn-sm">Unban User</button>
</form>
{%endif%}
</div>
</div>
{% endblock %}
@@ -0,0 +1,69 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
.bg-dark {
background-color: rgb(32, 32, 32) !important;
box-shadow: 0px 0px 5px 0px rgb(0,0,0);
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 800px;">
<a class="mb-2" href="/admin/manage-users/{{userObj.id}}"> < Return to User Page</a>
<div class="d-flex align-items-center">
<img height="100px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{userObj.id}}&x=100&y=100">
<div>
<h1 class="m-0 {% if userObj.accountstatus == 1 %}text-white{% elif userObj.accountstatus == 2 %}text-warning{% elif userObj.accountstatus == 3%}text-danger{% else %}text-decoration-line-through text-white{%endif%}">{{userObj.username}}<span class="text-secondary"> Ban History</span></h1>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{userObj.id}}</span></p>
</div>
</div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{% for message in messages %}
<div class="alert border border-danger p-2 text-center messagealerts">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="p-1">
{% for banRecord in BanHistory %}
<div class="w-100 bg-dark p-2 mb-2 mt-2">
<h4 class="m-0">{{banRecord.ban_type.name}}</h4>
<p class="m-0 text-secondary" style="font-size: 14px;">Reason: <span class="text-white">{{banRecord.reason}}</span></p>
<p class="m-0 text-secondary" style="font-size: 14px;">Moderator Note: <span class="text-white">{{banRecord.moderator_note}}</span></p>
<p class="m-0 text-secondary" style="font-size: 12px;">Ban Author: <a href="/admin/manage-users/{{banRecord.author_userid}}">{{GetBanAuthorName(banRecord)}}</a></p>
<div class="linebreak"></div>
<div class="d-flex">
<p class="m-0 text-secondary me-auto ms-auto" style="font-size: 14px;">Banned At: <span class="text-white">{{banRecord.created_at}}</span></p>
<p class="m-0 text-secondary me-auto ms-auto" style="font-size: 14px;">Expires At: <span class="text-white">{{banRecord.expires_at}}</span></p>
<p class="m-0 text-secondary me-auto ms-auto" style="font-size: 14px;">Acknowledged: <span class="text-white">{{banRecord.acknowledged}}</span></p>
</div>
</div>
{% endfor %}
{% if len(BanHistory.items) == 0 %}
<p class="text-secondary m-5 text-center w-100">No previous bans</p>
{% endif %}
</div>
<div class="align-items-center d-flex justify-content-center mt-2 mb-2">
<a class="ms-auto m-0 text-decoration-none {% if not BanHistory.has_prev %}text-secondary{%endif%}" {% if BanHistory.has_prev %}href="/admin/manage-users/{{userObj.id}}/ban-history?page={{BanHistory.prev_num}}"{%endif%}>Previous</a>
<p class="ms-2 me-2 text-white m-0">Page {{BanHistory.page}} of {{BanHistory.pages}}</p>
<a class="me-auto m-0 text-decoration-none {% if not BanHistory.has_next %}text-secondary{%endif%}" {% if BanHistory.has_next %}href="/admin/manage-users/{{userObj.id}}/ban-history?page={{BanHistory.next_num}}"{%endif%}>Next</a>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,65 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
.bg-dark {
background-color: rgb(32, 32, 32) !important;
box-shadow: 0px 0px 5px 0px rgb(0,0,0);
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 1200px;">
<a class="mb-2" href="/admin/manage-users/{{userObj.id}}"> < Return to User Page</a>
<div class="d-flex align-items-center">
<img height="100px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{userObj.id}}&x=100&y=100">
<div>
<h1 class="m-0 {% if userObj.accountstatus == 1 %}text-white{% elif userObj.accountstatus == 2 %}text-warning{% elif userObj.accountstatus == 3%}text-danger{% else %}text-decoration-line-through text-white{%endif%}">{{userObj.username}}<span class="text-secondary"> Game Sessions</span></h1>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{userObj.id}}</span></p>
</div>
</div>
<table class="table table-dark table-striped">
<thead>
<tr class="rounded-top">
<th scope="col" style="width: 5%;">ID</th>
<th scope="col" style="width: 27.5%;">JobId</th>
<th scope="col" style="width: 22.5%;">Joined At</th>
<th scope="col" style="width: 22.5%;">Left At</th>
<th scope="col" style="width: 22.5%;">Place</th>
</tr>
</thead>
<tbody class="table-group-divider" style="border-color: rgb(20,20,20);">
{% for session in GameSessions: %}
<tr>
<th scope="row">{{session.id}}</th>
<td>{{session.serveruuid}}</td>
<td>{{session.joined_at}} UTC</td>
<td>{{session.left_at}} UTC</td>
<td><a class="text-primary" href="/games/{{session.place_id}}/--">{{get_place_name(session.place_id)}}</a> ({{session.place_id}})</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if len(GameSessions.items) == 0: %}
<div class="text-center">
<p class="text-secondary">No Game Session Logs Found</p>
</div>
{%endif%}
<div class="align-items-center d-flex justify-content-center mt-2 mb-2">
<a class="ms-auto m-0 text-decoration-none {% if not GameSessions.has_prev %}text-secondary{%endif%}" {% if GameSessions.has_prev %}href="/admin/manage-users/{{userObj.id}}/game-sessions?page={{GameSessions.prev_num}}"{%endif%}>Previous</a>
<p class="ms-2 me-2 text-white m-0">Page {{GameSessions.page}} of {{GameSessions.pages}}</p>
<a class="me-auto m-0 text-decoration-none {% if not GameSessions.has_next %}text-secondary{%endif%}" {% if GameSessions.has_next %}href="/admin/manage-users/{{userObj.id}}/game-sessions?page={{GameSessions.next_num}}"{%endif%}>Next</a>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,74 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
.bg-dark {
background-color: rgb(32, 32, 32) !important;
box-shadow: 0px 0px 5px 0px rgb(0,0,0);
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 1200px;">
<a class="mb-2" href="/admin/manage-users/{{userObj.id}}"> < Return to User Page</a>
<div class="d-flex align-items-center">
<img height="100px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{userObj.id}}&x=100&y=100">
<div>
<h1 class="m-0 {% if userObj.accountstatus == 1 %}text-white{% elif userObj.accountstatus == 2 %}text-warning{% elif userObj.accountstatus == 3%}text-danger{% else %}text-decoration-line-through text-white{%endif%}">{{userObj.username}}<span class="text-secondary"> Invite Keys</span></h1>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{userObj.id}}</span></p>
</div>
</div>
<table class="table table-dark table-striped">
<thead>
<tr class="rounded-top">
<th scope="col" style="width: 5%;">ID</th>
<th scope="col" style="width: 20%;">Key</th>
<th scope="col" style="width: 15%;">Created</th>
<th scope="col" style="width: 15%;">Used On</th>
<th scope="col" style="width: 45%;">Used By</th>
</tr>
</thead>
<tbody class="table-group-divider" style="border-color: rgb(20,20,20);">
{% for invite in InviteKeys: %}
<tr>
<th scope="row">{{invite.id}}</th>
<td>{{invite.key}}</td>
<td>{{invite.created_at}} UTC</td>
<td>{% if invite.used_on != None: %}{{invite.used_on}} UTC{%else%}Unused{%endif%}</td>
<td>
{% if invite.used_by != None: %}
<a href="/admin/manage-users/{{invite.used_by}}" class="text-decoration-none text-truncate">
<img class="rounded" style="aspect-ratio: 1/1;width: 35px;" src="/Thumbs/Head.ashx?x=48&y=48&userId={{invite.used_by}}">
{{invite.user.username}}
</a>
{% else%}
Unused
{%endif%}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if len(InviteKeys.items) == 0: %}
<div class="text-center">
<p class="text-secondary">No Invite Keys found</p>
</div>
{%endif%}
<div class="align-items-center d-flex justify-content-center mt-2 mb-2">
<a class="ms-auto m-0 text-decoration-none {% if not InviteKeys.has_prev %}text-secondary{%endif%}" {% if InviteKeys.has_prev %}href="/admin/manage-users/{{userObj.id}}/invite-keys?page={{InviteKeys.prev_num}}"{%endif%}>Previous</a>
<p class="ms-2 me-2 text-white m-0">Page {{InviteKeys.page}} of {{InviteKeys.pages}}</p>
<a class="me-auto m-0 text-decoration-none {% if not InviteKeys.has_next %}text-secondary{%endif%}" {% if InviteKeys.has_next %}href="/admin/manage-users/{{userObj.id}}/invite-keys?page={{InviteKeys.next_num}}"{%endif%}>Next</a>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,105 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
.bg-dark {
background-color: rgb(32, 32, 32) !important;
box-shadow: 0px 0px 5px 0px rgb(0,0,0);
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 800px;">
<a class="mb-2" href="/admin/manage-users/{{userObj.id}}"> < Return to User Page</a>
<div class="d-flex align-items-center">
<img height="100px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{userObj.id}}&x=100&y=100">
<div>
<h1 class="m-0 {% if userObj.accountstatus == 1 %}text-white{% elif userObj.accountstatus == 2 %}text-warning{% elif userObj.accountstatus == 3%}text-danger{% else %}text-decoration-line-through text-white{%endif%}">{{userObj.username}}<span class="text-secondary"> Login History</span></h1>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{userObj.id}}</span></p>
</div>
</div>
{% if len(AlternateAccounts) != 0 %}
<div class="border-top border-bottom p-2 ">
<h5 class="m-0 mb-1">Possible Alt Accounts ( {{len( AlternateAccounts )}} )</h5>
{% for altAccount in AlternateAccounts %}
<a class="d-flex align-items-center text-white text-decoration-none" href="/admin/manage-users/{{altAccount.id}}/login-history">
<img height="50px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{altAccount.id}}&x=50&y=50">
<div>
<h5 class="m-0 {% if altAccount.accountstatus == 1 %}text-white{% elif altAccount.accountstatus == 2 %}text-warning{% elif altAccount.accountstatus == 3%}text-danger{% else %}text-decoration-line-through text-white{%endif%}">
{{altAccount.username}}
{% if altAccount.flags['hwidmatch'] %}
<span class="badge rounded-pill text-bg-light ms-1" style="font-size: 12px;font-weight: 400;">HWID</span>
{% endif %}
{% if altAccount.flags['sessiontokenmatch'] %}
<span class="badge rounded-pill text-bg-danger ms-1" style="font-size: 12px;font-weight: 400;">SessionToken</span>
{% endif %}
{% if altAccount.flags['ipmatch'] %}
<span class="badge rounded-pill text-bg-warning ms-1" style="font-size: 12px;font-weight: 400;">IPAddress</span>
{% endif %}
{% if altAccount.flags['useragentmatch'] %}
<span class="badge rounded-pill text-bg-primary ms-1" style="font-size: 12px;font-weight: 400;">UserAgent</span>
{% endif %}
</h5>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{altAccount.id}}</span></p>
</div>
</a>
{%endfor%}
</div>
{% endif %}
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{% for message in messages %}
<div class="alert border border-danger p-2 text-center messagealerts">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% if canViewSensitiveInfo %}
<div class="p-1">
{% for loginRecord in LoginHistory %}
<div class="w-100 bg-dark p-2 mb-2 mt-2">
<div class="d-flex">
<div>
<h5 class="m-0">{{loginRecord.ip}}</h5>
<p class="m-0 text-secondary" style="font-size: 13px;">Timestamp: <span class="text-white">{{loginRecord.timestamp}} UTC</span></p>
</div>
<a data-bs-toggle="collapse" href="#additional-record-data-{{loginRecord.id}}" role="button" class="ms-auto mt-auto mb-auto me-2" style="font-size: 20px;"><i class="bi bi-info-circle"></i></a>
</div>
<div class="collapse mt-1" id="additional-record-data-{{loginRecord.id}}">
<div class="border p-1">
<p class="m-0 text-secondary" style="font-size: 13px;">Login Record ID: <span class="text-white">{{loginRecord.id}}</span></p>
<div class="border mt-1" style="font-size: 13px;padding: 4px;">
<p class="m-0 text-secondary">User Agent</p>
<p class="text-white m-0" style="word-break: break-all;white-space: normal;">{{loginRecord.useragent}}</p>
</div>
<div class="border mt-1" style="font-size: 13px;padding: 4px;">
<p class="m-0 text-secondary">Session Token</p>
<p class="text-white m-0" style="word-break: break-all;white-space: normal;">{{loginRecord.session_token}}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="align-items-center d-flex justify-content-center mt-2 mb-2">
<a class="ms-auto m-0 text-decoration-none {% if not LoginHistory.has_prev %}text-secondary{%endif%}" {% if LoginHistory.has_prev %}href="/admin/manage-users/{{userObj.id}}/login-history?page={{LoginHistory.prev_num}}"{%endif%}>Previous</a>
<p class="ms-2 me-2 text-white m-0">Page {{LoginHistory.page}} of {{LoginHistory.pages}}</p>
<a class="me-auto m-0 text-decoration-none {% if not LoginHistory.has_next %}text-secondary{%endif%}" {% if LoginHistory.has_next %}href="/admin/manage-users/{{userObj.id}}/login-history?page={{LoginHistory.next_num}}"{%endif%}>Next</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}
@@ -0,0 +1,208 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
.bg-dark {
background-color: rgb(32, 32, 32) !important;
box-shadow: 0px 0px 5px 0px rgb(0,0,0);
}
</style>
{% endblock %}
{% block content %}
<div id="main" data-csrf-token="{{ csrf_token() }}" data-userid="{{userObj.id}}">
<div class="container" style="max-width: 800px;">
<a class="mb-2" href="/admin/manage-users/{{userObj.id}}"> < Return to User Page</a>
<div class="d-flex align-items-center">
<img height="100px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{userObj.id}}&x=100&y=100">
<div>
<h1 class="m-0 {% if userObj.accountstatus == 1 %}text-white{% elif userObj.accountstatus == 2 %}text-warning{% elif userObj.accountstatus == 3%}text-danger{% else %}text-decoration-line-through text-white{%endif%}">{{userObj.username}}<span class="text-secondary"> Admin Permissions</span></h1>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{userObj.id}}</span></p>
</div>
</div>
<div class="border rounded p-2 mt-2 mb-2">
<p class="mt-5 mb-5 w-100 text-center text-white" id="status-text" style="display: none;"></p>
<div id="data-container">
<div class="w-100 bg-dark p-2 mb-2 mt-2 admin-permission-template" style="display: none;">
<div class="d-flex align-items-center w-100">
<a data-bs-toggle="collapse" href="#admin-permission-info-PermissionName" role="button" class="mt-auto mb-auto me-2 permission-expand-info" style="font-size: 20px;"><i class="bi bi-info-circle"></i></a>
<div class="d-flex align-items-center">
<h5 class="m-0 permission-name-text">Admin Permission name</h5>
<i class="bi ms-2 permission-icon" style="display: none;"></i>
</div>
<div class="form-check form-switch m-0 me-2 ms-auto">
<input class="form-check-input" type="checkbox" role="switch">
</div>
</div>
<div class="collapse mt-1 permission-info-container" id="admin-permission-info-PermissionName">
<div class="border p-1">
<p class="m-0 text-secondary" style="font-size: 13px;">Description: <span class="text-white permission-description-text">Permission Description</span></p>
</div>
</div>
</div>
</div>
</div>
<input type="password" class="form-control mb-2" placeholder="2FA Code" id="2fa-code-input">
<input type="submit" class="btn btn-primary w-100 mb-2" value="Save Changes" id="save-changes-button">
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", async function () {
const ActionCSRFToken = document.getElementById("main").getAttribute("data-csrf-token");
const userId = document.getElementById("main").getAttribute("data-userid");
const statusText = document.getElementById("status-text");
const dataContainer = document.getElementById("data-container");
const permissionTemplate = document.getElementsByClassName("admin-permission-template")[0];
const saveChangesButton = document.getElementById("save-changes-button");
const twoFactorCodeInput = document.getElementById("2fa-code-input");
var isPermissionsLoaded = false;
async function setStatusText( new_text ) {
if ( new_text ) {
statusText.innerText = new_text;
statusText.style.display = "block";
} else {
statusText.style.display = "none";
}
}
async function fetchUserPermissions() {
const response = await fetch(`/admin/manage-users/${userId}/manage-admin-permissions/api/fetch-permissions`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": ActionCSRFToken
}
});
const data = await response.json();
return data;
}
async function loadPermission( permission_data ) {
const newPermission = permissionTemplate.cloneNode(true);
newPermission.style.display = "block";
newPermission.classList.remove("admin-permission-template");
newPermission.classList.add("admin-permission-container");
newPermission.setAttribute("data-permission-internal-name", permission_data.internal_name);
newPermission.getElementsByClassName("permission-name-text")[0].innerText = permission_data.friendly_name;
newPermission.getElementsByClassName("permission-description-text")[0].innerText = permission_data.description;
newPermission.getElementsByClassName("form-check-input")[0].checked = permission_data.hasPermission;
newPermission.getElementsByClassName("permission-expand-info")[0].setAttribute("href", `#admin-permission-info-${permission_data.internal_name}`);
newPermission.getElementsByClassName("permission-info-container")[0].setAttribute("id", `admin-permission-info-${permission_data.internal_name}`);
if ( permission_data.bi_icon ) {
newPermission.getElementsByClassName("permission-icon")[0].style.display = "inline-block";
newPermission.getElementsByClassName("permission-icon")[0].classList.add(permission_data.bi_icon);
}
dataContainer.appendChild(newPermission);
}
async function refreshData( new_data ) {
dataContainer.innerHTML = "";
for ( const permission of new_data ) {
await loadPermission( permission );
}
}
async function attemptFetchAndLoad() {
dataContainer.innerHTML = "";
isPermissionsLoaded = false;
saveChangesButton.disabled = true;
twoFactorCodeInput.disabled = true;
setStatusText("Fetching Admin Permissions")
try{
const permissionsData = await fetchUserPermissions();
await refreshData( permissionsData );
setStatusText();
isPermissionsLoaded = true;
twoFactorCodeInput.disabled = false;
} catch (e) {
setStatusText("Failed to fetch Admin Permissions");
console.error(e);
}
}
twoFactorCodeInput.addEventListener("input", async function () {
if ( !isPermissionsLoaded ) {
return;
}
if ( twoFactorCodeInput.value.length != 6 ) {
saveChangesButton.disabled = true;
}
if ( twoFactorCodeInput.value.length == 6 ) {
saveChangesButton.disabled = false;
}
});
saveChangesButton.addEventListener("click", async function() {
if ( !isPermissionsLoaded ) {
return;
}
if ( twoFactorCodeInput.value.length != 6 ) {
return;
}
isPermissionsLoaded = false;
saveChangesButton.disabled = true;
const permissionsData = [];
const permissionContainers = document.getElementsByClassName("admin-permission-container");
for ( const permissionContainer of permissionContainers ) {
if ( permissionContainer.getElementsByClassName("form-check-input")[0].checked ) {
permissionsData.push(permissionContainer.getAttribute("data-permission-internal-name"));
}
}
setStatusText("Saving Admin Permissions");
try {
const response = await fetch(`/admin/manage-users/${userId}/manage-admin-permissions/api/set-permissions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": ActionCSRFToken
},
body: JSON.stringify({
"permissions": permissionsData,
"2fa_code": twoFactorCodeInput.value
})
});
const data = await response.json();
if ( data.success ) {
alert("Successfully saved Admin Permissions");
twoFactorCodeInput.value = "";
await attemptFetchAndLoad();
return;
} else {
alert(`Failed to save Admin Permissions, reason: ${data.reason}`);
}
} catch (e) {
alert("Failed to save Admin Permissions");
console.error(e);
}
isPermissionsLoaded = true;
saveChangesButton.disabled = false;
twoFactorCodeInput.value = "";
twoFactorCodeInput.disabled = false;
})
await attemptFetchAndLoad();
})
</script>
{% endblock %}
@@ -0,0 +1,94 @@
{% extends '__layout__.html' %}
{% block title %}Manage User{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/>
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 800px;">
<a class="mb-2 d-block" href="/admin/manage-users/{{userObj.id}}">← Return to User Page</a>
<div class="d-flex align-items-center mb-3">
<img height="100px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{userObj.id}}&x=100&y=100">
<div class="ms-3">
<h1 class="m-0 text-secondary">Manage Robux & Builders Club for <span class="text-white">{{userObj.username}}</span></h1>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{userObj.id}}</span></p>
</div>
</div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{% for message in messages %}
<div class="alert border border-danger p-2 text-center messagealerts">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="linebreak"></div>
<div class="robux-management">
<h3>Robux & Tix Management</h3>
<form method="POST" id="robux-form">
<div class="form-floating">
<select class="form-control" id="action-select" required>
<option value="add">Add</option>
<option value="subtract">Subtract</option>
</select>
<label for="action-select">Action</label>
</div>
<div class="form-floating mt-2">
<select class="form-control" name="currency_type" id="currency-type-select" required>
<option value="robux">Robux</option>
<option value="tix">Tix</option>
</select>
<label for="currency-type-select">Currency</label>
</div>
<div class="form-floating mt-2">
<input type="number" class="form-control" name="amount" id="amount-input" placeholder="Amount" required>
<label for="amount-input">Amount</label>
</div>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="linebreak"></div>
<button type="submit" class="btn btn-primary mt-2 w-100 btn-sm">Submit Currency Change</button>
</form>
</div>
<div class="linebreak"></div>
</div>
</div>
{% block scripts %}
<script>
const actionSelect = document.getElementById("action-select");
const amountInput = document.getElementById("amount-input");
const robuxForm = document.getElementById("robux-form");
robuxForm.addEventListener("submit", function(event) {
if (actionSelect.value === "subtract") {
amountInput.value = -Math.abs(amountInput.value);
} else {
amountInput.value = Math.abs(amountInput.value);
}
});
</script>
{% endblock %}
{% endblock %}
@@ -0,0 +1,61 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
.bg-dark {
background-color: rgb(32, 32, 32) !important;
box-shadow: 0px 0px 5px 0px rgb(0,0,0);
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 800px;">
<a class="mb-2" href="/admin/manage-users/{{userObj.id}}"> < Return to User Page</a>
<div class="d-flex align-items-center">
<img height="100px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{userObj.id}}&x=100&y=100">
<div>
<h1 class="m-0 {% if userObj.accountstatus == 1 %}text-white{% elif userObj.accountstatus == 2 %}text-warning{% elif userObj.accountstatus == 3%}text-danger{% else %}text-decoration-line-through text-white{%endif%}">{{userObj.username}}<span class="text-secondary"> Moderator Notes</span></h1>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{userObj.id}}</span></p>
</div>
</div>
<div>
{% for note in UserModeratorNotes.items %}
<div class="rounded border bg-dark p-2 mb-1">
<div class="d-flex align-items-center">
<img height="40px" style="aspect-ratio: 1/1;" class="rounded-2 border" src="/Thumbs/Head.ashx?userId={{note.note_creator_id}}&x=50&y=50">
<div class="ms-1">
<h5 class="m-0 text-white">{{ GetUserName(note.note_creator_id) }}</h5>
<p class="m-0" style="font-size: 12px;color: rgb(200,200,200);">Created at {{note.created_at}} UTC</p>
</div>
</div>
<div class="p-1 border w-100 mt-1">
{% for note_line in note.note.split("\n") %}
<p class="m-0 text-white">{{note_line}}</p>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% if len(UserModeratorNotes.items) == 0 %}
<div class="alert alert-danger">
This user currently has no moderator notes.
</div>
{% endif %}
<div class="align-items-center d-flex justify-content-center mt-2 mb-2">
<a class="ms-auto m-0 text-decoration-none {% if not UserModeratorNotes.has_prev %}text-secondary{%endif%}" {% if UserModeratorNotes.has_prev %}href="/admin/manage-users/{{userObj.id}}/moderator-notes?page={{UserModeratorNotes.prev_num}}"{%endif%}>Previous</a>
<p class="ms-2 me-2 text-white m-0">Page {{UserModeratorNotes.page}} of {{UserModeratorNotes.pages}}</p>
<a class="me-auto m-0 text-decoration-none {% if not UserModeratorNotes.has_next %}text-secondary{%endif%}" {% if UserModeratorNotes.has_next %}href="/admin/manage-users/{{userObj.id}}/moderator-notes?page={{UserModeratorNotes.next_num}}"{%endif%}>Next</a>
</div>
</div>
</div>
{% endblock %}
+82
View File
@@ -0,0 +1,82 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="min-width: 1100px;">
<a href="/admin" class="btn border-primary btn-sm mb-2">Back to Admin Panel</a>
<h1>User Manager</h1>
<form method="post">
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
<div class="input-group mb-3">
<div class="form-floating" style="width: 52%;">
<input type="text" class="form-control" id="user-search-input" name="user-search-input" value="{%if query != None: %}{{query}}{%endif%}" placeholder="ROBLOX">
<label for="user-search-input">Search</label>
</div>
<div class="form-floating" style="min-width: 130px;">
<select class="form-select" id="user-search-type" name="user-search-type">
<option value="userid">UserID</option>
<option value="username" {%if searchType == "username"%}selected{%endif%}>Username</option>
<option value="discordid" {%if searchType == "discordid"%}selected{%endif%}>Discord ID</option>
</select>
<label for="user-search-type">Search With</label>
</div>
<div class="form-floating" style="min-width: 160px;">
<select class="form-select" id="user-order-by" name="user-order-by">
<option value="userid">UserID</option>
<option value="creation" {%if orderBy == "creation"%}selected{%endif%}>Creation Date</option>
<option value="lastonline" {%if orderBy == "lastonline"%}selected{%endif%}>Last Online</option>
<option value="robux" {%if orderBy == "robux"%}selected{%endif%}>Robux Balance</option>
<option value="tix" {%if orderBy == "tix"%}selected{%endif%}>Tickets Balance</option>
</select>
<label for="user-order-by">Order By</label>
</div>
<div class="form-floating" style="min-width: 130px;">
<select class="form-select" id="user-order-direction" name="user-order-direction">
<option value="asc">Ascending</option>
<option value="desc" {%if orderType == "desc"%}selected{%endif%}>Descending</option>
</select>
<label for="user-order-direction">Order Direction</label>
</div>
<input type="submit" class="btn btn-outline-primary fw-bold" value="🔍">
</div>
</form>
<table class="table table-dark table-striped">
<thead>
<tr class="rounded-top">
<th scope="col">ID</th>
<th scope="col">Username</th>
<th scope="col">Created</th>
<th scope="col">Last Online</th>
<th scope="col">Robux</th>
<th scope="col">Tickets</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody class="table-group-divider" style="border-color: rgb(20,20,20);">
{% for user in returnList: %}
<tr>
<td>{{user.id}}</td>
<td><span class="{% if user.accountstatus == 2%}text-warning{% endif %}{% if user.accountstatus > 2 %}text-danger{%endif%}">{{user.username}}</span></td>
<td>{{user.creation}}</td>
<td>{{user.lastonline}}</td>
<td>{{user.robux}}</td>
<td>{{user.tix}}</td>
<td><a href="/admin/manage-users/{{user.id}}" class="text-decoration-none btn btn-primary btn-sm w-100">View</a></td>
</tr>
{%endfor%}
</tbody>
</table>
{% if len(returnList) == 0: %}
<p class="text-white w-100 text-center">No results found</p>
{%endif%}
<div class="mt-3 d-flex">
<a class="text-decoration-none ms-auto {%if page <= 1: %}disabled text-secondary{%else%}text-primary{%endif%}" {% if page > 1:%}href="/admin/manage-users?searchType={{searchType}}&orderBy={{orderBy}}&page={{page-1}}&orderType={{orderType}}{%if query != None: %}&query={{query}}{%endif%}"{%endif%}>Previous</a>
<p class="ms-2 me-2 text-white">Page {{page}}</p>
<a class="text-decoration-none me-auto {%if not isThereNextPage: %}disabled text-secondary{%else%}text-primary{%endif%}" {%if isThereNextPage: %}href="/admin/manage-users?searchType={{searchType}}&orderBy={{orderBy}}&page={{page+1}}&orderType={{orderType}}{%if query != None: %}&query={{query}}{%endif%}"{%endif%}>Next</a>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,116 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
.bg-dark {
background-color: rgb(32, 32, 32) !important;
box-shadow: 0px 0px 5px 0px rgb(0,0,0);
}
.text-secondary {
color: rgb(199, 199, 199) !important;
}
.text-tickets {
color: rgb(224, 224, 60) !important;
font-weight: 600;
}
.text-robux {
color: rgb(26, 212, 103) !important;
font-weight: 600;
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 1000px;">
<a class="mb-2" href="/admin/manage-users/{{userObj.id}}"> < Return to User Page</a>
<div class="d-flex align-items-center">
<img height="100px" style="aspect-ratio: 1/1;" class="rounded-2" src="/Thumbs/Avatar.ashx?userId={{userObj.id}}&x=100&y=100">
<div>
<h1 class="m-0 {% if userObj.accountstatus == 1 %}text-white{% elif userObj.accountstatus == 2 %}text-warning{% elif userObj.accountstatus == 3%}text-danger{% else %}text-decoration-line-through text-white{%endif%}">{{userObj.username}}<span class="text-secondary"> Transactions</span></h1>
<p class="text-secondary m-0" style="font-size: 12px;">UserId: <span class="text-white">{{userObj.id}}</span></p>
</div>
</div>
<div class="form-floating mb-2">
<select class="form-control" id="change-category-select">
<option value="purchase" {%if PageCategory == "purchase"%}selected{%endif%}>Purchases</option>
<option value="sale" {%if PageCategory == "sale"%}selected{%endif%}>Sales</option>
<option value="group-payout" {%if PageCategory == "group-payout"%}selected{%endif%}>Group Payouts</option>
<option value="stipends" {%if PageCategory == "stipends"%}selected{%endif%}>Stipends</option>
</select>
<label for="change-category-select">Category</label>
</div>
<table class="table table-dark table-striped">
<thead>
<tr class="rounded-top">
<th scope="col" style="width: 15%;">Date</th>
<th scope="col" style="width: 20%;">Source</th>
<th scope="col" style="width: 45%;">Description</th>
<th scope="col" style="width: 20%;">Amount</th>
</tr>
</thead>
<tbody class="table-group-divider" style="border-color: rgb(20,20,20);">
{% for Transaction in TransactionInfo: %}
<tr>
<td>{{Transaction.created_at}}</td>
<td>
{% if Transaction["source"]["type"] == 0: %}
<a href="/users/{{Transaction['source']['id']}}/profile" class="text-decoration-none text-white text-truncate">
<img class="rounded-5 overflow-hidden me-2" width="38" height="38" src="/Thumbs/Head.ashx?x=48&amp;y=48&amp;userId={{Transaction['source']['id']}}" alt="{{Transaction['source']['name']}}">
{{Transaction["source"]["name"]}}
</a>
{% else %}
<a href="/groups/{{Transaction['source']['id']}}/--" class="text-decoration-none text-white text-truncate">
<img class="rounded-5 overflow-hidden me-2" width="38" height="38" src="/Thumbs/GroupIcon.ashx?x=48&amp;y=48&amp;groupid={{Transaction['source']['id']}}" alt="{{Transaction['source']['name']}}">
{{Transaction["source"]["name"]}}
</a>
{% endif %}
</td>
<td>
{% if Transaction['custom_text'] != None %}
{{Transaction['custom_text']}}
{% else %}
{% if Transaction['asset'] != None: %}
<a href="/catalog/{{Transaction['asset']['id']}}/--" class="text-decoration-none text-white">
<img class="rounded-2 overflow-hidden me-2 border" width="38" height="38" src="/Thumbs/Asset.ashx?x=48&y=48&assetId={{Transaction['asset']['id']}}" alt="{{Transaction['asset']['name']}}">
{{Transaction['asset']['name']}}
</a>
{% else %}
Unknown
{%endif%}
{% endif %}
</td>
<td>
{% if Transaction['currency_type'] == 0 %}
<span class="text-robux">R$ {{Transaction['currency_amount']}}</span>
{% else %}
<span class="text-tickets">T$ {{Transaction['currency_amount']}}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if len(TransactionInfo) == 0: %}
<div class="text-center">
<p class="text-secondary">No transactions found</p>
</div>
{%endif%}
<div class="align-items-center d-flex justify-content-center mt-2 mb-2">
<a class="ms-auto m-0 text-decoration-none {% if not Pagination.has_prev %}text-secondary{%endif%}" {% if Pagination.has_prev %}href="/admin/manage-users/{{userObj.id}}/transactions?page={{Pagination.prev_num}}&category={{PageCategory}}"{%endif%}>Previous</a>
<p class="ms-2 me-2 text-white m-0">Page {{Pagination.page}} of {{Pagination.pages}}</p>
<a class="me-auto m-0 text-decoration-none {% if not Pagination.has_next %}text-secondary{%endif%}" {% if Pagination.has_next %}href="/admin/manage-users/{{userObj.id}}/transactions?page={{Pagination.next_num}}&category={{PageCategory}}"{%endif%}>Next</a>
</div>
</div>
</div>
<script>const s = document.getElementById("change-category-select");s.addEventListener("change", function() {var u = new URL(window.location.href);var sb = s.value;u.searchParams.set("category", sb);u.searchParams.set("page", 1);window.location.href = u;});</script>
{% endblock %}
+193
View File
@@ -0,0 +1,193 @@
{% extends '__layout__.html' %}
{% block title %}Admin{% endblock %}
{% block head %}
<link href="/static/css/admin.css" rel="stylesheet"/> <!-- Its just easier to resuse this-->
<style>
.show-on-hover {
filter: blur(4px);
transition: 0.2s;
}
.show-on-hover:hover {
filter: blur(0px);
transition: 0.2s;
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="min-width: 1100px;">
<div class="d-flex">
<a href="/admin/manage-users" class="btn border-success btn-sm mb-2">Back to User Manager</a>
<a href="/users/{{userObj.id}}/profile" class="btn border-primary btn-sm mb-2 ms-3">View user profile</a>
</div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{% for message in messages %}
<div class="alert alert-danger p-2 text-center">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="row">
<div class="col-3">
<div class="border rounded-2">
<div class="bg-dark p-2 rounded-2 rounded-bottom-0 border-bottom">
<div class="d-flex align-items-center">
<img width="30px" height="30px" class="rounded me-2" src="/Thumbs/Head.ashx?x=48&y=48&userId={{userObj.id}}" style="aspect-ratio: 1/1;">
<h5 class="m-0">{{userObj.username}}</h5>
</div>
</div>
<img width="100%" class="p-2" src="/Thumbs/Avatar.ashx?x=420&y=420&userId={{userObj.id}}" style="aspect-ratio: 1/1;">
{% if isAdministrator: %}
<div class="border-top" style="background-color: rgba(24, 220, 255,0.5);font-size:12px;padding:3px;">
<p class="m-0 fw-bold text-white text-center">Administrator</p>
</div>
{% endif %}
{% if userObj.accountstatus == 2: %}
<div class="border-top" style="background-color: rgba(255, 201, 24, 0.5);font-size:12px;padding:3px;">
<p class="m-0 fw-bold text-white text-center">This account is temporarily banned</p>
</div>
{% endif %}
{% if userObj.accountstatus == 3: %}
<div class="border-top" style="background-color: rgba(255, 24, 24, 0.5);font-size:12px;padding:3px;">
<p class="m-0 fw-bold text-white text-center">This account has been deleted</p>
</div>
{% endif %}
{% if userObj.accountstatus == 4: %}
<div class="border-top" style="background-color: rgba(255, 24, 243, 0.5);font-size:12px;padding:3px;">
<p class="m-0 fw-bold text-white text-center">This account is GDPR deleted</p>
</div>
{% endif %}
<div class="bg-dark p-2 rounded-2 rounded-top-0 border-top text-secondary" style="font-size: 13px;">
<span>Created: <span class="text-white">{{userObj.created}}</span></span>
<br>
<span>Last Online: <span class="text-white">{{userObj.lastonline}}</span></span>
<br>
<span>2FA Enabled: <span class="text-white">{{userObj.TOTPEnabled}}</span></span>
<br>
<span>Place Visits: <span class="text-white">{{TotalVisits}}</span></span>
</div>
</div>
<div class="border rounded-2 mt-2">
<div class="bg-dark p-2 rounded-2 rounded-bottom-0 border-bottom">
<h5 class="m-0">Economy</h5>
</div>
<div class="text-secondary p-2 pt-1" style="font-size: 13px;">
<span>Robux: <span class="text-success fw-bold">R${{UserEconomyObj.robux}}</span></span>
<br>
<span>Tickets: <span class="text-warning fw-bold">T${{UserEconomyObj.tix}}</span></span>
</div>
</div>
</div>
<div class="col-7">
{% if LastestUserBanObj != None: %}
<div class="border rounded-2 mb-2">
<div class="bg-dark p-2 rounded-2 rounded-bottom-0 border-bottom">
<h5 class="m-0">Ban Information</h5>
</div>
<div class="p-2 text-secondary text-truncate" style="font-size: 14px;">
<span>Ban Type: <span class="text-white">{{LastestUserBanObj.ban_type.name}}</span></span>
<br>
<span>Banned On: <span class="text-white">{{LastestUserBanObj.created_at}}</span></span>
<br>
<span>Expires On: <span class="text-white">{{LastestUserBanObj.expires_at}}</span></span>
<br>
<span>Reason: <span class="text-white">{{LastestUserBanObj.reason}}</span></span>
<br>
<span>Moderator Note: <span class="text-white" style="word-break: break-all;white-space: normal;">{{LastestUserBanObj.moderator_note}}</span></span>
<br>
<span>Banned By: <a class="text-decoration-underline text-primary" href="/admin/manage-users/{{LastestUserBanObj.author_userid}}">{{LastestUserBanObj.author_userid}}</a></span>
</div>
</div>
{%endif%}
<div class="border rounded-2" style="min-height: 150px;">
<div class="bg-dark p-2 rounded-2 rounded-bottom-0 border-bottom">
<h5 class="m-0">Description</h5>
</div>
<div class="p-2">
{% for line in DescriptionLines %}
<p class="text-white m-0">{{line}}</p>
{% endfor %}
</div>
</div>
{% if LastLogin != None: %}
<div class="border rounded-2 mt-2">
<div class="bg-dark p-2 rounded-2 rounded-bottom-0 border-bottom">
<h5 class="m-0">Latest Login</h5>
</div>
<div class="p-2 text-secondary text-truncate" style="font-size: 14px;">
<span>User-Agent: <span class="text-white">{{LastLogin.useragent}}</span></span>
<br>
<span>Login Timestamp: <span class="text-white">{{LastLogin.timestamp}}</span></span>
</div>
</div>
{%endif%}
{% if LinkedDiscordObj != None: %}
<div class="border rounded-2 mt-2">
<div class="bg-dark p-2 rounded-2 rounded-bottom-0 border-bottom">
<h5 class="m-0">Discord Account</h5>
</div>
<div class="p-2 text-secondary text-truncate" style="font-size: 14px;">
<div class="row">
<div class="col-10">
<span>Discord ID: <span class="text-white">{{DiscordUserInfo.UserId}}</span></span>
<br>
<span>Username: <span class="text-white">{{DiscordUserInfo.Username}}</span></span>
<br>
<span>Discriminator: <span class="text-white">{{DiscordUserInfo.Discriminator}}</span></span>
<br>
<span>Linked On: <span class="text-white">{{LinkedDiscordObj.linked_on}}</span></span>
<br>
<span>Last Refresh: <span class="text-white">{{LinkedDiscordObj.last_updated}}</span></span>
</div>
<div class="col-2">
<img width="100%" style="aspect-ratio: 1/1;" class="border rounded" src="{{DiscordUserInfo.GetAvatarURL()}}">
</div>
</div>
</div>
</div>
{%endif%}
{% if InviteKeyUsed != None: %}
<div class="border rounded-2 mt-2">
<div class="bg-dark p-2 rounded-2 rounded-bottom-0 border-bottom">
<h5 class="m-0">Invite Key Info</h5>
</div>
<div class="p-2 text-secondary text-truncate" style="font-size: 14px;">
<span>Invite Key: <span class="text-white show-on-hover">{{InviteKeyUsed.key}}</span></span>
<br>
<span>Creator: {% if InviteKeyUsed.created_by != None: %}<a class="text-decoration-none text-primary" href="/admin/manage-users/{{InviteKeyUsed.created_by}}">{{InviteKeyUsed.creator.username}}</a>{%else%}<span class="text-white">Unknown</span>{%endif%}</span>
<br>
<span>Created At: <span class="text-white">{{InviteKeyUsed.created_at}}</span></span>
</div>
</div>
{%endif%}
</div>
<div class="col-2">
<div class="border rounded-2">
<div class="bg-dark p-2 rounded-2 rounded-bottom-0">
<h5 class="m-0">Actions</h5>
</div>
<a class="btn border-top rounded-0 w-100 text-decoration-none" href="/admin/manage-users/{{userObj.id}}/ban-user"><i class="bi bi-hammer"></i> Ban User</a>
{% if HasAdminPermission("ManageAdminPermissions") %}<a class="btn border-top rounded-0 w-100 text-decoration-none" href="/admin/manage-users/{{userObj.id}}/manage-admin-permissions"><i class="bi bi-shield"></i> Manage Permissions</a>{%endif%}
<a class="btn border-top rounded-0 w-100 text-decoration-none" href="/admin/manage-users/{{userObj.id}}/manage-currency"><i class="bi bi-cash-stack"></i> Manage Robux & BC</a>
</div>
<div class="border rounded-2 mt-2">
<div class="bg-dark p-2 rounded-2 rounded-bottom-0">
<h5 class="m-0">View</h5>
</div>
<a class="btn border-top rounded-0 w-100 text-decoration-none" href="/admin/manage-users/{{userObj.id}}/moderator-notes"><i class="bi bi-journal-text"></i></i> Moderator Notes</a>
<a class="btn border-top rounded-0 w-100 text-decoration-none" href="/admin/manage-users/{{userObj.id}}/login-history"><i class="bi bi-clock-history"></i></i> Login History</a>
<a class="btn border-top rounded-0 w-100 text-decoration-none" href="/admin/manage-users/{{userObj.id}}/invite-keys"><i class="bi bi-key"></i></i> Invite Keys</a>
<a class="btn border-top rounded-0 w-100 text-decoration-none" href="/admin/manage-users/{{userObj.id}}/game-sessions"><i class="bi bi-controller"></i></i> Game Sessions</a>
<a class="btn border-top rounded-0 w-100 text-decoration-none" href="/admin/manage-users/{{userObj.id}}/transactions"><i class="bi bi-bank"></i></i> Transactions</a>
<a class="btn border-top rounded-0 w-100 text-decoration-none" href="/admin/manage-users/{{userObj.id}}/ban-history"><i class="bi bi-exclamation-triangle"></i></i> Ban History</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}