feat: MSK, MSK CONNECT and SES
This commit is contained in:
@@ -9,6 +9,7 @@ import {formatBytes} from "@/lib/format";
|
||||
import {CopyValueButton} from "@/components/CopyValueButton";
|
||||
import {rebootCloudResource, startCloudResource, stopCloudResource} from "@/api/cloudProxyClient";
|
||||
import {cloudQueryKeys} from "@/api/queries/cloudQueries";
|
||||
import {decodeRawMime, htmlMimePart, inlineMimeImages, parseMimeAttachments} from '@/lib/emailMime'
|
||||
|
||||
interface ResourceInspectorProps {
|
||||
resource?: CloudResource;
|
||||
@@ -217,7 +218,12 @@ function EmailInspector({resource}: {resource: CloudResource}) {
|
||||
const textBody = getStringMetadata(resource.metadata.textBody)
|
||||
const htmlBody = getStringMetadata(resource.metadata.htmlBody)
|
||||
const rawData = getStringMetadata(resource.metadata.rawData)
|
||||
const hasPreview = Boolean(htmlBody || textBody)
|
||||
const rawMime = rawData ? decodeRawMime(rawData) : null
|
||||
const attachments = rawMime ? parseMimeAttachments(rawMime) : []
|
||||
const inlineImages = rawMime ? inlineMimeImages(rawMime) : {}
|
||||
const rawHtmlBody = rawMime ? htmlMimePart(rawMime) : null
|
||||
const previewHtml = htmlBody ?? rawHtmlBody
|
||||
const hasPreview = Boolean(previewHtml || textBody)
|
||||
const activeTab = tab === 'preview' && !hasPreview ? (rawData ? 'raw' : 'text') : tab
|
||||
|
||||
return (
|
||||
@@ -235,17 +241,18 @@ function EmailInspector({resource}: {resource: CloudResource}) {
|
||||
<InspectorItem label="Captured At" value={resource.createdAt ?? '-'} />
|
||||
</div>
|
||||
<section className="inspector-section">
|
||||
<div className="drawer-tabs" style={{marginBottom: 10}}>
|
||||
<div className="drawer-tabs" style={{marginBottom: 10}}>
|
||||
<button className={`drawer-tab ${activeTab === 'preview' ? 'active' : ''}`} disabled={!hasPreview} onClick={() => setTab('preview')}>Preview</button>
|
||||
<button className={`drawer-tab ${activeTab === 'text' ? 'active' : ''}`} disabled={!textBody} onClick={() => setTab('text')}>Text</button>
|
||||
<button className={`drawer-tab ${activeTab === 'raw' ? 'active' : ''}`} disabled={!rawData} onClick={() => setTab('raw')}>Raw</button>
|
||||
<button className={`drawer-tab ${activeTab === 'raw' ? 'active' : ''}`} disabled={!rawData} onClick={() => setTab('raw')}>Raw</button>
|
||||
{rawMime && <button className="button compact" type="button" onClick={() => downloadRawEmail(resource.id, rawMime)}>Download MIME</button>}
|
||||
</div>
|
||||
{activeTab === 'preview' && htmlBody ? (
|
||||
{activeTab === 'preview' && previewHtml ? (
|
||||
<iframe
|
||||
title="Email HTML preview"
|
||||
sandbox=""
|
||||
referrerPolicy="no-referrer"
|
||||
srcDoc={safeEmailDocument(htmlBody)}
|
||||
srcDoc={safeEmailDocument(previewHtml, inlineImages)}
|
||||
style={{width: '100%', minHeight: 260, border: '1px solid var(--border)', borderRadius: 4, background: '#fff'}}
|
||||
/>
|
||||
) : activeTab === 'preview' ? (
|
||||
@@ -253,15 +260,32 @@ function EmailInspector({resource}: {resource: CloudResource}) {
|
||||
) : activeTab === 'text' ? (
|
||||
<pre className="metadata-block" style={{whiteSpace: 'pre-wrap', margin: 0}}>{textBody ?? 'No text body captured.'}</pre>
|
||||
) : (
|
||||
<pre className="metadata-block" style={{whiteSpace: 'pre-wrap', wordBreak: 'break-all', margin: 0}}>{rawData ?? 'No raw MIME data captured.'}</pre>
|
||||
)}
|
||||
</section>
|
||||
</aside>
|
||||
<pre className="metadata-block" style={{whiteSpace: 'pre-wrap', wordBreak: 'break-all', margin: 0}}>{rawMime ?? 'No raw MIME data captured.'}</pre>
|
||||
)}
|
||||
</section>
|
||||
{attachments.length > 0 && <section className="inspector-section">
|
||||
<p className="metric-label">MIME attachments</p>
|
||||
{attachments.map((attachment, index) => <div className="apigateway-route-item" key={`${attachment.filename}-${index}`}>
|
||||
<div><code>{attachment.filename ?? '(unnamed MIME part)'}</code><small>{attachment.contentType} · {attachment.disposition}</small>{attachment.contentId && <small>Content-ID: {attachment.contentId} ({attachment.disposition === 'inline' ? 'inline' : 'referenced part'})</small>}</div>
|
||||
</div>)}
|
||||
</section>}
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
function safeEmailDocument(html: string): string {
|
||||
return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'"></head><body>${html}</body></html>`
|
||||
function downloadRawEmail(id: string, rawData: string): void {
|
||||
const url = URL.createObjectURL(new Blob([rawData], {type: 'message/rfc822'}))
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `${id || 'message'}.eml`
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
|
||||
function safeEmailDocument(html: string, inlineImages: Record<string, string> = {}): string {
|
||||
const withInlineImages = html.replace(/cid:([^"'\s>]+)/gi, (reference, contentId: string) => inlineImages[contentId] ?? reference)
|
||||
return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'"></head><body>${withInlineImages}</body></html>`
|
||||
}
|
||||
|
||||
function ProviderDatabaseSection({ cloud }: { cloud: string }) {
|
||||
|
||||
Reference in New Issue
Block a user