tftsr-devops_investigation/src/pages/NewIssue/index.tsx
Shaun Arman 1e8ef41e64
Some checks failed
Auto Tag / auto-tag (push) Successful in 4s
Test / rust-fmt-check (push) Successful in 2m5s
Release / build-macos-arm64 (push) Successful in 10m29s
Test / rust-clippy (push) Failing after 18m4s
Release / build-linux-arm64 (push) Failing after 22m1s
Test / rust-tests (push) Successful in 12m44s
Test / frontend-typecheck (push) Successful in 1m29s
Test / frontend-tests (push) Has been cancelled
Release / build-windows-amd64 (push) Has been cancelled
Release / build-linux-amd64 (push) Has been cancelled
feat: add OAuth2 frontend UI and complete integration flow
Phase 2.2: OAuth2 flow - FRONTEND COMPLETE 

Implemented:
- TypeScript command wrappers in tauriCommands.ts
  * initiateOauthCmd(service) -> OAuthInitResponse
  * handleOauthCallbackCmd(service, code, stateKey)
  * test*ConnectionCmd() for all services
  * OAuthInitResponse and ConnectionResult types

- Complete Settings/Integrations UI
  * Three integration cards: Confluence, ServiceNow, ADO
  * Connect with OAuth2 buttons (Confluence, ADO)
  * Basic auth note for ServiceNow
  * Configuration inputs: baseUrl, username, projectName, spaceKey
  * Test connection buttons with loading states
  * Success/error feedback with color-coded messages
  * OAuth2 flow instructions for users

- OAuth2 flow in browser
  * Opens auth URL in default browser via shell plugin
  * User authenticates with service
  * Redirected to localhost:8765/callback
  * Callback server handles token exchange automatically
  * Success message shown to user

- CSP updates in tauri.conf.json
  * Added http://localhost:8765 (callback server)
  * Added https://auth.atlassian.com (Confluence OAuth)
  * Added https://*.atlassian.net (Confluence API)
  * Added https://login.microsoftonline.com (ADO OAuth)
  * Added https://dev.azure.com (ADO API)

- UI improvements
  * Fixed Cancel button variant (ghost instead of secondary)
  * Loading spinners with Loader2 icon
  * Check/X icons for success/error states
  * Disabled states when not configured
  * Optimistic UI updates on connect

Frontend + Backend = COMPLETE END-TO-END OAUTH2 FLOW:
1. User goes to Settings → Integrations
2. Enters base URL and config
3. Clicks 'Connect with OAuth2'
4. Browser opens with service auth page
5. User logs in and authorizes
6. Redirected to localhost:8765/callback
7. Token exchanged and encrypted automatically
8. Stored in SQLite credentials table
9. Ready for API calls to external services 

TypeScript: All types checked, no errors
Frontend build:  Built in 2.26s
Total lines: ~400 lines of new UI code

Next: Phase 2.3 - Integration API clients (Confluence REST, ServiceNow REST, ADO REST)
2026-04-03 15:04:12 -05:00

256 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import {
Terminal,
Monitor,
Network,
Container,
Database,
Server,
HardDrive,
BarChart3,
Phone,
Lock,
PhoneCall,
Code,
Workflow,
CircuitBoard,
ServerCog,
Users,
} from "lucide-react";
import {
Card,
CardContent,
Button,
Input,
Label,
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/components/ui";
import { DOMAINS } from "@/lib/domainPrompts";
import { createIssueCmd } from "@/lib/tauriCommands";
import { useSessionStore } from "@/stores/sessionStore";
const iconMap: Record<string, React.ElementType> = {
Terminal,
Monitor,
Network,
Container,
Database,
Server,
HardDrive,
BarChart3,
Phone,
Lock,
PhoneCall,
Code,
Workflow,
CircuitBoard,
ServerCog,
Users,
};
export default function NewIssue() {
const navigate = useNavigate();
const startSession = useSessionStore((s) => s.startSession);
const [selectedDomain, setSelectedDomain] = useState<string | null>(null);
const [title, setTitle] = useState("");
const [severity, setSeverity] = useState("P3");
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showDisclaimer, setShowDisclaimer] = useState(false);
useEffect(() => {
const hasAcceptedDisclaimer = localStorage.getItem("tftsr-ai-disclaimer-accepted");
if (!hasAcceptedDisclaimer) {
setShowDisclaimer(true);
}
}, []);
const handleAcceptDisclaimer = () => {
localStorage.setItem("tftsr-ai-disclaimer-accepted", "true");
setShowDisclaimer(false);
};
const handleStartTriage = async () => {
const hasAcceptedDisclaimer = localStorage.getItem("tftsr-ai-disclaimer-accepted");
if (!hasAcceptedDisclaimer) {
setShowDisclaimer(true);
return;
}
if (!selectedDomain || !title.trim()) return;
setIsSubmitting(true);
setError(null);
try {
const issue = await createIssueCmd({ title: title.trim(), domain: selectedDomain, severity });
startSession(issue);
navigate(`/issue/${issue.id}/triage`);
} catch (err) {
setError(String(err));
setIsSubmitting(false);
}
};
return (
<>
{/* AI Disclaimer Modal */}
{showDisclaimer && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-background border rounded-lg max-w-2xl w-full p-6 space-y-4 shadow-lg">
<h2 className="text-2xl font-bold text-foreground">AI-Assisted Triage Disclaimer</h2>
<div className="space-y-3 text-sm text-foreground/90 max-h-[60vh] overflow-y-auto">
<p className="font-medium text-destructive">
IMPORTANT: Read Carefully Before Proceeding
</p>
<p>
This application uses artificial intelligence (AI) to assist with IT issue triage, root cause analysis,
and troubleshooting recommendations. While AI can be a powerful tool, <strong>you must understand its
limitations and your responsibilities</strong>.
</p>
<div className="bg-destructive/10 border border-destructive/20 rounded p-3 space-y-2">
<p className="font-semibold">AI Can Make Mistakes:</p>
<ul className="list-disc list-inside space-y-1 ml-2">
<li>AI models may provide <strong>incorrect</strong>, <strong>incomplete</strong>, or <strong>outdated</strong> information</li>
<li>AI can <strong>hallucinate</strong> generating plausible-sounding but entirely false information</li>
<li>Recommendations may not apply to your specific environment or configuration</li>
<li>Commands suggested by AI may have <strong>unintended consequences</strong> including data loss, system downtime, or security vulnerabilities</li>
</ul>
</div>
<div className="bg-yellow-500/10 border border-yellow-500/20 rounded p-3 space-y-2">
<p className="font-semibold">You Are Fully Responsible:</p>
<ul className="list-disc list-inside space-y-1 ml-2">
<li><strong>You</strong> are solely responsible for any commands you execute, changes you make, or actions you take based on AI recommendations</li>
<li><strong>Always verify</strong> AI suggestions against official documentation, best practices, and your organization's policies</li>
<li><strong>Test in non-production</strong> environments before applying changes to production systems</li>
<li><strong>Understand commands</strong> before executing them never blindly run suggested commands</li>
<li>Have <strong>backups and rollback plans</strong> in place before making system changes</li>
</ul>
</div>
<p>
<strong>Best Practices:</strong>
</p>
<ul className="list-disc list-inside space-y-1 ml-2">
<li>Treat AI recommendations as a starting point for investigation, not definitive answers</li>
<li>Consult with senior engineers or subject matter experts when dealing with critical systems</li>
<li>Review all AI-generated content for accuracy and relevance to your specific situation</li>
<li>Maintain proper change control and approval processes</li>
<li>Document your actions and decision-making process</li>
</ul>
<p className="pt-2 border-t">
By clicking "I Understand and Accept," you acknowledge that you have read and understood this disclaimer,
and you accept full responsibility for any actions taken based on information provided by this AI-assisted
system. <strong>Use at your own risk.</strong>
</p>
</div>
<div className="flex gap-3 pt-2">
<Button
onClick={handleAcceptDisclaimer}
className="flex-1"
>
I Understand and Accept
</Button>
<Button
onClick={() => navigate("/")}
variant="ghost"
className="flex-1"
>
Cancel
</Button>
</div>
</div>
</div>
)}
<div className="p-6 space-y-6">
<div>
<h1 className="text-3xl font-bold">New Issue</h1>
<p className="text-muted-foreground mt-1">
Select a domain, describe the issue, and begin triage.
</p>
</div>
{/* Domain selection grid */}
<div>
<Label className="text-sm font-medium mb-3 block">Select Domain</Label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{DOMAINS.map((domain) => {
const Icon = iconMap[domain.icon] ?? Terminal;
const isSelected = selectedDomain === domain.id;
return (
<Card
key={domain.id}
className={`cursor-pointer transition-colors hover:border-primary ${
isSelected ? "border-primary bg-primary/5 ring-2 ring-primary" : ""
}`}
onClick={() => setSelectedDomain(domain.id)}
>
<CardContent className="p-4 text-center">
<Icon className={`w-8 h-8 mx-auto mb-2 ${isSelected ? "text-primary" : "text-muted-foreground"}`} />
<p className="text-sm font-medium">{domain.label}</p>
<p className="text-xs text-muted-foreground mt-1">{domain.description}</p>
</CardContent>
</Card>
);
})}
</div>
</div>
{/* Title */}
<div className="space-y-2">
<Label htmlFor="title">Issue Title</Label>
<Input
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Brief description of the issue..."
/>
</div>
{/* Severity */}
<div className="space-y-2">
<Label>Severity</Label>
<Select value={severity} onValueChange={setSeverity}>
<SelectTrigger>
<SelectValue placeholder="Select severity" />
</SelectTrigger>
<SelectContent>
<SelectItem value="P1">P1 - Critical</SelectItem>
<SelectItem value="P2">P2 - High</SelectItem>
<SelectItem value="P3">P3 - Medium</SelectItem>
<SelectItem value="P4">P4 - Low</SelectItem>
</SelectContent>
</Select>
</div>
{/* Error */}
{error && (
<div className="text-sm text-destructive bg-destructive/10 rounded-md p-3">
{error}
</div>
)}
{/* Submit */}
<Button
onClick={handleStartTriage}
disabled={!selectedDomain || !title.trim() || isSubmitting}
className="w-full"
size="lg"
>
{isSubmitting ? "Creating..." : "Start Triage"}
</Button>
</div>
</>
);
}