ultisuite-client/components/admin/settings/sections/file-policies-section.tsx
R3D347HR4Y f44dadc453
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
feat(admin): add VirusTotal scan settings and mail UI
File policies API key field, conditional scan badge, safe policy merge.
2026-06-07 22:05:28 +02:00

149 lines
5.5 KiB
TypeScript

"use client"
import { OrgSettingsSection } from "@/components/admin/settings/org-settings-form"
import { useOrgSettingsStore } from "@/lib/admin-settings/org-settings-store"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export function FilePoliciesSection() {
const filePolicies = useOrgSettingsStore((s) => s.filePolicies)
const setFilePolicies = useOrgSettingsStore((s) => s.setFilePolicies)
const vtKeyConfigured = useOrgSettingsStore(
(s) => s.meta?.secrets?.virustotal_api_key?.configured ?? false
)
const vtKeyMissing =
filePolicies.virus_scan_enabled &&
!vtKeyConfigured &&
!(filePolicies.virustotal_api_key ?? "").trim()
return (
<OrgSettingsSection
title="Politiques fichiers"
description="Règles d'upload, partage externe et rétention pour UltiDrive."
policySection="file_policies"
>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<Label>Taille max upload (Mo)</Label>
<Input
className="mt-1 h-9"
type="number"
min={1}
value={filePolicies.max_upload_mib}
onChange={(e) =>
setFilePolicies({ max_upload_mib: Number(e.target.value) || 1 })
}
/>
</div>
<div>
<Label>Expiration liens par défaut (jours)</Label>
<Input
className="mt-1 h-9"
type="number"
min={1}
value={filePolicies.default_link_expiry_days}
onChange={(e) =>
setFilePolicies({
default_link_expiry_days: Number(e.target.value) || 1,
})
}
/>
</div>
<div>
<Label>Rétention corbeille (jours)</Label>
<Input
className="mt-1 h-9"
type="number"
min={1}
value={filePolicies.retention_trash_days}
onChange={(e) =>
setFilePolicies({ retention_trash_days: Number(e.target.value) || 1 })
}
/>
</div>
<div>
<Label>Partage externe</Label>
<Select
value={filePolicies.external_sharing}
onValueChange={(external_sharing) =>
setFilePolicies({
external_sharing: external_sharing as typeof filePolicies.external_sharing,
})
}
>
<SelectTrigger className="mt-1 h-9">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="disabled">Désactivé</SelectItem>
<SelectItem value="authenticated">Utilisateurs authentifiés</SelectItem>
<SelectItem value="public_link">Liens publics autorisés</SelectItem>
</SelectContent>
</Select>
</div>
<div className="sm:col-span-2">
<Label>Extensions autorisées (vide = toutes)</Label>
<Textarea
className="mt-1 min-h-[80px] font-mono text-xs"
value={filePolicies.allowed_extensions}
onChange={(e) => setFilePolicies({ allowed_extensions: e.target.value })}
placeholder="pdf, docx, png, jpg"
/>
</div>
<label className="flex items-center justify-between gap-4 rounded-lg border p-3 sm:col-span-2">
<div>
<p className="text-sm font-medium">Bloquer les exécutables</p>
<p className="text-xs text-muted-foreground">exe, bat, sh, app, etc.</p>
</div>
<Switch
checked={filePolicies.block_executable}
onCheckedChange={(block_executable) => setFilePolicies({ block_executable })}
/>
</label>
<label className="flex items-center justify-between gap-4 rounded-lg border p-3 sm:col-span-2">
<div>
<p className="text-sm font-medium">Analyse antivirus à l&apos;upload</p>
<p className="text-xs text-muted-foreground">
VirusTotal scan synchrone à l&apos;upload Drive et pièces jointes mail
</p>
</div>
<Switch
checked={filePolicies.virus_scan_enabled}
onCheckedChange={(virus_scan_enabled) => setFilePolicies({ virus_scan_enabled })}
/>
</label>
{filePolicies.virus_scan_enabled ? (
<div className="sm:col-span-2">
<Label>Clé API VirusTotal</Label>
<Input
className="mt-1 h-9"
type="password"
autoComplete="off"
value={filePolicies.virustotal_api_key ?? ""}
onChange={(e) => setFilePolicies({ virustotal_api_key: e.target.value })}
placeholder={vtKeyConfigured ? "•••••••• (laisser vide pour conserver)" : "Coller la clé API"}
/>
{vtKeyConfigured && !(filePolicies.virustotal_api_key ?? "").trim() ? (
<p className="mt-1 text-xs text-muted-foreground">Clé configurée</p>
) : null}
{vtKeyMissing ? (
<p className="mt-1 text-xs text-amber-600 dark:text-amber-500">
Analyse activée sans clé API les uploads ne seront pas scannés.
</p>
) : null}
</div>
) : null}
</div>
</OrgSettingsSection>
)
}