feat: améliorations SharePoint - test connexion Azure AD, alerte expiration secret, log statut upload dans BapHistory

This commit is contained in:
Manus
2026-05-06 07:55:46 -04:00
parent bbee4cd7c9
commit 6553aee8a6
5 changed files with 176 additions and 1 deletions

View File

@@ -18,6 +18,9 @@ import {
User,
Download,
RefreshCw,
Cloud,
CloudOff,
ExternalLink,
} from "lucide-react";
// Helper : télécharge un PDF annoté BAP depuis son URL de stockage
@@ -269,6 +272,31 @@ export default function BapHistory() {
<Download className="h-3.5 w-3.5" />
</Button>
</div>
) : (entry as any).sharepointUploadStatus === 'success' ? (
<div className="flex items-center gap-1">
<div className="flex items-center gap-1 text-blue-600">
<Cloud className="h-3.5 w-3.5" />
<span className="text-xs">SharePoint</span>
</div>
{(entry as any).sharepointUploadPath && (
<a
href={(entry as any).sharepointUploadPath}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700"
title="Ouvrir dans SharePoint"
>
<ExternalLink className="h-3.5 w-3.5" />
</a>
)}
</div>
) : (entry as any).sharepointUploadStatus === 'error' ? (
<div className="flex items-center gap-1" title={(entry as any).sharepointUploadError || 'Erreur SharePoint'}>
<div className="flex items-center gap-1 text-red-600">
<CloudOff className="h-3.5 w-3.5" />
<span className="text-xs">SP Erreur</span>
</div>
</div>
) : entry.exportPath ? (
<div className="flex items-center gap-1">
<div className="flex items-center gap-1 text-orange-600">

View File

@@ -7,7 +7,7 @@ import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Badge } from "@/components/ui/badge";
import { toast } from "sonner";
import { Loader2, Save, Upload, FolderOpen, Mail, Play, Square, Download, Inbox, CheckCircle2, Monitor, FolderOutput } from "lucide-react";
import { Loader2, Save, Upload, FolderOpen, Mail, Play, Square, Download, Inbox, CheckCircle2, Monitor, FolderOutput, Wifi, WifiOff, AlertTriangle, Calendar } from "lucide-react";
import DashboardLayout from "@/components/DashboardLayout";
export default function ImportSettings() {
@@ -50,6 +50,8 @@ export default function ImportSettings() {
const [azureTenantId, setAzureTenantId] = useState("");
const [azureClientId, setAzureClientId] = useState("");
const [azureClientSecret, setAzureClientSecret] = useState("");
const [azureSecretExpiresAt, setAzureSecretExpiresAt] = useState("");
const testAzureConnectionMutation = trpc.importSettings.testAzureConnection.useMutation();
// Initialize form with settings from database
useEffect(() => {
@@ -83,6 +85,8 @@ export default function ImportSettings() {
setAzureTenantId((settings as any).azureTenantId || "");
setAzureClientId((settings as any).azureClientId || "");
setAzureClientSecret((settings as any).azureClientSecret || "");
const expDate = (settings as any).azureSecretExpiresAt;
setAzureSecretExpiresAt(expDate ? new Date(expDate).toISOString().split('T')[0] : "");
}
}, [settings]);
@@ -112,6 +116,7 @@ export default function ImportSettings() {
azureTenantId: azureTenantId || null,
azureClientId: azureClientId || null,
azureClientSecret: azureClientSecret || null,
azureSecretExpiresAt: azureSecretExpiresAt ? new Date(azureSecretExpiresAt) : null,
});
toast.success("Paramètres enregistrés avec succès");
@@ -680,6 +685,72 @@ export default function ImportSettings() {
/>
<p className="text-xs text-muted-foreground">Stocké de façon sécurisée. Permissions requises : <code>Files.ReadWrite.All</code> et <code>Sites.ReadWrite.All</code></p>
</div>
{/* Date d'expiration du secret */}
<div className="space-y-1">
<Label htmlFor="azureSecretExpiresAt" className="text-sm font-medium flex items-center gap-1">
<Calendar className="h-3.5 w-3.5" />
Date d'expiration du secret
</Label>
<Input
id="azureSecretExpiresAt"
type="date"
value={azureSecretExpiresAt}
onChange={(e) => setAzureSecretExpiresAt(e.target.value)}
className="h-10"
/>
<p className="text-xs text-muted-foreground">Renseignez la date d'expiration pour recevoir une alerte avant renouvellement</p>
</div>
{/* Alerte expiration */}
{azureSecretExpiresAt && (() => {
const expDate = new Date(azureSecretExpiresAt);
const daysLeft = Math.ceil((expDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
if (daysLeft <= 0) return (
<div className="flex items-center gap-2 p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
<AlertTriangle className="h-4 w-4 shrink-0" />
<span><strong>Secret expiré</strong> renouvelez-le immédiatement dans le portail Azure AD</span>
</div>
);
if (daysLeft <= 30) return (
<div className="flex items-center gap-2 p-3 bg-amber-50 border border-amber-200 rounded-lg text-amber-700 text-sm">
<AlertTriangle className="h-4 w-4 shrink-0" />
<span><strong>Expiration dans {daysLeft} jours</strong> pensez à renouveler le secret Azure AD</span>
</div>
);
return (
<div className="flex items-center gap-2 p-3 bg-green-50 border border-green-200 rounded-lg text-green-700 text-sm">
<CheckCircle2 className="h-4 w-4 shrink-0" />
<span>Secret valide encore <strong>{daysLeft} jours</strong> (expire le {expDate.toLocaleDateString('fr-FR')})</span>
</div>
);
})()}
{/* Bouton test connexion */}
<div className="pt-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={async () => {
const result = await testAzureConnectionMutation.mutateAsync();
if (result.success) {
toast.success(result.message || 'Connexion Azure AD réussie');
} else {
toast.error(result.error || 'Erreur de connexion Azure AD');
}
}}
disabled={testAzureConnectionMutation.isPending || !azureTenantId || !azureClientId || !azureClientSecret}
className="gap-2"
>
{testAzureConnectionMutation.isPending ? (
<><Loader2 className="h-4 w-4 animate-spin" />Test en cours...</>
) : (
<><Wifi className="h-4 w-4" />Tester la connexion Azure AD</>
)}
</Button>
<p className="text-xs text-muted-foreground mt-1">Vérifie le token OAuth2 et l'accès au site SharePoint</p>
</div>
</div>
</div>
)}