From 7cde0c57d8e6c30975d9084b07af3313d8979452 Mon Sep 17 00:00:00 2001 From: htlee Date: Tue, 17 Mar 2026 15:50:05 +0900 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20UI=20=EB=B8=8C=EB=9E=9C?= =?UTF-8?q?=EB=94=A9=20=EA=B0=9C=EC=84=A0=20+=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인 화면: kcg.svg 로고 적용 (이모지 교체) - 헤더 우측: 사용자 프로필/이름 + 로그아웃 버튼 추가 - 브라우저 탭: favicon → kcg.svg, 제목 → kcg-dashboard-demo - 프로덕션 빌드: console/debugger 자동 제거 - CORS: CorsFilter 최우선 순위 등록 (AuthFilter 이전) - deploy.yml: secrets → .env 파일로 배포 - systemd/nginx: 경로 /devdata/services/kcg/ 반영 --- .gitea/workflows/deploy.yml | 9 + .../java/gc/mda/kcg/config/WebConfig.java | 38 +- deploy/kcg-backend.service | 7 +- deploy/nginx-kcg.conf | 2 +- frontend/index.html | 4 +- frontend/public/kcg.svg | 1004 +++++++++++++++++ frontend/src/App.css | 23 + frontend/src/App.tsx | 13 +- frontend/src/components/auth/LoginPage.tsx | 2 +- frontend/vite.config.ts | 7 +- 10 files changed, 1088 insertions(+), 21 deletions(-) create mode 100644 frontend/public/kcg.svg diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 06fa3e9..1f34937 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -43,6 +43,9 @@ jobs: run: mvn -B clean package -DskipTests - name: Deploy backend + env: + GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} run: | DEPLOY_DIR=/deploy/kcg-backend mkdir -p $DEPLOY_DIR/backup @@ -53,6 +56,12 @@ jobs: ls -t $DEPLOY_DIR/backup/*.jar | tail -n +6 | xargs -r rm fi + # Secrets → 환경변수 파일 (systemd EnvironmentFile) + cat > $DEPLOY_DIR/.env << ENVEOF + GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} + JWT_SECRET=${JWT_SECRET} + ENVEOF + # JAR 교체 + 재시작 트리거 cp backend/target/kcg.jar $DEPLOY_DIR/kcg.jar date '+%s' > $DEPLOY_DIR/.deploy-trigger diff --git a/backend/src/main/java/gc/mda/kcg/config/WebConfig.java b/backend/src/main/java/gc/mda/kcg/config/WebConfig.java index f18492d..b9afae0 100644 --- a/backend/src/main/java/gc/mda/kcg/config/WebConfig.java +++ b/backend/src/main/java/gc/mda/kcg/config/WebConfig.java @@ -1,18 +1,36 @@ package gc.mda.kcg.config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.core.Ordered; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import java.util.List; @Configuration -public class WebConfig implements WebMvcConfigurer { +public class WebConfig { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/api/**") - .allowedOrigins("http://localhost:5173") - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") - .allowedHeaders("*") - .allowCredentials(true); + @Value("${app.cors.allowed-origins:http://localhost:5173}") + private List allowedOrigins; + + @Bean + public FilterRegistrationBean corsFilterRegistration() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(allowedOrigins); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowedHeaders(List.of("*")); + config.setAllowCredentials(true); + config.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/api/**", config); + + FilterRegistrationBean bean = new FilterRegistrationBean<>(new CorsFilter(source)); + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + return bean; } } diff --git a/deploy/kcg-backend.service b/deploy/kcg-backend.service index 1932377..36f471e 100644 --- a/deploy/kcg-backend.service +++ b/deploy/kcg-backend.service @@ -6,12 +6,13 @@ After=network.target Type=simple User=root Group=root -WorkingDirectory=/deploy/kcg-backend +WorkingDirectory=/devdata/services/kcg/backend +EnvironmentFile=-/devdata/services/kcg/backend/.env ExecStart=/usr/lib/jvm/java-17-openjdk-17.0.18.0.8-1.el9.x86_64/bin/java \ -Xms2g -Xmx4g \ -Dspring.profiles.active=prod \ - -Dspring.config.additional-location=file:/deploy/kcg-backend/ \ - -jar /deploy/kcg-backend/kcg.jar + -Dspring.config.additional-location=file:/devdata/services/kcg/backend/ \ + -jar /devdata/services/kcg/backend/kcg.jar Restart=on-failure RestartSec=10 diff --git a/deploy/nginx-kcg.conf b/deploy/nginx-kcg.conf index b86d694..4820fea 100644 --- a/deploy/nginx-kcg.conf +++ b/deploy/nginx-kcg.conf @@ -8,7 +8,7 @@ server { ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # ── Frontend SPA ── - root /deploy/kcg; + root /devdata/services/kcg/dist; # Static cache location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { expires 1y; diff --git a/frontend/index.html b/frontend/index.html index bcd194d..462491d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,9 @@ - + - iran-airstrike-replay + kcg-dashboard-demo
diff --git a/frontend/public/kcg.svg b/frontend/public/kcg.svg new file mode 100644 index 0000000..fb0960a --- /dev/null +++ b/frontend/public/kcg.svg @@ -0,0 +1,1004 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.css b/frontend/src/App.css index 46461bf..a466923 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -146,6 +146,29 @@ 50% { opacity: 0.4; } } +.header-user { + display: flex; + align-items: center; + gap: 6px; + padding-left: 8px; + border-left: 1px solid var(--kcg-border); +} + +.header-user-avatar { + width: 22px; + height: 22px; + border-radius: 50%; +} + +.header-user-name { + font-size: 11px; + color: var(--kcg-text); + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + /* Cheonghae Unit pulsing beacon */ @keyframes cheonghae-pulse { 0% { transform: scale(1); opacity: 0.8; } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7249338..22eb2bb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -103,7 +103,7 @@ interface AuthenticatedAppProps { onLogout: () => Promise; } -function AuthenticatedApp(_props: AuthenticatedAppProps) { +function AuthenticatedApp({ user, onLogout }: AuthenticatedAppProps) { const [appMode, setAppMode] = useState('live'); const [events, setEvents] = useState([]); const [sensorData, setSensorData] = useState([]); @@ -965,6 +965,17 @@ function AuthenticatedApp(_props: AuthenticatedAppProps) { {isLive ? t('header.live') : replay.state.isPlaying ? t('header.replaying') : t('header.paused')} + {user && ( +
+ {user.picture && ( + + )} + {user.name} + +
+ )} diff --git a/frontend/src/components/auth/LoginPage.tsx b/frontend/src/components/auth/LoginPage.tsx index 3a34ae7..9742506 100644 --- a/frontend/src/components/auth/LoginPage.tsx +++ b/frontend/src/components/auth/LoginPage.tsx @@ -104,7 +104,7 @@ const LoginPage = ({ onGoogleLogin, onDevLogin }: LoginPageProps) => { > {/* Title */}
-
🛡️
+ KCG

({ plugins: [tailwindcss(), react()], + esbuild: mode === 'production' ? { drop: ['console', 'debugger'] } : {}, server: { proxy: { '/api/ais': { @@ -117,4 +118,4 @@ export default defineConfig({ }, }, }, -}) +}))