From cddc2b7a23637a3df5d90847b117dea7d9ab6cff Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 4 Mar 2026 15:28:02 -0800 Subject: [PATCH] add created_at and updated_at to access page user lists --- OMICRON_VERSION | 2 +- app/api/__generated__/Api.ts | 49 ++++++++++--------- app/api/__generated__/OMICRON_VERSION | 2 +- app/api/__generated__/msw-handlers.ts | 20 ++++---- app/api/__generated__/validate.ts | 47 +++++++++--------- app/api/roles.ts | 4 ++ app/pages/SiloAccessPage.tsx | 19 +++++++ .../project/access/ProjectAccessPage.tsx | 20 ++++++++ mock-api/msw/handlers.ts | 2 +- mock-api/user-group.ts | 6 +++ mock-api/user.ts | 20 ++++++++ 11 files changed, 133 insertions(+), 58 deletions(-) diff --git a/OMICRON_VERSION b/OMICRON_VERSION index 060a31a389..3e6271950f 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -161aa6b612a9f05bf708de306293c06d300b3234 +22ad5a9f832f3588090f9ebdf982872ec042b11c diff --git a/app/api/__generated__/Api.ts b/app/api/__generated__/Api.ts index f92602780d..3489a9b857 100644 --- a/app/api/__generated__/Api.ts +++ b/app/api/__generated__/Api.ts @@ -1383,6 +1383,10 @@ export type CurrentUser = { siloId: string /** Name of the silo to which this user belongs. */ siloName: Name + /** Timestamp of when this user was created */ + timeCreated: Date + /** Timestamp of when this user was last modified */ + timeModified: Date } /** @@ -2306,6 +2310,10 @@ export type Group = { id: string /** Uuid of the silo to which this group belongs */ siloId: string + /** Timestamp of when this group was created */ + timeCreated: Date + /** Timestamp of when this group was last modified */ + timeModified: Date } /** @@ -4343,11 +4351,6 @@ export type Sled = { usablePhysicalRam: ByteCount } -/** - * The unique ID of a sled. - */ -export type SledId = { id: string } - /** * An operator's view of an instance running on a given sled */ @@ -5133,11 +5136,6 @@ export type TufRepoUpload = { repo: TufRepo; status: TufRepoUploadStatus } */ export type UninitializedSled = { baseboard: Baseboard; cubby: number; rackId: string } -/** - * The unique hardware ID for a sled - */ -export type UninitializedSledId = { part: string; serial: string } - /** * A single page of results */ @@ -5202,6 +5200,10 @@ export type User = { id: string /** Uuid of the silo to which this user belongs */ siloId: string + /** Timestamp of when this user was created */ + timeCreated: Date + /** Timestamp of when this user was last modified */ + timeModified: Date } /** @@ -7475,7 +7477,7 @@ export class Api { * Pulled from info.version in the OpenAPI schema. Sent in the * `api-version` header on all requests. */ - apiVersion = '2026021301.0.0' + apiVersion = '2026030200.0.0' constructor({ host = '', baseParams = {}, token }: ApiConfig = {}) { this.host = host @@ -10075,17 +10077,6 @@ export class Api { ...params, }) }, - /** - * Add sled to initialized rack - */ - sledAdd: ({ body }: { body: UninitializedSledId }, params: FetchParams = {}) => { - return this.request({ - path: `/v1/system/hardware/sleds`, - method: 'POST', - body, - ...params, - }) - }, /** * Fetch sled */ @@ -11522,6 +11513,20 @@ export class Api { ...params, }) }, + /** + * Instructs the system that a system recovery operation ("mupdate") was + */ + systemUpdateRecoveryFinish: ( + { body }: { body: SetTargetReleaseParams }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/system/update/recovery-finish`, + method: 'PUT', + body, + ...params, + }) + }, /** * List all TUF repositories */ diff --git a/app/api/__generated__/OMICRON_VERSION b/app/api/__generated__/OMICRON_VERSION index f50bf9d2f4..8973705cfd 100644 --- a/app/api/__generated__/OMICRON_VERSION +++ b/app/api/__generated__/OMICRON_VERSION @@ -1,2 +1,2 @@ # generated file. do not update manually. see docs/update-pinned-api.md -161aa6b612a9f05bf708de306293c06d300b3234 +22ad5a9f832f3588090f9ebdf982872ec042b11c diff --git a/app/api/__generated__/msw-handlers.ts b/app/api/__generated__/msw-handlers.ts index 07de86d9ee..be802ede5f 100644 --- a/app/api/__generated__/msw-handlers.ts +++ b/app/api/__generated__/msw-handlers.ts @@ -1110,12 +1110,6 @@ export interface MSWHandlers { req: Request cookies: Record }) => Promisable> - /** `POST /v1/system/hardware/sleds` */ - sledAdd: (params: { - body: Json - req: Request - cookies: Record - }) => Promisable> /** `GET /v1/system/hardware/sleds/:sledId` */ sledView: (params: { path: Api.SledViewPathParams @@ -1741,6 +1735,12 @@ export interface MSWHandlers { req: Request cookies: Record }) => Promisable> + /** `PUT /v1/system/update/recovery-finish` */ + systemUpdateRecoveryFinish: (params: { + body: Json + req: Request + cookies: Record + }) => Promisable /** `GET /v1/system/update/repositories` */ systemUpdateRepositoryList: (params: { query: Api.SystemUpdateRepositoryListQueryParams @@ -3028,10 +3028,6 @@ export function makeHandlers(handlers: MSWHandlers): HttpHandler[] { '/v1/system/hardware/sleds', handler(handlers['sledList'], schema.SledListParams, null) ), - http.post( - '/v1/system/hardware/sleds', - handler(handlers['sledAdd'], null, schema.UninitializedSledId) - ), http.get( '/v1/system/hardware/sleds/:sledId', handler(handlers['sledView'], schema.SledViewParams, null) @@ -3614,6 +3610,10 @@ export function makeHandlers(handlers: MSWHandlers): HttpHandler[] { null ) ), + http.put( + '/v1/system/update/recovery-finish', + handler(handlers['systemUpdateRecoveryFinish'], null, schema.SetTargetReleaseParams) + ), http.get( '/v1/system/update/repositories', handler( diff --git a/app/api/__generated__/validate.ts b/app/api/__generated__/validate.ts index 62458a55e8..b0b08f116d 100644 --- a/app/api/__generated__/validate.ts +++ b/app/api/__generated__/validate.ts @@ -1327,6 +1327,8 @@ export const CurrentUser = z.preprocess( siloAdmin: SafeBoolean, siloId: z.uuid(), siloName: Name, + timeCreated: z.coerce.date(), + timeModified: z.coerce.date(), }) ) @@ -2152,7 +2154,13 @@ export const FloatingIpUpdate = z.preprocess( */ export const Group = z.preprocess( processResponseBody, - z.object({ displayName: z.string(), id: z.uuid(), siloId: z.uuid() }) + z.object({ + displayName: z.string(), + id: z.uuid(), + siloId: z.uuid(), + timeCreated: z.coerce.date(), + timeModified: z.coerce.date(), + }) ) /** @@ -3969,11 +3977,6 @@ export const Sled = z.preprocess( }) ) -/** - * The unique ID of a sled. - */ -export const SledId = z.preprocess(processResponseBody, z.object({ id: z.uuid() })) - /** * An operator's view of an instance running on a given sled */ @@ -4671,14 +4674,6 @@ export const UninitializedSled = z.preprocess( z.object({ baseboard: Baseboard, cubby: z.number().min(0).max(65535), rackId: z.uuid() }) ) -/** - * The unique hardware ID for a sled - */ -export const UninitializedSledId = z.preprocess( - processResponseBody, - z.object({ part: z.string(), serial: z.string() }) -) - /** * A single page of results */ @@ -4722,7 +4717,13 @@ export const UpdatesTrustRootResultsPage = z.preprocess( */ export const User = z.preprocess( processResponseBody, - z.object({ displayName: z.string(), id: z.uuid(), siloId: z.uuid() }) + z.object({ + displayName: z.string(), + id: z.uuid(), + siloId: z.uuid(), + timeCreated: z.coerce.date(), + timeModified: z.coerce.date(), + }) ) /** @@ -7071,14 +7072,6 @@ export const SledListParams = z.preprocess( }) ) -export const SledAddParams = z.preprocess( - processResponseBody, - z.object({ - path: z.object({}), - query: z.object({}), - }) -) - export const SledViewParams = z.preprocess( processResponseBody, z.object({ @@ -8126,6 +8119,14 @@ export const SystemTimeseriesSchemaListParams = z.preprocess( }) ) +export const SystemUpdateRecoveryFinishParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({}), + }) +) + export const SystemUpdateRepositoryListParams = z.preprocess( processResponseBody, z.object({ diff --git a/app/api/roles.ts b/app/api/roles.ts index 9c3ff07da8..31d5e01b63 100644 --- a/app/api/roles.ts +++ b/app/api/roles.ts @@ -83,6 +83,8 @@ type UserAccessRow = { name: string roleName: RoleKey roleSource: string + timeCreated: Date | undefined + timeModified: Date | undefined } /** @@ -110,6 +112,8 @@ export function useUserRows( name: usersDict[ra.identityId]?.displayName || '', // placeholder until we get names, obviously roleName: ra.roleName, roleSource, + timeCreated: usersDict[ra.identityId]?.timeCreated, + timeModified: usersDict[ra.identityId]?.timeModified, })) }, [roleAssignments, roleSource, users, groups]) } diff --git a/app/pages/SiloAccessPage.tsx b/app/pages/SiloAccessPage.tsx index eb65e95359..de60f613e8 100644 --- a/app/pages/SiloAccessPage.tsx +++ b/app/pages/SiloAccessPage.tsx @@ -34,6 +34,7 @@ import { confirmDelete } from '~/stores/confirm-delete' import { getActionsCol } from '~/table/columns/action-col' import { Table } from '~/table/Table' import { CreateButton } from '~/ui/lib/CreateButton' +import { DateTime } from '~/ui/lib/DateTime' import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { TableActions, TableEmptyBox } from '~/ui/lib/Table' @@ -75,6 +76,8 @@ type UserRow = { name: string siloRole: RoleKey | undefined effectiveRole: RoleKey + timeCreated: Date | undefined + timeModified: Date | undefined } const colHelper = createColumnHelper() @@ -102,6 +105,8 @@ export default function SiloAccessPage() { siloRole, // we know there has to be at least one effectiveRole: getEffectiveRole(roles)!, + timeCreated: userAssignments[0].timeCreated, + timeModified: userAssignments[0].timeModified, } return row @@ -131,6 +136,20 @@ export default function SiloAccessPage() { return role ? silo.{role} : null }, }), + colHelper.accessor('timeCreated', { + header: 'created', + cell: (info) => { + const date = info.getValue() + return date ? : null + }, + }), + colHelper.accessor('timeModified', { + header: 'updated', + cell: (info) => { + const date = info.getValue() + return date ? : null + }, + }), // TODO: tooltips on disabled elements explaining why getActionsCol((row: UserRow) => [ { diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index 17cf4d4538..3197415824 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -40,6 +40,7 @@ import { addToast } from '~/stores/toast' import { getActionsCol } from '~/table/columns/action-col' import { Table } from '~/table/Table' import { CreateButton } from '~/ui/lib/CreateButton' +import { DateTime } from '~/ui/lib/DateTime' import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { TableActions, TableEmptyBox } from '~/ui/lib/Table' @@ -87,6 +88,8 @@ type UserRow = { name: string projectRole: RoleKey | undefined roleBadges: { roleSource: string; roleName: RoleKey }[] + timeCreated: Date | undefined + timeModified: Date | undefined } const colHelper = createColumnHelper() @@ -121,6 +124,8 @@ export default function ProjectAccessPage() { name, projectRole: projectAccessRow?.roleName, roleBadges, + timeCreated: userAssignments[0].timeCreated, + timeModified: userAssignments[0].timeModified, } satisfies UserRow }) .sort(byGroupThenName) @@ -165,6 +170,21 @@ export default function ProjectAccessPage() { ), }), + colHelper.accessor('timeCreated', { + header: 'created', + cell: (info) => { + const date = info.getValue() + return date ? : null + }, + }), + colHelper.accessor('timeModified', { + header: 'updated', + cell: (info) => { + const date = info.getValue() + return date ? : null + }, + }), + // TODO: tooltips on disabled elements explaining why getActionsCol((row: UserRow) => [ { diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index 1ad7369446..66b17ce601 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -2292,7 +2292,6 @@ export const handlers = makeHandlers({ siloSubnetPoolList: NotImplemented, siloUserList: NotImplemented, siloUserView: NotImplemented, - sledAdd: NotImplemented, sledListUninitialized: NotImplemented, sledSetProvisionPolicy: NotImplemented, systemSubnetPoolCreate: NotImplemented, @@ -2322,6 +2321,7 @@ export const handlers = makeHandlers({ systemPolicyUpdate: NotImplemented, systemQuotasList: NotImplemented, systemTimeseriesSchemaList: NotImplemented, + systemUpdateRecoveryFinish: NotImplemented, systemUpdateRepositoryView: NotImplemented, systemUpdateTrustRootCreate: NotImplemented, systemUpdateTrustRootDelete: NotImplemented, diff --git a/mock-api/user-group.ts b/mock-api/user-group.ts index 312b05439c..a7ea0e77bb 100644 --- a/mock-api/user-group.ts +++ b/mock-api/user-group.ts @@ -15,18 +15,24 @@ export const userGroup1: Json = { id: '0ff6da96-5d6d-4326-b059-2b72c1b51457', silo_id: defaultSilo.id, display_name: 'web-devs', + time_created: '2024-01-01T00:00:00.000Z', + time_modified: '2024-01-01T00:00:00.000Z', } export const userGroup2: Json = { id: '1b5fa004-a378-4225-960f-60f089684b05', silo_id: defaultSilo.id, display_name: 'kernel-devs', + time_created: '2024-01-15T00:00:00.000Z', + time_modified: '2024-01-15T00:00:00.000Z', } export const userGroup3: Json = { id: '5e30797c-cae3-4402-aeb7-d5044c4bed29', silo_id: defaultSilo.id, display_name: 'real-estate-devs', + time_created: '2024-02-01T00:00:00.000Z', + time_modified: '2024-02-01T00:00:00.000Z', } export const userGroups = [userGroup1, userGroup2, userGroup3] diff --git a/mock-api/user.ts b/mock-api/user.ts index af8dbd1fe6..5dbf545905 100644 --- a/mock-api/user.ts +++ b/mock-api/user.ts @@ -14,36 +14,48 @@ export const user1: Json = { id: '2e28576d-43e0-4e9e-9132-838a7b66f602', display_name: 'Hannah Arendt', silo_id: defaultSilo.id, + time_created: '2024-01-01T00:00:00.000Z', + time_modified: '2024-01-01T00:00:00.000Z', } export const user2: Json = { id: '6937b251-013c-4f96-9afc-c62b1318dd0b', display_name: 'Hans Jonas', silo_id: defaultSilo.id, + time_created: '2024-01-15T00:00:00.000Z', + time_modified: '2024-01-15T00:00:00.000Z', } export const user3: Json = { id: '4962021b-35e1-44a7-a40c-2264cd540615', display_name: 'Jacob Klein', silo_id: defaultSilo.id, + time_created: '2024-02-01T00:00:00.000Z', + time_modified: '2024-02-01T00:00:00.000Z', } export const user4: Json = { id: '37c6aa2f-899e-4d56-bad1-93b5526a7151', display_name: 'Simone de Beauvoir', silo_id: defaultSilo.id, + time_created: '2024-02-15T00:00:00.000Z', + time_modified: '2024-02-15T00:00:00.000Z', } export const user5: Json = { id: 'e7ab4cc1-a7c6-43df-8f69-84bb507c4d22', display_name: 'Jane Austen', silo_id: defaultSilo.id, + time_created: '2024-03-01T00:00:00.000Z', + time_modified: '2024-03-01T00:00:00.000Z', } export const user6: Json = { id: 'f8c2b4d3-6e7a-4b9c-8d1e-3a4f5b6c7d8e', display_name: 'Herbert Marcuse', silo_id: defaultSilo.id, + time_created: '2024-03-15T00:00:00.000Z', + time_modified: '2024-03-15T00:00:00.000Z', } // Users for test silos (different IP pool configurations) @@ -51,24 +63,32 @@ export const userKosman: Json = { id: '9a8b7c6d-5e4f-4a2b-9c0d-9e8f7a6b5c4d', display_name: 'Aryeh Kosman', silo_id: myriadSilo.id, + time_created: '2024-04-01T00:00:00.000Z', + time_modified: '2024-04-01T00:00:00.000Z', } export const userAnscombe: Json = { id: 'a9b8c7d6-e5f4-43b2-81d0-e9f8a7b6c5d4', display_name: 'Elizabeth Anscombe', silo_id: thraxSilo.id, + time_created: '2024-04-15T00:00:00.000Z', + time_modified: '2024-04-15T00:00:00.000Z', } export const userAdorno: Json = { id: 'b0c9d8e7-f6a5-44c3-92e1-f0a9b8c7d6e5', display_name: 'Theodor Adorno', silo_id: pelerinesSilo.id, + time_created: '2024-05-01T00:00:00.000Z', + time_modified: '2024-05-01T00:00:00.000Z', } export const userNoPools: Json = { id: 'c1d0e9f8-a7b6-45d4-a3f2-a1b0c9d8e7f6', display_name: 'Antonio Gramsci', silo_id: noPoolsSilo.id, + time_created: '2024-05-15T00:00:00.000Z', + time_modified: '2024-05-15T00:00:00.000Z', } export const users = [