@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.

Files changed (58) hide show
  1. package/README.md +1 -1
  2. package/client.js +428 -406
  3. package/index.js +7 -0
  4. package/locale/en.json +6 -6
  5. package/locale/zh.json +6 -6
  6. package/package.json +115 -106
  7. package/src/cortex-ui/dsh/api.ts +5 -2
  8. package/src/cortex-ui/dsh/css-overrides.css +8 -0
  9. package/src/cortex-ui/renderer/App.tsx +39 -0
  10. package/src/cortex-ui/renderer/components/asset/AssetDetail.tsx +9 -1
  11. package/src/cortex-ui/renderer/components/miniapp/FileTree.tsx +161 -0
  12. package/src/cortex-ui/renderer/components/miniapp/MiniappCard.tsx +263 -0
  13. package/src/cortex-ui/renderer/components/miniapp/MiniappDetail.tsx +1493 -0
  14. package/src/cortex-ui/renderer/components/retest/CurlImportDialog.tsx +158 -0
  15. package/src/cortex-ui/renderer/components/retest/RepeaterCollectionsDialog.tsx +327 -0
  16. package/src/cortex-ui/renderer/components/retest/RepeaterHistoryDialog.tsx +242 -0
  17. package/src/cortex-ui/renderer/components/retest/RepeaterPanel.tsx +333 -148
  18. package/src/cortex-ui/renderer/components/retest/YakitSendPanel.tsx +411 -0
  19. package/src/cortex-ui/renderer/components/vuln/DirectoryPickerDialog.tsx +187 -0
  20. package/src/cortex-ui/renderer/components/vuln/ExportReportDialog.tsx +173 -59
  21. package/src/cortex-ui/renderer/components/vuln/VulnDetail.tsx +16 -4
  22. package/src/cortex-ui/renderer/i18n/messages/common.ts +2 -0
  23. package/src/cortex-ui/renderer/i18n/messages/fuzzer.ts +32 -0
  24. package/src/cortex-ui/renderer/i18n/messages/index.ts +5 -1
  25. package/src/cortex-ui/renderer/i18n/messages/miniapp.ts +392 -0
  26. package/src/cortex-ui/renderer/i18n/messages/retest.ts +146 -2
  27. package/src/cortex-ui/renderer/i18n/messages/vuln.ts +26 -6
  28. package/src/cortex-ui/renderer/lib/curl.ts +635 -0
  29. package/src/cortex-ui/renderer/lib/miniappApi.ts +459 -0
  30. package/src/cortex-ui/renderer/lib/navigation.ts +13 -1
  31. package/src/cortex-ui/renderer/lib/repeaterRaw.ts +102 -0
  32. package/src/cortex-ui/renderer/lib/repeaterStoreApi.ts +163 -0
  33. package/src/cortex-ui/renderer/lib/retestResponse.ts +64 -0
  34. package/src/cortex-ui/renderer/lib/transport/httpClient.ts +1 -1
  35. package/src/cortex-ui/renderer/pages/DashboardPage.tsx +3 -3
  36. package/src/cortex-ui/renderer/pages/FuzzerPage.tsx +212 -0
  37. package/src/cortex-ui/renderer/pages/MiniappPage.tsx +664 -0
  38. package/src/cortex-ui/shared/ipc.ts +3 -4
  39. package/src/cortex-ui/shared/types.ts +9 -0
  40. package/src/host/cortex/fs.js +72 -0
  41. package/src/host/cortex/index.js +49 -1
  42. package/src/host/cortex/repeater-store.js +411 -0
  43. package/src/host/cortex/repeater.js +15 -2
  44. package/src/host/cortex/settings.js +54 -0
  45. package/src/host/cortex/tools.js +2 -0
  46. package/src/host/cortex/vulns.js +36 -13
  47. package/src/host/miniapp/analyze.js +1078 -0
  48. package/src/host/miniapp/assets-import.js +294 -0
  49. package/src/host/miniapp/detect.js +426 -0
  50. package/src/host/miniapp/format.js +263 -0
  51. package/src/host/miniapp/roots.js +360 -0
  52. package/src/host/miniapp/routes.js +304 -0
  53. package/src/host/miniapp/service.js +559 -0
  54. package/src/host/miniapp/store.js +756 -0
  55. package/src/host/miniapp/tools.js +335 -0
  56. package/src/host/miniapp/unpack.js +1010 -0
  57. package/src/host/miniapp/wxapkg.js +416 -0
  58. package/src/host/reports-office.js +341 -0
@@ -0,0 +1,242 @@
1
+ import * as DialogPrimitive from '@radix-ui/react-dialog'
2
+ import { Clock, Loader2, RefreshCw, Trash2, X } from 'lucide-react'
3
+ import { useCallback, useEffect, useState } from 'react'
4
+ import { toast } from 'sonner'
5
+ import { Button } from '@renderer/components/ui/button'
6
+ import {
7
+ clearRepeaterHistory,
8
+ listRepeaterHistory,
9
+ type RepeaterHistoryEntry
10
+ } from '@renderer/lib/repeaterStoreApi'
11
+ import { statusTone } from '@renderer/lib/retestResponse'
12
+ import { cn } from '@renderer/lib/utils'
13
+ import { useI18n } from '@renderer/i18n'
14
+ import { getPortalContainer } from '../../../dsh/portal'
15
+
16
+ interface RepeaterHistoryDialogProps {
17
+ open: boolean
18
+ onOpenChange: (open: boolean) => void
19
+ /** Restore both the raw request and the captured response into the editor. */
20
+ onRestore: (item: RepeaterHistoryEntry) => void
21
+ }
22
+
23
+ function describeError(cause: unknown): string {
24
+ return cause instanceof Error ? cause.message : String(cause)
25
+ }
26
+
27
+ function formatTime(value: string): string {
28
+ const date = new Date(value)
29
+ if (Number.isNaN(date.getTime())) return value
30
+ return date.toLocaleString()
31
+ }
32
+
33
+ /**
34
+ * Recent single-Repeater sends (host keeps the newest 50; this asks for 20).
35
+ * Entries are newest first and clicking one restores request + response.
36
+ */
37
+ export function RepeaterHistoryDialog({
38
+ open,
39
+ onOpenChange,
40
+ onRestore
41
+ }: RepeaterHistoryDialogProps) {
42
+ const { t } = useI18n()
43
+ const [items, setItems] = useState<RepeaterHistoryEntry[]>([])
44
+ const [loading, setLoading] = useState(false)
45
+ const [error, setError] = useState<string | null>(null)
46
+ const [clearing, setClearing] = useState(false)
47
+ const [loaded, setLoaded] = useState(false)
48
+
49
+ const load = useCallback(async (): Promise<void> => {
50
+ setLoading(true)
51
+ setError(null)
52
+ try {
53
+ const next = await listRepeaterHistory(20)
54
+ setItems(Array.isArray(next) ? next : [])
55
+ } catch (cause) {
56
+ setError(describeError(cause))
57
+ } finally {
58
+ setLoading(false)
59
+ setLoaded(true)
60
+ }
61
+ }, [])
62
+
63
+ useEffect(() => {
64
+ if (!open) return
65
+ void load()
66
+ }, [open, load])
67
+
68
+ const clearHistory = async (): Promise<void> => {
69
+ if (clearing) return
70
+ setClearing(true)
71
+ try {
72
+ await clearRepeaterHistory()
73
+ setItems([])
74
+ toast.success(t('retest.repeater.history.cleared'))
75
+ } catch (cause) {
76
+ toast.error(
77
+ t('retest.repeater.history.clearFailed', { error: describeError(cause) })
78
+ )
79
+ } finally {
80
+ setClearing(false)
81
+ }
82
+ }
83
+
84
+ const restore = (item: RepeaterHistoryEntry): void => {
85
+ onRestore(item)
86
+ onOpenChange(false)
87
+ }
88
+
89
+ return (
90
+ <DialogPrimitive.Root open={open} onOpenChange={onOpenChange} modal={false}>
91
+ <DialogPrimitive.Portal container={getPortalContainer()}>
92
+ <DialogPrimitive.Overlay className="sheet-overlay fixed inset-0 z-[70] bg-[hsl(224_18%_10%/0.3)]" />
93
+ <DialogPrimitive.Content
94
+ data-testid="repeater-history-dialog"
95
+ className="fixed left-1/2 top-1/2 z-[70] flex max-h-[min(80vh,620px)] w-[min(600px,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl border border-border bg-background shadow-panel focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
96
+ >
97
+ <div className="flex items-center gap-2 border-b border-border px-4 py-3">
98
+ <Clock aria-hidden="true" className="h-3.5 w-3.5 text-muted-foreground" />
99
+ <DialogPrimitive.Title className="text-[14px] font-medium tracking-tight text-foreground">
100
+ {t('retest.repeater.history.title')}
101
+ </DialogPrimitive.Title>
102
+ {loaded && error === null ? (
103
+ <span className="rounded border border-border bg-sidebar px-1.5 py-0.5 font-mono text-[9.5px] tabular-nums text-muted-foreground">
104
+ {items.length}
105
+ </span>
106
+ ) : null}
107
+ <div className="ml-auto flex items-center gap-1">
108
+ <Button
109
+ variant="ghost"
110
+ size="icon-sm"
111
+ aria-label={t('retest.repeater.history.refresh')}
112
+ disabled={loading}
113
+ onClick={() => void load()}
114
+ >
115
+ <RefreshCw
116
+ aria-hidden="true"
117
+ className={cn('h-3.5 w-3.5', loading && 'animate-spin')}
118
+ />
119
+ </Button>
120
+ <Button
121
+ variant="ghost"
122
+ size="icon-sm"
123
+ aria-label={t('common.close')}
124
+ onClick={() => onOpenChange(false)}
125
+ >
126
+ <X aria-hidden="true" className="h-3.5 w-3.5" />
127
+ </Button>
128
+ </div>
129
+ </div>
130
+ <DialogPrimitive.Description className="sr-only">
131
+ {t('retest.repeater.history.description')}
132
+ </DialogPrimitive.Description>
133
+
134
+ <div className="min-h-0 flex-1 overflow-y-auto p-2">
135
+ {loading && items.length === 0 ? (
136
+ <p className="flex h-[200px] items-center justify-center gap-2 text-[11.5px] text-muted-foreground">
137
+ <Loader2 aria-hidden="true" className="h-3.5 w-3.5 animate-spin" />
138
+ {t('retest.repeater.history.loading')}
139
+ </p>
140
+ ) : error !== null ? (
141
+ <div className="flex h-[200px] flex-col items-center justify-center gap-2 px-6 text-center">
142
+ <p role="alert" className="text-[11px] leading-relaxed text-red-700">
143
+ {t('retest.repeater.history.error', { error })}
144
+ </p>
145
+ <Button variant="outline" size="xs" onClick={() => void load()}>
146
+ {t('common.retry')}
147
+ </Button>
148
+ </div>
149
+ ) : items.length === 0 ? (
150
+ <div className="flex h-[200px] flex-col items-center justify-center gap-1.5 text-center">
151
+ <Clock aria-hidden="true" className="h-5 w-5 text-muted-foreground/60" />
152
+ <p className="text-[11.5px] text-muted-foreground">
153
+ {t('retest.repeater.history.empty')}
154
+ </p>
155
+ <p className="text-[10px] text-muted-foreground/80">
156
+ {t('retest.repeater.history.emptyHint')}
157
+ </p>
158
+ </div>
159
+ ) : (
160
+ <ul className="flex flex-col gap-0.5">
161
+ {items.map((item) => {
162
+ const status = item.response === null ? null : item.response.status
163
+ const time = formatTime(item.createdAt)
164
+ return (
165
+ <li key={item.id}>
166
+ <button
167
+ type="button"
168
+ data-testid="repeater-history-item"
169
+ onClick={() => restore(item)}
170
+ aria-label={t('retest.repeater.history.itemAria', {
171
+ method: item.request.method,
172
+ url: item.request.url,
173
+ status: status === null ? t('retest.repeater.history.noResponse') : status,
174
+ time
175
+ })}
176
+ title={item.request.url}
177
+ className="flex w-full flex-col gap-1 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors hover:border-border hover:bg-sidebar focus-visible:border-accent/50 focus-visible:bg-accent-soft/40 focus-visible:outline-none"
178
+ >
179
+ <span className="flex min-w-0 items-center gap-1.5">
180
+ <span className="shrink-0 rounded border border-border bg-background px-1.5 py-0.5 font-mono text-[9.5px] font-medium text-foreground">
181
+ {item.request.method}
182
+ </span>
183
+ {status === null ? (
184
+ <span className="shrink-0 text-[9.5px] text-muted-foreground">
185
+ {t('retest.repeater.history.noResponse')}
186
+ </span>
187
+ ) : (
188
+ <span
189
+ className={cn(
190
+ 'shrink-0 rounded border px-1.5 py-0.5 font-mono text-[9.5px] tabular-nums',
191
+ statusTone(status)
192
+ )}
193
+ >
194
+ {status}
195
+ </span>
196
+ )}
197
+ <span className="truncate font-mono text-[10.5px] text-foreground">
198
+ {item.request.url}
199
+ </span>
200
+ <span className="ml-auto shrink-0 font-mono text-[9.5px] tabular-nums text-muted-foreground">
201
+ {time}
202
+ </span>
203
+ </span>
204
+ <span className="flex items-center gap-2 text-[9.5px] text-muted-foreground">
205
+ <span className="truncate font-mono">{item.vulnId}</span>
206
+ <span className="ml-auto shrink-0">
207
+ {t('retest.repeater.history.restore')}
208
+ </span>
209
+ </span>
210
+ </button>
211
+ </li>
212
+ )
213
+ })}
214
+ </ul>
215
+ )}
216
+ </div>
217
+
218
+ <div className="flex items-center justify-between gap-2 border-t border-border px-4 py-2.5">
219
+ <span className="text-[9.5px] text-muted-foreground">
220
+ {t('retest.repeater.history.footerHint')}
221
+ </span>
222
+ <Button
223
+ variant="outline"
224
+ size="xs"
225
+ data-testid="repeater-history-clear"
226
+ disabled={items.length === 0 || loading || clearing}
227
+ onClick={() => void clearHistory()}
228
+ className="shrink-0 gap-1 text-muted-foreground"
229
+ >
230
+ {clearing ? (
231
+ <Loader2 aria-hidden="true" className="h-3 w-3 animate-spin" />
232
+ ) : (
233
+ <Trash2 aria-hidden="true" className="h-3 w-3" />
234
+ )}
235
+ {t('retest.repeater.history.clear')}
236
+ </Button>
237
+ </div>
238
+ </DialogPrimitive.Content>
239
+ </DialogPrimitive.Portal>
240
+ </DialogPrimitive.Root>
241
+ )
242
+ }