Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,6 @@ dist
.wrangler/

users.json

# Localflare generated files
.localflare/
295 changes: 147 additions & 148 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"eslint": "^9.39.1",
"typescript": "^5.5.2",
"vitest": "~3.2.0",
"wrangler": "^4.60.0"
"wrangler": "^4.65.0"
},
"dependencies": {
"bcrypt-ts": "^7.1.0",
Expand Down
119 changes: 118 additions & 1 deletion public/backend.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ <h1 ng-show="vm.timer>0" style="margin: 16px auto; font-size: 1.2em">
Tirage automatique des tables dans {{vm.getTimerRendering(vm.timer)}}
</h1>
</div>

<div class="button_container_admin">
<button class="secondary_background" type="button" ng-click="vm.clearTables()">Vider tables</button>
<button class="secondary_background" type="button" ng-click="vm.generateTables()">Distribuer Tables</button>
Expand Down Expand Up @@ -78,8 +79,124 @@ <h1 ng-show="vm.timer>0" style="margin: 16px auto; font-size: 1.2em">
</tbody>
</table>
</div>

<div class="admin-users">
<div class="admin-users__header">
<h2>Gestion des utilisateurs</h2>
<p>Créer, éditer ou supprimer des comptes (admin requis).</p>
</div>
<div class="admin-card">
<h3>Créer un utilisateur</h3>
<div class="admin-form-grid">
<label>Pseudo<input type="text" ng-model="vm.newUser.pseudo" placeholder="Pseudo" /></label>
<label>Email<input type="email" ng-model="vm.newUser.email" placeholder="Email" /></label>
<label>Mot de passe<input type="password" ng-model="vm.newUser.password" placeholder="Mot de passe" /></label>
<label class="checkbox-inline"><input type="checkbox" ng-model="vm.newUser.admin" /> Admin</label>
<label class="checkbox-inline"><input type="checkbox" ng-model="vm.newUser.ready" /> Prêt</label>
<label class="checkbox-inline"><input type="checkbox" ng-model="vm.newUser.canPlayTarot" /> Tarot</label>
<label class="checkbox-inline"><input type="checkbox" ng-model="vm.newUser.canPlayTwoTables" /> Deux tables</label>
</div>
<button class="secondary_background" type="button" ng-click="vm.createUser()">Créer</button>
</div>

<div class="admin-card">
<h3>Utilisateurs existants</h3>
<table class="tables user-admin-table">
<thead>
<tr>
<th>Pseudo</th>
<th>Email</th>
<th>Admin</th>
<th>Prêt</th>
<th>Tarot</th>
<th>Deux tables</th>
<th>Validité token</th>
<th>Dernière activité</th>
<th>Nouveau mot de passe</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in vm.users">
<td><input type="text" ng-model="user.pseudo" /></td>
<td><input type="email" ng-model="user.email" /></td>
<td class="center"><input type="checkbox" ng-model="user.admin" /></td>
<td class="center"><input type="checkbox" ng-model="user.ready" /></td>
<td class="center"><input type="checkbox" ng-model="user.canPlayTarot" /></td>
<td class="center"><input type="checkbox" ng-model="user.canPlayTwoTables" /></td>
<td>{{vm.formatTimestamp(user.tokenValidity)}}</td>
<td>{{vm.formatTimestamp(user.lastActiveAt)}}</td>
<td><input type="password" ng-model="user.newPassword" placeholder="Ne pas changer" /></td>
<td class="admin-actions">
<button class="secondary_background" type="button" ng-click="vm.saveUser(user)">Sauver</button>
<button class="secondary_background" type="button" ng-click="vm.deleteUserAdmin(user)">Supprimer</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<a href="/" target="_blank">Frontend</a>
<a href="/userList" target="_blank">Liste utilisateurs</a>
<br />

<style>
.admin-users {
margin: 16px;
padding: 12px;
background: var(--surface-dark, #0f172a);
border: 1px solid var(--border-color, #2a3b4d);
border-radius: 8px;
}
.admin-users__header h2 {
margin: 0 0 4px 0;
}
.admin-users__header p {
margin: 0 0 12px 0;
color: var(--text-secondary, #cbd5e1);
}
.admin-card {
background: var(--card-surface, #0b1220);
padding: 12px;
border-radius: 8px;
border: 1px solid var(--border-color, #2a3b4d);
margin-bottom: 12px;
}
.admin-card h3 {
margin-top: 0;
}
.admin-form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 8px;
margin-bottom: 10px;
}
.admin-form-grid label {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
}
.checkbox-inline {
align-items: center;
flex-direction: row !important;
gap: 6px;
}
.user-admin-table input[type='text'],
.user-admin-table input[type='email'],
.user-admin-table input[type='password'] {
width: 100%;
box-sizing: border-box;
}
.user-admin-table th,
.user-admin-table td {
vertical-align: middle;
}
.admin-actions button {
margin-right: 6px;
}
.center {
text-align: center;
}
</style>
</body>
</html>
48 changes: 48 additions & 0 deletions public/index_backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ angular.module('meltdownAdmin', [])
vm.username = '';
vm.pseudosSelected = [];
vm.tables = [];
vm.users = [];
vm.newUser = { pseudo: '', email: '', password: '', admin: false, ready: false, canPlayTarot: false, canPlayTwoTables: false };
vm.authToken = (localStorage.getItem('token') || '').trim();
vm.timer = -1;
if (!vm.authToken) {
Expand Down Expand Up @@ -141,6 +143,51 @@ angular.module('meltdownAdmin', [])
})
};

vm.loadUsers = function () {
return $http.get('/admin/users/full').then((resp) => {
vm.users = resp.data.map((user) => ({ ...user, newPassword: '' }));
});
};

vm.createUser = function () {
const payload = { ...vm.newUser };
$http.post('/admin/users/create', payload).then(() => {
vm.newUser = { pseudo: '', email: '', password: '', admin: false, ready: false, canPlayTarot: false, canPlayTwoTables: false };
vm.loadUsers();
});
};

vm.saveUser = function (user) {
const payload = {
pseudo: user.pseudo,
email: user.email,
admin: user.admin,
ready: user.ready,
canPlayTarot: user.canPlayTarot,
canPlayTwoTables: user.canPlayTwoTables,
newPassword: user.newPassword || undefined
};
$http.post('/admin/users/update?userId=' + encodeURIComponent(user.id), payload).then(() => {
user.newPassword = '';
vm.loadUsers();
});
};

vm.deleteUserAdmin = function (user) {
if (!window.confirm(`Supprimer ${user.pseudo} ?`)) {
return;
}
$http.delete('/admin/users/delete?userId=' + encodeURIComponent(user.id)).then(() => {
vm.loadUsers();
});
};

vm.formatTimestamp = function (value) {
if (!value) return '-';
const date = new Date(value);
return isNaN(date.getTime()) ? '-' : date.toLocaleString();
};

vm.toggleUser = function (userName, checked) {
if (checked) {
if (!vm.pseudosSelected.includes(userName)) {
Expand All @@ -158,6 +205,7 @@ angular.module('meltdownAdmin', [])
vm.refreshTables().then(() => {
vm.connectWebsocket();
});
vm.loadUsers();
});

vm.refreshTimer = function () {
Expand Down
Loading