@aiquants/auth-react-router 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.d.mts +70 -5
- package/dist/admin.d.ts +70 -5
- package/dist/admin.js +1 -1
- package/dist/admin.js.map +1 -1
- package/dist/admin.mjs +1 -1
- package/dist/admin.mjs.map +1 -1
- package/dist/server.d.mts +8 -2
- package/dist/server.d.ts +8 -2
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1 -1
- package/dist/server.mjs.map +1 -1
- package/package.json +7 -7
- package/src/admin/labels.ts +84 -0
- package/src/admin/server.ts +137 -7
- package/src/admin/views.tsx +119 -29
- package/src/server/authenticate-in-loader.ts +1 -1
- package/src/server/google-strategy.ts +1 -1
- package/src/server/ports.ts +8 -2
package/dist/admin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/admin/labels.ts","../src/admin/server.ts","../src/admin/ui.tsx","../src/admin/views.tsx"],"sourcesContent":["/**\n * Labels and i18n strings for the user administration UI.\n * ユーザー管理 UI で使用するラベルと i18n 文字列の定義。\n *\n * パッケージ既定は **英語** ([defaultUserAdminLabels])。日本語で使う場合はアプリ側が\n * [jaUserAdminLabels] を明示的に注入する (認可管理 `@aiquants/authz-react-router` と同じ規約)。\n * UI 文字列はすべて本モジュールに集約し、コンポーネント側へ直書きしないこと — 直書きすると\n * 日本語以外の利用者がその箇所を差し替えられなくなる。\n */\n\nexport type UserAdminLabels = {\n title: string\n /** シェル (タブ枠) の見出し。 */\n heading: string\n tabs: {\n users: string\n groups: string\n groupMembers: string\n allowlist: string\n }\n usersView: {\n title: string\n searchPlaceholder: string\n addUser: string\n editUser: string\n id: string\n displayName: string\n email: string\n status: string\n active: string\n inactive: string\n actions: string\n edit: string\n activate: string\n deactivate: string\n empty: string\n save: string\n cancel: string\n }\n groupsView: {\n title: string\n searchPlaceholder: string\n addGroup: string\n editGroup: string\n id: string\n groupKey: string\n name: string\n description: string\n memberCount: string\n status: string\n actions: string\n edit: string\n delete: string\n confirmDelete: string\n noDescription: string\n groupKeyPlaceholder: string\n namePlaceholder: string\n empty: string\n save: string\n cancel: string\n }\n groupMembersView: {\n selectGroup: string\n selectPrompt: string\n actionAdd: string\n actionRemove: string\n }\n allowlistView: {\n title: string\n addEntry: string\n pattern: string\n type: string\n emailType: string\n domainType: string\n /** 一覧のバッジ表示 (短縮形)。 */\n emailTypeShort: string\n domainTypeShort: string\n description: string\n actions: string\n /** 変更不能な供給元 (環境変数など) 由来の行に出す表示。 */\n readOnly: string\n delete: string\n empty: string\n save: string\n cancel: string\n }\n}\n\n/**\n * Package default labels (English).\n * パッケージ既定ラベル (英語)。\n */\nexport const defaultUserAdminLabels: UserAdminLabels = {\n title: \"Users & Groups\",\n heading: \"👤 User administration\",\n tabs: {\n users: \"👤 Users\",\n groups: \"👥 Groups\",\n groupMembers: \"🔗 Members\",\n allowlist: \"✉️ Allowlist\",\n },\n usersView: {\n title: \"Users\",\n searchPlaceholder: \"Search by name or email...\",\n addUser: \"+ Add user\",\n editUser: \"Edit user\",\n id: \"ID\",\n displayName: \"Name\",\n email: \"Email\",\n status: \"Status\",\n active: \"Active\",\n inactive: \"Inactive\",\n actions: \"Actions\",\n edit: \"Edit\",\n activate: \"Activate\",\n deactivate: \"Deactivate\",\n empty: \"No matching users\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupsView: {\n title: \"Groups\",\n searchPlaceholder: \"Search by group name or key...\",\n addGroup: \"+ Add group\",\n editGroup: \"Edit group\",\n id: \"ID\",\n groupKey: \"Group key\",\n name: \"Name\",\n description: \"Description\",\n memberCount: \"Members\",\n status: \"Status\",\n actions: \"Actions\",\n edit: \"Edit\",\n delete: \"Delete\",\n confirmDelete: \"Delete this group?\",\n noDescription: \"No description\",\n groupKeyPlaceholder: \"e.g. dev-team\",\n namePlaceholder: \"e.g. Development team\",\n empty: \"No groups registered\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupMembersView: {\n selectGroup: \"Select a group\",\n selectPrompt: \"Select a group to manage its members\",\n actionAdd: \"Add\",\n actionRemove: \"Remove\",\n },\n allowlistView: {\n title: \"Sign-in allowlist\",\n addEntry: \"+ Add pattern\",\n pattern: \"Pattern (e.g. @example.com or user@example.com)\",\n type: \"Type\",\n emailType: \"Exact email match\",\n domainType: \"Domain match\",\n emailTypeShort: \"Email\",\n domainTypeShort: \"Domain\",\n description: \"Description\",\n actions: \"Actions\",\n readOnly: \"Read-only\",\n delete: \"Delete\",\n empty: \"No allowlist entries registered\",\n save: \"Add\",\n cancel: \"Cancel\",\n },\n}\n\n/**\n * Standard Japanese labels for user administration (opt-in).\n * ユーザー管理の標準日本語ラベル (アプリ側で明示注入して使う)。\n */\nexport const jaUserAdminLabels: UserAdminLabels = {\n title: \"ユーザー・グループ管理\",\n heading: \"👤 ユーザー管理\",\n tabs: {\n users: \"👤 ユーザー一覧\",\n groups: \"👥 ユーザーグループ\",\n groupMembers: \"🔗 グループメンバー\",\n allowlist: \"✉️ アクセス許可リスト\",\n },\n usersView: {\n title: \"ユーザー管理\",\n searchPlaceholder: \"表示名や Email で検索...\",\n addUser: \"+ ユーザー追加\",\n editUser: \"ユーザーの編集\",\n id: \"ID\",\n displayName: \"表示名\",\n email: \"メールアドレス\",\n status: \"ステータス\",\n active: \"有効\",\n inactive: \"無効\",\n actions: \"操作\",\n edit: \"編集\",\n activate: \"有効化\",\n deactivate: \"無効化\",\n empty: \"該当するユーザーが見つかりません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupsView: {\n title: \"ユーザーグループ管理\",\n searchPlaceholder: \"グループ名やキーで検索...\",\n addGroup: \"+ グループ作成\",\n editGroup: \"グループの編集\",\n id: \"ID\",\n groupKey: \"グループキー\",\n name: \"グループ名\",\n description: \"説明\",\n memberCount: \"メンバー数\",\n status: \"ステータス\",\n actions: \"操作\",\n edit: \"編集\",\n delete: \"削除\",\n confirmDelete: \"このグループを削除してもよろしいですか?\",\n noDescription: \"説明なし\",\n groupKeyPlaceholder: \"例: dev-team\",\n namePlaceholder: \"例: 開発チーム\",\n empty: \"グループが登録されていません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupMembersView: {\n selectGroup: \"グループ選択\",\n selectPrompt: \"グループを選択してください\",\n actionAdd: \"追加\",\n actionRemove: \"除外\",\n },\n allowlistView: {\n title: \"アクセス許可リスト (Allowlist)\",\n addEntry: \"+ 許可パターン追加\",\n pattern: \"パターン (例: @example.com または user@example.com)\",\n type: \"種別\",\n emailType: \"メールアドレス完全一致\",\n domainType: \"ドメイン一致\",\n emailTypeShort: \"Email 完全一致\",\n domainTypeShort: \"ドメイン一致\",\n description: \"説明 / 備考\",\n actions: \"操作\",\n readOnly: \"変更不可\",\n delete: \"削除\",\n empty: \"アロワリストエントリが登録されていません\",\n save: \"登録\",\n cancel: \"キャンセル\",\n },\n}\n\n/** セクション単位の部分上書き。 */\nexport type PartialUserAdminLabels = {\n title?: string\n heading?: string\n tabs?: Partial<UserAdminLabels[\"tabs\"]>\n usersView?: Partial<UserAdminLabels[\"usersView\"]>\n groupsView?: Partial<UserAdminLabels[\"groupsView\"]>\n groupMembersView?: Partial<UserAdminLabels[\"groupMembersView\"]>\n allowlistView?: Partial<UserAdminLabels[\"allowlistView\"]>\n}\n\n/**\n * Shallow per-section merge over the English defaults.\n * 英語既定に対してセクション単位で浅くマージする処理。\n */\nexport function resolveUserAdminLabels(over?: PartialUserAdminLabels): UserAdminLabels {\n const d = defaultUserAdminLabels\n if (!over) return d\n return {\n title: over.title ?? d.title,\n heading: over.heading ?? d.heading,\n tabs: { ...d.tabs, ...over.tabs },\n usersView: { ...d.usersView, ...over.usersView },\n groupsView: { ...d.groupsView, ...over.groupsView },\n groupMembersView: { ...d.groupMembersView, ...over.groupMembersView },\n allowlistView: { ...d.allowlistView, ...over.allowlistView },\n }\n}\n","import type { AuthAllowlistEntry, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport { type PartialUserAdminLabels, resolveUserAdminLabels } from \"./labels\"\n\n/**\n * Server store interface for managing users, groups, memberships, and allowlists.\n * ユーザー・グループ・所属・アロワリストを管理するサーバー層ストアのインターフェース。\n */\nexport type UserAdminStore = {\n listUsers: () => Promise<AuthUser[]>\n createUser: (data: { displayName: string; email: string }) => Promise<AuthUser>\n updateUser: (id: number, data: { displayName: string; email: string }) => Promise<AuthUser>\n toggleUserActive: (id: number, isActive: boolean) => Promise<AuthUser>\n\n listGroups: () => Promise<AuthGroup[]>\n createGroup: (data: { groupKey: string; name: string; description?: string }) => Promise<AuthGroup>\n updateGroup: (id: number, data: { name: string; description?: string }) => Promise<AuthGroup>\n deleteGroup: (id: number) => Promise<void>\n\n listGroupMembers: () => Promise<AuthGroupMember[]>\n addGroupMember: (groupId: number, userId: number) => Promise<AuthGroupMember>\n removeGroupMember: (groupId: number, userId: number) => Promise<void>\n\n listAllowlist: () => Promise<AuthAllowlistEntry[]>\n addAllowlistEntry: (data: { pattern: string; type: \"email\" | \"domain\"; description?: string }) => Promise<AuthAllowlistEntry>\n removeAllowlistEntry: (id: number) => Promise<void>\n}\n\n/**\n * Creates an in-memory UserAdminStore initialized with optional seed data.\n * シードデータで初期化されるメモリ上の UserAdminStore を作成する。\n */\nexport function createInMemoryUserAdminStore(initial?: { users?: AuthUser[]; groups?: AuthGroup[]; groupMembers?: AuthGroupMember[]; allowlist?: AuthAllowlistEntry[] }): UserAdminStore {\n const users: AuthUser[] = initial?.users ?? [\n { id: 1, email: \"admin@example.com\", displayName: \"システム管理者\", isActive: true },\n { id: 2, email: \"user1@example.com\", displayName: \"開発 太郎\", isActive: true },\n { id: 3, email: \"user2@example.com\", displayName: \"営業 花子\", isActive: true },\n ]\n let groups: AuthGroup[] = initial?.groups ?? [\n { id: 1, groupKey: \"dev-team\", name: \"開発チーム\", description: \"システム開発担当グループ\", isActive: true },\n { id: 2, groupKey: \"sales-dept\", name: \"営業部\", description: \"営業ソリューション担当\", isActive: true },\n ]\n let groupMembers: AuthGroupMember[] = initial?.groupMembers ?? [\n { id: 1, groupId: 1, userId: 2 },\n { id: 2, groupId: 2, userId: 3 },\n ]\n let allowlist: AuthAllowlistEntry[] = initial?.allowlist ?? [\n { id: 1, pattern: \"admin@example.com\", type: \"email\", description: \"初期管理者\" },\n { id: 2, pattern: \"@example.com\", type: \"domain\", description: \"社内ドメイン\" },\n ]\n\n return {\n async listUsers() {\n return [...users]\n },\n async createUser(data) {\n const newUser: AuthUser = {\n id: Math.max(...users.map((u) => u.id), 0) + 1,\n email: data.email,\n displayName: data.displayName,\n isActive: true,\n }\n users.push(newUser)\n return newUser\n },\n async updateUser(id, data) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], displayName: data.displayName, email: data.email }\n return users[idx]\n },\n async toggleUserActive(id, isActive) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], isActive }\n return users[idx]\n },\n\n async listGroups() {\n return [...groups]\n },\n async createGroup(data) {\n const newGroup: AuthGroup = {\n id: Math.max(...groups.map((g) => g.id), 0) + 1,\n groupKey: data.groupKey,\n name: data.name,\n description: data.description,\n isActive: true,\n }\n groups.push(newGroup)\n return newGroup\n },\n async updateGroup(id, data) {\n const idx = groups.findIndex((g) => g.id === id)\n if (idx === -1) throw new Error(`Group not found: ${id}`)\n groups[idx] = { ...groups[idx], name: data.name, description: data.description }\n return groups[idx]\n },\n async deleteGroup(id) {\n groups = groups.filter((g) => g.id !== id)\n groupMembers = groupMembers.filter((m) => m.groupId !== id)\n },\n\n async listGroupMembers() {\n return [...groupMembers]\n },\n async addGroupMember(groupId, userId) {\n const existing = groupMembers.find((m) => m.groupId === groupId && m.userId === userId)\n if (existing) return existing\n const newMember: AuthGroupMember = {\n id: Math.max(...groupMembers.map((m) => m.id), 0) + 1,\n groupId,\n userId,\n }\n groupMembers.push(newMember)\n return newMember\n },\n async removeGroupMember(groupId, userId) {\n groupMembers = groupMembers.filter((m) => !(m.groupId === groupId && m.userId === userId))\n },\n\n async listAllowlist() {\n return [...allowlist]\n },\n async addAllowlistEntry(data) {\n const newEntry: AuthAllowlistEntry = {\n id: Math.max(...allowlist.map((a) => a.id), 0) + 1,\n pattern: data.pattern,\n type: data.type,\n description: data.description,\n }\n allowlist.push(newEntry)\n return newEntry\n },\n async removeAllowlistEntry(id) {\n allowlist = allowlist.filter((a) => a.id !== id)\n },\n }\n}\n\n/**\n * The CRUD verb an operation performs. Deliberately a plain string union so this package stays\n * independent of any particular authorization library.\n * 操作が行う CRUD 種別。特定の認可ライブラリに依存しないよう素の文字列 union で表現する。\n */\n/**\n * The only route-args field this module reads. React Router's full `LoaderFunctionArgs` satisfies it, so\n * route wiring is unchanged — but declaring the minimum keeps callers (and tests) from fabricating fields\n * that are never looked at.\n * このモジュールが実際に読むルート引数の全て。React Router の完全な引数はこれを満たすため結線は不変。\n */\ntype RouteArgs = { request: Request }\n\nexport type UserAdminAccess = \"read\" | \"create\" | \"update\" | \"delete\"\n\n/**\n * Access guard the user-admin surface is mounted behind. Deny by throwing\n * (a `Response`/`redirect` or an `Error`); returning normally means \"allow\".\n *\n * ユーザー管理面をマウントする際の必須アクセスガード。拒否は throw で表現する\n * (`Response` / `redirect` / `Error`)。正常 return は「許可」を意味する。\n *\n * 操作ごとの CRUD 種別を受け取るため、呼び出し側は「削除だけ別権限」といった細分化ができる。\n * `intent` は監査ログ用の補助情報(認可判断は `action` で行うこと)。\n */\nexport type UserAdminGuards = {\n /**\n * 許可は「正常 return」、拒否は「throw」で表す。返り値は判定に使わない (`void` 契約)。\n * 述語のつもりで `false` を返す実装を書くと素通りするため、返り値型で明示的に禁じる。\n */\n requireAccess: (request: Request, context: { action: UserAdminAccess; intent: string }) => Promise<void> | void\n}\n\n/**\n * Maps each action intent to the CRUD verb it actually performs.\n * 各 intent が実際に行う操作の CRUD 種別への対応表。\n *\n * 割当の付与・解除は行の追加・削除であるため、認可管理 UI (`/authz`) の\n * `assignUserRole` = create / `removeUserRole` = delete と同じ粒度に揃える。\n */\nconst INTENT_ACCESS = new Map<string, UserAdminAccess>([\n [\"createUser\", \"create\"],\n [\"updateUser\", \"update\"],\n [\"toggleUserActive\", \"update\"],\n [\"createGroup\", \"create\"],\n [\"updateGroup\", \"update\"],\n [\"deleteGroup\", \"delete\"],\n [\"addGroupMember\", \"create\"],\n [\"removeGroupMember\", \"delete\"],\n [\"addAllowlistEntry\", \"create\"],\n [\"removeAllowlistEntry\", \"delete\"],\n])\n\n/**\n * Neutral per-resource permission summary the admin shell renders in its header badge.\n * 管理シェルのヘッダーバッジが描画する、リソース単位の中立な権限サマリ。\n *\n * この形はこのパッケージ自身の契約であり、特定の認可ライブラリの型を import しない\n * (`AGENTS.md` のクロスパッケージ・ハードコード禁止)。`canRead` / `canWrite` / `canDelete` を持つ\n * ビューであれば構造的にそのまま渡せる。\n */\nexport type UserAdminPermission = {\n canRead: boolean\n canWrite: boolean\n canDelete: boolean\n}\n\nexport type UserAdminAppConfig = {\n store: UserAdminStore\n /** セクション単位の部分上書き。既定は英語 (`defaultUserAdminLabels`)。 */\n labels?: PartialUserAdminLabels\n /**\n * Required — omitting a guard would expose the whole user directory and its write actions.\n * 必須。省略を許すとユーザー名簿と全更新操作が無防備に露出するため、型で強制する。\n */\n guards: UserAdminGuards\n /**\n * Per-request self-permission lookup for the header annotation. Resolving to a map of\n * {@link UserAdminPermission} shows the badge; rejecting shows an explicit \"unknown\" badge — never a\n * permissive one. Any authz library whose view carries `canRead` / `canWrite` / `canDelete` satisfies it.\n * ヘッダー注釈に自分の実効権限を表示するためのリクエスト別権限取得関数。失敗時は「権限不明」表示。\n */\n getMyPermissions?: (request: Request) => Promise<Record<string, UserAdminPermission | undefined>>\n /**\n * Display names for the resource keys the badge shows, e.g. `{ authz_admin: \"ユーザー管理 UI\" }`.\n * バッジに出すリソースキーの表示名。未指定のキーはキー文字列をそのまま表示。\n *\n * 表示名をこのパッケージ側に埋め込むと、別パッケージのリソースキーを名指しすることになる\n * (`AGENTS.md` のクロスパッケージ・ハードコード禁止)。中立な差し込み口として受け取る。\n */\n resourceLabels?: Record<string, string>\n}\n\n/**\n * Reads a required positive integer id from submitted form data.\n * 送信フォームから必須の正整数 ID を読み取る処理。\n *\n * @throws when the field is missing, non-numeric, or not a positive integer.\n * 欠落・非数値・正整数でない場合は throw。\n */\nfunction requireId(formData: FormData, field: string): number {\n const raw = formData.get(field)\n const n = Number(raw)\n // NaN / 小数 / 0 以下は不正な ID として弾く (Number(null) === 0 の取り違えも防ぐ)\n if (raw === null || String(raw).trim() === \"\" || !Number.isInteger(n) || n <= 0) {\n throw new Error(`Invalid or missing \"${field}\"`)\n }\n return n\n}\n\n/**\n * Reads a required non-empty trimmed string from submitted form data.\n * 送信フォームから必須の非空文字列 (トリム済み) を読み取る処理。\n *\n * @throws when the field is missing or blank. 欠落・空白のみの場合は throw。\n */\nfunction requireText(formData: FormData, field: string): string {\n const value = String(formData.get(field) ?? \"\").trim()\n if (value === \"\") throw new Error(`Invalid or missing \"${field}\"`)\n return value\n}\n\n/** Reads an optional trimmed string; blank becomes `undefined`. 任意文字列の読み取り (空は undefined)。 */\nfunction optionalText(formData: FormData, field: string): string | undefined {\n const value = String(formData.get(field) ?? \"\").trim()\n return value === \"\" ? undefined : value\n}\n\n/**\n * Creates React Router loader & action handler for user administration.\n * ユーザー管理用 React Router の loader および action ハンドラーを作成する。\n *\n * `config.guards` は必須。loader / action の双方が、処理を始める前にガードを通過させる。\n */\nexport function createUserAdminApp(config: UserAdminAppConfig) {\n const labels = resolveUserAdminLabels(config.labels)\n\n async function loader({ request }: RouteArgs) {\n // 読取ガード: 拒否は throw されるためここで処理が打ち切られる\n await config.guards.requireAccess(request, { action: \"read\", intent: \"loader\" })\n const url = new URL(request.url)\n const pathSegments = url.pathname.split(\"/\").filter(Boolean)\n const segment = pathSegments[pathSegments.length - 1] ?? \"users\"\n\n const [users, groups, groupMembers, allowlist, myPermissions] = await Promise.all([\n config.store.listUsers(),\n config.store.listGroups(),\n config.store.listGroupMembers(),\n config.store.listAllowlist(),\n // 取得できない場合は null (= 権限不明)。空マップに倒すと「付与ゼロ」と区別できなくなる\n config.getMyPermissions\n ? config.getMyPermissions(request).catch((e) => {\n console.error(\"[@aiquants/auth-react-router] getMyPermissions failed; rendering the permission badge as unknown:\", e)\n return null\n })\n : Promise.resolve(null),\n ])\n\n return {\n resourceLabels: config.resourceLabels,\n segment: [\"groups\", \"group_members\", \"allowlist\"].includes(segment) ? segment : \"users\",\n users,\n groups,\n groupMembers,\n allowlist,\n myPermissions,\n labels,\n }\n }\n\n async function action({ request }: RouteArgs) {\n // intent の判定に FormData が要るため、ここでだけ本文を先に読む。\n // 未知 intent は「何も実行しない」ため、ガード前に読んでも権限判断には影響しない。\n const formData = await request.formData()\n const intent = String(formData.get(\"intent\") ?? \"\")\n\n // 未知 intent はストアへ到達しない (ガードも走らせず 400 相当を返す)。\n // Map 参照のためプロトタイプ継承キー (\"constructor\" 等) を拾わない。\n const access = INTENT_ACCESS.get(intent)\n if (!access) return { ok: false, error: `Unknown intent: ${intent}` }\n\n // 操作ごとの CRUD 種別でガードする (削除は delete 権限が要る)\n await config.guards.requireAccess(request, { action: access, intent })\n\n try {\n return await dispatch(intent, formData)\n } catch (error) {\n // ガード拒否 (Response/redirect) はそのまま伝播させる。ストア由来の業務エラーだけ\n // {ok:false,error} に写して UI へ届ける (ロックアウト理由・重複パターン等)。\n if (error instanceof Response) throw error\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /** intent ごとの実処理。認可・入力検証を通過した後にのみ呼ばれる。 */\n async function dispatch(intent: string, formData: FormData) {\n if (intent === \"createUser\") {\n await config.store.createUser({ displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"updateUser\") {\n await config.store.updateUser(requireId(formData, \"userId\"), { displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"toggleUserActive\") {\n // 破壊側 (無効化) を既定にしない。明示的な \"true\"/\"false\" 以外は不正入力として弾く。\n const rawActive = String(formData.get(\"isActive\") ?? \"\")\n if (rawActive !== \"true\" && rawActive !== \"false\") throw new Error('Invalid or missing \"isActive\"')\n await config.store.toggleUserActive(requireId(formData, \"userId\"), rawActive === \"true\")\n return { ok: true }\n }\n\n if (intent === \"createGroup\") {\n await config.store.createGroup({ groupKey: requireText(formData, \"groupKey\"), name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"updateGroup\") {\n await config.store.updateGroup(requireId(formData, \"groupId\"), { name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"deleteGroup\") {\n await config.store.deleteGroup(requireId(formData, \"groupId\"))\n return { ok: true }\n }\n\n if (intent === \"addGroupMember\") {\n await config.store.addGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"removeGroupMember\") {\n await config.store.removeGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"addAllowlistEntry\") {\n const type: \"email\" | \"domain\" = formData.get(\"type\") === \"domain\" ? \"domain\" : \"email\"\n await config.store.addAllowlistEntry({ pattern: requireText(formData, \"pattern\"), type, description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"removeAllowlistEntry\") {\n await config.store.removeAllowlistEntry(requireId(formData, \"entryId\"))\n return { ok: true }\n }\n\n // INTENT_ACCESS を通過した intent は上のいずれかで処理済み (到達しない安全網)\n return { ok: false, error: `Unknown intent: ${intent}` }\n }\n\n return { loader, action }\n}\n","import type React from \"react\"\nimport { Link, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\nimport type { UserAdminPermission } from \"./server\"\n\nexport type UserAdminShellProps = {\n children?: React.ReactNode\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Render one resource's permission summary as a compact pill.\n * 1 リソース分の権限サマリをコンパクトなタグとして描画するコンポーネント。\n */\nfunction GroupPill({ label, summary }: { label: string; summary: { text: string; color: string; bgColor: string; borderColor: string } }) {\n return (\n <span\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"0.18rem\",\n fontSize: \"0.65rem\",\n padding: \"0.08rem 0.35rem\",\n borderRadius: \"9999px\",\n backgroundColor: summary.bgColor,\n color: summary.color,\n border: `1px solid ${summary.borderColor}`,\n fontWeight: 600,\n lineHeight: \"1.2\",\n }}>\n <span>{label}:</span>\n <span>{summary.text}</span>\n </span>\n )\n}\n\n/**\n * Shell layout for user administration pages with header annotation delegation.\n * ヘッダー注釈委譲を備えたユーザー管理ページのシェルレイアウト。\n */\nexport function UserAdminShell({ children, renderHeader }: UserAdminShellProps) {\n const data = useLoaderData() as { segment?: string; labels?: UserAdminLabels; myPermissions?: Record<string, UserAdminPermission | undefined> | null; resourceLabels?: Record<string, string> } | undefined\n const segment = data?.segment ?? \"users\"\n const labels = data?.labels ?? defaultUserAdminLabels\n // null = 未結線または取得失敗。空 {} も「分からない」扱いで、いずれも許可として描画しない\n const myPermissions = data?.myPermissions ?? null\n // リソースキーの表示名は呼び出し側が差し込む (キーをこのパッケージに埋め込まない)\n const resourceLabels = data?.resourceLabels ?? {}\n const labelFor = (resourceKey: string) => (Object.hasOwn(resourceLabels, resourceKey) ? resourceLabels[resourceKey] : resourceKey)\n\n const tabs = [\n { key: \"users\", label: labels.tabs.users, href: \"/users\" },\n { key: \"groups\", label: labels.tabs.groups, href: \"/users/groups\" },\n { key: \"group_members\", label: labels.tabs.groupMembers, href: \"/users/group_members\" },\n { key: \"allowlist\", label: labels.tabs.allowlist, href: \"/users/allowlist\" },\n ]\n\n const permEntries = Object.entries(myPermissions ?? {})\n\n /**\n * Pick the badge text and colours for one resource, never rendering \"unknown\" as \"allowed\".\n * 1 リソース分のバッジ文言と配色を決める処理。**未知を許可として描かない** ことが要件。\n *\n * 以前は権限マップが空のとき「編集可」の固定値を描いていたため、ポート未結線・取得失敗・\n * 付与ゼロのいずれでも「全操作できます」と表示されていた (実際には操作するとガードで拒否される)。\n */\n const getSummary = (perm: UserAdminPermission | undefined) => {\n if (perm?.canWrite) {\n return { text: \"編集可\", color: \"#047857\", bgColor: \"#ecfdf5\", borderColor: \"#a7f3d0\" }\n }\n if (perm?.canRead) {\n return { text: \"閲覧のみ\", color: \"#1d4ed8\", bgColor: \"#eff6ff\", borderColor: \"#bfdbfe\" }\n }\n return { text: \"権限なし\", color: \"#64748b\", bgColor: \"#f1f5f9\", borderColor: \"#cbd5e1\" }\n }\n const unavailableSummary = { text: \"権限不明\", color: \"#92400e\", bgColor: \"#fffbeb\", borderColor: \"#fde68a\" }\n\n const annotation = (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.4rem\" }} data-testid=\"user-admin-header-annotation\">\n <span style={{ fontSize: \"0.75rem\", backgroundColor: \"#e0f2fe\", color: \"#0369a1\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>{labels.heading}</span>\n\n <div style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.25rem\", fontSize: \"0.68rem\", color: \"#475569\" }} data-testid=\"user-admin-permissions-badge\">\n <span style={{ fontWeight: 600, color: \"#334155\" }}>あなたの権限:</span>\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.18rem\" }}>\n {permEntries.length === 0 ? (\n <span data-testid=\"user-admin-permission-unavailable\">\n <GroupPill label={labels.heading} summary={unavailableSummary} />\n </span>\n ) : (\n permEntries.map(([resKey, perm]) => <GroupPill key={resKey} label={labelFor(resKey)} summary={getSummary(perm)} />)\n )}\n </span>\n </div>\n </div>\n )\n\n return (\n <div data-testid=\"user-admin-shell\" style={{ width: \"100%\", minHeight: \"100vh\", backgroundColor: \"#f8fafc\", padding: \"1.5rem\", boxSizing: \"border-box\" }}>\n {renderHeader ? (\n renderHeader({ title: labels.title, annotation })\n ) : (\n <div style={{ marginBottom: \"1.5rem\", display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\" }}>\n <h1 style={{ margin: 0, fontSize: \"1.5rem\", fontWeight: 700, color: \"#0f172a\" }}>{labels.title}</h1>\n {annotation}\n </div>\n )}\n\n <div style={{ display: \"flex\", gap: \"0.5rem\", marginBottom: \"1.2rem\", borderBottom: \"2px solid #e2e8f0\", paddingBottom: \"0.5rem\" }}>\n {tabs.map((tab) => {\n const isActive = segment === tab.key\n return (\n <Link\n key={tab.key}\n to={tab.href}\n data-testid={`user-admin-tab-${tab.key}`}\n style={{\n padding: \"0.5rem 1rem\",\n borderRadius: \"6px 6px 0 0\",\n fontSize: \"0.88rem\",\n fontWeight: isActive ? 700 : 500,\n color: isActive ? \"#0284c7\" : \"#64748b\",\n backgroundColor: isActive ? \"#ffffff\" : \"transparent\",\n textDecoration: \"none\",\n borderBottom: isActive ? \"3px solid #0284c7\" : \"3px solid transparent\",\n transition: \"all 0.15s ease\",\n }}>\n {tab.label}\n </Link>\n )\n })}\n </div>\n\n <main>{children}</main>\n </div>\n )\n}\n","import type { AuthAllowlistEntry, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type React from \"react\"\nimport { useState } from \"react\"\nimport { useFetcher, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\nimport { UserAdminShell, type UserAdminShellProps } from \"./ui\"\n\nexport type UserAdminLoaderData = {\n segment: string\n users: AuthUser[]\n groups: AuthGroup[]\n groupMembers: AuthGroupMember[]\n allowlist: AuthAllowlistEntry[]\n labels?: UserAdminLabels\n}\n\n/**\n * View for managing users (list, search, toggle active, modal edit).\n * ユーザー管理ビュー (一覧・検索・アクティブ切替・ダイアログ編集)。\n */\n/**\n * Renders the error returned by the last action submission, if any.\n * 直近の action 送信が返したエラーを表示する部品。\n *\n * ロックアウト理由・重複パターン・入力不正などの文言は action が `{ok:false,error}` で返す。\n * 表示しなければ操作が黙って失敗したように見えるため、各ビューの先頭に必ず置く。\n */\nfunction ActionError({ fetcher }: { fetcher: { data?: unknown } }) {\n const result = fetcher.data as { ok?: boolean; error?: string } | undefined\n if (result?.ok !== false || !result.error) return null\n return (\n <div data-testid=\"user-admin-action-error\" role=\"alert\" style={{ padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #fecaca\", backgroundColor: \"#fef2f2\", color: \"#b91c1c\", fontSize: \"0.85rem\" }}>\n {result.error}\n </div>\n )\n}\n\nexport function UsersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [editingUser, setEditingUser] = useState<AuthUser | null>(null)\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n\n const [newDisplayName, setNewDisplayName] = useState(\"\")\n const [newEmail, setNewEmail] = useState(\"\")\n\n const filteredUsers = data.users.filter((u) => u.displayName.toLowerCase().includes(searchQuery.toLowerCase()) || u.email.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleToggleActive = (user: AuthUser) => {\n fetcher.submit({ intent: \"toggleUserActive\", userId: String(user.id), isActive: String(!user.isActive) }, { method: \"post\" })\n }\n\n const handleCreateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newDisplayName && newEmail)) return\n fetcher.submit({ intent: \"createUser\", displayName: newDisplayName, email: newEmail }, { method: \"post\" })\n setNewDisplayName(\"\")\n setNewEmail(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingUser) return\n fetcher.submit({ intent: \"updateUser\", userId: String(editingUser.id), displayName: editingUser.displayName, email: editingUser.email }, { method: \"post\" })\n setEditingUser(null)\n }\n\n return (\n <section data-testid=\"users-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.usersView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"users-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"users-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.usersView.addUser}\n </button>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.id}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.displayName}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.email}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.status}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.usersView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {filteredUsers.map((u) => (\n <tr key={u.id} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`user-row-${u.id}`}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{u.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\" }}>{u.displayName}</td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{u.email}</td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: u.isActive ? \"#dcfce7\" : \"#fee2e2\", color: u.isActive ? \"#15803d\" : \"#b91c1c\", fontWeight: 600 }}>\n {u.isActive ? labels.usersView.active : labels.usersView.inactive}\n </span>\n </td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n <button\n type=\"button\"\n onClick={() => setEditingUser(u)}\n data-testid={`edit-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", marginRight: \"6px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleToggleActive(u)}\n data-testid={`toggle-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", color: u.isActive ? \"#b91c1c\" : \"#15803d\" }}>\n {u.isActive ? labels.usersView.deactivate : labels.usersView.activate}\n </button>\n </td>\n </tr>\n ))}\n {filteredUsers.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.usersView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Create Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-create-modal\">\n <form onSubmit={handleCreateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.addUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"create-user-display-name-input\"\n type=\"text\"\n value={newDisplayName}\n onChange={(e) => setNewDisplayName(e.target.value)}\n required\n data-testid=\"create-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"create-user-email-input\"\n type=\"email\"\n value={newEmail}\n onChange={(e) => setNewEmail(e.target.value)}\n required\n data-testid=\"create-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Modal */}\n {editingUser && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-edit-modal\">\n <form onSubmit={handleUpdateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.editUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"edit-user-display-name-input\"\n type=\"text\"\n value={editingUser.displayName}\n onChange={(e) => setEditingUser({ ...editingUser, displayName: e.target.value })}\n required\n data-testid=\"edit-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"edit-user-email-input\"\n type=\"email\"\n value={editingUser.email}\n onChange={(e) => setEditingUser({ ...editingUser, email: e.target.value })}\n required\n data-testid=\"edit-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingUser(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.usersView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.usersView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing user groups (create, edit, delete).\n * ユーザーグループ管理ビュー (作成・編集・削除)。\n */\nexport function GroupsView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n const [editingGroup, setEditingGroup] = useState<AuthGroup | null>(null)\n\n const [newGroupKey, setNewGroupKey] = useState(\"\")\n const [newName, setNewName] = useState(\"\")\n const [newDescription, setNewDescription] = useState(\"\")\n\n const filteredGroups = data.groups.filter((g) => g.name.toLowerCase().includes(searchQuery.toLowerCase()) || g.groupKey.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleCreateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newGroupKey && newName)) return\n fetcher.submit({ intent: \"createGroup\", groupKey: newGroupKey, name: newName, description: newDescription }, { method: \"post\" })\n setNewGroupKey(\"\")\n setNewName(\"\")\n setNewDescription(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingGroup) return\n fetcher.submit({ intent: \"updateGroup\", groupId: String(editingGroup.id), name: editingGroup.name, description: editingGroup.description ?? \"\" }, { method: \"post\" })\n setEditingGroup(null)\n }\n\n const handleDeleteGroup = (groupId: number) => {\n if (!window.confirm(labels.groupsView.confirmDelete)) return\n fetcher.submit({ intent: \"deleteGroup\", groupId: String(groupId) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"groups-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.groupsView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"groups-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"groups-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.groupsView.addGroup}\n </button>\n </div>\n\n <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(280px, 1fr))\", gap: \"1rem\" }}>\n {filteredGroups.map((g) => {\n const memberCount = data.groupMembers.filter((m) => m.groupId === g.id).length\n return (\n <div key={g.id} style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"1rem\", display: \"flex\", flexDirection: \"column\", justifyContent: \"space-between\" }} data-testid={`group-card-${g.id}`}>\n <div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: \"0.5rem\" }}>\n <h4 style={{ margin: 0, fontSize: \"1rem\", color: \"#0f172a\" }}>{g.name}</h4>\n <span style={{ fontSize: \"0.72rem\", backgroundColor: \"#e0e7ff\", color: \"#4338ca\", padding: \"2px 6px\", borderRadius: \"4px\", fontFamily: \"monospace\" }}>{g.groupKey}</span>\n </div>\n <p style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.82rem\", color: \"#64748b\", minHeight: \"2.4em\" }}>{g.description || labels.groupsView.noDescription}</p>\n </div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", borderTop: \"1px solid #f1f5f9\", paddingTop: \"0.6rem\" }}>\n <span style={{ fontSize: \"0.78rem\", color: \"#475569\" }}>\n 👥 メンバー: <strong>{memberCount}</strong> 人\n </span>\n <div>\n <button\n type=\"button\"\n onClick={() => setEditingGroup(g)}\n data-testid={`edit-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.75rem\", cursor: \"pointer\", marginRight: \"4px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeleteGroup(g.id)}\n data-testid={`delete-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.75rem\", cursor: \"pointer\" }}>\n 削除\n </button>\n </div>\n </div>\n </div>\n )\n })}\n </div>\n\n {/* Create Group Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-create-modal\">\n <form onSubmit={handleCreateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.addGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-key-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.groupKey}\n </label>\n <input\n id=\"create-group-key-input\"\n type=\"text\"\n value={newGroupKey}\n onChange={(e) => setNewGroupKey(e.target.value)}\n placeholder={labels.groupsView.groupKeyPlaceholder}\n required\n data-testid=\"create-group-key\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"create-group-name-input\"\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder={labels.groupsView.namePlaceholder}\n required\n data-testid=\"create-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"create-group-desc-input\"\n value={newDescription}\n onChange={(e) => setNewDescription(e.target.value)}\n data-testid=\"create-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Group Modal */}\n {editingGroup && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-edit-modal\">\n <form onSubmit={handleUpdateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.editGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"edit-group-name-input\"\n type=\"text\"\n value={editingGroup.name}\n onChange={(e) => setEditingGroup({ ...editingGroup, name: e.target.value })}\n required\n data-testid=\"edit-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"edit-group-desc-input\"\n value={editingGroup.description ?? \"\"}\n onChange={(e) => setEditingGroup({ ...editingGroup, description: e.target.value })}\n data-testid=\"edit-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingGroup(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.groupsView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.groupsView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing group member assignments.\n * グループメンバー割り当て管理ビュー。\n */\nexport function GroupMembersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [selectedGroupId, setSelectedGroupId] = useState<number>(data.groups[0]?.id ?? 0)\n\n const currentGroup = data.groups.find((g) => g.id === selectedGroupId)\n const currentMemberUserIds = new Set(data.groupMembers.filter((m) => m.groupId === selectedGroupId).map((m) => m.userId))\n\n const handleToggleMember = (userId: number, isMember: boolean) => {\n if (isMember) {\n fetcher.submit({ intent: \"removeGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n } else {\n fetcher.submit({ intent: \"addGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n }\n }\n\n return (\n <section data-testid=\"group-members-view-section\" style={{ display: \"grid\", gridTemplateColumns: \"240px 1fr\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <h4 style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.9rem\", color: \"#475569\" }}>{labels.groupMembersView.selectGroup}</h4>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.4rem\" }}>\n {data.groups.map((g) => {\n const isSelected = g.id === selectedGroupId\n return (\n <button\n type=\"button\"\n key={g.id}\n onClick={() => setSelectedGroupId(g.id)}\n data-testid={`select-group-btn-${g.id}`}\n style={{\n textAlign: \"left\",\n padding: \"8px 10px\",\n borderRadius: \"6px\",\n border: isSelected ? \"1px solid #6366f1\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#e0e7ff\" : \"#ffffff\",\n color: isSelected ? \"#3730a3\" : \"#0f172a\",\n cursor: \"pointer\",\n fontWeight: isSelected ? 600 : 400,\n fontSize: \"0.85rem\",\n }}>\n {g.name}\n </button>\n )\n })}\n </div>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n {currentGroup ? (\n <>\n <div style={{ marginBottom: \"1rem\", borderBottom: \"1px solid #e2e8f0\", paddingBottom: \"0.8rem\" }}>\n <h3 style={{ margin: 0, fontSize: \"1.1rem\", color: \"#0f172a\" }}>{currentGroup.name} の所属メンバー</h3>\n <span style={{ fontSize: \"0.8rem\", color: \"#64748b\" }}>グループキー: {currentGroup.groupKey}</span>\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\" }}>\n {data.users.map((u) => {\n const isMember = currentMemberUserIds.has(u.id)\n return (\n <div\n key={u.id}\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: \"8px 12px\",\n borderRadius: \"6px\",\n border: \"1px solid #f1f5f9\",\n backgroundColor: isMember ? \"#f0fdf4\" : \"#ffffff\",\n }}\n data-testid={`member-row-${u.id}`}>\n <div>\n <span style={{ fontWeight: 600, fontSize: \"0.88rem\", color: \"#0f172a\", marginRight: \"8px\" }}>{u.displayName}</span>\n <span style={{ fontSize: \"0.78rem\", color: \"#64748b\" }}>({u.email})</span>\n </div>\n <button\n type=\"button\"\n onClick={() => handleToggleMember(u.id, isMember)}\n data-testid={`toggle-member-btn-${u.id}`}\n style={{\n padding: \"4px 12px\",\n borderRadius: \"4px\",\n border: isMember ? \"1px solid #b91c1c\" : \"1px solid #0284c7\",\n backgroundColor: isMember ? \"#ffffff\" : \"#0284c7\",\n color: isMember ? \"#b91c1c\" : \"#ffffff\",\n fontSize: \"0.78rem\",\n fontWeight: 600,\n cursor: \"pointer\",\n }}>\n {isMember ? labels.groupMembersView.actionRemove : labels.groupMembersView.actionAdd}\n </button>\n </div>\n )\n })}\n </div>\n </>\n ) : (\n <div style={{ color: \"#94a3b8\", textAlign: \"center\", padding: \"2rem\" }}>{labels.groupMembersView.selectPrompt}</div>\n )}\n </div>\n </section>\n )\n}\n\n/**\n * View for managing authentication allowlist entries.\n * アクセス許可リスト (Allowlist) 管理ビュー。\n */\nexport function AllowlistView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [pattern, setPattern] = useState(\"\")\n const [type, setType] = useState<\"email\" | \"domain\">(\"email\")\n const [description, setDescription] = useState(\"\")\n\n const handleAddEntry = (e: React.FormEvent) => {\n e.preventDefault()\n if (!pattern) return\n fetcher.submit({ intent: \"addAllowlistEntry\", pattern, type, description }, { method: \"post\" })\n setPattern(\"\")\n setDescription(\"\")\n }\n\n const handleRemoveEntry = (id: number) => {\n fetcher.submit({ intent: \"removeAllowlistEntry\", entryId: String(id) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"allowlist-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <form onSubmit={handleAddEntry} style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", display: \"flex\", flexWrap: \"wrap\", gap: \"0.8rem\", alignItems: \"flex-end\" }}>\n <div style={{ flex: 2, minWidth: \"220px\" }}>\n <label htmlFor=\"allowlist-pattern-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.pattern}\n </label>\n <input\n id=\"allowlist-pattern-input-field\"\n type=\"text\"\n value={pattern}\n onChange={(e) => setPattern(e.target.value)}\n required\n data-testid=\"allowlist-pattern-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ flex: 1, minWidth: \"140px\" }}>\n <label htmlFor=\"allowlist-type-select-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.type}\n </label>\n <select\n id=\"allowlist-type-select-field\"\n value={type}\n onChange={(e) => setType(e.target.value as \"email\" | \"domain\")}\n data-testid=\"allowlist-type-select\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}>\n <option value=\"email\">{labels.allowlistView.emailType}</option>\n <option value=\"domain\">{labels.allowlistView.domainType}</option>\n </select>\n </div>\n <div style={{ flex: 2, minWidth: \"200px\" }}>\n <label htmlFor=\"allowlist-desc-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.description}\n </label>\n <input\n id=\"allowlist-desc-input-field\"\n type=\"text\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n data-testid=\"allowlist-desc-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <button type=\"submit\" data-testid=\"submit-allowlist-add\" style={{ padding: \"7px 16px\", borderRadius: \"6px\", backgroundColor: \"#047857\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.allowlistView.addEntry}\n </button>\n </form>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>ID</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.pattern}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.type}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.description}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.allowlistView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {data.allowlist.map((item) => (\n <tr key={item.id} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`allowlist-row-${item.id}`}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{item.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\", fontFamily: \"monospace\" }}>{item.pattern}</td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: item.type === \"email\" ? \"#e0f2fe\" : \"#fef3c7\", color: item.type === \"email\" ? \"#0369a1\" : \"#b45309\", fontWeight: 600 }}>\n {item.type === \"email\" ? labels.allowlistView.emailTypeShort : labels.allowlistView.domainTypeShort}\n </span>\n </td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{item.description || \"-\"}</td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n {/* 変更不能な供給元 (環境変数など) は削除ボタンを出さない */}\n {item.readOnly ? (\n <span data-testid={`readonly-allowlist-${item.id}`} style={{ fontSize: \"0.75rem\", color: \"#94a3b8\" }}>\n {labels.allowlistView.readOnly}\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={() => handleRemoveEntry(item.id)}\n data-testid={`remove-allowlist-btn-${item.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.78rem\", cursor: \"pointer\" }}>\n 削除\n </button>\n )}\n </td>\n </tr>\n ))}\n {data.allowlist.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.allowlistView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </section>\n )\n}\n\nconst SEGMENT_VIEWS: Record<string, () => React.ReactElement> = {\n users: UsersView,\n groups: GroupsView,\n group_members: GroupMembersView,\n allowlist: AllowlistView,\n}\n\nexport type AuthUserAdminAppViewProps = UserAdminShellProps\n\n/**\n * Top-level application view component for the user administration UI (`/users/*`).\n * ユーザー管理 UI 全体を描画するトップレベルコンポーネント。\n */\nexport function AuthUserAdminAppView({ renderHeader }: AuthUserAdminAppViewProps = {}) {\n const data = useLoaderData() as UserAdminLoaderData | undefined\n const segment = data?.segment ?? \"users\"\n const View = SEGMENT_VIEWS[segment] ?? UsersView\n\n return (\n <UserAdminShell renderHeader={renderHeader}>\n <View />\n </UserAdminShell>\n )\n}\n"],"mappings":"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","names":["defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","over","d","createInMemoryUserAdminStore","initial","users","groups","groupMembers","allowlist","data","newUser","u","id","idx","isActive","newGroup","g","m","groupId","userId","existing","newMember","newEntry","a","INTENT_ACCESS","requireId","formData","field","raw","n","requireText","value","optionalText","createUserAdminApp","config","labels","resolveUserAdminLabels","loader","request","pathSegments","segment","myPermissions","e","action","intent","access","dispatch","error","rawActive","type","Link","useLoaderData","jsx","jsxs","GroupPill","label","summary","UserAdminShell","children","renderHeader","data","useLoaderData","segment","labels","defaultUserAdminLabels","myPermissions","resourceLabels","labelFor","resourceKey","tabs","permEntries","getSummary","perm","unavailableSummary","annotation","resKey","tab","isActive","Link","useState","useFetcher","useLoaderData","Fragment","jsx","jsxs","ActionError","fetcher","result","UsersView","data","useLoaderData","useFetcher","labels","defaultUserAdminLabels","searchQuery","setSearchQuery","useState","editingUser","setEditingUser","isCreateOpen","setIsCreateOpen","newDisplayName","setNewDisplayName","newEmail","setNewEmail","filteredUsers","handleToggleActive","user","handleCreateUser","e","handleUpdateUser","GroupsView","editingGroup","setEditingGroup","newGroupKey","setNewGroupKey","newName","setNewName","newDescription","setNewDescription","filteredGroups","g","handleCreateGroup","handleUpdateGroup","handleDeleteGroup","groupId","memberCount","m","GroupMembersView","selectedGroupId","setSelectedGroupId","currentGroup","currentMemberUserIds","handleToggleMember","userId","isMember","isSelected","u","AllowlistView","pattern","setPattern","type","setType","description","setDescription","handleAddEntry","handleRemoveEntry","id","item","SEGMENT_VIEWS","AuthUserAdminAppView","renderHeader","segment","View","UserAdminShell"]}
|
|
1
|
+
{"version":3,"sources":["../src/admin/labels.ts","../src/admin/server.ts","../src/admin/ui.tsx","../src/admin/views.tsx"],"sourcesContent":["/**\n * Labels and i18n strings for the user administration UI.\n * ユーザー管理 UI で使用するラベルと i18n 文字列の定義。\n *\n * パッケージ既定は **英語** ([defaultUserAdminLabels])。日本語で使う場合はアプリ側が\n * [jaUserAdminLabels] を明示的に注入する (認可管理 `@aiquants/authz-react-router` と同じ規約)。\n * UI 文字列はすべて本モジュールに集約し、コンポーネント側へ直書きしないこと — 直書きすると\n * 日本語以外の利用者がその箇所を差し替えられなくなる。\n */\n\nexport type UserAdminLabels = {\n title: string\n /** シェル (タブ枠) の見出し。 */\n heading: string\n tabs: {\n users: string\n groups: string\n groupMembers: string\n allowlist: string\n }\n usersView: {\n title: string\n searchPlaceholder: string\n addUser: string\n editUser: string\n id: string\n displayName: string\n email: string\n status: string\n active: string\n inactive: string\n actions: string\n edit: string\n activate: string\n deactivate: string\n empty: string\n save: string\n cancel: string\n }\n groupsView: {\n title: string\n searchPlaceholder: string\n addGroup: string\n editGroup: string\n id: string\n groupKey: string\n name: string\n description: string\n memberCount: string\n status: string\n actions: string\n edit: string\n delete: string\n confirmDelete: string\n noDescription: string\n groupKeyPlaceholder: string\n namePlaceholder: string\n empty: string\n save: string\n cancel: string\n }\n groupMembersView: {\n selectGroup: string\n selectPrompt: string\n actionAdd: string\n actionRemove: string\n }\n allowlistView: {\n title: string\n addEntry: string\n pattern: string\n type: string\n emailType: string\n domainType: string\n /** 一覧のバッジ表示 (短縮形)。 */\n emailTypeShort: string\n domainTypeShort: string\n /** グループ単位の許可。上流ディレクトリの所属で判定される。 */\n groupTypeShort: string\n /** 対象列の見出し。パターンとグループが混在するため「パターン」ではない。 */\n target: string\n description: string\n actions: string\n /** 変更不能な供給元 (環境変数など) 由来の行に出す表示。 */\n readOnly: string\n /** 配備全体が所有する行 (環境変数由来) の所有者表示。 */\n deploymentOwner: string\n delete: string\n empty: string\n save: string\n cancel: string\n }\n directoryView: {\n title: string\n /** 出所列。ローカル管理か、どの上流かを示す。 */\n source: string\n localSource: string\n /** 上流所有のグループに出す編集不可の表示。 */\n managedUpstream: string\n membershipMode: string\n directMode: string\n transitiveMode: string\n lastSynced: string\n neverSynced: string\n /** 上流に居るがローカルユーザーへ解決できていない人数。 */\n unresolved: string\n syncPaused: string\n syncEnabled: string\n syncNow: string\n syncQueued: string\n /** 同期が滞っていることの警告。停止はセキュリティ事象である。 */\n staleWarning: string\n disabledWarning: string\n link: string\n unlink: string\n externalKey: string\n empty: string\n }\n}\n\n/**\n * Package default labels (English).\n * パッケージ既定ラベル (英語)。\n */\nexport const defaultUserAdminLabels: UserAdminLabels = {\n title: \"Users & Groups\",\n heading: \"👤 User administration\",\n tabs: {\n users: \"👤 Users\",\n groups: \"👥 Groups\",\n groupMembers: \"🔗 Members\",\n allowlist: \"✉️ Allowlist\",\n },\n usersView: {\n title: \"Users\",\n searchPlaceholder: \"Search by name or email...\",\n addUser: \"+ Add user\",\n editUser: \"Edit user\",\n id: \"ID\",\n displayName: \"Name\",\n email: \"Email\",\n status: \"Status\",\n active: \"Active\",\n inactive: \"Inactive\",\n actions: \"Actions\",\n edit: \"Edit\",\n activate: \"Activate\",\n deactivate: \"Deactivate\",\n empty: \"No matching users\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupsView: {\n title: \"Groups\",\n searchPlaceholder: \"Search by group name or key...\",\n addGroup: \"+ Add group\",\n editGroup: \"Edit group\",\n id: \"ID\",\n groupKey: \"Group key\",\n name: \"Name\",\n description: \"Description\",\n memberCount: \"Members\",\n status: \"Status\",\n actions: \"Actions\",\n edit: \"Edit\",\n delete: \"Delete\",\n confirmDelete: \"Delete this group?\",\n noDescription: \"No description\",\n groupKeyPlaceholder: \"e.g. dev-team\",\n namePlaceholder: \"e.g. Development team\",\n empty: \"No groups registered\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupMembersView: {\n selectGroup: \"Select a group\",\n selectPrompt: \"Select a group to manage its members\",\n actionAdd: \"Add\",\n actionRemove: \"Remove\",\n },\n allowlistView: {\n title: \"Sign-in allowlist\",\n addEntry: \"+ Add pattern\",\n pattern: \"Pattern (e.g. @example.com or user@example.com)\",\n type: \"Type\",\n emailType: \"Exact email match\",\n domainType: \"Domain match\",\n emailTypeShort: \"Email\",\n domainTypeShort: \"Domain\",\n groupTypeShort: \"Group\",\n target: \"Target\",\n description: \"Description\",\n actions: \"Actions\",\n readOnly: \"Read-only\",\n deploymentOwner: \"Deployment-wide\",\n delete: \"Delete\",\n empty: \"No allowlist entries registered\",\n save: \"Add\",\n cancel: \"Cancel\",\n },\n directoryView: {\n title: \"Directory synchronization\",\n source: \"Source\",\n localSource: \"Local\",\n managedUpstream: \"Managed upstream\",\n membershipMode: \"Membership\",\n directMode: \"Direct only\",\n transitiveMode: \"Nested expanded\",\n lastSynced: \"Last synced\",\n neverSynced: \"Never\",\n unresolved: \"unresolved\",\n syncPaused: \"Paused\",\n syncEnabled: \"Active\",\n syncNow: \"Sync now\",\n syncQueued: \"Queued\",\n staleWarning: \"Synchronization is overdue. Until it runs, people removed upstream keep their access.\",\n disabledWarning: \"Directory synchronization is not wired for this deployment. Existing memberships remain in force and are no longer being refreshed.\",\n link: \"Link to a directory group\",\n unlink: \"Unlink\",\n externalKey: \"Upstream group\",\n empty: \"No groups are linked to a directory\",\n },\n}\n\n/**\n * Standard Japanese labels for user administration (opt-in).\n * ユーザー管理の標準日本語ラベル (アプリ側で明示注入して使う)。\n */\nexport const jaUserAdminLabels: UserAdminLabels = {\n title: \"ユーザー・グループ管理\",\n heading: \"👤 ユーザー管理\",\n tabs: {\n users: \"👤 ユーザー一覧\",\n groups: \"👥 ユーザーグループ\",\n groupMembers: \"🔗 グループメンバー\",\n allowlist: \"✉️ アクセス許可リスト\",\n },\n usersView: {\n title: \"ユーザー管理\",\n searchPlaceholder: \"表示名や Email で検索...\",\n addUser: \"+ ユーザー追加\",\n editUser: \"ユーザーの編集\",\n id: \"ID\",\n displayName: \"表示名\",\n email: \"メールアドレス\",\n status: \"ステータス\",\n active: \"有効\",\n inactive: \"無効\",\n actions: \"操作\",\n edit: \"編集\",\n activate: \"有効化\",\n deactivate: \"無効化\",\n empty: \"該当するユーザーが見つかりません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupsView: {\n title: \"ユーザーグループ管理\",\n searchPlaceholder: \"グループ名やキーで検索...\",\n addGroup: \"+ グループ作成\",\n editGroup: \"グループの編集\",\n id: \"ID\",\n groupKey: \"グループキー\",\n name: \"グループ名\",\n description: \"説明\",\n memberCount: \"メンバー数\",\n status: \"ステータス\",\n actions: \"操作\",\n edit: \"編集\",\n delete: \"削除\",\n confirmDelete: \"このグループを削除してもよろしいですか?\",\n noDescription: \"説明なし\",\n groupKeyPlaceholder: \"例: dev-team\",\n namePlaceholder: \"例: 開発チーム\",\n empty: \"グループが登録されていません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupMembersView: {\n selectGroup: \"グループ選択\",\n selectPrompt: \"グループを選択してください\",\n actionAdd: \"追加\",\n actionRemove: \"除外\",\n },\n allowlistView: {\n title: \"アクセス許可リスト (Allowlist)\",\n addEntry: \"+ 許可パターン追加\",\n pattern: \"パターン (例: @example.com または user@example.com)\",\n type: \"種別\",\n emailType: \"メールアドレス完全一致\",\n domainType: \"ドメイン一致\",\n emailTypeShort: \"Email 完全一致\",\n domainTypeShort: \"ドメイン一致\",\n groupTypeShort: \"グループ\",\n target: \"対象\",\n description: \"説明 / 備考\",\n actions: \"操作\",\n readOnly: \"変更不可\",\n deploymentOwner: \"配備共通\",\n delete: \"削除\",\n empty: \"アロワリストエントリが登録されていません\",\n save: \"登録\",\n cancel: \"キャンセル\",\n },\n directoryView: {\n title: \"ディレクトリ同期\",\n source: \"出所\",\n localSource: \"ローカル管理\",\n managedUpstream: \"上流が所有\",\n membershipMode: \"所属の展開\",\n directMode: \"直接所属のみ\",\n transitiveMode: \"入れ子を展開\",\n lastSynced: \"最終同期\",\n neverSynced: \"未実行\",\n unresolved: \"件 未解決\",\n syncPaused: \"停止中\",\n syncEnabled: \"有効\",\n syncNow: \"今すぐ同期\",\n syncQueued: \"同期を要求しました\",\n staleWarning: \"同期が滞っています。実行されるまで、上流から外れた人のアクセスは残り続けます。\",\n disabledWarning: \"この配備ではディレクトリ同期が結線されていません。既存の所属はそのまま有効で、更新だけが止まっています。\",\n link: \"ディレクトリのグループと連携する\",\n unlink: \"連携を解除\",\n externalKey: \"上流グループ\",\n empty: \"ディレクトリと連携しているグループはありません\",\n },\n}\n\n/** セクション単位の部分上書き。 */\nexport type PartialUserAdminLabels = {\n title?: string\n heading?: string\n tabs?: Partial<UserAdminLabels[\"tabs\"]>\n usersView?: Partial<UserAdminLabels[\"usersView\"]>\n groupsView?: Partial<UserAdminLabels[\"groupsView\"]>\n groupMembersView?: Partial<UserAdminLabels[\"groupMembersView\"]>\n allowlistView?: Partial<UserAdminLabels[\"allowlistView\"]>\n directoryView?: Partial<UserAdminLabels[\"directoryView\"]>\n}\n\n/**\n * Shallow per-section merge over the English defaults.\n * 英語既定に対してセクション単位で浅くマージする処理。\n */\nexport function resolveUserAdminLabels(over?: PartialUserAdminLabels): UserAdminLabels {\n const d = defaultUserAdminLabels\n if (!over) return d\n return {\n title: over.title ?? d.title,\n heading: over.heading ?? d.heading,\n tabs: { ...d.tabs, ...over.tabs },\n usersView: { ...d.usersView, ...over.usersView },\n groupsView: { ...d.groupsView, ...over.groupsView },\n groupMembersView: { ...d.groupMembersView, ...over.groupMembersView },\n allowlistView: { ...d.allowlistView, ...over.allowlistView },\n directoryView: { ...d.directoryView, ...over.directoryView },\n }\n}\n","import type { AuthAllowlistEntry, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport { type PartialUserAdminLabels, resolveUserAdminLabels } from \"./labels\"\n\n/**\n * Server store interface for managing users, groups, memberships, and allowlists.\n * ユーザー・グループ・所属・アロワリストを管理するサーバー層ストアのインターフェース。\n */\nexport type UserAdminStore = {\n listUsers: () => Promise<AuthUser[]>\n createUser: (data: { displayName: string; email: string }) => Promise<AuthUser>\n updateUser: (id: number, data: { displayName: string; email: string }) => Promise<AuthUser>\n toggleUserActive: (id: number, isActive: boolean) => Promise<AuthUser>\n\n listGroups: () => Promise<AuthGroup[]>\n createGroup: (data: { groupKey: string; name: string; description?: string }) => Promise<AuthGroup>\n updateGroup: (id: number, data: { name: string; description?: string }) => Promise<AuthGroup>\n deleteGroup: (id: number) => Promise<void>\n\n listGroupMembers: () => Promise<AuthGroupMember[]>\n addGroupMember: (groupId: number, userId: number) => Promise<AuthGroupMember>\n removeGroupMember: (groupId: number, userId: number) => Promise<void>\n\n listAllowlist: () => Promise<AuthAllowlistEntry[]>\n addAllowlistEntry: (data: { pattern: string; type: \"email\" | \"domain\"; description?: string }) => Promise<AuthAllowlistEntry>\n removeAllowlistEntry: (id: number) => Promise<void>\n\n /**\n * Allows every member of an externally-sourced group to sign in.\n * 外部供給グループのメンバー全員にサインインを許可する処理。\n *\n * 判定は上流メンバー台帳を読むため、ローカル管理のグループを渡しても誰にも何も与えない。\n * ストアはそれを拒否すること (無言の no-op を作らない)。\n */\n addAllowlistGroup: (groupId: number, description?: string) => Promise<void>\n removeAllowlistGroup: (id: number) => Promise<void>\n\n /**\n * Links a local group to a group in the upstream directory.\n * ローカルグループを上流ディレクトリのグループへ結び付ける処理。\n *\n * リンク後、そのグループの名称と所属は上流が所有する。ロール割当はローカルのままである。\n */\n linkDirectoryGroup: (groupId: number, data: { provider: string; externalKey: string; membershipMode: AuthDirectoryMembershipMode }) => Promise<void>\n /** Stops following the upstream, keeping the memberships already projected. 連携の解除。 */\n unlinkDirectoryGroup: (groupId: number) => Promise<void>\n /** Pauses or resumes synchronization for one group. 1 グループの同期を停止・再開する処理。 */\n setDirectorySyncEnabled: (groupId: number, isSyncEnabled: boolean) => Promise<void>\n /**\n * Queues a synchronization request. 同期要求をキューへ積む処理。\n *\n * 上流へ到達できるのは同期ジョブだけなので、web からは要求を残すことしかできない。\n *\n * @param groupId `null` requests every linked group of the tenant. `null` はテナント全件。\n */\n requestDirectorySync: (groupId: number | null) => Promise<void>\n /** Health of the synchronization, for the header warning. 同期の健全性 (警告表示用)。 */\n getDirectoryStatus: () => Promise<AuthDirectoryStatus>\n}\n\n/**\n * Creates an in-memory UserAdminStore initialized with optional seed data.\n * シードデータで初期化されるメモリ上の UserAdminStore を作成する。\n */\nexport function createInMemoryUserAdminStore(initial?: { users?: AuthUser[]; groups?: AuthGroup[]; groupMembers?: AuthGroupMember[]; allowlist?: AuthAllowlistEntry[] }): UserAdminStore {\n const users: AuthUser[] = initial?.users ?? [\n { id: 1, email: \"admin@example.com\", displayName: \"システム管理者\", isActive: true },\n { id: 2, email: \"user1@example.com\", displayName: \"開発 太郎\", isActive: true },\n { id: 3, email: \"user2@example.com\", displayName: \"営業 花子\", isActive: true },\n ]\n let groups: AuthGroup[] = initial?.groups ?? [\n { id: 1, groupKey: \"dev-team\", name: \"開発チーム\", description: \"システム開発担当グループ\", isActive: true, readOnly: false },\n { id: 2, groupKey: \"sales-dept\", name: \"営業部\", description: \"営業ソリューション担当\", isActive: true, readOnly: false },\n ]\n let groupMembers: AuthGroupMember[] = initial?.groupMembers ?? [\n { id: 1, groupId: 1, userId: 2 },\n { id: 2, groupId: 2, userId: 3 },\n ]\n let allowlist: AuthAllowlistEntry[] = initial?.allowlist ?? [\n { id: 1, pattern: \"admin@example.com\", type: \"email\", description: \"初期管理者\", owner: { kind: \"tenant\", tenantId: \"default\" }, readOnly: false },\n { id: 2, pattern: \"@example.com\", type: \"domain\", description: \"社内ドメイン\", owner: { kind: \"tenant\", tenantId: \"default\" }, readOnly: false },\n ]\n\n return {\n async listUsers() {\n return [...users]\n },\n async createUser(data) {\n const newUser: AuthUser = {\n id: Math.max(...users.map((u) => u.id), 0) + 1,\n email: data.email,\n displayName: data.displayName,\n isActive: true,\n }\n users.push(newUser)\n return newUser\n },\n async updateUser(id, data) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], displayName: data.displayName, email: data.email }\n return users[idx]\n },\n async toggleUserActive(id, isActive) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], isActive }\n return users[idx]\n },\n\n async listGroups() {\n return [...groups]\n },\n async createGroup(data) {\n const newGroup: AuthGroup = {\n id: Math.max(...groups.map((g) => g.id), 0) + 1,\n groupKey: data.groupKey,\n name: data.name,\n description: data.description,\n isActive: true,\n readOnly: false,\n }\n groups.push(newGroup)\n return newGroup\n },\n async updateGroup(id, data) {\n const idx = groups.findIndex((g) => g.id === id)\n if (idx === -1) throw new Error(`Group not found: ${id}`)\n groups[idx] = { ...groups[idx], name: data.name, description: data.description }\n return groups[idx]\n },\n async deleteGroup(id) {\n groups = groups.filter((g) => g.id !== id)\n groupMembers = groupMembers.filter((m) => m.groupId !== id)\n },\n\n async listGroupMembers() {\n return [...groupMembers]\n },\n async addGroupMember(groupId, userId) {\n const existing = groupMembers.find((m) => m.groupId === groupId && m.userId === userId)\n if (existing) return existing\n const newMember: AuthGroupMember = {\n id: Math.max(...groupMembers.map((m) => m.id), 0) + 1,\n groupId,\n userId,\n }\n groupMembers.push(newMember)\n return newMember\n },\n async removeGroupMember(groupId, userId) {\n groupMembers = groupMembers.filter((m) => !(m.groupId === groupId && m.userId === userId))\n },\n\n async listAllowlist() {\n return [...allowlist]\n },\n async addAllowlistEntry(data) {\n const newEntry: AuthAllowlistEntry = {\n id: Math.max(...allowlist.map((a) => a.id), 0) + 1,\n pattern: data.pattern,\n type: data.type,\n description: data.description,\n owner: { kind: \"tenant\", tenantId: \"default\" },\n readOnly: false,\n }\n allowlist.push(newEntry)\n return newEntry\n },\n async removeAllowlistEntry(id) {\n allowlist = allowlist.filter((a) => a.id !== id)\n },\n\n // --- ディレクトリ連携 ------------------------------------------------------------\n // このメモリ実装はパッケージ自身の spec 用のテストシームであり、上流を持たない。\n // 連携面は「結線されていない配備」を表現する形で応答する\n async addAllowlistGroup(groupId, description) {\n const group = groups.find((g) => g.id === groupId)\n if (!group) throw new Error(`Group not found: ${groupId}`)\n allowlist = [...allowlist, { id: Math.max(...allowlist.map((a) => a.id), 0) + 1, type: \"group\", groupId, groupKey: group.groupKey, groupName: group.name, description, owner: { kind: \"tenant\", tenantId: \"default\" }, readOnly: false }]\n },\n async removeAllowlistGroup(id) {\n allowlist = allowlist.filter((a) => !(a.type === \"group\" && a.id === id))\n },\n async linkDirectoryGroup() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to link groups.\")\n },\n async unlinkDirectoryGroup() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to unlink groups.\")\n },\n async setDirectorySyncEnabled() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to pause synchronization.\")\n },\n async requestDirectorySync() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to request a sync.\")\n },\n async getDirectoryStatus() {\n return { isEnabled: false, linkedGroupCount: 0, isStale: false, pausedGroupCount: 0, stalenessThresholdSec: 0, erroredGroupIds: [], pendingRequestCount: 0 }\n },\n }\n}\n\n/**\n * The CRUD verb an operation performs. Deliberately a plain string union so this package stays\n * independent of any particular authorization library.\n * 操作が行う CRUD 種別。特定の認可ライブラリに依存しないよう素の文字列 union で表現する。\n */\n/**\n * The only route-args field this module reads. React Router's full `LoaderFunctionArgs` satisfies it, so\n * route wiring is unchanged — but declaring the minimum keeps callers (and tests) from fabricating fields\n * that are never looked at.\n * このモジュールが実際に読むルート引数の全て。React Router の完全な引数はこれを満たすため結線は不変。\n */\ntype RouteArgs = { request: Request }\n\nexport type UserAdminAccess = \"read\" | \"create\" | \"update\" | \"delete\"\n\n/**\n * Access guard the user-admin surface is mounted behind. Deny by throwing\n * (a `Response`/`redirect` or an `Error`); returning normally means \"allow\".\n *\n * ユーザー管理面をマウントする際の必須アクセスガード。拒否は throw で表現する\n * (`Response` / `redirect` / `Error`)。正常 return は「許可」を意味する。\n *\n * 操作ごとの CRUD 種別を受け取るため、呼び出し側は「削除だけ別権限」といった細分化ができる。\n * `intent` は監査ログ用の補助情報(認可判断は `action` で行うこと)。\n */\nexport type UserAdminGuards = {\n /**\n * 許可は「正常 return」、拒否は「throw」で表す。返り値は判定に使わない (`void` 契約)。\n * 述語のつもりで `false` を返す実装を書くと素通りするため、返り値型で明示的に禁じる。\n */\n requireAccess: (request: Request, context: { action: UserAdminAccess; intent: string }) => Promise<void> | void\n}\n\n/**\n * Maps each action intent to the CRUD verb it actually performs.\n * 各 intent が実際に行う操作の CRUD 種別への対応表。\n *\n * 割当の付与・解除は行の追加・削除であるため、認可管理 UI (`/authz`) の\n * `assignUserRole` = create / `removeUserRole` = delete と同じ粒度に揃える。\n */\nconst INTENT_ACCESS = new Map<string, UserAdminAccess>([\n [\"createUser\", \"create\"],\n [\"updateUser\", \"update\"],\n [\"toggleUserActive\", \"update\"],\n [\"createGroup\", \"create\"],\n [\"updateGroup\", \"update\"],\n [\"deleteGroup\", \"delete\"],\n [\"addGroupMember\", \"create\"],\n [\"removeGroupMember\", \"delete\"],\n [\"addAllowlistEntry\", \"create\"],\n [\"removeAllowlistEntry\", \"delete\"],\n [\"addAllowlistGroup\", \"create\"],\n [\"removeAllowlistGroup\", \"delete\"],\n // 連携の開始・解除は行の追加削除ではなく、既存グループの属性変更である。create / delete に\n // 寄せると、グループ本体を作れない権限でリンクだけ張れてしまう\n [\"linkDirectoryGroup\", \"update\"],\n [\"unlinkDirectoryGroup\", \"update\"],\n [\"setDirectorySyncEnabled\", \"update\"],\n // 書き込みを伴わないが update を要求する。読取権限だけの主体が上流問い合わせを誘発できると、\n // レート制限を外部から枯渇させられる\n [\"requestDirectorySync\", \"update\"],\n])\n\n/**\n * Neutral per-resource permission summary the admin shell renders in its header badge.\n * 管理シェルのヘッダーバッジが描画する、リソース単位の中立な権限サマリ。\n *\n * この形はこのパッケージ自身の契約であり、特定の認可ライブラリの型を import しない\n * (`AGENTS.md` のクロスパッケージ・ハードコード禁止)。`canRead` / `canWrite` / `canDelete` を持つ\n * ビューであれば構造的にそのまま渡せる。\n */\nexport type UserAdminPermission = {\n canRead: boolean\n canWrite: boolean\n canDelete: boolean\n}\n\nexport type UserAdminAppConfig = {\n store: UserAdminStore\n /** セクション単位の部分上書き。既定は英語 (`defaultUserAdminLabels`)。 */\n labels?: PartialUserAdminLabels\n /**\n * Required — omitting a guard would expose the whole user directory and its write actions.\n * 必須。省略を許すとユーザー名簿と全更新操作が無防備に露出するため、型で強制する。\n */\n guards: UserAdminGuards\n /**\n * Per-request self-permission lookup for the header annotation. Resolving to a map of\n * {@link UserAdminPermission} shows the badge; rejecting shows an explicit \"unknown\" badge — never a\n * permissive one. Any authz library whose view carries `canRead` / `canWrite` / `canDelete` satisfies it.\n * ヘッダー注釈に自分の実効権限を表示するためのリクエスト別権限取得関数。失敗時は「権限不明」表示。\n */\n getMyPermissions?: (request: Request) => Promise<Record<string, UserAdminPermission | undefined>>\n /**\n * Display names for the resource keys the badge shows, e.g. `{ authz_admin: \"ユーザー管理 UI\" }`.\n * バッジに出すリソースキーの表示名。未指定のキーはキー文字列をそのまま表示。\n *\n * 表示名をこのパッケージ側に埋め込むと、別パッケージのリソースキーを名指しすることになる\n * (`AGENTS.md` のクロスパッケージ・ハードコード禁止)。中立な差し込み口として受け取る。\n */\n resourceLabels?: Record<string, string>\n}\n\n/**\n * Reads a required positive integer id from submitted form data.\n * 送信フォームから必須の正整数 ID を読み取る処理。\n *\n * @throws when the field is missing, non-numeric, or not a positive integer.\n * 欠落・非数値・正整数でない場合は throw。\n */\nfunction requireId(formData: FormData, field: string): number {\n const raw = formData.get(field)\n const n = Number(raw)\n // NaN / 小数 / 0 以下は不正な ID として弾く (Number(null) === 0 の取り違えも防ぐ)\n if (raw === null || String(raw).trim() === \"\" || !Number.isInteger(n) || n <= 0) {\n throw new Error(`Invalid or missing \"${field}\"`)\n }\n return n\n}\n\n/**\n * Reads a required non-empty trimmed string from submitted form data.\n * 送信フォームから必須の非空文字列 (トリム済み) を読み取る処理。\n *\n * @throws when the field is missing or blank. 欠落・空白のみの場合は throw。\n */\nfunction requireText(formData: FormData, field: string): string {\n const value = String(formData.get(field) ?? \"\").trim()\n if (value === \"\") throw new Error(`Invalid or missing \"${field}\"`)\n return value\n}\n\n/**\n * Reads an explicit boolean. 明示的な真偽値の読み取り。\n *\n * `\"true\"` / `\"false\"` のみを受け付ける。欠落を既定へ倒すと、破壊側 (停止) が既定方向になる。\n *\n * @throws when the field is absent or not one of the two literals. 明示指定でない場合。\n */\nfunction requireBoolean(formData: FormData, field: string): boolean {\n const raw = String(formData.get(field) ?? \"\")\n if (raw !== \"true\" && raw !== \"false\") throw new Error(`Invalid or missing \"${field}\"`)\n return raw === \"true\"\n}\n\n/**\n * Reads the membership expansion mode. 所属展開モードの読み取り。\n *\n * 既定へ倒さない。入れ子展開が黙って有効になると権限が静かに広がり、黙って無効になると\n * 入れ子側の全員が削除候補になる。どちらの向きにも倒さず、明示させる。\n *\n * @throws when the value is not one of the two modes. 2 値でない場合。\n */\nfunction requireMembershipMode(formData: FormData): AuthDirectoryMembershipMode {\n const raw = String(formData.get(\"membershipMode\") ?? \"\")\n if (raw !== \"direct\" && raw !== \"transitive\") throw new Error('Invalid or missing \"membershipMode\"')\n return raw\n}\n\n/** Reads an optional trimmed string; blank becomes `undefined`. 任意文字列の読み取り (空は undefined)。 */\nfunction optionalText(formData: FormData, field: string): string | undefined {\n const value = String(formData.get(field) ?? \"\").trim()\n return value === \"\" ? undefined : value\n}\n\n/**\n * Creates React Router loader & action handler for user administration.\n * ユーザー管理用 React Router の loader および action ハンドラーを作成する。\n *\n * `config.guards` は必須。loader / action の双方が、処理を始める前にガードを通過させる。\n */\nexport function createUserAdminApp(config: UserAdminAppConfig) {\n const labels = resolveUserAdminLabels(config.labels)\n\n async function loader({ request }: RouteArgs) {\n // 読取ガード: 拒否は throw されるためここで処理が打ち切られる\n await config.guards.requireAccess(request, { action: \"read\", intent: \"loader\" })\n const url = new URL(request.url)\n const pathSegments = url.pathname.split(\"/\").filter(Boolean)\n const segment = pathSegments[pathSegments.length - 1] ?? \"users\"\n\n const [users, groups, groupMembers, allowlist, directoryStatus, myPermissions] = await Promise.all([\n config.store.listUsers(),\n config.store.listGroups(),\n config.store.listGroupMembers(),\n config.store.listAllowlist(),\n config.store.getDirectoryStatus(),\n // 取得できない場合は null (= 権限不明)。空マップに倒すと「付与ゼロ」と区別できなくなる\n config.getMyPermissions\n ? config.getMyPermissions(request).catch((e) => {\n console.error(\"[@aiquants/auth-react-router] getMyPermissions failed; rendering the permission badge as unknown:\", e)\n return null\n })\n : Promise.resolve(null),\n ])\n\n return {\n resourceLabels: config.resourceLabels,\n segment: [\"groups\", \"group_members\", \"allowlist\", \"directory\"].includes(segment) ? segment : \"users\",\n users,\n groups,\n groupMembers,\n allowlist,\n directoryStatus,\n myPermissions,\n labels,\n }\n }\n\n async function action({ request }: RouteArgs) {\n // intent の判定に FormData が要るため、ここでだけ本文を先に読む。\n // 未知 intent は「何も実行しない」ため、ガード前に読んでも権限判断には影響しない。\n const formData = await request.formData()\n const intent = String(formData.get(\"intent\") ?? \"\")\n\n // 未知 intent はストアへ到達しない (ガードも走らせず 400 相当を返す)。\n // Map 参照のためプロトタイプ継承キー (\"constructor\" 等) を拾わない。\n const access = INTENT_ACCESS.get(intent)\n if (!access) return { ok: false, error: `Unknown intent: ${intent}` }\n\n // 操作ごとの CRUD 種別でガードする (削除は delete 権限が要る)\n await config.guards.requireAccess(request, { action: access, intent })\n\n try {\n return await dispatch(intent, formData)\n } catch (error) {\n // ガード拒否 (Response/redirect) はそのまま伝播させる。ストア由来の業務エラーだけ\n // {ok:false,error} に写して UI へ届ける (ロックアウト理由・重複パターン等)。\n if (error instanceof Response) throw error\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /** intent ごとの実処理。認可・入力検証を通過した後にのみ呼ばれる。 */\n async function dispatch(intent: string, formData: FormData) {\n if (intent === \"createUser\") {\n await config.store.createUser({ displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"updateUser\") {\n await config.store.updateUser(requireId(formData, \"userId\"), { displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"toggleUserActive\") {\n // 破壊側 (無効化) を既定にしない。明示的な \"true\"/\"false\" 以外は不正入力として弾く。\n const rawActive = String(formData.get(\"isActive\") ?? \"\")\n if (rawActive !== \"true\" && rawActive !== \"false\") throw new Error('Invalid or missing \"isActive\"')\n await config.store.toggleUserActive(requireId(formData, \"userId\"), rawActive === \"true\")\n return { ok: true }\n }\n\n if (intent === \"createGroup\") {\n await config.store.createGroup({ groupKey: requireText(formData, \"groupKey\"), name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"updateGroup\") {\n await config.store.updateGroup(requireId(formData, \"groupId\"), { name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"deleteGroup\") {\n await config.store.deleteGroup(requireId(formData, \"groupId\"))\n return { ok: true }\n }\n\n if (intent === \"addGroupMember\") {\n await config.store.addGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"removeGroupMember\") {\n await config.store.removeGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"addAllowlistEntry\") {\n const type: \"email\" | \"domain\" = formData.get(\"type\") === \"domain\" ? \"domain\" : \"email\"\n await config.store.addAllowlistEntry({ pattern: requireText(formData, \"pattern\"), type, description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"addAllowlistGroup\") {\n await config.store.addAllowlistGroup(requireId(formData, \"groupId\"), optionalText(formData, \"description\"))\n return { ok: true }\n }\n if (intent === \"removeAllowlistGroup\") {\n await config.store.removeAllowlistGroup(requireId(formData, \"entryId\"))\n return { ok: true }\n }\n if (intent === \"linkDirectoryGroup\") {\n await config.store.linkDirectoryGroup(requireId(formData, \"groupId\"), {\n provider: requireText(formData, \"provider\"),\n externalKey: requireText(formData, \"externalKey\"),\n membershipMode: requireMembershipMode(formData),\n })\n return { ok: true }\n }\n if (intent === \"unlinkDirectoryGroup\") {\n await config.store.unlinkDirectoryGroup(requireId(formData, \"groupId\"))\n return { ok: true }\n }\n if (intent === \"setDirectorySyncEnabled\") {\n await config.store.setDirectorySyncEnabled(requireId(formData, \"groupId\"), requireBoolean(formData, \"isSyncEnabled\"))\n return { ok: true }\n }\n if (intent === \"requestDirectorySync\") {\n const raw = String(formData.get(\"groupId\") ?? \"\").trim()\n await config.store.requestDirectorySync(raw === \"\" ? null : requireId(formData, \"groupId\"))\n return { ok: true }\n }\n if (intent === \"removeAllowlistEntry\") {\n await config.store.removeAllowlistEntry(requireId(formData, \"entryId\"))\n return { ok: true }\n }\n\n // INTENT_ACCESS を通過した intent は上のいずれかで処理済み (到達しない安全網)\n return { ok: false, error: `Unknown intent: ${intent}` }\n }\n\n return { loader, action }\n}\n","import type React from \"react\"\nimport { Link, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\nimport type { UserAdminPermission } from \"./server\"\n\nexport type UserAdminShellProps = {\n children?: React.ReactNode\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Render one resource's permission summary as a compact pill.\n * 1 リソース分の権限サマリをコンパクトなタグとして描画するコンポーネント。\n */\nfunction GroupPill({ label, summary }: { label: string; summary: { text: string; color: string; bgColor: string; borderColor: string } }) {\n return (\n <span\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"0.18rem\",\n fontSize: \"0.65rem\",\n padding: \"0.08rem 0.35rem\",\n borderRadius: \"9999px\",\n backgroundColor: summary.bgColor,\n color: summary.color,\n border: `1px solid ${summary.borderColor}`,\n fontWeight: 600,\n lineHeight: \"1.2\",\n }}>\n <span>{label}:</span>\n <span>{summary.text}</span>\n </span>\n )\n}\n\n/**\n * Shell layout for user administration pages with header annotation delegation.\n * ヘッダー注釈委譲を備えたユーザー管理ページのシェルレイアウト。\n */\nexport function UserAdminShell({ children, renderHeader }: UserAdminShellProps) {\n const data = useLoaderData() as { segment?: string; labels?: UserAdminLabels; myPermissions?: Record<string, UserAdminPermission | undefined> | null; resourceLabels?: Record<string, string> } | undefined\n const segment = data?.segment ?? \"users\"\n const labels = data?.labels ?? defaultUserAdminLabels\n // null = 未結線または取得失敗。空 {} も「分からない」扱いで、いずれも許可として描画しない\n const myPermissions = data?.myPermissions ?? null\n // リソースキーの表示名は呼び出し側が差し込む (キーをこのパッケージに埋め込まない)\n const resourceLabels = data?.resourceLabels ?? {}\n const labelFor = (resourceKey: string) => (Object.hasOwn(resourceLabels, resourceKey) ? resourceLabels[resourceKey] : resourceKey)\n\n const tabs = [\n { key: \"users\", label: labels.tabs.users, href: \"/users\" },\n { key: \"groups\", label: labels.tabs.groups, href: \"/users/groups\" },\n { key: \"group_members\", label: labels.tabs.groupMembers, href: \"/users/group_members\" },\n { key: \"allowlist\", label: labels.tabs.allowlist, href: \"/users/allowlist\" },\n ]\n\n const permEntries = Object.entries(myPermissions ?? {})\n\n /**\n * Pick the badge text and colours for one resource, never rendering \"unknown\" as \"allowed\".\n * 1 リソース分のバッジ文言と配色を決める処理。**未知を許可として描かない** ことが要件。\n *\n * 以前は権限マップが空のとき「編集可」の固定値を描いていたため、ポート未結線・取得失敗・\n * 付与ゼロのいずれでも「全操作できます」と表示されていた (実際には操作するとガードで拒否される)。\n */\n const getSummary = (perm: UserAdminPermission | undefined) => {\n if (perm?.canWrite) {\n return { text: \"編集可\", color: \"#047857\", bgColor: \"#ecfdf5\", borderColor: \"#a7f3d0\" }\n }\n if (perm?.canRead) {\n return { text: \"閲覧のみ\", color: \"#1d4ed8\", bgColor: \"#eff6ff\", borderColor: \"#bfdbfe\" }\n }\n return { text: \"権限なし\", color: \"#64748b\", bgColor: \"#f1f5f9\", borderColor: \"#cbd5e1\" }\n }\n const unavailableSummary = { text: \"権限不明\", color: \"#92400e\", bgColor: \"#fffbeb\", borderColor: \"#fde68a\" }\n\n const annotation = (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.4rem\" }} data-testid=\"user-admin-header-annotation\">\n <span style={{ fontSize: \"0.75rem\", backgroundColor: \"#e0f2fe\", color: \"#0369a1\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>{labels.heading}</span>\n\n <div style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.25rem\", fontSize: \"0.68rem\", color: \"#475569\" }} data-testid=\"user-admin-permissions-badge\">\n <span style={{ fontWeight: 600, color: \"#334155\" }}>あなたの権限:</span>\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.18rem\" }}>\n {permEntries.length === 0 ? (\n <span data-testid=\"user-admin-permission-unavailable\">\n <GroupPill label={labels.heading} summary={unavailableSummary} />\n </span>\n ) : (\n permEntries.map(([resKey, perm]) => <GroupPill key={resKey} label={labelFor(resKey)} summary={getSummary(perm)} />)\n )}\n </span>\n </div>\n </div>\n )\n\n return (\n <div data-testid=\"user-admin-shell\" style={{ width: \"100%\", minHeight: \"100vh\", backgroundColor: \"#f8fafc\", padding: \"1.5rem\", boxSizing: \"border-box\" }}>\n {renderHeader ? (\n renderHeader({ title: labels.title, annotation })\n ) : (\n <div style={{ marginBottom: \"1.5rem\", display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\" }}>\n <h1 style={{ margin: 0, fontSize: \"1.5rem\", fontWeight: 700, color: \"#0f172a\" }}>{labels.title}</h1>\n {annotation}\n </div>\n )}\n\n <div style={{ display: \"flex\", gap: \"0.5rem\", marginBottom: \"1.2rem\", borderBottom: \"2px solid #e2e8f0\", paddingBottom: \"0.5rem\" }}>\n {tabs.map((tab) => {\n const isActive = segment === tab.key\n return (\n <Link\n key={tab.key}\n to={tab.href}\n data-testid={`user-admin-tab-${tab.key}`}\n style={{\n padding: \"0.5rem 1rem\",\n borderRadius: \"6px 6px 0 0\",\n fontSize: \"0.88rem\",\n fontWeight: isActive ? 700 : 500,\n color: isActive ? \"#0284c7\" : \"#64748b\",\n backgroundColor: isActive ? \"#ffffff\" : \"transparent\",\n textDecoration: \"none\",\n borderBottom: isActive ? \"3px solid #0284c7\" : \"3px solid transparent\",\n transition: \"all 0.15s ease\",\n }}>\n {tab.label}\n </Link>\n )\n })}\n </div>\n\n <main>{children}</main>\n </div>\n )\n}\n","import type { AuthAllowlistEntry, AuthDirectoryStatus, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type React from \"react\"\nimport { useState } from \"react\"\nimport { useFetcher, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\nimport { UserAdminShell, type UserAdminShellProps } from \"./ui\"\n\nexport type UserAdminLoaderData = {\n segment: string\n users: AuthUser[]\n groups: AuthGroup[]\n groupMembers: AuthGroupMember[]\n allowlist: AuthAllowlistEntry[]\n directoryStatus: AuthDirectoryStatus\n labels?: UserAdminLabels\n}\n\n/**\n * View for managing users (list, search, toggle active, modal edit).\n * ユーザー管理ビュー (一覧・検索・アクティブ切替・ダイアログ編集)。\n */\n/**\n * Renders the error returned by the last action submission, if any.\n * 直近の action 送信が返したエラーを表示する部品。\n *\n * ロックアウト理由・重複パターン・入力不正などの文言は action が `{ok:false,error}` で返す。\n * 表示しなければ操作が黙って失敗したように見えるため、各ビューの先頭に必ず置く。\n */\nfunction ActionError({ fetcher }: { fetcher: { data?: unknown } }) {\n const result = fetcher.data as { ok?: boolean; error?: string } | undefined\n if (result?.ok !== false || !result.error) return null\n return (\n <div data-testid=\"user-admin-action-error\" role=\"alert\" style={{ padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #fecaca\", backgroundColor: \"#fef2f2\", color: \"#b91c1c\", fontSize: \"0.85rem\" }}>\n {result.error}\n </div>\n )\n}\n\nexport function UsersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [editingUser, setEditingUser] = useState<AuthUser | null>(null)\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n\n const [newDisplayName, setNewDisplayName] = useState(\"\")\n const [newEmail, setNewEmail] = useState(\"\")\n\n const filteredUsers = data.users.filter((u) => u.displayName.toLowerCase().includes(searchQuery.toLowerCase()) || u.email.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleToggleActive = (user: AuthUser) => {\n fetcher.submit({ intent: \"toggleUserActive\", userId: String(user.id), isActive: String(!user.isActive) }, { method: \"post\" })\n }\n\n const handleCreateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newDisplayName && newEmail)) return\n fetcher.submit({ intent: \"createUser\", displayName: newDisplayName, email: newEmail }, { method: \"post\" })\n setNewDisplayName(\"\")\n setNewEmail(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingUser) return\n fetcher.submit({ intent: \"updateUser\", userId: String(editingUser.id), displayName: editingUser.displayName, email: editingUser.email }, { method: \"post\" })\n setEditingUser(null)\n }\n\n return (\n <section data-testid=\"users-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.usersView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"users-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"users-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.usersView.addUser}\n </button>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.id}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.displayName}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.email}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.status}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.usersView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {filteredUsers.map((u) => (\n <tr key={u.id} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`user-row-${u.id}`}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{u.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\" }}>{u.displayName}</td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{u.email}</td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: u.isActive ? \"#dcfce7\" : \"#fee2e2\", color: u.isActive ? \"#15803d\" : \"#b91c1c\", fontWeight: 600 }}>\n {u.isActive ? labels.usersView.active : labels.usersView.inactive}\n </span>\n </td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n <button\n type=\"button\"\n onClick={() => setEditingUser(u)}\n data-testid={`edit-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", marginRight: \"6px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleToggleActive(u)}\n data-testid={`toggle-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", color: u.isActive ? \"#b91c1c\" : \"#15803d\" }}>\n {u.isActive ? labels.usersView.deactivate : labels.usersView.activate}\n </button>\n </td>\n </tr>\n ))}\n {filteredUsers.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.usersView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Create Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-create-modal\">\n <form onSubmit={handleCreateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.addUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"create-user-display-name-input\"\n type=\"text\"\n value={newDisplayName}\n onChange={(e) => setNewDisplayName(e.target.value)}\n required\n data-testid=\"create-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"create-user-email-input\"\n type=\"email\"\n value={newEmail}\n onChange={(e) => setNewEmail(e.target.value)}\n required\n data-testid=\"create-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Modal */}\n {editingUser && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-edit-modal\">\n <form onSubmit={handleUpdateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.editUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"edit-user-display-name-input\"\n type=\"text\"\n value={editingUser.displayName}\n onChange={(e) => setEditingUser({ ...editingUser, displayName: e.target.value })}\n required\n data-testid=\"edit-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"edit-user-email-input\"\n type=\"email\"\n value={editingUser.email}\n onChange={(e) => setEditingUser({ ...editingUser, email: e.target.value })}\n required\n data-testid=\"edit-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingUser(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.usersView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.usersView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing user groups (create, edit, delete).\n * ユーザーグループ管理ビュー (作成・編集・削除)。\n */\n/**\n * Warns when synchronization is not actually keeping the mirror fresh.\n * 同期が実際には鏡を新しく保てていないことを警告する部品。\n *\n * 同期の停止は「上流から外れた人がテナントに居続ける」というセキュリティ事象である。件数と\n * エラー数だけを見ると何も起きていないように読めるため、**滞留・全停止・未結線の 3 つを\n * それぞれ名指しで** 出す。出さなければ、状態を計算している意味がない。\n */\nfunction DirectorySyncBanner({ status, labels }: { status: AuthDirectoryStatus; labels: UserAdminLabels }) {\n if (status.linkedGroupCount === 0) return null\n const warnings: Array<{ id: string; text: string }> = []\n if (!status.isEnabled) warnings.push({ id: \"disabled\", text: labels.directoryView.disabledWarning })\n if (status.pausedGroupCount > 0) warnings.push({ id: \"paused\", text: `${labels.directoryView.syncPaused}: ${status.pausedGroupCount} / ${status.linkedGroupCount}` })\n if (status.isStale) warnings.push({ id: \"stale\", text: labels.directoryView.staleWarning })\n if (status.erroredGroupIds.length > 0) warnings.push({ id: \"errored\", text: `${labels.directoryView.title}: ${status.erroredGroupIds.join(\", \")}` })\n if (warnings.length === 0) return null\n return (\n <div data-testid=\"directory-sync-banner\" role=\"status\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.35rem\", padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #fde68a\", backgroundColor: \"#fffbeb\", color: \"#92400e\", fontSize: \"0.85rem\" }}>\n {warnings.map((w) => (\n <span key={w.id} data-directory-warning={w.id}>\n {w.text}\n </span>\n ))}\n </div>\n )\n}\n\nexport function GroupsView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n const [editingGroup, setEditingGroup] = useState<AuthGroup | null>(null)\n\n const [newGroupKey, setNewGroupKey] = useState(\"\")\n const [newName, setNewName] = useState(\"\")\n const [newDescription, setNewDescription] = useState(\"\")\n\n const filteredGroups = data.groups.filter((g) => g.name.toLowerCase().includes(searchQuery.toLowerCase()) || g.groupKey.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleCreateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newGroupKey && newName)) return\n fetcher.submit({ intent: \"createGroup\", groupKey: newGroupKey, name: newName, description: newDescription }, { method: \"post\" })\n setNewGroupKey(\"\")\n setNewName(\"\")\n setNewDescription(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingGroup) return\n fetcher.submit({ intent: \"updateGroup\", groupId: String(editingGroup.id), name: editingGroup.name, description: editingGroup.description ?? \"\" }, { method: \"post\" })\n setEditingGroup(null)\n }\n\n const handleDeleteGroup = (groupId: number) => {\n if (!window.confirm(labels.groupsView.confirmDelete)) return\n fetcher.submit({ intent: \"deleteGroup\", groupId: String(groupId) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"groups-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <DirectorySyncBanner status={data.directoryStatus} labels={labels} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.groupsView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"groups-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"groups-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.groupsView.addGroup}\n </button>\n </div>\n\n <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(280px, 1fr))\", gap: \"1rem\" }}>\n {filteredGroups.map((g) => {\n const memberCount = data.groupMembers.filter((m) => m.groupId === g.id).length\n return (\n <div\n key={g.id}\n style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"1rem\", display: \"flex\", flexDirection: \"column\", justifyContent: \"space-between\" }}\n data-testid={`group-card-${g.id}`}\n data-group-readonly={g.readOnly ? \"true\" : \"false\"}>\n <div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: \"0.5rem\" }}>\n <h4 style={{ margin: 0, fontSize: \"1rem\", color: \"#0f172a\" }}>{g.name}</h4>\n <span style={{ fontSize: \"0.72rem\", backgroundColor: \"#e0e7ff\", color: \"#4338ca\", padding: \"2px 6px\", borderRadius: \"4px\", fontFamily: \"monospace\" }}>{g.groupKey}</span>\n </div>\n <p style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.82rem\", color: \"#64748b\", minHeight: \"2.4em\" }}>{g.description || labels.groupsView.noDescription}</p>\n {/* 所属も名称も上流が所有していることを、操作を試す前に伝える */}\n {g.externalLink && (\n <div data-testid={`group-source-${g.id}`} style={{ display: \"flex\", alignItems: \"center\", gap: \"6px\", flexWrap: \"wrap\", marginBottom: \"0.6rem\" }}>\n <span style={{ fontSize: \"0.72rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: \"#ede9fe\", color: \"#6d28d9\", fontWeight: 600 }}>{labels.directoryView.managedUpstream}</span>\n <span style={{ fontSize: \"0.72rem\", fontFamily: \"monospace\", color: \"#64748b\" }}>{g.externalLink.externalKey}</span>\n {g.externalLink.unresolvedCount > 0 && (\n <span data-testid={`group-unresolved-${g.id}`} style={{ fontSize: \"0.72rem\", color: \"#b45309\" }}>\n {g.externalLink.unresolvedCount} {labels.directoryView.unresolved}\n </span>\n )}\n </div>\n )}\n </div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", borderTop: \"1px solid #f1f5f9\", paddingTop: \"0.6rem\" }}>\n <span style={{ fontSize: \"0.78rem\", color: \"#475569\" }}>\n 👥 <strong>{memberCount}</strong>\n </span>\n {/* 上流が所有する行では操作を **描かない**。無効化したボタンを出すと、\n 押せる操作と見分けがつかず、押せない理由も伝わらない */}\n {g.readOnly ? (\n <span data-testid={`readonly-group-${g.id}`} style={{ fontSize: \"0.75rem\", color: \"#94a3b8\" }}>\n {labels.allowlistView.readOnly}\n </span>\n ) : (\n <div>\n <button\n type=\"button\"\n onClick={() => setEditingGroup(g)}\n data-testid={`edit-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.75rem\", cursor: \"pointer\", marginRight: \"4px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeleteGroup(g.id)}\n data-testid={`delete-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.75rem\", cursor: \"pointer\" }}>\n {labels.allowlistView.delete}\n </button>\n </div>\n )}\n </div>\n </div>\n )\n })}\n </div>\n\n {/* Create Group Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-create-modal\">\n <form onSubmit={handleCreateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.addGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-key-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.groupKey}\n </label>\n <input\n id=\"create-group-key-input\"\n type=\"text\"\n value={newGroupKey}\n onChange={(e) => setNewGroupKey(e.target.value)}\n placeholder={labels.groupsView.groupKeyPlaceholder}\n required\n data-testid=\"create-group-key\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"create-group-name-input\"\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder={labels.groupsView.namePlaceholder}\n required\n data-testid=\"create-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"create-group-desc-input\"\n value={newDescription}\n onChange={(e) => setNewDescription(e.target.value)}\n data-testid=\"create-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Group Modal */}\n {editingGroup && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-edit-modal\">\n <form onSubmit={handleUpdateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.editGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"edit-group-name-input\"\n type=\"text\"\n value={editingGroup.name}\n onChange={(e) => setEditingGroup({ ...editingGroup, name: e.target.value })}\n required\n data-testid=\"edit-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"edit-group-desc-input\"\n value={editingGroup.description ?? \"\"}\n onChange={(e) => setEditingGroup({ ...editingGroup, description: e.target.value })}\n data-testid=\"edit-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingGroup(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.groupsView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.groupsView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing group member assignments.\n * グループメンバー割り当て管理ビュー。\n */\nexport function GroupMembersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [selectedGroupId, setSelectedGroupId] = useState<number>(data.groups[0]?.id ?? 0)\n\n const currentGroup = data.groups.find((g) => g.id === selectedGroupId)\n const currentMemberUserIds = new Set(data.groupMembers.filter((m) => m.groupId === selectedGroupId).map((m) => m.userId))\n\n const handleToggleMember = (userId: number, isMember: boolean) => {\n if (isMember) {\n fetcher.submit({ intent: \"removeGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n } else {\n fetcher.submit({ intent: \"addGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n }\n }\n\n return (\n <section data-testid=\"group-members-view-section\" style={{ display: \"grid\", gridTemplateColumns: \"240px 1fr\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <h4 style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.9rem\", color: \"#475569\" }}>{labels.groupMembersView.selectGroup}</h4>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.4rem\" }}>\n {data.groups.map((g) => {\n const isSelected = g.id === selectedGroupId\n return (\n <button\n type=\"button\"\n key={g.id}\n onClick={() => setSelectedGroupId(g.id)}\n data-testid={`select-group-btn-${g.id}`}\n style={{\n textAlign: \"left\",\n padding: \"8px 10px\",\n borderRadius: \"6px\",\n border: isSelected ? \"1px solid #6366f1\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#e0e7ff\" : \"#ffffff\",\n color: isSelected ? \"#3730a3\" : \"#0f172a\",\n cursor: \"pointer\",\n fontWeight: isSelected ? 600 : 400,\n fontSize: \"0.85rem\",\n }}>\n {g.name}\n </button>\n )\n })}\n </div>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n {currentGroup ? (\n <>\n <div style={{ marginBottom: \"1rem\", borderBottom: \"1px solid #e2e8f0\", paddingBottom: \"0.8rem\" }}>\n <h3 style={{ margin: 0, fontSize: \"1.1rem\", color: \"#0f172a\" }}>{currentGroup.name} の所属メンバー</h3>\n <span style={{ fontSize: \"0.8rem\", color: \"#64748b\" }}>グループキー: {currentGroup.groupKey}</span>\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\" }}>\n {data.users.map((u) => {\n const isMember = currentMemberUserIds.has(u.id)\n return (\n <div\n key={u.id}\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: \"8px 12px\",\n borderRadius: \"6px\",\n border: \"1px solid #f1f5f9\",\n backgroundColor: isMember ? \"#f0fdf4\" : \"#ffffff\",\n }}\n data-testid={`member-row-${u.id}`}>\n <div>\n <span style={{ fontWeight: 600, fontSize: \"0.88rem\", color: \"#0f172a\", marginRight: \"8px\" }}>{u.displayName}</span>\n <span style={{ fontSize: \"0.78rem\", color: \"#64748b\" }}>({u.email})</span>\n </div>\n <button\n type=\"button\"\n onClick={() => handleToggleMember(u.id, isMember)}\n data-testid={`toggle-member-btn-${u.id}`}\n style={{\n padding: \"4px 12px\",\n borderRadius: \"4px\",\n border: isMember ? \"1px solid #b91c1c\" : \"1px solid #0284c7\",\n backgroundColor: isMember ? \"#ffffff\" : \"#0284c7\",\n color: isMember ? \"#b91c1c\" : \"#ffffff\",\n fontSize: \"0.78rem\",\n fontWeight: 600,\n cursor: \"pointer\",\n }}>\n {isMember ? labels.groupMembersView.actionRemove : labels.groupMembersView.actionAdd}\n </button>\n </div>\n )\n })}\n </div>\n </>\n ) : (\n <div style={{ color: \"#94a3b8\", textAlign: \"center\", padding: \"2rem\" }}>{labels.groupMembersView.selectPrompt}</div>\n )}\n </div>\n </section>\n )\n}\n\n/** Badge colours per allowlist kind. 許可種別ごとのバッジ配色。 */\nfunction allowlistBadgeStyle(type: AuthAllowlistEntry[\"type\"]): { backgroundColor: string; color: string } {\n if (type === \"email\") return { backgroundColor: \"#e0f2fe\", color: \"#0369a1\" }\n if (type === \"domain\") return { backgroundColor: \"#fef3c7\", color: \"#b45309\" }\n return { backgroundColor: \"#ede9fe\", color: \"#6d28d9\" }\n}\n\n/** Badge text per allowlist kind. 許可種別ごとのバッジ文言。 */\nfunction allowlistTypeLabel(type: AuthAllowlistEntry[\"type\"], labels: UserAdminLabels): string {\n if (type === \"email\") return labels.allowlistView.emailTypeShort\n if (type === \"domain\") return labels.allowlistView.domainTypeShort\n return labels.allowlistView.groupTypeShort\n}\n\n/**\n * View for managing authentication allowlist entries.\n * アクセス許可リスト (Allowlist) 管理ビュー。\n */\nexport function AllowlistView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [pattern, setPattern] = useState(\"\")\n const [type, setType] = useState<\"email\" | \"domain\">(\"email\")\n const [description, setDescription] = useState(\"\")\n\n const handleAddEntry = (e: React.FormEvent) => {\n e.preventDefault()\n if (!pattern) return\n fetcher.submit({ intent: \"addAllowlistEntry\", pattern, type, description }, { method: \"post\" })\n setPattern(\"\")\n setDescription(\"\")\n }\n\n const handleRemoveEntry = (id: number) => {\n fetcher.submit({ intent: \"removeAllowlistEntry\", entryId: String(id) }, { method: \"post\" })\n }\n\n /** グループ許可は別の表に住むため、別の intent で消す。 */\n const handleRemoveGroupEntry = (id: number) => {\n fetcher.submit({ intent: \"removeAllowlistGroup\", entryId: String(id) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"allowlist-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <form onSubmit={handleAddEntry} style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", display: \"flex\", flexWrap: \"wrap\", gap: \"0.8rem\", alignItems: \"flex-end\" }}>\n <div style={{ flex: 2, minWidth: \"220px\" }}>\n <label htmlFor=\"allowlist-pattern-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.pattern}\n </label>\n <input\n id=\"allowlist-pattern-input-field\"\n type=\"text\"\n value={pattern}\n onChange={(e) => setPattern(e.target.value)}\n required\n data-testid=\"allowlist-pattern-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ flex: 1, minWidth: \"140px\" }}>\n <label htmlFor=\"allowlist-type-select-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.type}\n </label>\n <select\n id=\"allowlist-type-select-field\"\n value={type}\n onChange={(e) => setType(e.target.value as \"email\" | \"domain\")}\n data-testid=\"allowlist-type-select\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}>\n <option value=\"email\">{labels.allowlistView.emailType}</option>\n <option value=\"domain\">{labels.allowlistView.domainType}</option>\n </select>\n </div>\n <div style={{ flex: 2, minWidth: \"200px\" }}>\n <label htmlFor=\"allowlist-desc-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.description}\n </label>\n <input\n id=\"allowlist-desc-input-field\"\n type=\"text\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n data-testid=\"allowlist-desc-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <button type=\"submit\" data-testid=\"submit-allowlist-add\" style={{ padding: \"7px 16px\", borderRadius: \"6px\", backgroundColor: \"#047857\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.allowlistView.addEntry}\n </button>\n </form>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>ID</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.target}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.type}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.description}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.allowlistView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {data.allowlist.map((item) => (\n <tr key={`${item.type}-${item.id}`} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`allowlist-row-${item.id}`} data-allowlist-type={item.type}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{item.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\" }}>\n {/* パターン許可とグループ許可では正準値が違う。グループはアドレスではなく\n グループそのものが対象なので、名前とキーを見せる */}\n {item.type === \"group\" ? (\n <span data-testid={`allowlist-group-${item.id}`}>\n {item.groupName}\n <span style={{ marginLeft: \"6px\", fontFamily: \"monospace\", fontSize: \"0.78rem\", color: \"#64748b\" }}>{item.groupKey}</span>\n </span>\n ) : (\n <span style={{ fontFamily: \"monospace\" }}>{item.pattern}</span>\n )}\n </td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", ...allowlistBadgeStyle(item.type), fontWeight: 600 }}>{allowlistTypeLabel(item.type, labels)}</span>\n </td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>\n {item.description || \"-\"}\n {/* 配備全体が所有する行は、どのテナントの管理者でも編集できない */}\n {item.owner.kind === \"deployment\" && (\n <span data-testid={`allowlist-owner-${item.id}`} style={{ marginLeft: \"8px\", fontSize: \"0.72rem\", color: \"#94a3b8\" }}>\n {labels.allowlistView.deploymentOwner}\n </span>\n )}\n </td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n {/* 変更不能な供給元 (環境変数など) は削除ボタンを出さない。無効化ではなく\n 非描画にするのは、押せないボタンが押せる操作と見分けられないため */}\n {item.readOnly ? (\n <span data-testid={`readonly-allowlist-${item.id}`} style={{ fontSize: \"0.75rem\", color: \"#94a3b8\" }}>\n {labels.allowlistView.readOnly}\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={() => (item.type === \"group\" ? handleRemoveGroupEntry(item.id) : handleRemoveEntry(item.id))}\n data-testid={`remove-allowlist-btn-${item.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.78rem\", cursor: \"pointer\" }}>\n {labels.allowlistView.delete}\n </button>\n )}\n </td>\n </tr>\n ))}\n {data.allowlist.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.allowlistView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </section>\n )\n}\n\nconst SEGMENT_VIEWS: Record<string, () => React.ReactElement> = {\n users: UsersView,\n groups: GroupsView,\n group_members: GroupMembersView,\n allowlist: AllowlistView,\n}\n\nexport type AuthUserAdminAppViewProps = UserAdminShellProps\n\n/**\n * Top-level application view component for the user administration UI (`/users/*`).\n * ユーザー管理 UI 全体を描画するトップレベルコンポーネント。\n */\nexport function AuthUserAdminAppView({ renderHeader }: AuthUserAdminAppViewProps = {}) {\n const data = useLoaderData() as UserAdminLoaderData | undefined\n const segment = data?.segment ?? \"users\"\n const View = SEGMENT_VIEWS[segment] ?? UsersView\n\n return (\n <UserAdminShell renderHeader={renderHeader}>\n <View />\n </UserAdminShell>\n )\n}\n"],"mappings":"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","names":["defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","over","d","createInMemoryUserAdminStore","initial","users","groups","groupMembers","allowlist","data","newUser","u","id","idx","isActive","newGroup","g","m","groupId","userId","existing","newMember","newEntry","a","description","group","INTENT_ACCESS","requireId","formData","field","raw","n","requireText","value","requireBoolean","requireMembershipMode","optionalText","createUserAdminApp","config","labels","resolveUserAdminLabels","loader","request","pathSegments","segment","directoryStatus","myPermissions","e","action","intent","access","dispatch","error","rawActive","type","Link","useLoaderData","jsx","jsxs","GroupPill","label","summary","UserAdminShell","children","renderHeader","data","useLoaderData","segment","labels","defaultUserAdminLabels","myPermissions","resourceLabels","labelFor","resourceKey","tabs","permEntries","getSummary","perm","unavailableSummary","annotation","resKey","tab","isActive","Link","useState","useFetcher","useLoaderData","Fragment","jsx","jsxs","ActionError","fetcher","result","UsersView","data","useLoaderData","useFetcher","labels","defaultUserAdminLabels","searchQuery","setSearchQuery","useState","editingUser","setEditingUser","isCreateOpen","setIsCreateOpen","newDisplayName","setNewDisplayName","newEmail","setNewEmail","filteredUsers","u","handleToggleActive","user","handleCreateUser","e","handleUpdateUser","DirectorySyncBanner","status","warnings","w","GroupsView","editingGroup","setEditingGroup","newGroupKey","setNewGroupKey","newName","setNewName","newDescription","setNewDescription","filteredGroups","g","handleCreateGroup","handleUpdateGroup","handleDeleteGroup","groupId","memberCount","m","GroupMembersView","selectedGroupId","setSelectedGroupId","currentGroup","currentMemberUserIds","handleToggleMember","userId","isMember","isSelected","allowlistBadgeStyle","type","allowlistTypeLabel","AllowlistView","pattern","setPattern","setType","description","setDescription","handleAddEntry","handleRemoveEntry","id","handleRemoveGroupEntry","item","SEGMENT_VIEWS","AuthUserAdminAppView","renderHeader","segment","View","UserAdminShell"]}
|