+
+ {t("ui.dev.dashboard.title", "Dashboard")}
+
+
+ {t(
+ "msg.dev.dashboard.description",
+ "연동 앱 구성과 인증 운영 지표를 한 곳에서 확인합니다.",
+ )}
-
- {t("ui.dev.dashboard.next.subtitle", "Ship the RP controls")}
-
-
- {nextSteps.map((item, idx) => (
-
+
+
+
+ }
+ label={t("ui.dev.dashboard.summary.total_clients", "총 RP 수")}
+ value={formatMetric(stats?.total_clients ?? clients.length)}
+ />
+ }
+ label={t("ui.dev.dashboard.summary.active_clients", "활성 RP 수")}
+ value={formatMetric(distribution.activeClients)}
+ />
+ }
+ label={t("ui.dev.dashboard.summary.active_sessions", "활성 세션 수")}
+ value={formatMetric(stats?.active_sessions)}
+ />
+ }
+ label={t(
+ "ui.dev.dashboard.summary.auth_failures_24h",
+ "24시간 인증 실패 수",
+ )}
+ value={formatMetric(stats?.auth_failures_24h)}
+ />
+
+
+
+
+
+
+
+
+ {t(
+ "ui.dev.dashboard.chart.title",
+ "애플리케이션별 로그인요청/기타 요청 현황",
+ )}
+
+
+ {t(
+ "msg.dev.dashboard.chart.filter_description",
+ "전체 또는 선택한 애플리케이션만 기준으로 그래프를 확인합니다.",
+ )}
+
+
+
+
+ {[
+ ["day", t("ui.dev.dashboard.chart.period_day", "일")],
+ ["week", t("ui.dev.dashboard.chart.period_week", "주")],
+ ["month", t("ui.dev.dashboard.chart.period_month", "월")],
+ ].map(([value, label]) => (
+
+ {label}
+
))}
+
+
+
+ {clientFilterOptions.map((client) => (
+
+ ))}
+
+
+ {usageQuery.isError ? (
+
+ {usageErrorText}
+
+ ) : isAllClientsSelected ? (
+
+ ) : (
+
+ )}
-
-
-
-
- {t("ui.dev.dashboard.ops.title", "Ops board")}
-
-
- {t("ui.dev.dashboard.ops.subtitle", "현재 관측")}
+
+
+
+
+
+ {t(
+ "ui.dev.dashboard.distribution.title",
+ "애플리케이션 구성 요약",
+ )}
-
-
- {t("ui.dev.dashboard.ops.tag.consent", "Consent grants")}
-
-
- {t("ui.dev.dashboard.ops.tag.rp_status", "RP status")}
-
-
-
-
-
-
-
- {t("ui.dev.dashboard.ops.card.rp_requests", "RP 요청 추이")}
+
+ {t(
+ "msg.dev.dashboard.distribution.description",
+ "애플리케이션 유형과 headless login 사용 현황을 빠르게 확인합니다.",
+ )}
+
+
+
+
+ {t("ui.dev.dashboard.distribution.private", "Server side App")}
+
+
+ {distribution.privateClients.toLocaleString()}
+
-
- {t("ui.common.status.pending", "준비 중")}
-
-
-
-
-
- {t(
- "ui.dev.dashboard.ops.card.consent_revoked",
- "Consent 회수 건수",
- )}
+
+
+ {t("ui.dev.dashboard.distribution.pkce", "PKCE")}
+
+
+ {distribution.pkceClients.toLocaleString()}
+
-
- {t("ui.common.status.pending", "준비 중")}
-
-
-
-
-
- {t("ui.dev.dashboard.ops.card.hydra_status", "Hydra 상태")}
+
+
+ {t(
+ "ui.dev.dashboard.distribution.headless",
+ "Headless Login",
+ )}
+
+
+ {distribution.headlessClients.toLocaleString()}
+
-
- {t("ui.common.status.ok", "정상")}
-
-
-
+
+
+
+
+
+
+ {t("ui.dev.dashboard.recent.title", "내 애플리케이션")}
+
+
+
+ {t(
+ "msg.dev.dashboard.recent.empty",
+ "현재 계정이 접근할 수 있는 RP를 확인합니다.",
+ )}
+
+
+ {visibleClients.length === 0 ? (
+
+ {t(
+ "msg.dev.dashboard.recent.none",
+ "표시할 연동 앱이 없습니다.",
+ )}
+
+ ) : (
+ visibleClients.map((client) => (
+
+
+
+ {client.name || t("ui.dev.clients.untitled", "Untitled")}
+
+
+ {client.id}
+
+
+
+
+ {client.metadata?.headless_login_enabled === true
+ ? t(
+ "ui.dev.clients.type.private_headless",
+ "Server side App (Headless Login)",
+ )
+ : client.type === "private"
+ ? t(
+ "ui.dev.clients.type.private",
+ "Server side App",
+ )
+ : t("ui.dev.clients.type.pkce", "PKCE")}
+
+
+ {client.status === "active"
+ ? t("ui.dev.clients.status.active", "활성")
+ : client.status === "inactive"
+ ? t("ui.dev.clients.status.inactive", "비활성")
+ : client.status || "-"}
+
+
+
+ ))
+ )}
+
+
+
);
}
diff --git a/devfront/src/lib/devApi.ts b/devfront/src/lib/devApi.ts
index f1422f59..502f8f82 100644
--- a/devfront/src/lib/devApi.ts
+++ b/devfront/src/lib/devApi.ts
@@ -53,6 +53,27 @@ export type DevStats = {
auth_failures_24h: number;
};
+export type RPUsageDailyMetric = {
+ date: string;
+ tenantId: string;
+ tenantType: string;
+ tenantName?: string;
+ clientId: string;
+ clientName: string;
+ loginRequests: number;
+ otherRequests: number;
+ uniqueSubjects: number;
+};
+
+export type RPUsagePeriod = "day" | "week" | "month";
+
+export type RPUsageDailyResponse = {
+ items: RPUsageDailyMetric[];
+ days: number;
+ period: RPUsagePeriod;
+ tenantId?: string;
+};
+
export type DevAuditLog = {
event_id: string;
timestamp: string;
@@ -214,6 +235,22 @@ export async function fetchDevStats() {
return data;
}
+export async function fetchDevRPUsageDaily({
+ days = 14,
+ period = "day",
+}: {
+ days?: number;
+ period?: RPUsagePeriod;
+} = {}) {
+ const { data } = await apiClient.get
(
+ "/dev/rp-usage/daily",
+ {
+ params: { days, period },
+ },
+ );
+ return data;
+}
+
export async function fetchTenants(
limit = 1000,
offset = 0,
diff --git a/devfront/src/locales/en.toml b/devfront/src/locales/en.toml
index f81818bc..33cbe458 100644
--- a/devfront/src/locales/en.toml
+++ b/devfront/src/locales/en.toml
@@ -500,6 +500,7 @@ openid = "Openid"
profile = "Profile"
[msg.dev.dashboard]
+description = "View connected application composition and authentication operations metrics in one place."
[msg.dev.dashboard.hero]
body = "Body"
@@ -507,6 +508,29 @@ title_emphasis = "Title Emphasis"
title_prefix = "Title Prefix"
title_suffix = "Title Suffix"
+[msg.dev.dashboard.distribution]
+description = "Quickly review application types and headless login usage."
+
+[msg.dev.dashboard.chart]
+empty = "No RP usage aggregates to display."
+filter_description = "View the chart for all applications or only the ones you select."
+forbidden = "Your current account does not have permission to view RP usage statistics."
+server_error = "A server error occurred while loading RP usage statistics."
+service_unavailable = "The RP usage aggregation service is not ready yet."
+unavailable = "RP usage statistics API is unavailable. The chart will appear here once aggregate data is ready."
+unavailable_with_reason = "RP usage statistics API is unavailable. {{reason}}"
+
+[msg.dev.dashboard.quick_links]
+audit = "Review RP configuration changes and operational history."
+clients = "Browse registered RPs and manage their status and type."
+description = "Jump directly to key operational screens."
+developer_request = "Review developer access requests or submit a new one."
+new_client = "Configure redirect URIs, grant types, and authentication methods."
+
+[msg.dev.dashboard.recent]
+empty = "Review the relying parties this account can access."
+none = "No connected applications to display."
+
[msg.dev.dashboard.notice]
consent_audit = "Consent Audit"
dev_scope = "Dev Scope"
@@ -1252,6 +1276,7 @@ audit_logs = "Audit Logs"
clients = "Connected Application"
developer_request = "Developer Access Request"
logout = "Logout"
+overview = "Overview"
[ui.dev.audit]
load_more = "Load more"
@@ -1629,12 +1654,32 @@ private_headless = "Server side App (Headless Login)"
[ui.dev.dashboard]
ready_badge = "devfront ready"
+title = "Dashboard"
[ui.dev.dashboard.badge]
consent_guard = "Consent guard ready"
+oidc = "OIDC operations"
policy_toggle = "Policy toggle enabled"
+registry = "RP registry"
rp_synced = "RP registry synced"
+[ui.dev.dashboard.distribution]
+headless = "Headless Login"
+pkce = "PKCE"
+private = "Server side App"
+title = "Application Distribution"
+
+[ui.dev.dashboard.chart]
+aria = "RP request overview"
+filter_all = "All"
+login_requests = "Login requests"
+other_requests = "Other requests"
+period_day = "Day"
+period_month = "Month"
+period_week = "Week"
+series = "Login {{login}} / Other {{other}} / Users {{subjects}}"
+title = "Login and other requests by application"
+
[ui.dev.dashboard.next]
subtitle = "Ship the RP controls"
title = "Next actions"
@@ -1652,11 +1697,25 @@ rp_requests = "Rp Requests"
consent = "Consent grants"
rp_status = "RP status"
+[ui.dev.dashboard.quick_links]
+create_button = "Create RP"
+new_client = "New RP"
+title = "Quick links"
+
+[ui.dev.dashboard.recent]
+title = "My Applications"
+
[ui.dev.dashboard.stack]
notes = "Setup notes"
subtitle = "Devfront baseline"
title = "Stack readiness"
+[ui.dev.dashboard.summary]
+active_clients = "Active RPs"
+active_sessions = "Active sessions"
+auth_failures_24h = "24h auth failures"
+total_clients = "Total RPs"
+
[ui.dev.header]
plane = "Dev Plane"
subtitle = "Manage your applications"
diff --git a/devfront/src/locales/ko.toml b/devfront/src/locales/ko.toml
index c00aca4d..ba69c665 100644
--- a/devfront/src/locales/ko.toml
+++ b/devfront/src/locales/ko.toml
@@ -500,6 +500,7 @@ openid = "OIDC 인증 필수 스코프"
profile = "기본 프로필 정보 접근"
[msg.dev.dashboard]
+description = "연동 앱 구성과 인증 운영 지표를 한 곳에서 확인합니다."
[msg.dev.dashboard.hero]
body = "Hydra Admin API와 동기화된 RP 목록, 상태 토글, Consent 회수까지 devfront에서 처리하도록 준비합니다."
@@ -507,6 +508,29 @@ title_emphasis = " 하나의 화면"
title_prefix = "RP 등록 현황과 Consent 상태를"
title_suffix = "에서 관리합니다."
+[msg.dev.dashboard.distribution]
+description = "애플리케이션 유형과 headless login 사용 현황을 빠르게 확인합니다."
+
+[msg.dev.dashboard.chart]
+empty = "표시할 RP 이용 집계가 없습니다."
+filter_description = "전체 또는 선택한 애플리케이션만 기준으로 그래프를 확인합니다."
+forbidden = "현재 계정에는 RP 이용 통계를 볼 권한이 없습니다."
+server_error = "RP 이용 통계 조회 중 서버 오류가 발생했습니다."
+service_unavailable = "RP 이용 통계 집계 서비스가 아직 준비되지 않았습니다."
+unavailable = "RP 이용 통계 API 응답을 확인할 수 없습니다. 집계 데이터가 준비되면 이 영역에 그래프가 표시됩니다."
+unavailable_with_reason = "RP 이용 통계 API 응답을 확인할 수 없습니다. {{reason}}"
+
+[msg.dev.dashboard.quick_links]
+audit = "RP 설정 변경과 운영 이력을 확인합니다."
+clients = "등록된 RP를 조회하고 상태와 유형을 관리합니다."
+description = "주요 운영 화면으로 바로 이동합니다."
+developer_request = "개발자 권한 신청 내역을 확인하거나 새 요청을 등록합니다."
+new_client = "redirect URI, grant type, 인증 방식을 설정합니다."
+
+[msg.dev.dashboard.recent]
+empty = "현재 계정이 접근할 수 있는 RP를 확인합니다."
+none = "표시할 연동 앱이 없습니다."
+
[msg.dev.dashboard.notice]
consent_audit = "Consent 회수는 감사 로그와 연계"
dev_scope = "RP 정책은 dev scope에서만 적용"
@@ -1252,6 +1276,7 @@ audit_logs = "감사 로그"
clients = "연동 앱"
developer_request = "개발자 권한 신청"
logout = "로그아웃"
+overview = "개요"
[ui.dev.audit]
load_more = "더 보기"
@@ -1628,12 +1653,32 @@ private_headless = "Server side App (Headless Login)"
[ui.dev.dashboard]
ready_badge = "devfront ready"
+title = "대시보드"
[ui.dev.dashboard.badge]
consent_guard = "Consent guard ready"
+oidc = "OIDC 운영"
policy_toggle = "Policy toggle enabled"
+registry = "RP registry"
rp_synced = "RP registry synced"
+[ui.dev.dashboard.distribution]
+headless = "Headless Login"
+pkce = "PKCE"
+private = "Server side App"
+title = "애플리케이션 구성 요약"
+
+[ui.dev.dashboard.chart]
+aria = "RP 요청 현황"
+filter_all = "전체"
+login_requests = "로그인 요청"
+other_requests = "기타 요청"
+period_day = "일"
+period_month = "월"
+period_week = "주"
+series = "로그인 {{login}} / 기타 {{other}} / 사용자 {{subjects}}"
+title = "애플리케이션별 로그인요청/기타 요청 현황"
+
[ui.dev.dashboard.next]
subtitle = "Ship the RP controls"
title = "Next actions"
@@ -1651,11 +1696,25 @@ rp_requests = "RP 요청 추이"
consent = "Consent grants"
rp_status = "RP status"
+[ui.dev.dashboard.quick_links]
+create_button = "새 RP 만들기"
+new_client = "새 RP 생성"
+title = "빠른 이동"
+
+[ui.dev.dashboard.recent]
+title = "내 애플리케이션"
+
[ui.dev.dashboard.stack]
notes = "Setup notes"
subtitle = "Devfront baseline"
title = "Stack readiness"
+[ui.dev.dashboard.summary]
+active_clients = "활성 RP 수"
+active_sessions = "활성 세션 수"
+auth_failures_24h = "24시간 인증 실패 수"
+total_clients = "총 RP 수"
+
[ui.dev.header]
plane = "Dev Plane"
subtitle = "Manage your applications"
diff --git a/devfront/src/locales/template.toml b/devfront/src/locales/template.toml
index 7cacce98..01e5506e 100644
--- a/devfront/src/locales/template.toml
+++ b/devfront/src/locales/template.toml
@@ -538,6 +538,7 @@ openid = ""
profile = ""
[msg.dev.dashboard]
+description = ""
[msg.dev.dashboard.hero]
body = ""
@@ -545,6 +546,29 @@ title_emphasis = ""
title_prefix = ""
title_suffix = ""
+[msg.dev.dashboard.distribution]
+description = ""
+
+[msg.dev.dashboard.chart]
+empty = ""
+filter_description = ""
+forbidden = ""
+server_error = ""
+service_unavailable = ""
+unavailable = ""
+unavailable_with_reason = ""
+
+[msg.dev.dashboard.quick_links]
+audit = ""
+clients = ""
+description = ""
+developer_request = ""
+new_client = ""
+
+[msg.dev.dashboard.recent]
+empty = ""
+none = ""
+
[msg.dev.dashboard.notice]
consent_audit = ""
dev_scope = ""
@@ -1303,8 +1327,9 @@ scope_badge = ""
[ui.dev.nav]
audit_logs = ""
clients = ""
-logout = ""
developer_request = ""
+logout = ""
+overview = ""
[ui.dev.welcome]
btn_request = ""
@@ -1685,12 +1710,32 @@ private_headless = ""
[ui.dev.dashboard]
ready_badge = ""
+title = ""
[ui.dev.dashboard.badge]
consent_guard = ""
+oidc = ""
policy_toggle = ""
+registry = ""
rp_synced = ""
+[ui.dev.dashboard.distribution]
+headless = ""
+pkce = ""
+private = ""
+title = ""
+
+[ui.dev.dashboard.chart]
+aria = ""
+filter_all = ""
+login_requests = ""
+other_requests = ""
+period_day = ""
+period_month = ""
+period_week = ""
+series = ""
+title = ""
+
[ui.dev.dashboard.next]
subtitle = ""
title = ""
@@ -1708,11 +1753,25 @@ rp_requests = ""
consent = ""
rp_status = ""
+[ui.dev.dashboard.quick_links]
+create_button = ""
+new_client = ""
+title = ""
+
+[ui.dev.dashboard.recent]
+title = ""
+
[ui.dev.dashboard.stack]
notes = ""
subtitle = ""
title = ""
+[ui.dev.dashboard.summary]
+active_clients = ""
+active_sessions = ""
+auth_failures_24h = ""
+total_clients = ""
+
[ui.dev.header]
plane = ""
subtitle = ""