前端大改6

This commit is contained in:
qi4l
2026-05-12 19:54:07 +08:00
parent 61ff0a2e47
commit 6f3b1b12a3
14 changed files with 1219 additions and 308 deletions
+3
View File
@@ -6,6 +6,7 @@
"name": "jyso-web",
"dependencies": {
"axios": "^1.6.0",
"liquid-glass-react": "^1.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0",
@@ -251,6 +252,8 @@
"json5": ["[email protected]", "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"liquid-glass-react": ["[email protected]", "", { "peerDependencies": { "react": ">=19", "react-dom": ">=19" } }, "sha512-pKzaktaMAEztd93wpWcz2Z5Z9qdLJUNJdMX+n00Ca4XsnrLTQ5xJzm/+GQXZUeuFXe/PQ8ziVMZO6531PyaFJw=="],
"loose-envify": ["[email protected]", "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"lru-cache": ["[email protected]", "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+1
View File
@@ -10,6 +10,7 @@
},
"dependencies": {
"axios": "^1.6.0",
"liquid-glass-react": "^1.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0"
Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

+2
View File
@@ -1,6 +1,7 @@
import { Routes, Route, Navigate } from 'react-router-dom'
import Login from './pages/Login'
import Dashboard from './pages/Dashboard'
import UserInfo from './pages/UserInfo'
import ProtectedRoute from './components/ProtectedRoute'
export default function App() {
@@ -8,6 +9,7 @@ export default function App() {
<Routes>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route path="/login" element={<Login />} />
<Route path="/user-info" element={<UserInfo />} />
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
@@ -0,0 +1,97 @@
import { useEffect, useRef } from 'react'
import { useTheme } from '../context/ThemeContext'
export default function AnimatedBackground() {
const { theme } = useTheme()
const canvasRef = useRef(null)
const mouseRef = useRef({ x: 0.5, y: 0.5 })
const rafRef = useRef(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
let width, height
let time = 0
function resize() {
const dpr = Math.min(window.devicePixelRatio || 1, 2)
width = window.innerWidth
height = window.innerHeight
canvas.width = width * dpr
canvas.height = height * dpr
canvas.style.width = width + 'px'
canvas.style.height = height + 'px'
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
}
resize()
window.addEventListener('resize', resize)
function onMouseMove(e) {
mouseRef.current.x = e.clientX / width
mouseRef.current.y = e.clientY / height
}
window.addEventListener('mousemove', onMouseMove)
const blobs = [
{ x: 0.25, y: 0.30, r: 0.44, sx: 0.12, sy: 0.08, color: theme === 'dark' ? '90, 40, 255' : '50, 80, 255', phase: 0 },
{ x: 0.70, y: 0.25, r: 0.38, sx: 0.10, sy: 0.11, color: theme === 'dark' ? '255, 30, 180' : '255, 60, 180', phase: 1.5 },
{ x: 0.50, y: 0.70, r: 0.46, sx: 0.09, sy: 0.10, color: theme === 'dark' ? '20, 140, 255' : '30, 200, 230', phase: 3 },
{ x: 0.80, y: 0.55, r: 0.34, sx: 0.11, sy: 0.09, color: theme === 'dark' ? '255, 140, 30' : '255, 150, 40', phase: 4.5 },
{ x: 0.20, y: 0.75, r: 0.36, sx: 0.10, sy: 0.12, color: theme === 'dark' ? '30, 220, 160' : '50, 230, 140', phase: 2 },
]
function draw() {
time += 0.0035
ctx.clearRect(0, 0, width, height)
const mx = mouseRef.current.x
const my = mouseRef.current.y
blobs.forEach(blob => {
const bx = width * (blob.x + Math.sin(time + blob.phase) * blob.sx)
const by = height * (blob.y + Math.cos(time * 0.7 + blob.phase) * blob.sy)
const br = Math.min(width, height) * blob.r
const parallaxX = (mx - 0.5) * 30
const parallaxY = (my - 0.5) * 30
const gradient = ctx.createRadialGradient(
bx + parallaxX, by + parallaxY, 0,
bx + parallaxX, by + parallaxY, br
)
const alpha = theme === 'dark' ? 0.22 : 0.40
gradient.addColorStop(0, `rgba(${blob.color}, ${alpha})`)
gradient.addColorStop(0.5, `rgba(${blob.color}, ${alpha * 0.35})`)
gradient.addColorStop(1, `rgba(${blob.color}, 0)`)
ctx.fillStyle = gradient
ctx.fillRect(0, 0, width, height)
})
rafRef.current = requestAnimationFrame(draw)
}
rafRef.current = requestAnimationFrame(draw)
return () => {
window.removeEventListener('resize', resize)
window.removeEventListener('mousemove', onMouseMove)
cancelAnimationFrame(rafRef.current)
}
}, [theme])
return (
<canvas
ref={canvasRef}
style={{
position: 'fixed',
top: 0,
left: 0,
zIndex: -1,
pointerEvents: 'none',
}}
/>
)
}
@@ -0,0 +1,37 @@
import { useTheme } from '../context/ThemeContext'
const SunIcon = () => (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
)
const MoonIcon = () => (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
)
export default function FloatingThemeToggle() {
const { theme, toggleTheme } = useTheme()
const isDark = theme === 'dark'
return (
<button
className="floating-theme-toggle"
onClick={toggleTheme}
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
title={isDark ? 'Light mode' : 'Dark mode'}
>
{isDark ? <SunIcon /> : <MoonIcon />}
</button>
)
}
@@ -0,0 +1,28 @@
import LiquidGlass from 'liquid-glass-react'
export default function LiquidGlassCard({
children,
className = '',
style = {},
...props
}) {
return (
<div className={`liquid-glass-wrapper ${className}`} style={style}>
<LiquidGlass
cornerRadius={28}
displacementScale={48}
blurAmount={1.2}
saturation={160}
aberrationIntensity={1.5}
elasticity={0.12}
mode="standard"
padding="0px"
{...props}
>
<div className="liquid-glass-inner">
{children}
</div>
</LiquidGlass>
</div>
)
}
+5 -19
View File
@@ -1,32 +1,18 @@
import { createContext, useContext, useState, useEffect } from 'react'
import { createContext, useContext, useEffect } from 'react'
import { useLocation } from 'react-router-dom'
const ThemeContext = createContext()
function getInitialTheme() {
try {
const saved = localStorage.getItem('jyso_theme')
if (saved === 'dark' || saved === 'light') return saved
} catch (e) { /* ignore */ }
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark'
}
return 'light'
}
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState(getInitialTheme)
const location = useLocation()
const theme = location.pathname === '/login' ? 'dark' : 'light'
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme)
try { localStorage.setItem('jyso_theme', theme) } catch (e) { /* ignore */ }
}, [theme])
function toggleTheme() {
setTheme(prev => prev === 'light' ? 'dark' : 'light')
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<ThemeContext.Provider value={{ theme }}>
{children}
</ThemeContext.Provider>
)
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -8,12 +8,12 @@ import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<ThemeProvider>
<ToastProvider>
<HashRouter>
<HashRouter>
<ThemeProvider>
<ToastProvider>
<App />
</HashRouter>
</ToastProvider>
</ThemeProvider>
</ToastProvider>
</ThemeProvider>
</HashRouter>
</React.StrictMode>
)
+136 -69
View File
@@ -1,4 +1,4 @@
import { useTheme } from '../context/ThemeContext'
import { useState } from 'react'
import { useToast } from '../components/Toast'
import useDashboard from '../hooks/useDashboard'
import CopyButton from '../components/CopyButton'
@@ -28,10 +28,56 @@ const FileIcon = () => (
</svg>
)
const JndiIcon = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="3"/><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>
</svg>
)
const GadgetIcon = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
</svg>
)
const ArrowRightIcon = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="9 18 15 12 9 6"/>
</svg>
)
const CloseIcon = () => (
<svg width="16" height="16" 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>
)
const MenuIcon = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="21" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg>
)
const WikiIcon = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>
)
const LogoutIcon = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>
</svg>
)
export default function Dashboard() {
const { theme, toggleTheme } = useTheme()
const { showToast } = useToast()
const d = useDashboard()
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
async function handleToggleServer(server) {
await d.handleToggleServer(server)
@@ -76,48 +122,63 @@ export default function Dashboard() {
<div>
<div className="dashboard-bg" />
<div className="page-shell">
<div className="glass-card" style={{ marginBottom: 20 }}>
<div className="header">
<h1>
<span className="logo-dot" />
JYso
</h1>
<div className={'mode-segment-control' + (d.mode === 'gadget' ? ' gadget-mode' : '')}>
<button
className={'mode-segment-btn' + (d.mode === 'jndi' ? ' active' : '')}
onClick={() => d.switchMode('jndi')}
>
JNDI EXP
</button>
<button
className={'mode-segment-btn' + (d.mode === 'gadget' ? ' active' : '')}
onClick={() => d.switchMode('gadget')}
>
Gadget
</button>
</div>
<div className="header-right">
<a className="wiki-btn" href="https://github.com/qi4L/JYso/wiki" target="_blank" rel="noopener noreferrer" title="Wiki">
Wiki <ExternalLinkIcon />
</a>
<button
className="theme-toggle"
onClick={toggleTheme}
aria-label={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
title={theme === 'light' ? 'Dark mode' : 'Light mode'}
/>
<button className="logout-btn" onClick={d.logout}>Logout</button>
<div className="page-shell dashboard-layout">
<aside className={'sidebar' + (sidebarCollapsed ? ' collapsed' : '')}>
<div className="sidebar-content">
<button
className="sidebar-toggle-btn"
onClick={() => setSidebarCollapsed(v => !v)}
title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
<span className="toggle-icon-default">
{sidebarCollapsed ? <MenuIcon /> : <CloseIcon />}
</span>
<span className="toggle-icon-hover">
<ArrowRightIcon />
</span>
</button>
<div className="header sidebar-header">
<h1>JYso <span style={{fontSize: 11, fontWeight: 400, color: 'rgba(255,255,255,0.35)', marginLeft: 4}}>v1.3.8</span></h1>
<div className="sidebar-nav">
<button
className={'sidebar-nav-item' + (d.mode === 'jndi' ? ' active' : '')}
onClick={() => d.switchMode('jndi')}
title="JNDI EXP"
>
<JndiIcon />
<span>JNDI EXP</span>
</button>
<button
className={'sidebar-nav-item' + (d.mode === 'gadget' ? ' active' : '')}
onClick={() => d.switchMode('gadget')}
title="Gadget"
>
<GadgetIcon />
<span>Gadget</span>
</button>
</div>
<div className="header-right sidebar-actions">
<a className="wiki-btn" href="https://github.com/qi4L/JYso/wiki" target="_blank" rel="noopener noreferrer" title="Wiki">
<WikiIcon />
<span>Wiki <ExternalLinkIcon /></span>
</a>
<button className="logout-btn" onClick={d.logout} title="Logout">
<LogoutIcon />
<span>Logout</span>
</button>
</div>
</div>
</div>
</div>
</aside>
<div key={d.animKey}>
<main className="main-content">
<div key={d.animKey}>
{d.mode === 'jndi' && (
<>
<div className="glass-card section-enter" style={{ marginBottom: 20 }}>
<h2>Server Status</h2>
<div className="status-grid">
<div style={{ display: 'flex', gap: 20, marginBottom: 20, alignItems: 'flex-start' }}>
<div className="glass-card section-enter" style={{ width: 200, flexShrink: 0, marginBottom: 0 }}>
<h2>Server Status</h2>
<div className="status-grid" style={{ gridTemplateColumns: '1fr' }}>
<div className={'status-item status-clickable' + (d.toggling === 'ldap' ? ' status-toggling' : '')}
onClick={() => handleToggleServer('ldap')}
title="Click to toggle LDAP server">
@@ -154,15 +215,11 @@ export default function Dashboard() {
<span className="status-label">IP Address</span>
<span className="status-value" style={{ color: 'var(--accent)' }}>{d.status.ip || '0.0.0.0'}</span>
</div>
<div className="status-item">
<span className="status-label">Version</span>
<span className="status-value" style={{ color: 'var(--accent)' }}>1.3.8</span>
</div>
</div>
</div>
<div className="glass-card section-enter">
<div className="control-bar">
<div className="glass-card section-enter" style={{ flex: 1, marginBottom: 0 }}>
<div className="control-bar" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<div className={'tab-segment-control tabs-3' + (d.activeJndiTab === 'payload' ? ' config-tab' : '') + (d.activeJndiTab === 'logs' ? ' tab-3' : '')}>
<button
className={'tab-segment-btn' + (d.activeJndiTab === 'config' ? ' active' : '')}
@@ -183,6 +240,23 @@ export default function Dashboard() {
Logs
</button>
</div>
{d.activeJndiTab === 'config' && (
<button className="btn btn-primary" style={{ width: 'auto', padding: '8px 18px', fontSize: 14 }} onClick={handleSaveConfig} disabled={d.loading}>
{d.loading ? 'Saving...' : 'Save Configuration'}
</button>
)}
{d.activeJndiTab === 'payload' && d.payloadSubTab === 'gadget' && (
<button className="btn btn-primary" style={{ width: 'auto', padding: '8px 18px', fontSize: 14 }} onClick={handleGenerateJndiPayload}
disabled={d.loading || (!(d.jndiGadgetInput || d.selectedGadget).trim()) || !d.payloadCmd}>
{d.loading ? 'Generating...' : 'Generate'}
</button>
)}
{d.activeJndiTab === 'payload' && d.payloadSubTab === 'classloader' && (
<button className="btn btn-primary" style={{ width: 'auto', padding: '8px 18px', fontSize: 14 }} onClick={handleGenerateClassLoader}
disabled={d.loading || !d.filePath.trim() || !d.routing.trim()}>
{d.loading ? 'Generating...' : 'Generate'}
</button>
)}
</div>
{d.activeJndiTab === 'config' && (
@@ -238,15 +312,12 @@ export default function Dashboard() {
<input type="text" value={d.configForm.certFile} placeholder="/path/to/cert.jks"
onChange={e => d.setConfigForm({ ...d.configForm, certFile: e.target.value })} />
</div>
<div className="form-group" style={{ gridColumn: 'span 2' }}>
<div className="form-group" style={{ gridColumn: 'span 3' }}>
<label className="form-group" style={{ marginBottom: 0 }}>
<input type="checkbox" checked={d.configForm.TLSProxy}
onChange={e => d.setConfigForm({ ...d.configForm, TLSProxy: e.target.checked })} /> TLS Proxy (LDAPS Port Forwarding)
</label>
</div>
<button className="btn btn-primary" onClick={handleSaveConfig} disabled={d.loading}>
{d.loading ? 'Saving...' : 'Save Configuration'}
</button>
</div>
</div>
)}
@@ -305,10 +376,6 @@ export default function Dashboard() {
onChange={e => d.setPayloadCmd(e.target.value)} />
<span className="input-icon" style={{ top: 38 }}><CommandIcon /></span>
</div>
<button className="btn btn-primary" onClick={handleGenerateJndiPayload}
disabled={d.loading || (!(d.jndiGadgetInput || d.selectedGadget).trim()) || !d.payloadCmd}>
{d.loading ? 'Generating...' : 'Generate'}
</button>
{d.jndiPayloadResult && (
<div className="payload-output" style={{ marginTop: 14, position: 'relative', paddingRight: 42 }}>
{d.jndiPayloadResult}
@@ -366,10 +433,6 @@ export default function Dashboard() {
onChange={e => d.setFilePath(e.target.value)} />
<span className="input-icon"><FileIcon /></span>
</div>
<button className="btn btn-primary" onClick={handleGenerateClassLoader}
disabled={d.loading || !d.filePath.trim() || !d.routing.trim()}>
{d.loading ? 'Generating...' : 'Generate'}
</button>
{d.classLoaderResult && (
<div className="payload-output" style={{ marginTop: 14, position: 'relative', paddingRight: 42 }}>
{d.classLoaderResult}
@@ -415,7 +478,8 @@ export default function Dashboard() {
</div>
)}
</div>
</>
</div>
</>
)}
{d.mode === 'gadget' && (
@@ -423,13 +487,19 @@ export default function Dashboard() {
<div className="glass-card section-enter" style={{ flex: 3, marginBottom: 0 }}>
<div className="header" style={{ padding: 0, marginBottom: 16 }}>
<h2>Payload Generator</h2>
<button
className={`btn btn-secondary${d.showAdvanced ? ' active-tab' : ''}`}
style={{ padding: '6px 14px', fontSize: 12, width: 'auto' }}
onClick={() => d.setShowAdvanced(v => !v)}
>
{d.showAdvanced ? 'Hide Options' : 'Advanced Options'}
</button>
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
<button
className={`btn btn-secondary${d.showAdvanced ? ' active-tab' : ''}`}
style={{ padding: '6px 14px', fontSize: 12, width: 'auto' }}
onClick={() => d.setShowAdvanced(v => !v)}
>
{d.showAdvanced ? 'Hide Options' : 'Advanced Options'}
</button>
<button className="btn btn-primary" style={{ width: 'auto', padding: '8px 18px', fontSize: 14 }} onClick={handleGeneratePayload}
disabled={d.loading || !(d.gadgetModeInput || d.selectedGadget).trim()}>
{d.loading ? 'Generating...' : 'Generate'}
</button>
</div>
</div>
<div className="control-bar" style={{ marginBottom: 12 }}>
<label className="form-group" style={{ marginBottom: 0, display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' }}>
@@ -539,10 +609,6 @@ export default function Dashboard() {
onChange={e => d.setSaveFilename(e.target.value)} />
<span className="input-icon"><FileIcon /></span>
</div>
<button className="btn btn-primary" onClick={handleGeneratePayload}
disabled={d.loading || !(d.gadgetModeInput || d.selectedGadget).trim()}>
{d.loading ? 'Generating...' : 'Generate'}
</button>
{d.payloadResult && (
<div className="payload-output" style={{ marginTop: 14, position: 'relative', paddingRight: 42 }}>
{d.payloadResult}
@@ -599,6 +665,7 @@ export default function Dashboard() {
</div>
)}
</div>
</main>
</div>
</div>
)
+2 -15
View File
@@ -1,11 +1,9 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { login, setAuthToken } from '../api'
import { useTheme } from '../context/ThemeContext'
export default function Login() {
const navigate = useNavigate()
const { theme, toggleTheme } = useTheme()
const [username, setUsername] = useState('qi')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
@@ -43,20 +41,9 @@ export default function Login() {
return (
<div className="login-wrapper">
<div style={{ position: 'fixed', top: 22, right: 22, zIndex: 200 }}>
<button
className="theme-toggle"
onClick={toggleTheme}
aria-label={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
title={theme === 'light' ? 'Dark mode' : 'Light mode'}
/>
</div>
<div className="glass-card" style={{ width: 380 }}>
<div className="glass-card" style={{ width: 400, borderRadius: 36 }}>
<div className="login-inner">
<h1>
<span className="logo-dot" style={{ width: 12, height: 12, borderRadius: 6 }} />
JYso
</h1>
<h1>JYso</h1>
<p className="subtitle">JNDI Exploitation Toolkit</p>
<p className="tagline">Sign in to access the dashboard</p>
{error && <div className="error-msg">{error}</div>}
+277
View File
@@ -0,0 +1,277 @@
import { useRef } from 'react'
import LiquidGlass from 'liquid-glass-react'
export default function UserInfo() {
const containerRef = useRef(null)
return (
<div
ref={containerRef}
style={{
width: '100vw',
height: '100vh',
position: 'relative',
overflow: 'hidden',
background:
'linear-gradient(180deg, #f4d89d 0%, #f8df9e 15%, #f5c86e 35%, #e4a84a 50%, #b47d35 65%, #7a4e28 80%, #4a2e18 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontFamily:
"'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', sans-serif",
}}
>
{/* Soft cloud texture */}
<div
style={{
position: 'absolute',
inset: 0,
background:
'radial-gradient(ellipse 70% 40% at 25% 18%, rgba(255,255,255,0.35) 0%, transparent 60%),' +
'radial-gradient(ellipse 50% 30% at 65% 12%, rgba(255,255,255,0.2) 0%, transparent 55%),' +
'radial-gradient(ellipse 60% 35% at 85% 28%, rgba(255,245,210,0.15) 0%, transparent 50%)',
pointerEvents: 'none',
}}
/>
{/* Sun glow */}
<div
style={{
position: 'absolute',
top: '38%',
left: '55%',
transform: 'translate(-50%, -50%)',
width: '500px',
height: '350px',
background:
'radial-gradient(ellipse, rgba(255,240,190,0.55) 0%, rgba(255,220,150,0.25) 40%, transparent 70%)',
pointerEvents: 'none',
}}
/>
{/* Water line / horizon */}
<div
style={{
position: 'absolute',
bottom: '22%',
left: 0,
right: 0,
height: '1px',
background: 'rgba(80,50,25,0.35)',
pointerEvents: 'none',
}}
/>
{/* Water shimmer */}
<div
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: '22%',
background:
'linear-gradient(180deg, rgba(180,125,53,0.2) 0%, rgba(74,46,24,0.5) 100%)',
pointerEvents: 'none',
}}
/>
{/* Pier silhouette - left side */}
<svg
viewBox="0 0 420 700"
style={{
position: 'absolute',
left: '-60px',
bottom: '-10px',
width: '380px',
height: '560px',
opacity: 0.9,
pointerEvents: 'none',
}}
preserveAspectRatio="xMidYMax meet"
>
<g fill="#1e140e">
{/* Thick main supports */}
<rect x="25" y="60" width="18" height="640" rx="2" />
<rect x="75" y="110" width="14" height="590" rx="2" />
<rect x="120" y="155" width="12" height="545" rx="2" />
<rect x="160" y="195" width="10" height="505" rx="2" />
<rect x="195" y="235" width="10" height="465" rx="2" />
<rect x="225" y="275" width="8" height="425" rx="2" />
<rect x="250" y="315" width="8" height="385" rx="2" />
{/* Horizontal deck beams */}
<rect x="0" y="120" width="230" height="12" rx="2" />
<rect x="5" y="210" width="225" height="10" rx="2" />
<rect x="15" y="300" width="215" height="8" rx="2" />
<rect x="25" y="390" width="205" height="8" rx="2" />
<rect x="35" y="480" width="195" height="6" rx="2" />
{/* Cross bracing (diagonal) */}
<polygon points="34,120 75,210 82,210 38,120" />
<polygon points="82,210 120,300 127,300 85,210" />
<polygon points="127,300 160,390 166,390 130,300" />
<polygon points="166,390 195,480 200,480 170,390" />
<polygon points="38,210 82,300 75,300 34,210" />
<polygon points="85,300 127,390 120,390 82,300" />
<polygon points="130,390 170,480 163,480 127,390" />
{/* Some broken/jagged top edges to look like ruins */}
<polygon points="25,60 43,60 40,85 28,80" />
<polygon points="75,110 89,110 86,130 78,128" />
</g>
</svg>
{/* Birds - right side */}
<svg
viewBox="0 0 220 140"
style={{
position: 'absolute',
right: '6%',
top: '18%',
width: '180px',
height: '115px',
opacity: 0.65,
pointerEvents: 'none',
}}
>
<g fill="#1e140e">
{/* Bird 1 */}
<path d="M175,35 Q181,26 187,35 Q181,31 175,35 Z" />
{/* Bird 2 */}
<path d="M130,55 Q137,46 143,55 Q137,51 130,55 Z" />
{/* Bird 3 */}
<path d="M160,72 Q166,64 172,72 Q166,68 160,72 Z" />
{/* Bird 4 */}
<path d="M195,58 Q200,50 206,58 Q200,54 195,58 Z" />
{/* Bird 5 - smaller, farther */}
<path d="M150,28 Q154,22 158,28 Q154,25 150,28 Z" />
</g>
</svg>
<LiquidGlass
mouseContainer={containerRef}
displacementScale={70}
blurAmount={0.08}
saturation={145}
aberrationIntensity={2.5}
elasticity={0.25}
cornerRadius={40}
padding="36px 44px"
style={{
width: '480px',
maxWidth: '92vw',
position: 'relative',
zIndex: 10,
marginTop: '-8vh',
marginLeft: '2vw',
}}
>
<div style={{ color: '#fff' }}>
<h2
style={{
fontSize: '24px',
fontWeight: 600,
marginBottom: '24px',
letterSpacing: '-0.4px',
textShadow: '0 2px 4px rgba(0,0,0,0.25)',
}}
>
User Info
</h2>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '18px',
marginBottom: '32px',
}}
>
<div
style={{
width: '64px',
height: '64px',
borderRadius: '50%',
background: 'rgba(0,0,0,0.18)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '22px',
fontWeight: 600,
color: '#fff',
border: '1px solid rgba(255,255,255,0.25)',
textShadow: '0 1px 2px rgba(0,0,0,0.2)',
flexShrink: 0,
}}
>
JD
</div>
<div>
<div
style={{
fontSize: '22px',
fontWeight: 600,
letterSpacing: '-0.3px',
textShadow: '0 2px 4px rgba(0,0,0,0.25)',
}}
>
John Doe
</div>
<div
style={{
fontSize: '15px',
fontWeight: 400,
color: 'rgba(255,255,255,0.85)',
marginTop: '3px',
textShadow: '0 1px 2px rgba(0,0,0,0.2)',
}}
>
Software Engineer
</div>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<InfoRow label="Email" value="[email protected]" />
<InfoRow label="Location" value="San Francisco, CA" />
<InfoRow label="Joined" value="March 2023" />
</div>
</div>
</LiquidGlass>
</div>
)
}
function InfoRow({ label, value }) {
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
fontSize: '16px',
}}
>
<span
style={{
color: 'rgba(255,255,255,0.85)',
fontWeight: 400,
textShadow: '0 1px 2px rgba(0,0,0,0.2)',
}}
>
{label}:
</span>
<span
style={{
color: '#fff',
fontWeight: 500,
textShadow: '0 1px 2px rgba(0,0,0,0.25)',
}}
>
{value}
</span>
</div>
)
}