feat(i18n): add multi-language support (ja/ko/vi/ru/zh-TW) with system auto-detection
- 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
This commit is contained in:
1 parent
fea6d8db04
commit
b0d0e3a5f3
301 files changed
+28002
-2705
No files matched your search
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 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>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user