Added personal role management.
This commit is contained in:
parent
3cb971aa03
commit
e7cddfd37e
12 changed files with 226 additions and 31 deletions
66
assets/less/classes/settings/role.less
Normal file
66
assets/less/classes/settings/role.less
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
.settings__role {
|
||||||
|
border: 1px solid var(--accent-colour);
|
||||||
|
background-color: var(--accent-colour);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 2px;
|
||||||
|
overflow: none;
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
&__collection {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
background-color: var(--background-colour-translucent);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
font-size: 1.2em;
|
||||||
|
line-height: 1.7em;
|
||||||
|
border-bottom: 1px solid var(--accent-colour);
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
font-size: .9em;
|
||||||
|
line-height: 1.8em;
|
||||||
|
padding: 0 2px;
|
||||||
|
margin: 0 2px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__options {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.5em;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__option {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
text-shadow: inherit;
|
||||||
|
transition: color .2s;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
&:not(&--disabled):hover {
|
||||||
|
color: var(--accent-colour);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
opacity: .2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&--current {
|
&--current {
|
||||||
// todo: make this now bad
|
// todo: make this not bad
|
||||||
background-image: linear-gradient(0deg, #111c, #111c);
|
background-image: linear-gradient(0deg, #111c, #111c);
|
||||||
background-color: var(--accent-colour);
|
background-color: var(--accent-colour);
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,6 +133,7 @@ body {
|
||||||
@import "classes/settings/login-history";
|
@import "classes/settings/login-history";
|
||||||
@import "classes/settings/session";
|
@import "classes/settings/session";
|
||||||
@import "classes/settings/sessions";
|
@import "classes/settings/sessions";
|
||||||
|
@import "classes/settings/role";
|
||||||
|
|
||||||
// News
|
// News
|
||||||
@import "classes/news/container";
|
@import "classes/news/container";
|
||||||
|
|
56
build.php
56
build.php
|
@ -141,23 +141,35 @@ function recursiveConcat(string $source, string $existing = ''): string
|
||||||
return $existing;
|
return $existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$doAll = empty($argv[1]) || $argv[1] === 'all';
|
||||||
|
$doCss = $doAll || $argv[1] === 'css';
|
||||||
|
$doJs = $doAll || $argv[1] === 'js';
|
||||||
|
|
||||||
// Make sure we're running from the misuzu root directory.
|
// Make sure we're running from the misuzu root directory.
|
||||||
chdir(__DIR__);
|
chdir(__DIR__);
|
||||||
|
|
||||||
misuzu_log('Cleanup');
|
misuzu_log('Cleanup');
|
||||||
createDirIfNotExist(CSS_DIR);
|
|
||||||
createDirIfNotExist(JS_DIR);
|
|
||||||
deleteAllFilesInDir(CSS_DIR, '*.css');
|
|
||||||
deleteAllFilesInDir(JS_DIR, '*.js');
|
|
||||||
deleteAllFilesInDir(TS_DIR, '*.d.ts');
|
|
||||||
|
|
||||||
misuzu_log();
|
if ($doCss) {
|
||||||
misuzu_log('Compiling LESS');
|
createDirIfNotExist(CSS_DIR);
|
||||||
|
deleteAllFilesInDir(CSS_DIR, '*.css');
|
||||||
|
}
|
||||||
|
|
||||||
if (!is_file(LESS_DIR . LESS_ENTRY_POINT)) {
|
if ($doJs) {
|
||||||
misuzu_log('==> ERR: Entry point for this style does not exist (' . basename(LESS_ENTRY_POINT) . ')');
|
createDirIfNotExist(JS_DIR);
|
||||||
} else {
|
deleteAllFilesInDir(JS_DIR, '*.js');
|
||||||
system(sprintf(LESS_CMD, escapeshellarg(LESS_DIR . LESS_ENTRY_POINT), LESS_DEST));
|
deleteAllFilesInDir(TS_DIR, '*.d.ts');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($doCss) {
|
||||||
|
misuzu_log();
|
||||||
|
misuzu_log('Compiling LESS');
|
||||||
|
|
||||||
|
if (!is_file(LESS_DIR . LESS_ENTRY_POINT)) {
|
||||||
|
misuzu_log('==> ERR: Entry point for this style does not exist (' . basename(LESS_ENTRY_POINT) . ')');
|
||||||
|
} else {
|
||||||
|
system(sprintf(LESS_CMD, escapeshellarg(LESS_DIR . LESS_ENTRY_POINT), LESS_DEST));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
misuzu_log();
|
misuzu_log();
|
||||||
|
@ -169,16 +181,22 @@ define('IMPORT_SEQ', [
|
||||||
'files' => NODE_IMPORT_CSS,
|
'files' => NODE_IMPORT_CSS,
|
||||||
'destination' => NODE_DEST_CSS,
|
'destination' => NODE_DEST_CSS,
|
||||||
'insert-semicolon' => false,
|
'insert-semicolon' => false,
|
||||||
|
'do' => $doCss,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'JavaScript',
|
'name' => 'JavaScript',
|
||||||
'files' => NODE_IMPORT_JS,
|
'files' => NODE_IMPORT_JS,
|
||||||
'destination' => NODE_DEST_JS,
|
'destination' => NODE_DEST_JS,
|
||||||
'insert-semicolon' => true,
|
'insert-semicolon' => true,
|
||||||
|
'do' => $doJs,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
foreach (IMPORT_SEQ as $sequence) {
|
foreach (IMPORT_SEQ as $sequence) {
|
||||||
|
if (!$sequence['do']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
misuzu_log("=> {$sequence['name']}");
|
misuzu_log("=> {$sequence['name']}");
|
||||||
|
|
||||||
$contents = '';
|
$contents = '';
|
||||||
|
@ -203,12 +221,14 @@ foreach (IMPORT_SEQ as $sequence) {
|
||||||
file_put_contents($sequence['destination'], $contents);
|
file_put_contents($sequence['destination'], $contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
misuzu_log();
|
if ($doJs) {
|
||||||
misuzu_log('Compiling TypeScript');
|
misuzu_log();
|
||||||
misuzu_log(shell_exec('tsc --extendedDiagnostics -p tsconfig.json'));
|
misuzu_log('Compiling TypeScript');
|
||||||
file_put_contents(TS_DEST, recursiveConcat(TS_SRC));
|
misuzu_log(shell_exec('tsc --extendedDiagnostics -p tsconfig.json'));
|
||||||
deleteAllFilesInDir(TS_SRC, '*.js');
|
file_put_contents(TS_DEST, recursiveConcat(TS_SRC));
|
||||||
rmdir(TS_SRC);
|
deleteAllFilesInDir(TS_SRC, '*.js');
|
||||||
|
rmdir(TS_SRC);
|
||||||
|
}
|
||||||
|
|
||||||
misuzu_log();
|
misuzu_log();
|
||||||
misuzu_log('Copying data...');
|
misuzu_log('Copying data...');
|
||||||
|
@ -222,7 +242,7 @@ foreach (NODE_COPY_DIRECTORY as $source => $dest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// no need to do this in debug mode, auto reload is enabled and cache is disabled
|
// no need to do this in debug mode, auto reload is enabled and cache is disabled
|
||||||
if (!file_exists(__DIR__ . '/.debug')) {
|
if ($doAll && !file_exists(__DIR__ . '/.debug')) {
|
||||||
// Clear Twig cache
|
// Clear Twig cache
|
||||||
misuzu_log();
|
misuzu_log();
|
||||||
misuzu_log('Deleting template cache');
|
misuzu_log('Deleting template cache');
|
||||||
|
|
22
database/2018_11_17_191807_roles_table_extensions.php
Normal file
22
database/2018_11_17_191807_roles_table_extensions.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
namespace Misuzu\DatabaseMigrations\RolesTableExtensions;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
function migrate_up(PDO $conn): void
|
||||||
|
{
|
||||||
|
$conn->exec("
|
||||||
|
ALTER TABLE `msz_roles`
|
||||||
|
CHANGE COLUMN `role_secret` `role_hidden` TINYINT(1) NOT NULL DEFAULT '0' AFTER `role_description`,
|
||||||
|
ADD COLUMN `role_can_leave` TINYINT(1) NOT NULL DEFAULT '0' AFTER `role_hidden`;
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrate_down(PDO $conn): void
|
||||||
|
{
|
||||||
|
$conn->exec("
|
||||||
|
ALTER TABLE `msz_roles`
|
||||||
|
CHANGE COLUMN `role_hidden` `role_secret` TINYINT(1) NOT NULL DEFAULT '0' AFTER `role_description`,
|
||||||
|
DROP COLUMN `role_can_leave`;
|
||||||
|
");
|
||||||
|
}
|
|
@ -375,12 +375,12 @@ switch ($_GET['v'] ?? null) {
|
||||||
$updateRole = db_prepare('
|
$updateRole = db_prepare('
|
||||||
INSERT INTO `msz_roles`
|
INSERT INTO `msz_roles`
|
||||||
(
|
(
|
||||||
`role_name`, `role_hierarchy`, `role_secret`, `role_colour`,
|
`role_name`, `role_hierarchy`, `role_hidden`, `role_colour`,
|
||||||
`role_description`, `role_title`
|
`role_description`, `role_title`
|
||||||
)
|
)
|
||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
:role_name, :role_hierarchy, :role_secret, :role_colour,
|
:role_name, :role_hierarchy, :role_hidden, :role_colour,
|
||||||
:role_description, :role_title
|
:role_description, :role_title
|
||||||
)
|
)
|
||||||
');
|
');
|
||||||
|
@ -389,7 +389,7 @@ switch ($_GET['v'] ?? null) {
|
||||||
UPDATE `msz_roles`
|
UPDATE `msz_roles`
|
||||||
SET `role_name` = :role_name,
|
SET `role_name` = :role_name,
|
||||||
`role_hierarchy` = :role_hierarchy,
|
`role_hierarchy` = :role_hierarchy,
|
||||||
`role_secret` = :role_secret,
|
`role_hidden` = :role_hidden,
|
||||||
`role_colour` = :role_colour,
|
`role_colour` = :role_colour,
|
||||||
`role_description` = :role_description,
|
`role_description` = :role_description,
|
||||||
`role_title` = :role_title
|
`role_title` = :role_title
|
||||||
|
@ -400,7 +400,7 @@ switch ($_GET['v'] ?? null) {
|
||||||
|
|
||||||
$updateRole->bindValue('role_name', $roleName);
|
$updateRole->bindValue('role_name', $roleName);
|
||||||
$updateRole->bindValue('role_hierarchy', $roleHierarchy);
|
$updateRole->bindValue('role_hierarchy', $roleHierarchy);
|
||||||
$updateRole->bindValue('role_secret', $roleSecret ? 1 : 0);
|
$updateRole->bindValue('role_hidden', $roleSecret ? 1 : 0);
|
||||||
$updateRole->bindValue('role_colour', $roleColour);
|
$updateRole->bindValue('role_colour', $roleColour);
|
||||||
$updateRole->bindValue('role_description', $roleDescription);
|
$updateRole->bindValue('role_description', $roleDescription);
|
||||||
$updateRole->bindValue('role_title', $roleTitle);
|
$updateRole->bindValue('role_title', $roleTitle);
|
||||||
|
|
|
@ -78,7 +78,7 @@ if (!$role) {
|
||||||
$roles = db_query('
|
$roles = db_query('
|
||||||
SELECT `role_id`, `role_name`, `role_colour`
|
SELECT `role_id`, `role_name`, `role_colour`
|
||||||
FROM `msz_roles`
|
FROM `msz_roles`
|
||||||
WHERE `role_secret` = 0
|
WHERE `role_hidden` = 0
|
||||||
ORDER BY `role_id`
|
ORDER BY `role_id`
|
||||||
')->fetchAll(PDO::FETCH_ASSOC);
|
')->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
|
|
@ -51,9 +51,29 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$disableAccountOptions) {
|
if (!empty($_POST['role'])) {
|
||||||
$currentPasswordValid = !empty($_POST['current_password']);
|
$roleId = (int)($_POST['role']['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($roleId > 0 && user_role_has(user_session_current('user_id'), $roleId)) {
|
||||||
|
switch ($_POST['role']['mode'] ?? '') {
|
||||||
|
case 'display':
|
||||||
|
user_role_set_display(user_session_current('user_id'), $roleId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'leave':
|
||||||
|
if (user_role_can_leave($roleId)) {
|
||||||
|
user_role_remove(user_session_current('user_id'), $roleId);
|
||||||
|
} else {
|
||||||
|
$errors[] = "You're not allow to leave this role, an administrator has to remove it for you.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$errors[] = "You're trying to modify a role that hasn't been assigned to you.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$disableAccountOptions && !empty($_POST['current_password'])) {
|
||||||
if (!user_password_verify_db(user_session_current('user_id'), $_POST['current_password'] ?? '')) {
|
if (!user_password_verify_db(user_session_current('user_id'), $_POST['current_password'] ?? '')) {
|
||||||
$errors[] = 'Your password was incorrect.';
|
$errors[] = 'Your password was incorrect.';
|
||||||
} else {
|
} else {
|
||||||
|
@ -158,17 +178,16 @@ $logins['list'] = user_login_attempts_list($sessions['offset'], $sessions['take'
|
||||||
$logs['list'] = audit_log_list($logs['offset'], $logs['take'], user_session_current('user_id'));
|
$logs['list'] = audit_log_list($logs['offset'], $logs['take'], user_session_current('user_id'));
|
||||||
|
|
||||||
$getUserRoles = db_prepare('
|
$getUserRoles = db_prepare('
|
||||||
SELECT r.`role_id`, r.`role_name`
|
SELECT r.`role_id`, r.`role_name`, r.`role_description`, r.`role_colour`, r.`role_can_leave`
|
||||||
FROM `msz_user_roles` as ur
|
FROM `msz_user_roles` as ur
|
||||||
LEFT JOIN `msz_roles` as r
|
LEFT JOIN `msz_roles` as r
|
||||||
ON r.`role_id` = ur.`role_id`
|
ON r.`role_id` = ur.`role_id`
|
||||||
WHERE ur.`user_id` = :user_id
|
WHERE ur.`user_id` = :user_id
|
||||||
|
ORDER BY r.`role_hierarchy` DESC
|
||||||
');
|
');
|
||||||
$getUserRoles->bindValue('user_id', user_session_current('user_id'));
|
$getUserRoles->bindValue('user_id', user_session_current('user_id'));
|
||||||
$userRoles = $getUserRoles->execute() ? $getUserRoles->fetchAll(PDO::FETCH_ASSOC) : [];
|
$userRoles = $getUserRoles->execute() ? $getUserRoles->fetchAll(PDO::FETCH_ASSOC) : [];
|
||||||
|
|
||||||
var_dump($userRoles);
|
|
||||||
|
|
||||||
if (empty($errors)) { // delete this in 2019
|
if (empty($errors)) { // delete this in 2019
|
||||||
$errors[] = 'A few of the elements on this page have been moved to the on-profile editor. To find them, go to your profile and hit the "Edit Profile" button below your avatar.';
|
$errors[] = 'A few of the elements on this page have been moved to the on-profile editor. To find them, go to your profile and hit the "Edit Profile" button below your avatar.';
|
||||||
}
|
}
|
||||||
|
@ -180,5 +199,6 @@ echo tpl_render('user.settings', [
|
||||||
'sessions' => $sessions,
|
'sessions' => $sessions,
|
||||||
'logins' => $logins,
|
'logins' => $logins,
|
||||||
'logs' => $logs,
|
'logs' => $logs,
|
||||||
'roles' => $userRoles,
|
'user_roles' => $userRoles,
|
||||||
|
'user_display_role' => user_role_get_display(user_session_current('user_id')),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -26,6 +26,17 @@ function user_role_remove(int $userId, int $roleId): bool
|
||||||
return $removeRole->execute();
|
return $removeRole->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function user_role_can_leave(int $roleId): bool
|
||||||
|
{
|
||||||
|
$canLeaveRole = db_prepare('
|
||||||
|
SELECT `role_can_leave` != 0
|
||||||
|
FROM `msz_roles`
|
||||||
|
WHERE `role_id` = :role_id
|
||||||
|
');
|
||||||
|
$canLeaveRole->bindValue('role_id', $roleId);
|
||||||
|
return $canLeaveRole->execute() ? (bool)$canLeaveRole->fetchColumn() : false;
|
||||||
|
}
|
||||||
|
|
||||||
function user_role_has(int $userId, int $roleId): bool
|
function user_role_has(int $userId, int $roleId): bool
|
||||||
{
|
{
|
||||||
$hasRole = db_prepare('
|
$hasRole = db_prepare('
|
||||||
|
@ -55,3 +66,18 @@ function user_role_set_display(int $userId, int $roleId): bool
|
||||||
|
|
||||||
return $setDisplay->execute();
|
return $setDisplay->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function user_role_get_display(int $userId): int
|
||||||
|
{
|
||||||
|
if ($userId < 1) {
|
||||||
|
return MSZ_ROLE_MAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fetchRole = db_prepare('
|
||||||
|
SELECT `display_role`
|
||||||
|
FROM `msz_users`
|
||||||
|
WHERE `user_id` = :user_id
|
||||||
|
');
|
||||||
|
$fetchRole->bindValue('user_id', $userId);
|
||||||
|
return $fetchRole->execute() ? (int)$fetchRole->fetchColumn() : MSZ_ROLE_MAIN;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
// Quick note to myself and others about the `display_role` column in the users database.
|
||||||
|
// Never ever EVER use it for ANYTHING other than determining display colours, there's a small chance that it might not be accurate.
|
||||||
|
// And even if it were, roles properties are aggregated and thus must all be accounted for.
|
||||||
|
|
||||||
define('MSZ_PERM_USER_EDIT_PROFILE', 1);
|
define('MSZ_PERM_USER_EDIT_PROFILE', 1);
|
||||||
define('MSZ_PERM_USER_CHANGE_AVATAR', 1 << 1);
|
define('MSZ_PERM_USER_CHANGE_AVATAR', 1 << 1);
|
||||||
define('MSZ_PERM_USER_CHANGE_BACKGROUND', 1 << 2);
|
define('MSZ_PERM_USER_CHANGE_BACKGROUND', 1 << 2);
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Hide Rank</div>
|
<div class="form__label__text">Hide Rank</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
{{ input_checkbox('role[secret]', '', edit_role is defined and edit_role.role_secret) }}
|
{{ input_checkbox('role[secret]', '', edit_role is defined and edit_role.role_hidden) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,42 @@
|
||||||
<div class="settings__description">
|
<div class="settings__description">
|
||||||
<p>This is a listing of the user roles you're a part of, you can select which you want to leave or which one you want to boast as your main role which will change your username colour accordingly.</p>
|
<p>This is a listing of the user roles you're a part of, you can select which you want to leave or which one you want to boast as your main role which will change your username colour accordingly.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings__role__collection">
|
||||||
|
{% for role in user_roles %}
|
||||||
|
{% set is_display_role = user_display_role == role.role_id %}
|
||||||
|
|
||||||
|
<div class="settings__role" style="{{ role.role_colour|html_colour('--accent-colour') }}">
|
||||||
|
<div class="settings__role__content">
|
||||||
|
<div class="settings__role__name">
|
||||||
|
{{ role.role_name }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings__role__description">
|
||||||
|
{{ role.role_description }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="settings__role__options" method="post" action="/settings.php">
|
||||||
|
{{ input_csrf('settings') }}
|
||||||
|
{{ input_hidden('role[id]', role.role_id) }}
|
||||||
|
|
||||||
|
<button class="settings__role__option{% if is_display_role %} settings__role__option--disabled{% endif %}"
|
||||||
|
name="role[mode]" value="display" title="Set this as your display role"
|
||||||
|
{% if is_display_role %}disabled{% endif %}>
|
||||||
|
<i class="far {{ is_display_role ? 'fa-check-square' : 'fa-square' }}"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="settings__role__option{% if not role.role_can_leave %} settings__role__option--disabled{% endif %}"
|
||||||
|
name="role[mode]" value="leave" title="Leave this role"
|
||||||
|
onclick="return confirm('ARE YOU SURE DAWG?')"
|
||||||
|
{% if not role.role_can_leave %}disabled{% endif %}>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container settings__container" id="sessions">
|
<div class="container settings__container" id="sessions">
|
||||||
|
|
Loading…
Add table
Reference in a new issue