small feat: svg animation for servers, codebuild, codePipleline etc...

This commit is contained in:
Zakaria
2026-10-15 10:25:16 -04:00
parent ca1e845cb6
commit 668911ed6e
29 changed files with 10034 additions and 117 deletions
@@ -1,11 +1,14 @@
import { useEffect, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import ec2CpuAnimation from '../../../../svgAsset/animated-cpu.svg'
import { CopyValueButton } from './CopyValueButton'
import { ServerLaunchProgress } from './ServerLaunchProgress'
import {
AlertTriangle,
Cpu,
HardDrive,
Loader2,
Network,
Play,
Plus,
Power,
@@ -13,6 +16,7 @@ import {
RotateCcw,
Save,
Search,
Server,
Terminal,
Trash2,
X,
@@ -395,7 +399,11 @@ function InstancePanel({ instanceId }: { instanceId: string }) {
queryFn: ({ signal }) => describeEc2Instance(instanceId, signal),
refetchInterval: (query) => {
const state = query.state.data?.state
return state === 'pending' || state === 'stopping' || state === 'shutting-down' ? 1_500 : false
if (state === 'pending' || state === 'stopping' || state === 'shutting-down') {
return 1_500
}
const connection = query.state.data?.connection
return connection?.launchStatus === 'in-progress' || connection?.sshStatus === 'initializing' ? 5_000 : false
},
})
@@ -675,6 +683,9 @@ function InstancePanel({ instanceId }: { instanceId: string }) {
const failure = typeof connection.failure === 'string'
? connection.failure
: connection.failure?.message
const launchPending = connection.launchStatus === 'in-progress'
const sshPending = connection.sshStatus === 'initializing'
const keyInjected = connection.keyInjectionStatus === 'succeeded' || connection.keyInjectionStatus === 'ok'
return <div className="ec2-connect-panel">
{!canCheckSsh && <div className="ec2-connection-notice">
{state === 'terminated'
@@ -685,17 +696,17 @@ function InstancePanel({ instanceId }: { instanceId: string }) {
</div>}
<div className="ec2-readiness-grid">
<div className="ec2-readiness-card">
<span>Launch</span>
<div className="ec2-readiness-heading"><span>Launch</span><LaunchGear active={launchPending}/></div>
<strong className={connection.launchStatus === 'failed' ? 'error' : ''}>{connection.launchStatus ?? 'unknown'}</strong>
<small>{connection.selectedImageReference ?? 'No selected image recorded'}</small>
</div>
<div className="ec2-readiness-card">
<span>SSH readiness</span>
<div className="ec2-readiness-heading"><span>SSH readiness</span><SshHeartbeat active={sshPending}/></div>
<strong className={connection.sshStatus === 'impaired' ? 'error' : ''}>{connection.sshStatus ?? 'unknown'}</strong>
<small>{connection.sshStatusMessage ?? 'No readiness result recorded'}</small>
</div>
<div className="ec2-readiness-card">
<span>Key injection</span>
<div className="ec2-readiness-heading"><span>Key injection</span>{keyInjected && <KeyInjectionReady/>}</div>
<strong>{connection.keyInjectionStatus ?? (inst.keyName ? 'unknown' : 'not requested')}</strong>
<small>{inst.keyName ?? 'No key pair selected'}</small>
</div>
@@ -771,6 +782,29 @@ function InstancePanel({ instanceId }: { instanceId: string }) {
)
}
function LaunchGear({active}: {active: boolean}) {
return <img className={`ec2-launch-cpu${active ? ' active' : ''}`} src={ec2CpuAnimation} alt="Animated instance CPU"/>
}
function SshHeartbeat({active}: {active: boolean}) {
return <svg className={`ec2-ssh-heartbeat${active ? ' active' : ''}`} viewBox="0 0 100 32" aria-hidden="true">
<path d="M2 17h16l6-11 9 22 10-17 6 6h49"/>
</svg>
}
function KeyInjectionReady() {
return <svg className="ec2-key-ready" viewBox="0 0 150 150" aria-label="Key injected">
<rect x="12" y="13" width="126" height="125" rx="9" fill="#f4f1f7" stroke="#19152d" strokeWidth="3"/>
<path d="M12 33V22a9 9 0 0 1 9-9h108a9 9 0 0 1 9 9v11Z" fill="#393044"/>
<circle cx="24" cy="23" r="3" fill="#ff5a36"/><circle cx="35" cy="23" r="3" fill="#ffc33a"/><circle cx="46" cy="23" r="3" fill="#62c9aa"/>
<path d="M63 62v-6c0-7 5-11 12-11s12 4 12 11v6" fill="none" stroke="#19152d" strokeWidth="2.6"/>
<path d="M57 62h36v17c0 6-5 10-10 10H67c-5 0-10-4-10-10Z" fill="#ffc33a" stroke="#19152d" strokeWidth="2.6"/>
<path d="M75 71v7" stroke="#19152d" strokeWidth="2.6" strokeLinecap="round"/>
<rect x="34" y="100" width="79" height="24" rx="12" fill="#45b5fa" stroke="#19152d" strokeWidth="2.6"/>
<circle cx="51" cy="112" r="1.8" fill="#19152d"/><circle cx="66" cy="112" r="1.8" fill="#19152d"/><circle cx="81" cy="112" r="1.8" fill="#19152d"/><circle cx="96" cy="112" r="1.8" fill="#19152d"/>
</svg>
}
// ─── AMI panel ────────────────────────────────────────────────────────────────
function AmiPanel({ imageId }: { imageId: string }) {
@@ -929,6 +963,7 @@ export function LaunchInstanceForm({
const [createdKeyMaterial, setCreatedKeyMaterial] = useState<{ name: string; material: string }>()
const [rootVolumeSize, setRootVolumeSize] = useState('20')
const [rootVolumeType, setRootVolumeType] = useState('gp3')
const [userData, setUserData] = useState('')
const [subnetId, setSubnetId] = useState('')
const [selectedSgs, setSelectedSgs] = useState<string[]>([])
const [showCreateSg, setShowCreateSg] = useState(false)
@@ -1049,6 +1084,7 @@ export function LaunchInstanceForm({
securityGroupIds: selectedSgs.length ? selectedSgs.join(',') : undefined,
rootVolumeSize,
rootVolumeType,
userData: userData || undefined,
})
}
@@ -1120,17 +1156,22 @@ export function LaunchInstanceForm({
]
return (
<form className="launch-form" aria-label="Launch instance wizard" onSubmit={(event) => { event.preventDefault(); if (step === 4) launch() }}>
<div className="sns-tabs" aria-label="Launch steps" style={{ gridColumn: '1 / -1', overflowX: 'auto' }}>
<form className="launch-form ec2-launch-form" aria-label="Launch instance wizard" onSubmit={(event) => { event.preventDefault(); if (step === 4) launch() }}>
<header className="launch-form-header">
<div><p className="eyebrow">EC2 compute</p><h3>Launch an instance</h3><p className="muted compact-text">Choose an image, configure access and networking, then review the instance before launch.</p></div>
<span>4-step setup</span>
</header>
<nav className="launch-stepper" aria-label="Launch steps">
{steps.map((item) => (
<button key={item.number} type="button" className={`sns-tab${step === item.number ? ' active' : ''}`} onClick={() => item.number < step && setStep(item.number)} disabled={item.number > step}>
{item.number}. {item.label}
<button key={item.number} type="button" className={step === item.number ? 'active' : ''} onClick={() => item.number < step && setStep(item.number)} disabled={item.number > step}>
<b>{item.number}</b><span>{item.label}</span>
</button>
))}
</div>
</nav>
{step === 1 && <>
<div className="launch-form-group">Name and AMI</div>
{step === 1 && <section className="launch-form-section">
<div className="launch-section-title"><Server size={17}/><div><h4>Instance identity</h4><p>Name the instance and select its operating system image.</p></div></div>
<div className="launch-form-grid">
<label className="launch-field launch-field--span">
<span>Instance name <em className="field-required">*</em></span>
<input className="input" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. my-web-server" />
@@ -1188,10 +1229,12 @@ export function LaunchInstanceForm({
</>}
</div>
</div>
</>}
</div>
</section>}
{step === 2 && <>
<div className="launch-form-group">Instance type and key pair</div>
{step === 2 && <section className="launch-form-section">
<div className="launch-section-title"><Cpu size={17}/><div><h4>Compute and access</h4><p>Choose the instance capacity and SSH credentials.</p></div></div>
<div className="launch-form-grid">
<label className="launch-field">
<span>Instance type <em className="field-required">*</em></span>
<select className="input" value={instanceType} onChange={(e) => setInstanceType(e.target.value)}>
@@ -1201,16 +1244,16 @@ export function LaunchInstanceForm({
</label>
<div className="launch-field launch-field--span">
<span>Key pair</span>
<div className="field-row" style={{ marginTop: 6 }}>
<div className="launch-choice-row">
<label><input type="radio" checked={keyPairMode === 'existing'} onChange={() => setKeyPairMode('existing')} /> Choose existing</label>
<label><input type="radio" checked={keyPairMode === 'new'} onChange={() => { setKeyPairMode('new'); setKeyName('') }} /> Create new</label>
</div>
{keyPairMode === 'existing' ? (
<select className="input" style={{ marginTop: 8 }} value={keyName} onChange={(e) => setKeyName(e.target.value)}>
<select className="input" value={keyName} onChange={(e) => setKeyName(e.target.value)}>
<option value="">No key pair</option>
{(keysQ.data ?? []).map((keyPair: Ec2KeyPair) => <option key={keyPair.keyPairId} value={keyPair.keyName}>{keyPair.keyName}</option>)}
</select>
) : <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 8 }}>
) : <div className="launch-key-create">
<input className="input" value={newKeyName} onChange={(e) => { setNewKeyName(e.target.value); setKeyName('') }} placeholder="Key pair name" />
<select className="input" value={newKeyType} onChange={(e) => setNewKeyType(e.target.value as 'rsa' | 'ed25519')}><option value="rsa">RSA</option><option value="ed25519">ED25519</option></select>
<button type="button" className="button" disabled={!newKeyName.trim() || createKeyMut.isPending} onClick={() => createKeyMut.mutate()}>{createKeyMut.isPending ? 'Creating…' : 'Create key pair'}</button>
@@ -1219,10 +1262,12 @@ export function LaunchInstanceForm({
{createKeyMut.error instanceof Error && <p className="form-error" style={{ marginTop: 8 }}>{createKeyMut.error.message}</p>}
{createdKeyMaterial && <p className="muted" style={{ margin: '8px 0 0' }}>The private key is available only from this creation response. Download it before leaving this wizard.</p>}
</div>
</>}
</div>
</section>}
{step === 3 && <>
<div className="launch-form-group">Network and security</div>
{step === 3 && <section className="launch-form-section">
<div className="launch-section-title"><Network size={17}/><div><h4>Network and security</h4><p>Place the instance in a subnet and control inbound access.</p></div></div>
<div className="launch-form-grid">
<label className="launch-field launch-field--span">
<span>Subnet</span>
<select className="input" value={subnetId} onChange={(e) => setSubnetId(e.target.value)}>
@@ -1360,10 +1405,12 @@ export function LaunchInstanceForm({
</div>
)}
</div>
</>}
</div>
</section>}
{step === 4 && <>
<div className="launch-form-group">Storage and review</div>
{step === 4 && <section className="launch-form-section">
<div className="launch-section-title"><HardDrive size={17}/><div><h4>Storage and review</h4><p>Configure the root disk, bootstrap script, and verify the launch request.</p></div></div>
<div className="launch-form-grid">
<label className="launch-field">
<span>Root volume size (GiB)</span>
<input className="input" type="number" min="1" value={rootVolumeSize} onChange={(event) => setRootVolumeSize(event.target.value)} />
@@ -1379,9 +1426,9 @@ export function LaunchInstanceForm({
<option value="sc1">Cold HDD (sc1)</option>
</select>
</label>
<div className="launch-field launch-field--span">
<div className="launch-field launch-field--span launch-review">
<span>Review: fields sent to RunInstances</span>
<div className="meta-grid" style={{ marginTop: 8 }}>
<div className="meta-grid">
<div className="meta-row"><span className="meta-label">Name</span><span className="meta-value">{name}</span></div>
<div className="meta-row"><span className="meta-label">AMI</span><span className="meta-value">{selectedAmi?.name || imageId} ({imageId})</span></div>
<div className="meta-row"><span className="meta-label">Instance type</span><span className="meta-value">{instanceType}</span></div>
@@ -1389,14 +1436,22 @@ export function LaunchInstanceForm({
<div className="meta-row"><span className="meta-label">Subnet</span><span className="meta-value">{selectedSubnet ? `${selectedSubnet.availabilityZone} (${subnetId})` : 'Not sent'}</span></div>
<div className="meta-row"><span className="meta-label">Security groups</span><span className="meta-value">{selectedGroups.length ? selectedGroups.map((group) => group.groupName || group.groupId).join(', ') : 'Not sent'}</span></div>
<div className="meta-row"><span className="meta-label">Root volume</span><span className="meta-value">{rootVolumeSize} GiB {rootVolumeType}, delete on termination</span></div>
<div className="meta-row"><span className="meta-label">User data</span><span className="meta-value">{userData ? 'Shell script or cloud-init supplied' : 'Not sent'}</span></div>
</div>
</div>
</>}
<label className="launch-field launch-field--span">
<span>User data</span>
<textarea className="textarea code-textarea small" value={userData} onChange={(event) => setUserData(event.target.value)} placeholder={'#!/bin/bash\napt-get update\napt-get upgrade -y\napt-get install -y curl git'} rows={7}/>
<small>Runs once after the instance starts. Use a shell script or cloud-init document, matching the AWS RunInstances UserData field.</small>
<input type="file" accept=".sh,.txt,.yaml,.yml,.cfg,text/plain,text/x-shellscript" onChange={async (event) => { const file = event.currentTarget.files?.[0]; if (file) setUserData(await file.text()); event.currentTarget.value = '' }}/>
</label>
</div>
</section>}
{stepError && <div className="form-error launch-field--span">{stepError}</div>}
{createMut.error instanceof Error && <div className="form-error launch-field--span">{createMut.error.message}</div>}
{createMut.isPending && <div className="launch-field--span"><ServerLaunchProgress name={name} description="Creating the instance and preparing its local runtime."/></div>}
<div className="launch-form-actions" style={{ gridColumn: '1 / -1' }}>
<div className="launch-form-actions">
<button type="button" className="button" onClick={step === 1 ? onCancel : () => { setStep((current) => (current - 1) as LaunchStep); setStepError('') }}>{step === 1 ? 'Cancel' : 'Back'}</button>
{step < 4 ? <button type="button" className="button primary" onClick={nextStep}>Next</button> : <button type="button" className="button primary" disabled={createMut.isPending} onClick={launch}><Plus size={14}/>{createMut.isPending ? 'Launching…' : 'Launch instance'}</button>}
</div>