fix: various fixes to sentry-reported errors and more
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user