Skip to main content
Glama
Arize-ai

@arizeai/phoenix-mcp

Official
by Arize-ai
frontend-implementation.md5.46 kB
# Frontend Implementation ## New Component: LDAPLoginForm.tsx ```typescript import { useState, FormEvent } from "react"; import { useNavigate } from "react-router"; import { Button, Flex, Form, Input, Label, TextField, Alert, View } from "@phoenix/components"; import { getReturnUrl, prependBasename } from "@phoenix/utils/routingUtils"; export function LDAPLoginForm() { const navigate = useNavigate(); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState<string | null>(null); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setIsLoading(true); setError(null); try { const response = await fetch(prependBasename("/auth/ldap/login"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), }); if (!response.ok) { const errorMessage = response.status === 429 ? "Too many requests. Please try again later." : response.status === 503 ? "Authentication service temporarily unavailable" : "Invalid credentials"; setError(errorMessage); return; } // Success - navigate to app const returnUrl = getReturnUrl(); navigate(returnUrl); } catch (error) { setError("Authentication service unavailable"); } finally { setIsLoading(false); } }; return ( <> {error && ( <View paddingBottom="size-100"> <Alert variant="danger">{error}</Alert> </View> )} <Form onSubmit={handleSubmit}> <Flex direction="column" gap="size-100"> <TextField isRequired onChange={(v) => setUsername(v)} value={username} autoComplete="username" > <Label>Username</Label> <Input placeholder="your LDAP username" /> </TextField> <TextField type="password" isRequired onChange={(v) => setPassword(v)} value={password} autoComplete="current-password" > <Label>Password</Label> <Input placeholder="your password" /> </TextField> <Button variant="primary" type="submit" isDisabled={isLoading}> {isLoading ? "Logging In..." : "Log In with LDAP"} </Button> </Flex> </Form> </> ); } ``` ## Update LoginPage.tsx ```typescript import { LDAPLoginForm } from "./LDAPLoginForm"; export function LoginPage() { const showLoginForm = !window.Config.basicAuthDisabled; const showLDAPLogin = window.Config.ldapEnabled; const oAuth2Idps = window.Config.oAuth2Idps; const hasOAuth2Idps = oAuth2Idps.length > 0; return ( <AuthLayout> <Flex direction="column" gap="size-200" alignItems="center"> <View paddingBottom="size-200"> <PhoenixLogo /> </View> </Flex> {/* Basic auth form */} {showLoginForm && <LoginForm />} {/* Separator */} {showLoginForm && (showLDAPLogin || hasOAuth2Idps) ? ( <div css={separatorCSS}>or</div> ) : null} {/* LDAP login form */} {showLDAPLogin && <LDAPLoginForm />} {/* Separator */} {showLDAPLogin && hasOAuth2Idps ? ( <div css={separatorCSS}>or</div> ) : null} {/* OAuth2 provider buttons */} {hasOAuth2Idps && ( <ul css={oAuthLoginButtonListCSS}> {oAuth2Idps.map((idp) => ( <li key={idp.name}> <OAuth2Login idpName={idp.name} idpDisplayName={idp.displayName} returnUrl={returnUrl} /> </li> ))} </ul> )} </AuthLayout> ); } ``` ## Update UsersTable.tsx ```typescript // app/src/pages/settings/UsersTable.tsx { header: "method", accessorKey: "authMethod", size: 10, cell: ({ row }) => { const authMethod = row.original.authMethod; // If using Approach 1 (zero-migration) // Need to check oauth2_client_id prefix to distinguish LDAP if (authMethod === "OAUTH2") { const isLDAP = row.original.oauth2ClientId?.startsWith("\ue000LDAP(stopgap)"); return isLDAP ? "ldap" : "oauth2"; } // If using Approach 2 (migration) // GraphQL returns "LDAP" directly return authMethod.toLowerCase(); } } ``` ## Update Window Config ```typescript // app/src/globals.d.ts interface WindowConfig { // Existing basicAuthDisabled: boolean; oAuth2Idps: Array<{ name: string; displayName: string }>; // Add for LDAP ldapEnabled: boolean; ldapDisplayName?: string; // Optional custom display name } ``` ```python # src/phoenix/server/main.py @app.get("/config") async def get_config(): auth_settings = get_env_auth_settings() return { "basicAuthDisabled": auth_settings.disable_basic_auth, "oAuth2Idps": [ {"name": c.name, "displayName": c.display_name} for c in auth_settings.oauth2_clients ], "ldapEnabled": auth_settings.ldap_config is not None, "ldapDisplayName": auth_settings.ldap_config.display_name if auth_settings.ldap_config else None, } ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Arize-ai/phoenix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server