Files
xingrin/frontend/components/scan/engine/engine-columns.tsx
yyhuni 0baabe0753 feat(i18n): internationalize frontend components with English translations
- Replace Chinese comments with English equivalents across auth, dashboard, and scan components
- Update UI text labels and descriptions from Chinese to English in bulk-add-urls-dialog
- Translate placeholder text and dialog titles in asset management components
- Update column headers and data table labels to English in organization and engine modules
- Standardize English documentation strings in auth-guard and auth-layout components
- Improve code maintainability and accessibility for international users
- Align with existing internationalization efforts across the frontend codebase
2025-12-29 18:39:25 +08:00

345 lines
8.7 KiB
TypeScript

"use client"
import React from "react"
import { ColumnDef } from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
MoreHorizontal,
Trash2,
Check,
Edit,
X as XIcon,
} from "lucide-react"
import { DataTableColumnHeader } from "@/components/ui/data-table/column-header"
import * as yaml from "js-yaml"
import type { ScanEngine } from "@/types/engine.types"
// Translation type definitions
export interface EngineTranslations {
columns: {
engineName: string
subdomainDiscovery: string
portScan: string
siteScan: string
directoryScan: string
urlFetch: string
osint: string
vulnerabilityScan: string
wafDetection: string
screenshot: string
}
actions: {
editEngine: string
delete: string
openMenu: string
}
tooltips: {
editEngine: string
}
}
/**
* Parse engine YAML configuration and detect if features are enabled
*/
function parseEngineFeatures(engine: ScanEngine) {
if (engine.configuration) {
try {
const config = yaml.load(engine.configuration) as any
return {
subdomain_discovery: !!config?.subdomain_discovery,
port_scan: !!config?.port_scan,
site_scan: !!config?.site_scan,
directory_scan: !!config?.directory_scan,
url_fetch: !!config?.url_fetch || !!config?.fetch_url,
osint: !!config?.osint,
vulnerability_scan: !!config?.vulnerability_scan,
waf_detection: !!config?.waf_detection,
screenshot: !!config?.screenshot,
}
} catch (error) {
console.error("Failed to parse YAML configuration:", error)
}
}
return {
subdomain_discovery: false,
port_scan: false,
site_scan: false,
directory_scan: false,
url_fetch: false,
osint: false,
vulnerability_scan: false,
waf_detection: false,
screenshot: false,
}
}
/**
* Feature support status component
*/
function FeatureStatus({ enabled }: { enabled?: boolean }) {
if (enabled) {
return (
<div className="flex justify-center">
<Check className="h-5 w-5 text-chart-4" />
</div>
)
}
return (
<div className="flex justify-center">
<XIcon className="h-5 w-5 text-destructive" />
</div>
)
}
interface CreateColumnsProps {
handleEdit: (engine: ScanEngine) => void
handleDelete: (engine: ScanEngine) => void
t: EngineTranslations
}
/**
* Engine row actions component
*/
function EngineRowActions({
onEdit,
onDelete,
t,
}: {
onEdit: () => void
onDelete: () => void
t: EngineTranslations
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
>
<MoreHorizontal />
<span className="sr-only">{t.actions.openMenu}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={onEdit}>
<Edit />
{t.actions.editEngine}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={onDelete}
className="text-destructive focus:text-destructive"
>
<Trash2 />
{t.actions.delete}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
/**
* Create engine table column definitions
*/
export const createEngineColumns = ({
handleEdit,
handleDelete,
t,
}: CreateColumnsProps): ColumnDef<ScanEngine>[] => [
{
accessorKey: "name",
size: 200,
minSize: 150,
maxSize: 350,
meta: { title: t.columns.engineName },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.engineName} />
),
cell: ({ row }) => {
const name = row.getValue("name") as string
return (
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => handleEdit(row.original)}
className="max-w-[300px] truncate font-medium text-left hover:text-primary hover:underline underline-offset-2 cursor-pointer transition-colors"
>
{name}
</button>
</TooltipTrigger>
<TooltipContent>{t.tooltips.editEngine}</TooltipContent>
</Tooltip>
)
},
},
{
id: "subdomain_discovery",
meta: { title: t.columns.subdomainDiscovery },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.subdomainDiscovery} />
),
size: 80,
minSize: 60,
maxSize: 100,
enableResizing: false,
cell: ({ row }) => {
const features = parseEngineFeatures(row.original)
return <FeatureStatus enabled={features.subdomain_discovery} />
},
enableSorting: false,
},
{
id: "port_scan",
meta: { title: t.columns.portScan },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.portScan} />
),
size: 80,
minSize: 60,
maxSize: 100,
enableResizing: false,
cell: ({ row }) => {
const features = parseEngineFeatures(row.original)
return <FeatureStatus enabled={features.port_scan} />
},
enableSorting: false,
},
{
id: "site_scan",
meta: { title: t.columns.siteScan },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.siteScan} />
),
size: 80,
minSize: 60,
maxSize: 100,
enableResizing: false,
cell: ({ row }) => {
const features = parseEngineFeatures(row.original)
return <FeatureStatus enabled={features.site_scan} />
},
enableSorting: false,
},
{
id: "directory_scan",
meta: { title: t.columns.directoryScan },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.directoryScan} />
),
size: 80,
minSize: 60,
maxSize: 100,
enableResizing: false,
cell: ({ row }) => {
const features = parseEngineFeatures(row.original)
return <FeatureStatus enabled={features.directory_scan} />
},
enableSorting: false,
},
{
id: "url_fetch",
meta: { title: t.columns.urlFetch },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.urlFetch} />
),
size: 80,
minSize: 60,
maxSize: 100,
enableResizing: false,
cell: ({ row }) => {
const features = parseEngineFeatures(row.original)
return <FeatureStatus enabled={features.url_fetch} />
},
enableSorting: false,
},
{
id: "osint",
meta: { title: t.columns.osint },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.osint} />
),
size: 80,
minSize: 60,
maxSize: 100,
enableResizing: false,
cell: ({ row }) => {
const features = parseEngineFeatures(row.original)
return <FeatureStatus enabled={features.osint} />
},
enableSorting: false,
},
{
id: "vulnerability_scan",
meta: { title: t.columns.vulnerabilityScan },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.vulnerabilityScan} />
),
size: 80,
minSize: 60,
maxSize: 100,
enableResizing: false,
cell: ({ row }) => {
const features = parseEngineFeatures(row.original)
return <FeatureStatus enabled={features.vulnerability_scan} />
},
enableSorting: false,
},
{
id: "waf_detection",
meta: { title: t.columns.wafDetection },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.wafDetection} />
),
size: 80,
minSize: 60,
maxSize: 100,
enableResizing: false,
cell: ({ row }) => {
const features = parseEngineFeatures(row.original)
return <FeatureStatus enabled={features.waf_detection} />
},
enableSorting: false,
},
{
id: "screenshot",
meta: { title: t.columns.screenshot },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t.columns.screenshot} />
),
size: 80,
minSize: 60,
maxSize: 100,
enableResizing: false,
cell: ({ row }) => {
const features = parseEngineFeatures(row.original)
return <FeatureStatus enabled={features.screenshot} />
},
enableSorting: false,
},
{
id: "actions",
size: 60,
minSize: 60,
maxSize: 60,
enableResizing: false,
cell: ({ row }) => (
<EngineRowActions
onEdit={() => handleEdit(row.original)}
onDelete={() => handleDelete(row.original)}
t={t}
/>
),
enableSorting: false,
enableHiding: false,
},
]