149 lines
4.6 KiB
TypeScript
149 lines
4.6 KiB
TypeScript
import { Box, Flex, Image, Link, Text } from '@traefiklabs/faency'
|
|
import { useMemo, useEffect, useState } from 'react'
|
|
import { Helmet } from 'react-helmet-async'
|
|
import { useParams } from 'react-router-dom'
|
|
|
|
import verifySignature from '../../utils/workers/scriptVerification'
|
|
|
|
import { PUBLIC_KEY } from './constants'
|
|
|
|
import { SpinnerLoader } from 'components/SpinnerLoader'
|
|
import { useIsDarkMode } from 'hooks/use-theme'
|
|
import { TopNav } from 'layout/Navigation'
|
|
|
|
const SCRIPT_URL = 'https://assets.traefik.io/hub-ui-demo.js'
|
|
|
|
// Module-level cache to persist across component mount/unmount
|
|
let cachedBlobUrl: string | null = null
|
|
|
|
// Export a function to reset the cache (for testing)
|
|
export const resetCache = () => {
|
|
cachedBlobUrl = null
|
|
}
|
|
|
|
const HubDashboard = ({ path }: { path: string }) => {
|
|
const isDarkMode = useIsDarkMode()
|
|
const [scriptError, setScriptError] = useState<boolean | undefined>(undefined)
|
|
const [signatureVerified, setSignatureVerified] = useState(false)
|
|
const [verificationInProgress, setVerificationInProgress] = useState(false)
|
|
const [scriptBlobUrl, setScriptBlobUrl] = useState<string | null>(null)
|
|
|
|
const { id } = useParams()
|
|
|
|
const usedPath = useMemo(() => {
|
|
if (path?.includes(':id')) {
|
|
const splitted = path.split(':')
|
|
return `${splitted[0]}/${id}`
|
|
}
|
|
|
|
return path
|
|
}, [id, path])
|
|
|
|
useEffect(() => {
|
|
const verifyAndLoadScript = async () => {
|
|
setVerificationInProgress(true)
|
|
|
|
try {
|
|
const { verified, scriptContent: content } = await verifySignature(SCRIPT_URL, `${SCRIPT_URL}.sig`, PUBLIC_KEY)
|
|
|
|
if (!verified || !content) {
|
|
setScriptError(true)
|
|
setVerificationInProgress(false)
|
|
} else {
|
|
setScriptError(false)
|
|
|
|
const blob = new Blob([content], { type: 'application/javascript' })
|
|
cachedBlobUrl = URL.createObjectURL(blob)
|
|
|
|
setScriptBlobUrl(cachedBlobUrl)
|
|
setSignatureVerified(true)
|
|
setVerificationInProgress(false)
|
|
}
|
|
} catch {
|
|
setScriptError(true)
|
|
setVerificationInProgress(false)
|
|
}
|
|
}
|
|
|
|
if (!cachedBlobUrl) {
|
|
verifyAndLoadScript()
|
|
} else {
|
|
setScriptBlobUrl(cachedBlobUrl)
|
|
setSignatureVerified(true)
|
|
}
|
|
}, [])
|
|
|
|
if (scriptError && !verificationInProgress) {
|
|
return (
|
|
<Flex gap={4} align="center" justify="center" direction="column" css={{ width: '100%', mt: '$8', maxWidth: 690 }}>
|
|
<Image src="/img/gopher-something-went-wrong.png" width={400} />
|
|
<Text css={{ fontSize: 24, fontWeight: '$semiBold' }}>Oops! We couldn't load the demo content.</Text>
|
|
<Text size={6} css={{ textAlign: 'center', lineHeight: 1.4 }}>
|
|
Don't worry — you can still learn more about{' '}
|
|
<Text size={6} css={{ fontWeight: '$semiBold' }}>
|
|
Traefik Hub API Management
|
|
</Text>{' '}
|
|
on our{' '}
|
|
<Link
|
|
href="https://traefik.io/traefik-hub?utm_campaign=hub-demo&utm_source=proxy-button&utm_medium=in-product"
|
|
target="_blank"
|
|
>
|
|
website
|
|
</Link>{' '}
|
|
or in our{' '}
|
|
<Link href="https://doc.traefik.io/traefik-hub/" target="_blank">
|
|
documentation
|
|
</Link>
|
|
.
|
|
</Text>
|
|
</Flex>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Box css={{ width: '100%' }}>
|
|
<Helmet>
|
|
<title>Hub Demo - Traefik Proxy</title>
|
|
<meta
|
|
httpEquiv="Content-Security-Policy"
|
|
content="script-src 'self' blob: 'unsafe-inline'; object-src 'none'; base-uri 'self';"
|
|
/>
|
|
{signatureVerified && scriptBlobUrl && <script src={scriptBlobUrl} type="module"></script>}
|
|
</Helmet>
|
|
<Box
|
|
css={{
|
|
margin: 'auto',
|
|
position: 'relative',
|
|
maxWidth: '1334px',
|
|
'@media (max-width:1440px)': {
|
|
maxWidth: '100%',
|
|
},
|
|
}}
|
|
>
|
|
<TopNav noHubButton css={{ position: 'absolute', top: 125, right: '$5' }} />
|
|
</Box>
|
|
{verificationInProgress ? (
|
|
<Box css={{ width: '100%', justifyItems: 'center', mt: '$8' }}>
|
|
<SpinnerLoader size={48} />
|
|
</Box>
|
|
) : (
|
|
<hub-ui-demo-app
|
|
key={usedPath}
|
|
path={usedPath}
|
|
theme={isDarkMode ? 'dark' : 'light'}
|
|
baseurl="#/hub-dashboard"
|
|
containercss={JSON.stringify({
|
|
maxWidth: '1334px',
|
|
'@media (max-width:1440px)': {
|
|
maxWidth: '100%',
|
|
},
|
|
margin: 'auto',
|
|
marginTop: '90px',
|
|
})}
|
|
></hub-ui-demo-app>
|
|
)}
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
export default HubDashboard
|