diff --git a/src/main/frontend/src/lib/pages/IndefBansPage.svelte b/src/main/frontend/src/lib/pages/IndefBansPage.svelte
index 2f19775..e00053d 100644
--- a/src/main/frontend/src/lib/pages/IndefBansPage.svelte
+++ b/src/main/frontend/src/lib/pages/IndefBansPage.svelte
@@ -5,29 +5,69 @@
import {api} from '$lib/api';
import {Card} from '$lib/components/ui/card';
import {Input} from '$lib/components/ui/input';
- import {lowerSearch, titleCase} from '$lib/utils';
+ import {lowerSearch} from '$lib/utils';
+
+ interface BanGroup {
+ usernames: string[];
+ uuids: string[];
+ ips: string[];
+ reason: string;
+ }
let bans: Array> = $state([]);
let loading = $state(true);
let error = $state(null);
let filter = $state('');
- const visible = $derived(bans.filter((ban) => !filter.trim() || lowerSearch(ban).includes(filter.toLowerCase().trim())));
+ const groups = $derived(bans.map(toGroup));
+ const visible = $derived.by(() => {
+ const q = filter.toLowerCase().trim();
+ return groups.filter((group) => !q || lowerSearch(group).includes(q));
+ });
const totals = $derived.by(() => {
- const text = JSON.stringify(bans);
return {
- groups: bans.length,
- users: (text.match(/user(name)?/gi) ?? []).length,
- uuids: (text.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi) ?? []).length,
- ips: (text.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g) ?? []).length
+ groups: groups.length,
+ users: groups.reduce((total, group) => total + group.usernames.length, 0),
+ uuids: groups.reduce((total, group) => total + group.uuids.length, 0),
+ ips: groups.reduce((total, group) => total + group.ips.length, 0)
};
});
- function display(value: unknown): string {
- if (value == null || value === '') return '-';
- if (Array.isArray(value)) return value.map(display).join(', ');
- if (typeof value === 'object') return JSON.stringify(value);
- return String(value);
+ function isRecord(value: unknown): value is Record {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+ }
+
+ function listValues(value: unknown): string[] {
+ if (value == null) return [];
+ if (Array.isArray(value)) return value.flatMap(listValues);
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed ? [trimmed] : [];
+ }
+ if (typeof value === 'number' || typeof value === 'boolean') return [String(value)];
+ if (isRecord(value)) return Object.values(value).flatMap(listValues);
+ return [];
+ }
+
+ function toGroup(ban: Record): BanGroup {
+ const entries = Object.entries(ban);
+ const nested = entries.length === 1 && isRecord(entries[0][1]) ? entries[0][1] : ban;
+ const reason = listValues(nested.reason).join(', ');
+
+ return {
+ usernames: listValues(nested.usernames ?? nested.users ?? nested.names),
+ uuids: listValues(nested.uuids ?? nested.uuid),
+ ips: listValues(nested.ips ?? nested.ip),
+ reason: reason || listValues(ban.reason).join(', ')
+ };
+ }
+
+ function entryCount(group: BanGroup): number {
+ return group.usernames.length + group.uuids.length + group.ips.length;
+ }
+
+ function groupKey(group: BanGroup, index: number): string {
+ return `${index}:${group.usernames[0] ?? ''}:${group.uuids[0] ?? ''}:${group.ips[0] ?? ''}`;
}
onMount(async () => {
@@ -45,7 +85,7 @@
Indefinite bans
{totals.groups} groups
- {totals.users} user keys
+ {totals.users} users
{totals.uuids} uuids
{totals.ips} ips
@@ -63,18 +103,52 @@
Loading bans...
{:else if error}
{error}
+{:else if groups.length === 0}
+
+ No indefinite bans configured.
+
{:else if visible.length === 0}
No indefinite bans match that filter.
{:else}
- {#each visible as ban, index (index)}
-
- Group {index + 1}
-
- {#each Object.entries(ban) as [key, value] (key)}
- - {titleCase(key)}
- - {display(value)}
- {/each}
+ {#each visible as group, index (groupKey(group, index))}
+ {@const total = entryCount(group)}
+
+
+
+ {#if group.usernames.length}
+ - Users
+ -
+ {#each group.usernames as username, usernameIndex (`${username}:${usernameIndex}`)}
+ {username}
+ {/each}
+
+ {/if}
+ {#if group.uuids.length}
+ - UUIDs
+ -
+ {#each group.uuids as uuid, uuidIndex (`${uuid}:${uuidIndex}`)}
+ {uuid}
+ {/each}
+
+ {/if}
+ {#if group.ips.length}
+ - IPs
+ -
+ {#each group.ips as ip, ipIndex (`${ip}:${ipIndex}`)}
+ {ip}
+ {/each}
+
+ {/if}
{/each}