feat: MSK, MSK CONNECT and SES

This commit is contained in:
Zakaria
2026-10-05 11:06:57 -04:00
parent 2d8ff50a99
commit c0d3e5f402
84 changed files with 3513 additions and 195 deletions
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { CopyValueButton } from './CopyValueButton'
import {
AlertTriangle,
HardDrive,
@@ -32,10 +33,12 @@ import {
startEc2Instance,
stopEc2Instance,
terminateEc2Instance,
updateEc2InstanceSecurityGroups,
updateEc2InstanceTags,
type Ec2Image,
type Ec2KeyPair,
type Ec2SecurityGroup,
type Ec2SecurityGroupRef,
type Ec2Subnet,
type Ec2Tag,
type Ec2Vpc,
@@ -143,6 +146,42 @@ function InlineTagEditor({ instanceId, initialTags }: { instanceId: string; init
)
}
function InlineSecurityGroupEditor({ instanceId, vpcId, initialGroups }: {
instanceId: string; vpcId?: string; initialGroups: Ec2SecurityGroupRef[]
}) {
const qc = useQueryClient()
const [selected, setSelected] = useState(() => new Set(initialGroups.map((group) => group.id).filter(Boolean) as string[]))
const groups = useQuery({queryKey: ['ec2', 'security-groups', vpcId], queryFn: ({signal}) => listEc2SecurityGroups(vpcId, signal)})
const mutation = useMutation({
mutationFn: () => updateEc2InstanceSecurityGroups(instanceId, [...selected]),
onSuccess: () => {
void qc.invalidateQueries({queryKey: ['ec2', 'instance', instanceId]})
void qc.invalidateQueries({queryKey: ['ec2', 'instances']})
void qc.invalidateQueries({queryKey: ['cloud-resources', 'aws', 'compute']})
},
})
const compatibleGroups = (groups.data ?? []).filter((group) => !vpcId || group.vpcId === vpcId)
return <div style={{display: 'flex', flexDirection: 'column', gap: 8}}>
{compatibleGroups.map((group) => <label key={group.groupId} style={{display: 'flex', gap: 6, alignItems: 'center'}}>
<input type="checkbox" checked={selected.has(group.groupId)} onChange={(event) => {
setSelected((previous) => {
const next = new Set(previous)
if (event.target.checked) next.add(group.groupId)
else next.delete(group.groupId)
return next
})
}}/>
<span>{group.groupName} <code>{group.groupId}</code></span>
</label>)}
{groups.isError && <p className="error-text compact-text">Unable to load security groups.</p>}
{mutation.isError && <p className="error-text compact-text">{mutation.error instanceof Error ? mutation.error.message : 'Save failed'}</p>}
<button className="button primary compact" disabled={groups.isLoading || selected.size === 0 || mutation.isPending} onClick={() => mutation.mutate()}>
<Save size={13}/>{mutation.isPending ? 'Saving' : 'Save security groups'}
</button>
</div>
}
// ─── Create AMI modal (inline) ────────────────────────────────────────────────
function CreateAmiModal({ instanceId, instanceName, onClose, onCreated }: {
@@ -341,7 +380,7 @@ function DeregisterAmiConfirm({ imageId, onConfirm, onCancel, isPending }: {
function InstancePanel({ instanceId }: { instanceId: string }) {
const qc = useQueryClient()
const [tab, setTab] = useState<'details' | 'tags' | 'console'>('details')
const [tab, setTab] = useState<'details' | 'connection' | 'securityGroups' | 'tags' | 'console'>('details')
const [showAmiModal, setShowAmiModal] = useState(false)
const [showConsoleModal, setShowConsoleModal] = useState(false)
const [terminateStep, setTerminateStep] = useState<'idle' | 'confirm'>('idle')
@@ -510,6 +549,18 @@ function InstancePanel({ instanceId }: { instanceId: string }) {
>
Details
</button>
<button
className={`sns-tab${tab === 'connection' ? ' active' : ''}`}
onClick={() => setTab('connection')}
>
Connect
</button>
<button
className={`sns-tab${tab === 'securityGroups' ? ' active' : ''}`}
onClick={() => setTab('securityGroups')}
>
Security groups
</button>
<button
className={`sns-tab${tab === 'tags' ? ' active' : ''}`}
onClick={() => setTab('tags')}
@@ -602,10 +653,38 @@ function InstancePanel({ instanceId }: { instanceId: string }) {
</div>
)}
{tab === 'connection' && (() => {
const connection = inst.connection
const command = connection?.sshHost && connection.sshPort && inst.keyName
? `ssh -i ${inst.keyName}.pem -p ${connection.sshPort} root@${connection.sshHost}`
: undefined
if (state === 'stopped') {
return <p style={{ fontSize: 12, color: 'var(--text-muted)', margin: 0 }}>Start this instance before connecting over SSH.</p>
}
if (state === 'terminated') {
return <p style={{ fontSize: 12, color: 'var(--text-muted)', margin: 0 }}>Terminated instances cannot accept connections.</p>
}
if (state !== 'running') {
return <p style={{ fontSize: 12, color: 'var(--text-muted)', margin: 0 }}>SSH is unavailable while this instance is {state}.</p>
}
if (!command || connection?.containerStatus !== 'running') {
return <p style={{ fontSize: 12, color: 'var(--text-muted)', margin: 0 }}>SSH is unavailable. Backing container status: {connection?.containerStatus ?? 'unavailable'}.</p>
}
return <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<div className="meta-row"><span className="meta-label">SSH port</span><span className="meta-value">{connection.sshPort}</span></div>
<div className="meta-row"><span className="meta-label">Backing container</span><span className="meta-value">{connection.containerStatus}</span></div>
<div className="meta-row" style={{ gridColumn: '1 / -1' }}><span className="meta-label">Command</span><code style={{ overflowWrap: 'anywhere' }}>{command}</code><CopyValueButton value={command} label="SSH command" /></div>
</div>
})()}
{tab === 'tags' && (
<InlineTagEditor instanceId={instanceId} initialTags={inst.tags} />
)}
{tab === 'securityGroups' && (
<InlineSecurityGroupEditor instanceId={instanceId} vpcId={inst.vpcId} initialGroups={inst.securityGroups}/>
)}
{tab === 'console' && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<p style={{ fontSize: 12, color: 'var(--text-muted)', margin: 0 }}>