refactor(frontend): centralize severity styling configuration

- Extract severity color and style definitions into dedicated severity-config module
- Create SEVERITY_STYLES constant with unified badge styling for all severity levels
- Create SEVERITY_COLORS constant for chart visualization consistency
- Add getSeverityStyle() helper function for dynamic severity badge generation
- Add SEVERITY_CARD_STYLES and SEVERITY_ICON_BG constants for notification styling
- Update dashboard components to use centralized severity configuration
- Update fingerprint columns to use getSeverityStyle() helper
- Update notification drawer to reference centralized severity styles
- Update search result cards to use centralized configuration
- Update vulnerability components to import from severity-config module
- Eliminate duplicate severity styling definitions across multiple components
- Improve maintainability by having single source of truth for severity styling
This commit is contained in:
yyhuni
2026-01-14 09:05:14 +08:00
parent 4ce6b148f8
commit ce4330b628
10 changed files with 141 additions and 55 deletions

View File

@@ -26,15 +26,7 @@ import { IconExternalLink } from "@tabler/icons-react"
import type { VulnerabilitySeverity } from "@/types/vulnerability.types"
import { useTranslations } from "next-intl"
import { useLocale } from "next-intl"
// Unified vulnerability severity color configuration (consistent with charts)
const severityStyles: Record<VulnerabilitySeverity, string> = {
critical: "bg-[#da3633]/10 text-[#da3633] border border-[#da3633]/20 dark:text-[#f85149]",
high: "bg-[#d29922]/10 text-[#d29922] border border-[#d29922]/20",
medium: "bg-[#d4a72c]/10 text-[#d4a72c] border border-[#d4a72c]/20",
low: "bg-[#238636]/10 text-[#238636] border border-[#238636]/20 dark:text-[#3fb950]",
info: "bg-[#848d97]/10 text-[#848d97] border border-[#848d97]/20",
}
import { SEVERITY_STYLES } from "@/lib/severity-config"
export function RecentVulnerabilities() {
const router = useRouter()
@@ -54,11 +46,11 @@ export function RecentVulnerabilities() {
}
const severityConfig = useMemo(() => ({
critical: { label: tSeverity("critical"), className: severityStyles.critical },
high: { label: tSeverity("high"), className: severityStyles.high },
medium: { label: tSeverity("medium"), className: severityStyles.medium },
low: { label: tSeverity("low"), className: severityStyles.low },
info: { label: tSeverity("info"), className: severityStyles.info },
critical: { label: tSeverity("critical"), className: SEVERITY_STYLES.critical.className },
high: { label: tSeverity("high"), className: SEVERITY_STYLES.high.className },
medium: { label: tSeverity("medium"), className: SEVERITY_STYLES.medium.className },
low: { label: tSeverity("low"), className: SEVERITY_STYLES.low.className },
info: { label: tSeverity("info"), className: SEVERITY_STYLES.info.className },
}), [tSeverity])
const { data, isLoading } = useQuery({

View File

@@ -18,15 +18,7 @@ import {
} from "@/components/ui/chart"
import { Skeleton } from "@/components/ui/skeleton"
import { useTranslations } from "next-intl"
// 漏洞严重程度使用固定语义化颜色
const SEVERITY_COLORS = {
critical: "#dc2626", // 红色
high: "#f97316", // 橙色
medium: "#eab308", // 黄色
low: "#3b82f6", // 蓝色
info: "#6b7280", // 灰色
}
import { SEVERITY_COLORS } from "@/lib/severity-config"
export function VulnSeverityChart() {
const { data, isLoading } = useAssetStatistics()

View File

@@ -9,6 +9,7 @@ import { ExpandableCell, ExpandableMonoCell } from "@/components/ui/data-table/e
import { ChevronDown, ChevronUp } from "lucide-react"
import { useTranslations } from "next-intl"
import type { FingerPrintHubFingerprint } from "@/types/fingerprint.types"
import { getSeverityStyle } from "@/lib/severity-config"
interface ColumnOptions {
formatDate: (date: string) => string
@@ -18,15 +19,7 @@ interface ColumnOptions {
* Severity badge with color coding (matching Vulnerabilities style)
*/
function SeverityBadge({ severity }: { severity: string }) {
const severityConfig: Record<string, { className: string }> = {
critical: { className: "bg-[#da3633]/10 text-[#da3633] border border-[#da3633]/20 dark:text-[#f85149]" },
high: { className: "bg-[#d29922]/10 text-[#d29922] border border-[#d29922]/20" },
medium: { className: "bg-[#d4a72c]/10 text-[#d4a72c] border border-[#d4a72c]/20" },
low: { className: "bg-[#238636]/10 text-[#238636] border border-[#238636]/20 dark:text-[#3fb950]" },
info: { className: "bg-[#848d97]/10 text-[#848d97] border border-[#848d97]/20" },
}
const config = severityConfig[severity?.toLowerCase()] || severityConfig.info
const config = getSeverityStyle(severity)
return (
<Badge className={config.className}>

View File

@@ -18,6 +18,7 @@ import { cn } from "@/lib/utils"
import { transformBackendNotification, useNotificationSSE } from "@/hooks/use-notification-sse"
import { useMarkAllAsRead, useNotifications } from "@/hooks/use-notifications"
import type { Notification, NotificationType, NotificationSeverity } from "@/types/notification.types"
import { SEVERITY_CARD_STYLES, SEVERITY_ICON_BG } from "@/lib/severity-config"
/**
* Notification drawer component
@@ -199,10 +200,10 @@ export function NotificationDrawer() {
}
const severityCardClassMap: Record<NotificationSeverity, string> = {
critical: "border-[#da3633]/30 bg-[#da3633]/5 hover:bg-[#da3633]/10 dark:border-[#f85149]/30 dark:bg-[#f85149]/5 dark:hover:bg-[#f85149]/10",
high: "border-[#d29922]/30 bg-[#d29922]/5 hover:bg-[#d29922]/10 dark:border-[#d29922]/30 dark:bg-[#d29922]/5 dark:hover:bg-[#d29922]/10",
medium: "border-[#d4a72c]/30 bg-[#d4a72c]/5 hover:bg-[#d4a72c]/10 dark:border-[#d4a72c]/30 dark:bg-[#d4a72c]/5 dark:hover:bg-[#d4a72c]/10",
low: "border-[#848d97]/30 bg-[#848d97]/5 hover:bg-[#848d97]/10 dark:border-[#848d97]/30 dark:bg-[#848d97]/5 dark:hover:bg-[#848d97]/10",
critical: SEVERITY_CARD_STYLES.critical,
high: SEVERITY_CARD_STYLES.high,
medium: SEVERITY_CARD_STYLES.medium,
low: SEVERITY_CARD_STYLES.low,
}
const getNotificationCardClasses = (severity?: NotificationSeverity) => {
@@ -256,10 +257,10 @@ export function NotificationDrawer() {
<div className="flex items-start gap-3">
<div className={cn(
"mt-0.5 p-1.5 rounded-full shrink-0",
notification.severity === 'critical' && "bg-[#da3633]/10 dark:bg-[#f85149]/10",
notification.severity === 'high' && "bg-[#d29922]/10",
notification.severity === 'medium' && "bg-[#d4a72c]/10",
(!notification.severity || notification.severity === 'low') && "bg-muted"
notification.severity === 'critical' && SEVERITY_ICON_BG.critical,
notification.severity === 'high' && SEVERITY_ICON_BG.high,
notification.severity === 'medium' && SEVERITY_ICON_BG.medium,
(!notification.severity || notification.severity === 'low') && SEVERITY_ICON_BG.info
)}>
{getNotificationIcon(notification.type, notification.severity)}
</div>

View File

@@ -37,13 +37,15 @@ interface SearchResultCardProps {
onViewVulnerability?: (vuln: Vulnerability) => void
}
import { SEVERITY_STYLES } from "@/lib/severity-config"
// 漏洞严重程度颜色配置
const severityColors: Record<string, string> = {
critical: "bg-[#da3633]/10 text-[#da3633] border border-[#da3633]/20 dark:text-[#f85149]",
high: "bg-[#d29922]/10 text-[#d29922] border border-[#d29922]/20",
medium: "bg-[#d4a72c]/10 text-[#d4a72c] border border-[#d4a72c]/20",
low: "bg-[#238636]/10 text-[#238636] border border-[#238636]/20 dark:text-[#3fb950]",
info: "bg-[#848d97]/10 text-[#848d97] border border-[#848d97]/20",
critical: SEVERITY_STYLES.critical.className,
high: SEVERITY_STYLES.high.className,
medium: SEVERITY_STYLES.medium.className,
low: SEVERITY_STYLES.low.className,
info: SEVERITY_STYLES.info.className,
}
// 状态码 Badge variant

View File

@@ -8,6 +8,7 @@ import { Badge } from "@/components/ui/badge"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { ExpandableUrlCell } from "@/components/ui/data-table/expandable-cell"
import { DataTableColumnHeader } from "@/components/ui/data-table/column-header"
import { SEVERITY_STYLES } from "@/lib/severity-config"
import type { Vulnerability, VulnerabilitySeverity } from "@/types/vulnerability.types"
@@ -54,12 +55,13 @@ export function createVulnerabilityColumns({
t,
}: ColumnActions): ColumnDef<Vulnerability>[] {
// Unified vulnerability severity color configuration
// Color progression: cool (info) → warm (low/medium) → hot (high/critical)
const severityConfig: Record<VulnerabilitySeverity, { className: string }> = {
critical: { className: "bg-[#da3633]/10 text-[#da3633] border border-[#da3633]/20 dark:text-[#f85149]" },
high: { className: "bg-[#d29922]/10 text-[#d29922] border border-[#d29922]/20" },
medium: { className: "bg-[#d4a72c]/10 text-[#d4a72c] border border-[#d4a72c]/20" },
low: { className: "bg-[#238636]/10 text-[#238636] border border-[#238636]/20 dark:text-[#3fb950]" },
info: { className: "bg-[#848d97]/10 text-[#848d97] border border-[#848d97]/20" },
critical: { className: SEVERITY_STYLES.critical.className },
high: { className: SEVERITY_STYLES.high.className },
medium: { className: SEVERITY_STYLES.medium.className },
low: { className: SEVERITY_STYLES.low.className },
info: { className: SEVERITY_STYLES.info.className },
}
return [

View File

@@ -60,6 +60,7 @@ interface VulnerabilitiesDataTableProps {
reviewFilter?: ReviewFilter
onReviewFilterChange?: (filter: ReviewFilter) => void
pendingCount?: number
reviewedCount?: number
selectedRows?: Vulnerability[]
onBulkMarkAsReviewed?: () => void
onBulkMarkAsPending?: () => void
@@ -89,6 +90,7 @@ export function VulnerabilitiesDataTable({
reviewFilter = "all",
onReviewFilterChange,
pendingCount = 0,
reviewedCount = 0,
selectedRows = [],
onBulkMarkAsReviewed,
onBulkMarkAsPending,
@@ -202,6 +204,11 @@ export function VulnerabilitiesDataTable({
</TabsTrigger>
<TabsTrigger value="reviewed">
{tVuln("reviewStatus.reviewed")}
{reviewedCount > 0 && (
<Badge variant="secondary" className="ml-1.5 h-5 min-w-5 rounded-full px-1.5 text-xs">
{reviewedCount}
</Badge>
)}
</TabsTrigger>
</TabsList>
</Tabs>

View File

@@ -167,6 +167,10 @@ export function VulnerabilitiesDetailView({
? 0
: (targetId ? targetStatsQuery.data?.pendingCount : globalStatsQuery.data?.pendingCount) ?? 0
const reviewedCount = scanId
? 0
: (targetId ? targetStatsQuery.data?.reviewedCount : globalStatsQuery.data?.reviewedCount) ?? 0
const formatDate = (dateString: string): string => {
return new Date(dateString).toLocaleString(getDateLocale(locale), {
@@ -327,6 +331,7 @@ export function VulnerabilitiesDetailView({
reviewFilter={reviewFilter}
onReviewFilterChange={handleReviewFilterChange}
pendingCount={pendingCount}
reviewedCount={reviewedCount}
selectedRows={selectedVulnerabilities}
onBulkMarkAsReviewed={handleBulkMarkAsReviewed}
onBulkMarkAsPending={handleBulkMarkAsPending}

View File

@@ -16,6 +16,7 @@ import { Copy, Check, Info, FileCode, Terminal, Database, ExternalLink } from "l
import { toast } from "sonner"
import { getDateLocale } from "@/lib/date-utils"
import type { Vulnerability, VulnerabilitySeverity } from "@/types/vulnerability.types"
import { SEVERITY_STYLES, SEVERITY_COLORS } from "@/lib/severity-config"
interface SeverityConfigItem {
variant: "default" | "secondary" | "destructive" | "outline"
@@ -24,11 +25,11 @@ interface SeverityConfigItem {
}
const severityConfig: Record<VulnerabilitySeverity, SeverityConfigItem> = {
critical: { variant: "outline", color: "bg-[#da3633]", className: "bg-[#da3633]/10 text-[#da3633] border-[#da3633]/20 dark:text-[#f85149]" },
high: { variant: "outline", color: "bg-[#d29922]", className: "bg-[#d29922]/10 text-[#d29922] border-[#d29922]/20" },
medium: { variant: "outline", color: "bg-[#d4a72c]", className: "bg-[#d4a72c]/10 text-[#d4a72c] border-[#d4a72c]/20" },
low: { variant: "outline", color: "bg-[#238636]", className: "bg-[#238636]/10 text-[#238636] border-[#238636]/20 dark:text-[#3fb950]" },
info: { variant: "outline", color: "bg-[#848d97]", className: "bg-[#848d97]/10 text-[#848d97] border-[#848d97]/20" },
critical: { variant: "outline", color: `bg-[${SEVERITY_COLORS.critical}]`, className: SEVERITY_STYLES.critical.className },
high: { variant: "outline", color: `bg-[${SEVERITY_COLORS.high}]`, className: SEVERITY_STYLES.high.className },
medium: { variant: "outline", color: `bg-[${SEVERITY_COLORS.medium}]`, className: SEVERITY_STYLES.medium.className },
low: { variant: "outline", color: `bg-[${SEVERITY_COLORS.low}]`, className: SEVERITY_STYLES.low.className },
info: { variant: "outline", color: `bg-[${SEVERITY_COLORS.info}]`, className: SEVERITY_STYLES.info.className },
}
interface VulnerabilityDetailDialogProps {

View File

@@ -0,0 +1,91 @@
/**
* Global severity color configuration
* Color progression: cool (info) → warm (low/medium) → hot (high/critical)
*
* Used for: vulnerabilities, notifications, fingerprints, etc.
*/
export type SeverityLevel = 'critical' | 'high' | 'medium' | 'low' | 'info'
export interface SeverityStyle {
className: string
color: string // solid color for charts/icons
bgColor: string // background color
}
// Core color values (for charts, icons, etc.)
export const SEVERITY_COLORS = {
critical: '#9b1c31',
high: '#dc2626',
medium: '#f97316',
low: '#eab308',
info: '#6b7280',
} as const
// Dark mode text colors
export const SEVERITY_COLORS_DARK = {
critical: '#f87171',
high: '#f87171',
medium: '#fb923c',
low: '#facc15',
info: '#9ca3af',
} as const
// Badge/Tag styles with background, text, and border
export const SEVERITY_STYLES: Record<SeverityLevel, SeverityStyle> = {
critical: {
className: 'bg-[#9b1c31]/10 text-[#9b1c31] border border-[#9b1c31]/20 dark:bg-[#9b1c31]/20 dark:text-[#f87171]',
color: SEVERITY_COLORS.critical,
bgColor: 'rgba(155, 28, 49, 0.1)',
},
high: {
className: 'bg-[#dc2626]/10 text-[#dc2626] border border-[#dc2626]/20 dark:text-[#f87171]',
color: SEVERITY_COLORS.high,
bgColor: 'rgba(220, 38, 38, 0.1)',
},
medium: {
className: 'bg-[#f97316]/10 text-[#ea580c] border border-[#f97316]/20 dark:text-[#fb923c]',
color: SEVERITY_COLORS.medium,
bgColor: 'rgba(249, 115, 22, 0.1)',
},
low: {
className: 'bg-[#eab308]/10 text-[#ca8a04] border border-[#eab308]/20 dark:text-[#facc15]',
color: SEVERITY_COLORS.low,
bgColor: 'rgba(234, 179, 8, 0.1)',
},
info: {
className: 'bg-[#6b7280]/10 text-[#6b7280] border border-[#6b7280]/20 dark:text-[#9ca3af]',
color: SEVERITY_COLORS.info,
bgColor: 'rgba(107, 114, 128, 0.1)',
},
}
// Card styles for notifications (with hover states)
export const SEVERITY_CARD_STYLES: Record<SeverityLevel, string> = {
critical: 'border-[#9b1c31]/30 bg-[#9b1c31]/5 hover:bg-[#9b1c31]/10 dark:border-[#f87171]/30 dark:bg-[#f87171]/5 dark:hover:bg-[#f87171]/10',
high: 'border-[#dc2626]/30 bg-[#dc2626]/5 hover:bg-[#dc2626]/10 dark:border-[#f87171]/30 dark:bg-[#f87171]/5 dark:hover:bg-[#f87171]/10',
medium: 'border-[#f97316]/30 bg-[#f97316]/5 hover:bg-[#f97316]/10 dark:border-[#fb923c]/30 dark:bg-[#fb923c]/5 dark:hover:bg-[#fb923c]/10',
low: 'border-[#eab308]/30 bg-[#eab308]/5 hover:bg-[#eab308]/10 dark:border-[#facc15]/30 dark:bg-[#facc15]/5 dark:hover:bg-[#facc15]/10',
info: 'border-[#6b7280]/30 bg-[#6b7280]/5 hover:bg-[#6b7280]/10 dark:border-[#9ca3af]/30 dark:bg-[#9ca3af]/5 dark:hover:bg-[#9ca3af]/10',
}
// Icon background styles
export const SEVERITY_ICON_BG: Record<SeverityLevel, string> = {
critical: 'bg-[#9b1c31]/10 dark:bg-[#f87171]/10',
high: 'bg-[#dc2626]/10 dark:bg-[#f87171]/10',
medium: 'bg-[#f97316]/10 dark:bg-[#fb923c]/10',
low: 'bg-[#eab308]/10 dark:bg-[#facc15]/10',
info: 'bg-muted',
}
// Helper function to get severity style
export function getSeverityStyle(severity: string): SeverityStyle {
const normalized = severity?.toLowerCase() as SeverityLevel
return SEVERITY_STYLES[normalized] || SEVERITY_STYLES.info
}
// Helper function to get severity color
export function getSeverityColor(severity: string): string {
const normalized = severity?.toLowerCase() as SeverityLevel
return SEVERITY_COLORS[normalized] || SEVERITY_COLORS.info
}