fix: various fixes to sentry-reported errors and more

This commit is contained in:
Hampus Kraft
2026-02-18 15:38:51 +00:00
parent 302c0d2a0c
commit 0517a966a3
357 changed files with 25420 additions and 16281 deletions

View File

@@ -105,6 +105,14 @@ const ReindexControls: FC<{config: Config; csrfToken: string}> = ({config, csrfT
<ReindexButton config={config} title="Guilds" indexType="guilds" csrfToken={csrfToken} />
<ReindexButton config={config} title="Reports" indexType="reports" csrfToken={csrfToken} />
<ReindexButton config={config} title="Audit Logs" indexType="audit_logs" csrfToken={csrfToken} />
<Heading level={3} class="subtitle mt-6 text-neutral-900">
Discovery Index
</Heading>
<Text color="muted" size="sm" class="mb-3">
Rebuilds the discovery search index for all approved discoverable communities. This syncs guild metadata,
descriptions, categories, and online counts.
</Text>
<ReindexButton config={config} title="Discovery Index" indexType="discovery" csrfToken={csrfToken} />
<Heading level={3} class="subtitle mt-6 text-neutral-900">
Guild-specific Search Indexes
</Heading>

View File

@@ -45,6 +45,7 @@ import type {
ListUserSessionsResponse,
UserAdminResponse,
} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
import type {WebAuthnCredentialListResponse} from '@fluxer/schema/src/domains/auth/AuthSchemas';
import {BackButton, NotFoundView} from '@fluxer/ui/src/components/Navigation';
import {formatDiscriminator, getUserAvatarUrl, getUserBannerUrl} from '@fluxer/ui/src/utils/FormatUser';
import type {FC} from 'hono/jsx';
@@ -170,6 +171,7 @@ export const UserDetailPage: FC<UserDetailPageProps> = async ({
let sessionsResult: {ok: true; data: ListUserSessionsResponse} | {ok: false; error: ApiError} | null = null;
let guildsResult: {ok: true; data: ListUserGuildsResponse} | {ok: false; error: ApiError} | null = null;
let dmChannelsResult: {ok: true; data: ListUserDmChannelsResponse} | {ok: false; error: ApiError} | null = null;
let webAuthnCredentials: WebAuthnCredentialListResponse | null = null;
let messageShredStatusResult:
| {ok: true; data: messagesApi.MessageShredStatusResponse}
| {ok: false; error: ApiError}
@@ -182,6 +184,13 @@ export const UserDetailPage: FC<UserDetailPageProps> = async ({
if (activeTab === 'account') {
sessionsResult = await usersApi.listUserSessions(config, session, userId);
const hasWebAuthn = user.authenticator_types.includes(2);
if (hasWebAuthn) {
const credResult = await usersApi.listWebAuthnCredentials(config, session, userId);
if (credResult.ok) {
webAuthnCredentials = credResult.data;
}
}
}
if (activeTab === 'guilds') {
@@ -304,6 +313,7 @@ export const UserDetailPage: FC<UserDetailPageProps> = async ({
user={user}
userId={userId}
sessionsResult={sessionsResult}
webAuthnCredentials={webAuthnCredentials}
csrfToken={csrfToken}
/>
)}

View File

@@ -32,6 +32,10 @@ import type {
UserAdminResponse,
UserSessionResponse,
} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
import type {
WebAuthnCredentialListResponse,
WebAuthnCredentialResponse,
} from '@fluxer/schema/src/domains/auth/AuthSchemas';
import {Button} from '@fluxer/ui/src/components/Button';
import {Card} from '@fluxer/ui/src/components/Card';
import {CsrfInput} from '@fluxer/ui/src/components/CsrfInput';
@@ -43,10 +47,18 @@ interface AccountTabProps {
user: UserAdminResponse;
userId: string;
sessionsResult: {ok: true; data: ListUserSessionsResponse} | {ok: false; error: ApiError} | null;
webAuthnCredentials: WebAuthnCredentialListResponse | null;
csrfToken: string;
}
export function AccountTab({config: _config, user, userId: _userId, sessionsResult, csrfToken}: AccountTabProps) {
export function AccountTab({
config: _config,
user,
userId: _userId,
sessionsResult,
webAuthnCredentials,
csrfToken,
}: AccountTabProps) {
return (
<VStack gap={6}>
<Card padding="md">
@@ -266,10 +278,82 @@ export function AccountTab({config: _config, user, userId: _userId, sessionsResu
</div>
</VStack>
</Card>
{webAuthnCredentials && webAuthnCredentials.length > 0 && (
<Card padding="md">
<VStack gap={4}>
<Heading level={2} size="base">
WebAuthn Credentials
</Heading>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-neutral-200 border-b text-left">
<th class="pb-2 font-medium text-neutral-600">Name</th>
<th class="pb-2 font-medium text-neutral-600">Created</th>
<th class="pb-2 font-medium text-neutral-600">Last Used</th>
<th class="pb-2 font-medium text-neutral-600" />
</tr>
</thead>
<tbody>
{webAuthnCredentials.map((credential) => (
<WebAuthnCredentialRow credential={credential} csrfToken={csrfToken} />
))}
</tbody>
</table>
</div>
</VStack>
</Card>
)}
</VStack>
);
}
const WebAuthnCredentialRow: FC<{credential: WebAuthnCredentialResponse; csrfToken: string}> = ({
credential,
csrfToken,
}) => {
function formatTimestamp(value: string): string {
const [datePart, timePartRaw] = value.split('T');
if (!datePart || !timePartRaw) return value;
const timePart = timePartRaw.replace('Z', '').split('.')[0] ?? timePartRaw;
return `${datePart} ${timePart}`;
}
return (
<tr class="border-neutral-100 border-b">
<td class="py-2 pr-4">
<Text size="sm" class="text-neutral-900">
{credential.name}
</Text>
</td>
<td class="py-2 pr-4">
<Text size="sm" class="text-neutral-900">
{formatTimestamp(credential.created_at)}
</Text>
</td>
<td class="py-2 pr-4">
<Text size="sm" class="text-neutral-900">
{credential.last_used_at ? formatTimestamp(credential.last_used_at) : 'Never'}
</Text>
</td>
<td class="py-2">
<form
method="post"
action="?action=delete_webauthn_credential&tab=account"
onsubmit={`return confirm('Are you sure you want to delete the WebAuthn credential "${credential.name}"?')`}
>
<CsrfInput token={csrfToken} />
<input type="hidden" name="credential_id" value={credential.id} />
<Button type="submit" variant="primary" size="small">
Delete
</Button>
</form>
</td>
</tr>
);
};
const SessionCard: FC<{session: UserSessionResponse}> = ({session}) => {
function formatSessionTimestamp(value: string): string {
const [datePart, timePartRaw] = value.split('T');