|
1 | | -import { useState } from 'react'; |
| 1 | +import React, { useState } from 'react'; |
2 | 2 | import { useForm, Controller } from 'react-hook-form'; |
3 | 3 | import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; |
4 | 4 | import type { AS2Partner } from '../types'; |
5 | 5 | import { useCertificatesExportQuery, useUpdatePlatformPartnerMutation, useRotateCertificatesMutation } from '../api/partnerHooks'; |
| 6 | +import { pki } from 'node-forge'; |
6 | 7 | import { Copy, Download, Loader2, ChevronDown, ChevronRight, CheckCircle2, Clock, ClipboardPaste } from 'lucide-react'; |
7 | 8 | import { Button } from '@/components/ui/button'; |
8 | 9 | import { Input } from '@/components/ui/input'; |
9 | 10 | import { Label } from '@/components/ui/label'; |
10 | 11 | import { useToast } from '@/hooks/use-toast'; |
11 | | -import { usePlatformConfig } from '@/features/platform/api/configHooks'; |
| 12 | +import { usePlatformSettings } from '@/features/platform/api/settingsHooks'; |
12 | 13 | import { Combobox } from '@/components/ui/combobox'; |
13 | 14 | import { CertificateInput } from './CertificateInput'; |
14 | 15 |
|
15 | 16 | export function As2PartnerDetails({ partner, onCancel }: { partner: AS2Partner, onCancel?: () => void }) { |
16 | 17 | const { toast } = useToast(); |
17 | | - const { data: platformConfig } = usePlatformConfig(); |
| 18 | + const { data: platformSettings } = usePlatformSettings(); |
18 | 19 |
|
19 | 20 | const updatePlatform = useUpdatePlatformPartnerMutation(); |
20 | 21 | const rotateCertificates = useRotateCertificatesMutation(); |
@@ -161,7 +162,7 @@ export function As2PartnerDetails({ partner, onCancel }: { partner: AS2Partner, |
161 | 162 | control={control} |
162 | 163 | render={({ field }) => ( |
163 | 164 | <Combobox |
164 | | - options={platformConfig?.available_as2_receive_urls || []} |
| 165 | + options={platformSettings?.available_as2_receive_urls || []} |
165 | 166 | value={field.value} |
166 | 167 | onChange={field.onChange} |
167 | 168 | placeholder="https://..." |
@@ -199,8 +200,11 @@ export function As2PartnerDetails({ partner, onCancel }: { partner: AS2Partner, |
199 | 200 | <table className="w-full text-left border-collapse"> |
200 | 201 | <thead> |
201 | 202 | <tr className="border-b border-slate-200/60 bg-slate-50/50"> |
202 | | - <th className="px-6 py-4 text-xs font-semibold text-slate-500 uppercase tracking-wider">Status</th> |
| 203 | + <th className="px-6 py-4 text-xs font-semibold text-slate-500 uppercase tracking-wider w-32">Status</th> |
203 | 204 | <th className="px-6 py-4 text-xs font-semibold text-slate-500 uppercase tracking-wider">Description</th> |
| 205 | + <th className="px-6 py-4 text-xs font-semibold text-slate-500 uppercase tracking-wider w-32">Issued</th> |
| 206 | + <th className="px-6 py-4 text-xs font-semibold text-slate-500 uppercase tracking-wider w-32">Expires</th> |
| 207 | + <th className="px-6 py-4 w-full"></th> |
204 | 208 | </tr> |
205 | 209 | </thead> |
206 | 210 | <tbody className="divide-y divide-slate-100"> |
@@ -280,6 +284,19 @@ function CertificateRow({ |
280 | 284 | const [expanded, setExpanded] = useState(false); |
281 | 285 | const { toast } = useToast(); |
282 | 286 |
|
| 287 | + const certInfo = React.useMemo(() => { |
| 288 | + if (!publicPem || !publicPem.includes('-----BEGIN CERTIFICATE-----')) return null; |
| 289 | + try { |
| 290 | + const cert = pki.certificateFromPem(publicPem); |
| 291 | + return { |
| 292 | + notBefore: cert.validity.notBefore.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }), |
| 293 | + notAfter: cert.validity.notAfter.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }), |
| 294 | + }; |
| 295 | + } catch (e) { |
| 296 | + return null; |
| 297 | + } |
| 298 | + }, [publicPem]); |
| 299 | + |
283 | 300 | const handleCopy = (text: string, label: string) => { |
284 | 301 | navigator.clipboard.writeText(text); |
285 | 302 | toast({ title: 'Copied', description: `${label} copied to clipboard.` }); |
@@ -327,21 +344,35 @@ function CertificateRow({ |
327 | 344 | </span> |
328 | 345 | )} |
329 | 346 | </td> |
330 | | - <td className="px-6 py-4 text-sm text-slate-500 w-full"> |
331 | | - <div className="flex items-center justify-between"> |
332 | | - <span>{role === 'Active' ? 'Actively used for signing and decryption' : 'In grace period for legacy traffic'}</span> |
333 | | - <div className="flex items-center gap-3"> |
334 | | - <span className="text-xs font-medium text-slate-400 group-hover:text-indigo-600 transition-colors"> |
335 | | - {expanded ? 'Hide Details' : 'View Details'} |
336 | | - </span> |
337 | | - {expanded ? <ChevronDown className="w-4 h-4 text-slate-400 group-hover:text-indigo-600 transition-colors" /> : <ChevronRight className="w-4 h-4 text-slate-400 group-hover:text-indigo-600 transition-colors" />} |
338 | | - </div> |
| 347 | + <td className="px-6 py-4 text-sm text-slate-500 whitespace-nowrap"> |
| 348 | + <span>{role === 'Active' ? 'Actively used for signing and decryption' : 'In grace period for legacy traffic'}</span> |
| 349 | + </td> |
| 350 | + <td className="px-6 py-4 whitespace-nowrap"> |
| 351 | + {certInfo ? ( |
| 352 | + <span className="text-sm text-slate-600 font-medium">{certInfo.notBefore}</span> |
| 353 | + ) : ( |
| 354 | + <span className="text-sm text-slate-400 italic">Unknown</span> |
| 355 | + )} |
| 356 | + </td> |
| 357 | + <td className="px-6 py-4 whitespace-nowrap"> |
| 358 | + {certInfo ? ( |
| 359 | + <span className="text-sm text-slate-600 font-medium">{certInfo.notAfter}</span> |
| 360 | + ) : ( |
| 361 | + <span className="text-sm text-slate-400 italic">Unknown</span> |
| 362 | + )} |
| 363 | + </td> |
| 364 | + <td className="px-6 py-4 whitespace-nowrap text-right"> |
| 365 | + <div className="flex items-center justify-end gap-3"> |
| 366 | + <span className="text-xs font-medium text-slate-400 group-hover:text-indigo-600 transition-colors"> |
| 367 | + {expanded ? 'Hide Details' : 'View Details'} |
| 368 | + </span> |
| 369 | + {expanded ? <ChevronDown className="w-4 h-4 text-slate-400 group-hover:text-indigo-600 transition-colors" /> : <ChevronRight className="w-4 h-4 text-slate-400 group-hover:text-indigo-600 transition-colors" />} |
339 | 370 | </div> |
340 | 371 | </td> |
341 | 372 | </tr> |
342 | 373 | {expanded && ( |
343 | 374 | <tr> |
344 | | - <td colSpan={2} className="p-0 border-t-0"> |
| 375 | + <td colSpan={5} className="p-0 border-t-0"> |
345 | 376 | <div className="bg-slate-50 p-4 border-b border-slate-100 flex flex-col gap-4 shadow-inner"> |
346 | 377 |
|
347 | 378 | <div className="flex flex-col gap-2"> |
|
0 commit comments