@lievclouds/dsh-cortex 0.4.4 → 1.0.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.
Potentially problematic release.
This version of @lievclouds/dsh-cortex might be problematic. Click here for more details.
- package/README.md +1 -1
- package/client.js +428 -406
- package/index.js +7 -0
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +115 -106
- package/src/cortex-ui/dsh/api.ts +5 -2
- package/src/cortex-ui/dsh/css-overrides.css +8 -0
- package/src/cortex-ui/renderer/App.tsx +39 -0
- package/src/cortex-ui/renderer/components/asset/AssetDetail.tsx +9 -1
- package/src/cortex-ui/renderer/components/miniapp/FileTree.tsx +161 -0
- package/src/cortex-ui/renderer/components/miniapp/MiniappCard.tsx +263 -0
- package/src/cortex-ui/renderer/components/miniapp/MiniappDetail.tsx +1493 -0
- package/src/cortex-ui/renderer/components/retest/CurlImportDialog.tsx +158 -0
- package/src/cortex-ui/renderer/components/retest/RepeaterCollectionsDialog.tsx +327 -0
- package/src/cortex-ui/renderer/components/retest/RepeaterHistoryDialog.tsx +242 -0
- package/src/cortex-ui/renderer/components/retest/RepeaterPanel.tsx +333 -148
- package/src/cortex-ui/renderer/components/retest/YakitSendPanel.tsx +411 -0
- package/src/cortex-ui/renderer/components/vuln/DirectoryPickerDialog.tsx +187 -0
- package/src/cortex-ui/renderer/components/vuln/ExportReportDialog.tsx +173 -59
- package/src/cortex-ui/renderer/components/vuln/VulnDetail.tsx +16 -4
- package/src/cortex-ui/renderer/i18n/messages/common.ts +2 -0
- package/src/cortex-ui/renderer/i18n/messages/fuzzer.ts +32 -0
- package/src/cortex-ui/renderer/i18n/messages/index.ts +5 -1
- package/src/cortex-ui/renderer/i18n/messages/miniapp.ts +392 -0
- package/src/cortex-ui/renderer/i18n/messages/retest.ts +146 -2
- package/src/cortex-ui/renderer/i18n/messages/vuln.ts +26 -6
- package/src/cortex-ui/renderer/lib/curl.ts +635 -0
- package/src/cortex-ui/renderer/lib/miniappApi.ts +459 -0
- package/src/cortex-ui/renderer/lib/navigation.ts +13 -1
- package/src/cortex-ui/renderer/lib/repeaterRaw.ts +102 -0
- package/src/cortex-ui/renderer/lib/repeaterStoreApi.ts +163 -0
- package/src/cortex-ui/renderer/lib/retestResponse.ts +64 -0
- package/src/cortex-ui/renderer/lib/transport/httpClient.ts +1 -1
- package/src/cortex-ui/renderer/pages/DashboardPage.tsx +3 -3
- package/src/cortex-ui/renderer/pages/FuzzerPage.tsx +212 -0
- package/src/cortex-ui/renderer/pages/MiniappPage.tsx +664 -0
- package/src/cortex-ui/shared/ipc.ts +3 -4
- package/src/cortex-ui/shared/types.ts +9 -0
- package/src/host/cortex/fs.js +72 -0
- package/src/host/cortex/index.js +49 -1
- package/src/host/cortex/repeater-store.js +411 -0
- package/src/host/cortex/repeater.js +15 -2
- package/src/host/cortex/settings.js +54 -0
- package/src/host/cortex/tools.js +2 -0
- package/src/host/cortex/vulns.js +36 -13
- package/src/host/miniapp/analyze.js +1078 -0
- package/src/host/miniapp/assets-import.js +294 -0
- package/src/host/miniapp/detect.js +426 -0
- package/src/host/miniapp/format.js +263 -0
- package/src/host/miniapp/roots.js +360 -0
- package/src/host/miniapp/routes.js +304 -0
- package/src/host/miniapp/service.js +559 -0
- package/src/host/miniapp/store.js +756 -0
- package/src/host/miniapp/tools.js +335 -0
- package/src/host/miniapp/unpack.js +1010 -0
- package/src/host/miniapp/wxapkg.js +416 -0
- package/src/host/reports-office.js +341 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
2
2
|
import { Download, FolderOpen, Loader2 } from 'lucide-react'
|
|
3
3
|
import { useEffect, useState } from 'react'
|
|
4
|
+
import type { ExportReportFormat } from '@shared/types'
|
|
4
5
|
import { Button } from '@renderer/components/ui/button'
|
|
5
6
|
import { useI18n } from '@renderer/i18n'
|
|
7
|
+
import { cn } from '@renderer/lib/utils'
|
|
6
8
|
import { getPortalContainer } from '../../../dsh/portal'
|
|
9
|
+
import { DirectoryPickerDialog } from './DirectoryPickerDialog'
|
|
7
10
|
|
|
8
11
|
const STORAGE_KEY = 'cortex.export.dir'
|
|
12
|
+
const FORMAT_KEY = 'cortex.export.format'
|
|
9
13
|
|
|
10
14
|
function isAbsoluteLike(value: string): boolean {
|
|
11
15
|
const trimmed = value.trim()
|
|
@@ -21,7 +25,7 @@ interface ExportReportDialogProps {
|
|
|
21
25
|
count: number
|
|
22
26
|
pending: boolean
|
|
23
27
|
onOpenChange: (open: boolean) => void
|
|
24
|
-
onConfirm: (outputDir: string | undefined) => void
|
|
28
|
+
onConfirm: (outputDir: string | undefined, format: ExportReportFormat) => void
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/**
|
|
@@ -38,13 +42,49 @@ export function ExportReportDialog({
|
|
|
38
42
|
}: ExportReportDialogProps) {
|
|
39
43
|
const { t } = useI18n()
|
|
40
44
|
const [directory, setDirectory] = useState('')
|
|
45
|
+
const [format, setFormat] = useState<ExportReportFormat>('md')
|
|
41
46
|
const [invalid, setInvalid] = useState(false)
|
|
47
|
+
const [pickerOpen, setPickerOpen] = useState(false)
|
|
42
48
|
|
|
43
49
|
useEffect(() => {
|
|
44
50
|
if (!open) return
|
|
45
|
-
|
|
51
|
+
let alive = true
|
|
52
|
+
let stored = ''
|
|
53
|
+
try {
|
|
54
|
+
stored = window.localStorage.getItem(STORAGE_KEY) ?? ''
|
|
55
|
+
} catch {
|
|
56
|
+
stored = ''
|
|
57
|
+
}
|
|
58
|
+
const storedFormat = (() => {
|
|
59
|
+
try {
|
|
60
|
+
return window.localStorage.getItem(FORMAT_KEY)
|
|
61
|
+
} catch {
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
})()
|
|
46
65
|
setDirectory(stored)
|
|
66
|
+
setFormat(storedFormat === 'docx' || storedFormat === 'pdf' ? storedFormat : 'md')
|
|
47
67
|
setInvalid(false)
|
|
68
|
+
// The Host remembers the last successful export directory; prefer it over
|
|
69
|
+
// any stale per-browser value so a fresh WebView still defaults correctly.
|
|
70
|
+
void fetch('/cortex-api/cortex/export/settings')
|
|
71
|
+
.then((response) => (response.ok ? response.json() : null))
|
|
72
|
+
.then((payload) => {
|
|
73
|
+
if (!alive) return
|
|
74
|
+
const hostDir = payload && typeof payload.outputDir === 'string' ? payload.outputDir.trim() : ''
|
|
75
|
+
if (hostDir.length > 0) {
|
|
76
|
+
setDirectory(hostDir)
|
|
77
|
+
try {
|
|
78
|
+
window.localStorage.setItem(STORAGE_KEY, hostDir)
|
|
79
|
+
} catch {
|
|
80
|
+
/* ignore */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
.catch(() => {})
|
|
85
|
+
return () => {
|
|
86
|
+
alive = false
|
|
87
|
+
}
|
|
48
88
|
}, [open])
|
|
49
89
|
|
|
50
90
|
const confirm = (): void => {
|
|
@@ -53,68 +93,142 @@ export function ExportReportDialog({
|
|
|
53
93
|
setInvalid(true)
|
|
54
94
|
return
|
|
55
95
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
96
|
+
try {
|
|
97
|
+
if (trimmed !== '') window.localStorage.setItem(STORAGE_KEY, trimmed)
|
|
98
|
+
else window.localStorage.removeItem(STORAGE_KEY)
|
|
99
|
+
window.localStorage.setItem(FORMAT_KEY, format)
|
|
100
|
+
} catch {
|
|
101
|
+
/* localStorage may be unavailable; the Host remembers the directory */
|
|
102
|
+
}
|
|
103
|
+
onConfirm(trimmed === '' ? undefined : trimmed, format)
|
|
59
104
|
}
|
|
60
105
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
data-testid="export-report-dialog"
|
|
67
|
-
className="fixed left-1/2 top-1/2 z-[70] w-[min(480px,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-background p-5 shadow-panel focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
|
68
|
-
>
|
|
69
|
-
<DialogPrimitive.Title className="text-[16px] font-medium tracking-tight text-foreground">
|
|
70
|
-
{t('vuln.export.title')}
|
|
71
|
-
</DialogPrimitive.Title>
|
|
72
|
-
<DialogPrimitive.Description className="mt-1.5 text-[12.5px] leading-relaxed text-muted-foreground">
|
|
73
|
-
{t('vuln.export.description', { count })}
|
|
74
|
-
</DialogPrimitive.Description>
|
|
106
|
+
const formats: Array<{ value: ExportReportFormat; label: string; testId: string }> = [
|
|
107
|
+
{ value: 'md', label: t('vuln.export.formatMd'), testId: 'export-format-md' },
|
|
108
|
+
{ value: 'docx', label: t('vuln.export.formatDocx'), testId: 'export-format-docx' },
|
|
109
|
+
{ value: 'pdf', label: t('vuln.export.formatPdf'), testId: 'export-format-pdf' },
|
|
110
|
+
]
|
|
75
111
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
spellCheck={false}
|
|
88
|
-
disabled={pending}
|
|
89
|
-
placeholder={t('vuln.export.dirPlaceholder')}
|
|
90
|
-
onChange={(event) => {
|
|
91
|
-
setDirectory(event.target.value)
|
|
92
|
-
if (invalid) setInvalid(false)
|
|
112
|
+
return (
|
|
113
|
+
<>
|
|
114
|
+
<DialogPrimitive.Root open={open} onOpenChange={onOpenChange} modal={false}>
|
|
115
|
+
<DialogPrimitive.Portal container={getPortalContainer()}>
|
|
116
|
+
<DialogPrimitive.Overlay className="sheet-overlay fixed inset-0 z-[70] bg-[hsl(224_18%_10%/0.3)]" />
|
|
117
|
+
<DialogPrimitive.Content
|
|
118
|
+
data-testid="export-report-dialog"
|
|
119
|
+
onInteractOutside={(event) => {
|
|
120
|
+
// The directory picker is a sibling Radix layer; keep this dialog
|
|
121
|
+
// open while the user browses folders.
|
|
122
|
+
if (pickerOpen) event.preventDefault()
|
|
93
123
|
}}
|
|
94
|
-
|
|
95
|
-
if (event.
|
|
124
|
+
onEscapeKeyDown={(event) => {
|
|
125
|
+
if (pickerOpen) event.preventDefault()
|
|
96
126
|
}}
|
|
97
|
-
className="
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
127
|
+
className="fixed left-1/2 top-1/2 z-[70] w-[min(480px,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-background p-5 shadow-panel focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
|
128
|
+
>
|
|
129
|
+
<DialogPrimitive.Title className="text-[16px] font-medium tracking-tight text-foreground">
|
|
130
|
+
{t('vuln.export.title')}
|
|
131
|
+
</DialogPrimitive.Title>
|
|
132
|
+
<DialogPrimitive.Description className="sr-only">
|
|
133
|
+
{t('vuln.export.title')}
|
|
134
|
+
</DialogPrimitive.Description>
|
|
135
|
+
|
|
136
|
+
<div className="mt-4">
|
|
137
|
+
<p className="text-[11.5px] font-medium text-foreground">{t('vuln.export.formatLabel')}</p>
|
|
138
|
+
<div
|
|
139
|
+
role="radiogroup"
|
|
140
|
+
aria-label={t('vuln.export.formatLabel')}
|
|
141
|
+
className="mt-1.5 inline-flex rounded-md border border-border bg-muted p-0.5"
|
|
142
|
+
>
|
|
143
|
+
{formats.map((item) => (
|
|
144
|
+
<button
|
|
145
|
+
key={item.value}
|
|
146
|
+
type="button"
|
|
147
|
+
role="radio"
|
|
148
|
+
aria-checked={format === item.value}
|
|
149
|
+
data-testid={item.testId}
|
|
150
|
+
disabled={pending}
|
|
151
|
+
onClick={() => setFormat(item.value)}
|
|
152
|
+
className={cn(
|
|
153
|
+
'h-7 rounded px-2.5 text-[11.5px] transition-colors',
|
|
154
|
+
format === item.value
|
|
155
|
+
? 'bg-background font-medium text-foreground shadow-sm'
|
|
156
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
157
|
+
)}
|
|
158
|
+
>
|
|
159
|
+
{item.label}
|
|
160
|
+
</button>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<label
|
|
166
|
+
htmlFor="export-output-dir"
|
|
167
|
+
className="mt-4 flex items-center gap-1.5 text-[11.5px] font-medium text-foreground"
|
|
168
|
+
>
|
|
169
|
+
<FolderOpen aria-hidden="true" className="h-3.5 w-3.5 text-muted-foreground" />
|
|
170
|
+
{t('vuln.export.dirLabel')}
|
|
171
|
+
</label>
|
|
172
|
+
<div className="mt-1.5 flex items-center gap-1.5">
|
|
173
|
+
<input
|
|
174
|
+
id="export-output-dir"
|
|
175
|
+
data-testid="export-output-dir"
|
|
176
|
+
value={directory}
|
|
177
|
+
spellCheck={false}
|
|
178
|
+
disabled={pending}
|
|
179
|
+
placeholder={t('vuln.export.dirPlaceholder')}
|
|
180
|
+
onChange={(event) => {
|
|
181
|
+
setDirectory(event.target.value)
|
|
182
|
+
if (invalid) setInvalid(false)
|
|
183
|
+
}}
|
|
184
|
+
onKeyDown={(event) => {
|
|
185
|
+
if (event.key === 'Enter') confirm()
|
|
186
|
+
}}
|
|
187
|
+
className="h-9 min-w-0 flex-1 rounded-md border border-border bg-background px-2.5 font-mono text-[11.5px] text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
188
|
+
/>
|
|
189
|
+
<Button
|
|
190
|
+
variant="outline"
|
|
191
|
+
size="sm"
|
|
192
|
+
data-testid="export-browse-dir"
|
|
193
|
+
disabled={pending}
|
|
194
|
+
onClick={() => setPickerOpen(true)}
|
|
195
|
+
className="h-9 shrink-0 gap-1.5"
|
|
196
|
+
>
|
|
197
|
+
<FolderOpen aria-hidden="true" className="h-3.5 w-3.5" />
|
|
198
|
+
{t('vuln.export.browse')}
|
|
199
|
+
</Button>
|
|
200
|
+
</div>
|
|
201
|
+
{invalid ? (
|
|
202
|
+
<p className="mt-1.5 text-[10.5px] text-red-700">{t('vuln.export.dirInvalid')}</p>
|
|
203
|
+
) : null}
|
|
204
|
+
|
|
205
|
+
<div className="mt-5 flex items-center justify-end gap-2">
|
|
206
|
+
<Button variant="outline" size="sm" onClick={() => onOpenChange(false)} disabled={pending}>
|
|
207
|
+
{t('common.cancel')}
|
|
208
|
+
</Button>
|
|
209
|
+
<Button size="sm" onClick={confirm} disabled={pending} className="gap-1.5">
|
|
210
|
+
{pending ? (
|
|
211
|
+
<Loader2 aria-hidden="true" className="h-3.5 w-3.5 animate-spin" />
|
|
212
|
+
) : (
|
|
213
|
+
<Download aria-hidden="true" className="h-3.5 w-3.5" />
|
|
214
|
+
)}
|
|
215
|
+
{pending ? t('vuln.detail.exporting') : t('vuln.export.confirm')}
|
|
216
|
+
</Button>
|
|
217
|
+
</div>
|
|
218
|
+
</DialogPrimitive.Content>
|
|
219
|
+
</DialogPrimitive.Portal>
|
|
220
|
+
</DialogPrimitive.Root>
|
|
102
221
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
</Button>
|
|
115
|
-
</div>
|
|
116
|
-
</DialogPrimitive.Content>
|
|
117
|
-
</DialogPrimitive.Portal>
|
|
118
|
-
</DialogPrimitive.Root>
|
|
222
|
+
<DirectoryPickerDialog
|
|
223
|
+
open={pickerOpen}
|
|
224
|
+
initialPath={directory}
|
|
225
|
+
onOpenChange={setPickerOpen}
|
|
226
|
+
onSelect={(path) => {
|
|
227
|
+
setDirectory(path)
|
|
228
|
+
setInvalid(false)
|
|
229
|
+
setPickerOpen(false)
|
|
230
|
+
}}
|
|
231
|
+
/>
|
|
232
|
+
</>
|
|
119
233
|
)
|
|
120
234
|
}
|
|
@@ -254,16 +254,17 @@ export function VulnDetail({
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
const handleExport = async (outputDir?: string) => {
|
|
257
|
+
const handleExport = async (outputDir?: string, format?: 'md' | 'docx' | 'pdf') => {
|
|
258
258
|
if (!detail || loading) return
|
|
259
259
|
const api = getApi()
|
|
260
260
|
if (!api) {
|
|
261
261
|
toast.error(apiUnavailableMessage(t))
|
|
262
|
+
setExportOpen(false)
|
|
262
263
|
return
|
|
263
264
|
}
|
|
264
265
|
setExporting(true)
|
|
265
266
|
try {
|
|
266
|
-
const result = await api.exportVulnerabilities([detail.id], outputDir)
|
|
267
|
+
const result = await api.exportVulnerabilities([detail.id], { outputDir, format })
|
|
267
268
|
if (!result.cancelled) {
|
|
268
269
|
const savedPath = result.savedPaths?.[0]
|
|
269
270
|
toast.success(
|
|
@@ -287,7 +288,18 @@ export function VulnDetail({
|
|
|
287
288
|
if (!next) onClose()
|
|
288
289
|
}}
|
|
289
290
|
>
|
|
290
|
-
<SheetContent
|
|
291
|
+
<SheetContent
|
|
292
|
+
data-testid="vuln-sheet"
|
|
293
|
+
onInteractOutside={(event) => {
|
|
294
|
+
// Nested dialogs (export/build path, delete confirm) are portaled
|
|
295
|
+
// outside this sheet in the DOM; without this the sheet treats a
|
|
296
|
+
// click inside them as an outside click and closes itself.
|
|
297
|
+
if (exportOpen || confirmOpen) event.preventDefault()
|
|
298
|
+
}}
|
|
299
|
+
onEscapeKeyDown={(event) => {
|
|
300
|
+
if (exportOpen || confirmOpen) event.preventDefault()
|
|
301
|
+
}}
|
|
302
|
+
>
|
|
291
303
|
<div className="flex h-12 shrink-0 items-center justify-between gap-3 border-b border-border px-6">
|
|
292
304
|
<span className="font-mono text-[10px] uppercase tracking-[0.16em] text-muted-foreground">
|
|
293
305
|
{t('vuln.detail.eyebrow')}
|
|
@@ -629,7 +641,7 @@ export function VulnDetail({
|
|
|
629
641
|
count={1}
|
|
630
642
|
pending={exporting}
|
|
631
643
|
onOpenChange={setExportOpen}
|
|
632
|
-
onConfirm={(outputDir) => void handleExport(outputDir)}
|
|
644
|
+
onConfirm={(outputDir, format) => void handleExport(outputDir, format)}
|
|
633
645
|
/>
|
|
634
646
|
</>
|
|
635
647
|
)
|
|
@@ -21,6 +21,7 @@ export const commonMessages: MessageBundle = {
|
|
|
21
21
|
'nav.snapshots': '认知快照',
|
|
22
22
|
'nav.mcp': 'MCP 接入',
|
|
23
23
|
'nav.retest': '漏洞复测',
|
|
24
|
+
'nav.fuzzer': 'Web Fuzzer',
|
|
24
25
|
'sidebar.databaseConnected': '数据库已连接',
|
|
25
26
|
'sidebar.databaseButton': '数据库',
|
|
26
27
|
'mcp.connect.button': '接入 Agent',
|
|
@@ -109,6 +110,7 @@ export const commonMessages: MessageBundle = {
|
|
|
109
110
|
'nav.snapshots': 'Snapshots',
|
|
110
111
|
'nav.mcp': 'MCP Access',
|
|
111
112
|
'nav.retest': 'Vulnerability Retest',
|
|
113
|
+
'nav.fuzzer': 'Web Fuzzer',
|
|
112
114
|
'sidebar.databaseConnected': 'Database connected',
|
|
113
115
|
'sidebar.databaseButton': 'Database',
|
|
114
116
|
'mcp.connect.button': 'Connect agents',
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MessageBundle } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Web Fuzzer page chrome (manual Yakit-style sender).
|
|
5
|
+
*
|
|
6
|
+
* The request/response controls reuse the existing `retest.repeater.*` keys, so
|
|
7
|
+
* only the page-level target selector needs its own bundle.
|
|
8
|
+
*/
|
|
9
|
+
export const fuzzerMessages: MessageBundle = {
|
|
10
|
+
zh: {
|
|
11
|
+
'fuzzer.page.description':
|
|
12
|
+
'Yakit 风格的手动发包页面:左侧编辑请求、右侧查看响应;请求仍受目标漏洞 origin 白名单约束。',
|
|
13
|
+
'fuzzer.target.title': '目标漏洞',
|
|
14
|
+
'fuzzer.target.select': '选择目标漏洞',
|
|
15
|
+
'fuzzer.target.empty': '没有可用的 Web 漏洞',
|
|
16
|
+
'fuzzer.target.loadFailed': '无法加载 Web 漏洞:{error}',
|
|
17
|
+
'fuzzer.target.detailFailed': '无法加载漏洞详情',
|
|
18
|
+
'fuzzer.request.paneAria': '请求编辑与发送',
|
|
19
|
+
'fuzzer.response.aria': '响应查看面板'
|
|
20
|
+
},
|
|
21
|
+
en: {
|
|
22
|
+
'fuzzer.page.description':
|
|
23
|
+
'Yakit-style manual HTTP sender: edit the request on the left and inspect the response on the right. Every request is still constrained by the target finding origin allowlist.',
|
|
24
|
+
'fuzzer.target.title': 'Target finding',
|
|
25
|
+
'fuzzer.target.select': 'Select target finding',
|
|
26
|
+
'fuzzer.target.empty': 'No Web findings available',
|
|
27
|
+
'fuzzer.target.loadFailed': 'Cannot load Web findings: {error}',
|
|
28
|
+
'fuzzer.target.detailFailed': 'Cannot load the finding details',
|
|
29
|
+
'fuzzer.request.paneAria': 'Request editor and send controls',
|
|
30
|
+
'fuzzer.response.aria': 'Response viewer'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -2,8 +2,10 @@ import type { MessageBundle } from './types'
|
|
|
2
2
|
import { assetMessages } from './asset'
|
|
3
3
|
import { commonMessages } from './common'
|
|
4
4
|
import { dashboardMessages } from './dashboard'
|
|
5
|
+
import { fuzzerMessages } from './fuzzer'
|
|
5
6
|
import { internalMessages } from './internal'
|
|
6
7
|
import { mcpMessages } from './mcp'
|
|
8
|
+
import { miniappMessages } from './miniapp'
|
|
7
9
|
import { nebulaMessages } from './nebula'
|
|
8
10
|
import { retestMessages } from './retest'
|
|
9
11
|
import { snapshotMessages } from './snapshot'
|
|
@@ -22,7 +24,9 @@ const bundles: MessageBundle[] = [
|
|
|
22
24
|
snapshotMessages,
|
|
23
25
|
nebulaMessages,
|
|
24
26
|
mcpMessages,
|
|
25
|
-
retestMessages
|
|
27
|
+
retestMessages,
|
|
28
|
+
miniappMessages,
|
|
29
|
+
fuzzerMessages
|
|
26
30
|
]
|
|
27
31
|
|
|
28
32
|
function mergeBundles(lang: 'zh' | 'en'): Record<string, string> {
|