feat: start making an admin panel

This commit is contained in:
TheClashFruit 2024-04-01 19:10:19 +02:00
parent c8b4814aef
commit 063292dd9d
Signed by: TheClashFruit
GPG key ID: 09BB24C34C2F3204
13 changed files with 277 additions and 159 deletions

View file

@ -21,17 +21,22 @@
$guildIds[] = $guild['id'];
}
if(!in_array('1127731341283307520', $guildIds)) {
if(!in_array('1127731341283307520', $guildIds) || !in_array('1195393418151596032', $guildIds)) {
echo json_encode(array(
'error' => true,
'error_description' => 'You are not in the CRSS guild.'
'error_description' => 'You are not in any of CRSS\'s guilds.'
));
} else {
$_SESSION['user'] = $discord->getUser($res['access_token']);
$mysql->createUserRecord($_SESSION['user']);
header('Location: /');
if (isset($_GET['state'])) {
header('Location: ' . $_GET['state']);
} else {
header('Location: /');
}
}
} else {
echo json_encode($res);

View file

@ -1,5 +1,5 @@
<?php
global $twig;
global $twig, $mysql;
require_once '_config.php';
@ -32,8 +32,15 @@
)
);
if(isset($_SESSION['user']))
$twig->addGlobal('user', $_SESSION['user']);
if(isset($_SESSION['user'])) {
$dbUser = $mysql->getUserRecordFromId($_SESSION['user']['id']);
$user = $_SESSION['user'];
$user['is_admin'] = $dbUser['is_admin'];
$twig->addGlobal('user', $user);
}
$res = $curl->get('https://crss.blurryface.xyz/api/v1/players');
@ -45,6 +52,7 @@
$twig->addGlobal('playerCount', $json);
$twig->addGlobal('nations', $nations);
$twig->addGlobal('dc_uri', 'https://discord.com/api/oauth2/authorize?client_id=1144248396467683338&redirect_uri=' . urlencode($_ENV['DISCORD_REDIRECT']) . '&response_type=code&scope=identify%20guilds&state=' . urlencode($_SERVER['REQUEST_URI']));
$twig->addGlobal('reduced', isset($_GET['reduced']));
@ -152,16 +160,45 @@
$markers = $mysql->getMarkers();
if ($user == null && $user['admin'] == 0) {
http_response_code(404);
http_response_code(401);
echo $twig->render('404.twig');
echo '<style>body { overflow: hidden; height: 100svh; background: black; display: flex; justify-content: center; align-items: center; }</style><img src="https://http.cat/401" alt="401 Unauthorized" />';
} else {
echo $twig->render('admin/index.twig', array('users' => $users, 'markers' => $markers));
}
} else {
http_response_code(404);
http_response_code(401);
echo $twig->render('404.twig');
echo '<style>body { overflow: hidden; height: 100svh; background: black; display: flex; justify-content: center; align-items: center; }</style><img src="https://http.cat/401" alt="401 Unauthorized" />';
}
});
$router->get('/admin/__data/page/([a-z]+)', function($page) {
global $twig, $mysql;
if (isset($_SESSION['user'])) {
$user = $mysql->getUserRecordFromId($_SESSION['user']['id']);
$users = $mysql->getUsers();
$markers = $mysql->getMarkers();
if ($user == null && $user['admin'] == 0) {
http_response_code(401);
echo '<style>body { overflow: hidden; height: 100svh; background: black; display: flex; justify-content: center; align-items: center; }</style><img src="https://http.cat/401" alt="401 Unauthorized" />';
} else {
try {
echo $twig->render('admin/pages/' . urlencode($page) . '.twig', array('users' => $users, 'markers' => $markers));
} catch (Exception $e) {
http_response_code(404);
echo $twig->render('admin/pages/404.twig');
}
}
} else {
http_response_code(401);
echo '<style>body { overflow: hidden; height: 100svh; background: black; display: flex; justify-content: center; align-items: center; }</style><img src="https://http.cat/401" alt="401 Unauthorized" />';
}
});

50
js/admin/nav.js Normal file
View file

@ -0,0 +1,50 @@
const pageContainer = document.querySelector('.pageContainer');
window.history.pushState({}, '', '#/');
window.addEventListener('hashchange', () => {
let uri = window.location.href.split('#')[1];
if (!window.location.href.includes('#'))
uri = '/admin#/'
const allActiveLinks = document.querySelectorAll('.nav a.active');
const allLinksWithThisUrl = document.querySelectorAll(`.nav a[href="#${uri}"]`);
allActiveLinks.forEach(activeLink => {
activeLink.classList.remove('active');
activeLink.classList.add('link-body-emphasis');
});
allLinksWithThisUrl.forEach(link => {
link.classList.add('active');
link.classList.remove('link-body-emphasis');
});
changePage(window.location.href.split('#')[1].replace('/', ''));
});
const changePage = (url) => {
if (!url)
url = 'dashboard';
pageContainer.innerHTML = `<i class="loader" data-lucide="loader-circle"></i>`;
pageContainer.classList.add('d-flex');
pageContainer.classList.add('align-items-center');
pageContainer.classList.add('justify-content-center');
lucide.createIcons();
fetch(`/admin/__data/page/${url}`)
.then(res => res.text())
.then(html => {
pageContainer.innerHTML = html;
pageContainer.classList.remove('d-flex');
pageContainer.classList.remove('align-items-center');
pageContainer.classList.remove('justify-content-center');
lucide.createIcons();
});
};

View file

@ -0,0 +1,12 @@
<script src="/js/admin/nav.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
lucide.createIcons();
changePage();
</script>
</body>
</html>

View file

@ -0,0 +1,35 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>{{ pageTitle }} - Admin &bull; Clyde's Real Survival SMP</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" />
<style>
body {
min-height: 100svh;
}
.lucide {
vertical-align: -10%;
}
/* Loading Animation */
.loader {
animation: spin 1s linear infinite;
}
@keyframes spin {
100% {
-webkit-transform: rotate(360deg);
transform:rotate(360deg);
}
}
</style>
</head>
<body class="d-flex flex-column">

View file

@ -0,0 +1,52 @@
<div class="d-flex flex-column flex-shrink-0 p-3 bg-body-tertiary" style="width: 280px;">
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-body-emphasis text-decoration-none">
<i class="pe-none me-2" data-lucide="gauge" width="40" height="32"></i>
<span class="fs-4">Admin Panel</span>
</a>
<hr>
<ul class="nav nav-pills flex-column mb-auto">
<li class="nav-item">
<a href="#/" class="nav-link active" aria-current="page">
<i class="pe-none me-2" data-lucide="home" width="16" height="16"></i>
Home
</a>
</li>
<li class="nav-item">
<a href="#/pages" class="nav-link link-body-emphasis">
<i class="pe-none me-2" data-lucide="notebook-text" width="16" height="16"></i>
Pages
</a>
</li>
<li class="nav-item">
<a href="#/users" class="nav-link link-body-emphasis">
<i class="pe-none me-2" data-lucide="users" width="16" height="16"></i>
Users
</a>
</li>
<li class="nav-item">
<a href="#/markers" class="nav-link link-body-emphasis">
<i class="pe-none me-2" data-lucide="map-pin" width="16" height="16"></i>
Markers
</a>
</li>
</ul>
<hr>
<div class="dropdown">
<a href="#" class="d-flex align-items-center link-body-emphasis text-decoration-none dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<img src="https://cdn.discordapp.com/avatars/{{ user.id }}/{{ user.avatar }}.png" alt="" width="32" height="32" class="rounded-circle me-2">
<span>{{ user.global_name }}</span>
</a>
<ul class="dropdown-menu text-small shadow">
<li><a class="dropdown-item" href="#">New project...</a></li>
<li><a class="dropdown-item" href="#">Settings</a></li>
<li><a class="dropdown-item" href="#">Profile</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/">Exit Admin</a></li>
</ul>
</div>
</div>

View file

@ -1,150 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
{% include 'admin/includes/head.twig' with {'pageTitle': 'Dashboard'} %}
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<div class="d-flex flex-grow-1">
{% include 'admin/includes/sidebar.twig' %}
<title>Admin Panel</title>
<div class="flex-grow-1 pageContainer">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" />
</head>
<body>
<div class="container">
<h3>Users</h3>
</div>
</div>
<table class="table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">username</th>
<th scope="col">display_name</th>
<th scope="col">is_admin</th>
<th scope="col">date</th>
<th scope="col">actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<th scope="row">{{ user.id }}</th>
<td>{{ user.username }}</td>
<td>{{ user.display_name }}</td>
<td>{{ user.is_admin == 1 ? "true" : "false" }}</td>
<td>{{ user.date }}</td>
<td>
{% if user.is_admin == 1 %}
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#confirmModal">Revoke Admin</button>
{% else %}
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#confirmModal">Make Admin</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="modal fade" id="staticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Are you sure?</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus aspernatur fugit id impedit iusto perferendis possimus provident quos sunt vero. Assumenda blanditiis culpa eaque error ipsa non quasi sint unde?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-bs-dismiss="modal">Yes</button>
<button type="button" class="btn btn-danger">No</button>
</div>
</div>
</div>
</div>
<div class="container">
<h3>Markers</h3>
<table class="table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">name</th>
<th scope="col">category</th>
<th scope="col">data</th>
<th scope="col">actions</th>
</tr>
</thead>
<tbody>
{% for marker in markers %}
<tr>
<th scope="row">{{ marker.id }}</th>
<td>{{ marker.name }}</td>
<td>{{ marker.category }}</td>
<td>{{ marker.data }}</td>
<td>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="modal fade" id="confirmModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Are you sure?</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus aspernatur fugit id impedit iusto perferendis possimus provident quos sunt vero. Assumenda blanditiis culpa eaque error ipsa non quasi sint unde?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-bs-dismiss="modal">Yes</button>
<button type="button" class="btn btn-danger">No</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="editModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Editing "${name}"</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form class="frm">
<input value="update_marker" name="action" class="visually-hidden">
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp">
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check me out</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-bs-dismiss="modal" onclick="document.querySelector('.frm').submit()">Save</button>
<button type="button" class="btn btn-danger" data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
{% include 'admin/includes/foot.twig' %}

View file

@ -0,0 +1,3 @@
<div class="d-flex align-items-center justify-content-center h-100">
<h1>Not Found :(</h1>
</div>

View file

@ -0,0 +1,3 @@
<div class="d-flex align-items-center justify-content-center h-100">
<h1>Welcome {{ user.global_name }} to the admin panel!</h1>
</div>

View file

@ -0,0 +1,24 @@
<table class="table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">name</th>
<th scope="col">category</th>
<th scope="col">data</th>
<th scope="col">actions</th>
</tr>
</thead>
<tbody>
{% for marker in markers %}
<tr>
<th scope="row">{{ marker.id }}</th>
<td>{{ marker.name }}</td>
<td>{{ marker.category }}</td>
<td>{{ marker.data }}</td>
<td>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View file

@ -0,0 +1 @@
pages

View file

@ -0,0 +1,30 @@
<table class="table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">username</th>
<th scope="col">display_name</th>
<th scope="col">is_admin</th>
<th scope="col">date</th>
<th scope="col">actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<th scope="row">{{ user.id }}</th>
<td>{{ user.username }}</td>
<td>{{ user.display_name }}</td>
<td>{{ user.is_admin == 1 ? "true" : "false" }}</td>
<td>{{ user.date }}</td>
<td>
{% if user.is_admin == 1 %}
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#confirmModal">Revoke Admin</button>
{% else %}
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#confirmModal">Make Admin</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>

View file

@ -16,14 +16,19 @@
<a class="{% if page == 'map.js' %} active {% endif %}" href="/map">
Map & Rails
</a>
{% if user.is_admin == 1 %}
<a href="/admin">
Admin Panel
</a>
{% endif %}
</div>
<div class="navRight">
{% if user %}
<a class="userButton {% if page == 'profile' %}active{% endif %}" href="{% if page == 'profile' %}#{% else %}/profile{% endif %}">
<a class="transitionEnabled userButton {% if page == 'profile' %}active{% endif %}" href="{% if page == 'profile' %}#{% else %}/profile{% endif %}">
{{ user.global_name }}
</a>
{% else %}
<a class="buttonPrimary" href="{{ discord_auth }}">
<a class="buttonPrimary" href="{{ dc_uri }}">
Login
</a>
{% endif %}