-
Notifications
You must be signed in to change notification settings - Fork 68
feat: incident.io connector #287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 23 commits
afa016c
ea966b1
ab5f81d
aab5c6e
6776ec9
1dbaa28
d0d501c
ac5c660
1f81ad4
7d3161c
afc897d
8a419bc
dc7b43e
a96cc94
751297c
9dd0e5a
7405379
51cb51c
ffa7e3b
7c59994
0f2dd90
e116c5d
e786673
242a086
4f9b1d1
9a11b7f
3bbbadf
779d74e
b16a15a
f803698
909f7ac
ff7d167
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { NextRequest } from 'next/server'; | ||
| import { forwardRequest } from '@/lib/backend-proxy'; | ||
|
|
||
| export async function GET(request: NextRequest) { | ||
| return forwardRequest(request, 'GET', '/incidentio/alerts', 'incident-io/alerts'); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { NextRequest } from 'next/server'; | ||
| import { forwardRequest } from '@/lib/backend-proxy'; | ||
|
|
||
| export async function GET(request: NextRequest) { | ||
| return forwardRequest(request, 'GET', '/incidentio/alerts/webhook-url', 'incident-io/webhook-url'); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { NextRequest } from 'next/server'; | ||
| import { forwardRequest } from '@/lib/backend-proxy'; | ||
|
|
||
| export async function POST(request: NextRequest) { | ||
| return forwardRequest(request, 'POST', '/incidentio/connect', 'incident-io/connect'); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { NextRequest } from 'next/server'; | ||
| import { forwardRequest } from '@/lib/backend-proxy'; | ||
|
|
||
| export async function GET(request: NextRequest) { | ||
| return forwardRequest(request, 'GET', '/incidentio/rca-settings', 'incident-io/rca-settings'); | ||
| } | ||
|
|
||
| export async function PUT(request: NextRequest) { | ||
| return forwardRequest(request, 'PUT', '/incidentio/rca-settings', 'incident-io/rca-settings'); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { NextRequest } from 'next/server'; | ||
| import { forwardRequest } from '@/lib/backend-proxy'; | ||
|
|
||
| export async function GET(request: NextRequest) { | ||
| return forwardRequest(request, 'GET', '/incidentio/status', 'incident-io/status'); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| "use client"; | ||
|
|
||
| import { useState } from "react"; | ||
| import { useToast } from "@/hooks/use-toast"; | ||
| import { useConnectorAuth } from "@/hooks/use-connector-auth"; | ||
| import { incidentIoService } from "@/lib/services/incident-io"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Input } from "@/components/ui/input"; | ||
| import { Label } from "@/components/ui/label"; | ||
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; | ||
| import { Loader2 } from "lucide-react"; | ||
| import { getUserFriendlyError } from "@/lib/utils"; | ||
| import { IncidentIoWebhookStep } from "@/components/incident-io/IncidentIoWebhookStep"; | ||
| import ConnectorAuthGuard from "@/components/connectors/ConnectorAuthGuard"; | ||
| import Image from "next/image"; | ||
|
|
||
| export default function IncidentIoAuthPage() { | ||
| const { toast } = useToast(); | ||
| const [apiKey, setApiKey] = useState(""); | ||
| const [loading, setLoading] = useState(false); | ||
|
|
||
| const { | ||
| isConnected, | ||
| isCheckingStatus, | ||
| updateLocalState, | ||
| disconnect, | ||
| } = useConnectorAuth({ | ||
| cacheKey: "incident_io_connection_status", | ||
| storageKey: "isIncidentIoConnected", | ||
| fetchStatus: () => incidentIoService.getStatus(), | ||
| disconnectPath: "/api/connected-accounts/incidentio", | ||
| }); | ||
|
|
||
| const handleConnect = async (event: React.FormEvent<HTMLFormElement>) => { | ||
| event.preventDefault(); | ||
| setLoading(true); | ||
|
|
||
| try { | ||
| const result = await incidentIoService.connect({ apiKey }); | ||
| updateLocalState(result); | ||
| toast({ title: "Success", description: "incident.io connected successfully!" }); | ||
| } catch (err: any) { | ||
| console.error("incident.io connection failed", err); | ||
| toast({ | ||
| title: "Failed to connect to incident.io", | ||
| description: getUserFriendlyError(err), | ||
| variant: "destructive", | ||
| }); | ||
| } finally { | ||
| setLoading(false); | ||
| setApiKey(""); | ||
| } | ||
| }; | ||
|
|
||
| const handleDisconnect = async () => { | ||
| setLoading(true); | ||
| try { | ||
| await disconnect(); | ||
| toast({ title: "Success", description: "incident.io disconnected successfully" }); | ||
| } catch (err: any) { | ||
| console.error("incident.io disconnect failed", err); | ||
| toast({ | ||
| title: "Failed to disconnect incident.io", | ||
| description: getUserFriendlyError(err), | ||
| variant: "destructive", | ||
| }); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| if (isCheckingStatus) { | ||
| return ( | ||
| <ConnectorAuthGuard connectorName="incident.io"> | ||
| <div className="container mx-auto py-8 px-4 max-w-2xl"> | ||
| <Card> | ||
| <CardContent className="flex items-center justify-center py-12"> | ||
| <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> | ||
| </CardContent> | ||
| </Card> | ||
| </div> | ||
| </ConnectorAuthGuard> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <ConnectorAuthGuard connectorName="incident.io"> | ||
| <div className="container mx-auto py-8 px-4 max-w-2xl"> | ||
| <div className="flex items-center gap-4 mb-6"> | ||
| <div className="flex items-center justify-center h-12 w-12 rounded-lg bg-white dark:bg-white p-1.5"> | ||
| <Image src="/incidentio.svg" alt="incident.io" width={40} height={40} /> | ||
| </div> | ||
| <div> | ||
| <h1 className="text-3xl font-bold">incident.io</h1> | ||
| <p className="text-muted-foreground mt-0.5"> | ||
| Incident lifecycle tracking and automated RCA | ||
| </p> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex items-center justify-center mb-8"> | ||
| <div className="flex items-center"> | ||
| <div className={`flex items-center justify-center w-10 h-10 rounded-full font-bold ${!isConnected ? 'text-white' : 'bg-gray-200 text-gray-600'}`} style={!isConnected ? { backgroundColor: '#F04438' } : undefined}> | ||
|
Check warning on line 103 in client/src/app/incident-io/auth/page.tsx
|
||
| 1 | ||
| </div> | ||
| <div className="w-24 h-1" style={{ backgroundColor: isConnected ? '#F04438' : '#e5e7eb' }}></div> | ||
| <div className={`flex items-center justify-center w-10 h-10 rounded-full font-bold ${isConnected ? 'text-white' : 'bg-gray-200 text-gray-600'}`} style={isConnected ? { backgroundColor: '#F04438' } : undefined}> | ||
| 2 | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex items-center justify-center mb-6 text-sm font-medium"> | ||
| <span className={!isConnected ? 'text-foreground' : 'text-muted-foreground'} style={!isConnected ? { color: '#F04438' } : undefined}> | ||
|
Check warning on line 114 in client/src/app/incident-io/auth/page.tsx
|
||
| Connect | ||
| </span> | ||
| <span className="mx-4 text-muted-foreground">→</span> | ||
| <span className={isConnected ? 'text-foreground' : 'text-muted-foreground'} style={isConnected ? { color: '#F04438' } : undefined}> | ||
| Configure Webhook | ||
| </span> | ||
| </div> | ||
|
|
||
| {!isConnected ? ( | ||
|
Check warning on line 123 in client/src/app/incident-io/auth/page.tsx
|
||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Connect to incident.io</CardTitle> | ||
| <CardDescription> | ||
| Create an API key at <strong>Settings → API keys</strong> in incident.io, then paste it below. | ||
| </CardDescription> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <form className="space-y-4" onSubmit={handleConnect}> | ||
| <div className="space-y-2"> | ||
| <Label htmlFor="apiKey">API Key</Label> | ||
| <Input | ||
| id="apiKey" | ||
| type="password" | ||
| placeholder="Paste your incident.io API key" | ||
| value={apiKey} | ||
| onChange={(e) => setApiKey(e.target.value)} | ||
| required | ||
| /> | ||
| <p className="text-xs text-muted-foreground"> | ||
| The API key needs these permissions: <strong>View all incident data (including private incidents)</strong> and <strong>Create incidents</strong>. Keys are stored securely in Vault. | ||
| </p> | ||
| </div> | ||
|
|
||
| <Button type="submit" className="w-full" disabled={loading || !apiKey}> | ||
| {loading ? ( | ||
| <> | ||
| <Loader2 className="mr-2 h-4 w-4 animate-spin" /> | ||
| Connecting... | ||
| </> | ||
| ) : ( | ||
| "Connect incident.io" | ||
| )} | ||
| </Button> | ||
| </form> | ||
| </CardContent> | ||
| </Card> | ||
| ) : ( | ||
| <IncidentIoWebhookStep | ||
| onDisconnect={handleDisconnect} | ||
| loading={loading} | ||
| /> | ||
| )} | ||
| </div> | ||
| </ConnectorAuthGuard> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.