forked from kimhyungsik/ax_hub_mcp_tool
feat: Tool metadata register flag and dynamic source updater UI
This commit is contained in:
@@ -55,11 +55,24 @@ public class ToolMetadata {
|
|||||||
private String podUrl;
|
private String podUrl;
|
||||||
|
|
||||||
// 2-4. 가시성 여부
|
// 2-4. 가시성 여부
|
||||||
private boolean visible = true;
|
@Builder.Default
|
||||||
|
private Boolean visible = true;
|
||||||
|
|
||||||
// 2-5. Redis 등록 여부 (UI 표출용)
|
// 2-5. Redis 등록 여부 (UI 표출용)
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private boolean isRegistered = true;
|
private Boolean isRegistered = true;
|
||||||
|
|
||||||
|
public boolean isVisible() {
|
||||||
|
return visible != null ? visible : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRegistered() {
|
||||||
|
return isRegistered != null ? isRegistered : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegistered(Boolean isRegistered) {
|
||||||
|
this.isRegistered = isRegistered;
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI)
|
// 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI)
|
||||||
private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI"
|
private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI"
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class ToolScaffolder {
|
|||||||
String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
String createDate = getOrAsk(args, 7, scanner, "8. 작성일 (엔터 입력 시 '" + defaultDate + "'): ");
|
||||||
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
if (createDate.trim().isEmpty()) createDate = defaultDate;
|
||||||
|
|
||||||
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate);
|
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true);
|
||||||
System.out.println(result);
|
System.out.println(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ public class ToolScaffolder {
|
|||||||
return scanner.nextLine().trim();
|
return scanner.nextLine().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String 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, boolean register) throws IOException {
|
||||||
Path serviceDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "service");
|
Path serviceDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "service");
|
||||||
Path dtoDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "dto");
|
Path dtoDir = Paths.get(moduleName, BASE_PACKAGE_PATH, "dto");
|
||||||
|
|
||||||
@@ -186,39 +186,55 @@ public class ToolScaffolder {
|
|||||||
name = "%s",
|
name = "%s",
|
||||||
description = "%s",
|
description = "%s",
|
||||||
prompt = "%s",
|
prompt = "%s",
|
||||||
mappingId = "%s"
|
mappingId = "%s",
|
||||||
|
register = %s
|
||||||
)
|
)
|
||||||
public Object execute(%sReq req) {
|
public Object execute(%sReq req) {
|
||||||
return executeLegacy("%s", "%s", req);
|
return executeLegacy("%s", "%s", req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""".formatted(
|
""".formatted(
|
||||||
|
BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE,
|
||||||
BASE_PACKAGE, baseName, author, createDate, createDate, author,
|
BASE_PACKAGE, baseName, author, createDate, createDate, author,
|
||||||
routingType, group, baseName,
|
routingType, group, baseName,
|
||||||
toolName, description, description + " 해줘.", interfaceId,
|
toolName, description, description + " 해줘.", interfaceId, register,
|
||||||
baseName, routingType, interfaceId
|
baseName, routingType, interfaceId
|
||||||
);
|
);
|
||||||
|
|
||||||
Files.writeString(serviceDir.resolve(baseName + "Service.java"), serviceContent);
|
Files.writeString(serviceDir.resolve(baseName + "Service.java"), serviceContent);
|
||||||
|
|
||||||
// Append to YAML if exists
|
// Append to YAML
|
||||||
Path yamlPath = Paths.get(moduleName, "src/main/resources", "application-local.yml");
|
Path resourcesDir = Paths.get(moduleName, "src/main/resources");
|
||||||
|
Files.createDirectories(resourcesDir);
|
||||||
|
Path yamlPath = resourcesDir.resolve("application-local.yml");
|
||||||
|
|
||||||
|
String yamlContent = "";
|
||||||
if (Files.exists(yamlPath)) {
|
if (Files.exists(yamlPath)) {
|
||||||
String yamlContent = Files.readString(yamlPath);
|
yamlContent = Files.readString(yamlPath);
|
||||||
if (yamlContent.contains("functions:")) {
|
|
||||||
String newFunctionYaml = """
|
|
||||||
|
|
||||||
%s:
|
|
||||||
description: "%s"
|
|
||||||
prompt: "%s"
|
|
||||||
mappingId: "%s"
|
|
||||||
""".formatted(toolName, description, description + " 해줘.", interfaceId);
|
|
||||||
yamlContent = yamlContent.replaceFirst("functions:", "functions:" + newFunctionYaml);
|
|
||||||
Files.writeString(yamlPath, yamlContent);
|
|
||||||
log.append("[YAML] ").append(yamlPath).append(" (함수 설정 자동 등록됨)\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String newFunctionYaml = """
|
||||||
|
|
||||||
|
%s:
|
||||||
|
description: "%s"
|
||||||
|
prompt: "%s"
|
||||||
|
mappingId: "%s"
|
||||||
|
""".formatted(toolName, description, description + " 해줘.", interfaceId);
|
||||||
|
|
||||||
|
if (yamlContent.contains("functions:")) {
|
||||||
|
yamlContent = yamlContent.replaceFirst("functions:", "functions:" + newFunctionYaml);
|
||||||
|
} else {
|
||||||
|
if (!yamlContent.isEmpty() && !yamlContent.endsWith("\n")) {
|
||||||
|
yamlContent += "\n";
|
||||||
|
}
|
||||||
|
yamlContent += """
|
||||||
|
mcp:
|
||||||
|
functions:%s""".formatted(newFunctionYaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.writeString(yamlPath, yamlContent);
|
||||||
|
log.append("[YAML] ").append(yamlPath).append(" (함수 설정 자동 등록됨)\n");
|
||||||
|
|
||||||
log.append("\n=========================================\n");
|
log.append("\n=========================================\n");
|
||||||
log.append(" Scaffolding Complete!\n");
|
log.append(" Scaffolding Complete!\n");
|
||||||
log.append("=========================================\n");
|
log.append("=========================================\n");
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package io.shinhanlife.axhub.common.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class ToolSourceUpdater {
|
||||||
|
|
||||||
|
public static void updateToolSource(String toolName, String domainGroup, String description, boolean register) throws Exception {
|
||||||
|
// 1. Find all *Service.java files in axhub-tool-* directories
|
||||||
|
Path rootDir = Paths.get(".");
|
||||||
|
|
||||||
|
List<Path> javaFiles;
|
||||||
|
try (Stream<Path> paths = Files.walk(rootDir)) {
|
||||||
|
javaFiles = paths
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.filter(p -> p.toString().endsWith("Service.java"))
|
||||||
|
.filter(p -> p.toString().contains("axhub-tool-"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
Path targetFile = null;
|
||||||
|
String content = null;
|
||||||
|
|
||||||
|
// 2. Find the specific file for the tool
|
||||||
|
Pattern namePattern = Pattern.compile("@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\"", Pattern.DOTALL);
|
||||||
|
|
||||||
|
for (Path path : javaFiles) {
|
||||||
|
String text = Files.readString(path);
|
||||||
|
if (namePattern.matcher(text).find()) {
|
||||||
|
targetFile = path;
|
||||||
|
content = text;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetFile == null) {
|
||||||
|
throw new Exception("소스 코드를 찾을 수 없습니다: " + toolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Update @McpTool group
|
||||||
|
if (domainGroup != null && !domainGroup.trim().isEmpty()) {
|
||||||
|
Pattern groupPattern = Pattern.compile("(@McpTool\\\\s*\\\\([^)]*group\\\\s*=\\\\s*\\\")([^\\\"]+)(\\\")", Pattern.DOTALL);
|
||||||
|
Matcher groupMatcher = groupPattern.matcher(content);
|
||||||
|
if (groupMatcher.find()) {
|
||||||
|
content = groupMatcher.replaceFirst("$1" + domainGroup + "$3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Update @McpFunction description
|
||||||
|
if (description != null) {
|
||||||
|
Pattern funcPattern = Pattern.compile("(@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\"[^)]*description\\\\s*=\\\\s*\\\")([^\\\"]+)(\\\")", Pattern.DOTALL);
|
||||||
|
Matcher funcMatcher = funcPattern.matcher(content);
|
||||||
|
if (funcMatcher.find()) {
|
||||||
|
content = funcMatcher.replaceFirst("$1" + description.replace("\\", "\\\\").replace("$", "\\\\$") + "$3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Update register flag
|
||||||
|
Pattern regPattern = Pattern.compile("(@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\"[^)]*register\\\\s*=\\\\s*)(true|false)([^a-zA-Z0-9])", Pattern.DOTALL);
|
||||||
|
Matcher regMatcher = regPattern.matcher(content);
|
||||||
|
if (regMatcher.find()) {
|
||||||
|
content = regMatcher.replaceFirst("$1" + register + "$3");
|
||||||
|
} else {
|
||||||
|
Pattern addRegPattern = Pattern.compile("(@McpFunction\\\\s*\\\\([^)]*name\\\\s*=\\\\s*\\\"" + Pattern.quote(toolName) + "\\\")", Pattern.DOTALL);
|
||||||
|
Matcher addRegMatcher = addRegPattern.matcher(content);
|
||||||
|
if (addRegMatcher.find()) {
|
||||||
|
content = addRegMatcher.replaceFirst("$1, register = " + register);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Write back to file
|
||||||
|
Files.writeString(targetFile, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,11 +55,24 @@ public class ToolMetadata {
|
|||||||
private String podUrl;
|
private String podUrl;
|
||||||
|
|
||||||
// 2-4. 가시성 여부
|
// 2-4. 가시성 여부
|
||||||
private boolean visible = true;
|
@Builder.Default
|
||||||
|
private Boolean visible = true;
|
||||||
|
|
||||||
// 2-5. Redis 등록 여부 (UI 표출용)
|
// 2-5. Redis 등록 여부 (UI 표출용)
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private boolean isRegistered = true;
|
private Boolean isRegistered = true;
|
||||||
|
|
||||||
|
public boolean isVisible() {
|
||||||
|
return visible != null ? visible : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRegistered() {
|
||||||
|
return isRegistered != null ? isRegistered : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegistered(Boolean isRegistered) {
|
||||||
|
this.isRegistered = isRegistered;
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI)
|
// 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI)
|
||||||
private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI"
|
private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI"
|
||||||
|
|||||||
@@ -36,8 +36,24 @@ public class ScaffoldingController {
|
|||||||
String moduleName = req.getOrDefault("moduleName", "axhub-tool-other");
|
String moduleName = req.getOrDefault("moduleName", "axhub-tool-other");
|
||||||
String author = req.getOrDefault("author", "System");
|
String author = req.getOrDefault("author", "System");
|
||||||
String date = req.getOrDefault("date", java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd")));
|
String date = req.getOrDefault("date", java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd")));
|
||||||
|
boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true"));
|
||||||
|
|
||||||
return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date);
|
return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date, register);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "오류 발생: " + e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/tool/update")
|
||||||
|
public String updateTool(@RequestBody Map<String, String> req) {
|
||||||
|
try {
|
||||||
|
String toolName = req.get("toolName");
|
||||||
|
String domainGroup = req.get("domainGroup");
|
||||||
|
String description = req.get("description");
|
||||||
|
boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true"));
|
||||||
|
|
||||||
|
io.shinhanlife.axhub.common.util.ToolSourceUpdater.updateToolSource(toolName, domainGroup, description, register);
|
||||||
|
return "성공";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return "오류 발생: " + e.getMessage();
|
return "오류 발생: " + e.getMessage();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,11 +301,21 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="row mb-4">
|
||||||
<label class="form-label">타겟 모듈명</label>
|
<div class="col-md-6">
|
||||||
<input type="text" class="form-control" name="moduleName" value="axhub-tool-other" required>
|
<label class="form-label">타겟 모듈명</label>
|
||||||
<div class="input-hint">코드가 생성될 대상 프로젝트(Pod) 폴더명입니다.</div>
|
<input type="text" class="form-control" name="moduleName" value="axhub-tool-other" required>
|
||||||
|
<div class="input-hint">코드가 생성될 대상 프로젝트(Pod) 폴더명입니다.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mt-3 mt-md-0">
|
||||||
|
<label class="form-label">레지스트리 등록 (register)</label>
|
||||||
|
<select class="form-select" name="register">
|
||||||
|
<option value="true">True (공개)</option>
|
||||||
|
<option value="false">False (비공개)</option>
|
||||||
|
</select>
|
||||||
|
<div class="input-hint">Gateway에서 자동 라우팅될지 여부입니다.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
@@ -331,6 +341,9 @@
|
|||||||
<small class="text-muted">Redis에 등록되지 않은(비공개) Tool도 포함되어 조회됩니다.</small>
|
<small class="text-muted">Redis에 등록되지 않은(비공개) Tool도 포함되어 조회됩니다.</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
|
<select id="groupFilterSelect" class="form-select form-select-sm" style="width: auto; min-width: 140px;" onchange="filterTools()">
|
||||||
|
<option value="ALL">ALL (전체 그룹)</option>
|
||||||
|
</select>
|
||||||
<input type="text" id="toolFilter" class="form-control form-control-sm" placeholder="🔍 이름 또는 그룹 검색..." onkeyup="filterTools()">
|
<input type="text" id="toolFilter" class="form-control form-control-sm" placeholder="🔍 이름 또는 그룹 검색..." onkeyup="filterTools()">
|
||||||
<button class="btn btn-sm btn-outline-primary text-nowrap" onclick="loadToolList()">🔄 새로고침</button>
|
<button class="btn btn-sm btn-outline-primary text-nowrap" onclick="loadToolList()">🔄 새로고침</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -362,6 +375,48 @@
|
|||||||
<div id="resultBox"></div>
|
<div id="resultBox"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Tool Modal -->
|
||||||
|
<div class="modal fade" id="editToolModal" tabindex="-1" aria-labelledby="editToolModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="editToolModalLabel">Tool 소스 코드 수정</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="editToolForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Tool 이름 (수정불가)</label>
|
||||||
|
<input type="text" class="form-control" id="editToolName" name="toolName" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">소속 도메인 그룹</label>
|
||||||
|
<input type="text" class="form-control" id="editDomainGroup" name="domainGroup" oninput="this.value = this.value.toUpperCase()">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">설명</label>
|
||||||
|
<textarea class="form-control" id="editDescription" name="description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">레지스트리 등록 (register)</label>
|
||||||
|
<select class="form-select" id="editRegister" name="register">
|
||||||
|
<option value="true">True (공개)</option>
|
||||||
|
<option value="false">False (비공개)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning p-2" style="font-size: 0.85rem;">
|
||||||
|
⚠️ <strong>주의</strong>: 수정을 적용하면 물리적인 자바 소스 코드가 변경됩니다. 반영을 위해서는 백엔드 컨테이너의 재빌드가 필요합니다!
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="submitToolUpdate()">소스 수정 적용</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
@@ -422,20 +477,49 @@
|
|||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const tools = data.result.tools || [];
|
let tools = data.result.tools || [];
|
||||||
if (tools.length === 0) {
|
if (tools.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-4 text-muted">등록된 Tool이 없습니다.</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-4 text-muted">등록된 Tool이 없습니다.</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 그룹명 기준으로 오름차순 정렬
|
||||||
|
tools.sort((a, b) => {
|
||||||
|
const gA = a.domainGroup || '';
|
||||||
|
const gB = b.domainGroup || '';
|
||||||
|
return gA.localeCompare(gB);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 그룹 필터 옵션 채우기
|
||||||
|
const groupSet = new Set();
|
||||||
|
tools.forEach(t => groupSet.add(t.domainGroup || 'UNKNOWN'));
|
||||||
|
const groupSelect = document.getElementById('groupFilterSelect');
|
||||||
|
const currentSelectedGroup = groupSelect.value;
|
||||||
|
groupSelect.innerHTML = '<option value="ALL">ALL (전체 그룹)</option>';
|
||||||
|
Array.from(groupSet).sort().forEach(g => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = g;
|
||||||
|
opt.textContent = g;
|
||||||
|
groupSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
if (groupSet.has(currentSelectedGroup)) {
|
||||||
|
groupSelect.value = currentSelectedGroup;
|
||||||
|
}
|
||||||
|
|
||||||
tbody.innerHTML = tools.map(t => {
|
tbody.innerHTML = tools.map(t => {
|
||||||
const isReg = t.registered !== false; // default true
|
const isReg = t.registered !== false; // default true
|
||||||
const badgeHtml = isReg ? '' : ' <span class="badge bg-warning text-dark border border-warning" title="Redis 미등록 (직접 호출만 가능)">비공개 (미등록)</span>';
|
const badgeHtml = isReg ? '' : ' <span class="badge bg-warning text-dark border border-warning" title="Redis 미등록 (직접 호출만 가능)">비공개 (미등록)</span>';
|
||||||
|
const escDesc = (t.description || '').replace(/'/g, "\\'").replace(/"/g, '"');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr class="tool-row">
|
<tr class="tool-row">
|
||||||
<td class="text-nowrap"><span class="badge bg-secondary tool-group">${t.domainGroup || 'UNKNOWN'}</span></td>
|
<td class="text-nowrap"><span class="badge bg-secondary tool-group">${t.domainGroup || 'UNKNOWN'}</span></td>
|
||||||
<td class="fw-bold text-primary text-break tool-name">${t.toolName}${badgeHtml}</td>
|
<td class="fw-bold text-primary text-break tool-name">
|
||||||
|
<a href="javascript:void(0)" onclick="openEditModal('${t.toolName}', '${t.domainGroup}', '${escDesc}', ${isReg})" class="text-decoration-none">
|
||||||
|
${t.toolName}
|
||||||
|
</a>
|
||||||
|
${badgeHtml}
|
||||||
|
</td>
|
||||||
<td class="text-break">${t.description}</td>
|
<td class="text-break">${t.description}</td>
|
||||||
<td class="text-nowrap"><span class="badge bg-light text-dark border">${t.podUrl}</span></td>
|
<td class="text-nowrap"><span class="badge bg-light text-dark border">${t.podUrl}</span></td>
|
||||||
<td class="text-nowrap"><span class="badge bg-info text-dark">${t.integrationType}</span> <small class="text-muted ms-1">${t.mciServiceId || '-'}</small></td>
|
<td class="text-nowrap"><span class="badge bg-info text-dark">${t.integrationType}</span> <small class="text-muted ms-1">${t.mciServiceId || '-'}</small></td>
|
||||||
@@ -452,19 +536,76 @@
|
|||||||
|
|
||||||
function filterTools() {
|
function filterTools() {
|
||||||
const input = document.getElementById('toolFilter').value.toLowerCase();
|
const input = document.getElementById('toolFilter').value.toLowerCase();
|
||||||
|
const selectedGroup = document.getElementById('groupFilterSelect').value;
|
||||||
const rows = document.querySelectorAll('.tool-row');
|
const rows = document.querySelectorAll('.tool-row');
|
||||||
|
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
const name = row.querySelector('.tool-name').textContent.toLowerCase();
|
const name = row.querySelector('.tool-name').textContent.toLowerCase();
|
||||||
const group = row.querySelector('.tool-group').textContent.toLowerCase();
|
const group = row.querySelector('.tool-group').textContent;
|
||||||
|
|
||||||
if (name.includes(input) || group.includes(input)) {
|
const matchText = name.includes(input) || group.toLowerCase().includes(input);
|
||||||
|
const matchGroup = (selectedGroup === 'ALL' || group === selectedGroup);
|
||||||
|
|
||||||
|
if (matchText && matchGroup) {
|
||||||
row.style.display = '';
|
row.style.display = '';
|
||||||
} else {
|
} else {
|
||||||
row.style.display = 'none';
|
row.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let editModalInstance = null;
|
||||||
|
function openEditModal(toolName, domainGroup, description, isReg) {
|
||||||
|
document.getElementById('editToolName').value = toolName;
|
||||||
|
document.getElementById('editDomainGroup').value = domainGroup !== 'null' ? domainGroup : '';
|
||||||
|
document.getElementById('editDescription').value = description;
|
||||||
|
document.getElementById('editRegister').value = isReg ? 'true' : 'false';
|
||||||
|
|
||||||
|
if (!editModalInstance) {
|
||||||
|
editModalInstance = new bootstrap.Modal(document.getElementById('editToolModal'));
|
||||||
|
}
|
||||||
|
editModalInstance.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitToolUpdate() {
|
||||||
|
const toolName = document.getElementById('editToolName').value;
|
||||||
|
const domainGroup = document.getElementById('editDomainGroup').value;
|
||||||
|
const description = document.getElementById('editDescription').value;
|
||||||
|
const register = document.getElementById('editRegister').value;
|
||||||
|
|
||||||
|
const btn = document.querySelector('#editToolModal .btn-primary');
|
||||||
|
const originalText = btn.innerHTML;
|
||||||
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 처리중...';
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
fetch('/api/v1/scaffold/tool/update', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
toolName: toolName,
|
||||||
|
domainGroup: domainGroup,
|
||||||
|
description: description,
|
||||||
|
register: register
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(result => {
|
||||||
|
if (result.includes('[오류]') || result.includes('오류 발생:')) {
|
||||||
|
alert('수정 중 오류가 발생했습니다.\n' + result);
|
||||||
|
} else {
|
||||||
|
alert('소스 코드가 성공적으로 수정되었습니다!\n반영을 위해 모듈(Pod)을 재빌드해주세요.');
|
||||||
|
editModalInstance.hide();
|
||||||
|
loadToolList(); // Refresh list
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('네트워크 오류 발생: ' + err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
btn.innerHTML = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -40,9 +40,21 @@ public class ToolMetadata {
|
|||||||
|
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private boolean visible = true;
|
private Boolean visible = true;
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private boolean isRegistered = true;
|
private Boolean isRegistered = true;
|
||||||
|
|
||||||
|
public boolean isVisible() {
|
||||||
|
return visible != null ? visible : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRegistered() {
|
||||||
|
return isRegistered != null ? isRegistered : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegistered(Boolean isRegistered) {
|
||||||
|
this.isRegistered = isRegistered;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user