feat: MSK, MSK CONNECT and SES
This commit is contained in:
@@ -41,13 +41,49 @@ export function SqsQueuePanel({resource, runtimeReachable}: {resource?: CloudRes
|
||||
</form>
|
||||
<div className="dynamic-stage-header"><div><p className="eyebrow">Receive</p><h3>Received messages</h3></div><button className="button" type="button" disabled={!runtimeReachable || receive.isPending} onClick={() => receive.mutate()}>{receive.isPending ? 'Polling' : 'Poll for messages'}</button></div>
|
||||
{messages.length === 0 && <p className="muted compact-text">Poll to receive up to 10 messages. Leaving this panel or waiting for the visibility timeout does not delete them.</p>}
|
||||
{messages.map((message) => <article className="apigateway-route-item" key={message.receiptHandle}><div className="apigateway-route-select"><code>{message.messageId}</code><pre>{message.body}</pre><small>Receive count: {message.attributes.ApproximateReceiveCount ?? '-'} · Visibility timeout: {String(resource.metadata.visibilityTimeout ?? 30)}s</small><details><summary>Message details</summary><pre>{JSON.stringify({attributes: message.attributes, messageAttributes: message.messageAttributes, receiptHandle: message.receiptHandle}, null, 2)}</pre></details></div><button className="icon-btn danger" type="button" aria-label={`Delete message ${message.messageId}`} disabled={remove.isPending} onClick={() => remove.mutate(message)}><Trash2 size={14}/></button></article>)}
|
||||
{messages.map((message) => <article className="apigateway-route-item sqs-received-message" key={message.receiptHandle}><div className="apigateway-route-select"><code>{message.messageId}</code><SqsMessageBody body={message.body}/><small>Receive count: {message.attributes.ApproximateReceiveCount ?? '-'} · Visibility timeout: {String(resource.metadata.visibilityTimeout ?? 30)}s</small><details><summary>Message details</summary><pre className="sqs-message-body">{JSON.stringify({attributes: message.attributes, messageAttributes: message.messageAttributes, receiptHandle: message.receiptHandle}, null, 2)}</pre></details></div><button className="icon-btn danger" type="button" aria-label={`Delete message ${message.messageId}`} disabled={remove.isPending} onClick={() => remove.mutate(message)}><Trash2 size={14}/></button></article>)}
|
||||
<section className="apigateway-route-explorer"><p className="eyebrow">Queue management</p>{!purgeConfirm ? <button className="button danger" type="button" onClick={() => setPurgeConfirm(true)}>Purge queue</button> : <div className="apigateway-route-confirm"><span>Purge approximately {String(resource.metadata.approximateMessages ?? 0)} visible messages?</span><button className="button danger compact" type="button" disabled={purge.isPending} onClick={() => purge.mutate()}>Confirm purge</button><button className="button compact" type="button" onClick={() => setPurgeConfirm(false)}>Cancel</button></div>}</section>
|
||||
<section className="apigateway-route-explorer"><p className="eyebrow">Experimental: dead-letter queues</p><p className="muted compact-text">DLQ redrive threshold behavior is not fully AWS-compatible in this runtime. Use only for local experiments.</p><div className="dynamic-form"><label className="dynamic-field"><span>Target DLQ</span><select className="input" value={dlqArn} onChange={(event) => setDlqArn(event.target.value)}><option value="">Select a matching queue</option>{queues.map((queue) => <option key={queue.id} value={String(queue.metadata.arn)}>{queue.name}</option>)}</select></label><label className="dynamic-field"><span>Max receive count</span><input className="input" type="number" min="1" max="1000" value={maxReceiveCount} onChange={(event) => setMaxReceiveCount(event.target.value)}/></label><button className="button" type="button" disabled={!dlqArn || configureDlq.isPending} onClick={() => configureDlq.mutate()}>Save experimental DLQ</button></div><div className="dynamic-form"><label className="dynamic-field"><span>Move destination</span><select className="input" value={moveDestination} onChange={(event) => setMoveDestination(event.target.value)}><option value="">Original source queue</option>{queues.map((queue) => <option key={queue.id} value={String(queue.metadata.arn)}>{queue.name}</option>)}</select></label><button className="button" type="button" disabled={move.isPending} onClick={() => move.mutate()}>Move DLQ messages</button>{move.data && <code>{move.data.taskHandle}</code>}</div></section>
|
||||
{error instanceof Error && <p className="error-text compact-text">{error.message}</p>}
|
||||
</section>
|
||||
}
|
||||
|
||||
function SqsMessageBody({body}: {body: string}) {
|
||||
const notification = snsNotification(body)
|
||||
if (!notification) return <pre className="sqs-message-body">{body}</pre>
|
||||
return <div className="sqs-sns-notification"><p className="metric-label">SNS notification envelope</p><JsonCode value={notification.envelope}/><p className="metric-label">SNS message payload</p>{notification.payload === null ? <pre className="sqs-message-body">{notification.message}</pre> : <JsonCode value={notification.payload}/>}</div>
|
||||
}
|
||||
|
||||
function snsNotification(body: string): {envelope: Record<string, unknown>; message: string; payload: unknown | null} | null {
|
||||
try {
|
||||
const parsed = JSON.parse(body) as Record<string, unknown>
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || parsed.Type !== 'Notification' || typeof parsed.Message !== 'string') return null
|
||||
const {Message, ...envelope} = parsed
|
||||
try {
|
||||
return {envelope, message: Message, payload: JSON.parse(Message)}
|
||||
} catch {
|
||||
return {envelope, message: Message, payload: null}
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function JsonCode({value}: {value: unknown}) {
|
||||
const text = JSON.stringify(value, null, 2)
|
||||
const tokens: Array<{value: string; kind?: string}> = []
|
||||
const expression = /("(?:\\.|[^"\\])*")(?=\s*:)|(\btrue\b|\bfalse\b|\bnull\b)|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|("(?:\\.|[^"\\])*")/g
|
||||
let cursor = 0
|
||||
for (const match of text.matchAll(expression)) {
|
||||
const index = match.index ?? 0
|
||||
if (index > cursor) tokens.push({value: text.slice(cursor, index)})
|
||||
tokens.push({value: match[0], kind: match[1] ? 'key' : match[2] ? 'literal' : match[3] ? 'number' : 'string'})
|
||||
cursor = index + match[0].length
|
||||
}
|
||||
if (cursor < text.length) tokens.push({value: text.slice(cursor)})
|
||||
return <pre className="sqs-message-body sqs-json-code">{tokens.map((token, index) => <span className={token.kind ? `json-${token.kind}` : undefined} key={index}>{token.value}</span>)}</pre>
|
||||
}
|
||||
|
||||
function parseAttributes(value: string): Record<string, SqsMessageAttribute> | undefined {
|
||||
if (!value.trim()) return undefined
|
||||
const parsed = JSON.parse(value) as Record<string, SqsMessageAttribute>
|
||||
|
||||
Reference in New Issue
Block a user