import React, { useState } from "react"; import { ExternalLink, Check, X, Loader2, Key, Globe, Lock } from "lucide-react"; import { invoke } from "@tauri-apps/api/core"; import { Card, CardHeader, CardTitle, CardContent, CardDescription, Button, Input, Label, RadioGroup, RadioGroupItem, } from "@/components/ui"; import { initiateOauthCmd, authenticateWithWebviewCmd, extractCookiesFromWebviewCmd, saveManualTokenCmd, testConfluenceConnectionCmd, testServiceNowConnectionCmd, testAzureDevOpsConnectionCmd, } from "@/lib/tauriCommands"; type AuthMode = "oauth2" | "webview" | "token"; interface IntegrationConfig { service: string; baseUrl: string; username?: string; projectName?: string; spaceKey?: string; connected: boolean; authMode: AuthMode; token?: string; tokenType?: string; webviewId?: string; } export default function Integrations() { const [configs, setConfigs] = useState>({ confluence: { service: "confluence", baseUrl: "", spaceKey: "", connected: false, authMode: "webview", tokenType: "Bearer", }, servicenow: { service: "servicenow", baseUrl: "", username: "", connected: false, authMode: "token", tokenType: "Basic", }, azuredevops: { service: "azuredevops", baseUrl: "", projectName: "", connected: false, authMode: "webview", tokenType: "Bearer", }, }); const [loading, setLoading] = useState>({}); const [testResults, setTestResults] = useState>({}); const handleAuthModeChange = (service: string, mode: AuthMode) => { setConfigs((prev) => ({ ...prev, [service]: { ...prev[service], authMode: mode, connected: false }, })); setTestResults((prev) => ({ ...prev, [service]: null })); }; const handleConnectOAuth = async (service: string) => { setLoading((prev) => ({ ...prev, [service]: true })); try { const response = await initiateOauthCmd(service); // Open auth URL in default browser await invoke("plugin:shell|open", { path: response.auth_url }); setConfigs((prev) => ({ ...prev, [service]: { ...prev[service], connected: true }, })); setTestResults((prev) => ({ ...prev, [service]: { success: true, message: "Authentication window opened. Complete the login to continue." }, })); } catch (err) { console.error("Failed to initiate OAuth:", err); setTestResults((prev) => ({ ...prev, [service]: { success: false, message: String(err) }, })); } finally { setLoading((prev) => ({ ...prev, [service]: false })); } }; const handleConnectWebview = async (service: string) => { const config = configs[service]; setLoading((prev) => ({ ...prev, [service]: true })); try { const response = await authenticateWithWebviewCmd(service, config.baseUrl); setConfigs((prev) => ({ ...prev, [service]: { ...prev[service], webviewId: response.webview_id }, })); setTestResults((prev) => ({ ...prev, [service]: { success: true, message: response.message + " Click 'Complete Login' when done." }, })); } catch (err) { console.error("Failed to open webview:", err); setTestResults((prev) => ({ ...prev, [service]: { success: false, message: String(err) }, })); } finally { setLoading((prev) => ({ ...prev, [service]: false })); } }; const handleCompleteWebviewLogin = async (service: string) => { const config = configs[service]; if (!config.webviewId) { setTestResults((prev) => ({ ...prev, [service]: { success: false, message: "No webview session found. Click 'Login via Browser' first." }, })); return; } setLoading((prev) => ({ ...prev, [`complete-${service}`]: true })); try { const result = await extractCookiesFromWebviewCmd(service, config.webviewId); setConfigs((prev) => ({ ...prev, [service]: { ...prev[service], connected: true, webviewId: undefined }, })); setTestResults((prev) => ({ ...prev, [service]: { success: result.success, message: result.message }, })); } catch (err) { console.error("Failed to extract cookies:", err); setTestResults((prev) => ({ ...prev, [service]: { success: false, message: String(err) }, })); } finally { setLoading((prev) => ({ ...prev, [`complete-${service}`]: false })); } }; const handleSaveToken = async (service: string) => { const config = configs[service]; if (!config.token) { setTestResults((prev) => ({ ...prev, [service]: { success: false, message: "Please enter a token" }, })); return; } setLoading((prev) => ({ ...prev, [`save-${service}`]: true })); try { const result = await saveManualTokenCmd({ service, token: config.token, token_type: config.tokenType || "Bearer", base_url: config.baseUrl, }); if (result.success) { setConfigs((prev) => ({ ...prev, [service]: { ...prev[service], connected: true }, })); } setTestResults((prev) => ({ ...prev, [service]: result, })); } catch (err) { console.error("Failed to save token:", err); setTestResults((prev) => ({ ...prev, [service]: { success: false, message: String(err) }, })); } finally { setLoading((prev) => ({ ...prev, [`save-${service}`]: false })); } }; const handleTestConnection = async (service: string) => { setLoading((prev) => ({ ...prev, [`test-${service}`]: true })); setTestResults((prev) => ({ ...prev, [service]: null })); try { const config = configs[service]; let result; switch (service) { case "confluence": result = await testConfluenceConnectionCmd(config.baseUrl, { space_key: config.spaceKey, }); break; case "servicenow": result = await testServiceNowConnectionCmd(config.baseUrl, { username: config.username, }); break; case "azuredevops": result = await testAzureDevOpsConnectionCmd(config.baseUrl, { project: config.projectName, }); break; default: throw new Error(`Unknown service: ${service}`); } setTestResults((prev) => ({ ...prev, [service]: result })); } catch (err) { setTestResults((prev) => ({ ...prev, [service]: { success: false, message: String(err) }, })); } finally { setLoading((prev) => ({ ...prev, [`test-${service}`]: false })); } }; const updateConfig = (service: string, field: string, value: string) => { setConfigs((prev) => ({ ...prev, [service]: { ...prev[service], [field]: value }, })); }; const renderAuthSection = (service: string) => { const config = configs[service]; const isOAuthSupported = service !== "servicenow"; // ServiceNow doesn't support OAuth2 return (
{/* Auth Mode Selection */}
handleAuthModeChange(service, value as AuthMode)} > {isOAuthSupported && (
)}
{/* OAuth2 Mode */} {config.authMode === "oauth2" && (

OAuth2 requires pre-registered application credentials. This may not work in all enterprise environments.

)} {/* Webview Mode */} {config.authMode === "webview" && (

Opens an embedded browser for you to log in normally. Works even when off-VPN. Captures session cookies for API access.

{config.webviewId && ( )}
)} {/* Token Mode */} {config.authMode === "token" && (

Enter a Personal Access Token (PAT), API Key, or Bearer token. Most reliable method but requires manual token generation.

updateConfig(service, "token", e.target.value)} />

{service === "confluence" && "Generate at: https://id.atlassian.com/manage-profile/security/api-tokens"} {service === "azuredevops" && "Generate at: https://dev.azure.com/{org}/_usersSettings/tokens"} {service === "servicenow" && "Use your ServiceNow password or API key"}

)}
); }; return (

Integrations

Connect TFTSR with your existing tools and platforms. Choose the authentication method that works best for your environment.

{/* Confluence */} Confluence Publish RCA documents to Confluence spaces. Supports OAuth2, browser login, or API tokens.
updateConfig("confluence", "baseUrl", e.target.value)} />
updateConfig("confluence", "spaceKey", e.target.value)} />
{renderAuthSection("confluence")}
{testResults.confluence && (
{testResults.confluence.success ? ( ) : ( )} {testResults.confluence.message}
)}
{/* ServiceNow */} ServiceNow Link incidents and push resolution steps. Supports browser login or basic authentication.
updateConfig("servicenow", "baseUrl", e.target.value)} />
updateConfig("servicenow", "username", e.target.value)} />
{renderAuthSection("servicenow")}
{testResults.servicenow && (
{testResults.servicenow.success ? ( ) : ( )} {testResults.servicenow.message}
)}
{/* Azure DevOps */} Azure DevOps Create work items and attach RCA documents. Supports OAuth2, browser login, or PAT tokens.
updateConfig("azuredevops", "baseUrl", e.target.value)} />
updateConfig("azuredevops", "projectName", e.target.value)} />
{renderAuthSection("azuredevops")}
{testResults.azuredevops && (
{testResults.azuredevops.success ? ( ) : ( )} {testResults.azuredevops.message}
)}

Authentication Method Comparison:

  • OAuth2: Most secure, but requires pre-registered app. May not work with enterprise SSO.
  • Browser Login: Best for VPN environments. Lets you authenticate off-VPN, extracts session cookies for API use.
  • Manual Token: Most reliable fallback. Requires generating API tokens manually from each service.
); }