mirror of
https://github.com/qi4L/JYso.git
synced 2026-09-21 22:40:43 +08:00
perf:前端大改4
This commit is contained in:
@@ -1,13 +1,18 @@
|
||||
import axios from 'axios'
|
||||
|
||||
let authToken = null
|
||||
let authToken = localStorage.getItem('jyso_token')
|
||||
|
||||
export function setAuthToken(token) {
|
||||
authToken = token
|
||||
if (token) {
|
||||
localStorage.setItem('jyso_token', token)
|
||||
} else {
|
||||
localStorage.removeItem('jyso_token')
|
||||
}
|
||||
}
|
||||
|
||||
export function getAuthToken() {
|
||||
return authToken
|
||||
return authToken || localStorage.getItem('jyso_token')
|
||||
}
|
||||
|
||||
const api = axios.create({
|
||||
@@ -27,6 +32,7 @@ api.interceptors.response.use(
|
||||
error => {
|
||||
if (error.response && error.response.status === 401) {
|
||||
authToken = null
|
||||
localStorage.removeItem('jyso_token')
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
@@ -72,4 +78,16 @@ export function getLogs(count) {
|
||||
return api.get('/logs', { params: { count } })
|
||||
}
|
||||
|
||||
export function getFiles() {
|
||||
return api.get('/files')
|
||||
}
|
||||
|
||||
export function downloadFile(name) {
|
||||
return api.get('/files/download', { params: { name }, responseType: 'blob' })
|
||||
}
|
||||
|
||||
export function deleteFile(name) {
|
||||
return api.post('/files/delete', { name })
|
||||
}
|
||||
|
||||
export default api
|
||||
|
||||
@@ -825,6 +825,87 @@ select {
|
||||
.login-inner { padding: 36px 28px 32px; }
|
||||
}
|
||||
|
||||
.wiki-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 14px;
|
||||
border-radius: var(--btn-radius);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
.wiki-btn:hover {
|
||||
background: var(--accent-glow);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.file-list {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.file-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-size: 12px;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
.file-row:last-child { border-bottom: none; }
|
||||
.file-row:hover { background: var(--bg-primary); }
|
||||
.file-name {
|
||||
flex: 1;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.file-size {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
min-width: 45px;
|
||||
text-align: right;
|
||||
}
|
||||
.file-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
.file-btn:hover {
|
||||
background: var(--accent-glow);
|
||||
color: var(--accent);
|
||||
}
|
||||
.file-btn-del:hover {
|
||||
background: var(--danger-bg);
|
||||
color: var(--danger);
|
||||
}
|
||||
.file-empty {
|
||||
text-align: center;
|
||||
padding: 28px 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
transition-duration: 0ms !important;
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useNavigate } from 'react-router-dom'
|
||||
import { useTheme } from '../context/ThemeContext'
|
||||
import {
|
||||
getStatus, toggleServer, getGadgets,
|
||||
generatePayload as apiGenerate, updateConfig, setAuthToken, getLogs
|
||||
generatePayload as apiGenerate, updateConfig, setAuthToken, getLogs,
|
||||
getFiles, downloadFile, deleteFile as apiDeleteFile
|
||||
} from '../api'
|
||||
|
||||
export default function Dashboard() {
|
||||
@@ -50,6 +51,8 @@ export default function Dashboard() {
|
||||
const [logLines, setLogLines] = useState([])
|
||||
const [logLoading, setLogLoading] = useState(false)
|
||||
const logEndRef = useRef(null)
|
||||
const [files, setFiles] = useState([])
|
||||
const [filesLoading, setFilesLoading] = useState(false)
|
||||
|
||||
const ROUTING_OPTIONS = ['Basic', 'ELProcessor', 'Groovy', 'jdbcBypass1', 'jdbcBypass2', 'ldap2rmi', 'SnakeYaml', 'XStream', 'MemoryXXE']
|
||||
|
||||
@@ -140,6 +143,7 @@ export default function Dashboard() {
|
||||
if (res.data.success) {
|
||||
setPayloadResult(res.data.message || 'Payload generated successfully')
|
||||
setMsg({ success: 'Payload generated', error: '' })
|
||||
if (res.data.saved) fetchFiles()
|
||||
} else {
|
||||
setPayloadResult('Error: ' + (res.data.error || 'unknown'))
|
||||
setMsg({ success: '', error: res.data.error || 'Generation failed' })
|
||||
@@ -243,6 +247,38 @@ export default function Dashboard() {
|
||||
|
||||
useEffect(() => { loadStatus(); loadGadgets() }, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== 'gadget') return
|
||||
fetchFiles()
|
||||
}, [mode])
|
||||
|
||||
async function fetchFiles() {
|
||||
try {
|
||||
const res = await getFiles()
|
||||
setFiles(res.data)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function handleDownloadFile(name) {
|
||||
try {
|
||||
const res = await downloadFile(name)
|
||||
const url = window.URL.createObjectURL(new Blob([res.data]))
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = name
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function handleDeleteFile(name) {
|
||||
if (!confirm(`Delete ${name}?`)) return
|
||||
try {
|
||||
await apiDeleteFile(name)
|
||||
fetchFiles()
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (activeJndiTab !== 'logs') return
|
||||
fetchLogs()
|
||||
@@ -280,6 +316,9 @@ export default function Dashboard() {
|
||||
</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
|
||||
</a>
|
||||
<button
|
||||
className="theme-toggle"
|
||||
onClick={toggleTheme}
|
||||
@@ -633,8 +672,9 @@ export default function Dashboard() {
|
||||
)}
|
||||
|
||||
{mode === 'gadget' && (
|
||||
<div className="glass-card section-enter">
|
||||
<div className="header" style={{ padding: 0, marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', gap: 'var(--card-gap)', alignItems: 'flex-start' }}>
|
||||
<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${showAdvanced ? ' active-tab' : ''}`}
|
||||
@@ -766,6 +806,34 @@ export default function Dashboard() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="glass-card section-enter" style={{ flex: 2, marginBottom: 0 }}>
|
||||
<div className="header" style={{ padding: 0, marginBottom: 16 }}>
|
||||
<h2>File Manager</h2>
|
||||
<button className="btn btn-secondary" style={{ padding: '4px 10px', fontSize: 11, width: 'auto' }}
|
||||
onClick={fetchFiles} disabled={filesLoading}>
|
||||
{filesLoading ? 'Loading...' : 'Refresh'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="file-list">
|
||||
{files.length === 0 ? (
|
||||
<div className="file-empty">No saved files</div>
|
||||
) : (
|
||||
files.map(f => (
|
||||
<div key={f.name} className="file-row">
|
||||
<span className="file-name" title={f.name}>{f.name}</span>
|
||||
<span className="file-size">{f.size > 1024 ? (f.size / 1024).toFixed(1) + ' KB' : f.size + ' B'}</span>
|
||||
<button className="file-btn" onClick={() => handleDownloadFile(f.name)} title="Download">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 2v8M4 7l4 4 4-4M2 13h12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
</button>
|
||||
<button className="file-btn file-btn-del" onClick={() => handleDeleteFile(f.name)} title="Delete">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M3 5h10M6 5V3h4v2M5 5v8h6V5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -52,6 +52,12 @@ public class JettyApiServlet extends HttpServlet {
|
||||
handleStatus(resp);
|
||||
} else if ("/logs".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
|
||||
handleLogs(req, resp);
|
||||
} else if ("/files".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
|
||||
handleFileList(resp);
|
||||
} else if ("/files/download".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
|
||||
handleFileDownload(req, resp);
|
||||
} else if ("/files/delete".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
|
||||
handleFileDelete(req, resp);
|
||||
} else {
|
||||
resp.setStatus(404);
|
||||
resp.getWriter().write("{\"error\":\"Not found\"}");
|
||||
@@ -96,6 +102,63 @@ public class JettyApiServlet extends HttpServlet {
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
}
|
||||
|
||||
private void handleFileList(HttpServletResponse resp) throws IOException {
|
||||
java.io.File dir = new java.io.File(".");
|
||||
java.io.File[] files = dir.listFiles(f -> f.isFile() && f.getName().endsWith(".ser"));
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
if (files != null) {
|
||||
for (java.io.File f : files) {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("name", f.getName());
|
||||
item.put("size", f.length());
|
||||
item.put("modified", f.lastModified());
|
||||
list.add(item);
|
||||
}
|
||||
}
|
||||
list.sort((a, b) -> Long.compare((long) b.get("modified"), (long) a.get("modified")));
|
||||
resp.getWriter().write(JSON.toJSONString(list));
|
||||
}
|
||||
|
||||
private void handleFileDownload(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
String name = req.getParameter("name");
|
||||
if (name == null || name.isEmpty()) {
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().write("{\"error\":\"name required\"}");
|
||||
return;
|
||||
}
|
||||
Path filePath = Paths.get(name);
|
||||
if (!Files.exists(filePath)) {
|
||||
resp.setStatus(404);
|
||||
resp.getWriter().write("{\"error\":\"file not found\"}");
|
||||
return;
|
||||
}
|
||||
resp.setContentType("application/octet-stream");
|
||||
resp.setHeader("Content-Disposition", "attachment; filename=\"" + name + "\"");
|
||||
resp.setContentLengthLong(Files.size(filePath));
|
||||
Files.copy(filePath, resp.getOutputStream());
|
||||
}
|
||||
|
||||
private void handleFileDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
JSONObject json = readJson(req);
|
||||
String name = json.getString("name");
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
if (name == null || name.isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("error", "name required");
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
return;
|
||||
}
|
||||
Path filePath = Paths.get(name);
|
||||
if (!Files.exists(filePath)) {
|
||||
result.put("success", false);
|
||||
result.put("error", "file not found");
|
||||
} else {
|
||||
Files.delete(filePath);
|
||||
result.put("success", true);
|
||||
}
|
||||
resp.getWriter().write(JSON.toJSONString(result));
|
||||
}
|
||||
|
||||
private void handleServersStart(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
JSONObject json = readJson(req);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
|
||||
Reference in New Issue
Block a user