Skip to content

Commit 0469be4

Browse files
authored
Merge pull request #4536 from IBM/4530-add-advanced-settings-with-none-and-basic-auth
feat(ui-rewrite): add advanced settings for none and basic authentication
2 parents 4527347 + 1679276 commit 0469be4

13 files changed

Lines changed: 1398 additions & 302 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { Info } from "lucide-react";
2+
import { Textarea } from "@/components/ui/textarea";
3+
import {
4+
Select,
5+
SelectContent,
6+
SelectItem,
7+
SelectTrigger,
8+
SelectValue,
9+
} from "@/components/ui/select";
10+
import { CACertificateUpload } from "@/components/mcp-servers/CACertificateUpload";
11+
import { NoneAuth } from "@/components/mcp-servers/NoneAuth";
12+
import { BasicAuth } from "@/components/mcp-servers/BasicAuth";
13+
14+
type AuthType = "none" | "basic" | "bearer" | "custom" | "oauth" | "query";
15+
16+
interface AdvancedSettingsProps {
17+
visibility: string;
18+
onVisibilityChange: (value: string) => void;
19+
authType: AuthType;
20+
onAuthTypeChange: (value: AuthType) => void;
21+
basicAuthUsername: string;
22+
basicAuthPassword: string;
23+
onBasicAuthUsernameChange: (value: string) => void;
24+
onBasicAuthPasswordChange: (value: string) => void;
25+
oneTimeAuth: boolean;
26+
onOneTimeAuthChange: (checked: boolean) => void;
27+
passthroughHeaders: string;
28+
onPassthroughHeadersChange: (value: string) => void;
29+
onCACertificateFilesSelected: (files: File[]) => void;
30+
}
31+
32+
export function AdvancedSettings({
33+
visibility,
34+
onVisibilityChange,
35+
authType,
36+
onAuthTypeChange,
37+
basicAuthUsername,
38+
basicAuthPassword,
39+
onBasicAuthUsernameChange,
40+
onBasicAuthPasswordChange,
41+
oneTimeAuth,
42+
onOneTimeAuthChange,
43+
passthroughHeaders,
44+
onPassthroughHeadersChange,
45+
onCACertificateFilesSelected,
46+
}: AdvancedSettingsProps) {
47+
const renderAuthContent = () => {
48+
switch (authType) {
49+
case "none":
50+
return <NoneAuth />;
51+
case "basic":
52+
return (
53+
<BasicAuth
54+
username={basicAuthUsername}
55+
password={basicAuthPassword}
56+
onUsernameChange={onBasicAuthUsernameChange}
57+
onPasswordChange={onBasicAuthPasswordChange}
58+
/>
59+
);
60+
default:
61+
return null;
62+
}
63+
};
64+
65+
return (
66+
<div className="space-y-6 py-4">
67+
{/* Visibility */}
68+
<div className="space-y-3">
69+
<label
70+
htmlFor="visibility"
71+
className="text-sm font-medium text-neutral-950 dark:text-white"
72+
>
73+
Visibility
74+
</label>
75+
<Select value={visibility} onValueChange={onVisibilityChange}>
76+
<SelectTrigger className="h-10 w-full border-neutral-300 bg-white dark:border-neutral-700 dark:bg-neutral-950">
77+
<SelectValue placeholder="Select visibility" />
78+
</SelectTrigger>
79+
<SelectContent>
80+
<SelectItem value="public">Public</SelectItem>
81+
<SelectItem value="private">Private</SelectItem>
82+
<SelectItem value="team">Team</SelectItem>
83+
</SelectContent>
84+
</Select>
85+
</div>
86+
87+
{/* Authentication type */}
88+
<div className="space-y-3">
89+
<label className="text-sm font-medium text-neutral-950 dark:text-white">
90+
Authentication type
91+
</label>
92+
<div
93+
role="radiogroup"
94+
aria-label="Authentication type"
95+
className="flex w-full flex-nowrap gap-1 rounded-md bg-neutral-100 p-1 dark:bg-neutral-800"
96+
>
97+
{(["none", "basic", "bearer", "custom", "oauth", "query"] as AuthType[]).map((type) => {
98+
const label =
99+
type === "none"
100+
? "None"
101+
: type === "basic"
102+
? "Basic"
103+
: type === "bearer"
104+
? "Bearer token"
105+
: type === "custom"
106+
? "Custom headers"
107+
: type === "oauth"
108+
? "OAuth 2.0"
109+
: "Query parameter";
110+
const isLongerLabel = type === "custom" || type === "query";
111+
return (
112+
<div key={type} className={isLongerLabel ? "flex-[1.3] min-w-0" : "flex-1 min-w-0"}>
113+
<input
114+
type="radio"
115+
id={`auth-${type}`}
116+
name="auth-type"
117+
value={type}
118+
checked={authType === type}
119+
onChange={(e) => onAuthTypeChange(e.target.value as AuthType)}
120+
className="sr-only peer"
121+
/>
122+
<label
123+
htmlFor={`auth-${type}`}
124+
className="flex cursor-pointer items-center justify-center whitespace-nowrap rounded-md px-3 py-2 text-center text-sm font-medium text-neutral-500 transition hover:bg-neutral-200 hover:text-neutral-700 peer-checked:bg-neutral-800 peer-checked:text-white peer-checked:px-4 peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 dark:text-neutral-400 dark:hover:bg-neutral-900 dark:hover:text-neutral-300 dark:peer-checked:bg-neutral-950 dark:peer-checked:text-white"
125+
>
126+
{label}
127+
</label>
128+
</div>
129+
);
130+
})}
131+
</div>
132+
</div>
133+
134+
{/* Auth-specific content */}
135+
{renderAuthContent()}
136+
137+
{/* One-time authentication */}
138+
<div className="space-y-2">
139+
<label className="flex items-center gap-2">
140+
<input
141+
type="checkbox"
142+
checked={oneTimeAuth}
143+
onChange={(e) => onOneTimeAuthChange(e.target.checked)}
144+
className="h-4 w-4 rounded border-neutral-300 dark:border-neutral-700"
145+
/>
146+
<span className="text-sm font-medium text-neutral-950 dark:text-white">
147+
One-time authentication
148+
</span>
149+
<Info className="h-4 w-4 text-neutral-400 dark:text-neutral-500" />
150+
</label>
151+
<p className="pl-6 text-sm text-neutral-600 dark:text-neutral-400">
152+
{
153+
"Use credentials once, don't store them. Health checks will be disabled. For reusable credentials, configure passthrough headers."
154+
}
155+
</p>
156+
</div>
157+
158+
{/* Passthrough headers */}
159+
<div className="space-y-2">
160+
<label
161+
htmlFor="passthrough-headers"
162+
className="text-sm font-medium text-neutral-950 dark:text-white"
163+
>
164+
Passthrough headers
165+
</label>
166+
<p className="text-sm text-neutral-600 dark:text-neutral-400">
167+
Add comma-separate headers to forward from client requests. Leave empty to use global
168+
defaults.
169+
</p>
170+
<Textarea
171+
id="passthrough-headers"
172+
value={passthroughHeaders}
173+
onChange={(e) => onPassthroughHeadersChange(e.target.value)}
174+
placeholder="e.g. Authorization, X-Tenant-Id, X-Trace-Id..."
175+
className="min-h-20 focus-visible:ring-1 focus-visible:ring-offset-0"
176+
/>
177+
</div>
178+
179+
{/* CA certificate */}
180+
<CACertificateUpload onFilesSelected={onCACertificateFilesSelected} />
181+
</div>
182+
);
183+
}
184+
185+
// Made with Bob
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Input } from "@/components/ui/input";
2+
3+
interface BasicAuthProps {
4+
username: string;
5+
password: string;
6+
onUsernameChange: (value: string) => void;
7+
onPasswordChange: (value: string) => void;
8+
}
9+
10+
export function BasicAuth({
11+
username,
12+
password,
13+
onUsernameChange,
14+
onPasswordChange,
15+
}: BasicAuthProps) {
16+
return (
17+
<div className="space-y-4">
18+
<div className="space-y-1">
19+
<label
20+
htmlFor="basic-auth-username"
21+
className="inline-flex items-center gap-0.5 text-sm font-medium text-neutral-900 dark:text-neutral-100"
22+
>
23+
Username<span className="text-red-500">*</span>
24+
<span className="sr-only">(required)</span>
25+
</label>
26+
<Input
27+
id="basic-auth-username"
28+
type="text"
29+
value={username}
30+
onChange={(e) => onUsernameChange(e.target.value)}
31+
placeholder="Add username for basic authentication..."
32+
className="rounded-md border-neutral-300 bg-white px-4 text-sm text-neutral-900 shadow-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 placeholder:text-neutral-400 dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-100 dark:placeholder:text-neutral-500"
33+
/>
34+
</div>
35+
36+
<div className="space-y-1">
37+
<label
38+
htmlFor="basic-auth-password"
39+
className="inline-flex items-center gap-0.5 text-sm font-medium text-neutral-900 dark:text-neutral-100"
40+
>
41+
Password<span className="text-red-500">*</span>
42+
<span className="sr-only">(required)</span>
43+
</label>
44+
<Input
45+
id="basic-auth-password"
46+
type="password"
47+
value={password}
48+
onChange={(e) => onPasswordChange(e.target.value)}
49+
placeholder="Add password..."
50+
className="rounded-md border-neutral-300 bg-white px-4 text-sm text-neutral-900 shadow-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 placeholder:text-neutral-400 dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-100 dark:placeholder:text-neutral-500"
51+
/>
52+
</div>
53+
</div>
54+
);
55+
}

0 commit comments

Comments
 (0)