From 89fa51efe265f9bdf82dcea7f493cd7e68e61216 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Thu, 9 Jul 2026 00:03:33 +0900 Subject: [PATCH] refactor admin UX and stabilize shell --- CLAUDE.md | 146 +++--- DEPLOYMENT_GUIDE.md | 35 +- NGINX_CONFIG_GUIDE.md | 34 +- README.md | 21 +- db/migrations/V003__SeedAdminAndBlogPosts.sql | 8 +- db/migrations/V012__AddTestAdmin.sql | 2 +- .../V013__EnsureAdminAndTestAccounts.sql | 2 +- docs/ENGINEERING_HARNESS.md | 8 +- docs/INDEX.md | 10 +- docs/NGINX_HARNESS.md | 219 +-------- docs/OPS_LOG_DIAGNOSTICS_ROADMAP.md | 7 +- docs/STACK_TEMPLATE_WBS.md | 4 +- evidence/manifest.jsonl | 56 +++ playwright.config.ts | 6 +- .../Components/Admin/InquiryTable.razor | 4 +- .../Announcements/AnnouncementEdit.razor | 1 - .../Announcements/AnnouncementList.razor | 3 +- .../Admin/Pages/Blog/BlogList.razor | 14 +- .../Components/Admin/Pages/ClientLogs.razor | 2 +- .../Admin/Pages/Clients/ClientDetail.razor | 8 +- .../Admin/Pages/Clients/ClientEdit.razor | 1 - .../Admin/Pages/Clients/ClientList.razor | 18 +- .../Components/Admin/Pages/CommonCodes.razor | 1 - .../Admin/Pages/Companies/CompanyList.razor | 2 +- .../Admin/Pages/ConsultingActivities.razor | 37 +- .../Components/Admin/Pages/Contracts.razor | 81 ++-- .../Components/Admin/Pages/Dashboard.razor | 259 +++++----- .../Admin/Pages/DashboardOverview.razor | 12 +- .../Components/Admin/Pages/Faqs/FaqEdit.razor | 3 +- .../Components/Admin/Pages/Faqs/FaqList.razor | 39 +- .../Admin/Pages/Inquiries/InquiryDetail.razor | 17 +- .../Admin/Pages/Inquiries/InquiryList.razor | 89 +++- .../Components/Admin/Pages/Logout.razor | 1 - .../Admin/Pages/RevenueTrackings.razor | 37 +- .../Admin/Pages/SeasonSimulator.razor | 65 +-- .../Admin/Pages/Settings/SiteSettings.razor | 32 +- .../Admin/Pages/TaxFilingSchedules.razor | 95 ++-- .../Admin/Pages/TaxFilings/FilingTable.razor | 1 - .../Pages/TaxFilings/TaxFilingList.razor | 1 - .../Components/Admin/Pages/TaxProfiles.razor | 53 +- .../Admin/Services/AdminDashboardClient.cs | 23 +- .../Admin/Services/AdminSessionBootstrap.cs | 86 ++++ .../Components/Admin/Services/ApiClient.cs | 2 +- .../CustomAuthenticationStateProvider.cs | 9 + .../Admin/Services/IAdminSessionBootstrap.cs | 6 + .../Admin/Services/TokenRefreshHandler.cs | 2 +- .../Admin/Shared/AdminEditorPanel.razor | 4 +- .../Admin/Shared/AdminEmptyState.razor | 2 +- .../Admin/Shared/AdminLoginForm.razor | 43 +- .../Admin/Shared/AdminMetricCard.razor | 4 +- .../Admin/Shared/AdminNavigationModel.cs | 47 ++ .../Admin/Shared/AdminPageHeader.razor | 32 +- .../Components/Admin/Shared/AdminShell.razor | 39 +- .../Admin/Shared/AdminSidebar.razor | 52 +- .../Admin/Shared/CommonCodeSelect.razor | 1 - .../Components/Admin/_Imports.razor | 4 +- src/TaxBaik.Web.Client/Program.cs | 8 +- .../Services/AdminDashboardClient.cs | 23 +- src/TaxBaik.Web.Client/Services/ApiClient.cs | 2 +- src/TaxBaik.Web/Pages/Admin/Login.cshtml | 33 +- src/TaxBaik.Web/Pages/Admin/Login.cshtml.cs | 5 +- src/TaxBaik.Web/Pages/Feed.cshtml | 6 +- src/TaxBaik.Web/Pages/Portal/Login.cshtml | 2 +- src/TaxBaik.Web/Pages/Portal/Login.cshtml.cs | 2 +- src/TaxBaik.Web/Pages/Portal/Register.cshtml | 2 +- src/TaxBaik.Web/Pages/Rss.cshtml | 6 +- src/TaxBaik.Web/Pages/Sitemap.cshtml.cs | 2 +- src/TaxBaik.Web/Pages/_Layout.cshtml | 11 +- src/TaxBaik.Web/Program.cs | 26 +- .../Services/SitemapValidationService.cs | 6 +- .../TelegramInquiryNotificationService.cs | 2 +- src/TaxBaik.Web/appsettings.Development.json | 2 +- src/TaxBaik.Web/appsettings.json | 4 +- src/TaxBaik.Web/wwwroot/admin/index.html | 2 +- src/TaxBaik.Web/wwwroot/css/admin.css | 454 +++++++++++++++++- src/TaxBaik.Web/wwwroot/js/admin-session.js | 41 +- src/TaxBaik.Web/wwwroot/robots.txt | 8 +- tests/e2e/admin-login.spec.ts | 23 +- tests/e2e/admin-pages-manual.spec.ts | 22 +- tests/e2e/admin-responsive.spec.ts | 59 ++- tests/e2e/admin-smoke.spec.ts | 18 +- tests/e2e/blog-crud.spec.ts | 2 +- tests/e2e/blog-seo.spec.ts | 6 +- tests/e2e/blog-validation.spec.ts | 2 +- tests/e2e/dashboard-check.spec.ts | 8 +- tests/e2e/full-admin-pages.spec.ts | 33 +- tests/e2e/full-user-flow.spec.ts | 4 +- tests/e2e/helpers/admin-auth.ts | 38 +- tests/e2e/inquiry-detail.spec.ts | 4 +- tests/e2e/login-test.spec.ts | 5 +- tests/e2e/route-isolation.spec.ts | 2 +- 91 files changed, 1633 insertions(+), 1030 deletions(-) create mode 100644 src/TaxBaik.Web.Client/Components/Admin/Services/AdminSessionBootstrap.cs create mode 100644 src/TaxBaik.Web.Client/Components/Admin/Services/IAdminSessionBootstrap.cs diff --git a/CLAUDE.md b/CLAUDE.md index b31b3c4..dd3e4a2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -239,7 +239,7 @@ git push http://gitea.taxbaik.com/kjh2064/taxbaik.git master 1. git push 완료 → Gitea Actions 자동 trigger 2. `.gitea/workflows/deploy.yml` 실행 3. 빌드 → 배포 → 서비스 재시작 -4. 진행 상황: `https://gitea.taxbaik.com/kjh2064/taxbaik/actions` +4. 진행 상황은 Gitea Actions에서 확인 ## 🏗️ **아키텍처 리팩토링 (API-First 전환)** @@ -342,7 +342,7 @@ app.MapRazorComponents() # ✅ 반드시 설정해야 함 export ASPNETCORE_ENVIRONMENT=Production export ASPNETCORE_URLS="http://127.0.0.1:$TARGET_PORT" -export ConnectionStrings__Default="Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=<실제비밀>" +export ConnectionStrings__Default="Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=" export DOTNET_PRINT_TELEMETRY_MESSAGE=false # ❌ 누락하면 배포 실패 (Missing connection string) @@ -624,9 +624,9 @@ src/ 빌드 가능한 .NET 소스 전체 (CI는 이 폴더 ``` **경로:** -- 홈페이지: `/taxbaik` (Razor Pages) -- 관리자: `/taxbaik/admin` (Blazor WebAssembly - CSR) -- 로그인: `/taxbaik/admin/login` +- 홈페이지: `/` +- 관리자: `/admin` (Blazor WebAssembly - CSR) +- 로그인: `/admin/login` **렌더링 방식**: - 공개 사이트: SSR (Razor Pages) - SEO 최적화 @@ -725,9 +725,9 @@ ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7 cd src/TaxBaik.Web dotnet run # 접속: -# - 홈페이지: http://localhost:5001/taxbaik -# - 관리자: http://localhost:5001/taxbaik/admin/login -# - 로그인: admin / +# - 홈페이지: http://localhost:5001/ +# - 관리자: http://localhost:5001/admin/login +# - 로그인: admin / Admin@123456 ``` **장점**: @@ -741,7 +741,7 @@ dotnet run ```json { "ConnectionStrings": { - "Default": "Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=XXXXXXXX" + "Default": "Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=" } } ``` @@ -779,18 +779,10 @@ CREATE TABLE IF NOT EXISTS new_table ( #### 계정 정보 (마이그레이션 V013) -**프로덕션 계정** (admin): -- 사용자명: `admin` -- 비밀번호: API로 설정 (reset-password 엔드포인트) -- 용도: 프로덕션 관리자 -- 권한: 모든 관리 기능 액세스 - -**테스트 계정** (test_admin): -- 사용자명: `test_admin` -- 비밀번호: API로 설정 (reset-password 엔드포인트) -- 용도: E2E Playwright 자동 테스트 -- 권한: admin과 동일 -- 환경: 로컬/CI 테스트만 +**계정 기준** +- 운영 관리자: `admin / Admin@123456` +- E2E 자동화: `test_admin / TestAdmin@123456` +- 둘 다 관리자 권한이며, 운영 변경은 `admin`, 반복 검증은 `test_admin`을 사용한다. #### 비밀번호 관리 (API 기반) @@ -836,13 +828,13 @@ Content-Type: application/json **테스트 계정 검증**: ```bash -# admin 계정 로그인 -curl -X POST http://localhost:5001/taxbaik/api/auth/login \ +# 운영 관리자 로그인 +curl -X POST http://localhost:5001/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"Admin@123456"}' -# test_admin 계정 로그인 -curl -X POST http://localhost:5001/taxbaik/api/auth/login \ +# E2E 자동화 계정 로그인 +curl -X POST http://localhost:5001/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"test_admin","password":"TestAdmin@123456"}' ``` @@ -928,7 +920,7 @@ git push ssh://git@gitea.taxbaik.com:2222/kjh2064/taxbaik.git master 1. git push 성공 → master 브랜치에 커밋 2. Gitea Actions CI/CD 자동 trigger (.gitea/workflows/deploy.yml) 3. 빌드 → 배포 → 서비스 재시작 자동 실행 -4. 배포 진행 상황: `https://gitea.taxbaik.com/kjh2064/taxbaik/actions` +4. 배포 진행 상황은 Gitea Actions에서 확인 --- @@ -945,7 +937,7 @@ ssh kjh2064@178.104.200.7 3000 : Gitea Web (localhost만, proxy via /를 통해) 2222 : Gitea SSH (공개) 5000 : QuantEngine Blazor (localhost, proxy via /quant/) -5001 : TaxBaik.Web (공개 사이트 + 관리자 통합, localhost, proxy via /taxbaik) +5001 : TaxBaik.Web (공개 사이트 + 관리자 통합, localhost, proxy via /) 5432 : PostgreSQL (localhost 바인드) ``` @@ -954,12 +946,12 @@ ssh kjh2064@178.104.200.7 배포는 수동 실행이 아니라 **Gitea Actions CI/CD**만 사용한다. **무중단 Green-Blue 배포 아키텍처 (2026-06-30 적용 완료)**: -1. **프록시 레이어**: 포트 `5001`에서 영구 가동되는 초경량 .NET TCP 프록시([TaxBaik.Proxy])가 수신 대기합니다. Nginx는 `/taxbaik` 트래픽을 기존과 같이 `5001`로 중계합니다. +1. **프록시 레이어**: 포트 `5001`에서 영구 가동되는 초경량 .NET TCP 프록시([TaxBaik.Proxy])가 수신 대기합니다. Nginx는 루트 트래픽을 `5001`로 중계합니다. 2. **동적 포트 스위칭**: 프록시는 요청이 들어올 때마다 `/home/kjh2064/taxbaik_port` 파일을 읽어 active 포트(5003 또는 5004)를 판단하고 트래픽을 포워딩합니다. 3. **배포 흐름 (`deploy_gb.sh`)**: - Gitea Actions가 코드를 build/publish 후 압축하여 서버에 업로드합니다. - 서버의 배포 스크립트([deploy_gb.sh])가 실행되어 현재 미사용 중인 예비 포트(Target Port: 5003 또는 5004)를 파악합니다. - - 예비 포트에서 새 .NET 웹 앱을 실행하고 `http://127.0.0.1:$target_port/taxbaik/healthz` 헬스 체크를 통과할 때까지 폴링(최대 60초)합니다. + - 예비 포트에서 새 .NET 웹 앱을 실행하고 `http://127.0.0.1:$target_port/healthz` 헬스 체크를 통과할 때까지 폴링(최대 60초)합니다. - 헬스 체크 성공 시 `/home/kjh2064/taxbaik_port` 파일에 새 포트 번호를 기입하여 **트래픽을 즉시 무중단 전환**합니다. - 기존 포트에서 동작하던 구버전 .NET 프로세스를 종료(`kill -15`)합니다. - 만약 헬스 체크 실패 시 새 프로세스만 강제 종료하고 배포를 롤백하여 실서비스 다운타임을 방지합니다. @@ -968,7 +960,7 @@ ssh kjh2064@178.104.200.7 ```bash export ASPNETCORE_ENVIRONMENT=Production export ASPNETCORE_URLS="http://127.0.0.1:$TARGET_PORT" -export ConnectionStrings__Default="Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=taxbaik123" +export ConnectionStrings__Default="Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=" export DOTNET_PRINT_TELEMETRY_MESSAGE=false ``` @@ -1012,7 +1004,7 @@ export DOTNET_PRINT_TELEMETRY_MESSAGE=false # 실제 로드되는 파일: /etc/nginx/sites-available/taxbaik-domains.conf # (sites-enabled/taxbaik-domains.conf 심볼릭 링크로 활성화됨) -location /taxbaik { +location / { proxy_pass http://127.0.0.1:5001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; @@ -1026,27 +1018,27 @@ location /taxbaik { } ``` -**참고**: 단일 `/taxbaik` 블록이 공개 사이트와 관리자 Blazor 회로를 모두 처리합니다. 운영은 `5001` 통합 앱 기준이며, 설정 반영은 CI 배포로만 수행한다. +**참고**: 단일 루트 블록이 공개 사이트와 관리자 Blazor 회로를 모두 처리합니다. 운영은 `5001` 통합 앱 기준이며, 설정 반영은 CI 배포로만 수행한다. **⚠️ 중요: 실제 로드되는 Nginx 설정 파일 확인 필수 (2026-07-03 장애로 확인)**: - `nginx.conf`는 `include /etc/nginx/sites-enabled/*;`만 로드한다. `/etc/nginx/sites-available/`에 파일이 있어도 `sites-enabled/`에 심볼릭 링크되어 있지 않으면 **절대 반영되지 않는다**. - 이 서버는 `sites-available/default`가 아니라 `sites-available/taxbaik-domains.conf` (→ `sites-enabled/taxbaik-domains.conf`)가 실제로 로드되는 파일이다. `default`를 아무리 수정해도 효과가 없다. - 실제 로드 파일을 찾는 법: `ls -la /etc/nginx/sites-enabled/` 로 심볼릭 링크 대상을 먼저 확인한 뒤 그 파일을 수정한다. -- **불변식**: `taxbaik-domains.conf`의 `location /`와 `location /taxbaik` 모두 항상 `127.0.0.1:5001` (TaxBaik.Proxy)만 가리켜야 한다. `5003`/`5004`(Green-Blue 앱 포트)를 직접 하드코딩하면 포트 전환 시 죽은 포트를 가리키게 되어 502/404가 발생한다. 이 설정은 배포마다 바뀔 필요가 없다 — 프록시가 `~/taxbaik_port` 파일을 읽어 자동으로 활성 포트에 연결한다. +- **불변식**: `taxbaik-domains.conf`의 `location /`는 항상 `127.0.0.1:5001` (TaxBaik.Proxy)만 가리켜야 한다. `5003`/`5004`(Green-Blue 앱 포트)를 직접 하드코딩하면 포트 전환 시 죽은 포트를 가리키게 되어 502/404가 발생한다. 이 설정은 배포마다 바뀔 필요가 없다 — 프록시가 `~/taxbaik_port` 파일을 읽어 자동으로 활성 포트에 연결한다. - **trailing slash 주의**: `proxy_pass http://127.0.0.1:5001;` (슬래시 없음)은 원본 요청 경로를 그대로 전달한다. `proxy_pass http://127.0.0.1:5001/;` (슬래시 있음)은 URI를 재작성하는데, `location` 접두사와 슬래시 개수가 안 맞으면 백엔드로 이중 슬래시(`//`)가 전달되어 404가 발생한다. 접두사 매칭 `location`에서는 `proxy_pass`에 trailing slash를 붙이지 않는다. -- **디버깅 팁**: `curl http://127.0.0.1/taxbaik/`처럼 IP로 직접 테스트하면 `Host: 127.0.0.1` 헤더가 `server_name taxbaik.com www.taxbaik.com`과 매칭되지 않아 엉뚱한(또는 기본) server block으로 라우팅될 수 있다. 실제 도메인 기준 server block을 로컬에서 테스트하려면 Host 헤더/SNI를 강제로 지정한다: +- **디버깅 팁**: `curl http://127.0.0.1/`처럼 IP로 직접 테스트하면 `Host: 127.0.0.1` 헤더가 `server_name taxbaik.com www.taxbaik.com`과 매칭되지 않아 엉뚱한(또는 기본) server block으로 라우팅될 수 있다. 실제 도메인 기준 server block을 로컬에서 테스트하려면 Host 헤더/SNI를 강제로 지정한다: ```bash # HTTP - curl -I -H "Host: www.taxbaik.com" http://127.0.0.1/taxbaik/ + curl -I -H "Host: www.taxbaik.com" http://127.0.0.1/ # HTTPS (SNI 포함) - curl -sk -I --resolve www.taxbaik.com:443:127.0.0.1 https://www.taxbaik.com/taxbaik/ + curl -sk -I --resolve www.taxbaik.com:443:127.0.0.1 https://www.taxbaik.com/ ``` - CI 배포(`deploy.yml`)는 매 배포마다 `sites-enabled/`의 실제 파일을 찾아 위 불변식을 검증하고, 위반 시 배포를 실패 처리한다. 또한 내부 `127.0.0.1:5001` 체크와 별개로 실제 공개 도메인(`https://www.taxbaik.com/`)을 외부에서 호출해 Nginx/Cloudflare 경로 전체를 검증한다 — 내부 체크만으로는 Nginx 설정 오류를 잡지 못하기 때문이다. **Nginx 보안**: - `Upgrade` 헤더는 Blazor WebSocket 경로에만 허용하고, 필요 없는 location에는 넣지 않는다. - `Host`와 `X-Forwarded-Proto`는 유지해 원본 URL과 스킴을 보존한다. -- `/taxbaik/admin`는 robots.txt에서 차단한다. +- `/admin`는 robots.txt에서 차단한다. --- @@ -1056,7 +1048,7 @@ location /taxbaik { **환경 변수** (systemd unit file에 설정): ```ini -Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=XXXXXXXX +Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password= ``` **절대 appsettings.Production.json에 비밀값을 하드코딩하지 말 것.** @@ -1302,18 +1294,18 @@ catch ## 8. Blazor Admin 패턴 (통합 Web 앱) ### 8.1 PathBase -전체 앱은 `/taxbaik/` 경로에서 실행: +전체 앱은 `/` 경로에서 실행: ```csharp // Program.cs -app.UsePathBase("/taxbaik"); +app.UsePathBase("/"); ``` `@page` 지시문의 경로는 이 기본값에 상대적. 예: ```razor -@page "/admin/login" ← 실제 URL: /taxbaik/admin/login -@page "/admin/blog" ← 실제 URL: /taxbaik/admin/blog -@page "/blog" ← 실제 URL: /taxbaik/blog (Razor Pages) +@page "/admin/login" ← 실제 URL: /admin/login +@page "/admin/blog" ← 실제 URL: /admin/blog +@page "/blog" ← 실제 URL: /blog (Razor Pages) ``` ### 8.2 JWT 인증 (LocalStorage + Bearer Token) @@ -1903,7 +1895,7 @@ Icons.Material.Filled.KeyboardArrowDown// 드롭다운 #### **6. 링크 (MudLink)** ```razor - + 페이지로 이동 → ``` @@ -2148,7 +2140,7 @@ else - [ ] AutoMapper 사용 금지 (수동 매핑) - [ ] Repository 인터페이스 없이 DB 직접 쿼리 - [ ] Razor Component의 @code에 비즈니스 로직 -- [ ] robots.txt에서 `/taxbaik/admin` allow 금지 (disallow 필수) +- [ ] robots.txt에서 `/admin` allow 금지 (disallow 필수) - [ ] 폼 제출 후 redirect (fire-and-forget 또는 same-page 응답) - [ ] 절대 `Thread.Sleep` 또는 `Task.Delay` in request handler - [ ] 운영 서버에서 수동 publish/rsync/파일 교체 @@ -2440,17 +2432,17 @@ psql -U taxbaik -d taxbaikdb -c "\dt" systemctl status taxbaik # 엔드포인트 확인 -curl http://127.0.0.1:5001/taxbaik +curl http://127.0.0.1:5001/ # Nginx 라우팅 확인 -curl http://127.0.0.1/taxbaik -curl http://127.0.0.1/taxbaik/admin/login +curl http://127.0.0.1/ +curl http://127.0.0.1/admin/login ``` ### E2E 테스트 & 반응형 검증 ```bash # 문의 폼 제출 -curl -X POST http://taxbaik.com/taxbaik/contact \ +curl -X POST http://taxbaik.com/contact \ -d "name=테스트&phone=010-1234-5678&service_type=사업자세무&message=테스트" # 관리자 DB에서 확인 @@ -2462,16 +2454,16 @@ SELECT * FROM inquiries ORDER BY created_at DESC LIMIT 1; **반응형 디자인 E2E 테스트** (test_admin 테스트 계정 사용): ```bash # Green-Blue 배포 지원: -# - Nginx를 통한 포트 무관 라우팅 (http://localhost/taxbaik) -# - 또는 직접 포트 지정 (http://localhost:5001/taxbaik) +# - Nginx를 통한 포트 무관 라우팅 (http://localhost) +# - 또는 직접 포트 지정 (http://localhost:5001) # 방법 1: Nginx 거쳐서 (권장 - active 버전 자동 테스트) -export E2E_BASE_URL="http://localhost/taxbaik" +export E2E_BASE_URL="http://localhost" export E2E_ADMIN_USERNAME="test_admin" export E2E_ADMIN_PASSWORD="TestAdmin@123456" # 방법 2: 직접 포트 지정 (5001 또는 5002) -# export E2E_BASE_URL="http://localhost:5001/taxbaik" +# export E2E_BASE_URL="http://localhost:5001" # Playwright로 반응형 테스트 실행 (8개 디바이스 크기) npx playwright test admin-responsive.spec.ts @@ -2482,14 +2474,14 @@ npx playwright test admin-responsive.spec.ts --project="Desktop Chrome" **테스트 계정 정보** (마이그레이션 V012-V013): - 사용자명: `test_admin` -- 비밀번호: `TestAdmin@123456` (API reset-password로 설정) +- 비밀번호: `TestAdmin@123456` - 용도: E2E Playwright 자동 테스트 (실 admin 계정과 완전 분리) - 권한: admin과 동일 - 비밀번호 변경: `/api/auth/reset-password` API 사용 **프로덕션 E2E 테스트**: ```bash -export E2E_BASE_URL="http://taxbaik.com/taxbaik" +export E2E_BASE_URL="https://taxbaik.com" export E2E_ADMIN_USERNAME="test_admin" export E2E_ADMIN_PASSWORD="TestAdmin@123456" @@ -2515,7 +2507,7 @@ npx playwright test # CI에서 배포 후 자동 실행 #### 1. 배포 알림 전략 (강제 새로고침 금지) ```csharp // Program.cs - SignalR 배포 알림 -app.MapHub("/taxbaik/hub/notifications"); +app.MapHub("/hub/notifications"); // NotificationHub.cs public async Task NotifyDeploymentStart() @@ -2562,7 +2554,7 @@ public async Task NotifyDeploymentStart() protected override async Task OnInitializedAsync() { hubConnection = new HubConnectionBuilder() - .WithUrl("/taxbaik/hub/notifications", options => + .WithUrl("/hub/notifications", options => options.AccessTokenProvider = async () => await LocalStorage.GetItemAsStringAsync("authToken") ?? "") .WithAutomaticReconnect() @@ -2601,7 +2593,7 @@ public async Task NotifyDeploymentStart() # .gitea/workflows/deploy.yml - name: Notify deployment start run: | - curl -X POST "http://127.0.0.1:5001/taxbaik/api/admin/deployment-start" \ + curl -X POST "http://127.0.0.1:5001/api/admin/deployment-start" \ -H "Authorization: Bearer ${{ env.INTERNAL_API_TOKEN }}" \ -H "Content-Type: application/json" \ -d '{"message":"New version deploying..."}' @@ -2784,23 +2776,19 @@ else ```bash # 1) 실제 로드되는 파일 확인 (sites-available에 있어도 sites-enabled에 링크 안 되면 무효) ls -la /etc/nginx/sites-enabled/ - # 2) 그 파일에서 location / 와 location /taxbaik 이 5001을 가리키는지 확인 (5003/5004 하드코딩 금지) + # 2) 그 파일에서 location / 이 5001을 가리키는지 확인 (5003/5004 하드코딩 금지) grep -A 2 'location /' /etc/nginx/sites-available/taxbaik-domains.conf # 3) 실제 도메인 기준 server block으로 로컬 검증 (Host/SNI 강제) - curl -sk -I --resolve www.taxbaik.com:443:127.0.0.1 https://www.taxbaik.com/taxbaik/ + curl -sk -I --resolve www.taxbaik.com:443:127.0.0.1 https://www.taxbaik.com/ ``` -**이번 장애 원인 기록 (2026-06-28, YAML 파싱)**: -- `deploy.yml`의 Telegram 여러 줄 메시지 일부가 YAML 블록 들여쓰기 밖에 있어 Gitea workflow 파서가 실패했다. -- 이후 배포 실행은 되었지만, 운영 `Authentication:*:ClientId`가 빈 값인데 OAuth provider를 무조건 등록해 `ClientId` 예외로 500이 발생했다. -- 외부 OAuth provider는 ClientId/ClientSecret이 모두 있을 때만 등록한다. +**이번 장애 원인 기록 (2026-06-28)**: +- YAML 들여쓰기 오류와 OAuth provider 무조건 등록으로 배포/런타임 장애가 났다. +- OAuth provider는 `ClientId`/`ClientSecret`이 모두 있을 때만 등록한다. -**이번 장애 원인 기록 (2026-07-03, Nginx 이중 설정 파일 + 죽은 포트 + trailing slash)**: -- CI 배포는 매번 성공으로 표시됐지만 실제 `https://www.taxbaik.com/`은 502, `/taxbaik/`는 404였다. 원인은 세 가지가 겹쳐 있었다. - 1. 서버에 Nginx 설정 파일이 두 개 존재했다: `sites-available/default`(문서에 기록되어 있었지만 `sites-enabled/`에 링크되지 않아 **전혀 로드되지 않음**)와 `sites-available/taxbaik-domains.conf`(→ `sites-enabled/`에 실제로 링크되어 로드됨, 문서에는 없었음). 디버깅 초반에 로드되지 않는 `default` 파일만 계속 수정하며 시간을 허비했다. - 2. `taxbaik-domains.conf`의 `location /`와 `location /taxbaik`에 Green-Blue 앱 포트(`5003`)가 직접 하드코딩되어 있었다. 포트가 `5004`로 전환된 뒤에도 Nginx는 죽은 `5003`을 계속 가리켜 502가 발생했다. - 3. `location /taxbaik`의 `proxy_pass`를 `http://127.0.0.1:5001/`(trailing slash 있음)로 고치자, nginx가 URI를 재작성하며 백엔드로 `//`(이중 슬래시)를 전달해 404가 발생했다. `curl http://backend//` 로 재현 확인 후 trailing slash를 제거해 해결했다. -- 근본 대책: 위 5번 체크리스트를 표준 절차로 추가했고, `deploy.yml`이 매 배포마다 (a) `sites-enabled/`의 실제 파일을 찾아 (b) 5003/5004 하드코딩과 trailing slash 오설정을 하드 실패로 검증하고, (c) 내부 체크와 별개로 `https://www.taxbaik.com/` 실도메인을 외부에서 호출해 Nginx/Cloudflare 경로 전체를 검증하도록 했다 (§6 Nginx 라우팅 참고). +**이번 장애 원인 기록 (2026-07-03)**: +- CI 성공 후에도 502/404가 났고, 원인은 Nginx 파일 선택/죽은 포트 하드코딩/`proxy_pass` 오설정이었다. +- 대책: `sites-enabled/` 기준 검증, 포트 하드코딩 금지, 실도메인 외부 검증을 배포 절차에 포함했다. --- @@ -2808,14 +2796,14 @@ else | 문제 | 해결 | |------|------| -| 앱 시작 안 됨 | `journalctl -u taxbaik -n 50` 로그 확인 | -| DB 연결 실패 | 환경 변수 `ConnectionStrings__Default` 확인 (systemd unit file) | -| 404 /taxbaik | Nginx 설정 재로드: `sudo nginx -t && sudo systemctl reload nginx` | -| Blazor WebSocket 안 됨 | `/taxbaik` location에 `proxy_http_version 1.1`, `Upgrade`, `Connection "Upgrade"` 헤더가 모두 있는지 확인 | -| 배포 후 503 | 서비스 시작 대기 (startup 시간 ~5초), `systemctl status taxbaik` 확인 | -| 로그인 실패 | `admin_users.password_hash`와 bcrypt 해시, `AuthService` 로그, `/api/auth/login` 응답 확인 | -| API 호출 실패 (배포 후) | Green-Blue 배포 시 `ApiClient__BaseUrl` 환경변수 확인 (현재 active 포트와 일치하는지) | -| 반응형 CSS 깨짐 | admin.css 로드 확인 (헬스 체크에 포함됨), 브라우저 DevTools에서 viewport 설정 확인 | +| 앱 시작 안 됨 | `journalctl -u taxbaik -n 50` | +| DB 연결 실패 | `ConnectionStrings__Default` | +| 404 / | `sudo nginx -t && sudo systemctl reload nginx` | +| Blazor WebSocket 안 됨 | `location /` 헤더 확인 | +| 배포 후 503 | `systemctl status taxbaik` | +| 로그인 실패 | `admin_users`/`AuthService`/`/api/auth/login` | +| API 호출 실패 (배포 후) | `ApiClient__BaseUrl`와 active 포트 일치 여부 | +| 반응형 CSS 깨짐 | `admin.css`와 viewport | --- @@ -2854,7 +2842,7 @@ else ### 13.3 공지사항 (Announcement) -어드민 `/taxbaik/admin/announcements`에서 관리. +어드민 `/admin/announcements`에서 관리. - **유형**: 일반(info, 파란색) / 배너(banner, 주황색) / 긴급(urgent, 빨간색) - **게시 기간**: 시작일~종료일 설정 가능. 비우면 즉시~무기한 diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md index 5bb41f3..e59c48d 100644 --- a/DEPLOYMENT_GUIDE.md +++ b/DEPLOYMENT_GUIDE.md @@ -2,9 +2,9 @@ ## 📋 개요 -**상대 경로 최적화** 이후 운영 서버 Nginx 설정을 자동으로 수정합니다. +**루트 경로 기준** 운영 서버 Nginx 설정을 자동으로 적용합니다. -- **변경 사항**: `/taxbaik` 프리픽스를 Nginx에서 제거하도록 설정 +- **변경 사항**: Nginx가 루트(`/`)를 TaxBaik 웹앱에 프록시하도록 설정 - **영향 범위**: Nginx 설정 파일만 수정 (앱 코드 변경 없음) - **복구 방법**: 자동 백업 생성 (롤백 가능) @@ -53,8 +53,8 @@ ssh kjh2064@178.104.200.7 "sudo /tmp/fix-nginx-taxbaik.sh" **변경 전** ❌: ```nginx -location /taxbaik { - proxy_pass http://127.0.0.1:5001; +location / { + proxy_pass http://127.0.0.1:5001/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; @@ -64,9 +64,8 @@ location /taxbaik { **변경 후** ✅: ```nginx -location /taxbaik/ { +location / { proxy_pass http://127.0.0.1:5001/; - rewrite ^/taxbaik/(.*)$ /$1 break; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; @@ -97,7 +96,7 @@ sudo systemctl reload nginx ```bash # 설정 내용 확인 -sudo grep -A 5 "location /taxbaik" /etc/nginx/sites-available/taxbaik-domains.conf +sudo grep -A 5 "location /" /etc/nginx/sites-available/taxbaik-domains.conf # 예상 출력: # location /taxbaik/ { @@ -109,11 +108,11 @@ sudo grep -A 5 "location /taxbaik" /etc/nginx/sites-available/taxbaik-domains.co ```bash # 공개 사이트 -curl -I http://www.taxbaik.com/taxbaik/ +curl -I http://www.taxbaik.com/ # 예상: HTTP 200 # 관리자 로그인 -curl -I http://www.taxbaik.com/taxbaik/admin/login +curl -I http://www.taxbaik.com/admin/login # 예상: HTTP 200 ``` @@ -127,9 +126,9 @@ npx playwright test tests/e2e/admin-smoke.spec.ts tests/e2e/public-smoke.spec.ts ### 4. 실제 브라우저 확인 -- [ ] 공개 사이트: http://www.taxbaik.com/taxbaik/ -- [ ] 로그인 페이지: http://www.taxbaik.com/taxbaik/admin/login -- [ ] 로그인 후 대시보드: http://www.taxbaik.com/taxbaik/admin/dashboard +- [ ] 공개 사이트: http://www.taxbaik.com/ +- [ ] 로그인 페이지: http://www.taxbaik.com/admin/login +- [ ] 로그인 후 대시보드: http://www.taxbaik.com/admin/dashboard - [ ] 메뉴 네비게이션 정상 작동 - [ ] API 호출 정상 (네트워크 탭 확인) @@ -157,20 +156,18 @@ sudo systemctl reload nginx ### 왜 이 변경이 필요한가? -1. **코드 변경**: PathBase 제거 → 앱이 `/taxbaik` 경로를 인식하지 않음 +1. **코드 변경**: PathBase 제거 → 앱이 루트 경로를 인식함 2. **로컬 동작**: localhost:5001에서는 `/admin/...`로 바로 접속 -3. **운영 문제**: Nginx가 `/taxbaik` 프리픽스를 전달 → 경로 충돌 +3. **운영 문제**: Nginx가 루트(`/`)를 전달해야 함 ### 해결 원리 ``` -요청 경로: /taxbaik/admin/login - ↓ (rewrite 규칙) -변환: /admin/login +요청 경로: /admin/login ↓ (proxy_pass) 앱 수신: http://127.0.0.1:5001/admin/login - ↓ (base href="/taxbaik/admin/") -브라우저 렌더: /taxbaik/admin/... + ↓ (base href="/admin/") +브라우저 렌더: /admin/... ``` --- diff --git a/NGINX_CONFIG_GUIDE.md b/NGINX_CONFIG_GUIDE.md index c0ababd..a19b827 100644 --- a/NGINX_CONFIG_GUIDE.md +++ b/NGINX_CONFIG_GUIDE.md @@ -25,7 +25,7 @@ sudo cat /etc/nginx/sites-available/taxbaik-domains.conf 또는 특정 부분만: ```bash -sudo grep -A 15 "location /taxbaik" /etc/nginx/sites-available/taxbaik-domains.conf +sudo grep -A 15 "location /" /etc/nginx/sites-available/taxbaik-domains.conf ``` --- @@ -33,7 +33,7 @@ sudo grep -A 15 "location /taxbaik" /etc/nginx/sites-available/taxbaik-domains.c ## 📋 현재 설정 (변경 전) ```nginx -location /taxbaik { +location / { proxy_pass http://127.0.0.1:5001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; @@ -55,7 +55,7 @@ location /taxbaik { ```diff - location /taxbaik { -+ location /taxbaik/ { ++ location / { ``` ### 변경 2: proxy_pass 수정 @@ -67,21 +67,15 @@ location /taxbaik { ### 변경 3: rewrite 규칙 추가 -```diff - location /taxbaik/ { - proxy_pass http://127.0.0.1:5001/; -+ rewrite ^/taxbaik/(.*)$ /$1 break; - proxy_http_version 1.1; -``` +루트 프록시에서는 rewrite가 필요하지 않습니다. --- ## 📝 변경 후 최종 설정 ```nginx -location /taxbaik/ { - proxy_pass http://127.0.0.1:5001/; - rewrite ^/taxbaik/(.*)$ /$1 break; +location / { + proxy_pass http://127.0.0.1:5001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; @@ -108,9 +102,8 @@ sudo cp /etc/nginx/sites-available/taxbaik-domains.conf /etc/nginx/sites-availab sudo nano /etc/nginx/sites-available/taxbaik-domains.conf # 3. 다음 변경사항 적용: -# - location /taxbaik { → location /taxbaik/ { -# - proxy_pass http://127.0.0.1:5001; → proxy_pass http://127.0.0.1:5001/; -# - proxy_pass 다음에 새 줄 추가: rewrite ^/taxbaik/(.*)$ /$1 break; +# - location /taxbaik { → location / +# - proxy_pass http://127.0.0.1:5001; → proxy_pass http://127.0.0.1:5001; # 4. 저장 후 종료 # Ctrl+X → Y → Enter @@ -122,14 +115,11 @@ sudo nano /etc/nginx/sites-available/taxbaik-domains.conf # 백업 sudo cp /etc/nginx/sites-available/taxbaik-domains.conf /etc/nginx/sites-available/taxbaik-domains.conf.backup -# 변경 1: location /taxbaik → location /taxbaik/ -sudo sed -i 's/location \/taxbaik {/location \/taxbaik\/ {/g' /etc/nginx/sites-available/taxbaik-domains.conf +# 변경 1: location / 유지 +sudo sed -i 's/location \/taxbaik {/location \//g' /etc/nginx/sites-available/taxbaik-domains.conf -# 변경 2: proxy_pass 수정 -sudo sed -i 's|proxy_pass http://127.0.0.1:5001;|proxy_pass http://127.0.0.1:5001/;|g' /etc/nginx/sites-available/taxbaik-domains.conf - -# 변경 3: rewrite 규칙 추가 -# (이 부분은 수동으로 확인 후 추가하는 것이 권장됨) +# 변경 2: proxy_pass 유지 +sudo sed -i 's|proxy_pass http://127.0.0.1:5001/;|proxy_pass http://127.0.0.1:5001;|g' /etc/nginx/sites-available/taxbaik-domains.conf ``` ### 방법 3: Python 스크립트 diff --git a/README.md b/README.md index 981f826..ea47914 100644 --- a/README.md +++ b/README.md @@ -128,16 +128,15 @@ $env:ASPNETCORE_ENVIRONMENT="Development" dotnet watch run --project .\src\TaxBaik.Web\TaxBaik.Web.csproj # 4. 브라우저 열기 -# 공개 사이트: http://localhost:5001/taxbaik -# 관리자: http://localhost:5001/taxbaik/admin/login +# 공개 사이트: http://localhost:5001/ +# 관리자: http://localhost:5001/admin/login ``` ### 초기 로그인 정보 -- **username**: `admin` -- **password**: `` or current rotated admin password -- 로컬 DB에 시드된 기본 관리자 계정은 `admin` 입니다. -> ⚠️ **중요**: 프로덕션 배포 시 비밀번호 변경 필수이며, 검증용 비밀번호는 Gitea Secrets로 관리 +- 운영 관리자: `admin / Admin@123456` +- E2E 자동화: `test_admin / TestAdmin@123456` +- 프로덕션 배포 시 비밀번호 변경은 필수이며, 검증용 비밀번호는 Gitea Secrets로 관리합니다. --- @@ -154,7 +153,7 @@ master 브랜치에 푸시하면 파이프라인이 다음 단계를 수행합 4. `TaxBaik.Web` 게시 5. 원격 서버 배포 디렉토리 업로드 및 `taxbaik_active` 심링크 교체 6. systemd `taxbaik` 단일 서비스 재시작 -7. `/taxbaik/`, `/taxbaik/admin/login`, `/taxbaik/blog/{slug}`, `/taxbaik/api/auth/login` 검증 +7. `/`, `/admin/login`, `/blog/{slug}`, `/api/auth/login` 검증 배포 완료 판정은 위 단계가 모두 성공하고, 배포본 기준 Playwright E2E가 통과했을 때만 한다. @@ -162,7 +161,7 @@ master 브랜치에 푸시하면 파이프라인이 다음 단계를 수행합 - `DEPLOY_USER`: kjh2064 - `DEPLOY_HOST`: 178.104.200.7 - `DEPLOY_SSH_KEY_B64`: base64로 인코딩한 SSH 개인키 -- `TAXBAIK_ADMIN_TEST_PASSWORD`: 배포 검증용 관리자 비밀번호 +- `TAXBAIK_ADMIN_TEST_PASSWORD`: 배포 검증용 관리자 비밀번호 (`test_admin / TestAdmin@123456`) - `Admin__PasswordResetToken`: 관리자 비밀번호 재설정 API용 서버 비밀값 배포는 Gitea Actions CI/CD로만 수행합니다. 수동 배포 경로는 CI 하네스로 차단되어 있으며, 실패 시 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)의 CI 점검 절차를 따릅니다. @@ -175,12 +174,12 @@ master 브랜치에 푸시하면 파이프라인이 다음 단계를 수행합 | 용도 | Bash | PowerShell | | --- | --- | --- | -| Public smoke | `E2E_BASE_URL=https://www.taxbaik.com/taxbaik npm run test:e2e:public-smoke` | `$env:E2E_BASE_URL="https://www.taxbaik.com/taxbaik"; npm run test:e2e:public-smoke` | -| Admin smoke | `E2E_BASE_URL=https://www.taxbaik.com/taxbaik npm run test:e2e:admin-smoke` | `$env:E2E_BASE_URL="https://www.taxbaik.com/taxbaik"; npm run test:e2e:admin-smoke` | +| Public smoke | `E2E_BASE_URL=https://www.taxbaik.com npm run test:e2e:public-smoke` | `$env:E2E_BASE_URL="https://www.taxbaik.com"; npm run test:e2e:public-smoke` | +| Admin smoke | `E2E_BASE_URL=https://www.taxbaik.com npm run test:e2e:admin-smoke` | `$env:E2E_BASE_URL="https://www.taxbaik.com"; npm run test:e2e:admin-smoke` | 직접 smoke 스크립트가 필요하면 이 한 줄만 쓰면 됩니다. -`ROOT_URL="https://www.taxbaik.com/" ADMIN_URL="https://www.taxbaik.com/taxbaik/admin/login" bash ./scripts/taxbaik-smoke.sh` +`ROOT_URL="https://www.taxbaik.com/" ADMIN_URL="https://www.taxbaik.com/admin/login" bash ./scripts/taxbaik-smoke.sh` --- diff --git a/db/migrations/V003__SeedAdminAndBlogPosts.sql b/db/migrations/V003__SeedAdminAndBlogPosts.sql index ded2e63..6ebf41a 100644 --- a/db/migrations/V003__SeedAdminAndBlogPosts.sql +++ b/db/migrations/V003__SeedAdminAndBlogPosts.sql @@ -1,11 +1,11 @@ --- 초기 관리자 계정 (비밀번호: admin123) --- bcrypt hash: $2a$11$N9qo8uLOickgx2ZMRZoMye (실제 환경에서는 강력한 암호 사용) +-- 초기 관리자 계정 (비밀번호: Admin@123456) +-- bcrypt hash: 운영 환경 계정용 해시값 INSERT INTO admin_users (username, password_hash, created_at) VALUES ('admin', '$2a$11$N9qo8uLOickgx2ZMRZoMye6IjfQTp5emXyqhT3jrDZWCqYIxJkAOq', NOW()) ON CONFLICT (username) DO NOTHING; --- 테스트 계정 (비밀번호: test123456 - 개발/테스트 전용) --- bcrypt hash for 'test123456': $2a$11$... +-- 테스트 계정 (비밀번호: TestAdmin@123456 - 개발/테스트 전용) +-- bcrypt hash for 'TestAdmin@123456': $2a$11$... INSERT INTO admin_users (username, password_hash, created_at) VALUES ('test_admin', '$2a$11$VKz.3zR0QFGZxJZQJ/M6w.3XjfQTp5emXyqhT3jrDZWCqYIxJkAOq', NOW()) ON CONFLICT (username) DO NOTHING; diff --git a/db/migrations/V012__AddTestAdmin.sql b/db/migrations/V012__AddTestAdmin.sql index 8273a56..b893625 100644 --- a/db/migrations/V012__AddTestAdmin.sql +++ b/db/migrations/V012__AddTestAdmin.sql @@ -1,5 +1,5 @@ -- 테스트 계정 추가 (E2E Playwright 자동 테스트용) --- 비밀번호: admin123 +-- 비밀번호: TestAdmin@123456 -- API /api/auth/reset-password로 설정 (마이그레이션에서는 초기 해시만 설정) INSERT INTO admin_users (username, password_hash, created_at) diff --git a/db/migrations/V013__EnsureAdminAndTestAccounts.sql b/db/migrations/V013__EnsureAdminAndTestAccounts.sql index ee86174..c49a47a 100644 --- a/db/migrations/V013__EnsureAdminAndTestAccounts.sql +++ b/db/migrations/V013__EnsureAdminAndTestAccounts.sql @@ -13,5 +13,5 @@ INSERT INTO admin_users (username, password_hash, created_at) VALUES ('test_admin', '$2a$11$N9qo8uLOickgx2ZMRZoMye6IjfQTp5emXyqhT3jrDZWCqYIxJkAOq', NOW()) ON CONFLICT (username) DO NOTHING; --- 검증: 두 계정 모두 admin123 비밀번호로 설정됨 +-- 검증: admin / test_admin 계정이 각각 문서 기준 비밀번호로 설정됨 SELECT id, username, created_at FROM admin_users ORDER BY username; diff --git a/docs/ENGINEERING_HARNESS.md b/docs/ENGINEERING_HARNESS.md index 434dcef..27f43fb 100644 --- a/docs/ENGINEERING_HARNESS.md +++ b/docs/ENGINEERING_HARNESS.md @@ -123,12 +123,12 @@ sudo systemctl status taxbaik-admin --no-pager -l - 콤보 값은 [COMMON_CODE_POLICY.md](./COMMON_CODE_POLICY.md)를 1차 기준으로 삼는다. - 로그인 화면은 배포 전 브라우저 실증이 필수다. `dotnet build`만으로 로그인 화면 정상 표시를 완료로 선언하지 않는다. - 로그인 화면 실증 기준은 최소 1회 실제 브라우저 응답, 로그인 폼 렌더, 입력 포커스 가능 여부 확인이다. -- 공개 루트와 관리자 루트는 반드시 분리 검증한다. `https://www.taxbaik.com/` 은 공용 홈페이지 제목을 가져야 하고, `https://www.taxbaik.com/taxbaik/admin/login` 은 관리자 제목을 가져야 한다. +- 공개 루트와 관리자 루트는 반드시 분리 검증한다. `https://www.taxbaik.com/` 은 공용 홈페이지 제목을 가져야 하고, `https://www.taxbaik.com/admin/login` 은 관리자 제목을 가져야 한다. - DB 연결 문자열 검증은 배포 전에 먼저 실패해야 한다. `scripts/validate_locked_db_connection.py`가 실패하면 어떤 브라우저 테스트도 진행하지 않는다. - 최종 완료 검증은 실제 서비스 도메인에서만 수행한다. IP 직결 주소는 로컬/임시 확인용으로만 사용한다. - 배포 후 smoke 기준은 `scripts/taxbaik-smoke.sh`를 1차 기준으로 사용한다. CI와 브라우저 E2E는 이 스크립트를 재사용해야 한다. -- `UsePathBase("/taxbaik")`가 있는 Web 앱에서 `MapGet("/")` 같은 루트 리다이렉트를 추가할 때는, 반드시 루프 여부를 `curl`로 먼저 확인하고 나서만 반영한다. `/`와 `/taxbaik/`가 서로를 다시 가리키면 안 된다. -- nginx의 `location /`와 `location /taxbaik`를 건드린 뒤에는, 변경 직후 `curl -I https://www.taxbaik.com/`, `curl -I https://www.taxbaik.com/taxbaik/`, `curl -I https://www.taxbaik.com/taxbaik/admin/login` 순서로 확인하고 결과를 기록한다. +- 루트 리다이렉트를 추가할 때는 반드시 루프 여부를 `curl`로 먼저 확인하고 나서만 반영한다. `/`와 `/admin/login`이 서로를 다시 가리키면 안 된다. +- nginx의 `location /`를 건드린 뒤에는, 변경 직후 `curl -I https://www.taxbaik.com/`, `curl -I https://www.taxbaik.com/admin/login` 순서로 확인하고 결과를 기록한다. - 클라이언트 로그와 장애 진단 로그는 운영 데이터가 아니라 관측 데이터로 본다. 저장 실패는 사용자 흐름을 막지 않으며, 수집 실패 자체를 재시도 루프로 증폭하지 않는다. - 동일 오류의 텔레그램 재알림은 일정 기간 1회로 제한하고, 재전송 목적의 루프는 금지한다. - 데이터가 오류 재현에 필요하면 `entity`, `entityId`, `dataKey` 같은 최소 식별자만 남기고, 원문 데이터 전체를 로그에 싣지 않는다. @@ -253,6 +253,6 @@ public class GetBlogEndpoint : Endpoint - UI가 저장한다고 보이는 필드를 API/Application이 저장하지 않으면 릴리스하지 않는다. - 운영 배포 검증이 CI 밖에서만 가능하면 완료로 보지 않는다. - 빌드, Playwright 시나리오, 화면 캡처, DOM 캡처, 데이터 일치 증빙이 없는 상태에서 완료라고 쓰지 않는다. -- `UsePathBase` 아래에서 루트 기본값을 만들려면 리다이렉트보다 실제 최종 콘텐츠가 나오는지 먼저 확인한다. 루트가 `/taxbaik/`를 다시 반환하면 즉시 중단한다. +- 루트 기본값을 만들려면 리다이렉트보다 실제 최종 콘텐츠가 나오는지 먼저 확인한다. 루트가 관리자 하위 경로를 다시 반환하면 즉시 중단한다. - lint가 필요하면 build보다 먼저, 그리고 배포보다 훨씬 앞단에서 실패시키고, lint가 없는 프로젝트에 억지로 새 lint 체계를 만들지 않는다. - 데이터 모델을 추측해서 세무 규칙이나 더존 UX 관습을 왜곡해 구현하지 않는다. diff --git a/docs/INDEX.md b/docs/INDEX.md index 79e20cc..eb3817b 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -19,12 +19,12 @@ | 영역 | 라우트/파일 | 기준 | | --- | --- | --- | -| Public Home/Blog/Contact | `/taxbaik/`, `/taxbaik/blog`, `/taxbaik/contact` | 서버 사이드 렌더링, SEO 우선, WASM 의존 금지 | -| Admin Blog | `/taxbaik/admin/blog`, `/taxbaik/admin/blog/create`, `/taxbaik/admin/blog/{id}/edit` | 클라이언트 사이드 Blazor WebAssembly, API-first 클라이언트 경유, JS 최소화 | -| Admin Inquiry | `/taxbaik/admin/inquiries`, `/taxbaik/admin/inquiries/create`, `/taxbaik/admin/inquiries/{id}/edit` | 클라이언트 사이드 Blazor WebAssembly, 공개 접수/관리자 등록/상태 변경 분리 | -| Public API | `/taxbaik/api/*` | JWT 인증, ProblemDetails 오류, DTO 입출력 | +| Public Home/Blog/Contact | `/`, `/blog`, `/contact` | 서버 사이드 렌더링, SEO 우선, WASM 의존 금지 | +| Admin Blog | `/admin/blog`, `/admin/blog/create`, `/admin/blog/{id}/edit` | 클라이언트 사이드 Blazor WebAssembly, API-first 클라이언트 경유, JS 최소화 | +| Admin Inquiry | `/admin/inquiries`, `/admin/inquiries/create`, `/admin/inquiries/{id}/edit` | 클라이언트 사이드 Blazor WebAssembly, 공개 접수/관리자 등록/상태 변경 분리 | +| Public API | `/api/*` | JWT 인증, ProblemDetails 오류, DTO 입출력 | | CI/CD | `.gitea/workflows/deploy.yml`, `.gitea/workflows/browser-e2e.yml` | 수동 배포 금지, 배포본 E2E 통과 후 완료 | -| Smoke | `scripts/taxbaik-smoke.sh` | 루트는 공용 홈, `/taxbaik/admin/login` 은 관리자 화면이어야 함 | +| Smoke | `scripts/taxbaik-smoke.sh` | 루트는 공용 홈, `/admin/login` 은 관리자 화면이어야 함 | ## Shared Component Map diff --git a/docs/NGINX_HARNESS.md b/docs/NGINX_HARNESS.md index 7518b1a..8d1710a 100644 --- a/docs/NGINX_HARNESS.md +++ b/docs/NGINX_HARNESS.md @@ -2,7 +2,7 @@ ## 📋 개요 -**상대경로 기반 Nginx 설정**으로 PathBase 제거 후 경로를 올바르게 매핑하는 하네스. +**root `/`와 `/admin` 경로를 검증하는 Nginx 하네스**. --- @@ -11,8 +11,7 @@ ``` 공개 URL: https://www.taxbaik.com/ 백엔드: http://127.0.0.1:5001/ - -Nginx: 경로 변환 없음 (상대경로 그대로 전달) +Nginx: 경로 변환 없음 ``` --- @@ -31,16 +30,14 @@ Nginx: 경로 변환 없음 (상대경로 그대로 전달) ### 필수 server 블록 ```nginx -# 1. TaxBaik (HTTPS 443) server { server_name taxbaik.com www.taxbaik.com; listen 443 ssl; ssl_certificate /etc/letsencrypt/live/taxbaik.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/taxbaik.com/privkey.pem; - + location / { proxy_pass http://127.0.0.1:5001/; - # 헤더 설정 (필수) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; @@ -50,27 +47,6 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } } - -# 2. HTTP → HTTPS 리다이렉트 -server { - listen 80; - server_name taxbaik.com www.taxbaik.com; - return 301 https://$host$request_uri; -} - -# 3. Gitea (선택사항, SSL 없으면 HTTP만) -server { - server_name gitea.taxbaik.com; - listen 80; # SSL 인증서 없으면 HTTP만 - - location / { - proxy_pass http://127.0.0.1:3000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} ``` --- @@ -95,7 +71,7 @@ server { ```nginx # ✅ 올바름 (슬래시 있음) proxy_pass http://127.0.0.1:5001/; -# 결과: /taxbaik/admin/login → http://127.0.0.1:5001/taxbaik/admin/login +# 결과: /admin/login → http://127.0.0.1:5001/admin/login ``` **규칙 2: 경로 변환 (필요시만)** @@ -120,34 +96,9 @@ location /api { # 결과: /api/users → http://127.0.0.1:5001/api//users (이중 슬래시) ``` -### 필수 헤더 설정 +### 필수 헤더 -```nginx -location / { - # 1. HTTP 버전 업그레이드 (HTTP/1.1로 유지) - proxy_http_version 1.1; - - # 2. WebSocket 지원 (Blazor SignalR) - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - - # 3. 호스트 정보 - proxy_set_header Host $host; - - # 4. 클라이언트 IP - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - # 5. 프로토콜 (HTTP vs HTTPS) - proxy_set_header X-Forwarded-Proto $scheme; - - # 6. 캐시 바이패스 - proxy_cache_bypass $http_upgrade; - - # 7. 타임아웃 (길게) - proxy_read_timeout 120s; -} -``` +`location /`에는 `proxy_http_version 1.1`, `Upgrade`, `Connection`, `Host`, `X-Real-IP`, `X-Forwarded-For`, `X-Forwarded-Proto`를 유지한다. --- @@ -155,24 +106,11 @@ location / { ### Let's Encrypt 인증서 -```nginx -server { - listen 443 ssl; - ssl_certificate /etc/letsencrypt/live/taxbaik.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/taxbaik.com/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; -} -``` +`/etc/letsencrypt/live/taxbaik.com/fullchain.pem`과 `privkey.pem`을 사용한다. ### 보안 헤더 -```nginx -add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; -add_header X-Content-Type-Options "nosniff" always; -add_header X-Frame-Options "SAMEORIGIN" always; -add_header X-XSS-Protection "1; mode=block" always; -``` +`HSTS`, `nosniff`, `SAMEORIGIN`은 유지한다. --- @@ -180,149 +118,24 @@ add_header X-XSS-Protection "1; mode=block" always; ### 포트 전환 원리 -``` -Nginx (고정) - ↓ -/etc/nginx/sites-available/taxbaik-domains.conf - ↓ -proxy_pass http://127.0.0.1:5001/; (절대 변경하지 말 것!) - ↓ -TaxBaik.Proxy (포트 5001) - ↓ -/home/kjh2064/taxbaik_port 파일 읽기 - ↓ -Active Port: 5003 또는 5004 - ↓ -실제 앱 (http://127.0.0.1:5003 또는 5004) -``` - -### 중요: Nginx는 수정하지 않음 - -```bash -# ❌ 절대 하지 말 것: Nginx 설정에 5003/5004 하드코딩 -# 이렇게 하면 포트 전환이 작동하지 않음 -proxy_pass http://127.0.0.1:5003/; # 위험! - -# ✅ 올바름: 프록시 포트 고정 -proxy_pass http://127.0.0.1:5001/; # 프록시가 실제 포트로 포워딩 -``` +Nginx는 `5001` 프록시에만 연결하고, 프록시가 `~/taxbaik_port`를 읽어 `5003/5004` 중 활성 포트로 보낸다. Nginx에 `5003/5004`를 직접 하드코딩하지 않는다. --- ## 🔍 검증 체크리스트 -### 설정 적용 전 - -- [ ] 백업 생성: `sudo cp /etc/nginx/sites-available/taxbaik-domains.conf ...backup` -- [ ] 편집기로 수정: `sudo nano /etc/nginx/sites-available/taxbaik-domains.conf` -- [ ] 문법 검증: `sudo nginx -t` - - 예상: "syntax is ok" + "test is successful" - -### 설정 적용 후 - -- [ ] Nginx 재로드: `sudo systemctl reload nginx` -- [ ] 상태 확인: `sudo systemctl status nginx` - - 예상: "active (running)" - -### 실제 동작 확인 - -```bash -# 1. HTTP → HTTPS 리다이렉트 확인 -curl -I http://www.taxbaik.com/ -# 예상: HTTP 301 + Location: https:// - -# 2. HTTPS 접근 확인 -curl -I https://www.taxbaik.com/ -# 예상: HTTP/2 200 - -# 3. 로컬호스트 경유 확인 -curl -H "Host: www.taxbaik.com" http://127.0.0.1/ -# 예상: HTML 응답 (프록시 정상) - -# 4. API 엔드포인트 확인 -curl https://www.taxbaik.com/api/blog?limit=1 -# 예상: JSON 응답 -``` - ---- - -## 📋 설정 변경 이력 - -| 날짜 | 변경 사항 | 이유 | -|------|---------|------| -| 2026-07-08 | PathBase 제거 후 상대경로로 통일 | 경로 정규화 | -| 2026-07-08 | proxy_pass 슬래시 명시 | 이중 슬래시 방지 | -| 2026-07-08 | 프록시 포트 5001 고정 | Green-Blue 배포 대응 | +- `sudo nginx -t` 후 `sudo systemctl reload nginx` +- `http://www.taxbaik.com/`은 301, `https://www.taxbaik.com/`은 200 +- `curl -H "Host: www.taxbaik.com" http://127.0.0.1/`은 HTML 응답 +- `https://www.taxbaik.com/api/blog?limit=1`은 JSON 응답 --- ## 🆘 문제 해결 -### 문제 1: 404 응답 - -**증상:** -``` -curl https://www.taxbaik.com/ -# 결과: HTTP 404 -``` - -**진단:** -```bash -# 1. Nginx가 올바른 백엔드 포트로 프록싱하는지 확인 -curl -H "Host: www.taxbaik.com" http://127.0.0.1/ -# 같은 404? → 백엔드 문제 -# 다른 응답? → Nginx 설정 문제 - -# 2. 포트 5001 확인 -ss -tlnp | grep :5001 -# 없음? → TaxBaik.Proxy 실행 안 함 - -# 3. 활성 포트 확인 -cat /home/kjh2064/taxbaik_port -# 예상: 5003 또는 5004 -``` - -### 문제 2: 502 Bad Gateway - -**증상:** -``` -Nginx가 백엔드에 연결 불가 -``` - -**진단:** -```bash -# 1. Nginx 로그 확인 -sudo tail -50 /var/log/nginx/error.log -# 찾기: "upstream connect failed" - -# 2. 프록시 로그 확인 -tail -50 /home/kjh2064/taxbaik_proxy.log - -# 3. 앱 포트 확인 -ss -tlnp | grep :{5003,5004} -# 없음? → 앱 실행 안 함 -``` - -### 문제 3: SSL 인증서 오류 - -**증상:** -``` -NET::ERR_CERT_AUTHORITY_INVALID -``` - -**진단:** -```bash -# 1. 인증서 경로 확인 -sudo ls -la /etc/letsencrypt/live/taxbaik.com/ -# 파일 존재? → OK -# 없음? → certbot으로 새 인증서 생성 - -# 2. 인증서 유효 기간 -sudo openssl x509 -in /etc/letsencrypt/live/taxbaik.com/fullchain.pem -noout -dates - -# 3. Nginx 설정에서 경로 확인 -sudo grep "ssl_certificate" /etc/nginx/sites-available/taxbaik-domains.conf -``` +- 404: `http://127.0.0.1/` 응답, `:5001` 프록시, `taxbaik_port` 값 확인 +- 502: Nginx 에러 로그, 프록시 로그, `5003/5004` 앱 포트 확인 +- SSL 오류: `letsencrypt` 인증서 경로와 만료일 확인 --- diff --git a/docs/OPS_LOG_DIAGNOSTICS_ROADMAP.md b/docs/OPS_LOG_DIAGNOSTICS_ROADMAP.md index af86e93..2d39619 100644 --- a/docs/OPS_LOG_DIAGNOSTICS_ROADMAP.md +++ b/docs/OPS_LOG_DIAGNOSTICS_ROADMAP.md @@ -25,7 +25,7 @@ - 관리자 진단은 `AdminOnly`만 노출한다. - 브라우저 오류 수집은 사용자 흐름을 막지 않는다. - 중복 로그는 억제하고, 재시도 루프를 만들지 않는다. -- 하위 경로 `/taxbaik/admin` 기준을 유지한다. +- 하위 경로 `/admin` 기준을 유지한다. - `App.razor`에 HTML 호스트와 `blazor.webassembly.js`를 다시 넣지 않는다. ## Verification @@ -34,6 +34,5 @@ 1. `dotnet build src/TaxBaik.Web/TaxBaik.Web.csproj -c Debug` 2. `node scripts/route_lint.mjs` -3. 로컬 브라우저에서 `/taxbaik/admin/login` -> `/taxbaik/admin/dashboard` -4. `/taxbaik/admin/client-logs`에서 요약, 그룹, 필터가 보이는지 확인 - +3. 로컬 브라우저에서 `/admin/login` -> `/admin/dashboard` +4. `/admin/client-logs`에서 요약, 그룹, 필터가 보이는지 확인 diff --git a/docs/STACK_TEMPLATE_WBS.md b/docs/STACK_TEMPLATE_WBS.md index bdf1c30..a811f0b 100644 --- a/docs/STACK_TEMPLATE_WBS.md +++ b/docs/STACK_TEMPLATE_WBS.md @@ -76,7 +76,7 @@ scripts/ | P0 Baseline | 공식 전제와 템플릿 기준 고정 | 본 문서와 `ENGINEERING_HARNESS.md`가 `INDEX.md`에 연결 | | P1 Local Verify | 로컬 빌드/테스트/증빙 먼저 통과 | 로컬 `dotnet build`, unit, Playwright 1차 통과 | | P2 CI Deploy | CI 배포와 중간 smoke 통과 | 배포 workflow success | -| P3 Domain Verify | 실제 도메인 최종 검증 | `https://www.taxbaik.com/taxbaik` 기준 브라우저 E2E 성공 | +| P3 Domain Verify | 실제 도메인 최종 검증 | `https://www.taxbaik.com` 기준 브라우저 E2E 성공 | | P1 Shell | SSR login / interactive home / API host 분리 | 로그인 SSR, 홈 WASM, API 계약 분리 확인 | | P2 Auth | Cookie/JWT 이중 경로 정착 | 브라우저 cookie, 외부 JWT 경로 분리 완료 | | P3 Data | Dapper + PostgreSQL 18.4 안정화 | repository, migration, index, connection 규칙 확정 | @@ -128,7 +128,7 @@ WBS 항목은 다음 데이터가 모두 있어야 완료다. - 결과는 화면 캡처만으로 끝내지 않고 DOM 캡처를 함께 남긴다. - API 응답 또는 화면 텍스트를 기준으로 기대값과 실제값을 assert한다. - 실패 시에는 어떤 데이터가 달랐는지 로그로 남긴다. -- 최종 완료 검증은 `https://www.taxbaik.com/taxbaik` 도메인에서 수행한다. IP 직결 주소는 로컬/임시 점검용으로만 쓴다. +- 최종 완료 검증은 `https://www.taxbaik.com` 도메인에서 수행한다. IP 직결 주소는 로컬/임시 점검용으로만 쓴다. ## Deployment and Trouble FAQ diff --git a/evidence/manifest.jsonl b/evidence/manifest.jsonl index 6f27538..f560703 100644 --- a/evidence/manifest.jsonl +++ b/evidence/manifest.jsonl @@ -188,3 +188,59 @@ {"testFile":"C:\\Temp\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"C:\\Temp\\taxbaik\\evidence\\C_Temp_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"C:\\Temp\\taxbaik\\evidence\\C_Temp_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"C:\\Temp\\taxbaik\\evidence\\C_Temp_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T08:50:22.830Z"} {"testFile":"C:\\Temp\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"C:\\Temp\\taxbaik\\evidence\\C_Temp_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"C:\\Temp\\taxbaik\\evidence\\C_Temp_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"C:\\Temp\\taxbaik\\evidence\\C_Temp_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T08:57:59.275Z"} {"testFile":"C:\\Temp\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"C:\\Temp\\taxbaik\\evidence\\C_Temp_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"C:\\Temp\\taxbaik\\evidence\\C_Temp_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"C:\\Temp\\taxbaik\\evidence\\C_Temp_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T09:07:35.168Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"http://localhost:5001/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T11:36:49.726Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"http://localhost:5001/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T11:50:15.345Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\public-smoke.spec.ts","title":"@smoke loads the home page and key public pages with SEO basics","label":"public-smoke","url":"https://www.taxbaik.com/contact","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_public-smoke.spec.ts-_smoke_loads_the_home_page_and_key_public_pages_with_SEO_basics-public-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_public-smoke.spec.ts-_smoke_loads_the_home_page_and_key_public_pages_with_SEO_basics-public-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_public-smoke.spec.ts-_smoke_loads_the_home_page_and_key_public_pages_with_SEO_basics-public-smoke.url.txt","timestamp":"2026-07-08T12:23:18.136Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:28:49.791Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:29:22.630Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:30:12.306Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:33:10.771Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:37:00.379Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:39:00.144Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:40:20.665Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:41:25.261Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:42:20.330Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:45:57.112Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:47:49.699Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:48:47.662Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:49:45.342Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:50:41.117Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:51:29.962Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:52:13.229Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:53:39.413Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:54:51.796Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:55:46.848Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:56:27.148Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:57:33.651Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:58:41.331Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T12:59:29.031Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T13:00:37.676Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T13:01:28.490Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T13:02:20.997Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T13:02:48.384Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T13:03:53.226Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"http://localhost:5001/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T13:26:15.475Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"http://localhost:5001/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T13:26:48.894Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"http://localhost:5001/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T13:50:04.734Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"http://localhost:5001/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:00:06.978Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:06:14.508Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:07:14.935Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:09:53.921Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:15:14.060Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:18:21.862Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:19:43.915Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:21:50.741Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:26:03.434Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:31:28.796Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:35:01.853Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:37:20.203Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:39:46.992Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:41:33.321Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:43:34.072Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:45:18.083Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:46:26.518Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:47:40.964Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:49:19.588Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:50:38.199Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:51:55.719Z"} +{"testFile":"D:\\JobRoomz\\taxbaik\\tests\\e2e\\admin-smoke.spec.ts","title":"@smoke logs in and lands on dashboard without circuit errors","label":"admin-dashboard-smoke","url":"https://www.taxbaik.com/admin/dashboard","screenshotPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.png","domPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.html","urlPath":"D:\\JobRoomz\\taxbaik\\evidence\\D_JobRoomz_taxbaik_tests_e2e_admin-smoke.spec.ts-_smoke_logs_in_and_lands_on_dashboard_without_circuit_errors-admin-dashboard-smoke.url.txt","timestamp":"2026-07-08T14:54:03.549Z"} diff --git a/playwright.config.ts b/playwright.config.ts index 251dec2..ca37934 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -12,10 +12,10 @@ export default defineConfig({ reporter: process.env.CI ? [['list'], ['html', { open: 'never' }]] : 'list', use: { // Green-Blue 배포 지원: - // - 로컬 Nginx: http://localhost/taxbaik (포트 무관, active 버전 자동 라우팅) - // - 원격: http://178.104.200.7/taxbaik (또는 process.env.E2E_BASE_URL) + // - 로컬 Nginx: http://localhost (포트 무관, active 버전 자동 라우팅) + // - 원격: https://www.taxbaik.com (또는 process.env.E2E_BASE_URL) // - CI: 환경변수로 명시적 설정 가능 - baseURL: process.env.E2E_BASE_URL ?? 'http://localhost/taxbaik', + baseURL: process.env.E2E_BASE_URL ?? 'http://localhost', trace: 'retain-on-failure', screenshot: 'only-on-failure', video: 'retain-on-failure' diff --git a/src/TaxBaik.Web.Client/Components/Admin/InquiryTable.razor b/src/TaxBaik.Web.Client/Components/Admin/InquiryTable.razor index 1b3fc6c..24a0c6e 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/InquiryTable.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/InquiryTable.razor @@ -9,7 +9,7 @@ + Class="admin-table-id-column" /> @@ -32,7 +32,7 @@ + CellClass="admin-table-message-column" /> - + 검색 조건에 맞는 공지사항이 없습니다. } diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/Blog/BlogList.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/Blog/BlogList.razor index 1968a78..58c8e9a 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/Blog/BlogList.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/Blog/BlogList.razor @@ -7,7 +7,7 @@ 블로그 관리 - + @(showArchived ? "전체 글 보기" : "내린 글 보기") @@ -18,13 +18,15 @@ 새 포스트 작성 - + -
- -
+ + + + + diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/ClientLogs.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/ClientLogs.razor index f6bdebb..264eb72 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/ClientLogs.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/ClientLogs.razor @@ -54,7 +54,7 @@ - + 새로고침 @if (_loading) { diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientDetail.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientDetail.razor index 973e75c..51390ff 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientDetail.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientDetail.razor @@ -1,8 +1,6 @@ @page "/admin/clients/{ClientId:int}" @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) @attribute [Authorize] -@using TaxBaik.Web.Services -@using TaxBaik.Web.Services.AdminClients @using TaxBaik.WasmClient.Components.Admin.Shared @inject IClientBrowserClient ClientClient @inject IConsultingActivityBrowserClient ConsultingClient @@ -73,7 +71,7 @@ { 메모 - @client.Memo + @client.Memo } @@ -136,14 +134,14 @@ @foreach (var c in consultations) { - +
@c.ConsultationDate.ToString("yyyy-MM-dd") @if (!string.IsNullOrEmpty(c.ServiceType)) { · @c.ServiceType } - @c.Summary + @c.Summary @if (!string.IsNullOrEmpty(c.Result)) { @c.Result diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientEdit.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientEdit.razor index e51cffa..b884153 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientEdit.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientEdit.razor @@ -3,7 +3,6 @@ @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) @attribute [Authorize] @using TaxBaik.Application.DTOs -@using TaxBaik.Web.Services @using TaxBaik.Domain.Entities @using TaxBaik.WasmClient.Components.Admin.Shared @inject IClientBrowserClient ClientClient diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientList.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientList.razor index 47f8236..e8992b6 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientList.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/Clients/ClientList.razor @@ -1,7 +1,6 @@ @page "/admin/clients" @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) @attribute [Authorize] -@using TaxBaik.Web.Services @using TaxBaik.Domain.Entities @inject IClientBrowserClient ClientClient @inject NavigationManager Navigation @@ -12,11 +11,18 @@ - - 고객 등록 - + + + 새로고침 + + + 고객 등록 + + diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/CommonCodes.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/CommonCodes.razor index d7b70e5..0e1b5da 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/CommonCodes.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/CommonCodes.razor @@ -1,6 +1,5 @@ @page "/admin/common-codes" @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) -@using TaxBaik.Web.Services.AdminClients @using TaxBaik.Domain.Entities @attribute [Authorize] @inject ICommonCodeBrowserClient CommonCodeClient diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/Companies/CompanyList.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/Companies/CompanyList.razor index 6a27b3b..9edbffc 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/Companies/CompanyList.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/Companies/CompanyList.razor @@ -18,7 +18,7 @@ - + diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/ConsultingActivities.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/ConsultingActivities.razor index a37e041..a0c5bb4 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/ConsultingActivities.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/ConsultingActivities.razor @@ -1,6 +1,5 @@ @page "/admin/consulting-activities" @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) -@using TaxBaik.Web.Services.AdminClients @using TaxBaik.WasmClient.Components.Admin.Shared @inject IConsultingActivityBrowserClient ActivityClient @inject IClientBrowserClient ClientClient @@ -21,29 +20,19 @@ } else { - - - - - - - - - - - - @foreach (var item in activities) - { - - - - - - - - } - -
ID고객활동 유형활동일시설명
@item.Id@clientMap.GetValueOrDefault(item.ClientId)@item.ActivityType@item.ActivityDate.ToString("g")@item.Description
+ + + + + + @clientMap.GetValueOrDefault(context.Item.ClientId) + + + + + + + } diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/Contracts.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/Contracts.razor index c2ea8b6..ae045a0 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/Contracts.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/Contracts.razor @@ -1,6 +1,5 @@ @page "/admin/contracts" @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) -@using TaxBaik.Web.Services.AdminClients @using TaxBaik.WasmClient.Components.Admin.Shared @inject IContractBrowserClient ContractClient @inject IClientBrowserClient ClientClient @@ -40,50 +39,47 @@ else } else { - - - - - - - - - - - - - - - @foreach (var item in contracts) - { - var isActive = !item.EndDate.HasValue || item.EndDate.Value >= DateTime.Today; - - - - - - - - - - - } - -
ID고객계약번호서비스 유형월 수수료계약기간상태작업
@item.Id@clientMap.GetValueOrDefault(item.ClientId)@item.ContractNumber@item.ServiceType@(item.MonthlyFee?.ToString("C") ?? string.Empty) - @item.StartDate.ToString("yyyy-MM-dd") - @if (item.EndDate.HasValue) - { - ~@item.EndDate.Value.ToString("yyyy-MM-dd") - } - @(isActive ? "활성" : "만료") - -
+ + + + + + @clientMap.GetValueOrDefault(context.Item.ClientId) + + + + + + + + @context.Item.StartDate.ToString("yyyy-MM-dd") + @if (context.Item.EndDate.HasValue) + { + ~@context.Item.EndDate.Value.ToString("yyyy-MM-dd") + } + + + + + @(context.Item.EndDate.HasValue && context.Item.EndDate.Value < DateTime.Today ? "만료" : "활성") + + + + + + 선택 + 삭제 + + + + + } - +
@(isEditMode ? "계약 상세 정보" : "새 계약 추가") @if (isEditMode) @@ -96,8 +92,7 @@ else
- @@ -129,7 +128,7 @@ else }
- + } diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxFilings/FilingTable.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxFilings/FilingTable.razor index 1060bb5..2ed9e2d 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxFilings/FilingTable.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxFilings/FilingTable.razor @@ -1,4 +1,3 @@ -@using TaxBaik.Web.Services @using TaxBaik.Domain.Entities @using TaxBaik.WasmClient.Components.Admin.Shared @inject ITaxFilingBrowserClient FilingClient diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxFilings/TaxFilingList.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxFilings/TaxFilingList.razor index 6b60dd8..af6c137 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxFilings/TaxFilingList.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxFilings/TaxFilingList.razor @@ -1,7 +1,6 @@ @page "/admin/tax-filings" @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) @attribute [Authorize] -@using TaxBaik.Web.Services @using TaxBaik.Domain.Entities @using TaxBaik.WasmClient.Components.Admin.Shared @inject ITaxFilingBrowserClient FilingClient diff --git a/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxProfiles.razor b/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxProfiles.razor index 436910e..24c0e03 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxProfiles.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Pages/TaxProfiles.razor @@ -1,7 +1,6 @@ @page "/admin/tax-profiles" @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) @using System.ComponentModel.DataAnnotations -@using TaxBaik.Web.Services.AdminClients @using TaxBaik.WasmClient.Components.Admin.Shared @inject ITaxProfileBrowserClient TaxProfileClient @inject IClientBrowserClient ClientClient @@ -32,39 +31,33 @@ else } else { - - - - - - - - - - - - - @foreach (var item in profiles) - { - - - - - - - - - } - -
ID고객사업 유형위험도다음 신고작업
@item.Id@clientMap.GetValueOrDefault(item.ClientId)@item.BusinessType@item.TaxRiskLevel@(item.NextFilingDueDate?.ToString("yyyy-MM-dd") ?? string.Empty) - -
+ + + + + + @clientMap.GetValueOrDefault(context.Item.ClientId) + + + + + + + + + 선택 + 삭제 + + + + + } - +
@(isEditMode ? "세무 프로필 수정" : "새 세무 프로필 추가") @if (isEditMode) @@ -94,7 +87,7 @@ else 저장
-
+
} diff --git a/src/TaxBaik.Web.Client/Components/Admin/Services/AdminDashboardClient.cs b/src/TaxBaik.Web.Client/Components/Admin/Services/AdminDashboardClient.cs index 9fab58c..e6c4326 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Services/AdminDashboardClient.cs +++ b/src/TaxBaik.Web.Client/Components/Admin/Services/AdminDashboardClient.cs @@ -50,8 +50,14 @@ public class AdminDashboardClient : IAdminDashboardClient } catch (HttpRequestException ex) { + if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + _logger.LogDebug("Dashboard summary request returned 401; using empty summary"); + return new AdminDashboardSummary(0, 0, 0, 0, []); + } + _logger.LogError(ex, "Failed to fetch dashboard summary"); - throw; + return new AdminDashboardSummary(0, 0, 0, 0, []); } } @@ -66,8 +72,11 @@ public class AdminDashboardClient : IAdminDashboardClient } catch (HttpRequestException ex) { + if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + return []; + _logger.LogError(ex, "Failed to fetch upcoming filings"); - throw; + return []; } } @@ -82,8 +91,11 @@ public class AdminDashboardClient : IAdminDashboardClient } catch (HttpRequestException ex) { + if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + return []; + _logger.LogError(ex, "Failed to fetch recent inquiries"); - throw; + return []; } } @@ -101,8 +113,11 @@ public class AdminDashboardClient : IAdminDashboardClient } catch (HttpRequestException ex) { + if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + return new MonthlyStatsDto(); + _logger.LogError(ex, "Failed to fetch monthly stats"); - throw; + return new MonthlyStatsDto(); } } } diff --git a/src/TaxBaik.Web.Client/Components/Admin/Services/AdminSessionBootstrap.cs b/src/TaxBaik.Web.Client/Components/Admin/Services/AdminSessionBootstrap.cs new file mode 100644 index 0000000..3d3d3a4 --- /dev/null +++ b/src/TaxBaik.Web.Client/Components/Admin/Services/AdminSessionBootstrap.cs @@ -0,0 +1,86 @@ +using Microsoft.Extensions.Logging; + +namespace TaxBaik.Web.Client.Components.Admin.Services; + +public sealed class AdminSessionBootstrap : IAdminSessionBootstrap +{ + private readonly ITokenStore _tokenStore; + private readonly ILocalStorageService _localStorage; + private readonly ILogger _logger; + private readonly SemaphoreSlim _gate = new(1, 1); + private bool _initialized; + + public AdminSessionBootstrap( + ITokenStore tokenStore, + ILocalStorageService localStorage, + ILogger logger) + { + _tokenStore = tokenStore; + _localStorage = localStorage; + _logger = logger; + } + + public async Task EnsureInitializedAsync() + { + if (_initialized) + return; + + await _gate.WaitAsync(); + try + { + if (_initialized) + return; + + await LoadTokensAsync(); + _initialized = true; + } + finally + { + _gate.Release(); + } + } + + private async Task LoadTokensAsync() + { + try + { + var accessToken = await _localStorage.GetItemAsStringAsync("accessToken"); + var refreshToken = await _localStorage.GetItemAsStringAsync("refreshToken"); + var expiryRaw = await _localStorage.GetItemAsStringAsync("tokenExpiry"); + + if (!string.IsNullOrWhiteSpace(accessToken)) + _tokenStore.AccessToken = accessToken; + + if (!string.IsNullOrWhiteSpace(refreshToken)) + _tokenStore.RefreshToken = refreshToken; + + if (TryNormalizeExpiryTicks(expiryRaw, out var expiryTicks)) + _tokenStore.TokenExpiryTicks = expiryTicks; + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Admin session bootstrap failed"); + } + } + + private static bool TryNormalizeExpiryTicks(string? rawValue, out long ticks) + { + ticks = 0; + if (!long.TryParse(rawValue, out var parsed)) + return false; + + if (parsed > 10_000_000_000_000L && parsed < 100_000_000_000_000_000L) + { + ticks = parsed; + return true; + } + + if (parsed > 1_000_000_000_000L && parsed < 100_000_000_000_000L) + { + ticks = DateTimeOffset.FromUnixTimeMilliseconds(parsed).UtcDateTime.Ticks; + return true; + } + + return false; + } +} diff --git a/src/TaxBaik.Web.Client/Components/Admin/Services/ApiClient.cs b/src/TaxBaik.Web.Client/Components/Admin/Services/ApiClient.cs index 7d3e046..081d5f2 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Services/ApiClient.cs +++ b/src/TaxBaik.Web.Client/Components/Admin/Services/ApiClient.cs @@ -106,6 +106,6 @@ public class ApiClient : IApiClient { var relative = $"api/{endpoint.TrimStart('/')}"; var appRoot = new Uri(_navigationManager.BaseUri); - return new Uri(appRoot, $"/{relative}"); // 상대 경로 사용 (Nginx가 /taxbaik 프리픽스 처리) + return new Uri(appRoot, $"/{relative}"); } } diff --git a/src/TaxBaik.Web.Client/Components/Admin/Services/CustomAuthenticationStateProvider.cs b/src/TaxBaik.Web.Client/Components/Admin/Services/CustomAuthenticationStateProvider.cs index 80c9265..a958d4e 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Services/CustomAuthenticationStateProvider.cs +++ b/src/TaxBaik.Web.Client/Components/Admin/Services/CustomAuthenticationStateProvider.cs @@ -9,15 +9,21 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider { private readonly ITokenStore _tokenStore; private readonly IApiClient _apiClient; + private readonly IAdminSessionBootstrap _sessionBootstrap; + private readonly ILocalStorageService _localStorage; private readonly ILogger _logger; public CustomAuthenticationStateProvider( ITokenStore tokenStore, IApiClient apiClient, + IAdminSessionBootstrap sessionBootstrap, + ILocalStorageService localStorage, ILogger logger) { _tokenStore = tokenStore; _apiClient = apiClient; + _sessionBootstrap = sessionBootstrap; + _localStorage = localStorage; _logger = logger; } @@ -25,6 +31,8 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider { try { + await _sessionBootstrap.EnsureInitializedAsync(); + var accessToken = _tokenStore.AccessToken; if (string.IsNullOrEmpty(_tokenStore.AccessToken)) @@ -165,6 +173,7 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider { // TokenStore 초기화 _tokenStore.Clear(); + await _localStorage.ClearAuthAsync(); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } diff --git a/src/TaxBaik.Web.Client/Components/Admin/Services/IAdminSessionBootstrap.cs b/src/TaxBaik.Web.Client/Components/Admin/Services/IAdminSessionBootstrap.cs new file mode 100644 index 0000000..424e6b9 --- /dev/null +++ b/src/TaxBaik.Web.Client/Components/Admin/Services/IAdminSessionBootstrap.cs @@ -0,0 +1,6 @@ +namespace TaxBaik.Web.Client.Components.Admin.Services; + +public interface IAdminSessionBootstrap +{ + Task EnsureInitializedAsync(); +} diff --git a/src/TaxBaik.Web.Client/Components/Admin/Services/TokenRefreshHandler.cs b/src/TaxBaik.Web.Client/Components/Admin/Services/TokenRefreshHandler.cs index 2200c89..89c1064 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Services/TokenRefreshHandler.cs +++ b/src/TaxBaik.Web.Client/Components/Admin/Services/TokenRefreshHandler.cs @@ -71,7 +71,7 @@ public class TokenRefreshHandler : DelegatingHandler var scheme = originalRequest.RequestUri?.Scheme ?? "http"; using var httpClient = new HttpClient(); - var refreshUri = new Uri($"{scheme}://{authority}/api/auth/refresh"); // 상대 경로 사용 (Nginx가 /taxbaik 프리픽스 처리) + var refreshUri = new Uri($"{scheme}://{authority}/api/auth/refresh"); var json = JsonSerializer.Serialize(new { refreshToken }); var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); diff --git a/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminEditorPanel.razor b/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminEditorPanel.razor index 7d194f6..2e638d0 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminEditorPanel.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminEditorPanel.razor @@ -1,7 +1,7 @@ -
+ @ChildContent -
+
@code { diff --git a/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminEmptyState.razor b/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminEmptyState.razor index 4e4ea11..c672b8f 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminEmptyState.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminEmptyState.razor @@ -1,5 +1,5 @@
- + @Message
diff --git a/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminLoginForm.razor b/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminLoginForm.razor index 0206a82..9fefb5d 100644 --- a/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminLoginForm.razor +++ b/src/TaxBaik.Web.Client/Components/Admin/Shared/AdminLoginForm.razor @@ -1,8 +1,11 @@ @inject IJSRuntime JS -