@lievclouds/dsh-cortex 0.5.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 +434 -412
- package/index.js +7 -0
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +115 -110
- 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/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 +291 -137
- package/src/cortex-ui/renderer/components/retest/YakitSendPanel.tsx +411 -0
- 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 +144 -2
- 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 -17
- package/src/cortex-ui/renderer/pages/FuzzerPage.tsx +212 -0
- package/src/cortex-ui/renderer/pages/MiniappPage.tsx +664 -0
- package/src/host/cortex/index.js +42 -1
- package/src/host/cortex/repeater-store.js +411 -0
- package/src/host/cortex/repeater.js +15 -2
- package/src/host/cortex/tools.js +2 -0
- 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
|
@@ -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
|
+
}
|