Add HA guide for self-hosted Enterprise (#812)

* render mermaid disgrams

* wip: add ha docs

* improve high availability guide

* fix review

* remove nav links and references

* remove dead server.authSecret references

* fix Redis troubleshooting command using non-existent env var

* correct startup-log order
This commit is contained in:
Bethuel Mmbaga
2026-06-25 15:53:25 +03:00
committed by GitHub
parent e21d6da29d
commit ffb72cfa34
5 changed files with 892 additions and 1 deletions

View File

@@ -11,6 +11,7 @@ import clsx from 'clsx'
import { create } from 'zustand'
import { Tag } from '@/components/Tag'
import { Mermaid } from '@/components/Mermaid'
const languageNames = {
js: 'JavaScript',
@@ -290,6 +291,10 @@ export function Code({ children, ...props }) {
export function Pre({ children, ...props }) {
let isGrouped = useContext(CodeGroupContext)
if (props.language === 'mermaid') {
return <Mermaid chart={props.code} />
}
if (isGrouped) {
return children
}

186
src/components/Mermaid.jsx Normal file
View File

@@ -0,0 +1,186 @@
import { useCallback, useEffect, useId, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
let mermaidPromise
function loadMermaid() {
if (!mermaidPromise) {
mermaidPromise = import('mermaid').then((m) => m.default)
}
return mermaidPromise
}
function isDarkMode() {
return (
typeof document !== 'undefined' &&
document.documentElement.classList.contains('dark')
)
}
function CloseIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
)
}
export function Mermaid({ chart, alt = 'Diagram' }) {
let reactId = useId().replace(/[^a-zA-Z0-9]/g, '')
let renderCount = useRef(0)
let renderRun = useRef(0)
let hasRendered = useRef(false)
let [svg, setSvg] = useState('')
let [error, setError] = useState(null)
let [zoomed, setZoomed] = useState(false)
let [closing, setClosing] = useState(false)
useEffect(() => {
let cancelled = false
let lastTheme = null
async function render() {
let dark = isDarkMode()
// Skip redundant re-renders when an unrelated class toggles on <html>.
if (lastTheme === dark && hasRendered.current) return
lastTheme = dark
let run = ++renderRun.current
try {
let mermaid = await loadMermaid()
mermaid.initialize({
startOnLoad: false,
securityLevel: 'strict',
theme: dark ? 'dark' : 'default',
fontFamily:
"ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
})
let id = `mermaid-${reactId}-${renderCount.current++}`
let { svg: out } = await mermaid.render(id, chart)
if (!cancelled && run === renderRun.current) {
hasRendered.current = true
setError(null)
setSvg(out)
}
} catch (e) {
if (!cancelled && run === renderRun.current) {
setError(e?.message || String(e))
}
}
}
render()
// Re-render when the user toggles light/dark so the diagram matches the theme.
let observer = new MutationObserver(() => render())
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class'],
})
return () => {
cancelled = true
observer.disconnect()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chart])
let close = useCallback(() => {
setClosing(true)
setTimeout(() => {
setZoomed(false)
setClosing(false)
}, 200)
}, [])
useEffect(() => {
if (!zoomed) return
let onKey = (e) => {
if (e.key === 'Escape') close()
}
document.addEventListener('keydown', onKey)
return () => document.removeEventListener('keydown', onKey)
}, [zoomed, close])
if (error) {
return (
<pre className="my-6 overflow-x-auto rounded border border-red-500/30 bg-red-50/50 p-4 text-xs text-red-700 dark:bg-red-500/5 dark:text-red-300">
{`Mermaid render error: ${error}\n\n${chart}`}
</pre>
)
}
if (!svg) {
return <div className="not-prose my-6" aria-hidden="true" />
}
// Mermaid caps the SVG with `max-width: <px>`; strip it for the zoom copy so the
// diagram can scale up to fill the lightbox instead of staying at natural size.
let zoomSvg = svg.replace(/max-width:\s*[\d.]+px;?/g, '')
return (
<>
<div
className="not-prose my-6 flex justify-center overflow-x-auto [&_svg]:cursor-zoom-in"
role="button"
tabIndex={0}
aria-label={`${alt} — activate to zoom`}
onClick={() => setZoomed(true)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setZoomed(true)
}
}}
// svg is produced by mermaid from trusted, in-repo diagram source
dangerouslySetInnerHTML={{ __html: svg }}
/>
{zoomed &&
createPortal(
<div
className={`image-zoom-overlay ${closing ? 'closing' : ''}`}
onClick={close}
role="dialog"
aria-modal="true"
aria-label={`Zoomed ${alt}`}
>
<button
className="image-zoom-close"
onClick={close}
aria-label="Close zoomed diagram"
>
<CloseIcon />
</button>
<div
className={`image-zoom-content ${
closing ? 'closing' : ''
} flex items-center justify-center bg-white dark:bg-zinc-900 [&_svg]:!h-auto [&_svg]:!max-h-full [&_svg]:!w-auto [&_svg]:!max-w-full`}
style={{
width: '90vw',
height: '86vh',
maxWidth: '90vw',
maxHeight: '86vh',
padding: '1rem',
cursor: 'zoom-out',
}}
onClick={close}
dangerouslySetInnerHTML={{ __html: zoomSvg }}
/>
</div>,
document.body
)}
</>
)
}