diff --git a/api-generator/pom.xml b/api-generator/pom.xml
index 948579c..88a8f65 100644
--- a/api-generator/pom.xml
+++ b/api-generator/pom.xml
@@ -18,4 +18,26 @@
1.0.0.0
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+
+ generate-js-client
+ generate-sources
+
+ java
+
+
+ ru.kirillius.XCP.ApiGenerator.JavascriptClientGenerator
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api-generator/src/main/java/ru/kirillius/XCP/ApiGenerator/JavascriptClientGenerator.java b/api-generator/src/main/java/ru/kirillius/XCP/ApiGenerator/JavascriptClientGenerator.java
new file mode 100644
index 0000000..f06292a
--- /dev/null
+++ b/api-generator/src/main/java/ru/kirillius/XCP/ApiGenerator/JavascriptClientGenerator.java
@@ -0,0 +1,198 @@
+package ru.kirillius.XCP.ApiGenerator;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JavascriptClientGenerator {
+
+ private static final String SERVICES_PACKAGE = "ru.kirillius.XCP.RPC.Services";
+ private static final String OUTPUT_DIR = "target/generated-sources/api.js";
+
+ public static void main(String[] args) {
+ try {
+ generateJavascriptClient();
+ System.out.println("JavaScript API client generated successfully at: " + OUTPUT_DIR);
+ } catch (Exception e) {
+ System.err.println("Failed to generate JavaScript client: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ public static void generateJavascriptClient() throws IOException, ClassNotFoundException {
+ StringBuilder jsBuilder = new StringBuilder();
+ jsBuilder.append("API = {\n");
+
+ List> serviceClasses = findServiceClasses();
+ boolean first = true;
+
+ for (Class> serviceClass : serviceClasses) {
+ if (!first) {
+ jsBuilder.append(",\n");
+ }
+ first = false;
+
+ String className = serviceClass.getSimpleName();
+ jsBuilder.append(" ").append(className).append(" : {\n");
+
+ Method[] methods = serviceClass.getDeclaredMethods();
+ List rpcMethods = new ArrayList<>();
+
+ for (Method method : methods) {
+ Annotation[] annotations = method.getAnnotations();
+ for (Annotation annotation : annotations) {
+ if (annotation.annotationType().getSimpleName().equals("JsonRpcMethod")) {
+ rpcMethods.add(method);
+ break;
+ }
+ }
+ }
+
+ for (int i = 0; i < rpcMethods.size(); i++) {
+ Method method = rpcMethods.get(i);
+ Annotation jsonRpcMethod = null;
+
+ for (Annotation annotation : method.getAnnotations()) {
+ if (annotation.annotationType().getSimpleName().equals("JsonRpcMethod")) {
+ jsonRpcMethod = annotation;
+ break;
+ }
+ }
+
+ generateMethod(jsBuilder, method, jsonRpcMethod, className);
+
+ if (i < rpcMethods.size() - 1) {
+ jsBuilder.append(",\n");
+ } else {
+ jsBuilder.append("\n");
+ }
+ }
+
+ jsBuilder.append(" }");
+ }
+
+ jsBuilder.append("\n}\n");
+
+ File outputFile = new File(OUTPUT_DIR);
+ outputFile.getParentFile().mkdirs();
+
+ try (FileWriter writer = new FileWriter(outputFile)) {
+ writer.write(jsBuilder.toString());
+ }
+ }
+
+ private static List> findServiceClasses() throws ClassNotFoundException {
+ List> serviceClasses = new ArrayList<>();
+
+ serviceClasses.add(Class.forName(SERVICES_PACKAGE + ".Auth"));
+ serviceClasses.add(Class.forName(SERVICES_PACKAGE + ".Profile"));
+ serviceClasses.add(Class.forName(SERVICES_PACKAGE + ".UserManagement"));
+
+ return serviceClasses;
+ }
+
+ private static void generateMethod(StringBuilder builder, Method method, Annotation jsonRpcMethod, String className) {
+ String methodName = method.getName();
+
+ builder.append(" /**\n");
+
+ try {
+ Object description = jsonRpcMethod.annotationType().getMethod("description").invoke(jsonRpcMethod);
+ builder.append(" * ").append(description).append("\n");
+
+ Object parameters = jsonRpcMethod.annotationType().getMethod("parameters").invoke(jsonRpcMethod);
+ if (parameters instanceof Object[]) {
+ Object[] params = (Object[]) parameters;
+ for (Object param : params) {
+ Class> paramClass = param.getClass();
+ Object name = paramClass.getMethod("name").invoke(param);
+ Object optional = paramClass.getMethod("optional").invoke(param);
+
+ builder.append(" * @param ").append(name);
+ if (!(Boolean) optional) {
+ builder.append(" (required)");
+ } else {
+ builder.append(" (optional)");
+ }
+ builder.append("\n");
+ }
+ }
+
+ Object returnType = jsonRpcMethod.annotationType().getMethod("returnType").invoke(jsonRpcMethod);
+ String returnTypeName = getSimpleTypeName((Class>) returnType);
+ builder.append(" * @returns ").append(returnTypeName).append("\n");
+ } catch (Exception e) {
+ builder.append(" * Method: ").append(methodName).append("\n");
+ builder.append(" * @returns unknown\n");
+ }
+
+ builder.append(" */\n");
+
+ builder.append(" ").append(methodName).append(" : async function(");
+
+ List paramNames = new ArrayList<>();
+ try {
+ Object parameters = jsonRpcMethod.annotationType().getMethod("parameters").invoke(jsonRpcMethod);
+ if (parameters instanceof Object[]) {
+ Object[] params = (Object[]) parameters;
+ for (Object param : params) {
+ Object name = param.getClass().getMethod("name").invoke(param);
+ Object optional = param.getClass().getMethod("optional").invoke(param);
+ if (!(Boolean) optional) {
+ paramNames.add(name.toString());
+ }
+ }
+ }
+ } catch (Exception e) {
+ // No parameters available
+ }
+
+ builder.append(String.join(", ", paramNames));
+ builder.append(") {\n");
+ builder.append(" return await JSONRPC.invoke(\"").append(className).append(".").append(methodName).append("\", {");
+
+ List paramPairs = new ArrayList<>();
+ try {
+ Object parameters = jsonRpcMethod.annotationType().getMethod("parameters").invoke(jsonRpcMethod);
+ if (parameters instanceof Object[]) {
+ Object[] params = (Object[]) parameters;
+ for (Object param : params) {
+ Object name = param.getClass().getMethod("name").invoke(param);
+ Object optional = param.getClass().getMethod("optional").invoke(param);
+ if (!(Boolean) optional) {
+ paramPairs.add(name.toString() + ":" + name.toString());
+ }
+ }
+ }
+ } catch (Exception e) {
+ // No parameters available
+ }
+
+ builder.append(String.join(", ", paramPairs));
+ builder.append("});\n");
+ builder.append(" }");
+ }
+
+ private static String getSimpleTypeName(Class> type) {
+ if (type == null) return "unknown";
+ if (type == boolean.class) return "boolean";
+ if (type == int.class || type == long.class) return "number";
+ if (type == String.class) return "string";
+ if (type == void.class || type == Void.class) return "void";
+
+ String typeName = type.getSimpleName();
+ if (type.isArray()) {
+ return "Array";
+ }
+
+ if (typeName.contains("Node") || typeName.contains("Object")) {
+ return "Object";
+ }
+
+ return typeName;
+ }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index ac1266d..08da4ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,6 +17,7 @@
web-server
app
logging
+ web-ui
diff --git a/web-ui/pom.xml b/web-ui/pom.xml
new file mode 100644
index 0000000..c28bccc
--- /dev/null
+++ b/web-ui/pom.xml
@@ -0,0 +1,20 @@
+
+
+ 4.0.0
+
+ ru.kirillius
+ XCP
+ 1.0.0.0
+
+
+ web-ui
+
+
+ 21
+ 21
+ UTF-8
+
+
+
\ No newline at end of file