feat: start making the admin panel

This commit is contained in:
TheClashFruit 2024-01-14 00:58:35 +01:00
parent 8bcebc7069
commit a4e5c331e3
Signed by: TheClashFruit
GPG key ID: 09BB24C34C2F3204
11 changed files with 379 additions and 121 deletions

View file

@ -6,6 +6,7 @@
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/util/Database.php';
require __DIR__ . '/util/Discord.php';
require __DIR__ . '/util/Admin.php';
use Twig\Loader\FilesystemLoader;

2
css/map.min.css vendored
View file

@ -1 +1 @@
*{padding:0;margin:0;box-sizing:border-box}#map{height:100vh}#homeLink{position:absolute;bottom:10px;left:10px;padding:5px 10px;z-index:400;background-color:#fff;background-clip:padding-box;border:2px solid rgba(0,0,0,.2);border-radius:5px;text-decoration:none;color:#000}img.leaflet-tile{image-rendering:pixelated}.leaflet-control{color:#333}.leaflet-control-mouseposition{background:rgba(255,255,255,.8);margin:0 !important;padding:6px;width:calc(100% - 12px);text-align:end;border-top-left-radius:5px}.leaflet-marker-icon{transition:300ms}/*# sourceMappingURL=map.min.css.map */
*{padding:0;margin:0;box-sizing:border-box}#map{height:100vh}#homeLink{position:absolute;bottom:10px;left:10px;padding:5px 10px;z-index:400;background-color:#fff;background-clip:padding-box;border:2px solid rgba(0,0,0,.2);border-radius:5px;text-decoration:none;color:#000}img.leaflet-tile,img.leaflet-marker-icon{image-rendering:pixelated}.leaflet-control{color:#333}.leaflet-control-mouseposition{background:rgba(255,255,255,.8);margin:0 !important;padding:6px;width:100%;text-align:end;border-top-left-radius:5px}.leaflet-marker-icon{transition:300ms}/*# sourceMappingURL=map.min.css.map */

View file

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["src/map.scss"],"names":[],"mappings":"AAAA,EACE,UACA,SAEA,sBAKF,kBAEA,UACE,kBACA,YACA,UACA,iBACA,YAEA,sBACA,4BAEA,gCACA,kBAEA,qBACA,WAGF,iBACE,0BAGF,iBACE,WAGF,+BACE,gCAEA,oBACA,YAEA,wBAEA,eAEA,2BAGF,qBACE","file":"map.min.css"}
{"version":3,"sourceRoot":"","sources":["src/map.scss"],"names":[],"mappings":"AAAA,EACE,UACA,SAEA,sBAKF,kBAEA,UACE,kBACA,YACA,UACA,iBACA,YAEA,sBACA,4BAEA,gCACA,kBAEA,qBACA,WAGF,yCACE,0BAGF,iBACE,WAGF,+BACE,gCAEA,oBACA,YAEA,WAEA,eAEA,2BAGF,qBACE","file":"map.min.css"}

View file

@ -26,7 +26,7 @@
color: #000;
}
img.leaflet-tile {
img.leaflet-tile, img.leaflet-marker-icon {
image-rendering: pixelated;
}
@ -40,7 +40,7 @@ img.leaflet-tile {
margin: 0 !important;
padding: 6px;
width: calc(100% - 2 * 6px);
width: 100%;
text-align: end;

View file

@ -3,10 +3,12 @@
@use "colors/dark" as dark;
* {
-webkit-tap-highlight-color: transparent; // fuck you
-webkit-tap-highlight-color: transparent; // fuck you (L)
}
html, body { overflow-x: hidden;}
html, body {
overflow-x: hidden;
}
body {
margin: 0;
@ -21,21 +23,21 @@ body {
}
main {
transition: 0.24s;
transition: 0.24s;
&.buffering {
transform: scale(1.01);
transition: 0.16s;
opacity: 0.4;
pointer-events: none;
user-select: none;
}
&.buffering {
transform: scale(1.01);
transition: 0.16s;
opacity: 0.4;
pointer-events: none;
user-select: none;
}
&.transition {
transform: scale(0.96);
transition: 0.16s;
opacity: 0;
}
&.transition {
transform: scale(0.96);
transition: 0.16s;
opacity: 0;
}
}
.container {
@ -63,96 +65,96 @@ img {
}
.cards {
display: flex;
flex-wrap: wrap;
gap: 8px;
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 8px;
width: 100%;
@media (max-width: 600px) {
gap: 12px;
}
@media (max-width: 600px) {
gap: 12px;
}
}
.card {
position: relative;
display: flex;
flex-direction: column;
box-sizing: border-box;
max-width: 100%;
height: 150px;
position: relative;
display: flex;
flex-direction: column;
box-sizing: border-box;
max-width: 100%;
height: 150px;
text-decoration: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
overflow: hidden;
outline: 1px solid light.$cardNormalBorder;
outline: 1px solid light.$cardNormalBorder;
background: light.$cardNormalBG;
color: light.$cardNormalFG;
@media (prefers-color-scheme: dark) {
outline-color: dark.$cardNormalBorder;
background: dark.$cardNormalBG;
color: dark.$cardNormalFG;
color: dark.$cardNormalFG;
}
transition: 0.24s;
&:hover {
outline-width: 2px;
outline-color: light.$cardActiveBorder;
background: light.$cardActiveBG;
color: light.$cardActiveFG;
outline-width: 2px;
outline-color: light.$cardActiveBorder;
background: light.$cardActiveBG;
color: light.$cardActiveFG;
@media (prefers-color-scheme: dark) {
outline-color: dark.$cardActiveBorder;
background: dark.$cardActiveBG;
color: dark.$cardActiveFG;
}
transition: 0.08s;
@media (prefers-color-scheme: dark) {
outline-color: dark.$cardActiveBorder;
background: dark.$cardActiveBG;
color: dark.$cardActiveFG;
}
.icon {
transition: 0.48s;
transform: scale(1.2);
}
transition: 0.08s;
.icon {
transition: 0.48s;
transform: scale(1.2);
}
}
width: calc(100% / 3 - 6px);
width: calc(100% / 3 - 6px);
@media (max-width: 800px) {
width: calc(50% - 4px);
}
width: calc(50% - 4px);
}
@media (max-width: 600px) {
border-radius: 8px;
width: 100%;
}
@media (max-width: 600px) {
border-radius: 8px;
width: 100%;
}
h1 {
margin: 0;
font-size: 1.2em;
font-weight: 400;
margin: 0;
font-size: 1.2em;
font-weight: 400;
}
.icon {
position: absolute;
top: 0;
right: 0;
height: 100%;
aspect-ratio: 1/1;
object-fit: cover;
position: absolute;
top: 0;
right: 0;
height: 100%;
aspect-ratio: 1/1;
object-fit: cover;
mask-image: linear-gradient(to right, transparent, rgba(red, 0.3));
@media (prefers-color-scheme: dark) {
mask-image: linear-gradient(to right, transparent, rgba(red, 0.1));
}
mask-image: linear-gradient(to right, transparent, rgba(red, 0.3));
@media (prefers-color-scheme: dark) {
mask-image: linear-gradient(to right, transparent, rgba(red, 0.1));
}
transition: 0.64s;
transition: 0.64s;
}
p {
margin: 8px 0;
margin: 8px 0;
}
}
@ -169,13 +171,11 @@ a {
}
.pageHero {
background-image:
linear-gradient(rgba(light.$headerOverlay, 0.9),rgba(light.$headerOverlay, 0.9)), // white overlay. yeah that's a bit of an ugly hack?
url('/img/Panorama-Lens-Blur.png');
background-image: linear-gradient(rgba(light.$headerOverlay, 0.9), rgba(light.$headerOverlay, 0.9)), // white overlay. yeah that's a bit of an ugly hack?
url('/img/Panorama-Lens-Blur.png');
@media (prefers-color-scheme: dark) {
background-image:
linear-gradient(rgba(dark.$headerOverlay, 0.9),rgba(dark.$headerOverlay, 0.9)),
background-image: linear-gradient(rgba(dark.$headerOverlay, 0.9), rgba(dark.$headerOverlay, 0.9)),
url('/img/Panorama-Lens-Blur.png');
}
@ -211,7 +211,9 @@ a {
}
@media (prefers-color-scheme: dark) {
img { filter: invert(1) }
img {
filter: invert(1)
}
}
span {
@ -248,7 +250,7 @@ a {
display: none;
}
z-index: 10;
position: fixed;
top: 8px;
right: 8px;
@ -258,14 +260,14 @@ a {
color: light.$btnNormalFG;
@media (prefers-color-scheme: dark) {
background: dark.$btnNormalBG;
color: dark.$btnNormalFG;
background: dark.$btnNormalBG;
color: dark.$btnNormalFG;
}
padding: 8px 24px;
border-radius: 32px;
font-weight: bold;
user-select: none;
@ -302,24 +304,28 @@ a {
height: 100%;
z-index: 9;
backdrop-filter: blur(15px);
background: rgba(light.$navBG, 0.86);
@media (prefers-color-scheme: dark) {
background: rgba(dark.$navBG, 0.9);
}
backdrop-filter: blur(15px);
background: rgba(light.$navBG, 0.86);
@media (prefers-color-scheme: dark) {
background: rgba(dark.$navBG, 0.9);
}
justify-content: center;
justify-content: center;
flex-direction: column;
gap: 64px;
&, > * { transition: 0.16s }
&, > * {
transition: 0.16s
}
&:not(.opened) {
opacity: 0;
pointer-events: none;
//&, > * { transition: 0.12s }
> * { transform: translateX(32px); }
> * {
transform: translateX(32px);
}
}
}
@ -333,7 +339,7 @@ a {
gap: 6px;
@media (max-width: 600px) {
width: 100%;
width: 100%;
flex-direction: column;
}
@ -344,7 +350,9 @@ a {
font-size: 16px;
box-sizing: border-box;
color: light.$navLinkNormalColor;
@media (prefers-color-scheme: dark) { color: dark.$navLinkNormalColor; }
@media (prefers-color-scheme: dark) {
color: dark.$navLinkNormalColor;
}
@media (max-width: 600px) {
font-size: 28px;
@ -353,7 +361,7 @@ a {
text-align: end;
&.active::before {
content: ""; // i know..
content: ""; // i know..
}
}
@ -369,7 +377,7 @@ a {
transition: 0.24s;
&.active {
pointer-events: none;
pointer-events: none;
}
&:hover, &.active {
@ -387,18 +395,18 @@ a {
}
@media (min-width: 601px) {
&.buttonPrimary {
background: light.$btnNormalBG;
color: light.$btnNormalFG;
&.buttonPrimary {
background: light.$btnNormalBG;
color: light.$btnNormalFG;
@media (prefers-color-scheme: dark) {
background: dark.$btnNormalBG;
color: dark.$btnNormalFG;
}
@media (prefers-color-scheme: dark) {
background: dark.$btnNormalBG;
color: dark.$btnNormalFG;
}
font-weight: 700 !important;
}
}
font-weight: 700 !important;
}
}
}
}
}

View file

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["src/_fonts.scss","src/style.scss","src/colors/_light.scss","src/colors/_dark.scss"],"names":[],"mappings":"CAEQ,qKAER,WACE,sDAGF,kBACE,mDACA,gBAIF,UACE,uECXF,EACC,0CAGD,4BAEA,KACE,SAEA,WCXO,KDYP,MCXO,KDaP,mCANF,KAOI,WEfK,QFgBL,MEfK,MFmBT,KACC,gBAEA,eACC,sBACA,gBACA,WACA,oBACA,iBAGD,gBACC,sBACA,gBACA,UAIF,WACE,gBAEA,WAEA,cAEA,aACA,sBAGF,MACE,2BAGF,EACE,gBACA,YAGF,IACE,eAGF,OACC,aACA,eACA,QACA,WAEA,yBAND,OAOE,UAIF,MACC,kBACA,aACA,sBACA,sBACA,eACA,aACC,qBACA,kBACA,iBACA,eACA,gBAED,0BACC,WCzEa,QD0Eb,MCvFO,KD+FP,gBAsBD,iCA5BC,mCAjBF,MAkBI,cE3Ee,QF4Ef,WE9EW,QF+EZ,ME5FM,MFiGP,YACC,kBACA,cCxFS,QDyFT,WC1FK,QD2FL,MCnGM,QD2GP,gBANC,mCAND,YAOG,cE7FO,QF8FP,WE/FG,QFgGJ,MEvGQ,SF4GT,kBACC,gBACA,qBAMF,yBA/CF,MAgDE,uBAGD,yBAnDD,MAoDG,kBACD,YAGA,SACC,SACA,gBACA,gBAGD,YACC,kBACA,MACA,QACA,YACA,iBACA,iBAEA,wEAKA,gBAJA,mCATD,YAUE,yEAMF,QACC,aAIH,EACE,MC1JO,QD4JP,mCAHF,EAII,ME5JQ,SF+JV,UACE,WAIJ,UACE,iBACE,uGASF,2BACA,sBAEA,aATA,mCALF,UAMI,iBACA,kGASF,qBACE,YAEA,aACA,mBAEA,mBACA,8BAEA,yBATF,qBAUI,wBAGF,yBACE,aACA,sBAEA,SAEA,kCACE,uBAEA,yBAHF,kCAII,oBAGF,mCACE,wDAGF,uCACE,eACA,YAIJ,4CAKE,mBACA,QALA,yBADF,4CAEI,cAMF,kDACE,kBACA,eAEA,qEACE,cACA,2BACA,yBAQZ,WAIE,WAEA,eACA,QACA,UACA,eAEA,WC3PO,QD4PP,MCxPY,KD+PZ,iBAEA,mBAEA,iBACA,iBAEA,eAzBA,yBADF,WAEI,cAYF,mCAdF,WAeG,WE/PM,QFgQN,ME5PW,MFyQd,SACE,WAEA,qBACA,uBAEA,WC3QM,QD6QN,mCARF,SASI,aE7QQ,QF8QR,WE/QI,SFkRN,oBACE,kBAEA,aACA,eAEA,mBACA,8BAEA,yBATF,oBAUI,eACA,MACA,OACA,WACA,YACA,UAEF,2BACC,iCAKA,uBACC,sBACA,UANF,yDAnBF,oBAoBI,8BAXF,yBAkBE,0DAEA,iCACE,UACA,oBAGA,+DAIJ,wBACE,SAEA,aACA,eACA,qBAEA,QAEA,yBATF,wBAUG,WACC,uBAGF,0BACE,mBACA,iBAEA,eACA,sBACA,MC7Ta,QD4Ub,qBAEA,iBAEA,mBAEA,gCAEA,gBAtBA,mCAPF,0BAOwC,MElUzB,SFoUb,yBATF,0BAUI,eACA,aACA,iBACA,eAEA,yCACC,aAeH,iCACC,oBAGD,iEAEE,MCnXD,QDoXC,cC9VY,QDsWZ,gBANA,mCALF,iEAMI,MEnWS,QFoWT,cEtWU,SF6Wd,yBACC,wCACE,WCjYF,QDkYE,MC9XG,KDqYH,4BALA,yDAJF,wCAKI,WErYJ,QFsYI,MElYC,MFiZd,YACE,WAEA,uBACE,aACA,eACA,SAEA,yBACE,aACA,QAEA,eAEA,cAEA,6BACE,UAEA,mCAHF,6BAII,kBAIJ,4CACE","file":"style.min.css"}
{"version":3,"sourceRoot":"","sources":["src/_fonts.scss","src/style.scss","src/colors/_light.scss","src/colors/_dark.scss"],"names":[],"mappings":"CAEQ,qKAER,WACE,sDAGF,kBACE,mDACA,gBAIF,UACE,uECXF,EACE,0CAGF,UACE,kBAGF,KACE,SAEA,WCbO,KDcP,MCbO,KDeP,mCANF,KAOI,WEjBK,QFkBL,MEjBK,MFqBT,KACE,gBAEA,eACE,sBACA,gBACA,WACA,oBACA,iBAGF,gBACE,sBACA,gBACA,UAIJ,WACE,gBAEA,WAEA,cAEA,aACA,sBAGF,MACE,2BAGF,EACE,gBACA,YAGF,IACE,eAGF,OACE,aACA,eACA,QACA,WAEA,yBANF,OAOI,UAIJ,MACE,kBACA,aACA,sBACA,sBACA,eACA,aACA,qBACA,kBACA,iBACA,eACA,gBAEA,0BACA,WC3Ea,QD4Eb,MCzFO,KDiGP,gBAsBA,iCA5BA,mCAjBF,MAkBI,cE7Ee,QF8Ef,WEhFW,QFiFX,ME9FK,MFmGP,YACE,kBACA,cC1FQ,QD2FR,WC5FI,QD6FJ,MCrGK,QD6GL,gBANA,mCANF,YAOI,cE/FM,QFgGN,WEjGE,QFkGF,MEzGM,SF8GR,kBACE,gBACA,qBAMJ,yBA/CF,MAgDI,uBAGF,yBAnDF,MAoDI,kBACA,YAGF,SACE,SACA,gBACA,gBAGF,YACE,kBACA,MACA,QACA,YACA,iBACA,iBAEA,wEAKA,gBAJA,mCATF,YAUI,yEAMJ,QACE,aAIJ,EACE,MC5JO,QD8JP,mCAHF,EAII,ME9JQ,SFiKV,UACE,WAIJ,UACE,wHAQA,2BACA,sBAEA,aARA,mCAJF,UAKI,mHASF,qBACE,YAEA,aACA,mBAEA,mBACA,8BAEA,yBATF,qBAUI,wBAGF,yBACE,aACA,sBAEA,SAEA,kCACE,uBAEA,yBAHF,kCAII,oBAGF,mCACE,sCACE,kBAIJ,uCACE,eACA,YAIJ,4CAKE,mBACA,QALA,yBADF,4CAEI,cAMF,kDACE,kBACA,eAEA,qEACE,cACA,2BACA,yBAQZ,WAIE,WAEA,eACA,QACA,UACA,eAEA,WC7PO,QD8PP,MC1PY,KDiQZ,iBAEA,mBAEA,iBACA,iBAEA,eAzBA,yBADF,WAEI,cAYF,mCAdF,WAeI,WEjQK,QFkQL,ME9PU,MF2Qd,SACE,WAEA,qBACA,uBAEA,WC7QM,QD+QN,mCARF,SASI,aE/QQ,QFgRR,WEjRI,SFoRN,oBACE,kBAEA,aACA,eAEA,mBACA,8BAEA,yBATF,oBAUI,eACA,MACA,OACA,WACA,YACA,UAEA,2BACA,iCAKA,uBACA,sBACA,UANA,yDAnBJ,oBAoBM,8BAXJ,yBAkBE,0CACE,gBAGF,iCACE,UACA,oBAGA,mCACE,4BAKN,wBACE,SAEA,aACA,eACA,qBAEA,QAEA,yBATF,wBAUI,WACA,uBAGF,0BACE,mBACA,iBAEA,eACA,sBACA,MCnUa,QDoVb,qBAEA,iBAEA,mBAEA,gCAEA,gBAxBA,mCAPF,0BAQI,MEzUW,SF4Ub,yBAXF,0BAYI,eACA,aACA,iBACA,eAEA,yCACE,aAeJ,iCACE,oBAGF,iEAEE,MC3XD,QD4XC,cCtWY,QD8WZ,gBANA,mCALF,iEAMI,ME3WS,QF4WT,cE9WU,SFqXd,yBACE,wCACE,WCzYH,QD0YG,MCtYE,KD6YF,4BALA,yDAJF,wCAKI,WE7YL,QF8YK,ME1YA,MFyZd,YACE,WAEA,uBACE,aACA,eACA,SAEA,yBACE,aACA,QAEA,eAEA,cAEA,6BACE,UAEA,mCAHF,6BAII,kBAIJ,4CACE","file":"style.min.css"}

View file

@ -84,15 +84,21 @@
global $twig, $mysql;
$twig->addGlobal('pageUri', '/profile');
$user = $mysql->getUserRecordFromId($_SESSION['user']['id']);
if($user == null) {
if (isset($_SESSION['user'])) {
$user = $mysql->getUserRecordFromId($_SESSION['user']['id']);
if ($user == null && $user['admin'] == 0) {
http_response_code(404);
echo $twig->render('404.twig');
} else {
echo $twig->render('profile.twig', array('db_data' => $user));
}
} else {
http_response_code(404);
echo $twig->render('404.twig');
} else {
echo $twig->render('profile.twig', array('db_data' => $user));
}
});
@ -114,12 +120,12 @@
global $twig, $mysql, $discord;
$twig->addGlobal('pageUri', '/u/' . $name);
$user = $mysql->getUserRecordFromUsername($name);
if($user == null) {
http_response_code(404);
echo $twig->render('404.twig');
} else {
echo $twig->render('user.twig', array('db_user' => $user));
@ -133,17 +139,32 @@
$twig->addGlobal('pageUri', '/admin');
$user = $mysql->getUserRecordFromId($_SESSION['user']['id']);
if (isset($_SESSION['user'])) {
$user = $mysql->getUserRecordFromId($_SESSION['user']['id']);
if($user == null && $user['admin'] == 0) {
$users = $mysql->getUsers();
$markers = $mysql->getMarkers();
if ($user == null && $user['admin'] == 0) {
http_response_code(404);
echo $twig->render('404.twig');
} else {
echo $twig->render('admin/index.twig', array('users' => $users, 'markers' => $markers));
}
} else {
http_response_code(404);
echo $twig->render('404.twig');
} else {
echo $twig->render('admin/index.twig', array('db_data' => $user));
}
});
// ---------------- Admin API ---------------- //
$adminApi = new Admin($router);
$adminApi->registerApiRoutes();
// ----------------- 404 ----------------- //
$router->set404(function() {

View file

@ -137,13 +137,13 @@ const updatePlayerPos = (players) =>{
mappedPlayers[player.uniqueId] = playerMarker;
} else {
const playerIcon = L.icon({
iconUrl: `https://mc-heads.net/avatar/${player.displayName}`,
iconUrl: `https://mc-heads.net/avatar/${player.displayName}/16`,
iconSize: [28, 28],
iconAnchor: [14, 14],
popupAnchor: [0, -14]
});
const marker = L.marker([player.location.z, player.location.x], { icon: playerIcon })
const marker = L.marker([player.location.z, player.location.x], { icon: playerIcon, alt: player.displayName })
.bindPopup(`${player.displayName} (${Math.floor(player.location.x)}; ${Math.floor(player.location.y)}; ${Math.floor(player.location.z)})`);
playerMarkers.addLayer(marker);

View file

@ -11,7 +11,138 @@
</head>
<body>
<div class="container">
<h1 class="text-center">soon</h1>
<h3>Users</h3>
<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>

81
util/Admin.php Normal file
View file

@ -0,0 +1,81 @@
<?php
class Admin {
private Bramus\Router\Router $router;
function __construct($router) {
$this->router = $router;
}
function notFound($error): void {
header('Content-Type: application/json');
http_response_code(404);
echo json_encode(array(
'error' => true,
'error_description' => $error
));
exit();
}
function isSignedInAdmin(): void {
global $mysql;
if(!isset($_SESSION['user'])) {
$this->notFound('You are not logged in.');
}
$user = $mysql->getUserRecordFromId($_SESSION['user']['id']);
if($user == null && $user['admin'] == 0) {
$this->notFound('You are not an admin.');
}
}
function registerApiRoutes(): void {
$router = $this->router;
$router->mount('/admin/api', function() use ($router) {
$router->mount('/v1', function() use ($router) {
$router->get('/markers', function() {
global $mysql;
header('Content-Type: application/json');
$this->isSignedInAdmin();
$markers = $mysql->getMarkers();
$markersNew = array();
foreach ($markers as $marker) {
$markersNew[] = array(
'id' => $marker['id'],
'name' => $marker['name'],
'category' => $marker['category'],
'x' => floatval(explode(';', $marker['data'])[1]),
'y' => floatval(explode(';', $marker['data'])[0])
);
}
$finalOut = array(
'error' => false,
'markers' => $markersNew
);
echo json_encode($finalOut);
});
$router->get('/users', function() {
global $mysql;
header('Content-Type: application/json');
$this->isSignedInAdmin();
echo json_encode($mysql->getUsers());
});
});
});
}
}

View file

@ -58,7 +58,23 @@
}
}
function getMarkers() {
function getUsers(): array | null {
$sql = 'SELECT * FROM users';
$stmt = $this->conn->prepare($sql);
$stmt->execute();
$res = $stmt->get_result();
if($res->num_rows > 0) {
return $res->fetch_all(MYSQLI_ASSOC);
} else {
return null;
}
}
function getMarkers(): array | null {
$sql = 'SELECT * FROM markers';
$stmt = $this->conn->prepare($sql);