diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/SettingController.java b/src/main/java/com/cloudhandson/vpdbackoffice/web/SettingController.java index 789086b..c53a0d9 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/web/SettingController.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/web/SettingController.java @@ -31,6 +31,11 @@ public class SettingController { model.addAttribute("runtimeErrorTitle", error.title()); model.addAttribute("runtimeErrorMessage", error.message()); } + return "settings"; + } + + @GetMapping("/settings/database") + public String databaseSettings(Model model) { try { model.addAttribute("schemaPreflight", backofficeSchemaService.preflight()); } catch (DataAccessException exception) { @@ -38,7 +43,7 @@ public class SettingController { model.addAttribute("schemaPreflightErrorTitle", error.title()); model.addAttribute("schemaPreflightErrorMessage", error.message()); } - return "settings"; + return "settings-database"; } @PostMapping("/settings/ords") @@ -51,8 +56,16 @@ public class SettingController { return "redirect:/settings"; } - @PostMapping("/settings/schema/initialize") - public String initializeSchema(RedirectAttributes redirectAttributes) { + @PostMapping("/settings/database/initialize") + public String initializeSchema( + @RequestParam String confirmation, + RedirectAttributes redirectAttributes + ) { + if (!"DB 준비 실행".equals(confirmation)) { + redirectAttributes.addFlashAttribute( + "error", "DB 지원 DDL을 실행하려면 확인 문구 ‘DB 준비 실행’을 정확히 입력하세요."); + return "redirect:/settings/database"; + } var results = backofficeSchemaService.initializeSchema(); long failures = results.stream() .filter(result -> "FAILED".equalsIgnoreCase(result.status())) @@ -63,6 +76,6 @@ public class SettingController { } else { redirectAttributes.addFlashAttribute("message", "백오피스 지원 테이블 DDL을 실행했습니다."); } - return "redirect:/settings"; + return "redirect:/settings/database"; } } diff --git a/src/main/resources/static/css/app.css b/src/main/resources/static/css/app.css index fdc3a97..51b0728 100644 --- a/src/main/resources/static/css/app.css +++ b/src/main/resources/static/css/app.css @@ -358,6 +358,57 @@ body { padding: 1rem; } +.back-link { + color: var(--rw-primary); + display: inline-block; + font-size: .86rem; + font-weight: 800; + margin-bottom: .6rem; + text-decoration: none; +} + +.settings-database-link { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 1rem; + justify-content: space-between; +} + +.settings-database-link h2, +.settings-danger-zone h2 { + font-size: 1rem; + font-weight: 800; + margin: .3rem 0; +} + +.settings-database-link p { + color: var(--rw-muted); + margin: 0; + max-width: 52rem; +} + +.settings-danger-zone { + background: #fff8e8; + border-color: #e7d29c; +} + +.settings-impact-list { + color: var(--rw-muted); + margin: .75rem 0 1rem; + padding-left: 1.25rem; +} + +.settings-impact-list li + li { + margin-top: .3rem; +} + +.settings-confirm-form { + align-items: end; + grid-template-columns: minmax(220px, 1fr) auto; + max-width: 46rem; +} + .content-band h2, .section-heading h2 { font-size: 1rem; @@ -1587,6 +1638,10 @@ body { } @media (max-width: 640px) { + .settings-confirm-form { + grid-template-columns: 1fr; + } + .effective-tabs { overflow-x: auto; } diff --git a/src/main/resources/templates/settings-database.html b/src/main/resources/templates/settings-database.html new file mode 100644 index 0000000..e09553e --- /dev/null +++ b/src/main/resources/templates/settings-database.html @@ -0,0 +1,104 @@ + + + + + +
+
+ ← 연결 설정으로 돌아가기 +

DB 준비 상태

+

DB에 영향을 주는 지원 DDL과 VPD·ORDS runtime 준비 상태를 확인합니다.

+
+ 실행 전 반드시 확인할 내용 +

지원 테이블 생성/확인은 현재 연결 사용자의 백오피스 메타데이터를 생성하거나 보강합니다. VPD context, ORDS enablement, grant는 여기서 실행하지 않으며, 아래 sqlcl preview 순서를 따릅니다.

+
+
+ +
+
+
+ DB 준비 상태 확인 불가 +
DB 연결을 확인하세요.
+
+ +
+
+
+ 읽기 전용 점검 +

현재 DB 준비 상태

+

현재 연결 사용자 기준으로 메타데이터, VPD runtime, ORDS runtime, grant 상태를 점검합니다.

+
+ 운영 상태 보기 +
+
+
현재 연결 사용자ADMIN
+
권장 ORDS schemaCB_ORDS
+
OK0
+
WARN0
+
MISSING0
+
ERROR0
+
+
+ + + + + + + + + + + + +
LayerItemTypeStatusDetailNext Action
Backoffice MetadataCB_APP_USERTABLEOK존재합니다.앱 실행 가능
조치 없음
+
+
+ +
+

실행 전 SQL 확인

+
+
앱에서 실행하는 지원 DDL
CREATE TABLE ...
+
sqlcl로 실행할 VPD/ORDS runtime 순서
@sql/adb/...
+
+
+ +
+
+
+ 영향 있는 작업 +

지원 테이블 생성/확인

+

현재 연결 사용자에 백오피스 지원 테이블, 시퀀스, 컬럼을 생성하거나 보강합니다. VPD·ORDS runtime은 실행하지 않습니다.

+
+
+ +
+ + + +
+
+ + + + + + + + + + +
ObjectActionStatusMessageSQL
objectTABLECREATED생성했습니다.
보기
CREATE TABLE ...
-
+
+
+
+ + diff --git a/src/main/resources/templates/settings.html b/src/main/resources/templates/settings.html index eef7553..d29c464 100644 --- a/src/main/resources/templates/settings.html +++ b/src/main/resources/templates/settings.html @@ -1,15 +1,15 @@ - + -
+
-

설정

-

ORDS 연결값과 백오피스 DB 준비 상태를 확인합니다.

+

연결 설정

+

백오피스가 ORDS를 호출할 주소를 관리합니다. DB 지원 DDL은 별도 안전 화면에서 실행합니다.

- 설정 화면의 범위 보기 -

백오피스에서 사용하는 외부 연동 값을 관리합니다. DB 준비 상태에서는 메타데이터, VPD runtime, ORDS runtime, grant를 연결 사용자 기준으로 점검합니다.

+ 이 화면에서 바꾸는 범위 보기 +

여기서는 ORDS Base URL만 저장합니다. VPD runtime, grant, 지원 테이블 생성처럼 DB에 영향을 주는 작업은 DB 준비 상태에서 별도로 확인·실행합니다.

@@ -19,151 +19,32 @@ DB 준비 필요
지원 테이블을 생성하세요.
-
- DB 준비 상태 확인 불가 -
DB 연결을 확인하세요.
-
-

ORDS

+
+
+ ORDS 연결 +

ORDS Base URL

+

저장 후 권한 결과 확인과 연동 도구가 이 주소를 사용합니다.

+
+
- +
-
-
-
-

DB 준비 상태

-

현재 연결 사용자 기준으로 백오피스 메타데이터, VPD runtime, ORDS runtime, grant 상태를 점검합니다.

-
-
-
-
- 현재 연결 사용자 - ADMIN -
-
- 권장 ORDS parsing schema - CB_ORDS -
-
- OK - 0 -
-
- WARN - 0 -
-
- MISSING - 0 -
-
- ERROR - 0 -
-
-
- 앱에서 실행 가능한 항목은 아래 `지원 테이블 생성/확인` 버튼으로 보강하고, `sqlcl 필요` 항목은 preview 순서대로 DB 권한을 가진 세션에서 실행하세요. -
-
- - - - - - - - - - - - - - - - - - - - - -
LayerItemTypeStatusDetailNext Action
Backoffice MetadataCB_APP_USERTABLE - OK - 존재합니다. - 앱 실행 가능 -
조치 없음
-
-
-
- -
-

SQL Preview

-

- 앱 버튼이 실행할 SQL과 sqlcl로 실행할 runtime 준비 순서를 구분해서 확인합니다. -

-
-
- 앱에서 실행하는 지원 DDL -
CREATE TABLE ...
-
-
- sqlcl로 실행할 VPD/ORDS runtime 순서 -
@sql/adb/...
-
-
-
- -
-

DB 스키마 초기화

-

- 현재 연결된 DB 사용자에 백오피스 지원 테이블, 시퀀스, 컬럼을 생성하거나 보강합니다. VPD context, ORDS enablement, grant는 sqlcl preview 순서를 사용하세요. -

-
- - -
-
- - - - - - - - - - - - - - - - - - - -
ObjectActionStatusMessageSQL
objectTABLE - CREATED - 생성했습니다. -
- 보기 -
CREATE TABLE ...
-
- - -
+
diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java b/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java index 32c4b36..c34854d 100644 --- a/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java +++ b/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java @@ -113,6 +113,23 @@ class GuidedFlowTemplateTest { .contains("기술 태그 비교 예시"); } + @Test + void connectionSettingsAreSeparatedFromDatabasePreparation() throws IOException { + String settings = template("settings.html"); + String database = template("settings-database.html"); + + assertThat(settings) + .contains("연결 설정") + .contains("/settings/database") + .doesNotContain("action=\"/settings/database/initialize\""); + assertThat(database) + .contains("DB 준비 상태") + .contains("/settings/database/initialize") + .contains("확인 문구 입력") + .contains("DB 준비 실행") + .doesNotContain("action=\"/settings/schema/initialize\""); + } + @Test void effectiveMatrixStartsWithOneUserAndKeepsGroupAndRoleAsSecondaryViews() throws IOException { String html = template("effective-matrix.html"); diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/SettingsTemplateRenderTest.java b/src/test/java/com/cloudhandson/vpdbackoffice/web/SettingsTemplateRenderTest.java new file mode 100644 index 0000000..068e8bb --- /dev/null +++ b/src/test/java/com/cloudhandson/vpdbackoffice/web/SettingsTemplateRenderTest.java @@ -0,0 +1,43 @@ +package com.cloudhandson.vpdbackoffice.web; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.util.Locale; +import org.junit.jupiter.api.Test; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; +import org.thymeleaf.templateresolver.FileTemplateResolver; + +class SettingsTemplateRenderTest { + + @Test + void rendersConnectionAndDatabasePreparationPagesSeparately() { + var resolver = new FileTemplateResolver(); + resolver.setPrefix(Path.of("src/main/resources/templates").toAbsolutePath() + "/"); + resolver.setSuffix(".html"); + resolver.setTemplateMode("HTML"); + resolver.setCacheable(false); + var engine = new SpringTemplateEngine(); + engine.setTemplateResolver(resolver); + + var context = new Context(Locale.KOREAN); + context.setVariable("_csrf", new CsrfFixture("_csrf", "test-token")); + context.setVariable("ordsBaseUrl", "https://ords.example.test/ords"); + + String connection = engine.process("settings", context); + String database = engine.process("settings-database", context); + + assertThat(connection) + .contains("ORDS Base URL") + .contains("/settings/database") + .doesNotContain("/settings/database/initialize"); + assertThat(database) + .contains("DB 준비 상태") + .contains("/settings/database/initialize") + .contains("DB 준비 실행"); + } + + private record CsrfFixture(String parameterName, String token) { + } +}