109 lines
7.5 KiB
TypeScript
109 lines
7.5 KiB
TypeScript
import {useEffect, useState} from 'react'
|
|
import {Globe, Loader2, RefreshCw, Trash2} from 'lucide-react'
|
|
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
|
import {deleteS3BucketWebsite, getS3BucketWebsite, putS3BucketWebsite} from '@/api/cloudProxyClient'
|
|
import {useAccountId} from '@/lib/accountStore'
|
|
import type {CloudResource, S3BucketWebsiteConfiguration} from '@/types/resource'
|
|
|
|
interface S3BucketWebsitePanelProps {
|
|
resource?: CloudResource
|
|
runtimeReachable: boolean
|
|
}
|
|
|
|
export function S3BucketWebsitePanel({resource, runtimeReachable}: S3BucketWebsitePanelProps) {
|
|
const queryClient = useQueryClient()
|
|
const accountId = useAccountId()
|
|
const [mode, setMode] = useState<'index' | 'redirect'>('index')
|
|
const [indexDocument, setIndexDocument] = useState('index.html')
|
|
const [errorDocument, setErrorDocument] = useState('')
|
|
const [hostName, setHostName] = useState('')
|
|
const [protocol, setProtocol] = useState<'http' | 'https' | ''>('')
|
|
const [confirmDisable, setConfirmDisable] = useState(false)
|
|
const queryKey = ['s3-bucket-website', accountId, resource?.id]
|
|
const websiteQuery = useQuery({
|
|
queryKey,
|
|
queryFn: ({signal}) => getS3BucketWebsite('aws', resource?.id ?? '', signal),
|
|
enabled: Boolean(resource) && runtimeReachable,
|
|
})
|
|
|
|
useEffect(() => {
|
|
const configuration = websiteQuery.data
|
|
if (!configuration) {
|
|
setMode('index')
|
|
setIndexDocument('index.html')
|
|
setErrorDocument('')
|
|
setHostName('')
|
|
setProtocol('')
|
|
return
|
|
}
|
|
setMode(configuration.mode)
|
|
if (configuration.mode === 'index') {
|
|
setIndexDocument(configuration.indexDocument)
|
|
setErrorDocument(configuration.errorDocument ?? '')
|
|
} else {
|
|
setHostName(configuration.hostName)
|
|
setProtocol(configuration.protocol ?? '')
|
|
}
|
|
}, [websiteQuery.data, resource?.id])
|
|
|
|
const saveMutation = useMutation({
|
|
mutationFn: (configuration: S3BucketWebsiteConfiguration) => putS3BucketWebsite('aws', resource?.id ?? '', configuration),
|
|
onSuccess: () => {
|
|
void queryClient.invalidateQueries({queryKey})
|
|
},
|
|
})
|
|
const deleteMutation = useMutation({
|
|
mutationFn: () => deleteS3BucketWebsite('aws', resource?.id ?? ''),
|
|
onSuccess: () => {
|
|
setConfirmDisable(false)
|
|
void queryClient.setQueryData<S3BucketWebsiteConfiguration | null>(queryKey, null)
|
|
void queryClient.invalidateQueries({queryKey})
|
|
},
|
|
})
|
|
|
|
if (!resource || resource.service !== 'storage' || resource.type !== 'bucket') return null
|
|
const endpoint = `http://${resource.name}.s3-website-${resource.region ?? 'us-east-1'}.localhost:4566`
|
|
const configuration = mode === 'index'
|
|
? {mode, indexDocument: indexDocument.trim(), errorDocument: errorDocument.trim() || null}
|
|
: {mode, hostName: hostName.trim(), protocol: protocol || null}
|
|
const canSave = !saveMutation.isPending && (mode === 'index' ? Boolean(configuration.indexDocument) : Boolean(configuration.hostName))
|
|
|
|
return <section className="table-panel">
|
|
<div className="dynamic-stage-header">
|
|
<div>
|
|
<p className="eyebrow">Static website hosting</p>
|
|
<h3><Globe size={15}/> {websiteQuery.data ? 'Enabled' : 'Not configured'}</h3>
|
|
<p className="muted compact-text">Website endpoint: <a href={endpoint} target="_blank" rel="noreferrer"><code>{endpoint}</code></a></p>
|
|
</div>
|
|
<button className="icon-btn" type="button" aria-label="Refresh website configuration" onClick={() => websiteQuery.refetch()} disabled={websiteQuery.isFetching}>
|
|
{websiteQuery.isFetching ? <Loader2 size={13}/> : <RefreshCw size={13}/>}
|
|
</button>
|
|
</div>
|
|
{websiteQuery.isLoading && <p className="muted compact-text">Loading website configuration...</p>}
|
|
{websiteQuery.isError && <p className="error-text compact-text">{websiteQuery.error instanceof Error ? websiteQuery.error.message : 'Unable to load website configuration.'}</p>}
|
|
<form className="resource-create-inline" onSubmit={(event) => {
|
|
event.preventDefault()
|
|
if (canSave) saveMutation.mutate(configuration)
|
|
}}>
|
|
<label className="dynamic-field" htmlFor="s3-website-mode">
|
|
<span>Hosting mode</span>
|
|
<select id="s3-website-mode" className="input" value={mode} onChange={(event) => setMode(event.target.value as 'index' | 'redirect')} disabled={saveMutation.isPending}>
|
|
<option value="index">Index and error documents</option>
|
|
<option value="redirect">Redirect all requests</option>
|
|
</select>
|
|
</label>
|
|
{mode === 'index' ? <>
|
|
<label className="dynamic-field" htmlFor="s3-website-index-document"><span>Index document</span><input id="s3-website-index-document" className="input" value={indexDocument} onChange={(event) => setIndexDocument(event.target.value)} placeholder="index.html" required disabled={saveMutation.isPending}/></label>
|
|
<label className="dynamic-field" htmlFor="s3-website-error-document"><span>Error document</span><input id="s3-website-error-document" className="input" value={errorDocument} onChange={(event) => setErrorDocument(event.target.value)} placeholder="error.html" disabled={saveMutation.isPending}/><small>Optional.</small></label>
|
|
</> : <>
|
|
<label className="dynamic-field" htmlFor="s3-website-redirect-host"><span>Redirect host name</span><input id="s3-website-redirect-host" className="input" value={hostName} onChange={(event) => setHostName(event.target.value)} placeholder="www.example.com" required disabled={saveMutation.isPending}/></label>
|
|
<label className="dynamic-field" htmlFor="s3-website-redirect-protocol"><span>Redirect protocol</span><select id="s3-website-redirect-protocol" className="input" value={protocol} onChange={(event) => setProtocol(event.target.value as 'http' | 'https' | '')} disabled={saveMutation.isPending}><option value="">Preserve request protocol</option><option value="http">HTTP</option><option value="https">HTTPS</option></select></label>
|
|
</>}
|
|
<button className="button primary" type="submit" disabled={!canSave}>{saveMutation.isPending ? <Loader2 size={13}/> : null}{websiteQuery.data ? 'Save changes' : 'Enable website hosting'}</button>
|
|
</form>
|
|
{saveMutation.isError && <p className="error-text compact-text">{saveMutation.error instanceof Error ? saveMutation.error.message : 'Unable to save website configuration.'}</p>}
|
|
{websiteQuery.data && (confirmDisable ? <div className="apigateway-route-confirm"><span>Disable static website hosting?</span><button className="button danger compact" type="button" disabled={deleteMutation.isPending} onClick={() => deleteMutation.mutate()}>{deleteMutation.isPending ? 'Disabling' : 'Disable'}</button><button className="button compact" type="button" disabled={deleteMutation.isPending} onClick={() => setConfirmDisable(false)}>Cancel</button></div> : <button className="button danger compact" type="button" onClick={() => setConfirmDisable(true)}><Trash2 size={13}/> Disable website hosting</button>)}
|
|
{deleteMutation.isError && <p className="error-text compact-text">{deleteMutation.error instanceof Error ? deleteMutation.error.message : 'Unable to disable website hosting.'}</p>}
|
|
</section>
|
|
}
|