diff --git a/src/main/frontend/src/api/index.js b/src/main/frontend/src/api/index.js
index 681563e..00e2dda 100644
--- a/src/main/frontend/src/api/index.js
+++ b/src/main/frontend/src/api/index.js
@@ -68,4 +68,8 @@ export function getConfig() {
return api.get('/config')
}
+export function getLogs(count) {
+ return api.get('/logs', { params: { count } })
+}
+
export default api
diff --git a/src/main/frontend/src/index.css b/src/main/frontend/src/index.css
index 2f5cc79..d66e5b6 100644
--- a/src/main/frontend/src/index.css
+++ b/src/main/frontend/src/index.css
@@ -533,6 +533,14 @@ code, pre, .mono {
transform: translateX(calc(100% + 2px));
}
+.tab-segment-control.tab-3::before {
+ transform: translateX(calc(200% + 4px));
+}
+
+.tab-segment-control.tabs-3::before {
+ width: calc(33.333% - 4px);
+}
+
.tab-segment-btn {
position: relative;
z-index: 1;
@@ -646,6 +654,8 @@ code, pre, .mono {
color: var(--text-secondary);
margin-top: 14px;
white-space: pre-wrap;
+ word-break: break-all;
+ overflow-wrap: break-word;
max-height: 160px;
overflow-y: auto;
}
@@ -658,6 +668,40 @@ input[type="checkbox"] {
cursor: pointer;
}
+.base64-toggle {
+ width: 44px;
+ height: 26px;
+ background: var(--bg-input);
+ border: 1px solid var(--border-color);
+ border-radius: 13px;
+ position: relative;
+ cursor: pointer;
+ transition: background var(--transition-fast), border-color var(--transition-fast);
+ outline: none;
+ flex-shrink: 0;
+}
+.base64-toggle:focus-visible {
+ box-shadow: 0 0 0 3px var(--accent-glow);
+}
+.base64-toggle.active {
+ background: var(--accent);
+ border-color: var(--accent);
+}
+.base64-toggle-thumb {
+ width: 20px;
+ height: 20px;
+ background: #FFFFFF;
+ border-radius: 50%;
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ transition: transform var(--transition-fast);
+ box-shadow: 0 1px 3px rgba(0,0,0,0.2);
+}
+.base64-toggle.active .base64-toggle-thumb {
+ transform: translateX(18px);
+}
+
label.form-group {
display: flex;
align-items: center;
@@ -667,6 +711,112 @@ label.form-group {
cursor: pointer;
}
+.copy-btn {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ width: 28px;
+ height: 28px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--bg-input);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ cursor: pointer;
+ color: var(--text-secondary);
+ transition: all var(--transition-fast);
+ padding: 0;
+}
+.copy-btn:hover {
+ background: var(--accent-glow);
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+.advanced-section {
+ background: var(--bg-input);
+ border: 1px solid var(--border-color);
+ border-radius: 14px;
+ padding: 14px 16px;
+ margin-bottom: 4px;
+}
+.adv-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px 16px;
+}
+.adv-check {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--text-secondary);
+ cursor: pointer;
+ padding: 4px 0;
+}
+.adv-check em {
+ font-style: normal;
+ font-family: var(--font-mono);
+ font-size: 10px;
+ color: var(--text-muted);
+}
+.adv-check input[type="checkbox"] {
+ width: 14px;
+ height: 14px;
+ margin: 0;
+ flex-shrink: 0;
+}
+
+select {
+ appearance: none;
+ -webkit-appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23999999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 10px center;
+ padding-right: 30px;
+}
+
+.log-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.log-container {
+ background: #0d1117;
+ border: 1px solid #30363d;
+ border-radius: 12px;
+ padding: 12px;
+ max-height: 380px;
+ overflow-y: auto;
+ font-family: var(--font-mono);
+ font-size: 11px;
+ line-height: 1.65;
+ transition: background var(--transition-fluid), border-color var(--transition-fluid);
+}
+
+.log-line {
+ color: #c9d1d9;
+ white-space: pre-wrap;
+ word-break: break-all;
+ padding: 1px 0;
+}
+
+.log-empty {
+ color: #8b949e;
+ text-align: center;
+ padding: 32px 0;
+ font-family: 'Inter', sans-serif;
+ font-size: 13px;
+}
+
@media (max-width: 768px) {
.page-shell { padding: 48px 16px 32px; }
.grid-2 { grid-template-columns: 1fr; }
diff --git a/src/main/frontend/src/pages/Dashboard.jsx b/src/main/frontend/src/pages/Dashboard.jsx
index a20839c..2b85852 100644
--- a/src/main/frontend/src/pages/Dashboard.jsx
+++ b/src/main/frontend/src/pages/Dashboard.jsx
@@ -1,9 +1,9 @@
-import { useState, useEffect, useMemo, useCallback } from 'react'
+import { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { useTheme } from '../context/ThemeContext'
import {
getStatus, toggleServer, getGadgets,
- generatePayload as apiGenerate, updateConfig, setAuthToken
+ generatePayload as apiGenerate, updateConfig, setAuthToken, getLogs
} from '../api'
export default function Dashboard() {
@@ -21,6 +21,18 @@ export default function Dashboard() {
const [selectedGadget, setSelectedGadget] = useState('')
const [payloadCmd, setPayloadCmd] = useState('')
const [payloadResult, setPayloadResult] = useState('')
+ const [encodeBase64, setEncodeBase64] = useState(true)
+ const [copied, setCopied] = useState(false)
+ const [showAdvanced, setShowAdvanced] = useState(false)
+ const [inherit, setInherit] = useState(false)
+ const [obscure, setObscure] = useState(false)
+ const [dcfp, setDcfp] = useState('')
+ const [dirtyType, setDirtyType] = useState('')
+ const [dirtyLength, setDirtyLength] = useState('')
+ const [noComSun, setNoComSun] = useState(false)
+ const [mozillaClassLoader, setMozillaClassLoader] = useState(false)
+ const [rhino, setRhino] = useState(false)
+ const [utf8Overlong, setUtf8Overlong] = useState(false)
const [payloadSubTab, setPayloadSubTab] = useState('gadget')
const [gadgetOpen, setGadgetOpen] = useState(false)
const [jndiPayloadResult, setJndiPayloadResult] = useState('')
@@ -35,6 +47,9 @@ export default function Dashboard() {
const [ldapsClassLoaderResult, setLdapsClassLoaderResult] = useState('')
const [routing, setRouting] = useState('ELProcessor')
const [routingOpen, setRoutingOpen] = useState(false)
+ const [logLines, setLogLines] = useState([])
+ const [logLoading, setLogLoading] = useState(false)
+ const logEndRef = useRef(null)
const ROUTING_OPTIONS = ['Basic', 'ELProcessor', 'Groovy', 'jdbcBypass1', 'jdbcBypass2', 'ldap2rmi', 'SnakeYaml', 'XStream', 'MemoryXXE']
@@ -120,7 +135,8 @@ export default function Dashboard() {
setLoading(true)
setMsg({ success: '', error: '' })
try {
- const res = await apiGenerate({ gadget: gadgetName, command: payloadCmd, filename: saveFilename })
+ const res = await apiGenerate({ gadget: gadgetName, command: payloadCmd, filename: saveFilename, encodeBase64,
+ inherit, obscure, dcfp, dirtyType, dirtyLength, noComSun, mozillaClassLoader, rhino, utf8Overlong })
if (res.data.success) {
setPayloadResult(res.data.message || 'Payload generated successfully')
setMsg({ success: 'Payload generated', error: '' })
@@ -133,6 +149,24 @@ export default function Dashboard() {
} finally { setLoading(false) }
}
+ async function handleCopyPayload() {
+ if (!payloadResult) return
+ try {
+ await navigator.clipboard.writeText(payloadResult)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ } catch { /* ignore */ }
+ }
+
+ async function copyText(text) {
+ if (!text) return
+ try {
+ await navigator.clipboard.writeText(text)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ } catch { /* ignore */ }
+ }
+
function handleGenerateJndiPayload() {
const gadgetName = (jndiGadgetInput || selectedGadget || '').trim()
if (!gadgetName || !payloadCmd) return
@@ -200,8 +234,26 @@ export default function Dashboard() {
navigate('/login', { replace: true })
}
+ async function fetchLogs() {
+ try {
+ const res = await getLogs(100)
+ setLogLines(res.data.logs || [])
+ } catch { /* ignore */ }
+ }
+
useEffect(() => { loadStatus(); loadGadgets() }, [])
+ useEffect(() => {
+ if (activeJndiTab !== 'logs') return
+ fetchLogs()
+ const id = setInterval(fetchLogs, 2000)
+ return () => clearInterval(id)
+ }, [activeJndiTab])
+
+ useEffect(() => {
+ if (logEndRef.current) logEndRef.current.scrollIntoView({ behavior: 'smooth' })
+ }, [logLines])
+
return (
@@ -293,7 +345,7 @@ export default function Dashboard() {
-
+
+
@@ -432,13 +490,40 @@ export default function Dashboard() {
{loading ? 'Generating...' : 'Generate'}
{jndiPayloadResult && (
-
{jndiPayloadResult}
+
+ {jndiPayloadResult}
+
+
)}
{rmiPayloadResult && (
-
{rmiPayloadResult}
+
+ {rmiPayloadResult}
+
+
)}
{ldapsPayloadResult && (
-
{ldapsPayloadResult}
+
+ {ldapsPayloadResult}
+
+
)}
)}
@@ -482,26 +567,152 @@ export default function Dashboard() {
{loading ? 'Generating...' : 'Generate'}
{classLoaderResult && (
-
{classLoaderResult}
+
+ {classLoaderResult}
+
+
)}
{rmiClassLoaderResult && (
-
{rmiClassLoaderResult}
+
+ {rmiClassLoaderResult}
+
+
)}
{ldapsClassLoaderResult && (
-
{ldapsClassLoaderResult}
+
+ {ldapsClassLoaderResult}
+
+
)}
)}
)}
+
+ {activeJndiTab === 'logs' && (
+
+
+ Server Events
+
+
+
+ {logLines.length === 0 ? (
+
No events yet. Start a server or wait for incoming requests.
+ ) : (
+ logLines.map((line, i) => (
+
{line}
+ ))
+ )}
+
+
+
+ )}
>
)}
{mode === 'gadget' && (
-
Payload Generator
-
+
+
Payload Generator
+
+
+
+
+
+
+ {showAdvanced && (
+
+ )}
+
+
{payloadResult && (
-
{payloadResult}
+
+ {payloadResult}
+
+
)}
)}
diff --git a/src/main/java/com/qi4l/JYso/web/ApiAuthFilter.java b/src/main/java/com/qi4l/JYso/web/ApiAuthFilter.java
new file mode 100644
index 0000000..80df8f7
--- /dev/null
+++ b/src/main/java/com/qi4l/JYso/web/ApiAuthFilter.java
@@ -0,0 +1,49 @@
+package com.qi4l.JYso.web;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class ApiAuthFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletRequest req = (HttpServletRequest) request;
+ HttpServletResponse resp = (HttpServletResponse) response;
+
+ String path = req.getRequestURI();
+
+ if (!path.startsWith("/api/") || path.equals("/api/auth/login")) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ String authHeader = req.getHeader("Authorization");
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ resp.setStatus(401);
+ resp.setContentType("application/json");
+ resp.getWriter().write("{\"error\":\"Unauthorized\"}");
+ return;
+ }
+
+ String token = authHeader.substring(7);
+ if (!AuthServlet.validateToken(token)) {
+ resp.setStatus(401);
+ resp.setContentType("application/json");
+ resp.getWriter().write("{\"error\":\"Invalid token\"}");
+ return;
+ }
+
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
diff --git a/src/main/java/com/qi4l/JYso/web/AuthServlet.java b/src/main/java/com/qi4l/JYso/web/AuthServlet.java
new file mode 100644
index 0000000..b66ad35
--- /dev/null
+++ b/src/main/java/com/qi4l/JYso/web/AuthServlet.java
@@ -0,0 +1,57 @@
+package com.qi4l.JYso.web;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.qi4l.JYso.web.config.JYsoWebPasswordProvider;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class AuthServlet extends HttpServlet {
+
+ static final ConcurrentHashMap
tokens = new ConcurrentHashMap<>();
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ resp.setContentType("application/json");
+ resp.setCharacterEncoding("UTF-8");
+
+ StringBuilder sb = new StringBuilder();
+ try (BufferedReader reader = req.getReader()) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ }
+ JSONObject body = JSON.parseObject(sb.toString());
+
+ String username = body.getString("username");
+ String password = body.getString("password");
+
+ if (!"qi".equals(username) || !JYsoWebPasswordProvider.getPassword().equals(password)) {
+ Map error = new HashMap<>();
+ error.put("error", "Invalid credentials");
+ resp.setStatus(401);
+ resp.getWriter().write(JSON.toJSONString(error));
+ return;
+ }
+
+ String token = UUID.randomUUID().toString();
+ tokens.put(token, username);
+ Map result = new HashMap<>();
+ result.put("token", token);
+ result.put("username", username);
+ resp.getWriter().write(JSON.toJSONString(result));
+ }
+
+ static boolean validateToken(String token) {
+ return token != null && tokens.containsKey(token);
+ }
+}
diff --git a/src/main/java/com/qi4l/JYso/web/JYsoWebApplication.java b/src/main/java/com/qi4l/JYso/web/JYsoWebApplication.java
index b4ede27..789b2cb 100644
--- a/src/main/java/com/qi4l/JYso/web/JYsoWebApplication.java
+++ b/src/main/java/com/qi4l/JYso/web/JYsoWebApplication.java
@@ -33,6 +33,8 @@ public class JYsoWebApplication {
public static void start(String[] args) {
logo();
+ RequestLogCollector.install();
+
webPassword = WebPasswordGenerator.generatePassword();
JYsoWebPasswordProvider.setPassword(webPassword);
@@ -47,7 +49,7 @@ public class JYsoWebApplication {
}
}
- System.out.println(ansi().render("@|green [+]|@ Web GUI starting >> http://127.0.0.1:" + webPort));
+ System.out.println(ansi().render("@|green [+]|@ Web GUI started on http://127.0.0.1:" + webPort));
System.out.println(ansi().render("@|yellow [+]|@ ============================================"));
System.out.println(ansi().render("@|yellow [+]|@ Username: qi"));
System.out.println(ansi().render("@|yellow [+]|@ Password: " + webPassword));
@@ -74,8 +76,6 @@ public class JYsoWebApplication {
context.addServlet(new ServletHolder(new StaticResourceServlet()), "/");
server.start();
- System.out.println(ansi().render("@|green [+]|@ Web GUI started on http://127.0.0.1:" + webPort));
-
CountDownLatch latch = new CountDownLatch(1);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("Shutting down...");
diff --git a/src/main/java/com/qi4l/JYso/web/JettyApiServlet.java b/src/main/java/com/qi4l/JYso/web/JettyApiServlet.java
new file mode 100644
index 0000000..41f99b4
--- /dev/null
+++ b/src/main/java/com/qi4l/JYso/web/JettyApiServlet.java
@@ -0,0 +1,368 @@
+package com.qi4l.JYso.web;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.qi4l.JYso.HTTPServer;
+import com.qi4l.JYso.LdapServer;
+import com.qi4l.JYso.LdapsServer;
+import com.qi4l.JYso.RMIServer;
+import com.qi4l.JYso.Starter;
+import com.qi4l.JYso.gadgets.Config.Config;
+import com.qi4l.JYso.gadgets.Config.ysoserial;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+
+public class JettyApiServlet extends HttpServlet {
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("application/json");
+ resp.setCharacterEncoding("UTF-8");
+
+ String path = req.getRequestURI();
+ if (path.startsWith("/api/")) {
+ path = path.substring(4);
+ }
+
+ try {
+ if ("/status".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
+ handleStatus(resp);
+ } else if ("/servers/start".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
+ handleServersStart(req, resp);
+ } else if ("/servers/stop".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
+ handleServersStop(req, resp);
+ } else if ("/servers/toggle".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
+ handleServersToggle(req, resp);
+ } else if ("/gadgets".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
+ handleGadgets(resp);
+ } else if ("/payload/generate".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
+ handlePayloadGenerate(req, resp);
+ } else if ("/config/update".equals(path) && "POST".equalsIgnoreCase(req.getMethod())) {
+ handleConfigUpdate(req, resp);
+ } else if ("/config".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
+ handleStatus(resp);
+ } else if ("/logs".equals(path) && "GET".equalsIgnoreCase(req.getMethod())) {
+ handleLogs(req, resp);
+ } else {
+ resp.setStatus(404);
+ resp.getWriter().write("{\"error\":\"Not found\"}");
+ }
+ } catch (Exception e) {
+ resp.setStatus(500);
+ resp.getWriter().write("{\"success\":false,\"error\":\"" + escapeJson(e.getMessage()) + "\"}");
+ }
+ }
+
+ private void handleStatus(HttpServletResponse resp) throws IOException {
+ Map status = new LinkedHashMap<>();
+ status.put("ldapRunning", LdapServer.isRunning);
+ status.put("ldapsRunning", LdapsServer.isRunning);
+ status.put("httpRunning", HTTPServer.isRunning);
+ status.put("rmiRunning", RMIServer.isRunning);
+ status.put("ip", Config.ip);
+ status.put("ldapPort", Config.ldapPort);
+ status.put("ldapsPort", Config.ldapsPort);
+ status.put("httpPort", Config.httpPort);
+ status.put("rmiPort", Config.rmiPort);
+ status.put("codeBase", Config.codeBase);
+ status.put("AESkey", Config.AESkey);
+ status.put("user", Config.USER);
+ status.put("PASSWD", Config.PASSWD);
+ status.put("TLSProxy", Config.TLSProxy);
+ status.put("keyPass", Config.keyPass);
+ status.put("certFile", Config.certFile);
+ status.put("version", "1.3.8");
+ resp.getWriter().write(JSON.toJSONString(status));
+ }
+
+ private void handleLogs(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ int count = 100;
+ String countParam = req.getParameter("count");
+ if (countParam != null) {
+ try { count = Integer.parseInt(countParam); } catch (NumberFormatException ignored) {}
+ }
+ List logLines = RequestLogCollector.getLines(count);
+ Map result = new LinkedHashMap<>();
+ result.put("logs", logLines);
+ resp.getWriter().write(JSON.toJSONString(result));
+ }
+
+ private void handleServersStart(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ JSONObject json = readJson(req);
+ Map result = new LinkedHashMap<>();
+ List started = new ArrayList<>();
+ List errors = new ArrayList<>();
+
+ boolean startLdap = json.getBooleanValue("ldap");
+ boolean startLdaps = json.getBooleanValue("ldaps");
+ boolean startHttp = json.getBooleanValue("http");
+ boolean startRmi = json.getBooleanValue("rmi");
+
+ if (json.containsKey("ip")) Config.ip = json.getString("ip");
+ if (json.containsKey("ldapPort")) Config.ldapPort = json.getIntValue("ldapPort");
+ if (json.containsKey("ldapsPort")) Config.ldapsPort = json.getIntValue("ldapsPort");
+ if (json.containsKey("httpPort")) Config.httpPort = json.getIntValue("httpPort");
+ if (json.containsKey("rmiPort")) Config.rmiPort = json.getIntValue("rmiPort");
+
+ if (startLdap && !LdapServer.isRunning) {
+ new Thread(() -> { try { LdapServer.start(); } catch (Exception ignored) {} }, "ldap-starter").start();
+ started.add("LDAP");
+ }
+ if (startHttp && !HTTPServer.isRunning) {
+ new Thread(() -> { try { HTTPServer.start(); } catch (Exception ignored) {} }, "http-starter").start();
+ started.add("HTTP");
+ }
+ if (startLdaps && !LdapsServer.isRunning) {
+ new Thread(() -> { try { LdapsServer.start(); } catch (Exception ignored) {} }, "ldaps-starter").start();
+ started.add("LDAPS");
+ }
+ if (startRmi && !RMIServer.isRunning) {
+ new Thread(() -> { try { RMIServer.start(); } catch (Exception ignored) {} }, "rmi-starter").start();
+ started.add("RMI");
+ }
+
+ result.put("started", started);
+ result.put("errors", errors);
+ result.put("success", true);
+ resp.getWriter().write(JSON.toJSONString(result));
+ }
+
+ private void handleServersStop(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ JSONObject json = readJson(req);
+ Map result = new LinkedHashMap<>();
+ String server = json.getString("server");
+ if (server == null) {
+ result.put("success", false);
+ result.put("error", "server name required");
+ resp.getWriter().write(JSON.toJSONString(result));
+ return;
+ }
+ switch (server.toLowerCase()) {
+ case "ldap": LdapServer.stop(); break;
+ case "ldaps": LdapsServer.stop(); break;
+ case "http": HTTPServer.stop(); break;
+ case "rmi": RMIServer.stop(); break;
+ default:
+ result.put("success", false);
+ result.put("error", "unknown server: " + server);
+ resp.getWriter().write(JSON.toJSONString(result));
+ return;
+ }
+ result.put("success", true);
+ result.put("server", server);
+ result.put("status", buildStatusMap());
+ resp.getWriter().write(JSON.toJSONString(result));
+ }
+
+ private void handleServersToggle(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ JSONObject json = readJson(req);
+ Map result = new LinkedHashMap<>();
+ String server = json.getString("server");
+ if (server == null) {
+ result.put("success", false);
+ result.put("error", "server name required");
+ resp.getWriter().write(JSON.toJSONString(result));
+ return;
+ }
+ boolean nowRunning;
+ switch (server.toLowerCase()) {
+ case "ldap":
+ if (LdapServer.isRunning) LdapServer.stop();
+ else new Thread(() -> { try { LdapServer.start(); } catch (Exception ignored) {} }, "ldap-toggler").start();
+ nowRunning = !LdapServer.isRunning;
+ break;
+ case "ldaps":
+ if (LdapsServer.isRunning) LdapsServer.stop();
+ else new Thread(() -> { try { LdapsServer.start(); } catch (Exception ignored) {} }, "ldaps-toggler").start();
+ nowRunning = !LdapsServer.isRunning;
+ break;
+ case "http":
+ if (HTTPServer.isRunning) HTTPServer.stop();
+ else new Thread(() -> { try { HTTPServer.start(); } catch (Exception ignored) {} }, "http-toggler").start();
+ nowRunning = !HTTPServer.isRunning;
+ break;
+ case "rmi":
+ if (RMIServer.isRunning) RMIServer.stop();
+ else new Thread(() -> { try { RMIServer.start(); } catch (Exception ignored) {} }, "rmi-toggler").start();
+ nowRunning = !RMIServer.isRunning;
+ break;
+ default:
+ result.put("success", false);
+ result.put("error", "unknown server: " + server);
+ resp.getWriter().write(JSON.toJSONString(result));
+ return;
+ }
+ try { Thread.sleep(800); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
+ result.put("success", true);
+ result.put("server", server);
+ result.put("running", nowRunning);
+ result.put("status", buildStatusMap());
+ resp.getWriter().write(JSON.toJSONString(result));
+ }
+
+ private void handleGadgets(HttpServletResponse resp) throws IOException {
+ List