Skip to content
Merged
2 changes: 1 addition & 1 deletion OMICRON_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
91a1b9dc7c5e4fbf047b4c548f2c19f5f9f8f4b4
698ccddd1af6bbae07e854fe290984a08d369e54
26 changes: 26 additions & 0 deletions app/components/AccessNameCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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 = <
RowData extends { name: string; identityType: IdentityType }
>(
info: CellContext<RowData, string>
) => {
const name = info.getValue()
const identityType = info.row.original.identityType
return (
<>
<span>{name}</span>
{identityType === 'silo_group' ? (
<Badge color="neutral" className="ml-2">
Group
</Badge>
) : null}
</>
)
}
4 changes: 2 additions & 2 deletions app/components/RoleBadgeCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <TData extends { effectiveRole: Role }>(
info: CellContext<TData, Role>
export const RoleBadgeCell = <RowData extends { effectiveRole: Role }>(
info: CellContext<RowData, Role>
) => {
const cellRole = info.getValue()
if (!cellRole) return null
Expand Down
11 changes: 8 additions & 3 deletions app/pages/OrgAccessPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -25,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'
Expand Down Expand Up @@ -52,6 +53,7 @@ OrgAccessPage.loader = async ({ params }: LoaderFunctionArgs) => {

type UserRow = {
id: string
identityType: IdentityType
name: string
siloRole: SiloRole | undefined
orgRole: OrganizationRole | undefined
Expand Down Expand Up @@ -80,9 +82,12 @@ export function OrgAccessPage() {

const roles = [siloRole, orgRole].filter(isTruthy)

const { name, identityType } = userAssignments[0]

const row: UserRow = {
id: userId,
name: userAssignments[0].name,
identityType,
name,
siloRole,
orgRole,
// we know there has to be at least one
Expand All @@ -107,7 +112,7 @@ export function OrgAccessPage() {
const columns = useMemo(
() => [
colHelper.accessor('id', { header: 'ID' }),
colHelper.accessor('name', { header: 'Name' }),
colHelper.accessor('name', { header: 'Name', cell: AccessNameCell }),
colHelper.accessor('siloRole', {
header: 'Silo role',
cell: RoleBadgeCell,
Expand Down
11 changes: 8 additions & 3 deletions app/pages/project/access/ProjectAccessPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -25,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,
Expand Down Expand Up @@ -57,6 +58,7 @@ ProjectAccessPage.loader = async ({ params }: LoaderFunctionArgs) => {

type UserRow = {
id: string
identityType: IdentityType
name: string
siloRole: SiloRole | undefined
orgRole: OrganizationRole | undefined
Expand Down Expand Up @@ -93,9 +95,12 @@ export function ProjectAccessPage() {

const roles = [siloRole, orgRole, projectRole].filter(isTruthy)

const { name, identityType } = userAssignments[0]

const row: UserRow = {
id: userId,
name: userAssignments[0].name,
identityType,
name,
siloRole,
orgRole,
projectRole,
Expand All @@ -121,7 +126,7 @@ export function ProjectAccessPage() {
const columns = useMemo(
() => [
colHelper.accessor('id', { header: 'ID' }),
colHelper.accessor('name', { header: 'Name' }),
colHelper.accessor('name', { header: 'Name', cell: AccessNameCell }),
colHelper.accessor('siloRole', {
header: 'Silo role',
cell: RoleBadgeCell,
Expand Down
1 change: 1 addition & 0 deletions libs/api-mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions libs/api-mocks/msw/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export function lookupSshKey(params: PP.SshKey): Result<Json<Api.SshKey>> {
const initDb = {
disks: [...mock.disks],
globalImages: [...mock.globalImages],
userGroups: [...mock.userGroups],
images: [...mock.images],
instances: [mock.instance],
networkInterfaces: [mock.networkInterface],
Expand Down
7 changes: 4 additions & 3 deletions libs/api-mocks/msw/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<never, never, Json<Api.UserResultsPage> | GetErr>('/users', (req, res) => {
return res(json(paginated(req.url.search, db.users)))
}),

rest.get<never, never, Json<Api.GroupResultsPage> | GetErr>('/groups', (req, res) => {
return res(json(paginated(req.url.search, db.userGroups)))
}),

rest.post<Json<Api.DeviceAuthVerify>, never, PostErr>(
'/device/confirm',
async (req, res, ctx) => {
Expand Down
15 changes: 15 additions & 0 deletions libs/api-mocks/role-assignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { org } from './org'
import { project } from './project'
import { defaultSilo } from './silo'
import { user1, user2, user3 } from './user'
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
Expand Down Expand Up @@ -37,11 +38,25 @@ export const roleAssignments: DbRoleAssignment[] = [
identity_type: 'silo_user',
role_name: 'viewer',
},
{
resource_type: 'organization',
resource_id: org.id,
identity_id: userGroup1.id,
identity_type: 'silo_group',
role_name: 'collaborator',
},
{
resource_type: 'project',
resource_id: project.id,
identity_id: user3.id,
identity_type: 'silo_user',
role_name: 'collaborator',
},
{
resource_type: 'project',
resource_id: project.id,
identity_id: userGroup2.id,
identity_type: 'silo_group',
role_name: 'viewer',
},
]
3 changes: 3 additions & 0 deletions libs/api-mocks/session.ts
Original file line number Diff line number Diff line change
@@ -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',
}
18 changes: 18 additions & 0 deletions libs/api-mocks/user-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Group } from '@oxide/api'

import type { Json } from './json-type'
import { defaultSilo } from './silo'

export const userGroup1: Json<Group> = {
id: 'user-group-1',
silo_id: defaultSilo.id,
display_name: 'web-devs',
}

export const userGroup2: Json<Group> = {
id: 'user-group-2',
silo_id: defaultSilo.id,
display_name: 'kernel-devs',
}

export const userGroups = [userGroup1, userGroup2]
5 changes: 5 additions & 0 deletions libs/api-mocks/user.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import type { User } from '@oxide/api'

import type { Json } from './json-type'
import { defaultSilo } from './silo'

export const user1: Json<User> = {
id: 'user-1',
display_name: 'Hannah Arendt',
silo_id: defaultSilo.id,
}

export const user2: Json<User> = {
id: 'user-2',
display_name: 'Hans Jonas',
silo_id: defaultSilo.id,
}

export const user3: Json<User> = {
id: 'user-3',
display_name: 'Jacob Klein',
silo_id: defaultSilo.id,
}

export const user4: Json<User> = {
id: 'user-4',
display_name: 'Simone de Beauvoir',
silo_id: defaultSilo.id,
}

export const users = [user1, user2, user3, user4]
55 changes: 48 additions & 7 deletions libs/api/__generated__/Api.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion libs/api/__generated__/OMICRON_VERSION

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading