Projet final V5 + Forum V2

This commit is contained in:
Flatlogic Bot 2026-02-19 13:22:08 +00:00
parent f56ab7ba2b
commit 68972bbe6c
8 changed files with 337 additions and 111 deletions

View File

@ -227,7 +227,13 @@ if (empty($content) && empty($attachment_url)) {
}
// Check granular permissions
if (!Permissions::canSendInChannel($user_id, $channel_id)) {
$can_send = Permissions::canSendInChannel($user_id, $channel_id);
if ($thread_id) {
// For threads, we check the specific thread permission instead of the general channel permission
$can_send = Permissions::canDoInChannel($user_id, $channel_id, Permissions::SEND_MESSAGES_IN_THREADS);
}
if (!$can_send) {
echo json_encode(['success' => false, 'error' => 'You do not have permission to send messages in this channel.']);
exit;
}

View File

@ -65,7 +65,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
['value' => 4, 'name' => 'Manage Messages'],
['value' => 8, 'name' => 'Manage Channels'],
['value' => 16, 'name' => 'Manage Server'],
['value' => 32, 'name' => 'Administrator']
['value' => 32, 'name' => 'Administrator'],
['value' => 64, 'name' => 'Create Thread'],
['value' => 128, 'name' => 'Manage Tags'],
['value' => 256, 'name' => 'Pin Threads'],
['value' => 512, 'name' => 'Lock Threads'],
['value' => 1024, 'name' => 'Send Messages in Threads']
]
]);
exit;

View File

@ -29,7 +29,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
$stmt->execute([$channel_id]);
$chan = $stmt->fetch();
if (!$chan || !Permissions::hasPermission($user_id, $chan['server_id'], Permissions::MANAGE_CHANNELS)) {
if (!$chan || !Permissions::canDoInChannel($user_id, $channel_id, Permissions::MANAGE_TAGS)) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}

View File

@ -14,7 +14,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
exit;
}
if (!Permissions::canSendInChannel($user_id, $channel_id)) {
if (!Permissions::canDoInChannel($user_id, $channel_id, Permissions::CREATE_THREAD)) {
echo json_encode(['success' => false, 'error' => 'You do not have permission to create threads in this channel.']);
exit;
}
@ -81,28 +81,41 @@ if ($_SERVER['REQUEST_METHOD'] === 'PATCH' || $_SERVER['REQUEST_METHOD'] === 'DE
Permissions::hasPermission($user_id, $thread['server_id'], Permissions::MANAGE_MESSAGES) ||
$server['owner_id'] == $user_id;
if ($thread['user_id'] != $user_id && !$is_admin) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
try {
if ($action === 'solve') {
if ($thread['user_id'] != $user_id && !$is_admin) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']); exit;
}
$stmt = db()->prepare("UPDATE forum_threads SET solution_message_id = ? WHERE id = ?");
$stmt->execute([$message_id, $thread_id]);
} elseif ($action === 'pin') {
if (!Permissions::canDoInChannel($user_id, $thread['channel_id'], Permissions::PIN_THREADS)) {
echo json_encode(['success' => false, 'error' => 'You do not have permission to pin threads.']); exit;
}
$stmt = db()->prepare("UPDATE forum_threads SET is_pinned = 1 WHERE id = ?");
$stmt->execute([$thread_id]);
} elseif ($action === 'unpin') {
if (!Permissions::canDoInChannel($user_id, $thread['channel_id'], Permissions::PIN_THREADS)) {
echo json_encode(['success' => false, 'error' => 'You do not have permission to unpin threads.']); exit;
}
$stmt = db()->prepare("UPDATE forum_threads SET is_pinned = 0 WHERE id = ?");
$stmt->execute([$thread_id]);
} elseif ($action === 'lock') {
if (!Permissions::canDoInChannel($user_id, $thread['channel_id'], Permissions::LOCK_THREADS)) {
echo json_encode(['success' => false, 'error' => 'You do not have permission to lock threads.']); exit;
}
$stmt = db()->prepare("UPDATE forum_threads SET is_locked = 1 WHERE id = ?");
$stmt->execute([$thread_id]);
} elseif ($action === 'unlock') {
if (!Permissions::canDoInChannel($user_id, $thread['channel_id'], Permissions::LOCK_THREADS)) {
echo json_encode(['success' => false, 'error' => 'You do not have permission to unlock threads.']); exit;
}
$stmt = db()->prepare("UPDATE forum_threads SET is_locked = 0 WHERE id = ?");
$stmt->execute([$thread_id]);
} elseif ($action === 'delete') {
if ($thread['user_id'] != $user_id && !$is_admin) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']); exit;
}
db()->beginTransaction();
// Delete associated tags
$stmt = db()->prepare("DELETE FROM thread_tags WHERE thread_id = ?");

View File

@ -1256,8 +1256,10 @@ document.addEventListener('DOMContentLoaded', () => {
return perm.user_id == id && perm.type === 'member';
}) || { allow_permissions: 0, deny_permissions: 0 };
updateToggleUI(1, p.allow_permissions, p.deny_permissions);
updateToggleUI(2, p.allow_permissions, p.deny_permissions);
document.querySelectorAll('.perm-tri-state').forEach(group => {
const bit = parseInt(group.dataset.permBit);
updateToggleUI(bit, p.allow_permissions, p.deny_permissions);
});
}
function updateToggleUI(bit, allowPerms, denyPerms) {
@ -1311,8 +1313,9 @@ document.addEventListener('DOMContentLoaded', () => {
});
if (!p) {
p = { channel_id: channelId, allow_permissions: 0, deny_permissions: 0 };
p = { channel_id: channelId, allow_permissions: 0, deny_permissions: 0, type: type };
if (type === 'role') p.role_id = id; else p.user_id = id;
channelPermissionsData.push(p);
}
let allow = parseInt(p.allow_permissions);

View File

@ -7,6 +7,11 @@ class Permissions {
const MANAGE_CHANNELS = 8;
const MANAGE_SERVER = 16;
const ADMINISTRATOR = 32;
const CREATE_THREAD = 64;
const MANAGE_TAGS = 128;
const PIN_THREADS = 256;
const LOCK_THREADS = 512;
const SEND_MESSAGES_IN_THREADS = 1024;
public static function hasPermission($user_id, $server_id, $permission) {
$stmt = db()->prepare("SELECT is_admin FROM users WHERE id = ?");
@ -19,11 +24,12 @@ class Permissions {
$server = $stmt->fetch();
if ($server && $server['owner_id'] == $user_id) return true;
// Aggregate permissions from user's roles AND the @everyone role
$stmt = db()->prepare("
SELECT SUM(r.permissions) as total_perms
SELECT BIT_OR(r.permissions) as total_perms
FROM roles r
JOIN user_roles ur ON r.id = ur.role_id
WHERE ur.user_id = ? AND r.server_id = ?
LEFT JOIN user_roles ur ON r.id = ur.role_id AND ur.user_id = ?
WHERE r.server_id = ? AND (ur.user_id IS NOT NULL OR r.name = '@everyone' OR r.name = 'Everyone')
");
$stmt->execute([$user_id, $server_id]);
$row = $stmt->fetch();
@ -34,56 +40,14 @@ class Permissions {
}
public static function canViewChannel($user_id, $channel_id) {
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
$stmt->execute([$channel_id]);
$c = $stmt->fetch();
if (!$c) return false;
$server_id = $c['server_id'];
// Check if owner or admin
if (self::hasPermission($user_id, $server_id, self::ADMINISTRATOR)) return true;
// Fetch overrides for all roles the user has in this server
$stmt = db()->prepare("
SELECT cp.allow_permissions, cp.deny_permissions
FROM channel_permissions cp
JOIN user_roles ur ON cp.role_id = ur.role_id
WHERE ur.user_id = ? AND cp.channel_id = ?
");
$stmt->execute([$user_id, $channel_id]);
$overrides = $stmt->fetchAll();
// Check @everyone override specifically (even if user has no roles assigned)
$stmt = db()->prepare("SELECT id FROM roles WHERE server_id = ? AND (name = '@everyone' OR name = 'Everyone') LIMIT 1");
$stmt->execute([$server_id]);
$everyone_role = $stmt->fetch();
if ($everyone_role) {
$stmt = db()->prepare("SELECT allow_permissions, deny_permissions FROM channel_permissions WHERE channel_id = ? AND role_id = ?");
$stmt->execute([$channel_id, $everyone_role['id']]);
$eo = $stmt->fetch();
if ($eo) {
$overrides[] = $eo;
}
}
if (empty($overrides)) {
return true; // Default to yes
}
$allow = false;
$deny = false;
foreach($overrides as $o) {
if ($o['allow_permissions'] & self::VIEW_CHANNEL) $allow = true;
if ($o['deny_permissions'] & self::VIEW_CHANNEL) $deny = true;
}
if ($allow) return true;
if ($deny) return false;
return true; // Default to yes
return self::canDoInChannel($user_id, $channel_id, self::VIEW_CHANNEL);
}
public static function canSendInChannel($user_id, $channel_id) {
return self::canDoInChannel($user_id, $channel_id, self::SEND_MESSAGES);
}
public static function canDoInChannel($user_id, $channel_id, $permission) {
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
$stmt->execute([$channel_id]);
$c = $stmt->fetch();
@ -93,39 +57,68 @@ class Permissions {
// Check if owner or admin
if (self::hasPermission($user_id, $server_id, self::ADMINISTRATOR)) return true;
// Check overrides
// Fetch all relevant overrides
// 1. @everyone role
// 2. User's roles
// 3. User specifically
// Use a single query to get all relevant overrides
$stmt = db()->prepare("
SELECT cp.allow_permissions, cp.deny_permissions
FROM channel_permissions cp
JOIN user_roles ur ON cp.role_id = ur.role_id
WHERE ur.user_id = ? AND cp.channel_id = ?
SELECT cp.user_id, cp.role_id, cp.allow_permissions, cp.deny_permissions, r.name as role_name
FROM channel_permissions cp
LEFT JOIN roles r ON cp.role_id = r.id
LEFT JOIN user_roles ur ON cp.role_id = ur.role_id AND ur.user_id = ?
WHERE cp.channel_id = ? AND (
cp.user_id = ? OR
ur.user_id IS NOT NULL OR
r.name = '@everyone' OR
r.name = 'Everyone'
)
");
$stmt->execute([$user_id, $channel_id]);
$stmt->execute([$user_id, $channel_id, $user_id]);
$overrides = $stmt->fetchAll();
// Check @everyone override
$stmt = db()->prepare("SELECT id FROM roles WHERE server_id = ? AND (name = '@everyone' OR name = 'Everyone') LIMIT 1");
$stmt->execute([$server_id]);
$everyone_role = $stmt->fetch();
if ($everyone_role) {
$stmt = db()->prepare("SELECT allow_permissions, deny_permissions FROM channel_permissions WHERE channel_id = ? AND role_id = ?");
$stmt->execute([$channel_id, $everyone_role['id']]);
$eo = $stmt->fetch();
if ($eo) {
$overrides[] = $eo;
if (empty($overrides)) {
// No overrides, fallback to global permissions
return self::hasPermission($user_id, $server_id, $permission);
}
// Resolution order (simplified but effective):
// User overrides > Role overrides > @everyone
$user_override = null;
$role_allow = 0;
$role_deny = 0;
$everyone_override = null;
foreach($overrides as $o) {
if ($o['user_id'] == $user_id) {
$user_override = $o;
} elseif ($o['role_name'] === '@everyone' || $o['role_name'] === 'Everyone') {
$everyone_override = $o;
} else {
$role_allow |= (int)$o['allow_permissions'];
$role_deny |= (int)$o['deny_permissions'];
}
}
$allow = false;
$deny = false;
foreach($overrides as $o) {
if ($o['allow_permissions'] & self::SEND_MESSAGES) $allow = true;
if ($o['deny_permissions'] & self::SEND_MESSAGES) $deny = true;
// 1. User specifically
if ($user_override) {
if ($user_override['allow_permissions'] & $permission) return true;
if ($user_override['deny_permissions'] & $permission) return false;
}
if ($allow) return true;
if ($deny) return false;
// 2. Roles
if ($role_allow & $permission) return true;
if ($role_deny & $permission) return false;
return self::hasPermission($user_id, $server_id, self::SEND_MESSAGES);
// 3. @everyone
if ($everyone_override) {
if ($everyone_override['allow_permissions'] & $permission) return true;
if ($everyone_override['deny_permissions'] & $permission) return false;
}
// Fallback to base permissions
return self::hasPermission($user_id, $server_id, $permission);
}
}

145
index.php
View File

@ -719,19 +719,23 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<div class="thread-view-container p-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<a href="?server_id=<?php echo $active_server_id; ?>&channel_id=<?php echo $active_channel_id; ?><?php echo isset($_GET['status']) ? '&status='.htmlspecialchars($_GET['status']) : ''; ?>" class="btn btn-sm btn-outline-secondary"> Back to Forum</a>
<?php if ($active_thread['user_id'] == $current_user_id || $can_manage_server): ?>
<div class="d-flex gap-2">
<div class="d-flex gap-2">
<?php if (Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::PIN_THREADS)): ?>
<button class="btn btn-sm <?php echo $active_thread['is_pinned'] ? 'btn-primary' : 'btn-outline-primary'; ?>" id="toggle-pin-thread" data-id="<?php echo $active_thread['id']; ?>" data-pinned="<?php echo $active_thread['is_pinned']; ?>">
<i class="fa-solid fa-thumbtack me-1"></i> <?php echo $active_thread['is_pinned'] ? 'Unpin' : 'Pin'; ?>
</button>
<?php endif; ?>
<?php if (Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::LOCK_THREADS)): ?>
<button class="btn btn-sm <?php echo $active_thread['is_locked'] ? 'btn-warning' : 'btn-outline-warning'; ?>" id="toggle-lock-thread" data-id="<?php echo $active_thread['id']; ?>" data-locked="<?php echo $active_thread['is_locked']; ?>">
<i class="fa-solid <?php echo $active_thread['is_locked'] ? 'fa-unlock' : 'fa-lock'; ?> me-1"></i> <?php echo $active_thread['is_locked'] ? 'Unlock' : 'Lock'; ?>
</button>
<?php endif; ?>
<?php if ($active_thread['user_id'] == $current_user_id || $can_manage_server): ?>
<button class="btn btn-sm btn-outline-danger" id="delete-thread-btn" data-id="<?php echo $active_thread['id']; ?>" data-channel-id="<?php echo $active_channel_id; ?>" data-server-id="<?php echo $active_server_id; ?>">
<i class="fa-solid fa-trash me-1"></i> Delete
</button>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<h3>
<?php if($active_thread['is_pinned']): ?><i class="fa-solid fa-thumbtack text-primary me-2 small"></i><?php endif; ?>
@ -934,10 +938,12 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
</div>
</div>
<div class="d-flex gap-2">
<?php if($can_manage_channels): ?>
<?php if(Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::MANAGE_TAGS)): ?>
<button class="btn btn-outline-secondary" id="manage-tags-btn">Manage Tags</button>
<?php endif; ?>
<button class="btn btn-primary" id="new-thread-btn">New Discussion</button>
<?php if(Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::CREATE_THREAD)): ?>
<button class="btn btn-primary" id="new-thread-btn">New Discussion</button>
<?php endif; ?>
</div>
</div>
<div class="thread-list">
@ -1104,7 +1110,11 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<div class="chat-input-container">
<?php
require_once 'includes/permissions.php';
$can_send = Permissions::canSendInChannel($current_user_id, $active_channel_id);
if ($active_thread) {
$can_send = Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::SEND_MESSAGES_IN_THREADS);
} else {
$can_send = Permissions::canSendInChannel($current_user_id, $active_channel_id);
}
$show_input = true;
if ($channel_type === 'rules') $show_input = false;
if ($channel_type === 'forum' && !$active_thread) $show_input = false;
@ -2225,24 +2235,109 @@ async function handleSaveUserSettings(btn) {
</div>
</div>
<div class="permission-item mb-3 p-2 rounded" style="background: var(--separator-soft);">
<div class="d-flex justify-content-between align-items-center">
<div class="pe-3">
<div class="fw-bold" style="color: #ffffff; font-size: 0.9em;">Send Messages</div>
<div style="font-size: 0.75em; color: #b5bac1;">Allows members to send messages in this channel.</div>
</div>
<div class="btn-group btn-group-sm perm-tri-state" data-perm-bit="2">
<input type="radio" class="btn-check" name="perm_2" id="perm_2_deny" value="deny">
<label class="btn btn-outline-danger border-0" for="perm_2_deny" title="Deny"><i class="fa-solid fa-xmark"></i></label>
<input type="radio" class="btn-check" name="perm_2" id="perm_2_neutral" value="neutral" checked>
<label class="btn btn-outline-secondary border-0" for="perm_2_neutral" title="Neutral">/</label>
<input type="radio" class="btn-check" name="perm_2" id="perm_2_allow" value="allow">
<label class="btn btn-outline-success border-0" for="perm_2_allow" title="Allow"><i class="fa-solid fa-check"></i></label>
</div>
</div>
</div>
<div class="permission-item mb-3 p-2 rounded" style="background: var(--separator-soft);">
<div class="d-flex justify-content-between align-items-center">
<div class="pe-3">
<div class="fw-bold" style="color: #ffffff; font-size: 0.9em;">Send Messages</div>
<div style="font-size: 0.75em; color: #b5bac1;">Allows members to send messages in this channel.</div>
</div>
<div class="btn-group btn-group-sm perm-tri-state" data-perm-bit="2">
<input type="radio" class="btn-check" name="perm_2" id="perm_2_deny" value="deny">
<label class="btn btn-outline-danger border-0" for="perm_2_deny" title="Deny"><i class="fa-solid fa-xmark"></i></label>
<input type="radio" class="btn-check" name="perm_2" id="perm_2_neutral" value="neutral" checked>
<label class="btn btn-outline-secondary border-0" for="perm_2_neutral" title="Neutral">/</label>
<input type="radio" class="btn-check" name="perm_2" id="perm_2_allow" value="allow">
<label class="btn btn-outline-success border-0" for="perm_2_allow" title="Allow"><i class="fa-solid fa-check"></i></label>
</div>
</div>
</div>
<div class="permission-item mb-3 p-2 rounded" style="background: var(--separator-soft);">
<div class="d-flex justify-content-between align-items-center">
<div class="pe-3">
<div class="fw-bold" style="color: #ffffff; font-size: 0.9em;">Create Thread</div>
<div style="font-size: 0.75em; color: #b5bac1;">Allows members to create new discussions.</div>
</div>
<div class="btn-group btn-group-sm perm-tri-state" data-perm-bit="64">
<input type="radio" class="btn-check" name="perm_64" id="perm_64_deny" value="deny">
<label class="btn btn-outline-danger border-0" for="perm_64_deny" title="Deny"><i class="fa-solid fa-xmark"></i></label>
<input type="radio" class="btn-check" name="perm_64" id="perm_64_neutral" value="neutral" checked>
<label class="btn btn-outline-secondary border-0" for="perm_64_neutral" title="Neutral">/</label>
<input type="radio" class="btn-check" name="perm_64" id="perm_64_allow" value="allow">
<label class="btn btn-outline-success border-0" for="perm_64_allow" title="Allow"><i class="fa-solid fa-check"></i></label>
</div>
</div>
</div>
<div class="permission-item mb-3 p-2 rounded" style="background: var(--separator-soft);">
<div class="d-flex justify-content-between align-items-center">
<div class="pe-3">
<div class="fw-bold" style="color: #ffffff; font-size: 0.9em;">Manage Tags</div>
<div style="font-size: 0.75em; color: #b5bac1;">Allows adding/modifying forum tags.</div>
</div>
<div class="btn-group btn-group-sm perm-tri-state" data-perm-bit="128">
<input type="radio" class="btn-check" name="perm_128" id="perm_128_deny" value="deny">
<label class="btn btn-outline-danger border-0" for="perm_128_deny" title="Deny"><i class="fa-solid fa-xmark"></i></label>
<input type="radio" class="btn-check" name="perm_128" id="perm_128_neutral" value="neutral" checked>
<label class="btn btn-outline-secondary border-0" for="perm_128_neutral" title="Neutral">/</label>
<input type="radio" class="btn-check" name="perm_128" id="perm_128_allow" value="allow">
<label class="btn btn-outline-success border-0" for="perm_128_allow" title="Allow"><i class="fa-solid fa-check"></i></label>
</div>
</div>
</div>
<div class="permission-item mb-3 p-2 rounded" style="background: var(--separator-soft);">
<div class="d-flex justify-content-between align-items-center">
<div class="pe-3">
<div class="fw-bold" style="color: #ffffff; font-size: 0.9em;">Pin Threads</div>
<div style="font-size: 0.75em; color: #b5bac1;">Allows pinning/unpinning threads.</div>
</div>
<div class="btn-group btn-group-sm perm-tri-state" data-perm-bit="256">
<input type="radio" class="btn-check" name="perm_256" id="perm_256_deny" value="deny">
<label class="btn btn-outline-danger border-0" for="perm_256_deny" title="Deny"><i class="fa-solid fa-xmark"></i></label>
<input type="radio" class="btn-check" name="perm_256" id="perm_256_neutral" value="neutral" checked>
<label class="btn btn-outline-secondary border-0" for="perm_256_neutral" title="Neutral">/</label>
<input type="radio" class="btn-check" name="perm_256" id="perm_256_allow" value="allow">
<label class="btn btn-outline-success border-0" for="perm_256_allow" title="Allow"><i class="fa-solid fa-check"></i></label>
</div>
</div>
</div>
<div class="permission-item mb-3 p-2 rounded" style="background: var(--separator-soft);">
<div class="d-flex justify-content-between align-items-center">
<div class="pe-3">
<div class="fw-bold" style="color: #ffffff; font-size: 0.9em;">Lock Threads</div>
<div style="font-size: 0.75em; color: #b5bac1;">Allows locking/unlocking threads.</div>
</div>
<div class="btn-group btn-group-sm perm-tri-state" data-perm-bit="512">
<input type="radio" class="btn-check" name="perm_512" id="perm_512_deny" value="deny">
<label class="btn btn-outline-danger border-0" for="perm_512_deny" title="Deny"><i class="fa-solid fa-xmark"></i></label>
<input type="radio" class="btn-check" name="perm_512" id="perm_512_neutral" value="neutral" checked>
<label class="btn btn-outline-secondary border-0" for="perm_512_neutral" title="Neutral">/</label>
<input type="radio" class="btn-check" name="perm_512" id="perm_512_allow" value="allow">
<label class="btn btn-outline-success border-0" for="perm_512_allow" title="Allow"><i class="fa-solid fa-check"></i></label>
</div>
</div>
</div>
<div class="permission-item mb-3 p-2 rounded" style="background: var(--separator-soft);">
<div class="d-flex justify-content-between align-items-center">
<div class="pe-3">
<div class="fw-bold" style="color: #ffffff; font-size: 0.9em;">Send Messages in Threads</div>
<div style="font-size: 0.75em; color: #b5bac1;">Allows posting in unlocked threads.</div>
</div>
<div class="btn-group btn-group-sm perm-tri-state" data-perm-bit="1024">
<input type="radio" class="btn-check" name="perm_1024" id="perm_1024_deny" value="deny">
<label class="btn btn-outline-danger border-0" for="perm_1024_deny" title="Deny"><i class="fa-solid fa-xmark"></i></label>
<input type="radio" class="btn-check" name="perm_1024" id="perm_1024_neutral" value="neutral" checked>
<label class="btn btn-outline-secondary border-0" for="perm_1024_neutral" title="Neutral">/</label>
<input type="radio" class="btn-check" name="perm_1024" id="perm_1024_allow" value="allow">
<label class="btn btn-outline-success border-0" for="perm_1024_allow" title="Allow"><i class="fa-solid fa-check"></i></label>
</div>
</div>
</div>
<!-- More permissions can be added here -->
</div>
</div>

View File

@ -807,3 +807,114 @@
2026-02-19 12:41:48 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 12:41:52 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:41:56 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 12:43:11 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 12:43:25 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:43:45 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 12:44:28 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:44:41 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 12:52:27 - GET /?fl_project=38443 - POST: []
2026-02-19 12:52:31 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 12:53:05 - GET /index.php - POST: []
2026-02-19 12:53:07 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:53:11 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 12:53:22 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 12:53:31 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 12:53:37 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 12:53:43 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:53:46 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 12:53:51 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:53:53 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 12:54:03 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 12:54:07 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 12:54:10 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:54:12 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 12:54:22 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:54:23 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 12:54:30 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 12:54:32 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:54:35 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:54:45 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 12:54:51 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 12:54:56 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 12:55:11 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 12:55:21 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 12:55:23 - GET /index.php?server_id=1&channel_id=13&status=unresolved - POST: []
2026-02-19 12:55:25 - GET /index.php?server_id=1&channel_id=13&status=resolved - POST: []
2026-02-19 12:55:27 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 12:55:44 - GET /index.php?server_id=1&channel_id=13&thread_id=2&status=all - POST: []
2026-02-19 12:56:08 - GET /index.php?server_id=1&channel_id=13&thread_id=2&status=all - POST: []
2026-02-19 12:56:11 - GET /index.php?server_id=1&channel_id=13&thread_id=2&status=all - POST: []
2026-02-19 12:56:13 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 12:56:19 - GET /index.php?server_id=1&channel_id=13&thread_id=2&status=all - POST: []
2026-02-19 12:56:39 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 12:56:41 - GET /index.php?server_id=1&channel_id=13&thread_id=3&status=all - POST: []
2026-02-19 12:56:49 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 12:57:00 - GET /index.php?server_id=1&channel_id=13&thread_id=3&status=all - POST: []
2026-02-19 12:57:10 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 12:57:20 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:01:56 - GET /?fl_project=38443 - POST: []
2026-02-19 13:03:44 - GET /?fl_project=38443 - POST: []
2026-02-19 13:04:14 - GET /?fl_project=38443 - POST: []
2026-02-19 13:09:42 - GET /?fl_project=38443 - POST: []
2026-02-19 13:09:49 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 13:09:53 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:10:00 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:10:09 - GET /index.php?server_id=1&channel_id=13&thread_id=3&status=all - POST: []
2026-02-19 13:10:12 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:10:17 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:10:19 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:10:34 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:10:46 - GET /index.php?server_id=1&channel_id=21 - POST: []
2026-02-19 13:10:54 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:11:06 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:11:18 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:11:37 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:11:49 - GET /index.php?server_id=1&channel_id=13&status=all - POST: []
2026-02-19 13:11:51 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:11:51 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 13:11:54 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:11:57 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 13:11:59 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:12:03 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 13:12:05 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:12:07 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 13:12:23 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 13:12:26 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:12:28 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 13:12:31 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:12:32 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 13:12:34 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:12:44 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:12:45 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 13:12:48 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:12:49 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 13:12:54 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 13:12:58 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 13:13:01 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:13:03 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 13:13:05 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 13:13:07 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:13:12 - GET /index.php?server_id=1&channel_id=13&thread_id=4 - POST: []
2026-02-19 13:13:14 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:13:21 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:13:30 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:13:45 - GET /index.php?server_id=1&channel_id=13&thread_id=5 - POST: []
2026-02-19 13:13:48 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:14:34 - GET /index.php?server_id=1&channel_id=17 - POST: []
2026-02-19 13:14:36 - GET /index.php?server_id=1&channel_id=17 - POST: []
2026-02-19 13:14:38 - GET /index.php?server_id=1&channel_id=17 - POST: []
2026-02-19 13:15:06 - GET /index.php?server_id=1&channel_id=17 - POST: []
2026-02-19 13:15:08 - GET /index.php?server_id=1&channel_id=1 - POST: []
2026-02-19 13:15:12 - GET /index.php?server_id=1&channel_id=1 - POST: []
2026-02-19 13:15:30 - GET /index.php - POST: []
2026-02-19 13:15:33 - GET /index.php - POST: []
2026-02-19 13:15:34 - GET /index.php - POST: []
{"date":"2026-02-19 13:16:18","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_input_device":"OPTIyYzx3bNI0gtW62vaTCb7SxzY5rNnwOw5G42w36M=","voice_output_device":"znHy1zh6U7iZkBs7ovKSXvb3r4k0jk0DBbg\/TtaWmwk=","voice_input_volume":"1","voice_output_volume":"1","voice_echo_cancellation":"1","voice_noise_suppression":"1","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.11","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
{"date":"2026-02-19 13:17:25","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_input_device":"default","voice_output_device":"default","voice_input_volume":"1","voice_output_volume":"1","voice_echo_cancellation":"1","voice_noise_suppression":"1","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
2026-02-19 13:19:38 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:19:56 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 13:19:59 - GET /index.php?server_id=1&channel_id=13&thread_id=2 - POST: []
2026-02-19 13:20:01 - GET /index.php?server_id=1&channel_id=13 - POST: []
2026-02-19 13:20:10 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 13:20:12 - GET /index.php?server_id=1&channel_id=13&thread_id=3 - POST: []
2026-02-19 13:20:13 - GET /index.php?server_id=1&channel_id=13 - POST: []