mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-05-01 09:14:56 +00:00
refactor: extract security dialog components
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import { Button } from '@/components/ui'
|
||||
import { DialogFooter } from '@/components/ui'
|
||||
import type { SecurityScanResult } from '@/lib/security/scanner/security-scanner'
|
||||
|
||||
interface ActionButtonsProps {
|
||||
scanResult: SecurityScanResult
|
||||
onCancel: () => void
|
||||
onProceed?: () => void
|
||||
showProceedButton?: boolean
|
||||
}
|
||||
|
||||
export function ActionButtons({
|
||||
scanResult,
|
||||
onCancel,
|
||||
onProceed,
|
||||
showProceedButton = false
|
||||
}: ActionButtonsProps) {
|
||||
const disableProceed = scanResult.severity === 'critical'
|
||||
|
||||
return (
|
||||
<DialogFooter className="flex-col sm:flex-row gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{scanResult.safe ? 'Close' : 'Cancel'}
|
||||
</Button>
|
||||
|
||||
{!scanResult.safe && showProceedButton && (
|
||||
<Button
|
||||
variant={scanResult.severity === 'critical' ? 'destructive' : 'default'}
|
||||
onClick={onProceed}
|
||||
disabled={disableProceed}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{scanResult.severity === 'critical' ? 'Force Proceed (Not Recommended)' : 'Proceed Anyway'}
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import { Alert, AlertDescription } from '@/components/ui'
|
||||
import { Badge } from '@/components/ui'
|
||||
import { DialogDescription, DialogHeader, DialogTitle } from '@/components/ui'
|
||||
import { ScrollArea } from '@/components/ui'
|
||||
import { Separator } from '@/components/ui'
|
||||
import { ShieldWarning, Warning, Info, CheckCircle } from '@phosphor-icons/react'
|
||||
import type { SecurityIssue, SecurityScanResult } from '@/lib/security/scanner/security-scanner'
|
||||
import { getSeverityColor, getSeverityIcon } from '@/lib/security/scanner/security-scanner'
|
||||
|
||||
interface SecurityMessageProps {
|
||||
scanResult: SecurityScanResult
|
||||
codeType?: string
|
||||
}
|
||||
|
||||
const severityOrder = ['critical', 'high', 'medium', 'low'] as const
|
||||
|
||||
const getSeverityBadgeVariant = (severity: string) => {
|
||||
switch (severity) {
|
||||
case 'critical':
|
||||
case 'high':
|
||||
return 'destructive'
|
||||
case 'medium':
|
||||
return 'default'
|
||||
case 'low':
|
||||
return 'secondary'
|
||||
default:
|
||||
return 'outline'
|
||||
}
|
||||
}
|
||||
|
||||
const getIcon = (severity: string) => {
|
||||
switch (severity) {
|
||||
case 'critical':
|
||||
case 'high':
|
||||
return <ShieldWarning className="w-12 h-12 text-red-600" weight="fill" />
|
||||
case 'medium':
|
||||
return <Warning className="w-12 h-12 text-yellow-600" weight="fill" />
|
||||
case 'low':
|
||||
return <Info className="w-12 h-12 text-blue-600" weight="fill" />
|
||||
default:
|
||||
return <CheckCircle className="w-12 h-12 text-green-600" weight="fill" />
|
||||
}
|
||||
}
|
||||
|
||||
const getTitle = (severity: string, safe: boolean) => {
|
||||
if (safe) return 'Code Security Check Passed'
|
||||
|
||||
switch (severity) {
|
||||
case 'critical':
|
||||
return 'CRITICAL SECURITY THREAT DETECTED'
|
||||
case 'high':
|
||||
return 'High-Risk Security Issues Detected'
|
||||
case 'medium':
|
||||
return 'Security Warnings Detected'
|
||||
case 'low':
|
||||
return 'Minor Security Concerns'
|
||||
default:
|
||||
return 'Security Scan Complete'
|
||||
}
|
||||
}
|
||||
|
||||
const getDescription = (scanResult: SecurityScanResult, codeType: string) => {
|
||||
if (scanResult.safe) {
|
||||
return `Your ${codeType} has been scanned and appears to be safe.`
|
||||
}
|
||||
|
||||
const { issues } = scanResult
|
||||
return `Your ${codeType} contains ${issues.length} security ${issues.length === 1 ? 'issue' : 'issues'} that require attention.`
|
||||
}
|
||||
|
||||
export function SecurityMessage({ scanResult, codeType = 'code' }: SecurityMessageProps) {
|
||||
const groupedIssues = scanResult.issues.reduce((acc, issue) => {
|
||||
if (!acc[issue.severity]) {
|
||||
acc[issue.severity] = []
|
||||
}
|
||||
acc[issue.severity].push(issue)
|
||||
return acc
|
||||
}, {} as Record<string, SecurityIssue[]>)
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
{getIcon(scanResult.severity)}
|
||||
<div className="flex-1">
|
||||
<DialogTitle className="text-2xl">{getTitle(scanResult.severity, scanResult.safe)}</DialogTitle>
|
||||
<DialogDescription className="text-base mt-1">
|
||||
{getDescription(scanResult, codeType)}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<ScrollArea className="max-h-[50vh] pr-4">
|
||||
{scanResult.safe ? (
|
||||
<Alert className="border-green-200 bg-green-50">
|
||||
<CheckCircle className="h-5 w-5 text-green-600" weight="fill" />
|
||||
<AlertDescription className="text-green-800">
|
||||
No security issues detected. Your code follows security best practices.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{severityOrder.map(severity => {
|
||||
const issues = groupedIssues[severity]
|
||||
if (!issues || issues.length === 0) return null
|
||||
|
||||
return (
|
||||
<div key={severity} className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={getSeverityBadgeVariant(severity)} className="uppercase">
|
||||
{getSeverityIcon(severity)} {severity}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{issues.length} {issues.length === 1 ? 'issue' : 'issues'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{issues.map((issue, idx) => (
|
||||
<Alert key={idx} className={getSeverityColor(issue.severity)}>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold">{issue.message}</p>
|
||||
{issue.line && (
|
||||
<p className="text-sm opacity-75 mt-1">
|
||||
Line {issue.line}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{issue.pattern}
|
||||
</Badge>
|
||||
</div>
|
||||
{issue.recommendation && (
|
||||
<>
|
||||
<Separator className="my-2" />
|
||||
<div className="text-sm">
|
||||
<p className="font-medium mb-1">Recommendation:</p>
|
||||
<p className="opacity-90">{issue.recommendation}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Alert>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
{(scanResult.severity === 'critical' || scanResult.severity === 'high') && (
|
||||
<Alert className="border-red-300 bg-red-50 mt-4">
|
||||
<ShieldWarning className="h-5 w-5 text-red-600" weight="fill" />
|
||||
<AlertDescription className="text-red-800">
|
||||
<p className="font-semibold mb-2">Security Alert</p>
|
||||
<p>
|
||||
{scanResult.severity === 'critical'
|
||||
? 'This code contains CRITICAL security vulnerabilities that could compromise system security, steal data, or execute malicious actions. It is strongly recommended NOT to proceed.'
|
||||
: 'This code contains HIGH-RISK security issues that could lead to vulnerabilities or unexpected behavior. Carefully review and fix these issues before proceeding.'}
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +1,19 @@
|
||||
/**
|
||||
* SecurityWarningDialog - Organism Component
|
||||
*
|
||||
*
|
||||
* This component is categorized as an organism (not a molecule) because:
|
||||
* 1. It contains complex data processing (groups security issues by severity)
|
||||
* 2. It implements security-specific business rules (severity ordering, badge variants)
|
||||
* 3. It's a feature-specific component for security scanning results
|
||||
* 4. It exceeds the recommended 150 LOC guideline for molecules (235 LOC)
|
||||
*
|
||||
*
|
||||
* See: docs/analysis/molecule-organism-audit.md for full categorization analysis
|
||||
*/
|
||||
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui'
|
||||
import { Button } from '@/components/ui'
|
||||
import { Alert, AlertDescription } from '@/components/ui'
|
||||
import { ScrollArea } from '@/components/ui'
|
||||
import { Badge } from '@/components/ui'
|
||||
import { Separator } from '@/components/ui'
|
||||
import { ShieldWarning, Warning, Info, CheckCircle } from '@phosphor-icons/react'
|
||||
import type { SecurityScanResult, SecurityIssue } from '@/lib/security/scanner/security-scanner'
|
||||
import { getSeverityColor, getSeverityIcon } from '@/lib/security/scanner/security-scanner'
|
||||
import { Dialog, DialogContent } from '@/components/ui'
|
||||
import type { SecurityScanResult } from '@/lib/security/scanner/security-scanner'
|
||||
import { ActionButtons } from './ActionButtons'
|
||||
import { SecurityMessage } from './SecurityMessage'
|
||||
|
||||
interface SecurityWarningDialogProps {
|
||||
open: boolean
|
||||
@@ -47,200 +34,34 @@ export function SecurityWarningDialog({
|
||||
codeType = 'code',
|
||||
showProceedButton = false
|
||||
}: SecurityWarningDialogProps) {
|
||||
const [acknowledged, setAcknowledged] = useState(false)
|
||||
const closeDialog = () => {
|
||||
onOpenChange(false)
|
||||
}
|
||||
|
||||
const handleProceed = () => {
|
||||
if (onProceed) {
|
||||
onProceed()
|
||||
}
|
||||
setAcknowledged(false)
|
||||
onOpenChange(false)
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onCancel) {
|
||||
onCancel()
|
||||
}
|
||||
setAcknowledged(false)
|
||||
onOpenChange(false)
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
const getSeverityBadgeVariant = (severity: string) => {
|
||||
switch (severity) {
|
||||
case 'critical':
|
||||
return 'destructive'
|
||||
case 'high':
|
||||
return 'destructive'
|
||||
case 'medium':
|
||||
return 'default'
|
||||
case 'low':
|
||||
return 'secondary'
|
||||
default:
|
||||
return 'outline'
|
||||
}
|
||||
}
|
||||
|
||||
const getIcon = () => {
|
||||
switch (scanResult.severity) {
|
||||
case 'critical':
|
||||
case 'high':
|
||||
return <ShieldWarning className="w-12 h-12 text-red-600" weight="fill" />
|
||||
case 'medium':
|
||||
return <Warning className="w-12 h-12 text-yellow-600" weight="fill" />
|
||||
case 'low':
|
||||
return <Info className="w-12 h-12 text-blue-600" weight="fill" />
|
||||
default:
|
||||
return <CheckCircle className="w-12 h-12 text-green-600" weight="fill" />
|
||||
}
|
||||
}
|
||||
|
||||
const getTitle = () => {
|
||||
if (scanResult.safe) {
|
||||
return 'Code Security Check Passed'
|
||||
}
|
||||
switch (scanResult.severity) {
|
||||
case 'critical':
|
||||
return 'CRITICAL SECURITY THREAT DETECTED'
|
||||
case 'high':
|
||||
return 'High-Risk Security Issues Detected'
|
||||
case 'medium':
|
||||
return 'Security Warnings Detected'
|
||||
case 'low':
|
||||
return 'Minor Security Concerns'
|
||||
default:
|
||||
return 'Security Scan Complete'
|
||||
}
|
||||
}
|
||||
|
||||
const getDescription = () => {
|
||||
if (scanResult.safe) {
|
||||
return `Your ${codeType} has been scanned and appears to be safe.`
|
||||
}
|
||||
return `Your ${codeType} contains ${scanResult.issues.length} security ${scanResult.issues.length === 1 ? 'issue' : 'issues'} that require attention.`
|
||||
}
|
||||
|
||||
const groupedIssues = scanResult.issues.reduce((acc, issue) => {
|
||||
if (!acc[issue.severity]) {
|
||||
acc[issue.severity] = []
|
||||
}
|
||||
acc[issue.severity].push(issue)
|
||||
return acc
|
||||
}, {} as Record<string, SecurityIssue[]>)
|
||||
|
||||
const severityOrder = ['critical', 'high', 'medium', 'low']
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-3xl max-h-[80vh]">
|
||||
<DialogHeader>
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
{getIcon()}
|
||||
<div className="flex-1">
|
||||
<DialogTitle className="text-2xl">{getTitle()}</DialogTitle>
|
||||
<DialogDescription className="text-base mt-1">
|
||||
{getDescription()}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<ScrollArea className="max-h-[50vh] pr-4">
|
||||
{scanResult.safe ? (
|
||||
<Alert className="border-green-200 bg-green-50">
|
||||
<CheckCircle className="h-5 w-5 text-green-600" weight="fill" />
|
||||
<AlertDescription className="text-green-800">
|
||||
No security issues detected. Your code follows security best practices.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{severityOrder.map(severity => {
|
||||
const issues = groupedIssues[severity]
|
||||
if (!issues || issues.length === 0) return null
|
||||
|
||||
return (
|
||||
<div key={severity} className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={getSeverityBadgeVariant(severity)} className="uppercase">
|
||||
{getSeverityIcon(severity)} {severity}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{issues.length} {issues.length === 1 ? 'issue' : 'issues'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{issues.map((issue, idx) => (
|
||||
<Alert key={idx} className={getSeverityColor(issue.severity)}>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold">{issue.message}</p>
|
||||
{issue.line && (
|
||||
<p className="text-sm opacity-75 mt-1">
|
||||
Line {issue.line}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{issue.pattern}
|
||||
</Badge>
|
||||
</div>
|
||||
{issue.recommendation && (
|
||||
<>
|
||||
<Separator className="my-2" />
|
||||
<div className="text-sm">
|
||||
<p className="font-medium mb-1">Recommendation:</p>
|
||||
<p className="opacity-90">{issue.recommendation}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Alert>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
{(scanResult.severity === 'critical' || scanResult.severity === 'high') && (
|
||||
<Alert className="border-red-300 bg-red-50 mt-4">
|
||||
<ShieldWarning className="h-5 w-5 text-red-600" weight="fill" />
|
||||
<AlertDescription className="text-red-800">
|
||||
<p className="font-semibold mb-2">Security Alert</p>
|
||||
<p>
|
||||
{scanResult.severity === 'critical'
|
||||
? 'This code contains CRITICAL security vulnerabilities that could compromise system security, steal data, or execute malicious actions. It is strongly recommended NOT to proceed.'
|
||||
: 'This code contains HIGH-RISK security issues that could lead to vulnerabilities or unexpected behavior. Carefully review and fix these issues before proceeding.'
|
||||
}
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
<DialogFooter className="flex-col sm:flex-row gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{scanResult.safe ? 'Close' : 'Cancel'}
|
||||
</Button>
|
||||
|
||||
{!scanResult.safe && showProceedButton && (
|
||||
<Button
|
||||
variant={scanResult.severity === 'critical' ? 'destructive' : 'default'}
|
||||
onClick={handleProceed}
|
||||
disabled={scanResult.severity === 'critical' && !acknowledged}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{scanResult.severity === 'critical' ? 'Force Proceed (Not Recommended)' : 'Proceed Anyway'}
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
<SecurityMessage scanResult={scanResult} codeType={codeType} />
|
||||
<ActionButtons
|
||||
scanResult={scanResult}
|
||||
onCancel={handleCancel}
|
||||
onProceed={showProceedButton ? handleProceed : undefined}
|
||||
showProceedButton={showProceedButton}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user