();
for (const user of users) {
@@ -1230,7 +1293,7 @@ export function buildUsersMap(
name: primarySlug,
})
) {
- addTenantSlugCandidate(slugs, tenantIndexes, primarySlug);
+ addTenantSlugCandidate(slugs, membershipTenantIndexes, primarySlug);
}
if (
legacyCompanySlug &&
@@ -1241,31 +1304,40 @@ export function buildUsersMap(
name: legacyCompanySlug,
})
) {
- addTenantSlugCandidate(slugs, tenantIndexes, legacyCompanySlug);
+ addTenantSlugCandidate(slugs, membershipTenantIndexes, legacyCompanySlug);
}
if (user.tenant?.slug && !isSystemGlobalTenant(user.tenant)) {
- addTenantSlugCandidate(slugs, tenantIndexes, user.tenant.slug);
+ addTenantSlugCandidate(slugs, membershipTenantIndexes, user.tenant.slug);
}
for (const joinedTenant of user.joinedTenants || []) {
if (joinedTenant.slug && !isSystemGlobalTenant(joinedTenant)) {
- addTenantSlugCandidate(slugs, tenantIndexes, joinedTenant.slug);
+ addTenantSlugCandidate(
+ slugs,
+ membershipTenantIndexes,
+ joinedTenant.slug,
+ );
}
}
for (const appointment of getUserOrgAppointmentRefs(user)) {
if (appointment.tenantSlug) {
- addTenantSlugCandidate(slugs, tenantIndexes, appointment.tenantSlug);
+ addTenantSlugCandidate(
+ slugs,
+ membershipTenantIndexes,
+ appointment.tenantSlug,
+ );
continue;
}
const tenantById = appointment.tenantId
- ? tenantIndexes.byId.get(appointment.tenantId)
+ ? membershipTenantIndexes.byId.get(appointment.tenantId)
: undefined;
if (tenantById) {
- addTenantSlugCandidate(slugs, tenantIndexes, tenantById.slug);
+ addTenantSlugCandidate(slugs, membershipTenantIndexes, tenantById.slug);
}
}
- for (const slug of getLeafMembershipSlugs(slugs, tenantIndexes)) {
+ for (const slug of getLeafMembershipSlugs(slugs, membershipTenantIndexes)) {
+ if (!visibleTenantIndexes.bySlug.has(slug)) continue;
const list = map.get(slug) || [];
if (!list.some((existing) => existing.id === user.id)) list.push(user);
map.set(slug, list);
@@ -1340,11 +1412,15 @@ export function TenantOrgChartPage() {
const rootNodes = buildTenantFullTree(
filterSystemGlobalTenants(publicQuery.data.tenants, "public"),
).subTree;
+ const membershipRootNodes = buildTenantFullTree(
+ filterOrgChartMembershipTenants(publicQuery.data.tenants),
+ ).subTree;
return {
rootNodes,
usersMap: buildUsersMap(publicQuery.data.users, rootNodes, {
activeOnly: false,
+ membershipRootNodes,
}),
sharedWith: publicQuery.data.sharedWith,
};
@@ -1361,11 +1437,15 @@ export function TenantOrgChartPage() {
const rootNodes = buildTenantFullTree(
filterSystemGlobalTenants(tenantsQuery.data.items, visibilityMode),
).subTree;
+ const membershipRootNodes = buildTenantFullTree(
+ filterOrgChartMembershipTenants(tenantsQuery.data.items),
+ ).subTree;
return {
rootNodes,
usersMap: buildUsersMap(usersQuery.data.items, rootNodes, {
activeOnly: true,
+ membershipRootNodes,
}),
sharedWith: "",
};
@@ -1827,11 +1907,15 @@ function SvgOrgNode({
visualNode: VisualNode;
}) {
const { node, x, y, width, height, members, collapsed } = visualNode;
+ const tenantIdentity = { id: node.id, slug: node.companyCode ?? "" };
const headerFill = getOrgNodeHeaderFill(
node.companyColorDepth ?? node.level,
node.companyColorKey,
);
- const memberColumnCount = getMemberColumnCount(members.length);
+ const memberColumnCount = getMemberColumnCount(
+ members.length,
+ getMemberColumnWidth(members, tenantIdentity),
+ );
const showMemberRows = semanticZoomMode === "detail";
const showNodeName =
semanticZoomMode === "detail" ||
@@ -1899,23 +1983,31 @@ function SvgOrgNode({
gridTemplateColumns: `repeat(${memberColumnCount}, minmax(0, 1fr))`,
}}
>
- {members.map((member) => (
-
+ {members.map((member) => {
+ const profile = getUserOrgProfile(member, tenantIdentity);
+ const isHighlighted = profile.isHighlighted === true;
+
+ return (
-
- {getOrgChartUserDisplayName(member, {
- id: node.id,
- slug: node.companyCode ?? "",
- })}
+ className="flex h-5 items-center overflow-hidden rounded border border-[#e5e7eb] bg-white text-xs font-extrabold text-[#334155]"
+ data-highlighted={isHighlighted ? "true" : "false"}
+ data-testid={`orgchart-member-${member.id}`}
+ key={member.id}
+ >
+
+
+ {getOrgChartUserDisplayName(member, tenantIdentity)}
+
-
- ))}
+ );
+ })}
) : (
diff --git a/orgfront/src/features/orgchart/userDisplay.test.ts b/orgfront/src/features/orgchart/userDisplay.test.ts
index ac90501f..db3dd704 100644
--- a/orgfront/src/features/orgchart/userDisplay.test.ts
+++ b/orgfront/src/features/orgchart/userDisplay.test.ts
@@ -1,6 +1,9 @@
import { describe, expect, it } from "vitest";
import type { UserSummary } from "../../lib/adminApi";
-import { getOrgChartUserDisplayName } from "./userDisplay";
+import {
+ getOrgChartUserDisplayName,
+ getUserOrgProfile,
+} from "./userDisplay";
function user(overrides: Partial): UserSummary {
return {
@@ -16,15 +19,16 @@ function user(overrides: Partial): UserSummary {
}
describe("getOrgChartUserDisplayName", () => {
- it("renders name with grade and optional position", () => {
+ it("renders name with grade and without job details", () => {
expect(
getOrgChartUserDisplayName(
user({
grade: "수석",
position: "팀장",
+ jobTitle: "구조",
}),
),
- ).toBe("홍길동 수석(팀장)");
+ ).toBe("홍길동 수석");
});
it("uses tenant appointment grade before the user grade", () => {
@@ -44,6 +48,123 @@ describe("getOrgChartUserDisplayName", () => {
}),
{ id: "tenant-1", slug: "hanmac" },
),
- ).toBe("홍길동 수석(센터장)");
+ ).toBe("홍길동 수석");
+ });
+
+ it("uses short grade aliases in the display name", () => {
+ expect(
+ getOrgChartUserDisplayName(
+ user({
+ grade: "책임연구원",
+ jobTitle: "구조",
+ }),
+ ),
+ ).toBe("홍길동 책임");
+ });
+
+ it("does not add leader text to the display name", () => {
+ expect(
+ getOrgChartUserDisplayName(
+ user({
+ grade: "책임",
+ metadata: {
+ additionalAppointments: [
+ {
+ tenantSlug: "hanmac",
+ isOwner: true,
+ grade: "수석",
+ position: "센터장",
+ },
+ ],
+ },
+ }),
+ { id: "tenant-1", slug: "hanmac" },
+ ),
+ ).toBe("홍길동 수석");
+ });
+
+ it("does not leak an owner appointment flag into another tenant display", () => {
+ expect(
+ getOrgChartUserDisplayName(
+ user({
+ grade: "책임",
+ position: "팀원",
+ metadata: {
+ additionalAppointments: [
+ {
+ tenantSlug: "hanmac",
+ isOwner: true,
+ grade: "수석",
+ position: "센터장",
+ },
+ ],
+ },
+ }),
+ { id: "tenant-2", slug: "baron" },
+ ),
+ ).toBe("홍길동 책임");
+ });
+});
+
+describe("getUserOrgProfile", () => {
+ it("marks owner, manager, and admin flags as highlighted profiles", () => {
+ expect(
+ getUserOrgProfile(
+ user({
+ metadata: {
+ additionalAppointments: [
+ {
+ tenantSlug: "owner",
+ isOwner: true,
+ },
+ {
+ tenantSlug: "manager",
+ isManager: true,
+ },
+ {
+ tenantSlug: "admin",
+ isAdmin: true,
+ },
+ ],
+ },
+ }),
+ { id: "tenant-1", slug: "owner" },
+ ).isHighlighted,
+ ).toBe(true);
+ expect(
+ getUserOrgProfile(
+ user({
+ metadata: {
+ additionalAppointments: [{ tenantSlug: "leader", isLeader: true }],
+ },
+ }),
+ { id: "tenant-2", slug: "leader" },
+ ).isHighlighted,
+ ).toBe(false);
+ expect(
+ getUserOrgProfile(
+ user({
+ metadata: {
+ additionalAppointments: [
+ { tenantSlug: "manager", isManager: true },
+ ],
+ },
+ }),
+ { id: "tenant-2", slug: "manager" },
+ ).isHighlighted,
+ ).toBe(true);
+ expect(
+ getUserOrgProfile(
+ user({
+ metadata: {
+ additionalAppointments: [{ tenantSlug: "admin", isAdmin: true }],
+ },
+ }),
+ { id: "tenant-3", slug: "admin" },
+ ).isHighlighted,
+ ).toBe(true);
+ expect(getUserOrgProfile(user({ grade: "책임" })).isHighlighted).toBe(
+ false,
+ );
});
});
diff --git a/orgfront/src/features/orgchart/userDisplay.ts b/orgfront/src/features/orgchart/userDisplay.ts
index 7b04dc42..c7e6d085 100644
--- a/orgfront/src/features/orgchart/userDisplay.ts
+++ b/orgfront/src/features/orgchart/userDisplay.ts
@@ -1,8 +1,12 @@
import type { TenantSummary, UserSummary } from "../../lib/adminApi";
+import { getOrgRankDisplayName } from "./rankPriority";
type UserAppointment = {
tenantId?: string;
tenantSlug?: string;
+ isAdmin?: boolean;
+ isManager?: boolean;
+ isOwner?: boolean;
grade?: string;
jobTitle?: string;
position?: string;
@@ -26,6 +30,9 @@ function getUserAppointments(user: UserSummary): UserAppointment[] {
.map((item) => ({
tenantId: normalizeText(item.tenantId),
tenantSlug: normalizeText(item.tenantSlug),
+ isAdmin: item.isAdmin === true,
+ isManager: item.isManager === true,
+ isOwner: item.isOwner === true,
grade: normalizeText(item.grade),
jobTitle: normalizeText(item.jobTitle),
position: normalizeText(item.position),
@@ -47,6 +54,10 @@ export function getUserOrgProfile(user: UserSummary, tenant?: TenantIdentity) {
return {
grade: appointment?.grade || normalizeText(user.grade),
+ isHighlighted:
+ appointment?.isAdmin === true ||
+ appointment?.isManager === true ||
+ appointment?.isOwner === true,
jobTitle: appointment?.jobTitle || normalizeText(user.jobTitle),
position: appointment?.position || normalizeText(user.position),
};
@@ -56,12 +67,11 @@ export function getOrgChartUserDisplayName(
user: UserSummary,
tenant?: TenantIdentity,
) {
- const { grade, jobTitle, position } = getUserOrgProfile(user, tenant);
+ const { grade } = getUserOrgProfile(user, tenant);
const baseName = user.name.trim();
- const detail = position || jobTitle;
+ const displayGrade = getOrgRankDisplayName(grade);
- if (grade && detail) return `${baseName} ${grade}(${detail})`;
- if (grade) return `${baseName} ${grade}`;
- if (detail) return `${baseName}(${detail})`;
- return baseName;
+ let displayName = baseName;
+ if (displayGrade) displayName = `${baseName} ${displayGrade}`;
+ return displayName;
}
diff --git a/orgfront/src/sdk/org-context-chart/index.ts b/orgfront/src/sdk/org-context-chart/index.ts
index 32e606f2..941f2bd4 100644
--- a/orgfront/src/sdk/org-context-chart/index.ts
+++ b/orgfront/src/sdk/org-context-chart/index.ts
@@ -976,7 +976,12 @@ function selectionKey(selection: OrgPickerSelection) {
}
function formatMember(member: OrgContextMember) {
- return [member.name, member.position, member.jobTitle]
+ return [
+ member.name,
+ member.position,
+ member.jobTitle,
+ member.isLeader || member.isOwner ? "조직장" : "",
+ ]
.filter(Boolean)
.join(" · ");
}
diff --git a/orgfront/src/sdk/org-context-chart/orgContextChart.test.ts b/orgfront/src/sdk/org-context-chart/orgContextChart.test.ts
index aa8775c9..22d8b419 100644
--- a/orgfront/src/sdk/org-context-chart/orgContextChart.test.ts
+++ b/orgfront/src/sdk/org-context-chart/orgContextChart.test.ts
@@ -146,6 +146,7 @@ describe("org-context chart SDK", () => {
expect(
chartContainer.querySelectorAll("[data-baron-org-node]"),
).toHaveLength(3);
+ expect(chartContainer.textContent).toContain("Leader · 팀장 · 조직장");
const platformCheckbox = pickerContainer.querySelector(
'input[value="tenant:team-platform"]',
);
diff --git a/orgfront/tests/orgchart-pan-zoom.spec.ts b/orgfront/tests/orgchart-pan-zoom.spec.ts
index 18087b2b..8c7fd136 100644
--- a/orgfront/tests/orgchart-pan-zoom.spec.ts
+++ b/orgfront/tests/orgchart-pan-zoom.spec.ts
@@ -252,7 +252,7 @@ test("org chart renders dense member nodes with calculated member columns", asyn
await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();
const rootNode = page.locator('[data-testid="orgchart-node-root"]');
- await expect(rootNode).toHaveAttribute("width", /[4-9]\d{2,}/);
+ await expect(rootNode).toHaveAttribute("width", /3\d{2}/);
await expect(rootNode.locator('[data-member-columns="2"]')).toBeVisible();
await expect(rootNode.getByText("Dense User 10")).toBeVisible();
});
diff --git a/orgfront/tests/orgchart-vector-render.spec.ts b/orgfront/tests/orgchart-vector-render.spec.ts
index f4661f7a..6d774156 100644
--- a/orgfront/tests/orgchart-vector-render.spec.ts
+++ b/orgfront/tests/orgchart-vector-render.spec.ts
@@ -230,7 +230,7 @@ test("org chart balances large member groups with automatic member columns", asy
).toBeVisible();
});
-test("org chart displays user names with grade and optional position", async ({
+test("org chart displays user names with short grade aliases and no job details", async ({
page,
}) => {
await page.route("**/api/v1/public/orgchart**", async (route) => {
@@ -247,7 +247,7 @@ test("org chart displays user names with grade and optional position", async ({
{
...user("u-eng", "Engineering User", "engineering"),
jobTitle: "Platform Engineer",
- grade: "책임",
+ grade: "책임연구원",
position: "팀장",
},
],
@@ -258,7 +258,77 @@ test("org chart displays user names with grade and optional position", async ({
await page.goto("/chart?token=display-name");
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
- await expect(svg.getByText("Engineering User 책임(팀장)")).toBeVisible();
+ await expect(svg.getByText("Engineering User 책임")).toBeVisible();
+ await expect(svg.getByText(/팀장|Platform Engineer/)).toHaveCount(0);
+});
+
+test("org chart only highlights flagged member cards", async ({ page }) => {
+ await page.route("**/api/v1/public/orgchart**", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ body: JSON.stringify({
+ sharedWith: "Playwright",
+ tenants: [
+ tenant("group", "HMAC Group", "hmac"),
+ tenant("engineering", "Engineering", "engineering", "group"),
+ ],
+ users: [
+ user("u-normal", "Normal User", "engineering"),
+ {
+ ...user("u-owner", "Owner User", "engineering"),
+ metadata: {
+ additionalAppointments: [
+ {
+ tenantSlug: "engineering",
+ isOwner: true,
+ },
+ ],
+ },
+ },
+ {
+ ...user("u-admin", "Admin User", "engineering"),
+ metadata: {
+ additionalAppointments: [
+ {
+ tenantSlug: "engineering",
+ isAdmin: true,
+ },
+ ],
+ },
+ },
+ {
+ ...user("u-manager", "Manager User", "engineering"),
+ metadata: {
+ additionalAppointments: [
+ {
+ tenantSlug: "engineering",
+ isManager: true,
+ },
+ ],
+ },
+ },
+ ],
+ }),
+ });
+ });
+
+ await page.goto("/chart?token=highlighted-members");
+
+ const engineeringNode = page.locator(
+ '[data-testid="orgchart-node-engineering"]',
+ );
+ await expect(
+ engineeringNode.locator('[data-testid="orgchart-member-u-normal"]'),
+ ).toHaveAttribute("data-highlighted", "false");
+ await expect(
+ engineeringNode.locator('[data-testid="orgchart-member-u-owner"]'),
+ ).toHaveAttribute("data-highlighted", "true");
+ await expect(
+ engineeringNode.locator('[data-testid="orgchart-member-u-admin"]'),
+ ).toHaveAttribute("data-highlighted", "true");
+ await expect(
+ engineeringNode.locator('[data-testid="orgchart-member-u-manager"]'),
+ ).toHaveAttribute("data-highlighted", "true");
});
test("org chart places multi-tenant users only on leaf memberships without duplicate rendering", async ({
@@ -406,6 +476,14 @@ test("org chart places GPDTDC representative users on visible leaf appointments"
"ORGANIZATION",
{ visibility: "internal" },
),
+ tenant(
+ "internal-leaf",
+ "내부 구성 하위 조직",
+ "internal-leaf",
+ "gpdtdc",
+ "USER_GROUP",
+ { visibility: "internal" },
+ ),
],
users: [
{
@@ -427,6 +505,19 @@ test("org chart places GPDTDC representative users on visible leaf appointments"
],
},
},
+ {
+ ...user("u-hidden-only", "Hidden Only User", "gpdtdc"),
+ tenantSlug: "gpdtdc",
+ companyCode: undefined,
+ metadata: {
+ additionalAppointments: [
+ {
+ tenantSlug: "internal-leaf",
+ isPrimary: true,
+ },
+ ],
+ },
+ },
],
}),
});
@@ -438,6 +529,8 @@ test("org chart places GPDTDC representative users on visible leaf appointments"
const svg = page.locator('[data-testid="orgchart-vector-svg"]');
await expect(svg).toBeVisible();
await expect(svg.getByText("내부 구성 조직")).toHaveCount(0);
+ await expect(svg.getByText("내부 구성 하위 조직")).toHaveCount(0);
+ await expect(svg.getByText(/Hidden Only User/)).toHaveCount(0);
await expect(
page
.locator('[data-testid="orgchart-node-gpdtdc"]')
@@ -446,7 +539,7 @@ test("org chart places GPDTDC representative users on visible leaf appointments"
await expect(
page
.locator('[data-testid="orgchart-node-tdc-leaf"]')
- .getByText("GPDTDC Leaf User 책임(팀장)"),
+ .getByText("GPDTDC Leaf User 책임"),
).toBeVisible();
});
diff --git a/test/gateway_userfront_residue_policy_test.sh b/test/gateway_userfront_residue_policy_test.sh
new file mode 100644
index 00000000..7e4ed547
--- /dev/null
+++ b/test/gateway_userfront_residue_policy_test.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env sh
+set -eu
+
+assert_contains() {
+ file="$1"
+ pattern="$2"
+ if ! grep -Fq "$pattern" "$file"; then
+ echo "missing pattern in $file: $pattern" >&2
+ exit 1
+ fi
+}
+
+assert_not_contains() {
+ file="$1"
+ pattern="$2"
+ if grep -Fq "$pattern" "$file"; then
+ echo "forbidden pattern in $file: $pattern" >&2
+ exit 1
+ fi
+}
+
+deploy_gateway="deploy/templates/gateway/nginx.conf"
+
+if [ ! -f "$deploy_gateway" ]; then
+ echo "missing expected file: $deploy_gateway" >&2
+ exit 1
+fi
+
+assert_contains "$deploy_gateway" "root /usr/share/nginx/html;"
+assert_contains "$deploy_gateway" 'try_files $uri $uri/ /index.html;'
+assert_not_contains "$deploy_gateway" "baron_userfront"
+assert_not_contains "$deploy_gateway" "userfront_upstream"
+assert_not_contains "$deploy_gateway" "proxy_pass http://baron_userfront"
+
+echo "gateway userfront residue policy checks passed"
diff --git a/test/staging_frontend_deploy_policy_test.sh b/test/staging_frontend_deploy_policy_test.sh
index 588eaf3b..95900b2b 100644
--- a/test/staging_frontend_deploy_policy_test.sh
+++ b/test/staging_frontend_deploy_policy_test.sh
@@ -22,6 +22,7 @@ assert_not_contains() {
staging_pull=".gitea/workflows/staging_code_pull.yml"
pull_compose="docker/staging_pull_compose.template.yaml"
deploy_compose="deploy/templates/docker-compose.yaml"
+deploy_gateway="deploy/templates/gateway/nginx.conf"
userfront_dockerfile="userfront/Dockerfile"
devfront_vite="devfront/vite.config.ts"
orgfront_vite="orgfront/vite.config.ts"
@@ -34,6 +35,7 @@ for file in \
"$staging_pull" \
"$pull_compose" \
"$deploy_compose" \
+ "$deploy_gateway" \
"$userfront_dockerfile" \
"$adminfront_vite" \
"$devfront_vite" \
@@ -82,6 +84,11 @@ assert_contains "$pull_compose" 'APP_ENV=${APP_ENV:-stage}'
assert_contains "$deploy_compose" "sh ./scripts/runtime-mode.sh"
assert_not_contains "$deploy_compose" "command: npm run dev"
+assert_contains "$deploy_gateway" "root /usr/share/nginx/html;"
+assert_contains "$deploy_gateway" 'try_files $uri $uri/ /index.html;'
+assert_not_contains "$deploy_gateway" "baron_userfront"
+assert_not_contains "$deploy_gateway" "userfront_upstream"
+assert_not_contains "$deploy_gateway" "proxy_pass http://baron_userfront"
for app in adminfront devfront orgfront; do
assert_contains ".gitea/workflows/build_RC.yml" "Build and push $app RC image"
diff --git a/userfront-e2e/tests/auth-routing.spec.ts b/userfront-e2e/tests/auth-routing.spec.ts
index 362cd25a..4e81021c 100644
--- a/userfront-e2e/tests/auth-routing.spec.ts
+++ b/userfront-e2e/tests/auth-routing.spec.ts
@@ -169,6 +169,25 @@ async function makeWindowCloseNavigateToRoot(page: Page): Promise {
});
}
+async function enableFlutterAccessibility(page: Page): Promise {
+ await page.waitForTimeout(300);
+ const button = page.getByRole('button', { name: 'Enable accessibility' });
+ if (await button.count()) {
+ await button.first().evaluate((node) => {
+ (node as HTMLElement).click();
+ });
+ await page.waitForTimeout(200);
+ return;
+ }
+ const placeholder = page.locator('flt-semantics-placeholder').first();
+ if (await placeholder.count()) {
+ await placeholder.evaluate((node) => {
+ (node as HTMLElement).click();
+ });
+ await page.waitForTimeout(800);
+ }
+}
+
test.describe('UserFront WASM auth routing', () => {
test('비로그인 /ko 진입 시 /ko/signin 으로 리다이렉트된다', async ({ page }) => {
await mockUserfrontApis(page, { sessionStatus: 401 });
@@ -274,7 +293,7 @@ test.describe('UserFront WASM auth routing', () => {
expect(clientFailures).toEqual([]);
});
- test('verifyOnly 승인 완료 버튼은 SMS 링크에서 user/me 조회나 루트 이동을 만들지 않는다', async ({
+ test('verifyOnly 승인 완료 버튼은 SMS 링크에서 로그인 창으로 이동하고 user/me 조회를 만들지 않는다', async ({
page,
}) => {
let userMeCalls = 0;
@@ -298,20 +317,48 @@ test.describe('UserFront WASM auth routing', () => {
await expect(page).toHaveURL(/\/ko\/verify-complete$/);
expect(userMeCalls).toBe(0);
- const viewport = page.viewportSize();
- if (!viewport) throw new Error('viewport is required');
- await page.locator('flt-glass-pane').click({
- position: {
- x: Math.floor(viewport.width / 2),
- y: Math.floor(viewport.height * 0.66),
- },
- force: true,
- });
- await page.waitForTimeout(300);
+ await enableFlutterAccessibility(page);
+ await page.getByRole('button', { name: '로그인 창으로 이동하기' }).click();
expect(userMeCalls).toBe(0);
+ await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/);
+ expect(
+ clientFailures.filter(
+ (failure) => !failure.includes('401 (Unauthorized)'),
+ ),
+ ).toEqual([]);
+ });
+
+ test('verifyOnly 원격 승인 완료는 로그인 창 이동 모달 CTA를 표시한다', async ({
+ page,
+ }) => {
+ let verifyCalls = 0;
+ const clientFailures = collectClientFailures(page);
+
+ await mockUserfrontApis(page, {
+ sessionStatus: 401,
+ captureVerify: () => {
+ verifyCalls += 1;
+ },
+ });
+ await makeWindowCloseNavigateToRoot(page);
+
+ await page.goto('/ko/l/AB123456');
+
+ await expect.poll(() => verifyCalls, { timeout: 10_000 }).toBe(1);
await expect(page).toHaveURL(/\/ko\/verify-complete$/);
- await expect(page).not.toHaveURL(/\/signin(?:\?.*)?$/);
+ await enableFlutterAccessibility(page);
+
+ await expect(
+ page.getByText('요청하신 로그인이 완료되었습니다'),
+ ).toBeVisible();
+ await expect(page.getByRole('button', { name: '창 닫기' })).toHaveCount(0);
+ await expect(
+ page.getByRole('button', { name: '로그인 창으로 이동하기' }),
+ ).toBeVisible();
+
+ await page.getByRole('button', { name: '로그인 창으로 이동하기' }).click();
+ await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/);
expect(clientFailures).toEqual([]);
});
@@ -428,18 +475,13 @@ test.describe('UserFront WASM auth routing', () => {
await expect(popup).toHaveURL(/\/ko\/verify-complete$/);
expect(userMeCalls).toBe(0);
- const viewport = popup.viewportSize();
- if (!viewport) throw new Error('viewport is required');
if (!popup.isClosed()) {
+ await enableFlutterAccessibility(popup);
const closePromise = popup.waitForEvent('close').catch(() => undefined);
try {
- await popup.locator('flt-glass-pane').click({
- position: {
- x: Math.floor(viewport.width / 2),
- y: Math.floor(viewport.height * 0.66),
- },
- force: true,
- });
+ await popup
+ .getByRole('button', { name: '로그인 창으로 이동하기' })
+ .click();
} catch (error) {
if (!popup.isClosed()) {
throw error;
@@ -453,7 +495,7 @@ test.describe('UserFront WASM auth routing', () => {
expect(clientFailures).toEqual([]);
});
- test('verifyOnly 승인 완료 버튼은 이메일 magic link에서도 user/me 조회나 루트 이동을 만들지 않는다', async ({
+ test('verifyOnly 승인 완료 버튼은 이메일 magic link에서도 로그인 창으로 이동하고 user/me 조회를 만들지 않는다', async ({
page,
}) => {
let userMeCalls = 0;
@@ -485,24 +527,15 @@ test.describe('UserFront WASM auth routing', () => {
verifyOnly: true,
});
- const viewport = page.viewportSize();
- if (!viewport) throw new Error('viewport is required');
- await page.locator('flt-glass-pane').click({
- position: {
- x: Math.floor(viewport.width / 2),
- y: Math.floor(viewport.height * 0.66),
- },
- force: true,
- });
- await page.waitForTimeout(300);
+ await enableFlutterAccessibility(page);
+ await page.getByRole('button', { name: '로그인 창으로 이동하기' }).click();
expect(userMeCalls).toBe(0);
- await expect(page).toHaveURL(/\/ko\/verify-complete$/);
- await expect(page).not.toHaveURL(/\/signin(?:\?.*)?$/);
+ await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/);
expect(clientFailures).toEqual([]);
});
- test('verifyOnly 승인 완료 버튼은 이메일 code link에서도 user/me 조회나 루트 이동을 만들지 않는다', async ({
+ test('verifyOnly 승인 완료 버튼은 이메일 code link에서도 로그인 창으로 이동하고 user/me 조회를 만들지 않는다', async ({
page,
}) => {
let userMeCalls = 0;
@@ -538,20 +571,11 @@ test.describe('UserFront WASM auth routing', () => {
verifyOnly: true,
});
- const viewport = page.viewportSize();
- if (!viewport) throw new Error('viewport is required');
- await page.locator('flt-glass-pane').click({
- position: {
- x: Math.floor(viewport.width / 2),
- y: Math.floor(viewport.height * 0.66),
- },
- force: true,
- });
- await page.waitForTimeout(300);
+ await enableFlutterAccessibility(page);
+ await page.getByRole('button', { name: '로그인 창으로 이동하기' }).click();
expect(userMeCalls).toBe(0);
- await expect(page).toHaveURL(/\/ko\/verify-complete$/);
- await expect(page).not.toHaveURL(/\/signin(?:\?.*)?$/);
+ await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/);
expect(clientFailures).toEqual([]);
});
});
diff --git a/userfront/assets/translations/en.toml b/userfront/assets/translations/en.toml
index 1fefa5d0..87fbade4 100644
--- a/userfront/assets/translations/en.toml
+++ b/userfront/assets/translations/en.toml
@@ -231,7 +231,7 @@ body = "We could not find an account for that information.\\\\\\\\\\\\\\\\nPleas
[msg.userfront.login.verification]
approved = "Approved. Complete sign-in in the original window."
approved_local = "Approved. This device is already signed in, and the remote window will be signed in shortly."
-approved_remote = "Approved. Please return to the original browser or PC screen."
+approved_remote = "Your requested sign-in is complete."
pending_remote = "Checking the sign-in approval request. Please wait."
success = "Sign-in approval completed."
@@ -582,6 +582,7 @@ title = "Account not found"
[ui.userfront.login.verification]
action_label = "Done"
+action_label_remote = "Go to sign-in window"
action_label_close = "Close Window"
page_title = "Sign-in approval"
title = "Approval complete"
diff --git a/userfront/assets/translations/ko.toml b/userfront/assets/translations/ko.toml
index c250d088..69e3b7a6 100644
--- a/userfront/assets/translations/ko.toml
+++ b/userfront/assets/translations/ko.toml
@@ -455,7 +455,7 @@ body = "가입되지 않은 정보입니다.\\\\n회원가입 후 이용해 주
[msg.userfront.login.verification]
approved = "승인되었습니다. 로그인은 요청하신 창에서 완료됩니다."
approved_local = "승인 되었습니다. 이 기기는 로그인되어 있는 상태입니다. 원격 창도 로그인이 될 예정입니다"
-approved_remote = "승인되었습니다. 요청하신 브라우저 또는 PC 화면으로 돌아가 주세요."
+approved_remote = "요청하신 로그인이 완료되었습니다"
pending_remote = "승인 요청을 확인하고 있습니다. 잠시만 기다려 주세요."
success = "로그인 승인에 성공했습니다."
@@ -804,6 +804,7 @@ title = "미등록 회원"
[ui.userfront.login.verification]
action_label = "확인"
+action_label_remote = "로그인 창으로 이동하기"
page_title = "로그인 승인"
title = "승인 완료"
action_label_close = "창 닫기"
diff --git a/userfront/assets/translations/template.toml b/userfront/assets/translations/template.toml
index 228be315..33da33cf 100644
--- a/userfront/assets/translations/template.toml
+++ b/userfront/assets/translations/template.toml
@@ -776,6 +776,7 @@ title = ""
[ui.userfront.login.verification]
action_label = ""
+action_label_remote = ""
action_label_close = ""
page_title = ""
title = ""
diff --git a/userfront/lib/features/auth/presentation/login_screen.dart b/userfront/lib/features/auth/presentation/login_screen.dart
index e3da8a9a..0a0d256a 100644
--- a/userfront/lib/features/auth/presentation/login_screen.dart
+++ b/userfront/lib/features/auth/presentation/login_screen.dart
@@ -146,8 +146,10 @@ class _LoginScreenState extends ConsumerState
_markVerificationApproved(
tr('msg.userfront.login.verification.approved_remote'),
title: tr('ui.userfront.login.verification.title_remote'),
- actionLabel: tr('ui.userfront.login.verification.action_label_close'),
- onAction: _closeVerificationWindowIfPossible,
+ actionLabel: tr(
+ 'ui.userfront.login.verification.action_label_remote',
+ ),
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
return;
}
@@ -859,63 +861,91 @@ class _LoginScreenState extends ConsumerState
}
}
+ void _moveToSigninOrCloseVerificationWindow() {
+ if (webWindow.hasOpener()) {
+ webWindow.close();
+ return;
+ }
+ context.go(buildLocalizedSigninPath(Uri.base));
+ }
+
Widget _buildVerificationResultView() {
+ final colorScheme = Theme.of(context).colorScheme;
return Center(
- child: Padding(
+ child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Icon(
- Icons.check_circle_outline,
- color: Colors.green,
- size: 72,
- ),
- const SizedBox(height: 16),
- Text(
- _verificationTitle,
- style: const TextStyle(
- fontSize: 22,
- fontWeight: FontWeight.bold,
- color: Colors.green,
+ child: ConstrainedBox(
+ constraints: const BoxConstraints(maxWidth: 420),
+ child: Material(
+ color: colorScheme.surface,
+ elevation: 12,
+ shadowColor: Colors.black.withValues(alpha: 0.18),
+ borderRadius: BorderRadius.circular(24),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(24, 28, 24, 24),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ Icons.check_circle_outline,
+ color: colorScheme.primary,
+ size: 72,
+ ),
+ const SizedBox(height: 16),
+ Text(
+ _verificationTitle,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 22,
+ fontWeight: FontWeight.bold,
+ color: colorScheme.onSurface,
+ ),
+ ),
+ const SizedBox(height: 12),
+ Text(
+ _verificationMessage.isEmpty
+ ? tr('msg.userfront.login.verification.success')
+ : _verificationMessage,
+ textAlign: TextAlign.center,
+ style: TextStyle(color: colorScheme.onSurfaceVariant),
+ ),
+ const SizedBox(height: 24),
+ SizedBox(
+ width: double.infinity,
+ child: FilledButton(
+ onPressed: () {
+ if (_onVerificationAction != null) {
+ _runVerificationExitAction();
+ return;
+ }
+ if (_verificationOnly) {
+ _closeVerificationWindowIfPossible();
+ return;
+ }
+ final hasLocalSession =
+ (AuthTokenStore.getToken()?.isNotEmpty ?? false) ||
+ AuthTokenStore.usesCookie();
+ final target = hasLocalSession
+ ? buildLocalizedHomePath(Uri.base)
+ : buildLocalizedSigninPath(Uri.base);
+ if (mounted) {
+ setState(() {
+ _verificationOnly = false;
+ _verificationApproved = false;
+ });
+ }
+ context.go(target);
+ },
+ child: Text(
+ _verificationActionLabel,
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ),
+ ],
),
),
- const SizedBox(height: 12),
- Text(
- _verificationMessage.isEmpty
- ? tr('msg.userfront.login.verification.success')
- : _verificationMessage,
- textAlign: TextAlign.center,
- style: const TextStyle(color: Colors.black54),
- ),
- const SizedBox(height: 24),
- FilledButton(
- onPressed: () {
- if (_onVerificationAction != null) {
- _runVerificationExitAction();
- return;
- }
- if (_verificationOnly) {
- _closeVerificationWindowIfPossible();
- return;
- }
- final hasLocalSession =
- (AuthTokenStore.getToken()?.isNotEmpty ?? false) ||
- AuthTokenStore.usesCookie();
- final target = hasLocalSession
- ? buildLocalizedHomePath(Uri.base)
- : buildLocalizedSigninPath(Uri.base);
- if (mounted) {
- setState(() {
- _verificationOnly = false;
- _verificationApproved = false;
- });
- }
- context.go(target);
- },
- child: Text(_verificationActionLabel),
- ),
- ],
+ ),
),
),
);
@@ -956,12 +986,6 @@ class _LoginScreenState extends ConsumerState
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(_verificationPageTitle),
- leading: _verificationApproved && _onVerificationAction != null
- ? IconButton(
- icon: const Icon(Icons.close),
- onPressed: _runVerificationExitAction,
- )
- : null,
actions: const [ThemeToggleButton(compact: true)],
),
body: _verificationApproved
@@ -999,9 +1023,9 @@ class _LoginScreenState extends ConsumerState
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
actionLabel: tr(
- 'ui.userfront.login.verification.action_label_close',
+ 'ui.userfront.login.verification.action_label_remote',
),
- onAction: _closeVerificationWindowIfPossible,
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
}
return;
@@ -1035,9 +1059,9 @@ class _LoginScreenState extends ConsumerState
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
actionLabel: tr(
- 'ui.userfront.login.verification.action_label_close',
+ 'ui.userfront.login.verification.action_label_remote',
),
- onAction: _closeVerificationWindowIfPossible,
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
}
return;
@@ -1092,9 +1116,9 @@ class _LoginScreenState extends ConsumerState
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
actionLabel: tr(
- 'ui.userfront.login.verification.action_label_close',
+ 'ui.userfront.login.verification.action_label_remote',
),
- onAction: _closeVerificationWindowIfPossible,
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
}
return;
@@ -1113,9 +1137,9 @@ class _LoginScreenState extends ConsumerState
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
actionLabel: tr(
- 'ui.userfront.login.verification.action_label_close',
+ 'ui.userfront.login.verification.action_label_remote',
),
- onAction: _closeVerificationWindowIfPossible,
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
return;
}
@@ -1127,8 +1151,10 @@ class _LoginScreenState extends ConsumerState
_markVerificationApproved(
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
- actionLabel: tr('ui.userfront.login.verification.action_label_close'),
- onAction: _closeVerificationWindowIfPossible,
+ actionLabel: tr(
+ 'ui.userfront.login.verification.action_label_remote',
+ ),
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
}
} catch (e) {
@@ -1146,9 +1172,9 @@ class _LoginScreenState extends ConsumerState
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
actionLabel: tr(
- 'ui.userfront.login.verification.action_label_close',
+ 'ui.userfront.login.verification.action_label_remote',
),
- onAction: _closeVerificationWindowIfPossible,
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
}
return;
@@ -1194,9 +1220,9 @@ class _LoginScreenState extends ConsumerState
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
actionLabel: tr(
- 'ui.userfront.login.verification.action_label_close',
+ 'ui.userfront.login.verification.action_label_remote',
),
- onAction: _closeVerificationWindowIfPossible,
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
}
return;
@@ -1215,9 +1241,9 @@ class _LoginScreenState extends ConsumerState
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
actionLabel: tr(
- 'ui.userfront.login.verification.action_label_close',
+ 'ui.userfront.login.verification.action_label_remote',
),
- onAction: _closeVerificationWindowIfPossible,
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
return;
}
@@ -1229,8 +1255,10 @@ class _LoginScreenState extends ConsumerState
_markVerificationApproved(
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
- actionLabel: tr('ui.userfront.login.verification.action_label_close'),
- onAction: _closeVerificationWindowIfPossible,
+ actionLabel: tr(
+ 'ui.userfront.login.verification.action_label_remote',
+ ),
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
}
} catch (e) {
@@ -1246,9 +1274,9 @@ class _LoginScreenState extends ConsumerState
remoteApprovedMessage,
title: tr('ui.userfront.login.verification.title_remote'),
actionLabel: tr(
- 'ui.userfront.login.verification.action_label_close',
+ 'ui.userfront.login.verification.action_label_remote',
),
- onAction: _closeVerificationWindowIfPossible,
+ onAction: _moveToSigninOrCloseVerificationWindow,
);
}
return;
diff --git a/userfront/lib/i18n_data.dart b/userfront/lib/i18n_data.dart
index 79c1ae5b..761b776e 100644
--- a/userfront/lib/i18n_data.dart
+++ b/userfront/lib/i18n_data.dart
@@ -1,5 +1,13 @@
// locales/*.toml에서 생성됨
const Map koStrings = {
+ "\"msg.admin.tenants.bulk.update_error\"": "temp",
+ "\"msg.admin.tenants.bulk.update_success\"": "temp",
+ "\"msg.admin.tenants.status_error\"": "temp",
+ "\"ui.admin.tenants.bulk.selected_count\"": "temp",
+ "\"ui.admin.tenants.bulk.status_placeholder\"": "temp",
+ "\"ui.admin.tenants.data_mgmt\"": "temp",
+ "\"ui.admin.tenants.toggle_status\"": "temp",
+ "\"ui.admin.users.data_mgmt\"": "temp",
"domain.affiliation.affiliate": "가족사 임직원",
"domain.affiliation.general": "일반 사용자",
"domain.company.baron": "바론",
@@ -10,6 +18,7 @@ const Map koStrings = {
"domain.company.saman": "삼안",
"domain.tenant_type.company": "COMPANY (일반 기업)",
"domain.tenant_type.company_group": "COMPANY_GROUP (그룹사/지주사)",
+ "domain.tenant_type.organization": "ORGANIZATION (정규 조직)",
"domain.tenant_type.personal": "PERSONAL (개인 워크스페이스)",
"domain.tenant_type.user_group": "USER_GROUP (내부 부서/팀)",
"err.backend.authorization_pending": "인증 승인이 아직 완료되지 않았습니다.",
@@ -34,27 +43,9 @@ const Map koStrings = {
"err.userfront.auth_proxy.consent_reject": "동의 거부에 실패했습니다.",
"err.userfront.auth_proxy.linked_app_revoke": "연동 해지에 실패했습니다.",
"err.userfront.auth_proxy.login_failed": "로그인에 실패했습니다.",
- "err.userfront.auth_proxy.login_init": "로그인 초기화에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.login_poll": "로그인 상태 확인에 실패했습니다: {{error}}",
"err.userfront.auth_proxy.oidc_accept": "OIDC 로그인 승인에 실패했습니다.",
"err.userfront.auth_proxy.password_reset_complete": "비밀번호 재설정에 실패했습니다.",
- "err.userfront.auth_proxy.password_policy_fetch": "비밀번호 정책을 불러오지 못했습니다.",
"err.userfront.auth_proxy.password_reset_init": "비밀번호 재설정을 시작하지 못했습니다.",
- "err.userfront.auth_proxy.profile_load": "프로필을 불러오지 못했습니다: {{error}}",
- "err.userfront.auth_proxy.tenant_info_fetch": "테넌트 정보를 불러오지 못했습니다.",
- "err.userfront.auth_proxy.verify_failed": "검증에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.sms_send": "SMS 전송에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.code_verify": "인증 코드 확인에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.qr_init": "QR 로그인을 시작하지 못했습니다: {{error}}",
- "err.userfront.auth_proxy.qr_poll": "QR 상태 확인에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.qr_approve": "QR 승인에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.user_create": "사용자 생성에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.user_list": "사용자 목록 조회에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.user_delete": "사용자 삭제에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.user_status_update": "상태 업데이트에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.user_update": "사용자 수정에 실패했습니다: {{error}}",
- "err.userfront.auth_proxy.linked_apps_load": "연동된 앱 목록을 불러오지 못했습니다.",
- "err.userfront.auth_proxy.phone_code_send": "인증 코드 전송에 실패했습니다: {{error}}",
"err.userfront.profile.load_failed": "프로필을 불러오지 못했습니다: {{error}}",
"err.userfront.profile.password_change_failed": "비밀번호 변경에 실패했습니다: {{error}}",
"err.userfront.profile.send_code_failed": "인증번호 전송 실패: {{error}}",
@@ -74,9 +65,12 @@ const Map koStrings = {
"msg.admin.api_keys.create.success.notice_suffix": "표시됩니다.",
"msg.admin.api_keys.list.delete_confirm":
"API 키 \\\\\\\"{{name}}\\\\\\\"를 삭제할까요?",
+ "msg.admin.api_keys.list.edit_scopes_desc": "API 키에 부여할 권한 범위를 수정합니다.",
"msg.admin.api_keys.list.empty": "등록된 API 키가 없습니다.",
"msg.admin.api_keys.list.fetch_error": "API 키 목록 조회에 실패했습니다.",
"msg.admin.api_keys.list.registry.count": "총 {{count}}개 API 키",
+ "msg.admin.api_keys.list.rotate_confirm": "이 API 키의 Secret을 재발급할까요?",
+ "msg.admin.api_keys.list.rotate_secret_notice": "새 Secret은 지금 한 번만 표시됩니다.",
"msg.admin.api_keys.list.subtitle":
"서버 간 통신(Machine-to-Machine)을 위한 API 키를 발급하고 관리합니다.",
"msg.admin.apikeys.registry.count": "총 {{count}}개의 활성 키가 등록되어 있습니다.",
@@ -86,6 +80,8 @@ const Map koStrings = {
"msg.admin.audit.load_error": "감사 로그를 불러오지 못했습니다: {{error}}",
"msg.admin.audit.loading": "감사 로그를 불러오는 중...",
"msg.admin.audit.registry.count": "로드된 로그 {{count}}건",
+ "msg.admin.audit.registry.description":
+ "최근 감사 로그를 검색 조건에 맞춰 필터링하고, 작업 이력을 빠르게 확인합니다.",
"msg.admin.audit.subtitle":
"Command 요청 기반 ClickHouse 로그를 조회합니다. 사용자/테넌트는 추후 세션 연동 시 자동 채워집니다.",
"msg.admin.common.forbidden": "이 작업을 수행할 권한이 없습니다.",
@@ -100,10 +96,16 @@ const Map koStrings = {
"msg.admin.groups.list.import_error": "가져오기 실패",
"msg.admin.groups.list.import_success": "조직도가 임포트되었습니다.",
"msg.admin.groups.list.loading": "로딩 중...",
+ "msg.admin.groups.list.no_results": "그룹이 없습니다.",
"msg.admin.groups.list.subtitle": "이 테넌트에 정의된 사용자 그룹 목록입니다.",
+ "msg.admin.groups.members.add_modal_desc":
+ "이 테넌트에 속한 사용자 중 추가할 멤버를 검색하여 선택하세요.",
"msg.admin.groups.members.add_success": "구성원이 추가되었습니다.",
+ "msg.admin.groups.members.all_added": "모든 테넌트 멤버가 이미 이 그룹에 속해 있습니다.",
"msg.admin.groups.members.count": "{{count}} 명",
"msg.admin.groups.members.empty": "멤버가 없습니다.",
+ "msg.admin.groups.members.move_modal_desc": "선택한 멤버를 이동할 대상 그룹을 선택하세요.",
+ "msg.admin.groups.members.move_success": "멤버가 이동되었습니다.",
"msg.admin.groups.members.remove_confirm": "제거하시겠습니까?",
"msg.admin.groups.members.remove_success": "구성원이 제외되었습니다.",
"msg.admin.groups.members.title": "[{{name}}] 멤버 관리",
@@ -115,6 +117,38 @@ const Map koStrings = {
"msg.admin.groups.roles.remove_success": "역할이 회수되었습니다.",
"msg.admin.header.subtitle": "Tenant isolation & least privilege by default",
"msg.admin.idp_env_prod": "IDP env: prod",
+ "msg.admin.integrity.check.duplicate_tenant_slugs.description":
+ "삭제되지 않은 tenant의 LOWER(TRIM(slug)) 기준 중복을 검사합니다.",
+ "msg.admin.integrity.check.orphan_tenant_parents.description":
+ "tenants.parent_id가 존재하지 않거나 soft-deleted tenant를 참조하는지 검사합니다.",
+ "msg.admin.integrity.check.orphan_user_login_id_tenants.description":
+ "user_login_ids.tenant_id가 존재하지 않거나 soft-deleted tenant를 참조하는지 검사합니다.",
+ "msg.admin.integrity.check.orphan_user_login_id_users.description":
+ "user_login_ids.user_id가 존재하지 않거나 soft-deleted user를 참조하는지 검사합니다.",
+ "msg.admin.integrity.check.orphan_user_tenant_memberships.description":
+ "users.tenant_id가 존재하지 않거나 soft-deleted tenant를 참조하는지 검사합니다.",
+ "msg.admin.integrity.forbidden.description":
+ "이 화면은 super_admin 권한으로만 접근할 수 있습니다.",
+ "msg.admin.integrity.orphan_login_ids.delete_confirm":
+ "선택한 {{count}}개의 유령 로그인 ID를 삭제하시겠습니까?",
+ "msg.admin.integrity.orphan_login_ids.delete_success":
+ "{{count}}개의 유령 로그인 ID를 삭제했습니다.",
+ "msg.admin.integrity.orphan_login_ids.description":
+ "삭제되었거나 존재하지 않는 사용자/테넌트를 참조하는 로그인 ID를 확인한 뒤 선택 삭제합니다.",
+ "msg.admin.integrity.orphan_login_ids.empty": "삭제할 유령 로그인 ID가 없습니다.",
+ "msg.admin.integrity.orphan_login_ids.load_error":
+ "유령 로그인 ID 대상을 불러오지 못했습니다.",
+ "msg.admin.integrity.read_model.description":
+ "Ory SoT를 덮어쓰지 않고 backend DB read model의 이상 징후만 확인합니다.",
+ "msg.admin.integrity.recheck.error": "검사에 실패했습니다.",
+ "msg.admin.integrity.recheck.running": "정합성 검사를 실행 중입니다.",
+ "msg.admin.integrity.recheck.success": "검사가 완료되었습니다.",
+ "msg.admin.integrity.report.load_error": "정합성 리포트를 불러오지 못했습니다.",
+ "msg.admin.integrity.section.tenant_integrity.description":
+ "테넌트 slug 중복과 부모 관계 이상을 확인합니다.",
+ "msg.admin.integrity.section.user_integrity.description":
+ "사용자와 로그인 ID 참조의 고아 레코드를 확인합니다.",
+ "msg.admin.integrity.subtitle": "정합성 상태를 확인하고 데이터 모델 전반의 검증 결과를 살펴봅니다.",
"msg.admin.logout_confirm": "로그아웃 하시겠습니까?",
"msg.admin.notice.idp_policy":
"IDP 관리 키는 서버 내부 래핑 API로만 사용하며, 감사·레이트리밋을 기본 적용합니다.",
@@ -140,6 +174,7 @@ const Map koStrings = {
"msg.admin.overview.summary.oidc_clients": "등록된 OIDC 클라이언트",
"msg.admin.overview.summary.policy_gate": "정책 가이트 상태",
"msg.admin.overview.summary.total_tenants": "전체 테넌트 수",
+ "msg.admin.overview.summary.total_users": "전체 사용자 수",
"msg.admin.scope_admin": "Scoped to /admin",
"msg.admin.session_ttl": "Session TTL: 15m admin",
"msg.admin.tenant_headers": "Tenant-aware headers",
@@ -158,6 +193,7 @@ const Map koStrings = {
"생성 직후에는 기본 활성 상태로 부여되며, 필요 시 상태를 수정하세요.",
"msg.admin.tenants.create.memo.subtitle":
"Tenant 권한 정책은 추후 Keto 연계로 확장 예정입니다.",
+ "msg.admin.tenants.create.pick_parent_first": "상위 테넌트를 먼저 선택하세요.",
"msg.admin.tenants.create.profile.subtitle":
"필수 정보만 입력해도 생성 가능합니다. Slug는 없으면 자동 생성됩니다.",
"msg.admin.tenants.create.subtitle": "글로벌 운영 기준의 신규 테넌트를 등록합니다.",
@@ -165,16 +201,19 @@ const Map koStrings = {
"msg.admin.tenants.delete_confirm": "테넌트 \\\\\\\"{{name}}\\\\\\\"를 삭제할까요?",
"msg.admin.tenants.delete_success": "테넌트가 삭제되었습니다.",
"msg.admin.tenants.empty": "아직 등록된 테넌트가 없습니다.",
+ "msg.admin.tenants.export_error": "테넌트 내보내기에 실패했습니다.",
"msg.admin.tenants.fetch_error": "테넌트 목록 조회에 실패했습니다.",
- "msg.admin.tenants.import_empty": "가져올 테넌트 행이 없습니다.",
- "msg.admin.tenants.import_error": "테넌트 가져오기에 실패했습니다.",
+ "msg.admin.tenants.import_empty": "임포트 파일에 테넌트 행이 없습니다.",
+ "msg.admin.tenants.import_error": "테넌트 임포트에 실패했습니다: {{error}}",
"msg.admin.tenants.import_preview.description":
- "tenant_id가 없는 행은 기존 테넌트 후보와 비교한 뒤 신규 생성 또는 기존 테넌트 갱신으로 처리합니다.",
- "msg.admin.tenants.import_result":
- "생성 {{created}}, 갱신 {{updated}}, 실패 {{failed}}",
+ "임포트 전에 각 행의 매칭 결과를 검토하고 처리 방식을 선택하세요.",
+ "msg.admin.tenants.import_result": "{{count}}개의 테넌트 행을 처리했습니다.",
"msg.admin.tenants.members.desc": "조직에 소속된 사용자 목록을 확인합니다.",
"msg.admin.tenants.members.empty": "소속된 사용자가 없습니다.",
"msg.admin.tenants.members.limit_notice": "하위 조직이 많아 상위 10개 조직의 멤버만 표시됩니다.",
+ "msg.admin.tenants.members.remove_confirm": "'{{name}}'님을 이 조직에서 제외하시겠습니까?",
+ "msg.admin.tenants.members.remove_error": "조직에서 제외하는 중 오류가 발생했습니다.",
+ "msg.admin.tenants.members.remove_success": "조직에서 제외되었습니다.",
"msg.admin.tenants.missing_id": "테넌트 ID가 없습니다.",
"msg.admin.tenants.not_found": "테넌트를 찾을 수 없습니다.",
"msg.admin.tenants.owners.add_success": "소유자가 추가되었습니다.",
@@ -184,6 +223,11 @@ const Map koStrings = {
"msg.admin.tenants.owners.remove_self": "본인의 권한은 회수할 수 없습니다.",
"msg.admin.tenants.owners.remove_success": "소유자 권한이 회수되었습니다.",
"msg.admin.tenants.owners.subtitle": "이 테넌트의 최상위 권한을 가진 소유자(조직장) 목록입니다.",
+ "msg.admin.tenants.parent.local_picker_description":
+ "테넌트 목록에서 상위 테넌트로 사용할 항목을 선택합니다.",
+ "msg.admin.tenants.parent.local_picker_empty": "선택할 수 있는 테넌트가 없습니다.",
+ "msg.admin.tenants.parent.picker_description":
+ "org-chart에서 테넌트를 선택하면 상위 테넌트에 반영됩니다.",
"msg.admin.tenants.registry.count": "총 {{count}}개 테넌트",
"msg.admin.tenants.remove_sub_confirm":
"테넌트 \\\\\\\"{{name}}\\\\\\\"을(를) 하위 조직에서 제외할까요?",
@@ -193,9 +237,23 @@ const Map koStrings = {
"msg.admin.tenants.schema.subtitle": "이 테넌트의 사용자에게 적용할 커스텀 속성을 정의합니다.",
"msg.admin.tenants.schema.update_error": "스키마 업데이트에 실패했습니다.",
"msg.admin.tenants.schema.update_success": "스키마가 성공적으로 업데이트되었습니다.",
+ "msg.admin.tenants.scope.description": "상위 테넌트를 선택하면 해당 하위 테넌트만 목록에 표시합니다.",
+ "msg.admin.tenants.seed_delete_blocked": "초기 설정 테넌트는 삭제할 수 없습니다.",
"msg.admin.tenants.sub.empty": "하위 테넌트가 없습니다.",
"msg.admin.tenants.sub.subtitle": "현재 테넌트 하위에 생성된 조직입니다.",
"msg.admin.tenants.subtitle": "현재 등록된 테넌트를 확인하고 상태를 관리합니다.",
+ "msg.admin.user_projection.action_error": "사용자 동기화 작업에 실패했습니다.",
+ "msg.admin.user_projection.action_success":
+ "{{count}}명 기준으로 사용자 동기화를 갱신했습니다.",
+ "msg.admin.user_projection.forbidden.description":
+ "이 화면은 super_admin 권한으로만 접근할 수 있습니다.",
+ "msg.admin.user_projection.forbidden_description":
+ "이 화면은 super_admin 권한으로만 접근할 수 있습니다.",
+ "msg.admin.user_projection.load_error": "사용자 동기화 상태를 불러오지 못했습니다.",
+ "msg.admin.user_projection.reset_confirm":
+ "사용자 동기화를 Kratos 기준으로 다시 구축하시겠습니까?",
+ "msg.admin.user_projection.subtitle":
+ "Kratos 사용자 read model을 확인하고 동기화 상태를 갱신합니다.",
"msg.admin.users.bulk.delete_confirm": "선택한 {{count}}명의 사용자를 정말로 삭제하시겠습니까?",
"msg.admin.users.bulk.delete_success": "{{count}}명의 사용자가 삭제되었습니다.",
"msg.admin.users.bulk.description": "CSV 파일을 통해 사용자를 일괄 등록하거나 관리합니다.",
@@ -203,9 +261,12 @@ const Map koStrings = {
"msg.admin.users.bulk.move_error": "사용자 이동 중 오류가 발생했습니다.",
"msg.admin.users.bulk.move_success": "{{count}}명의 사용자가 성공적으로 이동되었습니다.",
"msg.admin.users.bulk.parsed_count": "{{count}}행의 데이터가 파싱되었습니다.",
+ "msg.admin.users.bulk.permission_placeholder": "권한 선택",
"msg.admin.users.bulk.schema_incompatible": "대상 테넌트 스키마에 없는 필드는 유실될 수 있습니다:",
"msg.admin.users.bulk.schema_missing": "대상 테넌트의 필수 필드가 누락되어 있습니다:",
+ "msg.admin.users.bulk.status_placeholder": "상태 선택",
"msg.admin.users.bulk.update_success": "사용자 정보가 일괄 업데이트되었습니다.",
+ "msg.admin.users.confirm_remove_org": "이 조직에서 사용자를 제외하시겠습니까?",
"msg.admin.users.create.account.subtitle": "새로운 사용자를 시스템에 등록합니다.",
"msg.admin.users.create.appointment_required":
"한맥 가족 구성원은 소속 테넌트를 하나 이상 선택해 주세요.",
@@ -260,7 +321,7 @@ const Map koStrings = {
"msg.admin.users.detail.tenants_desc": "각 테넌트별로 정의된 커스텀 스키마 정보를 관리합니다.",
"msg.admin.users.detail.update_error": "사용자 수정에 실패했습니다.",
"msg.admin.users.detail.update_success": "사용자 정보가 수정되었습니다.",
- "msg.admin.users.export_error": "사용자 내보내기에 실패했습니다.",
+ "msg.admin.users.export_error": "사용자 내보내기에 실패했습니다: {{error}}",
"msg.admin.users.list.columns.description": "테이블에 표시할 컬럼을 선택합니다.",
"msg.admin.users.list.columns.no_custom": "이 테넌트에 정의된 커스텀 필드가 없습니다.",
"msg.admin.users.list.delete_confirm":
@@ -270,7 +331,7 @@ const Map koStrings = {
"msg.admin.users.list.registry.count": "총 {{count}}명의 사용자가 등록되어 있습니다.",
"msg.admin.users.list.subtitle": "시스템 사용자를 조회하고 관리합니다. (Local DB)",
"msg.admin.users.self_delete_blocked": "자신의 계정은 삭제할 수 없습니다.",
- "msg.admin.users.status_error": "사용자 상태 변경에 실패했습니다.",
+ "msg.admin.users.status_error": "사용자 상태 변경에 실패했습니다: {{error}}",
"msg.common.copied": "복사되었습니다.",
"msg.common.copied_to_clipboard": "클립보드에 복사되었습니다.",
"msg.common.error": "오류가 발생했습니다.",
@@ -286,6 +347,8 @@ const Map koStrings = {
"msg.dev.audit.load_error": "감사 로그 조회 실패: {{error}}",
"msg.dev.audit.loaded_count": "로드된 로그 {{count}}건",
"msg.dev.audit.loading": "감사 로그를 불러오는 중...",
+ "msg.dev.audit.registry_description":
+ "최근 감사 로그를 검색 조건에 맞춰 필터링하고, 작업 이력을 빠르게 확인합니다.",
"msg.dev.audit.subtitle": "현재 테넌트/앱 범위의 DevFront 작업 이력을 조회합니다.",
"msg.dev.auth.access_denied_description":
"DevFront는 관리자 전용 화면입니다. 권한이 필요하면 관리자에게 요청해 주세요.",
@@ -433,6 +496,24 @@ const Map koStrings = {
"msg.dev.clients.scopes.openid": "OIDC 인증 필수 스코프",
"msg.dev.clients.scopes.profile": "기본 프로필 정보 접근",
"msg.dev.clients.showing": "전체 {{total}}개 중 {{shown}}개를 표시하는 중입니다.",
+ "msg.dev.dashboard.access_denied": "개요는 개발자 권한이 있어야 볼 수 있습니다.",
+ "msg.dev.dashboard.access_denied_detail":
+ "개발자 권한 신청 페이지에서 신청을 등록한 뒤 승인을 받아주세요.",
+ "msg.dev.dashboard.access_pending": "개발자 권한 신청을 검토 중입니다.",
+ "msg.dev.dashboard.access_pending_detail":
+ "super admin이 승인하면 개요와 개발자 기능을 사용할 수 있습니다.",
+ "msg.dev.dashboard.chart.empty": "표시할 RP 이용 집계가 없습니다.",
+ "msg.dev.dashboard.chart.filter_description":
+ "전체 또는 선택한 애플리케이션만 기준으로 그래프를 확인합니다.",
+ "msg.dev.dashboard.chart.forbidden": "현재 계정에는 RP 이용 통계를 볼 권한이 없습니다.",
+ "msg.dev.dashboard.chart.server_error": "RP 이용 통계 조회 중 서버 오류가 발생했습니다.",
+ "msg.dev.dashboard.chart.service_unavailable":
+ "RP 이용 통계 집계 서비스가 아직 준비되지 않았습니다.",
+ "msg.dev.dashboard.chart.unavailable_with_reason":
+ "RP 이용 통계 API 응답을 확인할 수 없습니다. {{reason}}",
+ "msg.dev.dashboard.description": "연동 앱 구성과 인증 운영 지표를 한 곳에서 확인합니다.",
+ "msg.dev.dashboard.distribution.description":
+ "애플리케이션 유형과 headless login 사용 현황을 빠르게 확인합니다.",
"msg.dev.dashboard.hero.body":
"Hydra Admin API와 동기화된 RP 목록, 상태 토글, Consent 회수까지 devfront에서 처리하도록 준비합니다.",
"msg.dev.dashboard.hero.title_emphasis": " 하나의 화면",
@@ -441,6 +522,8 @@ const Map koStrings = {
"msg.dev.dashboard.notice.consent_audit": "Consent 회수는 감사 로그와 연계",
"msg.dev.dashboard.notice.dev_scope": "RP 정책은 dev scope에서만 적용",
"msg.dev.dashboard.notice.hydra_health": "Hydra Admin 상태 체크 준비",
+ "msg.dev.dashboard.recent.empty": "현재 계정이 접근할 수 있는 RP를 확인합니다.",
+ "msg.dev.dashboard.recent.none": "표시할 연동 앱이 없습니다.",
"msg.dev.forbidden.default": "해당 리소스에 접근할 권한이 없습니다. 관리자에게 문의하세요.",
"msg.dev.forbidden.rp_admin": "RP 관리자는 담당 앱의 리소스만 조회할 수 있습니다.",
"msg.dev.forbidden.tenant_admin": "테넌트 관리자 권한이 올바르게 설정되지 않았거나 만료되었습니다.",
@@ -461,6 +544,7 @@ const Map koStrings = {
"msg.dev.request.cancel.notes_placeholder": "승인 취소 사유를 입력하세요.",
"msg.dev.request.cancelled": "승인이 취소되었습니다.",
"msg.dev.request.empty": "신청 내역이 없습니다.",
+ "msg.dev.request.list.approved_count": "총 {{count}}명의 사용자가 승인되었습니다.",
"msg.dev.request.list.title": "신청 내역",
"msg.dev.request.modal.desc": "개발자 권한을 신청하려면 아래 정보를 확인한 뒤 신청 사유를 입력하세요.",
"msg.dev.request.modal.email": "이메일",
@@ -607,7 +691,6 @@ const Map koStrings = {
"재설정 링크가 만료되었습니다. 다시 요청해 주세요.",
"msg.userfront.error.whitelist.recovery_invalid": "재설정 링크가 유효하지 않습니다.",
"msg.userfront.error.whitelist.settings_disabled": "현재 계정 설정 화면은 준비 중입니다.",
- "msg.userfront.error.whitelist.tenant_not_allowed": "허용되지 않은 테넌트입니다.",
"msg.userfront.error.whitelist.verification_required":
"추가 인증이 필요합니다. 안내에 따라 진행해 주세요.",
"msg.userfront.forgot.description":
@@ -645,10 +728,9 @@ const Map koStrings = {
"msg.userfront.login.token_missing": "로그인 토큰을 확인할 수 없습니다.",
"msg.userfront.login.unregistered.body": "가입되지 않은 정보입니다.\\\\n회원가입 후 이용해 주세요.",
"msg.userfront.login.verification.approved": "승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.",
- "msg.userfront.login.verification.approved_remote":
- "승인되었습니다. 요청하신 브라우저 또는 PC 화면으로 돌아가 주세요.",
"msg.userfront.login.verification.approved_local":
"승인 되었습니다. 이 기기는 로그인되어 있는 상태입니다. 원격 창도 로그인이 될 예정입니다",
+ "msg.userfront.login.verification.approved_remote": "요청하신 로그인이 완료되었습니다",
"msg.userfront.login.verification.pending_remote":
"승인 요청을 확인하고 있습니다. 잠시만 기다려 주세요.",
"msg.userfront.login.verification.success": "로그인 승인에 성공했습니다.",
@@ -777,7 +859,11 @@ const Map koStrings = {
"ui.admin.api_keys.list.add": "API 키 생성",
"ui.admin.api_keys.list.breadcrumb.list": "List",
"ui.admin.api_keys.list.breadcrumb.section": "API Keys",
+ "ui.admin.api_keys.list.edit_scopes": "권한 수정",
"ui.admin.api_keys.list.registry.title": "API Key Registry",
+ "ui.admin.api_keys.list.rotate_secret": "Secret 재발급",
+ "ui.admin.api_keys.list.rotate_secret_done": "Secret 재발급 완료",
+ "ui.admin.api_keys.list.save_scopes": "권한 저장",
"ui.admin.api_keys.list.table.actions": "ACTIONS",
"ui.admin.api_keys.list.table.client_id": "CLIENT ID",
"ui.admin.api_keys.list.table.last_used": "LAST USED",
@@ -816,6 +902,32 @@ const Map koStrings = {
"ui.admin.audit.table.time": "TIME",
"ui.admin.audit.target": "Target · {{target}}",
"ui.admin.audit.title": "감사 로그",
+ "ui.admin.auth_guard.checker.allowed": "접근 허용",
+ "ui.admin.auth_guard.checker.allowed_description":
+ "해당 사용자는 요청한 리소스에 대해 권한이 있습니다. (상속 포함)",
+ "ui.admin.auth_guard.checker.check": "권한 확인 실행",
+ "ui.admin.auth_guard.checker.checking": "검증 중...",
+ "ui.admin.auth_guard.checker.denied": "접근 거부",
+ "ui.admin.auth_guard.checker.denied_description":
+ "해당 사용자는 요청한 리소스에 대해 권한이 없습니다.",
+ "ui.admin.auth_guard.checker.description":
+ "특정 주체(Subject)가 특정 리소스(Object)에 대해 권한이 있는지 Ory Keto를 통해 실시간으로 확인합니다.",
+ "ui.admin.auth_guard.checker.namespace": "네임스페이스",
+ "ui.admin.auth_guard.checker.namespace.label": "네임스페이스",
+ "ui.admin.auth_guard.checker.namespace.relying_party": "애플리케이션(RP)",
+ "ui.admin.auth_guard.checker.namespace.system": "시스템",
+ "ui.admin.auth_guard.checker.namespace.tenant": "테넌트",
+ "ui.admin.auth_guard.checker.namespace.tenant_group": "테넌트 그룹",
+ "ui.admin.auth_guard.checker.object_id": "대상 ID",
+ "ui.admin.auth_guard.checker.object_id_placeholder": "Tenant UUID 등",
+ "ui.admin.auth_guard.checker.relation": "관계",
+ "ui.admin.auth_guard.checker.relation_placeholder": "view, manage, admins...",
+ "ui.admin.auth_guard.checker.subject": "주체 (User:ID)",
+ "ui.admin.auth_guard.checker.subject_placeholder":
+ "User:uuid 또는 Namespace:ID#Relation",
+ "ui.admin.auth_guard.checker.title": "ReBAC 권한 검증 도구",
+ "ui.admin.auth_guard.subtitle": "관리자 권한과 ReBAC 관계를 실제 정책 엔진 기준으로 확인합니다.",
+ "ui.admin.auth_guard.title": "인증 가드",
"ui.admin.brand": "Baron 로그인",
"ui.admin.dev_role_switcher": "🛠 DEV Role Switcher",
"ui.admin.dev_role_switcher_real": "실제 역할 사용",
@@ -838,6 +950,9 @@ const Map koStrings = {
"ui.admin.groups.form.unit_level_placeholder": "예: 본부, 팀",
"ui.admin.groups.import_csv": "CSV 임포트",
"ui.admin.groups.list.title": "User Groups",
+ "ui.admin.groups.members.add_modal_title": "그룹에 멤버 추가",
+ "ui.admin.groups.members.move_modal_title": "부서 이동",
+ "ui.admin.groups.members.table.actions": "ACTIONS",
"ui.admin.groups.members.table.email": "이메일",
"ui.admin.groups.members.table.name": "이름",
"ui.admin.groups.members.table.remove": "제거",
@@ -846,6 +961,45 @@ const Map koStrings = {
"ui.admin.groups.table.name": "NAME",
"ui.admin.header.plane": "Admin Plane",
"ui.admin.header.subtitle": "관리 및 정책 운영",
+ "ui.admin.integrity.check.duplicate_tenant_slugs.title": "중복 테넌트 slug",
+ "ui.admin.integrity.check.orphan_tenant_parents.title": "고아 테넌트 부모",
+ "ui.admin.integrity.check.orphan_user_login_id_tenants.title":
+ "고아 로그인 ID 테넌트",
+ "ui.admin.integrity.check.orphan_user_login_id_users.title": "고아 로그인 ID 사용자",
+ "ui.admin.integrity.check.orphan_user_tenant_memberships.title":
+ "고아 사용자 테넌트 소속",
+ "ui.admin.integrity.fetch_error": "정합성 최종 검증 결과를 불러오지 못했습니다.",
+ "ui.admin.integrity.forbidden.title": "접근 권한이 없습니다",
+ "ui.admin.integrity.kicker": "시스템",
+ "ui.admin.integrity.loading": "불러오는 중",
+ "ui.admin.integrity.orphan_login_ids.delete": "선택 삭제",
+ "ui.admin.integrity.orphan_login_ids.title": "유령 로그인 ID 정리",
+ "ui.admin.integrity.read_model.title": "읽기 모델 정합성",
+ "ui.admin.integrity.reason.deleted_tenant": "삭제된 테넌트",
+ "ui.admin.integrity.reason.deleted_user": "삭제된 사용자",
+ "ui.admin.integrity.reason.missing_tenant": "테넌트 없음",
+ "ui.admin.integrity.reason.missing_user": "사용자 없음",
+ "ui.admin.integrity.recheck.run": "다시 검사",
+ "ui.admin.integrity.recheck.running": "검사 중",
+ "ui.admin.integrity.section.tenant_integrity": "테넌트 정합성",
+ "ui.admin.integrity.section.user_integrity": "사용자 정합성",
+ "ui.admin.integrity.status.fail": "실패",
+ "ui.admin.integrity.status.pass": "정상",
+ "ui.admin.integrity.status.warning": "주의",
+ "ui.admin.integrity.summary.checked_at": "검사 시각",
+ "ui.admin.integrity.summary.failures": "실패 건수",
+ "ui.admin.integrity.summary.failures_text": "실패 {{count}}건",
+ "ui.admin.integrity.summary.passed": "정상",
+ "ui.admin.integrity.summary.title": "정합성 최종 검증",
+ "ui.admin.integrity.summary.total_checks": "검사 항목",
+ "ui.admin.integrity.table.field": "필드",
+ "ui.admin.integrity.table.login_id": "로그인 ID",
+ "ui.admin.integrity.table.reason": "사유",
+ "ui.admin.integrity.table.select": "선택",
+ "ui.admin.integrity.table.select_item": "{{loginId}} 선택",
+ "ui.admin.integrity.table.tenant": "테넌트",
+ "ui.admin.integrity.table.user": "사용자",
+ "ui.admin.integrity.title": "데이터 정합성 검증",
"ui.admin.nav.api_keys": "API 키",
"ui.admin.nav.audit_logs": "감사 로그",
"ui.admin.nav.auth_guard": "인증 가드",
@@ -861,6 +1015,8 @@ const Map koStrings = {
"ui.admin.org.import_btn": "임포트",
"ui.admin.org.import_title": "조직도 대량 등록",
"ui.admin.org.start_import": "임포트 시작",
+ "ui.admin.overview.chart.description": "전체 또는 선택한 조직 기준으로 그래프를 확인합니다.",
+ "ui.admin.overview.chart.title": "회사별 앱별 로그인 요청 현황",
"ui.admin.overview.kicker": "Global Overview",
"ui.admin.overview.playbook.title": "Admin playbook",
"ui.admin.overview.quick_links.add_tenant": "테넌트 추가",
@@ -872,6 +1028,7 @@ const Map koStrings = {
"ui.admin.overview.summary.oidc_clients": "OIDC 클라이언트",
"ui.admin.overview.summary.policy_gate": "정책 게이트",
"ui.admin.overview.summary.total_tenants": "전체 테넌트 수",
+ "ui.admin.overview.summary.total_users": "전체 사용자 수",
"ui.admin.overview.title": "통합 대시보드",
"ui.admin.profile.manageable_tenants": "관리 가능한 테넌트",
"ui.admin.role.rp_admin": "RP ADMIN",
@@ -902,11 +1059,18 @@ const Map koStrings = {
"ui.admin.tenants.create.form.name": "테넌트 이름",
"ui.admin.tenants.create.form.name_placeholder": "테넌트 이름을 입력하세요",
"ui.admin.tenants.create.form.parent": "상위 테넌트",
+ "ui.admin.tenants.create.form.pick_hanmac_parent": "한맥가족에서 선택",
+ "ui.admin.tenants.create.form.pick_other_parent": "다른 테넌트 선택",
+ "ui.admin.tenants.create.form.root_tenant": "최상위 테넌트로 생성",
"ui.admin.tenants.create.form.slug": "Slug",
"ui.admin.tenants.create.form.slug_placeholder": "tenant-slug",
"ui.admin.tenants.create.form.status": "상태",
"ui.admin.tenants.create.form.type": "유형",
"ui.admin.tenants.create.memo.title": "정책 메모",
+ "ui.admin.tenants.create.parent_context.general": "일반 하위 테넌트",
+ "ui.admin.tenants.create.parent_context.hanmac": "한맥가족 하위 테넌트",
+ "ui.admin.tenants.create.parent_context.pick_required": "상위 테넌트 선택 필요",
+ "ui.admin.tenants.create.parent_context.root": "최상위 테넌트",
"ui.admin.tenants.create.profile.title": "Tenant Profile",
"ui.admin.tenants.create.title": "테넌트 추가",
"ui.admin.tenants.csv_template": "템플릿",
@@ -919,6 +1083,7 @@ const Map koStrings = {
"ui.admin.tenants.detail.tab_permissions": "권한",
"ui.admin.tenants.detail.tab_profile": "프로필",
"ui.admin.tenants.detail.tab_schema": "사용자 스키마",
+ "ui.admin.tenants.detail.tab_worksmobile": "Worksmobile",
"ui.admin.tenants.detail.title": "상세",
"ui.admin.tenants.domain_conflict.description": "",
"ui.admin.tenants.domain_conflict.title": "도메인 충돌",
@@ -926,21 +1091,32 @@ const Map koStrings = {
"ui.admin.tenants.export_without_ids": "UUID 제외 내보내기",
"ui.admin.tenants.import": "가져오기",
"ui.admin.tenants.import_preview.candidates": "후보",
- "ui.admin.tenants.import_preview.confirm": "가져오기 실행",
+ "ui.admin.tenants.import_preview.confirm": "임포트 확정",
+ "ui.admin.tenants.import_preview.create_new": "새로 생성",
"ui.admin.tenants.import_preview.create_new_reset": "신규 생성 (ID/slug 재설정)",
+ "ui.admin.tenants.import_preview.csv_parents": "CSV 상위 테넌트",
"ui.admin.tenants.import_preview.external_id": "외부 ID",
- "ui.admin.tenants.import_preview.match": "매칭",
- "ui.admin.tenants.import_preview.no_candidates": "후보 없음",
+ "ui.admin.tenants.import_preview.fixed_id": "고정 ID",
+ "ui.admin.tenants.import_preview.match": "매칭된 테넌트",
+ "ui.admin.tenants.import_preview.no_candidates": "매칭 가능한 테넌트가 없습니다.",
+ "ui.admin.tenants.import_preview.parent": "상위",
+ "ui.admin.tenants.import_preview.parent_companies": "상위 회사",
+ "ui.admin.tenants.import_preview.parent_company_groups": "상위 그룹사",
+ "ui.admin.tenants.import_preview.parent_organizations": "상위 조직",
"ui.admin.tenants.import_preview.parent_unresolved": "부모 확인 필요",
"ui.admin.tenants.import_preview.slug_exists": "slug 충돌",
- "ui.admin.tenants.import_preview.title": "CSV 가져오기 확인",
+ "ui.admin.tenants.import_preview.title": "임포트 미리보기",
"ui.admin.tenants.list.search_placeholder": "테넌트 이름 또는 슬러그 검색...",
"ui.admin.tenants.list.select_placeholder": "테넌트를 선택하세요",
+ "ui.admin.tenants.members.add_existing": "기존 멤버 배정",
+ "ui.admin.tenants.members.create_new": "신규 멤버 생성",
"ui.admin.tenants.members.delete_selected": "선택 삭제",
"ui.admin.tenants.members.descendants": "하위 조직 멤버",
"ui.admin.tenants.members.direct": "소속 멤버",
"ui.admin.tenants.members.direct_label": "직속",
"ui.admin.tenants.members.list_title": "구성원 관리",
+ "ui.admin.tenants.members.remove": "조직에서 제외",
+ "ui.admin.tenants.members.table.actions": "ACTIONS",
"ui.admin.tenants.members.table.email": "EMAIL",
"ui.admin.tenants.members.table.name": "NAME",
"ui.admin.tenants.members.table.role": "ROLE",
@@ -949,6 +1125,7 @@ const Map koStrings = {
"ui.admin.tenants.members.total": "전체",
"ui.admin.tenants.members.total_label": "전체",
"ui.admin.tenants.members.view_org_chart": "전체 조직도 보기",
+ "ui.admin.tenants.members.view_profile": "상세 정보",
"ui.admin.tenants.owners.add_button": "소유자 추가",
"ui.admin.tenants.owners.already_owner": "이미 소유자",
"ui.admin.tenants.owners.dialog_description": "이름 또는 이메일로 사용자를 검색하세요.",
@@ -958,6 +1135,10 @@ const Map koStrings = {
"ui.admin.tenants.owners.table_email": "이메일",
"ui.admin.tenants.owners.table_name": "이름",
"ui.admin.tenants.owners.title": "테넌트 소유자",
+ "ui.admin.tenants.parent.company_only": "회사/그룹사만 표시",
+ "ui.admin.tenants.parent.local_search_placeholder": "테넌트 이름 또는 슬러그 검색",
+ "ui.admin.tenants.parent.pick_tenant": "테넌트 선택",
+ "ui.admin.tenants.parent.search_placeholder": "이름 또는 slug 검색",
"ui.admin.tenants.profile.allowed_domains": "허용된 도메인 (콤마로 구분)",
"ui.admin.tenants.profile.allowed_domains_help":
"이 도메인을 가진 이메일로 가입한 사용자는 자동으로 이 테넌트에 배정됩니다.",
@@ -967,11 +1148,13 @@ const Map koStrings = {
"ui.admin.tenants.profile.form.parent_help":
"가족사 테넌트나 하위 조직을 종속시킬 경우 상위 테넌트를 선택해주세요.",
"ui.admin.tenants.profile.name": "테넌트 이름",
+ "ui.admin.tenants.profile.org_unit_type": "조직 세부타입",
"ui.admin.tenants.profile.slug": "슬러그 (Slug)",
"ui.admin.tenants.profile.status": "상태",
"ui.admin.tenants.profile.subtitle": "슬러그 및 상태 변경은 즉시 적용됩니다.",
"ui.admin.tenants.profile.title": "테넌트 프로필",
"ui.admin.tenants.profile.type": "테넌트 유형",
+ "ui.admin.tenants.profile.visibility": "공개 범위",
"ui.admin.tenants.registry.title": "Tenant registry",
"ui.admin.tenants.schema.add_field": "필드 추가",
"ui.admin.tenants.schema.field.admin_only": "관리자 전용",
@@ -993,10 +1176,14 @@ const Map koStrings = {
"ui.admin.tenants.schema.field.validation_placeholder": "정규표현식 (선택 사항)",
"ui.admin.tenants.schema.save": "스키마 저장",
"ui.admin.tenants.schema.title": "User Schema Extension",
+ "ui.admin.tenants.scope.active": "{{name}} 하위",
+ "ui.admin.tenants.scope.pick": "상위 범위 선택",
+ "ui.admin.tenants.seed_badge": "초기 설정",
"ui.admin.tenants.sub.add": "하위 테넌트 추가",
"ui.admin.tenants.sub.add_dialog_desc": "하위 테넌트로 추가할 테넌트를 선택하세요.",
"ui.admin.tenants.sub.add_dialog_title": "하위 테넌트 추가",
"ui.admin.tenants.sub.add_existing": "기존 테넌트 추가",
+ "ui.admin.tenants.sub.export": "내보내기",
"ui.admin.tenants.sub.manage": "관리",
"ui.admin.tenants.sub.no_candidates": "추가 가능한 테넌트가 없습니다.",
"ui.admin.tenants.sub.search_placeholder": "검색...",
@@ -1007,6 +1194,7 @@ const Map koStrings = {
"ui.admin.tenants.sub.title": "하위 테넌트 ({{count}})",
"ui.admin.tenants.sub.tree_search_placeholder": "트리에서 검색...",
"ui.admin.tenants.table.actions": "ACTIONS",
+ "ui.admin.tenants.table.created": "CREATED",
"ui.admin.tenants.table.id": "ID",
"ui.admin.tenants.table.members": "멤버수",
"ui.admin.tenants.table.name": "NAME",
@@ -1015,8 +1203,45 @@ const Map koStrings = {
"ui.admin.tenants.table.type": "유형",
"ui.admin.tenants.table.updated": "UPDATED",
"ui.admin.tenants.title": "테넌트 목록",
+ "ui.admin.tenants.view.hierarchy": "계층 구조",
+ "ui.admin.tenants.view.list": "평면 목록",
+ "ui.admin.tenants.view.table": "평면",
+ "ui.admin.tenants.view.tree": "트리",
"ui.admin.tenants.view_org_chart": "전체 조직도 보기",
+ "ui.admin.tenants.worksmobile.compare": "Baron / Works 비교",
+ "ui.admin.tenants.worksmobile.compare_description":
+ "구성원은 기본적으로 Baron 또는 WORKS 한쪽에만 있는 항목을 보여줍니다.",
+ "ui.admin.tenants.worksmobile.compare_groups": "조직/그룹",
+ "ui.admin.tenants.worksmobile.compare_users": "구성원",
+ "ui.admin.tenants.worksmobile.dry_run": "Backfill Dry-run",
+ "ui.admin.tenants.worksmobile.forbidden": "Worksmobile 연동 권한이 없습니다.",
+ "ui.admin.tenants.worksmobile.initial_password_csv": "초기 비밀번호 CSV",
+ "ui.admin.tenants.worksmobile.recent_jobs": "최근 작업",
+ "ui.admin.tenants.worksmobile.refresh": "새로고침",
+ "ui.admin.tenants.worksmobile.single_sync": "단건 동기화",
+ "ui.admin.tenants.worksmobile.single_sync_description":
+ "Baron UUID 기준으로 조직 또는 구성원 sync 작업을 생성합니다.",
+ "ui.admin.tenants.worksmobile.subtitle":
+ "한맥가족 Directory 조직/구성원 동기화 상태를 확인하고 실패 작업을 재시도합니다.",
+ "ui.admin.tenants.worksmobile.sync_orgunit": "조직 Sync",
+ "ui.admin.tenants.worksmobile.sync_user": "구성원 Sync",
+ "ui.admin.tenants.worksmobile.title": "Worksmobile 연동",
"ui.admin.title": "Admin Control",
+ "ui.admin.user_projection.actions.reconcile": "재동기화",
+ "ui.admin.user_projection.actions.reset": "초기화 후 재구축",
+ "ui.admin.user_projection.card.description":
+ "Backend DB 통계가 참조하는 사용자 read model 상태입니다.",
+ "ui.admin.user_projection.card.title": "Kratos 사용자 동기화",
+ "ui.admin.user_projection.forbidden.title": "접근 권한이 없습니다",
+ "ui.admin.user_projection.loading": "불러오는 중",
+ "ui.admin.user_projection.status.failed": "실패",
+ "ui.admin.user_projection.status.not_ready": "준비되지 않음",
+ "ui.admin.user_projection.status.ready": "준비됨",
+ "ui.admin.user_projection.summary.last_synced": "마지막 동기화",
+ "ui.admin.user_projection.summary.projected_users": "동기화 사용자",
+ "ui.admin.user_projection.summary.status": "상태",
+ "ui.admin.user_projection.summary.updated_at": "상태 갱신",
+ "ui.admin.user_projection.title": "사용자 동기화 관리",
"ui.admin.users.bulk.acknowledge_warning": "경고를 확인했으며 계속 진행합니다.",
"ui.admin.users.bulk.create_missing_tenant": "신규 생성",
"ui.admin.users.bulk.do_move": "이동 실행",
@@ -1024,10 +1249,12 @@ const Map koStrings = {
"ui.admin.users.bulk.move_group": "테넌트 일괄 이동",
"ui.admin.users.bulk.move_title": "사용자 일괄 이동",
"ui.admin.users.bulk.no_department": "부서 없음",
+ "ui.admin.users.bulk.permission_placeholder": "권한 선택",
"ui.admin.users.bulk.schema_warning": "스키마 호환성 경고",
"ui.admin.users.bulk.select_group": "대상 테넌트 선택",
"ui.admin.users.bulk.selected_count": "{{count}}명 선택됨",
"ui.admin.users.bulk.start_upload": "업로드 시작",
+ "ui.admin.users.bulk.status_placeholder": "상태 선택",
"ui.admin.users.bulk.tenant_resolution": "테넌트 매핑",
"ui.admin.users.bulk.title": "일괄 작업",
"ui.admin.users.create.account.title": "계정 정보",
@@ -1051,6 +1278,7 @@ const Map koStrings = {
"ui.admin.users.create.form.password_placeholder": "********",
"ui.admin.users.create.form.phone": "전화번호",
"ui.admin.users.create.form.phone_placeholder": "010-1234-5678",
+ "ui.admin.users.create.form.picker_description": "배정할 테넌트를 검색해서 선택하세요.",
"ui.admin.users.create.form.position": "직급",
"ui.admin.users.create.form.position_placeholder": "수석/책임/선임",
"ui.admin.users.create.form.role": "역할",
@@ -1060,6 +1288,7 @@ const Map koStrings = {
"ui.admin.users.create.password_generated.title": "초기 비밀번호 생성 완료",
"ui.admin.users.create.submit": "사용자 생성",
"ui.admin.users.create.title": "사용자 추가",
+ "ui.admin.users.csv_template": "템플릿 다운로드",
"ui.admin.users.detail.back": "목록으로 돌아가기",
"ui.admin.users.detail.breadcrumb.section": "Users",
"ui.admin.users.detail.contact_title": "ui.admin.users.detail.contact_title",
@@ -1302,6 +1531,7 @@ const Map koStrings = {
"ui.admin.users.list.breadcrumb.list": "List",
"ui.admin.users.list.breadcrumb.section": "Users",
"ui.admin.users.list.bulk_import": "일괄 임포트",
+ "ui.admin.users.list.change_status": "{{name}} 상태 변경",
"ui.admin.users.list.columns.title": "컬럼 설정",
"ui.admin.users.list.empty": "검색 결과가 없습니다.",
"ui.admin.users.list.fetch_error": "사용자 목록 조회에 실패했습니다.",
@@ -1309,6 +1539,7 @@ const Map koStrings = {
"ui.admin.users.list.registry.count": "총 {{count}}명의 사용자가 등록되어 있습니다.",
"ui.admin.users.list.registry.title": "사용자 레지스트리",
"ui.admin.users.list.search_placeholder": "이름 또는 이메일 검색...",
+ "ui.admin.users.list.status_select": "{{name}} 상태",
"ui.admin.users.list.subtitle": "시스템 사용자를 조회하고 관리합니다.",
"ui.admin.users.list.table.actions": "ACTIONS",
"ui.admin.users.list.table.created": "CREATED",
@@ -1541,12 +1772,19 @@ const Map koStrings = {
"ui.common.select_placeholder": "선택하세요",
"ui.common.show_more": "+ 더보기",
"ui.common.status.active": "활성",
+ "ui.common.status.archived": "보관됨",
+ "ui.common.status.baron_guest": "Baron 게스트",
"ui.common.status.blocked": "ui.common.status.blocked",
+ "ui.common.status.extended_leave": "장기휴직",
"ui.common.status.failure": "실패",
"ui.common.status.inactive": "비활성",
+ "ui.common.status.leave_of_absence": "휴직",
"ui.common.status.ok": "정상",
"ui.common.status.pending": "준비 중",
+ "ui.common.status.preboarding": "입사대기",
"ui.common.status.success": "성공",
+ "ui.common.status.suspended": "정지",
+ "ui.common.status.temporary_leave": "단기휴무",
"ui.common.success": "성공",
"ui.common.theme_dark": "Dark",
"ui.common.theme_light": "Light",
@@ -1760,8 +1998,24 @@ const Map koStrings = {
"ui.dev.clients.untitled": "Untitled",
"ui.dev.console_title": "Developer Console",
"ui.dev.dashboard.badge.consent_guard": "Consent guard ready",
+ "ui.dev.dashboard.badge.oidc": "OIDC 운영",
"ui.dev.dashboard.badge.policy_toggle": "Policy toggle enabled",
+ "ui.dev.dashboard.badge.registry": "RP registry",
"ui.dev.dashboard.badge.rp_synced": "RP registry synced",
+ "ui.dev.dashboard.chart.aria": "RP 요청 현황",
+ "ui.dev.dashboard.chart.filter_all": "전체",
+ "ui.dev.dashboard.chart.period_day": "일",
+ "ui.dev.dashboard.chart.period_month": "월",
+ "ui.dev.dashboard.chart.period_week": "주",
+ "ui.dev.dashboard.chart.series": "로그인 {{login}} / 사용자 {{subjects}}",
+ "ui.dev.dashboard.chart.title": "애플리케이션별 로그인 요청 현황",
+ "ui.dev.dashboard.chart.x_axis": "X축: 기간",
+ "ui.dev.dashboard.chart.y_axis": "Y축: 로그인 요청 수",
+ "ui.dev.dashboard.distribution.headless_hint":
+ "이 중 Headless Login 사용 {{count}}",
+ "ui.dev.dashboard.distribution.pkce": "PKCE",
+ "ui.dev.dashboard.distribution.private": "Server side App",
+ "ui.dev.dashboard.distribution.title": "애플리케이션 구성 요약",
"ui.dev.dashboard.next.subtitle": "Ship the RP controls",
"ui.dev.dashboard.next.title": "Next actions",
"ui.dev.dashboard.ops.card.consent_revoked": "Consent 회수 건수",
@@ -1771,16 +2025,26 @@ const Map koStrings = {
"ui.dev.dashboard.ops.tag.consent": "Consent grants",
"ui.dev.dashboard.ops.tag.rp_status": "RP status",
"ui.dev.dashboard.ops.title": "Ops board",
+ "ui.dev.dashboard.quick_links.create_button": "새 RP 만들기",
+ "ui.dev.dashboard.quick_links.new_client": "새 RP 생성",
+ "ui.dev.dashboard.quick_links.title": "빠른 이동",
"ui.dev.dashboard.ready_badge": "devfront ready",
+ "ui.dev.dashboard.recent.title": "내 애플리케이션",
"ui.dev.dashboard.stack.notes": "Setup notes",
"ui.dev.dashboard.stack.subtitle": "Devfront baseline",
"ui.dev.dashboard.stack.title": "Stack readiness",
+ "ui.dev.dashboard.summary.active_clients": "활성 RP 수",
+ "ui.dev.dashboard.summary.active_sessions": "활성 세션 수",
+ "ui.dev.dashboard.summary.auth_failures_24h": "24시간 인증 실패 수",
+ "ui.dev.dashboard.summary.total_clients": "총 RP 수",
+ "ui.dev.dashboard.title": "대시보드",
"ui.dev.env_badge": "Env: dev",
"ui.dev.header.plane": "Dev Plane",
"ui.dev.header.subtitle": "Manage your applications",
"ui.dev.nav.clients": "연동 앱",
"ui.dev.nav.developer_request": "개발자 권한 신청",
"ui.dev.nav.logout": "로그아웃",
+ "ui.dev.nav.overview": "개요",
"ui.dev.profile.basic.email": "이메일",
"ui.dev.profile.basic.id": "사용자 ID",
"ui.dev.profile.basic.name": "이름",
@@ -1792,6 +2056,7 @@ const Map koStrings = {
"ui.dev.profile.menu_title": "계정",
"ui.dev.profile.org.company_code": "회사 코드",
"ui.dev.profile.org.tenant": "테넌트",
+ "ui.dev.profile.org.tenant_slug": "테넌트 slug",
"ui.dev.profile.org.title": "조직 정보",
"ui.dev.profile.role.current": "현재 역할",
"ui.dev.profile.role.description": "현재 계정에 부여된 권한 등급입니다.",
@@ -1840,6 +2105,19 @@ const Map koStrings = {
"ui.dev.tenant.workspace": "작업 테넌트 (컨텍스트)",
"ui.dev.tenant.workspace_desc": "현재 작업 중인 테넌트를 선택하고 저장하여 API 요청 컨텍스트를 변경합니다.",
"ui.dev.welcome.btn_request": "신규 신청하기",
+ "ui.shell.nav.logout": "로그아웃",
+ "ui.shell.nav.profile": "내 정보",
+ "ui.shell.profile.menu_aria": "계정 메뉴 열기",
+ "ui.shell.profile.menu_title": "계정",
+ "ui.shell.profile.unknown_email": "unknown@example.com",
+ "ui.shell.profile.unknown_name": "Unknown User",
+ "ui.shell.session.active": "세션 활성",
+ "ui.shell.session.auto_extend": "세션 만료 관리",
+ "ui.shell.session.disabled": "세션 만료 비활성화",
+ "ui.shell.session.expired": "세션 만료",
+ "ui.shell.session.expiring": "만료 임박: {{minutes}}분 {{seconds}}초 남음",
+ "ui.shell.session.remaining": "만료 예정: {{minutes}}분 {{seconds}}초 남음",
+ "ui.shell.session.unknown": "알 수 없음",
"ui.userfront.app_label.admin_console": "Admin Console",
"ui.userfront.app_label.baron": "Baron 로그인",
"ui.userfront.app_label.dev_console": "Dev Console",
@@ -1919,6 +2197,7 @@ const Map koStrings = {
"ui.userfront.login.unregistered.title": "미등록 회원",
"ui.userfront.login.verification.action_label": "확인",
"ui.userfront.login.verification.action_label_close": "창 닫기",
+ "ui.userfront.login.verification.action_label_remote": "로그인 창으로 이동하기",
"ui.userfront.login.verification.page_title": "로그인 승인",
"ui.userfront.login.verification.title": "승인 완료",
"ui.userfront.login.verification.title_pending": "로그인 승인 확인 중",
@@ -1995,6 +2274,14 @@ const Map koStrings = {
};
const Map enStrings = {
+ "\"msg.admin.tenants.bulk.update_error\"": "temp",
+ "\"msg.admin.tenants.bulk.update_success\"": "temp",
+ "\"msg.admin.tenants.status_error\"": "temp",
+ "\"ui.admin.tenants.bulk.selected_count\"": "temp",
+ "\"ui.admin.tenants.bulk.status_placeholder\"": "temp",
+ "\"ui.admin.tenants.data_mgmt\"": "temp",
+ "\"ui.admin.tenants.toggle_status\"": "temp",
+ "\"ui.admin.users.data_mgmt\"": "temp",
"domain.affiliation.affiliate": "Affiliate",
"domain.affiliation.general": "General",
"domain.company.baron": "Baron",
@@ -2005,6 +2292,7 @@ const Map enStrings = {
"domain.company.saman": "Saman",
"domain.tenant_type.company": "Company",
"domain.tenant_type.company_group": "Company Group",
+ "domain.tenant_type.organization": "Organization",
"domain.tenant_type.personal": "Personal",
"domain.tenant_type.user_group": "User Group",
"err.backend.authorization_pending":
@@ -2037,43 +2325,11 @@ const Map enStrings = {
"err.userfront.auth_proxy.linked_app_revoke":
"Failed to revoke the linked application.",
"err.userfront.auth_proxy.login_failed": "Login failed.",
- "err.userfront.auth_proxy.login_init":
- "Failed to initialize login: {{error}}",
- "err.userfront.auth_proxy.login_poll":
- "Failed to check login status: {{error}}",
"err.userfront.auth_proxy.oidc_accept": "OIDC Accept",
"err.userfront.auth_proxy.password_reset_complete":
"Failed to complete the password reset.",
- "err.userfront.auth_proxy.password_policy_fetch":
- "Failed to load the password policy.",
"err.userfront.auth_proxy.password_reset_init":
"Failed to start the password reset.",
- "err.userfront.auth_proxy.profile_load":
- "Failed to load the profile: {{error}}",
- "err.userfront.auth_proxy.tenant_info_fetch":
- "Failed to load tenant information.",
- "err.userfront.auth_proxy.verify_failed": "Verification failed: {{error}}",
- "err.userfront.auth_proxy.sms_send": "Failed to send SMS: {{error}}",
- "err.userfront.auth_proxy.code_verify":
- "Failed to verify the code: {{error}}",
- "err.userfront.auth_proxy.qr_init": "Failed to start QR login: {{error}}",
- "err.userfront.auth_proxy.qr_poll": "Failed to check QR status: {{error}}",
- "err.userfront.auth_proxy.qr_approve":
- "Failed to approve QR login: {{error}}",
- "err.userfront.auth_proxy.user_create":
- "Failed to create the user: {{error}}",
- "err.userfront.auth_proxy.user_list":
- "Failed to load the user list: {{error}}",
- "err.userfront.auth_proxy.user_delete":
- "Failed to delete the user: {{error}}",
- "err.userfront.auth_proxy.user_status_update":
- "Failed to update the user status: {{error}}",
- "err.userfront.auth_proxy.user_update":
- "Failed to update the user: {{error}}",
- "err.userfront.auth_proxy.linked_apps_load":
- "Failed to load linked applications.",
- "err.userfront.auth_proxy.phone_code_send":
- "Failed to send the verification code: {{error}}",
"err.userfront.profile.load_failed": "Failed to load the profile.",
"err.userfront.profile.password_change_failed": "Password Change Failed",
"err.userfront.profile.send_code_failed":
@@ -2100,9 +2356,15 @@ const Map enStrings = {
"Rotate the key immediately if you think it has been exposed.",
"msg.admin.api_keys.list.delete_confirm":
"Are you sure you want to delete this API key?",
+ "msg.admin.api_keys.list.edit_scopes_desc":
+ "Edit the scopes granted to this API key.",
"msg.admin.api_keys.list.empty": "No API keys have been issued yet.",
"msg.admin.api_keys.list.fetch_error": "Failed to load the API key list.",
"msg.admin.api_keys.list.registry.count": "{{count}} API keys loaded.",
+ "msg.admin.api_keys.list.rotate_confirm":
+ "Rotate the secret for this API key?",
+ "msg.admin.api_keys.list.rotate_secret_notice":
+ "The new secret is shown only once.",
"msg.admin.api_keys.list.subtitle":
"View and manage the API keys issued for server-to-server communication.",
"msg.admin.apikeys.registry.count":
@@ -2113,6 +2375,8 @@ const Map enStrings = {
"msg.admin.audit.load_error": "Error loading logs: {{error}}",
"msg.admin.audit.loading": "Loading audit logs...",
"msg.admin.audit.registry.count": "{{count}} logs loaded.",
+ "msg.admin.audit.registry.description":
+ "Filter recent audit logs by search criteria and review action history quickly.",
"msg.admin.audit.subtitle":
"Review command-driven ClickHouse audit logs from the admin workspace.",
"msg.admin.common.forbidden":
@@ -2135,12 +2399,20 @@ const Map enStrings = {
"msg.admin.groups.list.import_error": "Import Error",
"msg.admin.groups.list.import_success": "Import Success",
"msg.admin.groups.list.loading": "Loading...",
+ "msg.admin.groups.list.no_results": "No groups found.",
"msg.admin.groups.list.subtitle":
"Manage departments and teams under the current tenant.",
+ "msg.admin.groups.members.add_modal_desc":
+ "Search and select members to add from users in this tenant.",
"msg.admin.groups.members.add_success": "Member added successfully.",
+ "msg.admin.groups.members.all_added":
+ "All tenant members are already in this group.",
"msg.admin.groups.members.count": "{{count}} members loaded.",
"msg.admin.groups.members.empty":
"No members are assigned to this organization unit.",
+ "msg.admin.groups.members.move_modal_desc":
+ "Select the target group to move the selected member.",
+ "msg.admin.groups.members.move_success": "Member moved successfully.",
"msg.admin.groups.members.remove_confirm":
"Are you sure you want to remove this member?",
"msg.admin.groups.members.remove_success": "Member removed successfully.",
@@ -2155,6 +2427,41 @@ const Map enStrings = {
"msg.admin.groups.roles.remove_success": "Role revoked successfully.",
"msg.admin.header.subtitle": "Tenant isolation & least privilege by default",
"msg.admin.idp_env_prod": "IDP env: prod",
+ "msg.admin.integrity.check.duplicate_tenant_slugs.description":
+ "Checks duplicate active tenant slugs using LOWER(TRIM(slug)).",
+ "msg.admin.integrity.check.orphan_tenant_parents.description":
+ "Checks whether tenants.parent_id points to a missing or soft-deleted tenant.",
+ "msg.admin.integrity.check.orphan_user_login_id_tenants.description":
+ "Checks whether user_login_ids.tenant_id points to a missing or soft-deleted tenant.",
+ "msg.admin.integrity.check.orphan_user_login_id_users.description":
+ "Checks whether user_login_ids.user_id points to a missing or soft-deleted user.",
+ "msg.admin.integrity.check.orphan_user_tenant_memberships.description":
+ "Checks whether users.tenant_id points to a missing or soft-deleted tenant.",
+ "msg.admin.integrity.forbidden.description":
+ "This screen is available only to super_admin.",
+ "msg.admin.integrity.orphan_login_ids.delete_confirm":
+ "Delete {{count}} selected orphan login IDs?",
+ "msg.admin.integrity.orphan_login_ids.delete_success":
+ "Deleted {{count}} orphan login IDs.",
+ "msg.admin.integrity.orphan_login_ids.description":
+ "Review login IDs that reference deleted or missing users/tenants, then delete selected rows.",
+ "msg.admin.integrity.orphan_login_ids.empty":
+ "No orphan login IDs to delete.",
+ "msg.admin.integrity.orphan_login_ids.load_error":
+ "Failed to load orphan login ID targets.",
+ "msg.admin.integrity.read_model.description":
+ "Checks anomalies in the backend DB read model without overwriting the Ory SoT.",
+ "msg.admin.integrity.recheck.error": "Check failed.",
+ "msg.admin.integrity.recheck.running": "Running integrity check.",
+ "msg.admin.integrity.recheck.success": "Check completed.",
+ "msg.admin.integrity.report.load_error":
+ "Failed to load the integrity report.",
+ "msg.admin.integrity.section.tenant_integrity.description":
+ "Check duplicate tenant slugs and invalid parent relationships.",
+ "msg.admin.integrity.section.user_integrity.description":
+ "Check orphan records in user and login ID references.",
+ "msg.admin.integrity.subtitle":
+ "Review integrity status and inspect checks across the admin data model.",
"msg.admin.logout_confirm": "Are you sure you want to log out?",
"msg.admin.notice.idp_policy":
"IDP management keys are only used through server-side wrapper APIs with audit logging and rate limits enabled.",
@@ -2186,6 +2493,7 @@ const Map enStrings = {
"msg.admin.overview.summary.oidc_clients": "OIDC Clients",
"msg.admin.overview.summary.policy_gate": "Policy Gate Status",
"msg.admin.overview.summary.total_tenants": "Total Tenants",
+ "msg.admin.overview.summary.total_users": "Total Users",
"msg.admin.scope_admin": "Scoped to /admin",
"msg.admin.session_ttl": "Session TTL: 15m admin",
"msg.admin.tenant_headers": "Tenant-aware headers",
@@ -2207,6 +2515,8 @@ const Map enStrings = {
"Leave operational notes or policy reminders for this tenant.",
"msg.admin.tenants.create.memo.subtitle":
"Capture internal policy notes for administrators.",
+ "msg.admin.tenants.create.pick_parent_first":
+ "Select the parent tenant first.",
"msg.admin.tenants.create.profile.subtitle":
"Set the basic tenant profile information.",
"msg.admin.tenants.create.subtitle":
@@ -2215,6 +2525,7 @@ const Map enStrings = {
"msg.admin.tenants.delete_confirm": "Delete Tenant \\\\\\\"{{name}}\\\\\\\"?",
"msg.admin.tenants.delete_success": "Tenant deleted.",
"msg.admin.tenants.empty": "No tenants have been registered yet.",
+ "msg.admin.tenants.export_error": "Failed to export tenants.",
"msg.admin.tenants.fetch_error": "Failed to load the tenant list.",
"msg.admin.tenants.import_empty": "There are no tenant rows to import.",
"msg.admin.tenants.import_error": "Failed to import tenants.",
@@ -2227,6 +2538,12 @@ const Map enStrings = {
"msg.admin.tenants.members.empty": "No members found.",
"msg.admin.tenants.members.limit_notice":
"Showing members from the first 10 descendant organizations due to size limits.",
+ "msg.admin.tenants.members.remove_confirm":
+ "Are you sure you want to exclude '{{name}}' from this organization?",
+ "msg.admin.tenants.members.remove_error":
+ "An error occurred while excluding from organization.",
+ "msg.admin.tenants.members.remove_success":
+ "Successfully excluded from organization.",
"msg.admin.tenants.missing_id": "No Tenant ID.",
"msg.admin.tenants.not_found": "Tenant not found.",
"msg.admin.tenants.owners.add_success": "Owner added successfully.",
@@ -2238,6 +2555,12 @@ const Map enStrings = {
"msg.admin.tenants.owners.remove_success": "Owner permission revoked.",
"msg.admin.tenants.owners.subtitle":
"List of owners with top-level permissions for this tenant.",
+ "msg.admin.tenants.parent.local_picker_description":
+ "Select the tenant to use as the parent from the tenant list.",
+ "msg.admin.tenants.parent.local_picker_empty":
+ "No selectable tenants are available.",
+ "msg.admin.tenants.parent.picker_description":
+ "Select a tenant in org-chart to apply it as the parent tenant.",
"msg.admin.tenants.registry.count": "{{count}} tenants loaded.",
"msg.admin.tenants.remove_sub_confirm":
"Remove tenant \\\"{{name}}\\\" from sub-tenants?",
@@ -2250,11 +2573,26 @@ const Map enStrings = {
"Define custom attributes for users in this tenant.",
"msg.admin.tenants.schema.update_error": "Failed to update schema",
"msg.admin.tenants.schema.update_success": "Schema updated successfully",
+ "msg.admin.tenants.scope.description":
+ "Select a parent tenant to filter the list to its descendants.",
+ "msg.admin.tenants.seed_delete_blocked": "Seed tenants cannot be deleted.",
"msg.admin.tenants.sub.empty": "No child tenants are connected.",
"msg.admin.tenants.sub.subtitle":
"Review and manage child tenants linked under this tenant.",
"msg.admin.tenants.subtitle":
"Review registered tenants and manage their current status.",
+ "msg.admin.user_projection.action_error": "Projection operation failed.",
+ "msg.admin.user_projection.action_success":
+ "Refreshed the projection for {{count}} users.",
+ "msg.admin.user_projection.forbidden.description":
+ "This screen is only available to super_admin users.",
+ "msg.admin.user_projection.forbidden_description":
+ "This screen is only available to super_admin users.",
+ "msg.admin.user_projection.load_error": "Failed to load projection status.",
+ "msg.admin.user_projection.reset_confirm":
+ "Rebuild user projection from the Kratos source of truth?",
+ "msg.admin.user_projection.subtitle":
+ "Review and sync the Kratos user read model.",
"msg.admin.users.bulk.delete_confirm":
"Are you sure you want to delete the selected {{count}} users?",
"msg.admin.users.bulk.delete_success": "{{count}} users have been deleted.",
@@ -2265,11 +2603,15 @@ const Map enStrings = {
"msg.admin.users.bulk.move_error": "Error moving users.",
"msg.admin.users.bulk.move_success": "{{count}} users moved successfully.",
"msg.admin.users.bulk.parsed_count": "Parsed {{count}} rows.",
+ "msg.admin.users.bulk.permission_placeholder": "Select permission",
"msg.admin.users.bulk.schema_incompatible":
"Fields not in target schema may be lost:",
"msg.admin.users.bulk.schema_missing":
"Missing required fields for target tenant:",
+ "msg.admin.users.bulk.status_placeholder": "Select status",
"msg.admin.users.bulk.update_success": "User info updated successfully.",
+ "msg.admin.users.confirm_remove_org":
+ "Do you want to remove this user from the organization?",
"msg.admin.users.create.account.subtitle":
"Fill in the account details required to create the user.",
"msg.admin.users.create.appointment_required":
@@ -2356,6 +2698,8 @@ const Map enStrings = {
"msg.dev.audit.load_error": "Error loading audit logs: {{error}}",
"msg.dev.audit.loaded_count": "Loaded {{count}} rows",
"msg.dev.audit.loading": "Loading audit logs...",
+ "msg.dev.audit.registry_description":
+ "Filter recent audit logs by search criteria and review action history quickly.",
"msg.dev.audit.subtitle":
"Shows DevFront activity history within current tenant/app scope.",
"msg.dev.auth.access_denied_description":
@@ -2510,6 +2854,29 @@ const Map enStrings = {
"msg.dev.clients.scopes.openid": "Openid",
"msg.dev.clients.scopes.profile": "Profile",
"msg.dev.clients.showing": "Showing {{shown}} of {{total}} apps",
+ "msg.dev.dashboard.access_denied":
+ "Overview is available only to users with developer access.",
+ "msg.dev.dashboard.access_denied_detail":
+ "Submit a request on the developer access page and wait for approval.",
+ "msg.dev.dashboard.access_pending":
+ "Your developer access request is under review.",
+ "msg.dev.dashboard.access_pending_detail":
+ "You can use the overview and developer features after a super admin approves it.",
+ "msg.dev.dashboard.chart.empty": "No RP usage aggregates are available.",
+ "msg.dev.dashboard.chart.filter_description":
+ "View the graph for all apps or only the selected apps.",
+ "msg.dev.dashboard.chart.forbidden":
+ "This account does not have permission to view RP usage statistics.",
+ "msg.dev.dashboard.chart.server_error":
+ "A server error occurred while loading RP usage statistics.",
+ "msg.dev.dashboard.chart.service_unavailable":
+ "The RP usage aggregation service is not ready yet.",
+ "msg.dev.dashboard.chart.unavailable_with_reason":
+ "RP usage statistics are unavailable. {{reason}}",
+ "msg.dev.dashboard.description":
+ "Review RP composition and authentication operations in one place.",
+ "msg.dev.dashboard.distribution.description":
+ "Quickly review application types and headless login usage.",
"msg.dev.dashboard.hero.body":
"Monitor RP readiness, consent activity, and operational status for the current developer workspace.",
"msg.dev.dashboard.hero.title_emphasis": "Title Emphasis",
@@ -2518,6 +2885,8 @@ const Map enStrings = {
"msg.dev.dashboard.notice.consent_audit": "Consent Audit",
"msg.dev.dashboard.notice.dev_scope": "Dev Scope",
"msg.dev.dashboard.notice.hydra_health": "Hydra Health",
+ "msg.dev.dashboard.recent.empty": "Review the RPs this account can access.",
+ "msg.dev.dashboard.recent.none": "No linked applications are available.",
"msg.dev.forbidden.default":
"You do not have permission to access this resource. Please contact an administrator.",
"msg.dev.forbidden.rp_admin":
@@ -2544,6 +2913,7 @@ const Map enStrings = {
"Enter a reason for cancelling the approval.",
"msg.dev.request.cancelled": "Approval cancelled.",
"msg.dev.request.empty": "No requests found.",
+ "msg.dev.request.list.approved_count": "{{count}} users have been approved.",
"msg.dev.request.list.title": "Request History",
"msg.dev.request.modal.desc":
"Review the information below and enter a request reason to apply for developer access.",
@@ -2728,8 +3098,6 @@ const Map enStrings = {
"The recovery link is invalid.",
"msg.userfront.error.whitelist.settings_disabled":
"Account settings are currently unavailable.",
- "msg.userfront.error.whitelist.tenant_not_allowed":
- "This tenant is not allowed.",
"msg.userfront.error.whitelist.verification_required":
"Additional verification is required. Please follow the instructions.",
"msg.userfront.forgot.description":
@@ -2784,10 +3152,10 @@ const Map enStrings = {
"We could not find an account for that information.\\\\\\\\\\\\\\\\nPlease sign up before continuing.",
"msg.userfront.login.verification.approved":
"Approved. Complete sign-in in the original window.",
- "msg.userfront.login.verification.approved_remote":
- "Approved. Please return to the original browser or PC screen.",
"msg.userfront.login.verification.approved_local":
"Approved. This device is already signed in, and the remote window will be signed in shortly.",
+ "msg.userfront.login.verification.approved_remote":
+ "Your requested sign-in is complete.",
"msg.userfront.login.verification.pending_remote":
"Checking the sign-in approval request. Please wait.",
"msg.userfront.login.verification.success": "Sign-in approval completed.",
@@ -2954,7 +3322,11 @@ const Map enStrings = {
"ui.admin.api_keys.list.add": "Add",
"ui.admin.api_keys.list.breadcrumb.list": "List",
"ui.admin.api_keys.list.breadcrumb.section": "API Keys",
+ "ui.admin.api_keys.list.edit_scopes": "Edit scopes",
"ui.admin.api_keys.list.registry.title": "API Key Registry",
+ "ui.admin.api_keys.list.rotate_secret": "Rotate secret",
+ "ui.admin.api_keys.list.rotate_secret_done": "Secret rotated",
+ "ui.admin.api_keys.list.save_scopes": "Save scopes",
"ui.admin.api_keys.list.table.actions": "ACTIONS",
"ui.admin.api_keys.list.table.client_id": "CLIENT ID",
"ui.admin.api_keys.list.table.last_used": "LAST USED",
@@ -2993,6 +3365,33 @@ const Map enStrings = {
"ui.admin.audit.table.time": "TIME",
"ui.admin.audit.target": "Target · {{target}}",
"ui.admin.audit.title": "Audit Logs",
+ "ui.admin.auth_guard.checker.allowed": "Access ALLOWED",
+ "ui.admin.auth_guard.checker.allowed_description":
+ "The subject has access to the requested resource, including inherited permissions.",
+ "ui.admin.auth_guard.checker.check": "Check permission",
+ "ui.admin.auth_guard.checker.checking": "Checking...",
+ "ui.admin.auth_guard.checker.denied": "Access DENIED",
+ "ui.admin.auth_guard.checker.denied_description":
+ "The subject does not have access to the requested resource.",
+ "ui.admin.auth_guard.checker.description":
+ "Check in real time whether a subject has access to a resource through Ory Keto.",
+ "ui.admin.auth_guard.checker.namespace": "Namespace",
+ "ui.admin.auth_guard.checker.namespace.label": "Namespace",
+ "ui.admin.auth_guard.checker.namespace.relying_party": "RelyingParty",
+ "ui.admin.auth_guard.checker.namespace.system": "System",
+ "ui.admin.auth_guard.checker.namespace.tenant": "Tenant",
+ "ui.admin.auth_guard.checker.namespace.tenant_group": "TenantGroup",
+ "ui.admin.auth_guard.checker.object_id": "Object ID",
+ "ui.admin.auth_guard.checker.object_id_placeholder": "Tenant UUID, etc.",
+ "ui.admin.auth_guard.checker.relation": "Relation",
+ "ui.admin.auth_guard.checker.relation_placeholder": "view, manage, admins...",
+ "ui.admin.auth_guard.checker.subject": "Subject (User:ID)",
+ "ui.admin.auth_guard.checker.subject_placeholder":
+ "User:uuid or Namespace:ID#Relation",
+ "ui.admin.auth_guard.checker.title": "ReBAC permission checker",
+ "ui.admin.auth_guard.subtitle":
+ "Verify admin privileges and ReBAC relationships against the policy engine.",
+ "ui.admin.auth_guard.title": "Auth Guard",
"ui.admin.brand": "Brand",
"ui.admin.dev_role_switcher": "🛠 DEV Role Switcher",
"ui.admin.dev_role_switcher_real": "Use real role",
@@ -3016,6 +3415,9 @@ const Map enStrings = {
"ui.admin.groups.form.unit_level_placeholder": "Unit Level Placeholder",
"ui.admin.groups.import_csv": "Import Csv",
"ui.admin.groups.list.title": "User Groups",
+ "ui.admin.groups.members.add_modal_title": "Add Member to Group",
+ "ui.admin.groups.members.move_modal_title": "Move Department",
+ "ui.admin.groups.members.table.actions": "ACTIONS",
"ui.admin.groups.members.table.email": "Email",
"ui.admin.groups.members.table.name": "Name",
"ui.admin.groups.members.table.remove": "Remove",
@@ -3024,6 +3426,51 @@ const Map enStrings = {
"ui.admin.groups.table.name": "NAME",
"ui.admin.header.plane": "Admin Plane",
"ui.admin.header.subtitle": "Manage tenants, policies, and operators",
+ "ui.admin.integrity.check.duplicate_tenant_slugs.title":
+ "Duplicate tenant slug",
+ "ui.admin.integrity.check.orphan_tenant_parents.title":
+ "Orphan tenant parents",
+ "ui.admin.integrity.check.orphan_user_login_id_tenants.title":
+ "Orphan user login ID tenants",
+ "ui.admin.integrity.check.orphan_user_login_id_users.title":
+ "Orphan user login ID users",
+ "ui.admin.integrity.check.orphan_user_tenant_memberships.title":
+ "Orphan user tenant memberships",
+ "ui.admin.integrity.fetch_error":
+ "Unable to load the final integrity check result.",
+ "ui.admin.integrity.forbidden.title": "Access denied",
+ "ui.admin.integrity.kicker": "System",
+ "ui.admin.integrity.loading": "Loading data integrity report...",
+ "ui.admin.integrity.orphan_login_ids.delete": "Delete selected",
+ "ui.admin.integrity.orphan_login_ids.title": "Orphan Login ID Cleanup",
+ "ui.admin.integrity.read_model.title": "Read model integrity",
+ "ui.admin.integrity.reason.deleted_tenant": "Deleted tenant",
+ "ui.admin.integrity.reason.deleted_user": "Deleted user",
+ "ui.admin.integrity.reason.missing_tenant": "Missing tenant",
+ "ui.admin.integrity.reason.missing_user": "Missing user",
+ "ui.admin.integrity.recheck.run": "Run again",
+ "ui.admin.integrity.recheck.running": "Checking",
+ "ui.admin.integrity.section.tenant_integrity": "Tenant integrity",
+ "ui.admin.integrity.section.user_integrity": "User integrity",
+ "ui.admin.integrity.status.fail": "Failed",
+ "ui.admin.integrity.status.pass": "Passed",
+ "ui.admin.integrity.status.warning": "Warning",
+ "ui.admin.integrity.subtitle":
+ "Review integrity status and inspect checks across the admin data model.",
+ "ui.admin.integrity.summary.checked_at": "Checked at",
+ "ui.admin.integrity.summary.failures": "Failures",
+ "ui.admin.integrity.summary.failures_text": "Failures {{count}}",
+ "ui.admin.integrity.summary.passed": "Passed",
+ "ui.admin.integrity.summary.title": "Final integrity check",
+ "ui.admin.integrity.summary.total_checks": "Checks",
+ "ui.admin.integrity.table.field": "Field",
+ "ui.admin.integrity.table.login_id": "Login ID",
+ "ui.admin.integrity.table.reason": "Reason",
+ "ui.admin.integrity.table.select": "Select",
+ "ui.admin.integrity.table.select_item": "Select {{loginId}}",
+ "ui.admin.integrity.table.tenant": "Tenant",
+ "ui.admin.integrity.table.user": "User",
+ "ui.admin.integrity.title": "Data Integrity Check",
"ui.admin.nav.api_keys": "API Keys",
"ui.admin.nav.audit_logs": "Audit Logs",
"ui.admin.nav.auth_guard": "Auth Guard",
@@ -3039,6 +3486,9 @@ const Map enStrings = {
"ui.admin.org.import_btn": "Import",
"ui.admin.org.import_title": "Bulk Organization Import",
"ui.admin.org.start_import": "Start Import",
+ "ui.admin.overview.chart.description":
+ "Check the graph by all or selected organizations.",
+ "ui.admin.overview.chart.title": "Login request status by company and app",
"ui.admin.overview.kicker": "Global Overview",
"ui.admin.overview.playbook.title": "Admin playbook",
"ui.admin.overview.quick_links.add_tenant": "Tenant Add",
@@ -3050,6 +3500,7 @@ const Map enStrings = {
"ui.admin.overview.summary.oidc_clients": "OIDC Clients",
"ui.admin.overview.summary.policy_gate": "Policy Gate",
"ui.admin.overview.summary.total_tenants": "Total Tenants",
+ "ui.admin.overview.summary.total_users": "Total Users",
"ui.admin.overview.title": "Tenant-independent control plane",
"ui.admin.profile.manageable_tenants": "Manageable Tenants",
"ui.admin.role.rp_admin": "RP ADMIN",
@@ -3081,11 +3532,19 @@ const Map enStrings = {
"ui.admin.tenants.create.form.name": "Tenant name",
"ui.admin.tenants.create.form.name_placeholder": "Enter tenant name",
"ui.admin.tenants.create.form.parent": "Parent",
+ "ui.admin.tenants.create.form.pick_hanmac_parent": "Pick from Hanmac Family",
+ "ui.admin.tenants.create.form.pick_other_parent": "Pick another tenant",
+ "ui.admin.tenants.create.form.root_tenant": "Create as top-level tenant",
"ui.admin.tenants.create.form.slug": "Slug",
"ui.admin.tenants.create.form.slug_placeholder": "tenant-slug",
"ui.admin.tenants.create.form.status": "Status",
"ui.admin.tenants.create.form.type": "Type",
"ui.admin.tenants.create.memo.title": "Policy Memo",
+ "ui.admin.tenants.create.parent_context.general": "General child tenant",
+ "ui.admin.tenants.create.parent_context.hanmac": "Hanmac Family child tenant",
+ "ui.admin.tenants.create.parent_context.pick_required":
+ "Parent tenant selection required",
+ "ui.admin.tenants.create.parent_context.root": "Top-level tenant",
"ui.admin.tenants.create.profile.title": "Tenant Profile",
"ui.admin.tenants.create.title": "Tenant Add",
"ui.admin.tenants.csv_template": "Template",
@@ -3098,6 +3557,7 @@ const Map enStrings = {
"ui.admin.tenants.detail.tab_permissions": "Permissions",
"ui.admin.tenants.detail.tab_profile": "Profile",
"ui.admin.tenants.detail.tab_schema": "Tab Schema",
+ "ui.admin.tenants.detail.tab_worksmobile": "Worksmobile",
"ui.admin.tenants.detail.title": "Details",
"ui.admin.tenants.domain_conflict.description": "",
"ui.admin.tenants.domain_conflict.title": "Domain conflict",
@@ -3105,23 +3565,36 @@ const Map enStrings = {
"ui.admin.tenants.export_without_ids": "Export without UUIDs",
"ui.admin.tenants.import": "Import",
"ui.admin.tenants.import_preview.candidates": "Candidates",
- "ui.admin.tenants.import_preview.confirm": "Run import",
+ "ui.admin.tenants.import_preview.confirm": "Confirm Import",
+ "ui.admin.tenants.import_preview.create_new": "Create New",
"ui.admin.tenants.import_preview.create_new_reset":
"Create new (reset ID/slug)",
+ "ui.admin.tenants.import_preview.csv_parents": "CSV Parents",
"ui.admin.tenants.import_preview.external_id": "External ID",
- "ui.admin.tenants.import_preview.match": "Match",
- "ui.admin.tenants.import_preview.no_candidates": "No candidates",
+ "ui.admin.tenants.import_preview.fixed_id": "Fixed ID",
+ "ui.admin.tenants.import_preview.match": "Matched Tenant",
+ "ui.admin.tenants.import_preview.no_candidates": "No matching tenants found.",
+ "ui.admin.tenants.import_preview.parent": "Parent",
+ "ui.admin.tenants.import_preview.parent_companies": "Parent Companies",
+ "ui.admin.tenants.import_preview.parent_company_groups":
+ "Parent Company Groups",
+ "ui.admin.tenants.import_preview.parent_organizations":
+ "Parent Organizations",
"ui.admin.tenants.import_preview.parent_unresolved": "Parent needs review",
"ui.admin.tenants.import_preview.slug_exists": "slug conflict",
- "ui.admin.tenants.import_preview.title": "Confirm CSV import",
+ "ui.admin.tenants.import_preview.title": "Import Preview",
"ui.admin.tenants.list.search_placeholder":
"Search tenant by name or slug...",
"ui.admin.tenants.list.select_placeholder": "Select a tenant",
+ "ui.admin.tenants.members.add_existing": "Assign Existing Member",
+ "ui.admin.tenants.members.create_new": "Create New Member",
"ui.admin.tenants.members.delete_selected": "Delete Selected",
"ui.admin.tenants.members.descendants": "Descendant Members",
"ui.admin.tenants.members.direct": "Direct Members",
"ui.admin.tenants.members.direct_label": "Direct",
"ui.admin.tenants.members.list_title": "Member Management",
+ "ui.admin.tenants.members.remove": "Exclude from Organization",
+ "ui.admin.tenants.members.table.actions": "ACTIONS",
"ui.admin.tenants.members.table.email": "EMAIL",
"ui.admin.tenants.members.table.name": "NAME",
"ui.admin.tenants.members.table.role": "ROLE",
@@ -3130,6 +3603,7 @@ const Map enStrings = {
"ui.admin.tenants.members.total": "Total",
"ui.admin.tenants.members.total_label": "Total",
"ui.admin.tenants.members.view_org_chart": "View Full Org Chart",
+ "ui.admin.tenants.members.view_profile": "View Profile",
"ui.admin.tenants.owners.add_button": "Add Owner",
"ui.admin.tenants.owners.already_owner": "Already Owner",
"ui.admin.tenants.owners.dialog_description":
@@ -3140,6 +3614,11 @@ const Map enStrings = {
"ui.admin.tenants.owners.table_email": "Email",
"ui.admin.tenants.owners.table_name": "Name",
"ui.admin.tenants.owners.title": "Tenant Owners",
+ "ui.admin.tenants.parent.company_only": "Companies and groups only",
+ "ui.admin.tenants.parent.local_search_placeholder":
+ "Search tenant name or slug",
+ "ui.admin.tenants.parent.pick_tenant": "Pick tenant",
+ "ui.admin.tenants.parent.search_placeholder": "Search by name or slug",
"ui.admin.tenants.profile.allowed_domains": "Allowed Domains",
"ui.admin.tenants.profile.allowed_domains_help":
"Users with these email domains will be automatically assigned to this tenant.",
@@ -3150,12 +3629,14 @@ const Map enStrings = {
"ui.admin.tenants.profile.form.parent_help":
"Select a parent tenant if this is a subsidiary or sub-organization.",
"ui.admin.tenants.profile.name": "Tenant Name",
+ "ui.admin.tenants.profile.org_unit_type": "Organization detail type",
"ui.admin.tenants.profile.slug": "Slug",
"ui.admin.tenants.profile.status": "Status",
"ui.admin.tenants.profile.subtitle":
"Slug and status changes are applied immediately.",
"ui.admin.tenants.profile.title": "Tenant Profile",
"ui.admin.tenants.profile.type": "Type",
+ "ui.admin.tenants.profile.visibility": "Visibility",
"ui.admin.tenants.registry.title": "Tenant registry",
"ui.admin.tenants.schema.add_field": "Add Field",
"ui.admin.tenants.schema.field.admin_only": "Admin Only",
@@ -3178,11 +3659,15 @@ const Map enStrings = {
"Regex Pattern (Optional)",
"ui.admin.tenants.schema.save": "Save Schema",
"ui.admin.tenants.schema.title": "User Schema Extension",
+ "ui.admin.tenants.scope.active": "{{name}} descendants",
+ "ui.admin.tenants.scope.pick": "Select parent scope",
+ "ui.admin.tenants.seed_badge": "Seed",
"ui.admin.tenants.sub.add": "Add",
"ui.admin.tenants.sub.add_dialog_desc":
"Select a tenant to add as a sub-tenant.",
"ui.admin.tenants.sub.add_dialog_title": "Add Sub-tenant",
"ui.admin.tenants.sub.add_existing": "Add Existing Tenant",
+ "ui.admin.tenants.sub.export": "Export",
"ui.admin.tenants.sub.manage": "Manage",
"ui.admin.tenants.sub.no_candidates": "No available tenants to add.",
"ui.admin.tenants.sub.search_placeholder": "Search...",
@@ -3193,6 +3678,7 @@ const Map enStrings = {
"ui.admin.tenants.sub.title": "Sub-tenants ({{count}})",
"ui.admin.tenants.sub.tree_search_placeholder": "Search in tree...",
"ui.admin.tenants.table.actions": "ACTIONS",
+ "ui.admin.tenants.table.created": "CREATED",
"ui.admin.tenants.table.id": "ID",
"ui.admin.tenants.table.members": "Members",
"ui.admin.tenants.table.name": "NAME",
@@ -3201,8 +3687,48 @@ const Map enStrings = {
"ui.admin.tenants.table.type": "TYPE",
"ui.admin.tenants.table.updated": "UPDATED",
"ui.admin.tenants.title": "Tenant Registry",
+ "ui.admin.tenants.view.hierarchy": "Hierarchy",
+ "ui.admin.tenants.view.list": "List",
+ "ui.admin.tenants.view.table": "Table",
+ "ui.admin.tenants.view.tree": "Tree",
"ui.admin.tenants.view_org_chart": "View Full Org Chart",
+ "ui.admin.tenants.worksmobile.compare": "Baron / Works Comparison",
+ "ui.admin.tenants.worksmobile.compare_description":
+ "Users show entries that exist only in Baron or only in WORKS by default.",
+ "ui.admin.tenants.worksmobile.compare_groups": "Organizations / Groups",
+ "ui.admin.tenants.worksmobile.compare_users": "Users",
+ "ui.admin.tenants.worksmobile.dry_run": "Backfill Dry-run",
+ "ui.admin.tenants.worksmobile.forbidden":
+ "You do not have permission to manage the Worksmobile integration.",
+ "ui.admin.tenants.worksmobile.initial_password_csv": "Initial Password CSV",
+ "ui.admin.tenants.worksmobile.recent_jobs": "Recent Jobs",
+ "ui.admin.tenants.worksmobile.refresh": "Refresh",
+ "ui.admin.tenants.worksmobile.single_sync": "Single-item Sync",
+ "ui.admin.tenants.worksmobile.single_sync_description":
+ "Create an organization or user sync job using a Baron UUID.",
+ "ui.admin.tenants.worksmobile.subtitle":
+ "Review Hanmac Family Directory sync status for organizations and users, and retry failed jobs.",
+ "ui.admin.tenants.worksmobile.sync_orgunit": "Organization Sync",
+ "ui.admin.tenants.worksmobile.sync_user": "User Sync",
+ "ui.admin.tenants.worksmobile.title": "Worksmobile Integration",
"ui.admin.title": "Admin Control",
+ "ui.admin.user_projection.actions.reconcile": "Re-sync",
+ "ui.admin.user_projection.actions.reset": "Reset and rebuild",
+ "ui.admin.user_projection.card.description":
+ "Current user read model state referenced by backend DB statistics.",
+ "ui.admin.user_projection.card.title": "Kratos users projection",
+ "ui.admin.user_projection.forbidden.title": "Access denied",
+ "ui.admin.user_projection.loading": "Loading user projection data...",
+ "ui.admin.user_projection.status.failed": "failed",
+ "ui.admin.user_projection.status.not_ready": "not ready",
+ "ui.admin.user_projection.status.ready": "ready",
+ "ui.admin.user_projection.subtitle":
+ "Review and sync the Kratos user read model.",
+ "ui.admin.user_projection.summary.last_synced": "Last synced",
+ "ui.admin.user_projection.summary.projected_users": "Projected users",
+ "ui.admin.user_projection.summary.status": "Status",
+ "ui.admin.user_projection.summary.updated_at": "Updated at",
+ "ui.admin.user_projection.title": "User Projection Management",
"ui.admin.users.bulk.acknowledge_warning":
"I acknowledge the warning and will proceed.",
"ui.admin.users.bulk.create_missing_tenant": "Create new",
@@ -3211,10 +3737,12 @@ const Map enStrings = {
"ui.admin.users.bulk.move_group": "Bulk Tenant Move",
"ui.admin.users.bulk.move_title": "Bulk User Move",
"ui.admin.users.bulk.no_department": "No Department",
+ "ui.admin.users.bulk.permission_placeholder": "Select permission",
"ui.admin.users.bulk.schema_warning": "Schema Compatibility Warning",
"ui.admin.users.bulk.select_group": "Select Target Tenant",
"ui.admin.users.bulk.selected_count": "{{count}} users selected",
"ui.admin.users.bulk.start_upload": "Start Upload",
+ "ui.admin.users.bulk.status_placeholder": "Select status",
"ui.admin.users.bulk.tenant_resolution": "Tenant mapping",
"ui.admin.users.bulk.title": "Bulk Actions",
"ui.admin.users.create.account.title": "Account Information",
@@ -3238,6 +3766,8 @@ const Map enStrings = {
"ui.admin.users.create.form.password_placeholder": "********",
"ui.admin.users.create.form.phone": "Phone number",
"ui.admin.users.create.form.phone_placeholder": "010-1234-5678",
+ "ui.admin.users.create.form.picker_description":
+ "Search and select a tenant.",
"ui.admin.users.create.form.position": "Position",
"ui.admin.users.create.form.position_placeholder": "e.g. Senior",
"ui.admin.users.create.form.role": "Role",
@@ -3248,6 +3778,7 @@ const Map enStrings = {
"Initial Password Generated",
"ui.admin.users.create.submit": "User Create",
"ui.admin.users.create.title": "User Add",
+ "ui.admin.users.csv_template": "Download Template",
"ui.admin.users.detail.back": "Back",
"ui.admin.users.detail.breadcrumb.section": "Users",
"ui.admin.users.detail.contact_title": "ui.admin.users.detail.contact_title",
@@ -3495,6 +4026,7 @@ const Map enStrings = {
"ui.admin.users.list.breadcrumb.list": "List",
"ui.admin.users.list.breadcrumb.section": "Users",
"ui.admin.users.list.bulk_import": "Bulk Import",
+ "ui.admin.users.list.change_status": "Change {{name}} status",
"ui.admin.users.list.columns.title": "Column Settings",
"ui.admin.users.list.empty": "No users found.",
"ui.admin.users.list.fetch_error": "Failed to load the user list.",
@@ -3502,6 +4034,7 @@ const Map enStrings = {
"ui.admin.users.list.registry.count": "{{count}} users loaded.",
"ui.admin.users.list.registry.title": "User Registry",
"ui.admin.users.list.search_placeholder": "Search Placeholder",
+ "ui.admin.users.list.status_select": "{{name}} status",
"ui.admin.users.list.subtitle": "Browse and manage registered users.",
"ui.admin.users.list.table.actions": "ACTIONS",
"ui.admin.users.list.table.created": "CREATED",
@@ -3734,12 +4267,19 @@ const Map enStrings = {
"ui.common.select_placeholder": "Select Placeholder",
"ui.common.show_more": "Show More",
"ui.common.status.active": "Active",
+ "ui.common.status.archived": "Archived",
+ "ui.common.status.baron_guest": "Baron Guest",
"ui.common.status.blocked": "ui.common.status.blocked",
+ "ui.common.status.extended_leave": "Extended Leave",
"ui.common.status.failure": "Failure",
"ui.common.status.inactive": "Inactive",
+ "ui.common.status.leave_of_absence": "Leave of absence",
"ui.common.status.ok": "Ok",
"ui.common.status.pending": "Pending",
+ "ui.common.status.preboarding": "Preboarding",
"ui.common.status.success": "Success",
+ "ui.common.status.suspended": "Suspended",
+ "ui.common.status.temporary_leave": "Temporary Leave",
"ui.common.success": "Success",
"ui.common.theme_dark": "Dark",
"ui.common.theme_light": "Light",
@@ -3954,8 +4494,24 @@ const Map enStrings = {
"ui.dev.clients.untitled": "Untitled",
"ui.dev.console_title": "Developer Console",
"ui.dev.dashboard.badge.consent_guard": "Consent guard ready",
+ "ui.dev.dashboard.badge.oidc": "OIDC operations",
"ui.dev.dashboard.badge.policy_toggle": "Policy toggle enabled",
+ "ui.dev.dashboard.badge.registry": "RP registry",
"ui.dev.dashboard.badge.rp_synced": "RP registry synced",
+ "ui.dev.dashboard.chart.aria": "RP request overview",
+ "ui.dev.dashboard.chart.filter_all": "All",
+ "ui.dev.dashboard.chart.period_day": "Day",
+ "ui.dev.dashboard.chart.period_month": "Month",
+ "ui.dev.dashboard.chart.period_week": "Week",
+ "ui.dev.dashboard.chart.series": "Login {{login}} / Users {{subjects}}",
+ "ui.dev.dashboard.chart.title": "Login requests by application",
+ "ui.dev.dashboard.chart.x_axis": "X-axis: Period",
+ "ui.dev.dashboard.chart.y_axis": "Y-axis: Login requests",
+ "ui.dev.dashboard.distribution.headless_hint":
+ "{{count}} with Headless Login enabled",
+ "ui.dev.dashboard.distribution.pkce": "PKCE",
+ "ui.dev.dashboard.distribution.private": "Server side App",
+ "ui.dev.dashboard.distribution.title": "Application Distribution",
"ui.dev.dashboard.next.subtitle": "Ship the RP controls",
"ui.dev.dashboard.next.title": "Next actions",
"ui.dev.dashboard.ops.card.consent_revoked": "Consent Revoked",
@@ -3966,16 +4522,26 @@ const Map enStrings = {
"ui.dev.dashboard.ops.tag.consent": "Consent grants",
"ui.dev.dashboard.ops.tag.rp_status": "RP status",
"ui.dev.dashboard.ops.title": "Ops board",
+ "ui.dev.dashboard.quick_links.create_button": "Create RP",
+ "ui.dev.dashboard.quick_links.new_client": "New RP",
+ "ui.dev.dashboard.quick_links.title": "Quick links",
"ui.dev.dashboard.ready_badge": "devfront ready",
+ "ui.dev.dashboard.recent.title": "My Applications",
"ui.dev.dashboard.stack.notes": "Setup notes",
"ui.dev.dashboard.stack.subtitle": "Devfront baseline",
"ui.dev.dashboard.stack.title": "Stack readiness",
+ "ui.dev.dashboard.summary.active_clients": "Active RPs",
+ "ui.dev.dashboard.summary.active_sessions": "Active sessions",
+ "ui.dev.dashboard.summary.auth_failures_24h": "24h auth failures",
+ "ui.dev.dashboard.summary.total_clients": "Total RPs",
+ "ui.dev.dashboard.title": "Dashboard",
"ui.dev.env_badge": "Env: dev",
"ui.dev.header.plane": "Dev Plane",
"ui.dev.header.subtitle": "Manage your applications",
"ui.dev.nav.clients": "Connected Application",
"ui.dev.nav.developer_request": "Developer Access Request",
"ui.dev.nav.logout": "Logout",
+ "ui.dev.nav.overview": "Overview",
"ui.dev.profile.basic.email": "Email",
"ui.dev.profile.basic.id": "Account ID",
"ui.dev.profile.basic.name": "Name",
@@ -3987,6 +4553,7 @@ const Map enStrings = {
"ui.dev.profile.menu_title": "Account",
"ui.dev.profile.org.company_code": "Company Code",
"ui.dev.profile.org.tenant": "Tenant",
+ "ui.dev.profile.org.tenant_slug": "Tenant slug",
"ui.dev.profile.org.title": "Organization Info",
"ui.dev.profile.role.current": "Current Role",
"ui.dev.profile.role.description":
@@ -4042,6 +4609,19 @@ const Map enStrings = {
"ui.dev.tenant.workspace_desc":
"Select and save the current working tenant to change API request context.",
"ui.dev.welcome.btn_request": "New Request",
+ "ui.shell.nav.logout": "Logout",
+ "ui.shell.nav.profile": "My Profile",
+ "ui.shell.profile.menu_aria": "Open account menu",
+ "ui.shell.profile.menu_title": "Account",
+ "ui.shell.profile.unknown_email": "unknown@example.com",
+ "ui.shell.profile.unknown_name": "Unknown User",
+ "ui.shell.session.active": "Session active",
+ "ui.shell.session.auto_extend": "Session expiry",
+ "ui.shell.session.disabled": "Session expiry disabled",
+ "ui.shell.session.expired": "Session expired",
+ "ui.shell.session.expiring": "Expiring soon: {{minutes}}m {{seconds}}s left",
+ "ui.shell.session.remaining": "Expires in {{minutes}}m {{seconds}}s",
+ "ui.shell.session.unknown": "Unknown",
"ui.userfront.app_label.admin_console": "Admin Console",
"ui.userfront.app_label.baron": "Baron",
"ui.userfront.app_label.dev_console": "Dev Console",
@@ -4121,6 +4701,7 @@ const Map enStrings = {
"ui.userfront.login.unregistered.title": "Account not found",
"ui.userfront.login.verification.action_label": "Done",
"ui.userfront.login.verification.action_label_close": "Close Window",
+ "ui.userfront.login.verification.action_label_remote": "Go to sign-in window",
"ui.userfront.login.verification.page_title": "Sign-in approval",
"ui.userfront.login.verification.title": "Approval complete",
"ui.userfront.login.verification.title_pending": "Checking approval",