mirror of
https://github.com/qi4L/JYso.git
synced 2026-09-21 22:40:43 +08:00
前端大改6
This commit is contained in:
@@ -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=="],
|
||||
|
||||
@@ -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 |
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
+625
-199
File diff suppressed because it is too large
Load Diff
@@ -8,12 +8,12 @@ import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<HashRouter>
|
||||
<ThemeProvider>
|
||||
<ToastProvider>
|
||||
<HashRouter>
|
||||
<App />
|
||||
</HashRouter>
|
||||
</ToastProvider>
|
||||
</ThemeProvider>
|
||||
</HashRouter>
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
||||
@@ -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' : '')}>
|
||||
<div className="page-shell dashboard-layout">
|
||||
<aside className={'sidebar' + (sidebarCollapsed ? ' collapsed' : '')}>
|
||||
<div className="sidebar-content">
|
||||
<button
|
||||
className={'mode-segment-btn' + (d.mode === 'jndi' ? ' active' : '')}
|
||||
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"
|
||||
>
|
||||
JNDI EXP
|
||||
<JndiIcon />
|
||||
<span>JNDI EXP</span>
|
||||
</button>
|
||||
<button
|
||||
className={'mode-segment-btn' + (d.mode === 'gadget' ? ' active' : '')}
|
||||
className={'sidebar-nav-item' + (d.mode === 'gadget' ? ' active' : '')}
|
||||
onClick={() => d.switchMode('gadget')}
|
||||
title="Gadget"
|
||||
>
|
||||
Gadget
|
||||
<GadgetIcon />
|
||||
<span>Gadget</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="header-right">
|
||||
<div className="header-right sidebar-actions">
|
||||
<a className="wiki-btn" href="https://github.com/qi4L/JYso/wiki" target="_blank" rel="noopener noreferrer" title="Wiki">
|
||||
Wiki <ExternalLinkIcon />
|
||||
<WikiIcon />
|
||||
<span>Wiki <ExternalLinkIcon /></span>
|
||||
</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>
|
||||
<button className="logout-btn" onClick={d.logout} title="Logout">
|
||||
<LogoutIcon />
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main className="main-content">
|
||||
<div key={d.animKey}>
|
||||
{d.mode === 'jndi' && (
|
||||
<>
|
||||
<div className="glass-card section-enter" style={{ marginBottom: 20 }}>
|
||||
<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">
|
||||
<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,6 +478,7 @@ export default function Dashboard() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -423,6 +487,7 @@ 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>
|
||||
<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' }}
|
||||
@@ -430,6 +495,11 @@ export default function Dashboard() {
|
||||
>
|
||||
{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>
|
||||
)
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user