From 19f173a22d193a77fcb9dacdac7b776b7c998c3d Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 12:46:54 -0500 Subject: [PATCH 01/12] display group names in IAM tables (no API yet) --- libs/api/roles.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/api/roles.ts b/libs/api/roles.ts index 9ef084231d..aae6d1be70 100644 --- a/libs/api/roles.ts +++ b/libs/api/roles.ts @@ -92,6 +92,7 @@ export function setUserRole( type UserAccessRow = { id: string + type: 'user' | 'group' name: string roleName: Role roleSource: string @@ -111,15 +112,19 @@ export function useUserRows( // HACK: because the policy has no names, we are fetching ~all the users, // putting them in a dictionary, and adding the names to the rows const { data: users } = useApiQuery('userList', {}) + const { data: groups } = useApiQuery('userGroupList', {}) return useMemo(() => { - const usersDict = Object.fromEntries((users?.items || []).map((u) => [u.id, u])) + const userItems = users?.items || [] + const groupItems = groups?.items || [] + const usersDict = Object.fromEntries(userItems.concat(groupItems).map((u) => [u.id, u])) return (roleAssignments || []).map((ra) => ({ id: ra.identityId, + type: ra.identityType === 'silo_group' ? 'group' : 'user', name: usersDict[ra.identityId]?.displayName || '', // placeholder until we get names, obviously roleName: ra.roleName, roleSource, })) - }, [roleAssignments, roleSource, users]) + }, [roleAssignments, roleSource, users, groups]) } /** From b99daf777c0a76072c3dcff01616bd5057d8cdf6 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 13:05:34 -0500 Subject: [PATCH 02/12] show GROUP badge on groups --- app/pages/OrgAccessPage.tsx | 15 ++++++++++++++- app/pages/project/access/ProjectAccessPage.tsx | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/pages/OrgAccessPage.tsx b/app/pages/OrgAccessPage.tsx index 20ceb07d0e..1b8a1ac9f0 100644 --- a/app/pages/OrgAccessPage.tsx +++ b/app/pages/OrgAccessPage.tsx @@ -16,6 +16,7 @@ import { useApiQuery } from '@oxide/api' import { Table, getActionsCol } from '@oxide/table' import { Access24Icon, + Badge, Button, EmptyMessage, PageHeader, @@ -52,6 +53,7 @@ OrgAccessPage.loader = async ({ params }: LoaderFunctionArgs) => { type UserRow = { id: string + type: 'user' | 'group' name: string siloRole: SiloRole | undefined orgRole: OrganizationRole | undefined @@ -82,6 +84,7 @@ export function OrgAccessPage() { const row: UserRow = { id: userId, + type: userAssignments[0].type, name: userAssignments[0].name, siloRole, orgRole, @@ -107,7 +110,17 @@ export function OrgAccessPage() { const columns = useMemo( () => [ colHelper.accessor('id', { header: 'ID' }), - colHelper.accessor('name', { header: 'Name' }), + colHelper.accessor('name', { + header: 'Name', + cell: (info) => ( + <> + {info.getValue()} + {info.row.original.type === 'group' ? ( + Group + ) : null} + + ), + }), colHelper.accessor('siloRole', { header: 'Silo role', cell: RoleBadgeCell, diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index c738213478..e155cc0b45 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -16,6 +16,7 @@ import { useApiQuery } from '@oxide/api' import { Table, getActionsCol } from '@oxide/table' import { Access24Icon, + Badge, Button, EmptyMessage, PageHeader, @@ -57,6 +58,7 @@ ProjectAccessPage.loader = async ({ params }: LoaderFunctionArgs) => { type UserRow = { id: string + type: 'user' | 'group' name: string siloRole: SiloRole | undefined orgRole: OrganizationRole | undefined @@ -95,6 +97,7 @@ export function ProjectAccessPage() { const row: UserRow = { id: userId, + type: userAssignments[0].type, name: userAssignments[0].name, siloRole, orgRole, @@ -121,7 +124,17 @@ export function ProjectAccessPage() { const columns = useMemo( () => [ colHelper.accessor('id', { header: 'ID' }), - colHelper.accessor('name', { header: 'Name' }), + colHelper.accessor('name', { + header: 'Name', + cell: (info) => ( + <> + {info.getValue()} + {info.row.original.type === 'group' ? ( + Group + ) : null} + + ), + }), colHelper.accessor('siloRole', { header: 'Silo role', cell: RoleBadgeCell, From cbac575ace29c07eaa834b245a2b0e40a47ecbe7 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 13:09:47 -0500 Subject: [PATCH 03/12] share AccessNameCell --- app/components/AccessNameCell.tsx | 19 +++++++++++++++++++ app/pages/OrgAccessPage.tsx | 14 ++------------ .../project/access/ProjectAccessPage.tsx | 14 ++------------ 3 files changed, 23 insertions(+), 24 deletions(-) create mode 100644 app/components/AccessNameCell.tsx diff --git a/app/components/AccessNameCell.tsx b/app/components/AccessNameCell.tsx new file mode 100644 index 0000000000..cd27992189 --- /dev/null +++ b/app/components/AccessNameCell.tsx @@ -0,0 +1,19 @@ +import type { CellContext } from '@tanstack/react-table' + +import { Badge } from '@oxide/ui' + +/** + * Display the user or group name. If the row is for a group, add a GROUP badge. + */ +export const AccessNameCell = ( + info: CellContext +) => { + const name = info.getValue() + const identityType = info.row.original.type + return ( + <> + {name} + {identityType === 'group' ? Group : null} + + ) +} diff --git a/app/pages/OrgAccessPage.tsx b/app/pages/OrgAccessPage.tsx index 1b8a1ac9f0..95af7d8d02 100644 --- a/app/pages/OrgAccessPage.tsx +++ b/app/pages/OrgAccessPage.tsx @@ -16,7 +16,6 @@ import { useApiQuery } from '@oxide/api' import { Table, getActionsCol } from '@oxide/table' import { Access24Icon, - Badge, Button, EmptyMessage, PageHeader, @@ -26,6 +25,7 @@ import { } from '@oxide/ui' import { groupBy, isTruthy, sortBy } from '@oxide/util' +import { AccessNameCell } from 'app/components/AccessNameCell' import { RoleBadgeCell } from 'app/components/RoleBadgeCell' import { OrgAccessAddUserSideModal, OrgAccessEditUserSideModal } from 'app/forms/org-access' import { requireOrgParams, useRequiredParams } from 'app/hooks' @@ -110,17 +110,7 @@ export function OrgAccessPage() { const columns = useMemo( () => [ colHelper.accessor('id', { header: 'ID' }), - colHelper.accessor('name', { - header: 'Name', - cell: (info) => ( - <> - {info.getValue()} - {info.row.original.type === 'group' ? ( - Group - ) : null} - - ), - }), + colHelper.accessor('name', { header: 'Name', cell: AccessNameCell }), colHelper.accessor('siloRole', { header: 'Silo role', cell: RoleBadgeCell, diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index e155cc0b45..be1ade146f 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -16,7 +16,6 @@ import { useApiQuery } from '@oxide/api' import { Table, getActionsCol } from '@oxide/table' import { Access24Icon, - Badge, Button, EmptyMessage, PageHeader, @@ -26,6 +25,7 @@ import { } from '@oxide/ui' import { groupBy, isTruthy, sortBy } from '@oxide/util' +import { AccessNameCell } from 'app/components/AccessNameCell' import { RoleBadgeCell } from 'app/components/RoleBadgeCell' import { ProjectAccessAddUserSideModal, @@ -124,17 +124,7 @@ export function ProjectAccessPage() { const columns = useMemo( () => [ colHelper.accessor('id', { header: 'ID' }), - colHelper.accessor('name', { - header: 'Name', - cell: (info) => ( - <> - {info.getValue()} - {info.row.original.type === 'group' ? ( - Group - ) : null} - - ), - }), + colHelper.accessor('name', { header: 'Name', cell: AccessNameCell }), colHelper.accessor('siloRole', { header: 'Silo role', cell: RoleBadgeCell, From 043965058a007ce1f05cd6e462c9aff4a87cbc73 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 13:18:22 -0500 Subject: [PATCH 04/12] rename TData -> RowData --- app/components/AccessNameCell.tsx | 4 ++-- app/components/RoleBadgeCell.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/components/AccessNameCell.tsx b/app/components/AccessNameCell.tsx index cd27992189..43ee211467 100644 --- a/app/components/AccessNameCell.tsx +++ b/app/components/AccessNameCell.tsx @@ -5,8 +5,8 @@ import { Badge } from '@oxide/ui' /** * Display the user or group name. If the row is for a group, add a GROUP badge. */ -export const AccessNameCell = ( - info: CellContext +export const AccessNameCell = ( + info: CellContext ) => { const name = info.getValue() const identityType = info.row.original.type diff --git a/app/components/RoleBadgeCell.tsx b/app/components/RoleBadgeCell.tsx index f8a9a0ff17..4b1dd47088 100644 --- a/app/components/RoleBadgeCell.tsx +++ b/app/components/RoleBadgeCell.tsx @@ -12,8 +12,8 @@ type Role = SiloRole | OrganizationRole | ProjectRole * because it is the "stronger" role, i.e., it strictly includes the perms on * viewer. So collab is highlighted as the "effective" role. */ -export const RoleBadgeCell = ( - info: CellContext +export const RoleBadgeCell = ( + info: CellContext ) => { const cellRole = info.getValue() if (!cellRole) return null From 869e42e1f7102bd2046f4933534fa64466464810 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 13:29:01 -0500 Subject: [PATCH 05/12] add groups to mock data --- libs/api-mocks/role-assignment.ts | 15 +++++++++++++++ libs/api-mocks/user-group.ts | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 libs/api-mocks/user-group.ts diff --git a/libs/api-mocks/role-assignment.ts b/libs/api-mocks/role-assignment.ts index 744d832543..a5c3519434 100644 --- a/libs/api-mocks/role-assignment.ts +++ b/libs/api-mocks/role-assignment.ts @@ -9,6 +9,7 @@ import { org } from './org' import { project } from './project' import { defaultSilo } from './silo' import { user1, user2, user3 } from './user' +import { group1, group2 } from './user-group' // For most other resources, we can store the API types directly in the DB. But // in this case the API response doesn't have the resource ID on it, and we need @@ -37,6 +38,13 @@ export const roleAssignments: DbRoleAssignment[] = [ identity_type: 'silo_user', role_name: 'viewer', }, + { + resource_type: 'organization', + resource_id: org.id, + identity_id: group1.id, + identity_type: 'silo_group', + role_name: 'collaborator', + }, { resource_type: 'project', resource_id: project.id, @@ -44,4 +52,11 @@ export const roleAssignments: DbRoleAssignment[] = [ identity_type: 'silo_user', role_name: 'collaborator', }, + { + resource_type: 'project', + resource_id: project.id, + identity_id: group2.id, + identity_type: 'silo_group', + role_name: 'viewer', + }, ] diff --git a/libs/api-mocks/user-group.ts b/libs/api-mocks/user-group.ts new file mode 100644 index 0000000000..1b28de9919 --- /dev/null +++ b/libs/api-mocks/user-group.ts @@ -0,0 +1,15 @@ +import type { Json } from './json-type' + +// import type { Group } from '@oxide/api' + +type UserGroup = { id: string; displayName: string } + +export const group1: Json = { + id: 'group-1', + display_name: 'web-devs', +} + +export const group2: Json = { + id: 'group-2', + display_name: 'kernal-devs', +} From 5ce8cab21355259b67de80bae38052129233d812 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 14:03:27 -0500 Subject: [PATCH 06/12] add /user-groups mock endpoint --- libs/api-mocks/index.ts | 1 + libs/api-mocks/msw/db.ts | 1 + libs/api-mocks/msw/handlers.ts | 7 ++++--- libs/api-mocks/role-assignment.ts | 6 +++--- libs/api-mocks/user-group.ts | 10 ++++++---- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/libs/api-mocks/index.ts b/libs/api-mocks/index.ts index 10f6e77b21..3f05ab4ae7 100644 --- a/libs/api-mocks/index.ts +++ b/libs/api-mocks/index.ts @@ -11,6 +11,7 @@ export * from './session' export * from './snapshot' export * from './sshKeys' export * from './user' +export * from './user-group' export * from './vpc' export { handlers } from './msw/handlers' diff --git a/libs/api-mocks/msw/db.ts b/libs/api-mocks/msw/db.ts index 7607446b93..d2f5302bba 100644 --- a/libs/api-mocks/msw/db.ts +++ b/libs/api-mocks/msw/db.ts @@ -153,6 +153,7 @@ export function lookupSshKey(params: PP.SshKey): Result> { const initDb = { disks: [...mock.disks], globalImages: [...mock.globalImages], + userGroups: [...mock.userGroups], images: [...mock.images], instances: [mock.instance], networkInterfaces: [mock.networkInterface], diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts index 49e3928ce8..77791060d2 100644 --- a/libs/api-mocks/msw/handlers.ts +++ b/libs/api-mocks/msw/handlers.ts @@ -1139,13 +1139,14 @@ export const handlers = [ } ), - // note that in the API this is meant for system users, but that could change. - // kind of a hack to pretend it's about normal users. - // see https://github.com/oxidecomputer/omicron/issues/1235 rest.get | GetErr>('/users', (req, res) => { return res(json(paginated(req.url.search, db.users))) }), + rest.get | GetErr>('/user-groups', (req, res) => { + return res(json(paginated(req.url.search, db.userGroups))) + }), + rest.post, never, PostErr>( '/device/confirm', async (req, res, ctx) => { diff --git a/libs/api-mocks/role-assignment.ts b/libs/api-mocks/role-assignment.ts index a5c3519434..7f008648d1 100644 --- a/libs/api-mocks/role-assignment.ts +++ b/libs/api-mocks/role-assignment.ts @@ -9,7 +9,7 @@ import { org } from './org' import { project } from './project' import { defaultSilo } from './silo' import { user1, user2, user3 } from './user' -import { group1, group2 } from './user-group' +import { userGroup1, userGroup2 } from './user-group' // For most other resources, we can store the API types directly in the DB. But // in this case the API response doesn't have the resource ID on it, and we need @@ -41,7 +41,7 @@ export const roleAssignments: DbRoleAssignment[] = [ { resource_type: 'organization', resource_id: org.id, - identity_id: group1.id, + identity_id: userGroup1.id, identity_type: 'silo_group', role_name: 'collaborator', }, @@ -55,7 +55,7 @@ export const roleAssignments: DbRoleAssignment[] = [ { resource_type: 'project', resource_id: project.id, - identity_id: group2.id, + identity_id: userGroup2.id, identity_type: 'silo_group', role_name: 'viewer', }, diff --git a/libs/api-mocks/user-group.ts b/libs/api-mocks/user-group.ts index 1b28de9919..e7169d1d29 100644 --- a/libs/api-mocks/user-group.ts +++ b/libs/api-mocks/user-group.ts @@ -4,12 +4,14 @@ import type { Json } from './json-type' type UserGroup = { id: string; displayName: string } -export const group1: Json = { - id: 'group-1', +export const userGroup1: Json = { + id: 'user-group-1', display_name: 'web-devs', } -export const group2: Json = { - id: 'group-2', +export const userGroup2: Json = { + id: 'user-group-2', display_name: 'kernal-devs', } + +export const userGroups = [userGroup1, userGroup2] From 77bb016990c0d3e42c02b2d170b99bc3c0895554 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 14:17:05 -0500 Subject: [PATCH 07/12] hard code response so CI passes --- libs/api/roles.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libs/api/roles.ts b/libs/api/roles.ts index aae6d1be70..693fa0b4a8 100644 --- a/libs/api/roles.ts +++ b/libs/api/roles.ts @@ -112,7 +112,14 @@ export function useUserRows( // HACK: because the policy has no names, we are fetching ~all the users, // putting them in a dictionary, and adding the names to the rows const { data: users } = useApiQuery('userList', {}) - const { data: groups } = useApiQuery('userGroupList', {}) + const { data: groups } = { + data: { + items: [ + { id: 'user-group-1', displayName: 'web-devs' }, + { id: 'user-group-2', displayName: 'kernel-devs' }, + ], + }, + } // useApiQuery('userGroupList', {}) return useMemo(() => { const userItems = users?.items || [] const groupItems = groups?.items || [] From 9141d208c21a981dc9462e3e5607884a533d2dc9 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 14:56:26 -0500 Subject: [PATCH 08/12] put the margin on the badge, not the name --- app/components/AccessNameCell.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/components/AccessNameCell.tsx b/app/components/AccessNameCell.tsx index 43ee211467..6c1b768061 100644 --- a/app/components/AccessNameCell.tsx +++ b/app/components/AccessNameCell.tsx @@ -12,8 +12,12 @@ export const AccessNameCell = - {name} - {identityType === 'group' ? Group : null} + {name} + {identityType === 'group' ? ( + + Group + + ) : null} ) } From 34b5bff9d60d23cb6d0fde1efa37929b3a721a62 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 16:23:00 -0500 Subject: [PATCH 09/12] use identityType more directly --- app/components/AccessNameCell.tsx | 9 ++++++--- app/pages/OrgAccessPage.tsx | 10 ++++++---- app/pages/project/access/ProjectAccessPage.tsx | 10 ++++++---- libs/api/roles.ts | 6 +++--- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/components/AccessNameCell.tsx b/app/components/AccessNameCell.tsx index 6c1b768061..2509f882d5 100644 --- a/app/components/AccessNameCell.tsx +++ b/app/components/AccessNameCell.tsx @@ -1,19 +1,22 @@ import type { CellContext } from '@tanstack/react-table' +import type { IdentityType } from '@oxide/api' import { Badge } from '@oxide/ui' /** * Display the user or group name. If the row is for a group, add a GROUP badge. */ -export const AccessNameCell = ( +export const AccessNameCell = < + RowData extends { name: string; identityType: IdentityType } +>( info: CellContext ) => { const name = info.getValue() - const identityType = info.row.original.type + const identityType = info.row.original.identityType return ( <> {name} - {identityType === 'group' ? ( + {identityType === 'silo_group' ? ( Group diff --git a/app/pages/OrgAccessPage.tsx b/app/pages/OrgAccessPage.tsx index 95af7d8d02..81a90aed5d 100644 --- a/app/pages/OrgAccessPage.tsx +++ b/app/pages/OrgAccessPage.tsx @@ -11,7 +11,7 @@ import { useApiQueryClient, useUserRows, } from '@oxide/api' -import type { OrganizationRole, SiloRole } from '@oxide/api' +import type { IdentityType, OrganizationRole, SiloRole } from '@oxide/api' import { useApiQuery } from '@oxide/api' import { Table, getActionsCol } from '@oxide/table' import { @@ -53,7 +53,7 @@ OrgAccessPage.loader = async ({ params }: LoaderFunctionArgs) => { type UserRow = { id: string - type: 'user' | 'group' + identityType: IdentityType name: string siloRole: SiloRole | undefined orgRole: OrganizationRole | undefined @@ -82,10 +82,12 @@ export function OrgAccessPage() { const roles = [siloRole, orgRole].filter(isTruthy) + const { name, identityType } = userAssignments[0] + const row: UserRow = { id: userId, - type: userAssignments[0].type, - name: userAssignments[0].name, + identityType, + name, siloRole, orgRole, // we know there has to be at least one diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index be1ade146f..040d76caca 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -11,7 +11,7 @@ import { useApiQueryClient, useUserRows, } from '@oxide/api' -import type { OrganizationRole, ProjectRole, SiloRole } from '@oxide/api' +import type { IdentityType, OrganizationRole, ProjectRole, SiloRole } from '@oxide/api' import { useApiQuery } from '@oxide/api' import { Table, getActionsCol } from '@oxide/table' import { @@ -58,7 +58,7 @@ ProjectAccessPage.loader = async ({ params }: LoaderFunctionArgs) => { type UserRow = { id: string - type: 'user' | 'group' + identityType: IdentityType name: string siloRole: SiloRole | undefined orgRole: OrganizationRole | undefined @@ -95,10 +95,12 @@ export function ProjectAccessPage() { const roles = [siloRole, orgRole, projectRole].filter(isTruthy) + const { name, identityType } = userAssignments[0] + const row: UserRow = { id: userId, - type: userAssignments[0].type, - name: userAssignments[0].name, + identityType, + name, siloRole, orgRole, projectRole, diff --git a/libs/api/roles.ts b/libs/api/roles.ts index 693fa0b4a8..110f9fd6d9 100644 --- a/libs/api/roles.ts +++ b/libs/api/roles.ts @@ -92,7 +92,7 @@ export function setUserRole( type UserAccessRow = { id: string - type: 'user' | 'group' + identityType: IdentityType name: string roleName: Role roleSource: string @@ -119,14 +119,14 @@ export function useUserRows( { id: 'user-group-2', displayName: 'kernel-devs' }, ], }, - } // useApiQuery('userGroupList', {}) + } // useApiQuery('groupList', {}) return useMemo(() => { const userItems = users?.items || [] const groupItems = groups?.items || [] const usersDict = Object.fromEntries(userItems.concat(groupItems).map((u) => [u.id, u])) return (roleAssignments || []).map((ra) => ({ id: ra.identityId, - type: ra.identityType === 'silo_group' ? 'group' : 'user', + identityType: ra.identityType, name: usersDict[ra.identityId]?.displayName || '', // placeholder until we get names, obviously roleName: ra.roleName, roleSource, From 440dffb49c5e2a9287b9bf47efa00a1d2306fb4a Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 16:27:09 -0500 Subject: [PATCH 10/12] works with API client generated by cherry-picking PR on top of resource-branch --- OMICRON_VERSION | 2 +- libs/api-mocks/msw/handlers.ts | 2 +- libs/api/__generated__/Api.ts | 53 ++++++++++++++++++++++---- libs/api/__generated__/OMICRON_VERSION | 2 +- libs/api/__generated__/validate.ts | 38 ++++++++++++++---- libs/api/roles.ts | 9 +---- 6 files changed, 81 insertions(+), 25 deletions(-) diff --git a/OMICRON_VERSION b/OMICRON_VERSION index 159be45294..683eeb6e30 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -91a1b9dc7c5e4fbf047b4c548f2c19f5f9f8f4b4 +f2335326ab5b11b8cdd7a2a0612549b00c9b2041 diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts index 77791060d2..cdae6b4a68 100644 --- a/libs/api-mocks/msw/handlers.ts +++ b/libs/api-mocks/msw/handlers.ts @@ -1143,7 +1143,7 @@ export const handlers = [ return res(json(paginated(req.url.search, db.users))) }), - rest.get | GetErr>('/user-groups', (req, res) => { + rest.get | GetErr>('/groups', (req, res) => { return res(json(paginated(req.url.search, db.userGroups))) }), diff --git a/libs/api/__generated__/Api.ts b/libs/api/__generated__/Api.ts index e4ffa5b178..34e270dcee 100644 --- a/libs/api/__generated__/Api.ts +++ b/libs/api/__generated__/Api.ts @@ -401,6 +401,27 @@ export type GlobalImageResultsPage = { nextPage?: string | null } +/** + * Client view of a {@link Group} + */ +export type Group = { + /** Human-readable name that can identify the group */ + displayName: string + id: string + /** Uuid of the silo to which this group belongs */ + siloId: string +} + +/** + * A single page of results + */ +export type GroupResultsPage = { + /** list of items on this page of results */ + items: Group[] + /** token used to fetch the next page of results (if any) */ + nextPage?: string | null +} + export type IdentityProviderType = 'saml' /** @@ -1683,6 +1704,13 @@ export type VpcUpdate = { name?: Name | null } +/** + * Supported set of sort modes for scanning by id only. + * + * Currently, we only support scanning in ascending order. + */ +export type IdSortMode = 'id_ascending' + /** * Supported set of sort modes for scanning by name or id */ @@ -1709,13 +1737,6 @@ export type DiskMetricName = | 'write' | 'write_bytes' -/** - * Supported set of sort modes for scanning by id only. - * - * Currently, we only support scanning in ascending order. - */ -export type IdSortMode = 'id_ascending' - export type SystemMetricName = | 'virtual_disk_space_provisioned' | 'cpus_provisioned' @@ -1771,6 +1792,12 @@ export interface DeviceAuthConfirmParams {} export interface DeviceAccessTokenParams {} +export interface GroupListParams { + limit?: number + pageToken?: string | null + sortBy?: IdSortMode +} + export interface LoginSpoofParams {} export interface LoginSamlBeginParams { @@ -2459,6 +2486,7 @@ export type ApiViewByIdMethods = Pick< export type ApiListMethods = Pick< InstanceType['methods'], + | 'groupList' | 'organizationList' | 'projectList' | 'diskList' @@ -2644,6 +2672,17 @@ export class Api extends HttpClient { ...params, }), + /** + * List groups + */ + groupList: (query: GroupListParams, params: RequestParams = {}) => + this.request({ + path: `/groups`, + method: 'GET', + query, + ...params, + }), + loginSpoof: ( query: LoginSpoofParams, body: SpoofLoginBody, diff --git a/libs/api/__generated__/OMICRON_VERSION b/libs/api/__generated__/OMICRON_VERSION index 0b73600064..ec967d581d 100644 --- a/libs/api/__generated__/OMICRON_VERSION +++ b/libs/api/__generated__/OMICRON_VERSION @@ -1,2 +1,2 @@ # generated file. do not update manually. see docs/update-pinned-api.md -91a1b9dc7c5e4fbf047b4c548f2c19f5f9f8f4b4 +f2335326ab5b11b8cdd7a2a0612549b00c9b2041 diff --git a/libs/api/__generated__/validate.ts b/libs/api/__generated__/validate.ts index 8752ce83fb..e3e3d1da92 100644 --- a/libs/api/__generated__/validate.ts +++ b/libs/api/__generated__/validate.ts @@ -375,6 +375,23 @@ export const GlobalImageResultsPage = z.object({ nextPage: z.string().nullable().optional(), }) +/** + * Client view of a {@link Group} + */ +export const Group = z.object({ + displayName: z.string(), + id: z.string().uuid(), + siloId: z.string().uuid(), +}) + +/** + * A single page of results + */ +export const GroupResultsPage = z.object({ + items: Group.array(), + nextPage: z.string().nullable().optional(), +}) + export const IdentityProviderType = z.enum(['saml']) /** @@ -1407,6 +1424,13 @@ export const VpcUpdate = z.object({ name: Name.nullable().optional(), }) +/** + * Supported set of sort modes for scanning by id only. + * + * Currently, we only support scanning in ascending order. + */ +export const IdSortMode = z.enum(['id_ascending']) + /** * Supported set of sort modes for scanning by name or id */ @@ -1432,13 +1456,6 @@ export const DiskMetricName = z.enum([ 'write_bytes', ]) -/** - * Supported set of sort modes for scanning by id only. - * - * Currently, we only support scanning in ascending order. - */ -export const IdSortMode = z.enum(['id_ascending']) - export const SystemMetricName = z.enum([ 'virtual_disk_space_provisioned', 'cpus_provisioned', @@ -1511,6 +1528,13 @@ export type DeviceAuthConfirmParams = z.infer export const DeviceAccessTokenParams = z.object({}) export type DeviceAccessTokenParams = z.infer +export const GroupListParams = z.object({ + limit: z.number().min(1).max(4294967295).nullable().optional(), + pageToken: z.string().nullable().optional(), + sortBy: IdSortMode.optional(), +}) +export type GroupListParams = z.infer + export const LoginSpoofParams = z.object({}) export type LoginSpoofParams = z.infer diff --git a/libs/api/roles.ts b/libs/api/roles.ts index 110f9fd6d9..1a13587339 100644 --- a/libs/api/roles.ts +++ b/libs/api/roles.ts @@ -112,14 +112,7 @@ export function useUserRows( // HACK: because the policy has no names, we are fetching ~all the users, // putting them in a dictionary, and adding the names to the rows const { data: users } = useApiQuery('userList', {}) - const { data: groups } = { - data: { - items: [ - { id: 'user-group-1', displayName: 'web-devs' }, - { id: 'user-group-2', displayName: 'kernel-devs' }, - ], - }, - } // useApiQuery('groupList', {}) + const { data: groups } = useApiQuery('groupList', {}) return useMemo(() => { const userItems = users?.items || [] const groupItems = groups?.items || [] From cce91fbc208903683bb5612d50c2c589b2919438 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 16:33:11 -0500 Subject: [PATCH 11/12] use the actual API group type in the mock groups --- libs/api-mocks/msw/handlers.ts | 2 +- libs/api-mocks/user-group.ts | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts index cdae6b4a68..a807ce4f3b 100644 --- a/libs/api-mocks/msw/handlers.ts +++ b/libs/api-mocks/msw/handlers.ts @@ -1143,7 +1143,7 @@ export const handlers = [ return res(json(paginated(req.url.search, db.users))) }), - rest.get | GetErr>('/groups', (req, res) => { + rest.get | GetErr>('/groups', (req, res) => { return res(json(paginated(req.url.search, db.userGroups))) }), diff --git a/libs/api-mocks/user-group.ts b/libs/api-mocks/user-group.ts index e7169d1d29..3acdf56c90 100644 --- a/libs/api-mocks/user-group.ts +++ b/libs/api-mocks/user-group.ts @@ -1,16 +1,17 @@ -import type { Json } from './json-type' - -// import type { Group } from '@oxide/api' +import type { Group } from '@oxide/api' -type UserGroup = { id: string; displayName: string } +import type { Json } from './json-type' +import { defaultSilo } from './silo' -export const userGroup1: Json = { +export const userGroup1: Json = { id: 'user-group-1', + silo_id: defaultSilo.id, display_name: 'web-devs', } -export const userGroup2: Json = { +export const userGroup2: Json = { id: 'user-group-2', + silo_id: defaultSilo.id, display_name: 'kernal-devs', } From 5742db82638ff7372e81810496bbb91729a7be5f Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 10 Oct 2022 16:37:36 -0500 Subject: [PATCH 12/12] update API for folgers branch --- OMICRON_VERSION | 2 +- libs/api-mocks/session.ts | 3 +++ libs/api-mocks/user-group.ts | 2 +- libs/api-mocks/user.ts | 5 +++++ libs/api/__generated__/Api.ts | 2 ++ libs/api/__generated__/OMICRON_VERSION | 2 +- libs/api/__generated__/validate.ts | 6 +++++- 7 files changed, 18 insertions(+), 4 deletions(-) diff --git a/OMICRON_VERSION b/OMICRON_VERSION index 683eeb6e30..82ba529f67 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -f2335326ab5b11b8cdd7a2a0612549b00c9b2041 +698ccddd1af6bbae07e854fe290984a08d369e54 diff --git a/libs/api-mocks/session.ts b/libs/api-mocks/session.ts index bfd55ce766..f74941e49e 100644 --- a/libs/api-mocks/session.ts +++ b/libs/api-mocks/session.ts @@ -1,6 +1,9 @@ import type { User } from '@oxide/api' +import { defaultSilo } from './silo' + export const sessionMe: User = { id: '001de000-05e4-4000-8000-000000060001', + siloId: defaultSilo.id, displayName: 'Grace Hopper', } diff --git a/libs/api-mocks/user-group.ts b/libs/api-mocks/user-group.ts index 3acdf56c90..ef6c0be965 100644 --- a/libs/api-mocks/user-group.ts +++ b/libs/api-mocks/user-group.ts @@ -12,7 +12,7 @@ export const userGroup1: Json = { export const userGroup2: Json = { id: 'user-group-2', silo_id: defaultSilo.id, - display_name: 'kernal-devs', + display_name: 'kernel-devs', } export const userGroups = [userGroup1, userGroup2] diff --git a/libs/api-mocks/user.ts b/libs/api-mocks/user.ts index e657e31111..b2a6f267c5 100644 --- a/libs/api-mocks/user.ts +++ b/libs/api-mocks/user.ts @@ -1,25 +1,30 @@ import type { User } from '@oxide/api' import type { Json } from './json-type' +import { defaultSilo } from './silo' export const user1: Json = { id: 'user-1', display_name: 'Hannah Arendt', + silo_id: defaultSilo.id, } export const user2: Json = { id: 'user-2', display_name: 'Hans Jonas', + silo_id: defaultSilo.id, } export const user3: Json = { id: 'user-3', display_name: 'Jacob Klein', + silo_id: defaultSilo.id, } export const user4: Json = { id: 'user-4', display_name: 'Simone de Beauvoir', + silo_id: defaultSilo.id, } export const users = [user1, user2, user3, user4] diff --git a/libs/api/__generated__/Api.ts b/libs/api/__generated__/Api.ts index 34e270dcee..7636c2089b 100644 --- a/libs/api/__generated__/Api.ts +++ b/libs/api/__generated__/Api.ts @@ -1401,6 +1401,8 @@ export type User = { /** Human-readable name that can identify the user */ displayName: string id: string + /** Uuid of the silo to which this user belongs */ + siloId: string } /** diff --git a/libs/api/__generated__/OMICRON_VERSION b/libs/api/__generated__/OMICRON_VERSION index ec967d581d..90656fc895 100644 --- a/libs/api/__generated__/OMICRON_VERSION +++ b/libs/api/__generated__/OMICRON_VERSION @@ -1,2 +1,2 @@ # generated file. do not update manually. see docs/update-pinned-api.md -f2335326ab5b11b8cdd7a2a0612549b00c9b2041 +698ccddd1af6bbae07e854fe290984a08d369e54 diff --git a/libs/api/__generated__/validate.ts b/libs/api/__generated__/validate.ts index e3e3d1da92..b962abd2cc 100644 --- a/libs/api/__generated__/validate.ts +++ b/libs/api/__generated__/validate.ts @@ -1191,7 +1191,11 @@ export const TimeseriesSchemaResultsPage = z.object({ /** * Client view of a {@link User} */ -export const User = z.object({ displayName: z.string(), id: z.string().uuid() }) +export const User = z.object({ + displayName: z.string(), + id: z.string().uuid(), + siloId: z.string().uuid(), +}) /** * Client view of a {@link UserBuiltin}