Enhance environment configuration and expand Prisma schema for bot management
- Added OWNER_USER_IDS to .env.example for specifying global bot owners. - Introduced new models in the Prisma schema: CommandOverride, OwnerTeamMember, GlobalUserBlacklist, GuildBlacklist, FeatureFlag, BotPresenceConfig, ChangelogEntry, and OwnerAuditLog to support advanced bot management features. - Updated env.ts to preprocess OWNER_USER_IDS for better handling of global bot owner IDs. - Modified ModulePlaceholderPage to ensure proper routing for remaining dashboard modules.
This commit is contained in:
165
apps/webui/src/components/modules/automod-form.tsx
Normal file
165
apps/webui/src/components/modules/automod-form.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
'use client';
|
||||
|
||||
import type { AutomodConfigDashboard } from '@nexumi/shared';
|
||||
import { useTranslations } from '@/components/locale-provider';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import type { SettingsSaveResult } from '@/components/settings/settings-form';
|
||||
import { SettingsForm } from '@/components/settings/settings-form';
|
||||
|
||||
async function saveAutomod(guildId: string, value: AutomodConfigDashboard): Promise<SettingsSaveResult> {
|
||||
try {
|
||||
const response = await fetch(`/api/guilds/${guildId}/automod`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(value)
|
||||
});
|
||||
if (!response.ok) {
|
||||
const body = (await response.json().catch(() => null)) as { error?: string } | null;
|
||||
return { ok: false, error: body?.error ?? `Request failed with status ${response.status}` };
|
||||
}
|
||||
return { ok: true };
|
||||
} catch {
|
||||
return { ok: false, error: 'Network error' };
|
||||
}
|
||||
}
|
||||
|
||||
interface AutomodFormProps {
|
||||
guildId: string;
|
||||
initialValue: AutomodConfigDashboard;
|
||||
}
|
||||
|
||||
export function AutomodForm({ guildId, initialValue }: AutomodFormProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<SettingsForm<AutomodConfigDashboard> initialValue={initialValue} onSave={(value) => saveAutomod(guildId, value)}>
|
||||
{({ value, setValue }) => (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.automod.title')}</CardTitle>
|
||||
<CardDescription>{t('modulePages.automod.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t('modulePages.automod.enabledLabel')}</Label>
|
||||
<p className="text-sm text-muted-foreground">{t('modulePages.automod.enabledHint')}</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={value.enabled}
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, enabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.automod.antiRaidTitle')}</CardTitle>
|
||||
<CardDescription>{t('modulePages.automod.antiRaidDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.automod.antiRaidEnabled')}</Label>
|
||||
<Switch
|
||||
checked={value.antiRaidEnabled}
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, antiRaidEnabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.automod.antiRaidJoinThreshold')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={value.antiRaidJoinThreshold}
|
||||
onChange={(event) =>
|
||||
setValue((prev) => ({ ...prev, antiRaidJoinThreshold: Number(event.target.value) || 1 }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.automod.antiRaidWindowSeconds')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={value.antiRaidWindowSeconds}
|
||||
onChange={(event) =>
|
||||
setValue((prev) => ({ ...prev, antiRaidWindowSeconds: Number(event.target.value) || 1 }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{t('modulePages.automod.antiRaidActionHint')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('modulePages.automod.antiNukeTitle')}</CardTitle>
|
||||
<CardDescription>{t('modulePages.automod.antiNukeDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<Label>{t('modulePages.automod.antiNukeEnabled')}</Label>
|
||||
<Switch
|
||||
checked={value.antiNukeEnabled}
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, antiNukeEnabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.automod.antiNukeBanThreshold')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={value.antiNukeBanThreshold}
|
||||
onChange={(event) =>
|
||||
setValue((prev) => ({ ...prev, antiNukeBanThreshold: Number(event.target.value) || 1 }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.automod.antiNukeChannelThreshold')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={value.antiNukeChannelThreshold}
|
||||
onChange={(event) =>
|
||||
setValue((prev) => ({ ...prev, antiNukeChannelThreshold: Number(event.target.value) || 1 }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t('modulePages.automod.antiNukeWindowSeconds')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={value.antiNukeWindowSeconds}
|
||||
onChange={(event) =>
|
||||
setValue((prev) => ({ ...prev, antiNukeWindowSeconds: Number(event.target.value) || 1 }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label>{t('modulePages.automod.lockdownActive')}</Label>
|
||||
<p className="text-sm text-muted-foreground">{t('modulePages.automod.lockdownActiveHint')}</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={value.lockdownActive}
|
||||
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, lockdownActive: checked }))}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</SettingsForm>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user