@wakastellar/ui 2.4.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/dist/blocks/antivirus-dashboard/index.d.ts +44 -0
  2. package/dist/blocks/clamav-service-status/index.d.ts +35 -0
  3. package/dist/blocks/file-scan-uploader/index.d.ts +29 -0
  4. package/dist/blocks/index.d.ts +18 -9
  5. package/dist/blocks/quarantine-manager/index.d.ts +27 -0
  6. package/dist/blocks/scan-history-log/index.d.ts +28 -0
  7. package/dist/blocks/scan-policy-editor/index.d.ts +27 -0
  8. package/dist/blocks/scan-report-generator/index.d.ts +47 -0
  9. package/dist/blocks/signature-database-manager/index.d.ts +39 -0
  10. package/dist/blocks/threat-alert-banner/index.d.ts +26 -0
  11. package/dist/components/index.d.ts +4 -4
  12. package/dist/components/waka-signature-pad/index.d.ts +1 -1
  13. package/dist/exceljs.min-BcLLX0PC.js +29 -0
  14. package/dist/exceljs.min-KOayaaQ4.mjs +23013 -0
  15. package/dist/export.cjs.js +1 -1
  16. package/dist/export.d.ts +2 -2
  17. package/dist/export.es.js +1 -1
  18. package/dist/index.cjs.js +136 -136
  19. package/dist/index.es.js +29978 -27215
  20. package/dist/stories/Button.stories.d.ts +1 -1
  21. package/dist/stories/Header.stories.d.ts +1 -1
  22. package/dist/stories/Page.stories.d.ts +1 -1
  23. package/dist/useDataTableImport-COVnvslz.js +9 -0
  24. package/dist/useDataTableImport-DAlxBY8w.mjs +237 -0
  25. package/dist/utils/index.d.ts +1 -0
  26. package/dist/utils/logger.d.ts +9 -0
  27. package/package.json +6 -5
  28. package/src/blocks/antivirus-dashboard/AntivirusDashboard.stories.tsx +291 -0
  29. package/src/blocks/antivirus-dashboard/index.tsx +525 -0
  30. package/src/blocks/clamav-service-status/ClamAVServiceStatus.stories.tsx +195 -0
  31. package/src/blocks/clamav-service-status/index.tsx +370 -0
  32. package/src/blocks/file-scan-uploader/FileScanUploader.stories.tsx +257 -0
  33. package/src/blocks/file-scan-uploader/index.tsx +311 -0
  34. package/src/blocks/index.ts +163 -11
  35. package/src/blocks/quarantine-manager/QuarantineManager.stories.tsx +209 -0
  36. package/src/blocks/quarantine-manager/index.tsx +435 -0
  37. package/src/blocks/scan-history-log/ScanHistoryLog.stories.tsx +231 -0
  38. package/src/blocks/scan-history-log/index.tsx +406 -0
  39. package/src/blocks/scan-policy-editor/ScanPolicyEditor.stories.tsx +106 -0
  40. package/src/blocks/scan-policy-editor/index.tsx +418 -0
  41. package/src/blocks/scan-report-generator/ScanReportGenerator.stories.tsx +232 -0
  42. package/src/blocks/scan-report-generator/index.tsx +612 -0
  43. package/src/blocks/sidebar/index.tsx +2 -1
  44. package/src/blocks/signature-database-manager/SignatureDatabaseManager.stories.tsx +279 -0
  45. package/src/blocks/signature-database-manager/index.tsx +470 -0
  46. package/src/blocks/theme-creator-block/index.tsx +16 -2
  47. package/src/blocks/threat-alert-banner/ThreatAlertBanner.stories.tsx +152 -0
  48. package/src/blocks/threat-alert-banner/index.tsx +320 -0
  49. package/src/components/DataTable/DataTable.stories.tsx +203 -0
  50. package/src/components/DataTable/hooks/useDataTableExport.ts +38 -31
  51. package/src/components/DataTable/hooks/useDataTableImport.ts +31 -20
  52. package/src/components/error-boundary/ErrorBoundary.stories.tsx +125 -0
  53. package/src/components/index.ts +45 -4
  54. package/src/components/language-selector/LanguageSelector.stories.tsx +112 -0
  55. package/src/components/theme-selector/ThemeSelector.stories.tsx +77 -0
  56. package/src/components/toaster/Toaster.stories.tsx +67 -0
  57. package/src/components/waka-activity-feed/WakaActivityFeed.stories.tsx +116 -0
  58. package/src/components/waka-ad-banner/WakaAdBanner.stories.tsx +102 -0
  59. package/src/components/waka-ad-fallback/WakaAdFallback.stories.tsx +117 -0
  60. package/src/components/waka-ad-inline/WakaAdInline.stories.tsx +105 -0
  61. package/src/components/waka-ad-interstitial/WakaAdInterstitial.stories.tsx +92 -0
  62. package/src/components/waka-ad-placeholder/WakaAdPlaceholder.stories.tsx +89 -0
  63. package/src/components/waka-ad-provider/WakaAdProvider.stories.tsx +110 -0
  64. package/src/components/waka-ad-sidebar/WakaAdSidebar.stories.tsx +89 -0
  65. package/src/components/waka-ad-sidebar/index.tsx +3 -2
  66. package/src/components/waka-ad-sticky-footer/WakaAdStickyFooter.stories.tsx +88 -0
  67. package/src/components/waka-address-autocomplete/WakaAddressAutocomplete.stories.tsx +46 -0
  68. package/src/components/waka-admincrumb/WakaAdmincrumb.stories.tsx +166 -0
  69. package/src/components/waka-alert-panel/WakaAlertPanel.stories.tsx +45 -0
  70. package/src/components/waka-alert-stack/WakaAlertStack.stories.tsx +62 -0
  71. package/src/components/waka-allocation-matrix/WakaAllocationMatrix.stories.tsx +68 -0
  72. package/src/components/waka-approval-chain/WakaApprovalChain.stories.tsx +63 -0
  73. package/src/components/waka-audit-log/WakaAuditLog.stories.tsx +73 -0
  74. package/src/components/waka-autocomplete/WakaAutocomplete.stories.tsx +132 -172
  75. package/src/components/waka-biometric-prompt/WakaBiometricPrompt.stories.tsx +48 -0
  76. package/src/components/waka-breadcrumb/WakaBreadcrumb.stories.tsx +74 -191
  77. package/src/components/waka-breadcrumb-path/WakaBreadcrumbPath.stories.tsx +40 -0
  78. package/src/components/waka-budget-burn/WakaBudgetBurn.stories.tsx +86 -0
  79. package/src/components/waka-capacity-planner/WakaCapacityPlanner.stories.tsx +273 -0
  80. package/src/components/waka-cart-summary/WakaCartSummary.stories.tsx +176 -0
  81. package/src/components/waka-cart-summary/index.tsx +19 -10
  82. package/src/components/waka-challenge-timer/WakaChallengeTimer.stories.tsx +98 -0
  83. package/src/components/waka-chat-bubble/WakaChatBubble.stories.tsx +118 -0
  84. package/src/components/waka-checklist/WakaChecklist.stories.tsx +71 -0
  85. package/src/components/waka-checkout-stepper/WakaCheckoutStepper.stories.tsx +102 -0
  86. package/src/components/waka-cohort-table/WakaCohortTable.stories.tsx +56 -0
  87. package/src/components/waka-color-picker/WakaColorPicker.stories.tsx +99 -155
  88. package/src/components/waka-combo-counter/WakaComboCounter.stories.tsx +128 -0
  89. package/src/components/waka-command-bar/WakaCommandBar.stories.tsx +45 -0
  90. package/src/components/waka-compare-period/WakaComparePeriod.stories.tsx +76 -0
  91. package/src/components/waka-config-comparator/WakaConfigComparator.stories.tsx +143 -0
  92. package/src/components/waka-connection-matrix/WakaConnectionMatrix.stories.tsx +52 -0
  93. package/src/components/waka-content-recommendation/WakaContentRecommendation.stories.tsx +41 -0
  94. package/src/components/waka-coupon-input/WakaCouponInput.stories.tsx +126 -0
  95. package/src/components/waka-credit-card-input/WakaCreditCardInput.stories.tsx +120 -0
  96. package/src/components/waka-datetime-picker.form-integration/WakaDateTimePickerForm.stories.tsx +79 -0
  97. package/src/components/waka-dependency-tree/WakaDependencyTree.stories.tsx +72 -0
  98. package/src/components/waka-device-trust/WakaDeviceTrust.stories.tsx +109 -0
  99. package/src/components/waka-empty-state/WakaEmptyState.stories.tsx +87 -0
  100. package/src/components/waka-feature-announcement/WakaFeatureAnnouncement.stories.tsx +47 -0
  101. package/src/components/waka-feature-flag-row/WakaFeatureFlagRow.stories.tsx +188 -0
  102. package/src/components/waka-file-upload/WakaFileUpload.stories.tsx +118 -174
  103. package/src/components/waka-floating-nav/WakaFloatingNav.stories.tsx +53 -0
  104. package/src/components/waka-goal-progress/WakaGoalProgress.stories.tsx +137 -0
  105. package/src/components/waka-hotspot/WakaHotspot.stories.tsx +56 -0
  106. package/src/components/waka-invoice-preview/WakaInvoicePreview.stories.tsx +169 -0
  107. package/src/components/waka-kpi-dashboard/WakaKpiDashboard.stories.tsx +46 -0
  108. package/src/components/waka-level-progress/WakaLevelProgress.stories.tsx +94 -75
  109. package/src/components/waka-liquid-button/WakaLiquidButton.stories.tsx +45 -0
  110. package/src/components/waka-magic-link/WakaMagicLink.stories.tsx +61 -0
  111. package/src/components/waka-magnetic-button/WakaMagneticButton.stories.tsx +40 -0
  112. package/src/components/waka-mention-input/WakaMentionInput.stories.tsx +140 -0
  113. package/src/components/waka-milestone-road/WakaMilestoneRoad.stories.tsx +143 -0
  114. package/src/components/waka-orbital-menu/WakaOrbitalMenu.stories.tsx +54 -0
  115. package/src/components/waka-order-tracker/WakaOrderTracker.stories.tsx +163 -0
  116. package/src/components/waka-outstream-video/WakaOutstreamVideo.stories.tsx +94 -0
  117. package/src/components/waka-pagination/WakaPagination.stories.tsx +110 -280
  118. package/src/components/waka-password-strength/WakaPasswordStrength.stories.tsx +132 -268
  119. package/src/components/waka-payment-method-picker/WakaPaymentMethodPicker.stories.tsx +141 -0
  120. package/src/components/waka-permission-matrix/WakaPermissionMatrix.stories.tsx +124 -0
  121. package/src/components/waka-phone-input/WakaPhoneInput.stories.tsx +56 -0
  122. package/src/components/waka-points-popup/WakaPointsPopup.stories.tsx +96 -0
  123. package/src/components/waka-power-up/WakaPowerUp.stories.tsx +121 -0
  124. package/src/components/waka-presence-indicator/WakaPresenceIndicator.stories.tsx +49 -0
  125. package/src/components/waka-pricing-table/WakaPricingTable.stories.tsx +159 -0
  126. package/src/components/waka-product-card/WakaProductCard.stories.tsx +202 -0
  127. package/src/components/waka-progress-onboarding/WakaProgressOnboarding.stories.tsx +57 -0
  128. package/src/components/waka-pull-to-refresh/WakaPullToRefresh.stories.tsx +51 -0
  129. package/src/components/waka-rank-badge/WakaRankBadge.stories.tsx +108 -0
  130. package/src/components/waka-rating-input/WakaRatingInput.stories.tsx +51 -0
  131. package/src/components/waka-reaction-picker/WakaReactionPicker.stories.tsx +52 -0
  132. package/src/components/waka-region-map/WakaRegionMap.stories.tsx +181 -0
  133. package/src/components/waka-resource-pool/WakaResourcePool.stories.tsx +70 -0
  134. package/src/components/waka-rich-text-editor/WakaRichTextEditor.stories.tsx +108 -197
  135. package/src/components/waka-rollback-slider/WakaRollbackSlider.stories.tsx +41 -0
  136. package/src/components/waka-schedule-picker/WakaSchedulePicker.stories.tsx +64 -0
  137. package/src/components/waka-season-pass/WakaSeasonPass.stories.tsx +107 -0
  138. package/src/components/waka-security-scan-result/WakaSecurityScanResult.stories.tsx +146 -0
  139. package/src/components/waka-security-score/WakaSecurityScore.stories.tsx +63 -0
  140. package/src/components/waka-session-manager/WakaSessionManager.stories.tsx +68 -0
  141. package/src/components/waka-signature-pad/WakaSignaturePad.stories.tsx +159 -0
  142. package/src/components/waka-signature-pad/index.tsx +5 -3
  143. package/src/components/waka-sla-tracker/WakaSlaTracker.stories.tsx +65 -0
  144. package/src/components/waka-slider-range/WakaSliderRange.stories.tsx +66 -0
  145. package/src/components/waka-sponsored-badge/WakaSponsoredBadge.stories.tsx +60 -0
  146. package/src/components/waka-sponsored-card/WakaSponsoredCard.stories.tsx +64 -0
  147. package/src/components/waka-sponsored-feed/WakaSponsoredFeed.stories.tsx +58 -0
  148. package/src/components/waka-spotlight/WakaSpotlight.stories.tsx +53 -0
  149. package/src/components/waka-stats-hexagon/WakaStatsHexagon.stories.tsx +161 -0
  150. package/src/components/waka-stepper/WakaStepper.stories.tsx +137 -410
  151. package/src/components/waka-swipe-card/WakaSwipeCard.stories.tsx +51 -0
  152. package/src/components/waka-tag-input/WakaTagInput.stories.tsx +224 -0
  153. package/src/components/waka-team-banner/WakaTeamBanner.stories.tsx +50 -0
  154. package/src/components/waka-theme-creator/WakaThemeCreator.stories.tsx +58 -0
  155. package/src/components/waka-theme-manager/WakaThemeManager.stories.tsx +298 -0
  156. package/src/components/waka-theme-manager/index.tsx +6 -11
  157. package/src/components/waka-thread-view/WakaThreadView.stories.tsx +143 -0
  158. package/src/components/waka-timeline/WakaTimeline.stories.tsx +171 -324
  159. package/src/components/waka-tooltip-tour/WakaTooltipTour.stories.tsx +92 -0
  160. package/src/components/waka-tour-guide/WakaTourGuide.stories.tsx +89 -0
  161. package/src/components/waka-treemap-chart/WakaTreemapChart.stories.tsx +234 -129
  162. package/src/components/waka-treemap-chart/index.tsx +2 -2
  163. package/src/components/waka-two-factor-setup/WakaTwoFactorSetup.stories.tsx +142 -0
  164. package/src/components/waka-typing-indicator/WakaTypingIndicator.stories.tsx +134 -0
  165. package/src/components/waka-video-ad/WakaVideoAd.stories.tsx +138 -0
  166. package/src/components/waka-video-call/WakaVideoCall.stories.tsx +186 -0
  167. package/src/components/waka-video-overlay/WakaVideoOverlay.stories.tsx +100 -0
  168. package/src/components/waka-voice-message/WakaVoiceMessage.stories.tsx +190 -0
  169. package/src/components/waka-welcome-modal/WakaWelcomeModal.stories.tsx +87 -0
  170. package/src/components/waka-xp-bar/WakaXPBar.stories.tsx +29 -29
  171. package/dist/useDataTableImport-D8R2HQl6.mjs +0 -229
  172. package/dist/useDataTableImport-S_hhA5Wo.js +0 -9
  173. package/src/components/DataTable/README.md +0 -446
@@ -0,0 +1,406 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+ import { Download, Search, Copy, Check } from "lucide-react"
5
+ import { Badge } from "../../components/badge"
6
+ import { Button } from "../../components/button"
7
+ import { Card } from "../../components/card"
8
+ import { Input } from "../../components/input"
9
+ import { ScrollArea } from "../../components/scroll-area"
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "../../components/select"
17
+ import { cn } from "../../utils/cn"
18
+
19
+ export type ScanResult = "clean" | "infected" | "error"
20
+
21
+ export interface ScanLogEntry {
22
+ id: string
23
+ filename: string
24
+ fileSize: number
25
+ fileHash: string
26
+ result: ScanResult
27
+ threatName?: string
28
+ scanDate: Date
29
+ duration: number
30
+ userId: string
31
+ userName: string
32
+ engineVersion: string
33
+ signatureVersion: string
34
+ }
35
+
36
+ export interface ScanHistoryLogProps {
37
+ entries?: ScanLogEntry[]
38
+ totalEntries?: number
39
+ currentPage?: number
40
+ pageSize?: number
41
+ periodFilter?: "today" | "7d" | "30d" | "custom"
42
+ onPageChange?: (page: number) => void
43
+ onFilterChange?: (filters: Record<string, string>) => void
44
+ onExport?: () => void
45
+ className?: string
46
+ }
47
+
48
+ const defaultEntries: ScanLogEntry[] = []
49
+
50
+ export function ScanHistoryLog({
51
+ entries = defaultEntries,
52
+ totalEntries = entries.length,
53
+ currentPage = 1,
54
+ pageSize = 10,
55
+ periodFilter = "7d",
56
+ onPageChange,
57
+ onFilterChange,
58
+ onExport,
59
+ className,
60
+ }: ScanHistoryLogProps) {
61
+ const [searchQuery, setSearchQuery] = useState("")
62
+ const [resultFilter, setResultFilter] = useState<string>("all")
63
+ const [period, setPeriod] = useState<string>(periodFilter)
64
+ const [userFilter, setUserFilter] = useState<string>("all")
65
+ const [copiedHash, setCopiedHash] = useState<string | null>(null)
66
+
67
+ const filteredEntries = entries.filter((entry) => {
68
+ if (searchQuery && !entry.filename.toLowerCase().includes(searchQuery.toLowerCase())) {
69
+ return false
70
+ }
71
+ if (resultFilter !== "all" && entry.result !== resultFilter) {
72
+ return false
73
+ }
74
+ if (userFilter !== "all" && entry.userId !== userFilter) {
75
+ return false
76
+ }
77
+ return true
78
+ })
79
+
80
+ const paginatedEntries = filteredEntries.slice(
81
+ (currentPage - 1) * pageSize,
82
+ currentPage * pageSize
83
+ )
84
+
85
+ const totalPages = Math.ceil(filteredEntries.length / pageSize)
86
+
87
+ const stats = {
88
+ total: filteredEntries.length,
89
+ clean: filteredEntries.filter((e) => e.result === "clean").length,
90
+ infected: filteredEntries.filter((e) => e.result === "infected").length,
91
+ errors: filteredEntries.filter((e) => e.result === "error").length,
92
+ }
93
+
94
+ const uniqueUsers = Array.from(new Set(entries.map((e) => e.userId)))
95
+
96
+ const handleCopyHash = (hash: string) => {
97
+ navigator.clipboard.writeText(hash)
98
+ setCopiedHash(hash)
99
+ setTimeout(() => setCopiedHash(null), 2000)
100
+ }
101
+
102
+ const handleFilterChange = (key: string, value: string) => {
103
+ const filters: Record<string, string> = {
104
+ search: searchQuery,
105
+ result: resultFilter,
106
+ period,
107
+ user: userFilter,
108
+ [key]: value,
109
+ }
110
+ onFilterChange?.(filters)
111
+ }
112
+
113
+ const getResultBadge = (result: ScanResult) => {
114
+ switch (result) {
115
+ case "clean":
116
+ return <Badge variant="secondary" className="bg-green-500/10 text-green-600 border-green-500/20">Clean</Badge>
117
+ case "infected":
118
+ return <Badge variant="destructive">Infected</Badge>
119
+ case "error":
120
+ return <Badge variant="secondary" className="bg-yellow-500/10 text-yellow-600 border-yellow-500/20">Error</Badge>
121
+ }
122
+ }
123
+
124
+ const formatFileSize = (bytes: number) => {
125
+ if (bytes < 1024) return `${bytes} B`
126
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`
127
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
128
+ }
129
+
130
+ const formatDate = (date: Date) => {
131
+ return new Intl.DateTimeFormat("fr-FR", {
132
+ day: "2-digit",
133
+ month: "2-digit",
134
+ year: "numeric",
135
+ hour: "2-digit",
136
+ minute: "2-digit",
137
+ second: "2-digit",
138
+ }).format(date)
139
+ }
140
+
141
+ const truncateHash = (hash: string) => {
142
+ return `${hash.slice(0, 8)}...${hash.slice(-8)}`
143
+ }
144
+
145
+ return (
146
+ <div className={cn("space-y-6", className)}>
147
+ {/* Header */}
148
+ <div className="flex items-center justify-between">
149
+ <div>
150
+ <h2 className="text-2xl font-bold tracking-tight">Historique des scans</h2>
151
+ <p className="text-sm text-muted-foreground mt-1">
152
+ {totalEntries} scan{totalEntries > 1 ? "s" : ""} au total
153
+ </p>
154
+ </div>
155
+ <Button onClick={onExport} variant="outline">
156
+ <Download className="w-4 h-4 mr-2" />
157
+ Exporter le journal
158
+ </Button>
159
+ </div>
160
+
161
+ {/* Stats Cards */}
162
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
163
+ <Card className="p-4">
164
+ <div className="text-sm font-medium text-muted-foreground">Total scans</div>
165
+ <div className="text-2xl font-bold mt-1">{stats.total}</div>
166
+ </Card>
167
+ <Card className="p-4">
168
+ <div className="text-sm font-medium text-muted-foreground">Clean</div>
169
+ <div className="text-2xl font-bold mt-1 text-green-600 dark:text-green-500">
170
+ {stats.clean}
171
+ </div>
172
+ </Card>
173
+ <Card className="p-4">
174
+ <div className="text-sm font-medium text-muted-foreground">Infected</div>
175
+ <div className="text-2xl font-bold mt-1 text-red-600 dark:text-red-500">
176
+ {stats.infected}
177
+ </div>
178
+ </Card>
179
+ <Card className="p-4">
180
+ <div className="text-sm font-medium text-muted-foreground">Errors</div>
181
+ <div className="text-2xl font-bold mt-1 text-orange-600 dark:text-orange-500">
182
+ {stats.errors}
183
+ </div>
184
+ </Card>
185
+ </div>
186
+
187
+ {/* Filters */}
188
+ <Card className="p-4">
189
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
190
+ <div className="relative">
191
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
192
+ <Input
193
+ placeholder="Rechercher un fichier..."
194
+ value={searchQuery}
195
+ onChange={(e) => {
196
+ setSearchQuery(e.target.value)
197
+ handleFilterChange("search", e.target.value)
198
+ }}
199
+ className="pl-9"
200
+ />
201
+ </div>
202
+
203
+ <Select
204
+ value={resultFilter}
205
+ onValueChange={(value) => {
206
+ setResultFilter(value)
207
+ handleFilterChange("result", value)
208
+ }}
209
+ >
210
+ <SelectTrigger>
211
+ <SelectValue placeholder="Résultat" />
212
+ </SelectTrigger>
213
+ <SelectContent>
214
+ <SelectItem value="all">Tous les résultats</SelectItem>
215
+ <SelectItem value="clean">Clean</SelectItem>
216
+ <SelectItem value="infected">Infected</SelectItem>
217
+ <SelectItem value="error">Error</SelectItem>
218
+ </SelectContent>
219
+ </Select>
220
+
221
+ <Select
222
+ value={period}
223
+ onValueChange={(value) => {
224
+ setPeriod(value)
225
+ handleFilterChange("period", value)
226
+ }}
227
+ >
228
+ <SelectTrigger>
229
+ <SelectValue placeholder="Période" />
230
+ </SelectTrigger>
231
+ <SelectContent>
232
+ <SelectItem value="today">Aujourd'hui</SelectItem>
233
+ <SelectItem value="7d">7 derniers jours</SelectItem>
234
+ <SelectItem value="30d">30 derniers jours</SelectItem>
235
+ <SelectItem value="custom">Personnalisé</SelectItem>
236
+ </SelectContent>
237
+ </Select>
238
+
239
+ <Select
240
+ value={userFilter}
241
+ onValueChange={(value) => {
242
+ setUserFilter(value)
243
+ handleFilterChange("user", value)
244
+ }}
245
+ >
246
+ <SelectTrigger>
247
+ <SelectValue placeholder="Utilisateur" />
248
+ </SelectTrigger>
249
+ <SelectContent>
250
+ <SelectItem value="all">Tous les utilisateurs</SelectItem>
251
+ {uniqueUsers.map((userId) => {
252
+ const user = entries.find((e) => e.userId === userId)
253
+ return (
254
+ <SelectItem key={userId} value={userId}>
255
+ {user?.userName || userId}
256
+ </SelectItem>
257
+ )
258
+ })}
259
+ </SelectContent>
260
+ </Select>
261
+ </div>
262
+ </Card>
263
+
264
+ {/* Table */}
265
+ <Card>
266
+ <ScrollArea className="h-[600px]">
267
+ <div className="min-w-full">
268
+ <table className="w-full">
269
+ <thead className="sticky top-0 bg-muted/50 backdrop-blur-sm border-b">
270
+ <tr>
271
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
272
+ Date/Heure
273
+ </th>
274
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
275
+ Fichier
276
+ </th>
277
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
278
+ Taille
279
+ </th>
280
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
281
+ Hash SHA256
282
+ </th>
283
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
284
+ Résultat
285
+ </th>
286
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
287
+ Menace
288
+ </th>
289
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
290
+ Utilisateur
291
+ </th>
292
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
293
+ Durée
294
+ </th>
295
+ <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
296
+ Moteur
297
+ </th>
298
+ </tr>
299
+ </thead>
300
+ <tbody className="divide-y">
301
+ {paginatedEntries.length === 0 ? (
302
+ <tr>
303
+ <td colSpan={9} className="px-4 py-8 text-center text-muted-foreground">
304
+ Aucun scan trouvé
305
+ </td>
306
+ </tr>
307
+ ) : (
308
+ paginatedEntries.map((entry) => (
309
+ <tr
310
+ key={entry.id}
311
+ className="hover:bg-muted/50 transition-colors"
312
+ >
313
+ <td className="px-4 py-3 text-sm whitespace-nowrap">
314
+ {formatDate(entry.scanDate)}
315
+ </td>
316
+ <td className="px-4 py-3 text-sm font-medium max-w-xs truncate">
317
+ {entry.filename}
318
+ </td>
319
+ <td className="px-4 py-3 text-sm whitespace-nowrap">
320
+ {formatFileSize(entry.fileSize)}
321
+ </td>
322
+ <td className="px-4 py-3 text-sm">
323
+ <div className="flex items-center gap-2">
324
+ <code className="text-xs bg-muted px-2 py-1 rounded">
325
+ {truncateHash(entry.fileHash)}
326
+ </code>
327
+ <Button
328
+ variant="ghost"
329
+ size="sm"
330
+ onClick={() => handleCopyHash(entry.fileHash)}
331
+ className="h-6 w-6 p-0"
332
+ >
333
+ {copiedHash === entry.fileHash ? (
334
+ <Check className="w-3 h-3 text-green-600" />
335
+ ) : (
336
+ <Copy className="w-3 h-3" />
337
+ )}
338
+ </Button>
339
+ </div>
340
+ </td>
341
+ <td className="px-4 py-3 text-sm">
342
+ {getResultBadge(entry.result)}
343
+ </td>
344
+ <td className="px-4 py-3 text-sm">
345
+ {entry.threatName ? (
346
+ <code className="text-xs bg-red-100 dark:bg-red-900/20 text-red-700 dark:text-red-400 px-2 py-1 rounded">
347
+ {entry.threatName}
348
+ </code>
349
+ ) : (
350
+ <span className="text-muted-foreground">-</span>
351
+ )}
352
+ </td>
353
+ <td className="px-4 py-3 text-sm whitespace-nowrap">
354
+ {entry.userName}
355
+ </td>
356
+ <td className="px-4 py-3 text-sm whitespace-nowrap">
357
+ {entry.duration}ms
358
+ </td>
359
+ <td className="px-4 py-3 text-sm whitespace-nowrap">
360
+ <div className="text-xs">
361
+ <div className="font-medium">{entry.engineVersion}</div>
362
+ <div className="text-muted-foreground">
363
+ {entry.signatureVersion}
364
+ </div>
365
+ </div>
366
+ </td>
367
+ </tr>
368
+ ))
369
+ )}
370
+ </tbody>
371
+ </table>
372
+ </div>
373
+ </ScrollArea>
374
+
375
+ {/* Pagination */}
376
+ {totalPages > 1 && (
377
+ <div className="border-t px-4 py-3 flex items-center justify-between">
378
+ <div className="text-sm text-muted-foreground">
379
+ Page {currentPage} sur {totalPages}
380
+ </div>
381
+ <div className="flex gap-2">
382
+ <Button
383
+ variant="outline"
384
+ size="sm"
385
+ onClick={() => onPageChange?.(currentPage - 1)}
386
+ disabled={currentPage === 1}
387
+ >
388
+ Précédent
389
+ </Button>
390
+ <Button
391
+ variant="outline"
392
+ size="sm"
393
+ onClick={() => onPageChange?.(currentPage + 1)}
394
+ disabled={currentPage === totalPages}
395
+ >
396
+ Suivant
397
+ </Button>
398
+ </div>
399
+ </div>
400
+ )}
401
+ </Card>
402
+ </div>
403
+ )
404
+ }
405
+
406
+ export default ScanHistoryLog
@@ -0,0 +1,106 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+ import { ScanPolicyEditor, defaultScanPolicy, type ScanPolicy } from "./index"
3
+
4
+ const meta: Meta<typeof ScanPolicyEditor> = {
5
+ title: "Blocks/Antivirus/ScanPolicyEditor",
6
+ component: ScanPolicyEditor,
7
+ parameters: {
8
+ layout: "centered",
9
+ },
10
+ tags: ["autodocs"],
11
+ }
12
+
13
+ export default meta
14
+ type Story = StoryObj<typeof ScanPolicyEditor>
15
+
16
+ export const Default: Story = {
17
+ args: {
18
+ policy: defaultScanPolicy,
19
+ onSave: (policy: ScanPolicy) => {
20
+ console.log("Policy saved:", policy)
21
+ },
22
+ onCancel: () => {
23
+ console.log("Cancelled")
24
+ },
25
+ onResetDefaults: () => {
26
+ console.log("Reset to defaults")
27
+ },
28
+ },
29
+ }
30
+
31
+ const customPolicy: ScanPolicy = {
32
+ autoScanOnUpload: true,
33
+ maxFileSize: 500,
34
+ maxFileSizeUnit: "MB",
35
+ scanTimeout: 300,
36
+ maxConcurrentScans: 8,
37
+ scanArchives: true,
38
+ maxArchiveDepth: 5,
39
+ excludedExtensions: [".log", ".tmp", ".lock", ".cache", ".bak", ".old"],
40
+ excludedPaths: ["/tmp", "/var/cache", "/var/log", "/home/user/.cache"],
41
+ whitelistedHashes: [
42
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
43
+ "d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2",
44
+ ],
45
+ detectionAction: "block_and_notify",
46
+ notifyAdmin: true,
47
+ notifyUser: true,
48
+ auditLog: true,
49
+ }
50
+
51
+ export const CustomPolicy: Story = {
52
+ args: {
53
+ policy: customPolicy,
54
+ onSave: (policy: ScanPolicy) => {
55
+ console.log("Custom policy saved:", policy)
56
+ },
57
+ onCancel: () => {
58
+ console.log("Cancelled")
59
+ },
60
+ onResetDefaults: () => {
61
+ console.log("Reset to defaults")
62
+ },
63
+ },
64
+ }
65
+
66
+ export const Loading: Story = {
67
+ args: {
68
+ policy: defaultScanPolicy,
69
+ isLoading: true,
70
+ onSave: (policy: ScanPolicy) => {
71
+ console.log("Policy saved:", policy)
72
+ },
73
+ },
74
+ }
75
+
76
+ export const ArchiveScanningDisabled: Story = {
77
+ args: {
78
+ policy: {
79
+ ...defaultScanPolicy,
80
+ scanArchives: false,
81
+ },
82
+ onSave: (policy: ScanPolicy) => {
83
+ console.log("Policy saved:", policy)
84
+ },
85
+ },
86
+ }
87
+
88
+ export const StrictPolicy: Story = {
89
+ args: {
90
+ policy: {
91
+ ...defaultScanPolicy,
92
+ maxFileSize: 50,
93
+ maxFileSizeUnit: "MB",
94
+ scanTimeout: 60,
95
+ maxConcurrentScans: 2,
96
+ maxArchiveDepth: 1,
97
+ detectionAction: "delete",
98
+ notifyAdmin: true,
99
+ notifyUser: false,
100
+ auditLog: true,
101
+ },
102
+ onSave: (policy: ScanPolicy) => {
103
+ console.log("Strict policy saved:", policy)
104
+ },
105
+ },
106
+ }