- Extend AppLanguage to 7 locales: zh-CN, zh-TW, en, ja, ko, vi, ru - Auto-detect system language via navigator.languages; fallback to English - Add BCP47 locale mapping for date formatting and sorting - Create 50 translation JSON files (5 languages × 10 namespaces, 1137 keys each) - Update language selector with native language names - Extend locale key parity test to cover all 7 languages (70 assertions) - Add VNC/RDP desktop, serial terminal, AI agent, port forwarding, and SFTP features - Add credential encryption, host metrics, session backup crypto, and zmodem bridge - Update Rust backend: desktop services, host metrics, SSH/SFTP enhancements
186 lines
6.3 KiB
TypeScript
186 lines
6.3 KiB
TypeScript
/**
|
|
* Port-forward traffic diagram.
|
|
* Solid = path to firewall; dashed = SSH tunnel (explicit stroke — Tailwind stroke-* is unreliable on SVG).
|
|
*/
|
|
|
|
import { useTranslation } from 'react-i18next'
|
|
import appIcon from '../../assets/opsxshell-app-icon.png'
|
|
import firewallIcon from '../../assets/diagram/network-firewall.png'
|
|
import serverIcon from '../../assets/diagram/ec2.png'
|
|
import cloudGcp from '../../assets/diagram/cloud-gcp.svg'
|
|
import cloudCf from '../../assets/diagram/cloud-cf.svg'
|
|
import type { ForwardRuleType } from './forwardRules'
|
|
|
|
interface ForwardArchitectureDiagramProps {
|
|
type: ForwardRuleType
|
|
}
|
|
|
|
const SOLID = '#fb7185'
|
|
const DASH = '#60a5fa'
|
|
|
|
function AppBadge() {
|
|
return (
|
|
<div className="flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded-xl border border-accent-blue/40 bg-[#0b1220] shadow-lg shadow-accent-blue/15">
|
|
<img src={appIcon} alt="" className="h-10 w-10 object-cover" draggable={false} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function IconImg({
|
|
src,
|
|
className,
|
|
rounded,
|
|
}: {
|
|
src: string
|
|
className?: string
|
|
rounded?: boolean
|
|
}) {
|
|
return (
|
|
<img
|
|
src={src}
|
|
alt=""
|
|
draggable={false}
|
|
className={`object-contain drop-shadow-md ${rounded ? 'rounded-xl' : ''} ${className || ''}`}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function SolidPath({ d }: { d: string }) {
|
|
return (
|
|
<path
|
|
d={d}
|
|
fill="none"
|
|
stroke={SOLID}
|
|
strokeWidth="2.4"
|
|
strokeLinecap="round"
|
|
/>
|
|
)
|
|
}
|
|
|
|
function DashPath({ d }: { d: string }) {
|
|
return (
|
|
<path
|
|
d={d}
|
|
fill="none"
|
|
stroke={DASH}
|
|
strokeWidth="2.4"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeDasharray="6 5"
|
|
opacity={0.95}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export default function ForwardArchitectureDiagram({ type }: ForwardArchitectureDiagramProps) {
|
|
const { t } = useTranslation('common')
|
|
|
|
const caption =
|
|
type === 'local'
|
|
? t('portForward.diagram.localCaption')
|
|
: type === 'remote'
|
|
? t('portForward.diagram.remoteCaption')
|
|
: t('portForward.diagram.dynamicCaption')
|
|
|
|
return (
|
|
<div className="rounded-xl border border-dark-700/60 bg-dark-900/35 px-3 py-3">
|
|
<div className="mb-2 text-[11px] font-medium text-content-secondary">
|
|
{t('portForward.diagram.title')}
|
|
</div>
|
|
|
|
<div className="relative mx-auto h-[160px] w-full max-w-[360px] overflow-hidden rounded-lg border border-dark-700/40 bg-gradient-to-b from-dark-900/50 to-dark-950/70">
|
|
{/* Lines under icons */}
|
|
<svg
|
|
className="pointer-events-none absolute inset-0 h-full w-full"
|
|
viewBox="0 0 360 160"
|
|
preserveAspectRatio="none"
|
|
aria-hidden
|
|
>
|
|
{type === 'local' && (
|
|
<>
|
|
{/* App → firewall (solid) */}
|
|
<SolidPath d="M64 48 H155" />
|
|
{/* App → SSH host → remote targets (dashed) */}
|
|
<DashPath d="M56 58 C100 95, 150 118, 180 122" />
|
|
<DashPath d="M180 122 C235 115, 275 70, 300 48" />
|
|
<DashPath d="M180 122 C245 110, 285 75, 318 52" />
|
|
</>
|
|
)}
|
|
{type === 'remote' && (
|
|
<>
|
|
{/* Same span as Local/Dynamic: ~App→Firewall */}
|
|
<SolidPath d="M64 48 H155" />
|
|
<DashPath d="M56 58 C100 95, 150 118, 180 122" />
|
|
<DashPath d="M304 58 C260 95, 210 118, 180 122" />
|
|
</>
|
|
)}
|
|
{type === 'dynamic' && (
|
|
<>
|
|
<SolidPath d="M64 48 H155" />
|
|
<DashPath d="M56 58 C100 95, 150 118, 180 122" />
|
|
<DashPath d="M180 122 C245 110, 285 70, 318 42" />
|
|
<DashPath d="M180 122 C245 120, 285 100, 320 88" />
|
|
<DashPath d="M180 122 C235 132, 275 138, 318 132" />
|
|
</>
|
|
)}
|
|
</svg>
|
|
|
|
{type === 'local' && (
|
|
<>
|
|
<div className="absolute left-3 top-[26px]">
|
|
<AppBadge />
|
|
</div>
|
|
{/* Firewall + SSH host: same column, vertically centered as a pair */}
|
|
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center gap-3">
|
|
<IconImg src={firewallIcon} rounded className="h-11 w-11" />
|
|
<IconImg src={serverIcon} className="h-10 w-10" />
|
|
</div>
|
|
<div className="absolute right-3 top-[26px] flex items-center gap-1.5 rounded-xl border border-dark-600/60 bg-dark-800/40 px-2 py-1.5">
|
|
<IconImg src={serverIcon} className="h-9 w-9" />
|
|
<IconImg src={serverIcon} className="h-9 w-9" />
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{type === 'remote' && (
|
|
<>
|
|
{/* Match Local/Dynamic left inset so solid gap equals App→Firewall */}
|
|
<div className="absolute left-3 top-[26px]">
|
|
<IconImg src={serverIcon} className="h-11 w-11" />
|
|
</div>
|
|
<div className="absolute left-1/2 top-[26px] -translate-x-1/2">
|
|
<IconImg src={firewallIcon} rounded className="h-11 w-11" />
|
|
</div>
|
|
<div className="absolute right-3 top-[26px]">
|
|
<IconImg src={serverIcon} className="h-11 w-11" />
|
|
</div>
|
|
<div className="absolute bottom-4 left-1/2 -translate-x-1/2">
|
|
<AppBadge />
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{type === 'dynamic' && (
|
|
<>
|
|
<div className="absolute left-3 top-[26px]">
|
|
<AppBadge />
|
|
</div>
|
|
{/* Firewall + SSH host stacked & vertically centered */}
|
|
<div className="absolute left-[42%] top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center gap-3">
|
|
<IconImg src={firewallIcon} rounded className="h-11 w-11" />
|
|
<IconImg src={serverIcon} className="h-10 w-10" />
|
|
</div>
|
|
<div className="absolute right-3 top-1/2 flex -translate-y-1/2 flex-col items-center gap-2 rounded-2xl border border-dark-600/60 bg-dark-800/40 px-2.5 py-2.5">
|
|
<IconImg src={cloudGcp} className="h-7 w-7" />
|
|
<IconImg src={cloudCf} className="h-7 w-7" />
|
|
<IconImg src={cloudGcp} className="h-6 w-6 opacity-70" />
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<p className="mt-2.5 text-[11px] leading-relaxed text-txt-muted">{caption}</p>
|
|
</div>
|
|
)
|
|
}
|