@theihtisham/devtools-with-cloud 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.
- package/.env.example +15 -0
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/docker-compose.yml +23 -0
- package/jest.config.js +7 -0
- package/next-env.d.ts +5 -0
- package/next.config.mjs +22 -0
- package/package.json +82 -0
- package/postcss.config.js +6 -0
- package/prisma/schema.prisma +105 -0
- package/prisma/seed.ts +211 -0
- package/src/app/(app)/ai/page.tsx +122 -0
- package/src/app/(app)/collections/page.tsx +155 -0
- package/src/app/(app)/environments/page.tsx +96 -0
- package/src/app/(app)/history/page.tsx +107 -0
- package/src/app/(app)/import/page.tsx +102 -0
- package/src/app/(app)/layout.tsx +60 -0
- package/src/app/(app)/settings/page.tsx +79 -0
- package/src/app/(app)/workspace/page.tsx +284 -0
- package/src/app/api/ai/discover/route.ts +17 -0
- package/src/app/api/ai/explain/route.ts +29 -0
- package/src/app/api/ai/generate-tests/route.ts +37 -0
- package/src/app/api/ai/suggest/route.ts +29 -0
- package/src/app/api/collections/[id]/route.ts +66 -0
- package/src/app/api/collections/route.ts +48 -0
- package/src/app/api/environments/route.ts +40 -0
- package/src/app/api/export/openapi/route.ts +17 -0
- package/src/app/api/export/postman/route.ts +18 -0
- package/src/app/api/import/curl/route.ts +18 -0
- package/src/app/api/import/har/route.ts +20 -0
- package/src/app/api/import/openapi/route.ts +21 -0
- package/src/app/api/import/postman/route.ts +21 -0
- package/src/app/api/proxy/route.ts +35 -0
- package/src/app/api/requests/[id]/execute/route.ts +85 -0
- package/src/app/api/requests/[id]/history/route.ts +23 -0
- package/src/app/api/requests/[id]/route.ts +66 -0
- package/src/app/api/requests/route.ts +49 -0
- package/src/app/api/workspaces/route.ts +38 -0
- package/src/app/globals.css +99 -0
- package/src/app/layout.tsx +24 -0
- package/src/app/page.tsx +182 -0
- package/src/components/ai/ai-panel.tsx +65 -0
- package/src/components/ai/code-explainer.tsx +51 -0
- package/src/components/ai/endpoint-discovery.tsx +62 -0
- package/src/components/ai/test-generator.tsx +49 -0
- package/src/components/collections/collection-actions.tsx +36 -0
- package/src/components/collections/collection-tree.tsx +55 -0
- package/src/components/collections/folder-creator.tsx +54 -0
- package/src/components/landing/comparison.tsx +43 -0
- package/src/components/landing/cta.tsx +16 -0
- package/src/components/landing/features.tsx +24 -0
- package/src/components/landing/hero.tsx +23 -0
- package/src/components/response/body-viewer.tsx +33 -0
- package/src/components/response/headers-viewer.tsx +23 -0
- package/src/components/response/status-badge.tsx +25 -0
- package/src/components/response/test-results.tsx +50 -0
- package/src/components/response/timing-chart.tsx +39 -0
- package/src/components/ui/badge.tsx +24 -0
- package/src/components/ui/button.tsx +32 -0
- package/src/components/ui/code-editor.tsx +51 -0
- package/src/components/ui/dialog.tsx +56 -0
- package/src/components/ui/dropdown.tsx +63 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/key-value-editor.tsx +75 -0
- package/src/components/ui/select.tsx +24 -0
- package/src/components/ui/tabs.tsx +85 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/toast.tsx +54 -0
- package/src/components/workspace/request-panel.tsx +38 -0
- package/src/components/workspace/response-panel.tsx +81 -0
- package/src/components/workspace/sidebar.tsx +52 -0
- package/src/components/workspace/split-pane.tsx +49 -0
- package/src/components/workspace/tabs/auth-tab.tsx +94 -0
- package/src/components/workspace/tabs/body-tab.tsx +41 -0
- package/src/components/workspace/tabs/headers-tab.tsx +23 -0
- package/src/components/workspace/tabs/params-tab.tsx +23 -0
- package/src/components/workspace/tabs/pre-request-tab.tsx +26 -0
- package/src/components/workspace/url-bar.tsx +53 -0
- package/src/hooks/use-ai.ts +115 -0
- package/src/hooks/use-collection.ts +71 -0
- package/src/hooks/use-environment.ts +73 -0
- package/src/hooks/use-request.ts +111 -0
- package/src/lib/ai/endpoint-discovery.ts +158 -0
- package/src/lib/ai/explainer.ts +127 -0
- package/src/lib/ai/suggester.ts +164 -0
- package/src/lib/ai/test-generator.ts +161 -0
- package/src/lib/auth/api-key.ts +28 -0
- package/src/lib/auth/aws-sig.ts +131 -0
- package/src/lib/auth/basic.ts +17 -0
- package/src/lib/auth/bearer.ts +15 -0
- package/src/lib/auth/oauth2.ts +155 -0
- package/src/lib/auth/types.ts +16 -0
- package/src/lib/db/client.ts +15 -0
- package/src/lib/env/manager.ts +32 -0
- package/src/lib/env/resolver.ts +30 -0
- package/src/lib/exporters/openapi.ts +193 -0
- package/src/lib/exporters/postman.ts +140 -0
- package/src/lib/graphql/builder.ts +249 -0
- package/src/lib/graphql/formatter.ts +147 -0
- package/src/lib/graphql/index.ts +43 -0
- package/src/lib/graphql/introspection.ts +175 -0
- package/src/lib/graphql/types.ts +99 -0
- package/src/lib/graphql/validator.ts +216 -0
- package/src/lib/http/client.ts +112 -0
- package/src/lib/http/proxy.ts +83 -0
- package/src/lib/http/request-builder.ts +214 -0
- package/src/lib/http/response-parser.ts +106 -0
- package/src/lib/http/timing.ts +63 -0
- package/src/lib/importers/curl-parser.ts +346 -0
- package/src/lib/importers/har-parser.ts +128 -0
- package/src/lib/importers/openapi.ts +324 -0
- package/src/lib/importers/postman.ts +312 -0
- package/src/lib/test-runner/assertions.ts +163 -0
- package/src/lib/test-runner/reporter.ts +90 -0
- package/src/lib/test-runner/runner.ts +69 -0
- package/src/lib/utils/api-response.ts +85 -0
- package/src/lib/utils/cn.ts +6 -0
- package/src/lib/utils/content-type.ts +123 -0
- package/src/lib/utils/download.ts +53 -0
- package/src/lib/utils/errors.ts +92 -0
- package/src/lib/utils/format.ts +142 -0
- package/src/lib/utils/syntax-highlight.ts +108 -0
- package/src/lib/utils/validation.ts +231 -0
- package/src/lib/websocket/client.ts +182 -0
- package/src/lib/websocket/frames.ts +96 -0
- package/src/lib/websocket/history.ts +121 -0
- package/src/lib/websocket/index.ts +25 -0
- package/src/lib/websocket/types.ts +57 -0
- package/src/types/ai.ts +28 -0
- package/src/types/collection.ts +24 -0
- package/src/types/environment.ts +16 -0
- package/src/types/request.ts +54 -0
- package/src/types/response.ts +37 -0
- package/tailwind.config.ts +82 -0
- package/tests/lib/env/resolver.test.ts +108 -0
- package/tests/lib/graphql/builder.test.ts +349 -0
- package/tests/lib/graphql/formatter.test.ts +99 -0
- package/tests/lib/http/request-builder.test.ts +160 -0
- package/tests/lib/http/response-parser.test.ts +150 -0
- package/tests/lib/http/timing.test.ts +188 -0
- package/tests/lib/importers/curl-parser.test.ts +245 -0
- package/tests/lib/test-runner/assertions.test.ts +342 -0
- package/tests/lib/utils/cn.test.ts +46 -0
- package/tests/lib/utils/content-type.test.ts +175 -0
- package/tests/lib/utils/format.test.ts +188 -0
- package/tests/lib/utils/validation.test.ts +237 -0
- package/tests/lib/websocket/history.test.ts +186 -0
- package/tsconfig.json +29 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +21 -0
package/src/app/page.tsx
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
|
|
3
|
+
export default function LandingPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="min-h-screen bg-background">
|
|
6
|
+
{/* Header */}
|
|
7
|
+
<header className="border-b border-border">
|
|
8
|
+
<div className="container mx-auto flex items-center justify-between px-6 py-4">
|
|
9
|
+
<div className="flex items-center gap-2">
|
|
10
|
+
<div className="h-8 w-8 rounded-lg bg-primary flex items-center justify-center">
|
|
11
|
+
<span className="text-primary-foreground font-bold text-sm">AT</span>
|
|
12
|
+
</div>
|
|
13
|
+
<span className="text-xl font-bold text-foreground">APITester</span>
|
|
14
|
+
</div>
|
|
15
|
+
<nav className="flex items-center gap-6">
|
|
16
|
+
<a href="#features" className="text-muted-foreground hover:text-foreground transition-colors">Features</a>
|
|
17
|
+
<a href="#comparison" className="text-muted-foreground hover:text-foreground transition-colors">Compare</a>
|
|
18
|
+
<Link
|
|
19
|
+
href="/workspace"
|
|
20
|
+
className="bg-primary text-primary-foreground px-4 py-2 rounded-md hover:bg-primary/90 transition-colors"
|
|
21
|
+
>
|
|
22
|
+
Open App
|
|
23
|
+
</Link>
|
|
24
|
+
</nav>
|
|
25
|
+
</div>
|
|
26
|
+
</header>
|
|
27
|
+
|
|
28
|
+
{/* Hero */}
|
|
29
|
+
<section className="container mx-auto px-6 py-24 text-center">
|
|
30
|
+
<h1 className="text-5xl font-bold text-foreground mb-6">
|
|
31
|
+
API Testing,{' '}
|
|
32
|
+
<span className="text-primary">Reimagined</span>
|
|
33
|
+
</h1>
|
|
34
|
+
<p className="text-xl text-muted-foreground max-w-2xl mx-auto mb-8">
|
|
35
|
+
An open-source API testing tool with AI-powered test generation,
|
|
36
|
+
endpoint discovery, and a beautiful interface. Built for modern developers.
|
|
37
|
+
</p>
|
|
38
|
+
<div className="flex gap-4 justify-center">
|
|
39
|
+
<Link
|
|
40
|
+
href="/workspace"
|
|
41
|
+
className="bg-primary text-primary-foreground px-6 py-3 rounded-lg text-lg font-medium hover:bg-primary/90 transition-colors"
|
|
42
|
+
>
|
|
43
|
+
Get Started
|
|
44
|
+
</Link>
|
|
45
|
+
<a
|
|
46
|
+
href="https://github.com/api-tester/api-tester"
|
|
47
|
+
target="_blank"
|
|
48
|
+
rel="noopener noreferrer"
|
|
49
|
+
className="border border-border text-foreground px-6 py-3 rounded-lg text-lg font-medium hover:bg-accent transition-colors"
|
|
50
|
+
>
|
|
51
|
+
GitHub
|
|
52
|
+
</a>
|
|
53
|
+
</div>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
{/* Features */}
|
|
57
|
+
<section id="features" className="container mx-auto px-6 py-16">
|
|
58
|
+
<h2 className="text-3xl font-bold text-center text-foreground mb-12">
|
|
59
|
+
Features That Matter
|
|
60
|
+
</h2>
|
|
61
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
62
|
+
{[
|
|
63
|
+
{
|
|
64
|
+
title: 'AI Test Generation',
|
|
65
|
+
description: 'Automatically generate comprehensive test suites from your API responses. Save hours of manual test writing.',
|
|
66
|
+
icon: 'brain',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
title: 'Endpoint Discovery',
|
|
70
|
+
description: 'Scan your codebase and auto-generate API collections from Express, FastAPI, Next.js, and more.',
|
|
71
|
+
icon: 'search',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
title: 'Beautiful Interface',
|
|
75
|
+
description: 'A modern, responsive UI with syntax highlighting, collapsible JSON, and timing visualizations.',
|
|
76
|
+
icon: 'palette',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
title: 'Import Anything',
|
|
80
|
+
description: 'Import from OpenAPI, Postman, cURL, and HAR. Zero migration cost from your existing tools.',
|
|
81
|
+
icon: 'import',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
title: 'Environment Management',
|
|
85
|
+
description: 'Switch between development, staging, and production with variable interpolation and secrets masking.',
|
|
86
|
+
icon: 'env',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
title: 'Self-Hosted',
|
|
90
|
+
description: 'Run it locally or on your own infrastructure. Your data never leaves your network.',
|
|
91
|
+
icon: 'server',
|
|
92
|
+
},
|
|
93
|
+
].map((feature) => (
|
|
94
|
+
<div
|
|
95
|
+
key={feature.title}
|
|
96
|
+
className="border border-border rounded-lg p-6 hover:border-primary/50 transition-colors"
|
|
97
|
+
>
|
|
98
|
+
<div className="h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center mb-4">
|
|
99
|
+
<span className="text-primary text-lg">
|
|
100
|
+
{feature.icon === 'brain' && '{ }'}
|
|
101
|
+
{feature.icon === 'search' && '<>'}
|
|
102
|
+
{feature.icon === 'palette' && '#'}
|
|
103
|
+
{feature.icon === 'import' && '+'}
|
|
104
|
+
{feature.icon === 'env' && '$'}
|
|
105
|
+
{feature.icon === 'server' && '@'}
|
|
106
|
+
</span>
|
|
107
|
+
</div>
|
|
108
|
+
<h3 className="text-lg font-semibold text-foreground mb-2">{feature.title}</h3>
|
|
109
|
+
<p className="text-muted-foreground">{feature.description}</p>
|
|
110
|
+
</div>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
</section>
|
|
114
|
+
|
|
115
|
+
{/* Comparison */}
|
|
116
|
+
<section id="comparison" className="container mx-auto px-6 py-16">
|
|
117
|
+
<h2 className="text-3xl font-bold text-center text-foreground mb-12">
|
|
118
|
+
Why APITester?
|
|
119
|
+
</h2>
|
|
120
|
+
<div className="overflow-x-auto">
|
|
121
|
+
<table className="w-full border border-border rounded-lg">
|
|
122
|
+
<thead>
|
|
123
|
+
<tr className="border-b border-border bg-muted">
|
|
124
|
+
<th className="px-4 py-3 text-left text-foreground">Feature</th>
|
|
125
|
+
<th className="px-4 py-3 text-center text-primary font-bold">APITester</th>
|
|
126
|
+
<th className="px-4 py-3 text-center text-muted-foreground">Postman</th>
|
|
127
|
+
<th className="px-4 py-3 text-center text-muted-foreground">Insomnia</th>
|
|
128
|
+
</tr>
|
|
129
|
+
</thead>
|
|
130
|
+
<tbody>
|
|
131
|
+
{[
|
|
132
|
+
{ feature: 'Open Source', us: true, postman: false, insomnia: true },
|
|
133
|
+
{ feature: 'AI Test Generation', us: true, postman: false, insomnia: false },
|
|
134
|
+
{ feature: 'Self-Hosted', us: true, postman: false, insomnia: false },
|
|
135
|
+
{ feature: 'Unlimited Requests', us: true, postman: true, insomnia: true },
|
|
136
|
+
{ feature: 'Free Forever', us: true, postman: false, insomnia: true },
|
|
137
|
+
{ feature: 'Import/Export', us: true, postman: true, insomnia: true },
|
|
138
|
+
{ feature: 'Environment Variables', us: true, postman: true, insomnia: true },
|
|
139
|
+
].map((row) => (
|
|
140
|
+
<tr key={row.feature} className="border-b border-border">
|
|
141
|
+
<td className="px-4 py-3 text-foreground">{row.feature}</td>
|
|
142
|
+
<td className="px-4 py-3 text-center">
|
|
143
|
+
{row.us ? <span className="text-green-400">✓</span> : <span className="text-red-400">✗</span>}
|
|
144
|
+
</td>
|
|
145
|
+
<td className="px-4 py-3 text-center">
|
|
146
|
+
{row.postman ? <span className="text-green-400">✓</span> : <span className="text-red-400">✗</span>}
|
|
147
|
+
</td>
|
|
148
|
+
<td className="px-4 py-3 text-center">
|
|
149
|
+
{row.insomnia ? <span className="text-green-400">✓</span> : <span className="text-red-400">✗</span>}
|
|
150
|
+
</td>
|
|
151
|
+
</tr>
|
|
152
|
+
))}
|
|
153
|
+
</tbody>
|
|
154
|
+
</table>
|
|
155
|
+
</div>
|
|
156
|
+
</section>
|
|
157
|
+
|
|
158
|
+
{/* CTA */}
|
|
159
|
+
<section className="container mx-auto px-6 py-24 text-center">
|
|
160
|
+
<h2 className="text-3xl font-bold text-foreground mb-4">
|
|
161
|
+
Ready to Get Started?
|
|
162
|
+
</h2>
|
|
163
|
+
<p className="text-lg text-muted-foreground mb-8">
|
|
164
|
+
Start testing your APIs in seconds. No account required.
|
|
165
|
+
</p>
|
|
166
|
+
<Link
|
|
167
|
+
href="/workspace"
|
|
168
|
+
className="bg-primary text-primary-foreground px-8 py-3 rounded-lg text-lg font-medium hover:bg-primary/90 transition-colors inline-block"
|
|
169
|
+
>
|
|
170
|
+
Launch APITester
|
|
171
|
+
</Link>
|
|
172
|
+
</section>
|
|
173
|
+
|
|
174
|
+
{/* Footer */}
|
|
175
|
+
<footer className="border-t border-border py-8">
|
|
176
|
+
<div className="container mx-auto px-6 text-center text-muted-foreground">
|
|
177
|
+
<p>APITester - Open Source API Testing Tool</p>
|
|
178
|
+
</div>
|
|
179
|
+
</footer>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useAi } from '@/hooks/use-ai';
|
|
5
|
+
|
|
6
|
+
export function AiPanel() {
|
|
7
|
+
const [input, setInput] = useState('');
|
|
8
|
+
const [mode, setMode] = useState<'generate' | 'discover' | 'explain' | 'suggest'>('generate');
|
|
9
|
+
const { loading, result, error, generateTests, discoverEndpoints, explainResponse, suggestImprovements, clear } = useAi();
|
|
10
|
+
|
|
11
|
+
const handleSubmit = async () => {
|
|
12
|
+
if (!input.trim()) return;
|
|
13
|
+
switch (mode) {
|
|
14
|
+
case 'generate':
|
|
15
|
+
await generateTests(input);
|
|
16
|
+
break;
|
|
17
|
+
case 'discover':
|
|
18
|
+
await discoverEndpoints(input);
|
|
19
|
+
break;
|
|
20
|
+
case 'explain':
|
|
21
|
+
await explainResponse(input);
|
|
22
|
+
break;
|
|
23
|
+
case 'suggest':
|
|
24
|
+
await suggestImprovements(input);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex flex-col h-full">
|
|
31
|
+
<div className="flex gap-1 p-2 border-b border-border">
|
|
32
|
+
{(['generate', 'discover', 'explain', 'suggest'] as const).map((m) => (
|
|
33
|
+
<button
|
|
34
|
+
key={m}
|
|
35
|
+
onClick={() => { setMode(m); clear(); }}
|
|
36
|
+
className={`px-3 py-1 text-xs rounded ${mode === m ? 'bg-primary/10 text-primary' : 'text-muted-foreground hover:text-foreground'}`}
|
|
37
|
+
>
|
|
38
|
+
{m}
|
|
39
|
+
</button>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
<textarea
|
|
43
|
+
value={input}
|
|
44
|
+
onChange={(e) => setInput(e.target.value)}
|
|
45
|
+
className="flex-1 p-2 bg-transparent text-foreground text-sm font-mono resize-none outline-none"
|
|
46
|
+
placeholder={`Enter input for ${mode}...`}
|
|
47
|
+
/>
|
|
48
|
+
<div className="p-2 border-t border-border">
|
|
49
|
+
<button
|
|
50
|
+
onClick={handleSubmit}
|
|
51
|
+
disabled={loading}
|
|
52
|
+
className="w-full px-3 py-1.5 bg-primary text-primary-foreground rounded text-sm"
|
|
53
|
+
>
|
|
54
|
+
{loading ? 'Processing...' : 'Run'}
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
{error && <div className="p-2 text-destructive text-xs border-t border-border">{error}</div>}
|
|
58
|
+
{result && (
|
|
59
|
+
<div className="max-h-48 overflow-auto p-2 border-t border-border">
|
|
60
|
+
<pre className="text-xs font-mono text-foreground whitespace-pre-wrap">{result}</pre>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export function CodeExplainer() {
|
|
6
|
+
const [input, setInput] = useState('');
|
|
7
|
+
const [explanation, setExplanation] = useState('');
|
|
8
|
+
const [loading, setLoading] = useState(false);
|
|
9
|
+
|
|
10
|
+
const explain = async () => {
|
|
11
|
+
setLoading(true);
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch('/api/ai/explain', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16
|
+
body: JSON.stringify({ response: input }),
|
|
17
|
+
});
|
|
18
|
+
const data = await res.json();
|
|
19
|
+
if (data.success && data.data?.explanation) {
|
|
20
|
+
setExplanation(data.data.explanation);
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// Handle error silently
|
|
24
|
+
} finally {
|
|
25
|
+
setLoading(false);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<textarea
|
|
32
|
+
value={input}
|
|
33
|
+
onChange={(e) => setInput(e.target.value)}
|
|
34
|
+
placeholder="Paste a response to explain..."
|
|
35
|
+
className="w-full h-40 px-3 py-2 rounded border border-border bg-background text-foreground text-sm font-mono mb-3"
|
|
36
|
+
/>
|
|
37
|
+
<button
|
|
38
|
+
onClick={explain}
|
|
39
|
+
disabled={loading || !input.trim()}
|
|
40
|
+
className="px-4 py-2 bg-primary text-primary-foreground rounded text-sm mb-3"
|
|
41
|
+
>
|
|
42
|
+
{loading ? 'Analyzing...' : 'Explain'}
|
|
43
|
+
</button>
|
|
44
|
+
{explanation && (
|
|
45
|
+
<div className="p-3 rounded border border-border bg-muted/30">
|
|
46
|
+
<pre className="text-sm text-foreground whitespace-pre-wrap">{explanation}</pre>
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
interface DiscoveredEndpoint {
|
|
6
|
+
method: string;
|
|
7
|
+
path: string;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function EndpointDiscovery() {
|
|
12
|
+
const [code, setCode] = useState('');
|
|
13
|
+
const [endpoints, setEndpoints] = useState<DiscoveredEndpoint[]>([]);
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
|
|
16
|
+
const discover = async () => {
|
|
17
|
+
setLoading(true);
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch('/api/ai/discover', {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify({ code, framework: 'auto', workspaceId: 'default' }),
|
|
23
|
+
});
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
if (data.success && data.data?.endpoints) {
|
|
26
|
+
setEndpoints(data.data.endpoints);
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// Handle error silently
|
|
30
|
+
} finally {
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<textarea
|
|
38
|
+
value={code}
|
|
39
|
+
onChange={(e) => setCode(e.target.value)}
|
|
40
|
+
placeholder="Paste source code to discover endpoints..."
|
|
41
|
+
className="w-full h-40 px-3 py-2 rounded border border-border bg-background text-foreground text-sm font-mono mb-3"
|
|
42
|
+
/>
|
|
43
|
+
<button
|
|
44
|
+
onClick={discover}
|
|
45
|
+
disabled={loading || !code.trim()}
|
|
46
|
+
className="px-4 py-2 bg-primary text-primary-foreground rounded text-sm mb-3"
|
|
47
|
+
>
|
|
48
|
+
{loading ? 'Scanning...' : 'Discover'}
|
|
49
|
+
</button>
|
|
50
|
+
{endpoints.length > 0 && (
|
|
51
|
+
<div className="space-y-2">
|
|
52
|
+
{endpoints.map((ep, i) => (
|
|
53
|
+
<div key={i} className="flex items-center gap-2 p-2 border border-border rounded text-sm">
|
|
54
|
+
<span className="font-mono text-method-get">{ep.method}</span>
|
|
55
|
+
<span className="font-mono text-foreground">{ep.path}</span>
|
|
56
|
+
</div>
|
|
57
|
+
))}
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { CodeEditor } from '@/components/ui/code-editor';
|
|
5
|
+
|
|
6
|
+
interface TestGeneratorProps {
|
|
7
|
+
response: string;
|
|
8
|
+
onGenerated: (tests: string) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TestGenerator({ response, onGenerated }: TestGeneratorProps) {
|
|
12
|
+
const [loading, setLoading] = useState(false);
|
|
13
|
+
const [tests, setTests] = useState('');
|
|
14
|
+
|
|
15
|
+
const generate = async () => {
|
|
16
|
+
setLoading(true);
|
|
17
|
+
try {
|
|
18
|
+
const res = await fetch('/api/ai/generate-tests', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify({ requestId: 'manual', response }),
|
|
22
|
+
});
|
|
23
|
+
const data = await res.json();
|
|
24
|
+
if (data.success && data.data?.tests) {
|
|
25
|
+
setTests(data.data.tests);
|
|
26
|
+
onGenerated(data.data.tests);
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// Handle error silently
|
|
30
|
+
} finally {
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<button
|
|
38
|
+
onClick={generate}
|
|
39
|
+
disabled={loading || !response}
|
|
40
|
+
className="px-4 py-2 bg-primary text-primary-foreground rounded text-sm mb-3"
|
|
41
|
+
>
|
|
42
|
+
{loading ? 'Generating...' : 'Generate Tests'}
|
|
43
|
+
</button>
|
|
44
|
+
{tests && (
|
|
45
|
+
<CodeEditor value={tests} onChange={setTests} language="javascript" />
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
interface CollectionActionsProps {
|
|
4
|
+
collectionId: string;
|
|
5
|
+
onExport?: (format: string) => void;
|
|
6
|
+
onRunAll?: () => void;
|
|
7
|
+
onDelete?: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function CollectionActions({ collectionId, onExport, onRunAll, onDelete }: CollectionActionsProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex items-center gap-2">
|
|
13
|
+
<button
|
|
14
|
+
onClick={onRunAll}
|
|
15
|
+
className="px-3 py-1 text-xs bg-primary text-primary-foreground rounded hover:bg-primary/90 transition-colors"
|
|
16
|
+
>
|
|
17
|
+
Run All
|
|
18
|
+
</button>
|
|
19
|
+
<select
|
|
20
|
+
onChange={(e) => e.target.value && onExport?.(e.target.value)}
|
|
21
|
+
className="px-2 py-1 text-xs border border-border rounded bg-background text-foreground"
|
|
22
|
+
defaultValue=""
|
|
23
|
+
>
|
|
24
|
+
<option value="" disabled>Export</option>
|
|
25
|
+
<option value="openapi">OpenAPI 3.1</option>
|
|
26
|
+
<option value="postman">Postman</option>
|
|
27
|
+
</select>
|
|
28
|
+
<button
|
|
29
|
+
onClick={onDelete}
|
|
30
|
+
className="px-3 py-1 text-xs text-destructive hover:text-destructive/80 transition-colors"
|
|
31
|
+
>
|
|
32
|
+
Delete
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils/cn';
|
|
4
|
+
|
|
5
|
+
interface CollectionTreeItem {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
method?: string;
|
|
9
|
+
children?: CollectionTreeItem[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface CollectionTreeProps {
|
|
13
|
+
items: CollectionTreeItem[];
|
|
14
|
+
selectedId?: string;
|
|
15
|
+
onSelect: (id: string) => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function CollectionTree({ items, selectedId, onSelect, className }: CollectionTreeProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className={cn('space-y-0.5', className)}>
|
|
22
|
+
{items.map((item) => (
|
|
23
|
+
<div key={item.id}>
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => onSelect(item.id)}
|
|
26
|
+
className={cn(
|
|
27
|
+
'w-full flex items-center gap-2 px-2 py-1.5 rounded text-sm text-left transition-colors',
|
|
28
|
+
selectedId === item.id ? 'bg-primary/10 text-primary' : 'text-foreground hover:bg-accent',
|
|
29
|
+
)}
|
|
30
|
+
>
|
|
31
|
+
{item.method && (
|
|
32
|
+
<span className={cn(
|
|
33
|
+
'text-xs font-mono font-medium',
|
|
34
|
+
item.method === 'GET' && 'text-method-get',
|
|
35
|
+
item.method === 'POST' && 'text-method-post',
|
|
36
|
+
item.method === 'PUT' && 'text-method-put',
|
|
37
|
+
item.method === 'DELETE' && 'text-method-delete',
|
|
38
|
+
item.method === 'PATCH' && 'text-method-patch',
|
|
39
|
+
)}>
|
|
40
|
+
{item.method}
|
|
41
|
+
</span>
|
|
42
|
+
)}
|
|
43
|
+
{!item.method && <span className="text-xs text-muted-foreground">#</span>}
|
|
44
|
+
<span className="truncate">{item.name}</span>
|
|
45
|
+
</button>
|
|
46
|
+
{item.children && item.children.length > 0 && (
|
|
47
|
+
<div className="ml-4">
|
|
48
|
+
<CollectionTree items={item.children} selectedId={selectedId} onSelect={onSelect} />
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
interface FolderCreatorProps {
|
|
6
|
+
parentId?: string;
|
|
7
|
+
onCreate: (name: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function FolderCreator({ parentId, onCreate }: FolderCreatorProps) {
|
|
11
|
+
const [name, setName] = useState('');
|
|
12
|
+
const [show, setShow] = useState(false);
|
|
13
|
+
|
|
14
|
+
if (!show) {
|
|
15
|
+
return (
|
|
16
|
+
<button
|
|
17
|
+
onClick={() => setShow(true)}
|
|
18
|
+
className="text-xs text-primary hover:text-primary/80"
|
|
19
|
+
>
|
|
20
|
+
+ New Folder
|
|
21
|
+
</button>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex items-center gap-2">
|
|
27
|
+
<input
|
|
28
|
+
type="text"
|
|
29
|
+
value={name}
|
|
30
|
+
onChange={(e) => setName(e.target.value)}
|
|
31
|
+
placeholder="Folder name"
|
|
32
|
+
className="flex-1 px-2 py-1 text-sm border border-border rounded bg-background text-foreground"
|
|
33
|
+
autoFocus
|
|
34
|
+
onKeyDown={(e) => {
|
|
35
|
+
if (e.key === 'Enter' && name.trim()) {
|
|
36
|
+
onCreate(name.trim());
|
|
37
|
+
setName('');
|
|
38
|
+
setShow(false);
|
|
39
|
+
}
|
|
40
|
+
if (e.key === 'Escape') {
|
|
41
|
+
setShow(false);
|
|
42
|
+
setName('');
|
|
43
|
+
}
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
<button
|
|
47
|
+
onClick={() => { setShow(false); setName(''); }}
|
|
48
|
+
className="text-xs text-muted-foreground"
|
|
49
|
+
>
|
|
50
|
+
Cancel
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function Comparison() {
|
|
2
|
+
const rows = [
|
|
3
|
+
{ feature: 'Open Source', us: true, postman: false, insomnia: true },
|
|
4
|
+
{ feature: 'AI Test Generation', us: true, postman: false, insomnia: false },
|
|
5
|
+
{ feature: 'Self-Hosted', us: true, postman: false, insomnia: false },
|
|
6
|
+
{ feature: 'Free Forever', us: true, postman: false, insomnia: true },
|
|
7
|
+
{ feature: 'Unlimited Requests', us: true, postman: true, insomnia: true },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<section className="container mx-auto px-6 py-16">
|
|
12
|
+
<h2 className="text-3xl font-bold text-center text-foreground mb-12">Why APITester?</h2>
|
|
13
|
+
<div className="overflow-x-auto">
|
|
14
|
+
<table className="w-full border border-border rounded-lg">
|
|
15
|
+
<thead>
|
|
16
|
+
<tr className="border-b border-border bg-muted">
|
|
17
|
+
<th className="px-4 py-3 text-left text-foreground">Feature</th>
|
|
18
|
+
<th className="px-4 py-3 text-center text-primary font-bold">APITester</th>
|
|
19
|
+
<th className="px-4 py-3 text-center text-muted-foreground">Postman</th>
|
|
20
|
+
<th className="px-4 py-3 text-center text-muted-foreground">Insomnia</th>
|
|
21
|
+
</tr>
|
|
22
|
+
</thead>
|
|
23
|
+
<tbody>
|
|
24
|
+
{rows.map((row) => (
|
|
25
|
+
<tr key={row.feature} className="border-b border-border">
|
|
26
|
+
<td className="px-4 py-3 text-foreground">{row.feature}</td>
|
|
27
|
+
<td className="px-4 py-3 text-center">
|
|
28
|
+
{row.us ? <span className="text-green-400">✓</span> : <span className="text-red-400">✗</span>}
|
|
29
|
+
</td>
|
|
30
|
+
<td className="px-4 py-3 text-center">
|
|
31
|
+
{row.postman ? <span className="text-green-400">✓</span> : <span className="text-red-400">✗</span>}
|
|
32
|
+
</td>
|
|
33
|
+
<td className="px-4 py-3 text-center">
|
|
34
|
+
{row.insomnia ? <span className="text-green-400">✓</span> : <span className="text-red-400">✗</span>}
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
))}
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
41
|
+
</section>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
|
|
3
|
+
export function CTA() {
|
|
4
|
+
return (
|
|
5
|
+
<section className="container mx-auto px-6 py-24 text-center">
|
|
6
|
+
<h2 className="text-3xl font-bold text-foreground mb-4">Ready to Get Started?</h2>
|
|
7
|
+
<p className="text-lg text-muted-foreground mb-8">Start testing your APIs in seconds.</p>
|
|
8
|
+
<Link
|
|
9
|
+
href="/workspace"
|
|
10
|
+
className="bg-primary text-primary-foreground px-8 py-3 rounded-lg text-lg font-medium hover:bg-primary/90 transition-colors inline-block"
|
|
11
|
+
>
|
|
12
|
+
Launch APITester
|
|
13
|
+
</Link>
|
|
14
|
+
</section>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const features = [
|
|
2
|
+
{ title: 'AI Test Generation', description: 'Automatically generate test suites from API responses.' },
|
|
3
|
+
{ title: 'Endpoint Discovery', description: 'Scan codebases to discover API endpoints automatically.' },
|
|
4
|
+
{ title: 'Beautiful Interface', description: 'Modern, responsive UI with syntax highlighting.' },
|
|
5
|
+
{ title: 'Import Anything', description: 'Import from OpenAPI, Postman, cURL, and HAR.' },
|
|
6
|
+
{ title: 'Environment Management', description: 'Switch between dev, staging, and production.' },
|
|
7
|
+
{ title: 'Self-Hosted', description: 'Run locally. Your data never leaves your network.' },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export function Features() {
|
|
11
|
+
return (
|
|
12
|
+
<section className="container mx-auto px-6 py-16">
|
|
13
|
+
<h2 className="text-3xl font-bold text-center text-foreground mb-12">Features</h2>
|
|
14
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
15
|
+
{features.map((f) => (
|
|
16
|
+
<div key={f.title} className="border border-border rounded-lg p-6 hover:border-primary/50 transition-colors">
|
|
17
|
+
<h3 className="text-lg font-semibold text-foreground mb-2">{f.title}</h3>
|
|
18
|
+
<p className="text-muted-foreground">{f.description}</p>
|
|
19
|
+
</div>
|
|
20
|
+
))}
|
|
21
|
+
</div>
|
|
22
|
+
</section>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
|
|
3
|
+
export function Hero() {
|
|
4
|
+
return (
|
|
5
|
+
<section className="container mx-auto px-6 py-24 text-center">
|
|
6
|
+
<h1 className="text-5xl font-bold text-foreground mb-6">
|
|
7
|
+
API Testing, <span className="text-primary">Reimagined</span>
|
|
8
|
+
</h1>
|
|
9
|
+
<p className="text-xl text-muted-foreground max-w-2xl mx-auto mb-8">
|
|
10
|
+
An open-source API testing tool with AI-powered test generation,
|
|
11
|
+
endpoint discovery, and a beautiful interface.
|
|
12
|
+
</p>
|
|
13
|
+
<div className="flex gap-4 justify-center">
|
|
14
|
+
<Link
|
|
15
|
+
href="/workspace"
|
|
16
|
+
className="bg-primary text-primary-foreground px-6 py-3 rounded-lg text-lg font-medium hover:bg-primary/90 transition-colors"
|
|
17
|
+
>
|
|
18
|
+
Get Started
|
|
19
|
+
</Link>
|
|
20
|
+
</div>
|
|
21
|
+
</section>
|
|
22
|
+
);
|
|
23
|
+
}
|