@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,525 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Badge } from "../../components/badge"
6
+ import { Button } from "../../components/button"
7
+ import { Card, CardContent, CardHeader, CardTitle } from "../../components/card"
8
+ import { Progress } from "../../components/progress"
9
+ import { Separator } from "../../components/separator"
10
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/tabs"
11
+ import {
12
+ Shield,
13
+ ShieldAlert,
14
+ ShieldCheck,
15
+ ShieldX,
16
+ Activity,
17
+ AlertTriangle,
18
+ CheckCircle,
19
+ XCircle,
20
+ Clock,
21
+ FileText,
22
+ TrendingUp,
23
+ PlayCircle,
24
+ Archive,
25
+ AlertCircle,
26
+ } from "lucide-react"
27
+
28
+ // ============================================
29
+ // TYPES
30
+ // ============================================
31
+
32
+ export type ServiceHealth = "healthy" | "degraded" | "down"
33
+ export type ScanResult = "clean" | "infected" | "error"
34
+ export type ThreatSeverity = "critical" | "high" | "medium" | "low"
35
+
36
+ export interface ScanEntry {
37
+ id: string
38
+ filename: string
39
+ result: ScanResult
40
+ threatName?: string
41
+ scanDate: Date
42
+ duration: number // ms
43
+ fileSize: number
44
+ userId?: string
45
+ }
46
+
47
+ export interface ThreatInfo {
48
+ name: string
49
+ count: number
50
+ severity: ThreatSeverity
51
+ }
52
+
53
+ export interface WeeklyStat {
54
+ day: string
55
+ scans: number
56
+ threats: number
57
+ }
58
+
59
+ export interface AntivirusDashboardProps {
60
+ serviceHealth?: ServiceHealth
61
+ clamavVersion?: string
62
+ signatureCount?: number
63
+ lastSignatureUpdate?: Date
64
+ scansToday?: number
65
+ threatsDetected?: number
66
+ quarantinedFiles?: number
67
+ detectionRate?: number
68
+ recentScans?: ScanEntry[]
69
+ weeklyStats?: WeeklyStat[]
70
+ topThreats?: ThreatInfo[]
71
+ onStartScan?: () => void
72
+ onViewQuarantine?: () => void
73
+ className?: string
74
+ }
75
+
76
+ // ============================================
77
+ // CONFIG & HELPERS
78
+ // ============================================
79
+
80
+ const serviceHealthConfig: Record<ServiceHealth, { color: string; icon: React.ReactNode; label: string }> = {
81
+ healthy: { color: "text-green-500 bg-green-500/10", icon: <ShieldCheck className="h-4 w-4" />, label: "Healthy" },
82
+ degraded: { color: "text-yellow-500 bg-yellow-500/10", icon: <ShieldAlert className="h-4 w-4" />, label: "Degraded" },
83
+ down: { color: "text-red-500 bg-red-500/10", icon: <ShieldX className="h-4 w-4" />, label: "Down" },
84
+ }
85
+
86
+ const scanResultConfig: Record<ScanResult, { color: string; icon: React.ReactNode; label: string; bgColor: string }> = {
87
+ clean: { color: "text-green-500", icon: <CheckCircle className="h-4 w-4" />, label: "Clean", bgColor: "bg-green-500" },
88
+ infected: { color: "text-red-500", icon: <XCircle className="h-4 w-4" />, label: "Infected", bgColor: "bg-red-500" },
89
+ error: { color: "text-orange-500", icon: <AlertCircle className="h-4 w-4" />, label: "Error", bgColor: "bg-orange-500" },
90
+ }
91
+
92
+ const threatSeverityConfig: Record<ThreatSeverity, { color: string; bgColor: string }> = {
93
+ critical: { color: "text-red-500", bgColor: "bg-red-500" },
94
+ high: { color: "text-orange-500", bgColor: "bg-orange-500" },
95
+ medium: { color: "text-yellow-500", bgColor: "bg-yellow-500" },
96
+ low: { color: "text-blue-500", bgColor: "bg-blue-500" },
97
+ }
98
+
99
+ function formatFileSize(bytes: number): string {
100
+ if (bytes < 1024) return `${bytes} B`
101
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
102
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
103
+ }
104
+
105
+ function formatDuration(ms: number): string {
106
+ if (ms < 1000) return `${ms}ms`
107
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
108
+ return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`
109
+ }
110
+
111
+ function formatRelativeTime(date: Date): string {
112
+ const now = new Date()
113
+ const diff = now.getTime() - date.getTime()
114
+ const minutes = Math.floor(diff / 60000)
115
+ const hours = Math.floor(minutes / 60)
116
+ const days = Math.floor(hours / 24)
117
+
118
+ if (minutes < 1) return "just now"
119
+ if (minutes < 60) return `${minutes}m ago`
120
+ if (hours < 24) return `${hours}h ago`
121
+ return `${days}d ago`
122
+ }
123
+
124
+ // ============================================
125
+ // SUBCOMPONENTS
126
+ // ============================================
127
+
128
+ function ServiceHealthBadge({ health }: { health: ServiceHealth }) {
129
+ const config = serviceHealthConfig[health]
130
+ return (
131
+ <Badge variant="outline" className={cn("gap-1", config.color)}>
132
+ {config.icon}
133
+ {config.label}
134
+ </Badge>
135
+ )
136
+ }
137
+
138
+ function ScanResultBadge({ result }: { result: ScanResult }) {
139
+ const config = scanResultConfig[result]
140
+ return (
141
+ <Badge variant="outline" className={cn("gap-1", config.color)}>
142
+ {config.icon}
143
+ {config.label}
144
+ </Badge>
145
+ )
146
+ }
147
+
148
+ function KPICard({
149
+ title,
150
+ value,
151
+ icon,
152
+ trend,
153
+ color = "text-primary"
154
+ }: {
155
+ title: string
156
+ value: string | number
157
+ icon: React.ReactNode
158
+ trend?: string
159
+ color?: string
160
+ }) {
161
+ return (
162
+ <Card>
163
+ <CardContent className="p-6">
164
+ <div className="flex items-center justify-between">
165
+ <div>
166
+ <p className="text-sm text-muted-foreground mb-1">{title}</p>
167
+ <div className="flex items-baseline gap-2">
168
+ <p className={cn("text-3xl font-bold", color)}>{value}</p>
169
+ {trend && (
170
+ <span className="text-xs text-muted-foreground">{trend}</span>
171
+ )}
172
+ </div>
173
+ </div>
174
+ <div className={cn("p-3 rounded-lg", `${color.replace("text-", "bg-")}/10`)}>
175
+ {icon}
176
+ </div>
177
+ </div>
178
+ </CardContent>
179
+ </Card>
180
+ )
181
+ }
182
+
183
+ function WeeklyChart({ stats }: { stats: WeeklyStat[] }) {
184
+ const maxValue = Math.max(...stats.map(s => Math.max(s.scans, s.threats)), 1)
185
+
186
+ return (
187
+ <Card>
188
+ <CardHeader>
189
+ <CardTitle className="text-base flex items-center gap-2">
190
+ <TrendingUp className="h-4 w-4" />
191
+ Weekly Activity
192
+ </CardTitle>
193
+ </CardHeader>
194
+ <CardContent>
195
+ <div className="space-y-4">
196
+ {stats.map((stat, index) => {
197
+ const scansPercent = (stat.scans / maxValue) * 100
198
+ const threatsPercent = (stat.threats / maxValue) * 100
199
+
200
+ return (
201
+ <div key={stat.day} className="space-y-2">
202
+ <div className="flex items-center justify-between text-sm">
203
+ <span className="text-muted-foreground min-w-12">{stat.day}</span>
204
+ <div className="flex items-center gap-4 text-xs">
205
+ <span className="flex items-center gap-1">
206
+ <div className="w-2 h-2 rounded-full bg-blue-500" />
207
+ {stat.scans} scans
208
+ </span>
209
+ <span className="flex items-center gap-1">
210
+ <div className="w-2 h-2 rounded-full bg-red-500" />
211
+ {stat.threats} threats
212
+ </span>
213
+ </div>
214
+ </div>
215
+ <div className="space-y-1">
216
+ <div className="h-2 w-full bg-muted rounded-full overflow-hidden">
217
+ <div
218
+ className="h-full bg-blue-500 rounded-full transition-all"
219
+ style={{ width: `${scansPercent}%` }}
220
+ />
221
+ </div>
222
+ {stat.threats > 0 && (
223
+ <div className="h-2 w-full bg-muted rounded-full overflow-hidden">
224
+ <div
225
+ className="h-full bg-red-500 rounded-full transition-all"
226
+ style={{ width: `${threatsPercent}%` }}
227
+ />
228
+ </div>
229
+ )}
230
+ </div>
231
+ </div>
232
+ )
233
+ })}
234
+ </div>
235
+ </CardContent>
236
+ </Card>
237
+ )
238
+ }
239
+
240
+ function RecentScansTable({ scans }: { scans: ScanEntry[] }) {
241
+ return (
242
+ <Card>
243
+ <CardHeader>
244
+ <CardTitle className="text-base flex items-center gap-2">
245
+ <Activity className="h-4 w-4" />
246
+ Recent Scans
247
+ </CardTitle>
248
+ </CardHeader>
249
+ <CardContent>
250
+ <div className="space-y-3">
251
+ {scans.length === 0 ? (
252
+ <div className="text-center py-8 text-muted-foreground">
253
+ <Shield className="h-12 w-12 mx-auto mb-3 opacity-50" />
254
+ <p className="text-sm">No scans yet</p>
255
+ </div>
256
+ ) : (
257
+ scans.map((scan) => (
258
+ <div
259
+ key={scan.id}
260
+ className={cn(
261
+ "flex items-center justify-between p-3 rounded-lg border transition-colors",
262
+ scan.result === "infected" && "border-red-500/50 bg-red-500/5"
263
+ )}
264
+ >
265
+ <div className="flex items-center gap-3 flex-1 min-w-0">
266
+ <div className={cn("p-2 rounded-lg shrink-0", scanResultConfig[scan.result].color.replace("text-", "bg-") + "/10")}>
267
+ {scanResultConfig[scan.result].icon}
268
+ </div>
269
+ <div className="flex-1 min-w-0">
270
+ <div className="flex items-center gap-2 flex-wrap">
271
+ <p className="text-sm font-medium truncate">{scan.filename}</p>
272
+ <ScanResultBadge result={scan.result} />
273
+ </div>
274
+ {scan.threatName && (
275
+ <p className="text-xs text-red-500 mt-1 flex items-center gap-1">
276
+ <AlertTriangle className="h-3 w-3" />
277
+ {scan.threatName}
278
+ </p>
279
+ )}
280
+ <div className="flex items-center gap-3 mt-1 text-xs text-muted-foreground">
281
+ <span className="flex items-center gap-1">
282
+ <Clock className="h-3 w-3" />
283
+ {formatRelativeTime(scan.scanDate)}
284
+ </span>
285
+ <span>{formatDuration(scan.duration)}</span>
286
+ <span>{formatFileSize(scan.fileSize)}</span>
287
+ </div>
288
+ </div>
289
+ </div>
290
+ </div>
291
+ ))
292
+ )}
293
+ </div>
294
+ </CardContent>
295
+ </Card>
296
+ )
297
+ }
298
+
299
+ function TopThreatsTable({ threats }: { threats: ThreatInfo[] }) {
300
+ return (
301
+ <Card>
302
+ <CardHeader>
303
+ <CardTitle className="text-base flex items-center gap-2">
304
+ <AlertTriangle className="h-4 w-4" />
305
+ Top Threats
306
+ </CardTitle>
307
+ </CardHeader>
308
+ <CardContent>
309
+ <div className="space-y-3">
310
+ {threats.length === 0 ? (
311
+ <div className="text-center py-8 text-muted-foreground">
312
+ <ShieldCheck className="h-12 w-12 mx-auto mb-3 text-green-500 opacity-50" />
313
+ <p className="text-sm">No threats detected</p>
314
+ </div>
315
+ ) : (
316
+ threats.map((threat, index) => {
317
+ const sevConfig = threatSeverityConfig[threat.severity]
318
+ return (
319
+ <div key={threat.name} className="flex items-center gap-3">
320
+ <div className="flex items-center justify-center w-8 h-8 rounded-full bg-muted text-sm font-medium">
321
+ {index + 1}
322
+ </div>
323
+ <div className="flex-1 min-w-0">
324
+ <div className="flex items-center gap-2">
325
+ <p className="text-sm font-medium truncate">{threat.name}</p>
326
+ <Badge
327
+ variant="outline"
328
+ className={cn("text-xs capitalize", sevConfig.color)}
329
+ >
330
+ {threat.severity}
331
+ </Badge>
332
+ </div>
333
+ <div className="flex items-center gap-2 mt-1">
334
+ <div className="flex-1 h-2 bg-muted rounded-full overflow-hidden">
335
+ <div
336
+ className={cn("h-full rounded-full", sevConfig.bgColor)}
337
+ style={{ width: `${Math.min((threat.count / Math.max(...threats.map(t => t.count))) * 100, 100)}%` }}
338
+ />
339
+ </div>
340
+ <span className="text-xs text-muted-foreground font-medium min-w-12 text-right">
341
+ {threat.count} {threat.count === 1 ? 'time' : 'times'}
342
+ </span>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ )
347
+ })
348
+ )}
349
+ </div>
350
+ </CardContent>
351
+ </Card>
352
+ )
353
+ }
354
+
355
+ // ============================================
356
+ // MAIN COMPONENT
357
+ // ============================================
358
+
359
+ export function AntivirusDashboard({
360
+ serviceHealth = "healthy",
361
+ clamavVersion = "1.0.3",
362
+ signatureCount = 8642758,
363
+ lastSignatureUpdate,
364
+ scansToday = 0,
365
+ threatsDetected = 0,
366
+ quarantinedFiles = 0,
367
+ detectionRate = 0,
368
+ recentScans = [],
369
+ weeklyStats = [],
370
+ topThreats = [],
371
+ onStartScan,
372
+ onViewQuarantine,
373
+ className,
374
+ }: AntivirusDashboardProps) {
375
+ return (
376
+ <div className={cn("space-y-6", className)}>
377
+ {/* Header */}
378
+ <div className="flex flex-col sm:flex-row gap-4 justify-between items-start">
379
+ <div>
380
+ <div className="flex items-center gap-3">
381
+ <div className="p-2 rounded-lg bg-primary/10">
382
+ <Shield className="h-6 w-6 text-primary" />
383
+ </div>
384
+ <div>
385
+ <h1 className="text-2xl font-bold">Antivirus ClamAV</h1>
386
+ <p className="text-sm text-muted-foreground">
387
+ Version {clamavVersion} • {signatureCount.toLocaleString()} signatures
388
+ {lastSignatureUpdate && (
389
+ <> • Updated {formatRelativeTime(lastSignatureUpdate)}</>
390
+ )}
391
+ </p>
392
+ </div>
393
+ </div>
394
+ </div>
395
+ <div className="flex items-center gap-2">
396
+ <ServiceHealthBadge health={serviceHealth} />
397
+ {onViewQuarantine && (
398
+ <Button variant="outline" onClick={onViewQuarantine}>
399
+ <Archive className="h-4 w-4 mr-2" />
400
+ Quarantine
401
+ </Button>
402
+ )}
403
+ {onStartScan && (
404
+ <Button onClick={onStartScan}>
405
+ <PlayCircle className="h-4 w-4 mr-2" />
406
+ Start Scan
407
+ </Button>
408
+ )}
409
+ </div>
410
+ </div>
411
+
412
+ {/* KPI Cards */}
413
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
414
+ <KPICard
415
+ title="Scans Today"
416
+ value={scansToday.toLocaleString()}
417
+ icon={<Activity className="h-6 w-6" />}
418
+ color="text-blue-500"
419
+ />
420
+ <KPICard
421
+ title="Threats Detected"
422
+ value={threatsDetected.toLocaleString()}
423
+ icon={<AlertTriangle className="h-6 w-6" />}
424
+ color="text-red-500"
425
+ />
426
+ <KPICard
427
+ title="Quarantined Files"
428
+ value={quarantinedFiles.toLocaleString()}
429
+ icon={<Archive className="h-6 w-6" />}
430
+ color="text-orange-500"
431
+ />
432
+ <KPICard
433
+ title="Detection Rate"
434
+ value={`${detectionRate.toFixed(1)}%`}
435
+ icon={<ShieldCheck className="h-6 w-6" />}
436
+ color="text-green-500"
437
+ />
438
+ </div>
439
+
440
+ {/* Charts and Stats */}
441
+ <div className="grid gap-4 lg:grid-cols-2">
442
+ <WeeklyChart stats={weeklyStats} />
443
+ <TopThreatsTable threats={topThreats} />
444
+ </div>
445
+
446
+ {/* Recent Scans */}
447
+ <RecentScansTable scans={recentScans} />
448
+ </div>
449
+ )
450
+ }
451
+
452
+ // ============================================
453
+ // PRESET DATA
454
+ // ============================================
455
+
456
+ export const defaultRecentScans: ScanEntry[] = [
457
+ {
458
+ id: "scan1",
459
+ filename: "document.pdf",
460
+ result: "clean",
461
+ scanDate: new Date(Date.now() - 5 * 60000),
462
+ duration: 234,
463
+ fileSize: 2048576,
464
+ userId: "user123",
465
+ },
466
+ {
467
+ id: "scan2",
468
+ filename: "malicious_script.js",
469
+ result: "infected",
470
+ threatName: "Trojan.GenericKD.67890123",
471
+ scanDate: new Date(Date.now() - 15 * 60000),
472
+ duration: 156,
473
+ fileSize: 45678,
474
+ userId: "user456",
475
+ },
476
+ {
477
+ id: "scan3",
478
+ filename: "eicar_test.txt",
479
+ result: "infected",
480
+ threatName: "Eicar-Test-Signature",
481
+ scanDate: new Date(Date.now() - 30 * 60000),
482
+ duration: 89,
483
+ fileSize: 68,
484
+ userId: "admin",
485
+ },
486
+ {
487
+ id: "scan4",
488
+ filename: "report_2024.xlsx",
489
+ result: "clean",
490
+ scanDate: new Date(Date.now() - 45 * 60000),
491
+ duration: 567,
492
+ fileSize: 5242880,
493
+ userId: "user789",
494
+ },
495
+ {
496
+ id: "scan5",
497
+ filename: "suspicious_archive.zip",
498
+ result: "infected",
499
+ threatName: "Win.Trojan.Genome-9999999",
500
+ scanDate: new Date(Date.now() - 60 * 60000),
501
+ duration: 1234,
502
+ fileSize: 10485760,
503
+ userId: "user321",
504
+ },
505
+ ]
506
+
507
+ export const defaultWeeklyStats: WeeklyStat[] = [
508
+ { day: "Mon", scans: 145, threats: 3 },
509
+ { day: "Tue", scans: 198, threats: 5 },
510
+ { day: "Wed", scans: 167, threats: 2 },
511
+ { day: "Thu", scans: 223, threats: 8 },
512
+ { day: "Fri", scans: 189, threats: 4 },
513
+ { day: "Sat", scans: 98, threats: 1 },
514
+ { day: "Sun", scans: 76, threats: 0 },
515
+ ]
516
+
517
+ export const defaultTopThreats: ThreatInfo[] = [
518
+ { name: "Eicar-Test-Signature", count: 12, severity: "low" },
519
+ { name: "Trojan.GenericKD.67890123", count: 8, severity: "high" },
520
+ { name: "Win.Trojan.Genome-9999999", count: 5, severity: "critical" },
521
+ { name: "JS.Downloader.Nemucod", count: 4, severity: "medium" },
522
+ { name: "PHP.Webshell.Generic", count: 3, severity: "high" },
523
+ ]
524
+
525
+ export default AntivirusDashboard