forked from kimhyungsik/ax_hub_mcp_tool
feat: Add Developer Portal Web UI for scaffolding to Gateway
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.tool.util;
|
||||
package io.shinhanlife.axhub.common.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -31,7 +31,8 @@ public class PodScaffolder {
|
||||
String createDate = getOrAsk(args, 3, scanner, "4. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
||||
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
||||
|
||||
scaffoldPod(moduleName, portStr, shortName, author, createDate);
|
||||
String result = scaffoldPod(moduleName, portStr, shortName, author, createDate);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
|
||||
@@ -42,17 +43,17 @@ public class PodScaffolder {
|
||||
return scanner.nextLine().trim();
|
||||
}
|
||||
|
||||
private static void scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException {
|
||||
public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException {
|
||||
Path modulePath = Paths.get(moduleName);
|
||||
if (Files.exists(modulePath)) {
|
||||
System.out.println("[오류] 이미 존재하는 모듈입니다: " + moduleName);
|
||||
return;
|
||||
return "[오류] 이미 존재하는 모듈입니다: " + moduleName;
|
||||
}
|
||||
|
||||
System.out.println("\n[1/6] 모듈 디렉터리 생성 중...");
|
||||
StringBuilder log = new StringBuilder();
|
||||
log.append("[1/6] 모듈 디렉터리 생성 중...\n");
|
||||
Files.createDirectories(modulePath);
|
||||
|
||||
System.out.println("[2/6] build.gradle 생성 중...");
|
||||
log.append("[2/6] build.gradle 생성 중...\n");
|
||||
String buildGradle = """
|
||||
plugins {
|
||||
id 'org.springframework.boot'
|
||||
@@ -67,7 +68,7 @@ public class PodScaffolder {
|
||||
""";
|
||||
Files.writeString(modulePath.resolve("build.gradle"), buildGradle);
|
||||
|
||||
System.out.println("[3/6] Dockerfile 생성 중...");
|
||||
log.append("[3/6] Dockerfile 생성 중...\n");
|
||||
String dockerfile = """
|
||||
FROM eclipse-temurin:21-jdk-alpine
|
||||
WORKDIR /app
|
||||
@@ -76,7 +77,7 @@ public class PodScaffolder {
|
||||
""".formatted(moduleName);
|
||||
Files.writeString(modulePath.resolve("Dockerfile"), dockerfile);
|
||||
|
||||
System.out.println("[4/6] Application 클래스 및 설정 파일 생성 중...");
|
||||
log.append("[4/6] Application 클래스 및 설정 파일 생성 중...\n");
|
||||
Path srcPath = modulePath.resolve("src/main/java/io/shinhanlife/axhub/biz/mcp/tool/" + shortName);
|
||||
Files.createDirectories(srcPath);
|
||||
|
||||
@@ -200,7 +201,7 @@ public class PodScaffolder {
|
||||
""".formatted(moduleName, moduleName);
|
||||
Files.writeString(resPath.resolve("logback-spring.xml"), logbackXml);
|
||||
|
||||
System.out.println("[5/6] settings.gradle 에 모듈 등록 중...");
|
||||
log.append("[5/6] settings.gradle 에 모듈 등록 중...\n");
|
||||
Path settingsPath = Paths.get("settings.gradle");
|
||||
if (Files.exists(settingsPath)) {
|
||||
String settings = Files.readString(settingsPath);
|
||||
@@ -209,7 +210,7 @@ public class PodScaffolder {
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("[6/6] docker-compose.yml 에 서비스 추가 중...");
|
||||
log.append("[6/6] docker-compose.yml 에 서비스 추가 중...\n");
|
||||
Path dockerComposePath = Paths.get("docker-compose.yml");
|
||||
if (Files.exists(dockerComposePath)) {
|
||||
String compose = Files.readString(dockerComposePath);
|
||||
@@ -241,12 +242,13 @@ public class PodScaffolder {
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("\\n=========================================");
|
||||
System.out.println(" 🎉 Pod Scaffolding Complete! ");
|
||||
System.out.println("=========================================");
|
||||
System.out.println("1. [새로운 모듈] " + moduleName + " 폴더가 생성되었습니다.");
|
||||
System.out.println("2. [ToolScaffolder]를 사용해 이 모듈 안에 툴을 추가하세요.");
|
||||
System.out.println("3. 실행 전 Gradle 동기화(Sync)를 한 번 진행해 주세요.");
|
||||
log.append("\n=========================================\n");
|
||||
log.append(" 🎉 Pod Scaffolding Complete! \n");
|
||||
log.append("=========================================\n");
|
||||
log.append("1. [새로운 모듈] ").append(moduleName).append(" 폴더가 생성되었습니다.\n");
|
||||
log.append("2. [ToolScaffolder]를 사용해 이 모듈 안에 툴을 추가하세요.\n");
|
||||
log.append("3. 실행 전 Gradle 동기화(Sync)를 한 번 진행해 주세요.\n");
|
||||
return log.toString();
|
||||
}
|
||||
|
||||
private static String capitalize(String str) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.tool.util;
|
||||
package io.shinhanlife.axhub.common.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -68,7 +68,8 @@ public class ToolScaffolder {
|
||||
String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
||||
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
||||
|
||||
scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate);
|
||||
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) {
|
||||
@@ -79,13 +80,15 @@ public class ToolScaffolder {
|
||||
return scanner.nextLine().trim();
|
||||
}
|
||||
|
||||
private static void scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate) throws IOException {
|
||||
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate) throws IOException {
|
||||
Path serviceDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "service");
|
||||
Path dtoDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "dto");
|
||||
|
||||
Files.createDirectories(serviceDir);
|
||||
Files.createDirectories(dtoDir);
|
||||
|
||||
StringBuilder log = new StringBuilder();
|
||||
|
||||
// Generate Req DTO
|
||||
String reqContent = """
|
||||
package %s.dto;
|
||||
@@ -212,16 +215,18 @@ public class ToolScaffolder {
|
||||
""".formatted(toolName, description, description + " 해줘.", interfaceId);
|
||||
yamlContent = yamlContent.replaceFirst("functions:", "functions:" + newFunctionYaml);
|
||||
Files.writeString(yamlPath, yamlContent);
|
||||
System.out.println("[YAML] " + yamlPath + " (함수 설정 자동 등록됨)");
|
||||
log.append("[YAML] ").append(yamlPath).append(" (함수 설정 자동 등록됨)\n");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("\n=========================================");
|
||||
System.out.println(" Scaffolding Complete!");
|
||||
System.out.println("=========================================");
|
||||
System.out.println("[Service] " + serviceDir.resolve(baseName + "Service.java"));
|
||||
System.out.println("[Req DTO] " + dtoDir.resolve(baseName + "Req.java"));
|
||||
System.out.println("[Res DTO] " + dtoDir.resolve(baseName + "Res.java"));
|
||||
System.out.println("\n Tip: " + interfaceId + " 목업 데이터를 mock-responses.json에 추가하세요.");
|
||||
log.append("\n=========================================\n");
|
||||
log.append(" Scaffolding Complete!\n");
|
||||
log.append("=========================================\n");
|
||||
log.append("[Service] ").append(serviceDir.resolve(baseName + "Service.java")).append("\n");
|
||||
log.append("[Req DTO] ").append(dtoDir.resolve(baseName + "Req.java")).append("\n");
|
||||
log.append("[Res DTO] ").append(dtoDir.resolve(baseName + "Res.java")).append("\n");
|
||||
log.append("\n Tip: ").append(interfaceId).append(" 목업 데이터를 mock-responses.json에 추가하세요.\n");
|
||||
|
||||
return log.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.shinhanlife.axhub.biz.mcp.gateway.presentation;
|
||||
|
||||
import io.shinhanlife.axhub.common.util.PodScaffolder;
|
||||
import io.shinhanlife.axhub.common.util.ToolScaffolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/scaffold")
|
||||
public class ScaffoldingController {
|
||||
|
||||
@PostMapping("/pod")
|
||||
public String scaffoldPod(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String moduleName = req.getOrDefault("moduleName", "axhub-tool-other");
|
||||
if (!moduleName.startsWith("axhub-tool-")) moduleName = "axhub-tool-" + moduleName;
|
||||
String port = req.getOrDefault("port", "8085");
|
||||
String shortName = moduleName.replace("axhub-tool-", "").replace("-", "");
|
||||
String author = req.getOrDefault("author", "System");
|
||||
String date = req.getOrDefault("date", java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd")));
|
||||
|
||||
return PodScaffolder.scaffoldPod(moduleName, port, shortName, author, date);
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/tool")
|
||||
public String scaffoldTool(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String baseName = req.get("baseName");
|
||||
String interfaceId = req.get("interfaceId");
|
||||
String description = req.get("description");
|
||||
String group = req.getOrDefault("group", "COMMON");
|
||||
String routingType = req.getOrDefault("routingType", "HTTP");
|
||||
String moduleName = req.getOrDefault("moduleName", "axhub-tool-other");
|
||||
String author = req.getOrDefault("author", "System");
|
||||
String date = req.getOrDefault("date", java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd")));
|
||||
|
||||
return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date);
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
157
axhub-gateway/src/main/resources/static/admin/scaffold.html
Normal file
157
axhub-gateway/src/main/resources/static/admin/scaffold.html
Normal file
@@ -0,0 +1,157 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>AXHUB Developer Portal - Scaffolding</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f8f9fa; }
|
||||
.container { max-width: 800px; margin-top: 50px; }
|
||||
.card { box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
|
||||
.nav-tabs .nav-link.active { font-weight: bold; color: #0d6efd; }
|
||||
#resultBox { display: none; margin-top: 20px; white-space: pre-wrap; font-family: monospace; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h2 class="mb-4">🚀 AXHUB Developer Portal</h2>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="tool-tab" data-bs-toggle="tab" data-bs-target="#tool" type="button" role="tab">Tool (기능) 생성</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="pod-tab" data-bs-toggle="tab" data-bs-target="#pod" type="button" role="tab">Pod (모듈) 생성</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content" id="myTabContent">
|
||||
|
||||
<!-- Tool Creation Form -->
|
||||
<div class="tab-pane fade show active" id="tool" role="tabpanel">
|
||||
<form id="toolForm">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">기본 이름 (PascalCase)</label>
|
||||
<input type="text" class="form-control" name="baseName" placeholder="예: ExchangeRate" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">레거시 API 인터페이스 ID</label>
|
||||
<input type="text" class="form-control" name="interfaceId" placeholder="예: EXCH_001" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">기능 설명</label>
|
||||
<input type="text" class="form-control" name="description" placeholder="예: 환율 조회 기능" required>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">소속 그룹</label>
|
||||
<select class="form-select" name="group">
|
||||
<option value="COMMON">COMMON</option>
|
||||
<option value="CUSTOMER">CUSTOMER</option>
|
||||
<option value="CONTRACT">CONTRACT</option>
|
||||
<option value="CLAIM">CLAIM</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">통신 프로토콜</label>
|
||||
<select class="form-select" name="routingType">
|
||||
<option value="HTTP">HTTP</option>
|
||||
<option value="TCP">TCP</option>
|
||||
<option value="MCI">MCI</option>
|
||||
<option value="EAI">EAI</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">타겟 모듈명</label>
|
||||
<input type="text" class="form-control" name="moduleName" value="axhub-tool-other" required>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">작성자</label>
|
||||
<input type="text" class="form-control" name="author" placeholder="시스템 사용자명">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">작성일</label>
|
||||
<input type="text" class="form-control" name="date" placeholder="오늘 날짜">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Tool 자동 생성</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Pod Creation Form -->
|
||||
<div class="tab-pane fade" id="pod" role="tabpanel">
|
||||
<form id="podForm">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">새로운 모듈 이름</label>
|
||||
<input type="text" class="form-control" name="moduleName" placeholder="예: hr (axhub-tool-hr 로 자동 변환됨)" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">포트 번호</label>
|
||||
<input type="number" class="form-control" name="port" placeholder="예: 8086" required>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">작성자</label>
|
||||
<input type="text" class="form-control" name="author" placeholder="시스템 사용자명">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">작성일</label>
|
||||
<input type="text" class="form-control" name="date" placeholder="오늘 날짜">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success w-100">Pod 자동 생성</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resultBox" class="alert alert-info"></div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
function handleFormSubmit(formId, apiUrl) {
|
||||
document.getElementById(formId).addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(result => {
|
||||
const resultBox = document.getElementById('resultBox');
|
||||
resultBox.style.display = 'block';
|
||||
resultBox.className = 'alert alert-success';
|
||||
resultBox.textContent = result;
|
||||
|
||||
// Add a gentle reminder to run gradle build
|
||||
resultBox.textContent += "\n\n💡 변경사항 반영을 위해 IDE에서 Gradle 리로드(Sync)를 수행해주세요!";
|
||||
})
|
||||
.catch(error => {
|
||||
const resultBox = document.getElementById('resultBox');
|
||||
resultBox.style.display = 'block';
|
||||
resultBox.className = 'alert alert-danger';
|
||||
resultBox.textContent = "오류 발생: " + error;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleFormSubmit('toolForm', '/api/v1/scaffold/tool');
|
||||
handleFormSubmit('podForm', '/api/v1/scaffold/pod');
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user