From a4447affaef5a83d97d15157d9b5fe709441ed05 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Thu, 3 Oct 2024 17:41:42 -0700 Subject: [PATCH 01/20] Update OMICRON_VERSION --- OMICRON_VERSION | 2 +- app/api/__generated__/Api.ts | 406 +++++++++++++++++++++++++- app/api/__generated__/OMICRON_VERSION | 2 +- app/api/__generated__/msw-handlers.ts | 135 +++++++++ app/api/__generated__/validate.ts | 258 +++++++++++++++- 5 files changed, 782 insertions(+), 21 deletions(-) diff --git a/OMICRON_VERSION b/OMICRON_VERSION index f7a1d4ae6..d3996f17a 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -c50cf019cd9be35f98266a7f4acacab0236b3a3d +a9fd9dbbbcdaa80b822c3fc14b4f1a20ac72021d diff --git a/app/api/__generated__/Api.ts b/app/api/__generated__/Api.ts index e64990f1f..5ac00ea45 100644 --- a/app/api/__generated__/Api.ts +++ b/app/api/__generated__/Api.ts @@ -1710,6 +1710,16 @@ export type ImageResultsPage = { */ export type ImportBlocksBulkWrite = { base64EncodedData: string; offset: number } +/** + * A policy determining when an instance should be automatically restarted by the control plane. + */ +export type InstanceAutoRestartPolicy = + /** The instance should not be automatically restarted by the control plane if it fails. */ + | 'never' + + /** If this instance is running and unexpectedly fails (e.g. due to a host software crash or unexpected host reboot), the control plane will make a best-effort attempt to restart it. The control plane may choose not to restart the instance to preserve the overall availability of the system. */ + | 'best_effort' + /** * The number of CPUs in an Instance */ @@ -1761,6 +1771,10 @@ If this is not present, then either the instance has never been automatically re autoRestartCooldownExpiration?: Date /** `true` if this instance's auto-restart policy will permit the control plane to automatically restart it if it enters the `Failed` state. */ autoRestartEnabled: boolean + /** The auto-restart policy configured for this instance, or `None` if no explicit policy is configured. + +If this is not present, then this instance uses the default auto-restart policy, which may or may not allow it to be restarted. The `auto_restart_enabled` field indicates whether the instance will be automatically restarted. */ + autoRestartPolicy?: InstanceAutoRestartPolicy /** the ID of the disk used to boot this Instance, if a specific one is assigned. */ bootDiskId?: string /** human-readable free-form text about a resource */ @@ -1789,16 +1803,6 @@ If this is not present, then this instance has not been automatically restarted. timeRunStateUpdated: Date } -/** - * A policy determining when an instance should be automatically restarted by the control plane. - */ -export type InstanceAutoRestartPolicy = - /** The instance should not be automatically restarted by the control plane if it fails. */ - | 'never' - - /** If this instance is running and unexpectedly fails (e.g. due to a host software crash or unexpected host reboot), the control plane will make a best-effort attempt to restart it. The control plane may choose not to restart the instance to preserve the overall availability of the system. */ - | 'best_effort' - /** * Describe the instance's disks at creation time */ @@ -1976,12 +1980,128 @@ export type InstanceSerialConsoleData = { * Parameters of an `Instance` that can be reconfigured after creation. */ export type InstanceUpdate = { + /** The auto-restart policy for this instance. + +If not provided, unset the instance's auto-restart policy. */ + autoRestartPolicy?: InstanceAutoRestartPolicy /** Name or ID of the disk the instance should be instructed to boot from. If not provided, unset the instance's boot disk. */ bootDisk?: NameOrId } +/** + * An internet gateway provides a path between VPC networks and external networks. + */ +export type InternetGateway = { + /** human-readable free-form text about a resource */ + description: string + /** unique, immutable, system-controlled identifier for each resource */ + id: string + /** unique, mutable, user-controlled identifier for each resource */ + name: Name + /** timestamp when this resource was created */ + timeCreated: Date + /** timestamp when this resource was last modified */ + timeModified: Date + /** The VPC to which the gateway belongs. */ + vpcId: string +} + +/** + * Create-time parameters for an `InternetGateway` + */ +export type InternetGatewayCreate = { description: string; name: Name } + +/** + * An IP address that is attached to an internet gateway + */ +export type InternetGatewayIpAddress = { + /** The associated IP address, */ + address: string + /** human-readable free-form text about a resource */ + description: string + /** unique, immutable, system-controlled identifier for each resource */ + id: string + /** The associated internet gateway. */ + internetGatewayId: string + /** unique, mutable, user-controlled identifier for each resource */ + name: Name + /** timestamp when this resource was created */ + timeCreated: Date + /** timestamp when this resource was last modified */ + timeModified: Date +} + +/** + * Create-time identity-related parameters + */ +export type InternetGatewayIpAddressCreate = { + address: string + description: string + gateway: NameOrId + name: Name +} + +/** + * A single page of results + */ +export type InternetGatewayIpAddressResultsPage = { + /** list of items on this page of results */ + items: InternetGatewayIpAddress[] + /** token used to fetch the next page of results (if any) */ + nextPage?: string +} + +/** + * An IP pool that is attached to an internet gateway + */ +export type InternetGatewayIpPool = { + /** human-readable free-form text about a resource */ + description: string + /** unique, immutable, system-controlled identifier for each resource */ + id: string + /** The associated internet gateway. */ + internetGatewayId: string + /** The associated IP pool. */ + ipPoolId: string + /** unique, mutable, user-controlled identifier for each resource */ + name: Name + /** timestamp when this resource was created */ + timeCreated: Date + /** timestamp when this resource was last modified */ + timeModified: Date +} + +/** + * Create-time identity-related parameters + */ +export type InternetGatewayIpPoolCreate = { + description: string + ipPool: NameOrId + name: Name +} + +/** + * A single page of results + */ +export type InternetGatewayIpPoolResultsPage = { + /** list of items on this page of results */ + items: InternetGatewayIpPool[] + /** token used to fetch the next page of results (if any) */ + nextPage?: string +} + +/** + * A single page of results + */ +export type InternetGatewayResultsPage = { + /** list of items on this page of results */ + items: InternetGateway[] + /** token used to fetch the next page of results (if any) */ + nextPage?: string +} + /** * A collection of IP ranges. If a pool is linked to a silo, IP addresses from the pool can be allocated within that silo */ @@ -4363,6 +4483,90 @@ export interface InstanceStopQueryParams { project?: NameOrId } +export interface InternetGatewayIpAddressListQueryParams { + gateway?: NameOrId + limit?: number + pageToken?: string + project?: NameOrId + sortBy?: NameOrIdSortMode + vpc?: NameOrId +} + +export interface InternetGatewayIpAddressCreateQueryParams { + gateway: NameOrId + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayIpAddressDeletePathParams { + address: NameOrId +} + +export interface InternetGatewayIpAddressDeleteQueryParams { + cascade?: boolean + gateway?: NameOrId + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayIpPoolListQueryParams { + gateway?: NameOrId + limit?: number + pageToken?: string + project?: NameOrId + sortBy?: NameOrIdSortMode + vpc?: NameOrId +} + +export interface InternetGatewayIpPoolCreateQueryParams { + gateway: NameOrId + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayIpPoolDeletePathParams { + pool: NameOrId +} + +export interface InternetGatewayIpPoolDeleteQueryParams { + cascade?: boolean + gateway?: NameOrId + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayListQueryParams { + limit?: number + pageToken?: string + project?: NameOrId + sortBy?: NameOrIdSortMode + vpc?: NameOrId +} + +export interface InternetGatewayCreateQueryParams { + project?: NameOrId + vpc: NameOrId +} + +export interface InternetGatewayViewPathParams { + gateway: NameOrId +} + +export interface InternetGatewayViewQueryParams { + project?: NameOrId + vpc?: NameOrId +} + +export interface InternetGatewayDeletePathParams { + gateway: NameOrId +} + +export interface InternetGatewayDeleteQueryParams { + cascade?: boolean + project?: NameOrId + vpc?: NameOrId +} + export interface ProjectIpPoolListQueryParams { limit?: number pageToken?: string @@ -5103,6 +5307,9 @@ export type ApiListMethods = Pick< | 'instanceDiskList' | 'instanceExternalIpList' | 'instanceSshPublicKeyList' + | 'internetGatewayIpAddressList' + | 'internetGatewayIpPoolList' + | 'internetGatewayList' | 'projectIpPoolList' | 'currentUserSshKeyList' | 'instanceNetworkInterfaceList' @@ -5998,6 +6205,185 @@ export class Api extends HttpClient { ...params, }) }, + /** + * List addresses attached to an internet gateway. + */ + internetGatewayIpAddressList: ( + { query = {} }: { query?: InternetGatewayIpAddressListQueryParams }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-addresses`, + method: 'GET', + query, + ...params, + }) + }, + /** + * Attach ip pool to internet gateway + */ + internetGatewayIpAddressCreate: ( + { + query, + body, + }: { + query: InternetGatewayIpAddressCreateQueryParams + body: InternetGatewayIpAddressCreate + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-addresses`, + method: 'POST', + body, + query, + ...params, + }) + }, + /** + * Detach ip pool from internet gateway + */ + internetGatewayIpAddressDelete: ( + { + path, + query = {}, + }: { + path: InternetGatewayIpAddressDeletePathParams + query?: InternetGatewayIpAddressDeleteQueryParams + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-addresses/${path.address}`, + method: 'DELETE', + query, + ...params, + }) + }, + /** + * List IP pools attached to an internet gateway. + */ + internetGatewayIpPoolList: ( + { query = {} }: { query?: InternetGatewayIpPoolListQueryParams }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-pools`, + method: 'GET', + query, + ...params, + }) + }, + /** + * Attach ip pool to internet gateway + */ + internetGatewayIpPoolCreate: ( + { + query, + body, + }: { + query: InternetGatewayIpPoolCreateQueryParams + body: InternetGatewayIpPoolCreate + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-pools`, + method: 'POST', + body, + query, + ...params, + }) + }, + /** + * Detach ip pool from internet gateway + */ + internetGatewayIpPoolDelete: ( + { + path, + query = {}, + }: { + path: InternetGatewayIpPoolDeletePathParams + query?: InternetGatewayIpPoolDeleteQueryParams + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateway-ip-pools/${path.pool}`, + method: 'DELETE', + query, + ...params, + }) + }, + /** + * List internet gateways + */ + internetGatewayList: ( + { query = {} }: { query?: InternetGatewayListQueryParams }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateways`, + method: 'GET', + query, + ...params, + }) + }, + /** + * Create VPC internet gateway + */ + internetGatewayCreate: ( + { + query, + body, + }: { query: InternetGatewayCreateQueryParams; body: InternetGatewayCreate }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateways`, + method: 'POST', + body, + query, + ...params, + }) + }, + /** + * Fetch internet gateway + */ + internetGatewayView: ( + { + path, + query = {}, + }: { path: InternetGatewayViewPathParams; query?: InternetGatewayViewQueryParams }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateways/${path.gateway}`, + method: 'GET', + query, + ...params, + }) + }, + /** + * Delete internet gateway + */ + internetGatewayDelete: ( + { + path, + query = {}, + }: { + path: InternetGatewayDeletePathParams + query?: InternetGatewayDeleteQueryParams + }, + params: FetchParams = {} + ) => { + return this.request({ + path: `/v1/internet-gateways/${path.gateway}`, + method: 'DELETE', + query, + ...params, + }) + }, /** * List IP pools */ diff --git a/app/api/__generated__/OMICRON_VERSION b/app/api/__generated__/OMICRON_VERSION index 2c1de3ebd..258888bf6 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 -c50cf019cd9be35f98266a7f4acacab0236b3a3d +a9fd9dbbbcdaa80b822c3fc14b4f1a20ac72021d diff --git a/app/api/__generated__/msw-handlers.ts b/app/api/__generated__/msw-handlers.ts index 49df51a41..0e455163a 100644 --- a/app/api/__generated__/msw-handlers.ts +++ b/app/api/__generated__/msw-handlers.ts @@ -407,6 +407,73 @@ export interface MSWHandlers { req: Request cookies: Record }) => Promisable> + /** `GET /v1/internet-gateway-ip-addresses` */ + internetGatewayIpAddressList: (params: { + query: Api.InternetGatewayIpAddressListQueryParams + req: Request + cookies: Record + }) => Promisable> + /** `POST /v1/internet-gateway-ip-addresses` */ + internetGatewayIpAddressCreate: (params: { + query: Api.InternetGatewayIpAddressCreateQueryParams + body: Json + req: Request + cookies: Record + }) => Promisable> + /** `DELETE /v1/internet-gateway-ip-addresses/:address` */ + internetGatewayIpAddressDelete: (params: { + path: Api.InternetGatewayIpAddressDeletePathParams + query: Api.InternetGatewayIpAddressDeleteQueryParams + req: Request + cookies: Record + }) => Promisable + /** `GET /v1/internet-gateway-ip-pools` */ + internetGatewayIpPoolList: (params: { + query: Api.InternetGatewayIpPoolListQueryParams + req: Request + cookies: Record + }) => Promisable> + /** `POST /v1/internet-gateway-ip-pools` */ + internetGatewayIpPoolCreate: (params: { + query: Api.InternetGatewayIpPoolCreateQueryParams + body: Json + req: Request + cookies: Record + }) => Promisable> + /** `DELETE /v1/internet-gateway-ip-pools/:pool` */ + internetGatewayIpPoolDelete: (params: { + path: Api.InternetGatewayIpPoolDeletePathParams + query: Api.InternetGatewayIpPoolDeleteQueryParams + req: Request + cookies: Record + }) => Promisable + /** `GET /v1/internet-gateways` */ + internetGatewayList: (params: { + query: Api.InternetGatewayListQueryParams + req: Request + cookies: Record + }) => Promisable> + /** `POST /v1/internet-gateways` */ + internetGatewayCreate: (params: { + query: Api.InternetGatewayCreateQueryParams + body: Json + req: Request + cookies: Record + }) => Promisable> + /** `GET /v1/internet-gateways/:gateway` */ + internetGatewayView: (params: { + path: Api.InternetGatewayViewPathParams + query: Api.InternetGatewayViewQueryParams + req: Request + cookies: Record + }) => Promisable> + /** `DELETE /v1/internet-gateways/:gateway` */ + internetGatewayDelete: (params: { + path: Api.InternetGatewayDeletePathParams + query: Api.InternetGatewayDeleteQueryParams + req: Request + cookies: Record + }) => Promisable /** `GET /v1/ip-pools` */ projectIpPoolList: (params: { query: Api.ProjectIpPoolListQueryParams @@ -1685,6 +1752,74 @@ export function makeHandlers(handlers: MSWHandlers): HttpHandler[] { '/v1/instances/:instance/stop', handler(handlers['instanceStop'], schema.InstanceStopParams, null) ), + http.get( + '/v1/internet-gateway-ip-addresses', + handler( + handlers['internetGatewayIpAddressList'], + schema.InternetGatewayIpAddressListParams, + null + ) + ), + http.post( + '/v1/internet-gateway-ip-addresses', + handler( + handlers['internetGatewayIpAddressCreate'], + schema.InternetGatewayIpAddressCreateParams, + schema.InternetGatewayIpAddressCreate + ) + ), + http.delete( + '/v1/internet-gateway-ip-addresses/:address', + handler( + handlers['internetGatewayIpAddressDelete'], + schema.InternetGatewayIpAddressDeleteParams, + null + ) + ), + http.get( + '/v1/internet-gateway-ip-pools', + handler( + handlers['internetGatewayIpPoolList'], + schema.InternetGatewayIpPoolListParams, + null + ) + ), + http.post( + '/v1/internet-gateway-ip-pools', + handler( + handlers['internetGatewayIpPoolCreate'], + schema.InternetGatewayIpPoolCreateParams, + schema.InternetGatewayIpPoolCreate + ) + ), + http.delete( + '/v1/internet-gateway-ip-pools/:pool', + handler( + handlers['internetGatewayIpPoolDelete'], + schema.InternetGatewayIpPoolDeleteParams, + null + ) + ), + http.get( + '/v1/internet-gateways', + handler(handlers['internetGatewayList'], schema.InternetGatewayListParams, null) + ), + http.post( + '/v1/internet-gateways', + handler( + handlers['internetGatewayCreate'], + schema.InternetGatewayCreateParams, + schema.InternetGatewayCreate + ) + ), + http.get( + '/v1/internet-gateways/:gateway', + handler(handlers['internetGatewayView'], schema.InternetGatewayViewParams, null) + ), + http.delete( + '/v1/internet-gateways/:gateway', + handler(handlers['internetGatewayDelete'], schema.InternetGatewayDeleteParams, null) + ), http.get( '/v1/ip-pools', handler(handlers['projectIpPoolList'], schema.ProjectIpPoolListParams, null) diff --git a/app/api/__generated__/validate.ts b/app/api/__generated__/validate.ts index 77402ef0d..b45ccf9a5 100644 --- a/app/api/__generated__/validate.ts +++ b/app/api/__generated__/validate.ts @@ -1645,6 +1645,14 @@ export const ImportBlocksBulkWrite = z.preprocess( z.object({ base64EncodedData: z.string(), offset: z.number().min(0) }) ) +/** + * A policy determining when an instance should be automatically restarted by the control plane. + */ +export const InstanceAutoRestartPolicy = z.preprocess( + processResponseBody, + z.enum(['never', 'best_effort']) +) + /** * The number of CPUs in an Instance */ @@ -1682,6 +1690,7 @@ export const Instance = z.preprocess( z.object({ autoRestartCooldownExpiration: z.coerce.date().optional(), autoRestartEnabled: SafeBoolean, + autoRestartPolicy: InstanceAutoRestartPolicy.optional(), bootDiskId: z.string().uuid().optional(), description: z.string(), hostname: z.string(), @@ -1698,14 +1707,6 @@ export const Instance = z.preprocess( }) ) -/** - * A policy determining when an instance should be automatically restarted by the control plane. - */ -export const InstanceAutoRestartPolicy = z.preprocess( - processResponseBody, - z.enum(['never', 'best_effort']) -) - /** * Describe the instance's disks at creation time */ @@ -1852,7 +1853,110 @@ export const InstanceSerialConsoleData = z.preprocess( */ export const InstanceUpdate = z.preprocess( processResponseBody, - z.object({ bootDisk: NameOrId.optional() }) + z.object({ + autoRestartPolicy: InstanceAutoRestartPolicy.optional(), + bootDisk: NameOrId.optional(), + }) +) + +/** + * An internet gateway provides a path between VPC networks and external networks. + */ +export const InternetGateway = z.preprocess( + processResponseBody, + z.object({ + description: z.string(), + id: z.string().uuid(), + name: Name, + timeCreated: z.coerce.date(), + timeModified: z.coerce.date(), + vpcId: z.string().uuid(), + }) +) + +/** + * Create-time parameters for an `InternetGateway` + */ +export const InternetGatewayCreate = z.preprocess( + processResponseBody, + z.object({ description: z.string(), name: Name }) +) + +/** + * An IP address that is attached to an internet gateway + */ +export const InternetGatewayIpAddress = z.preprocess( + processResponseBody, + z.object({ + address: z.string().ip(), + description: z.string(), + id: z.string().uuid(), + internetGatewayId: z.string().uuid(), + name: Name, + timeCreated: z.coerce.date(), + timeModified: z.coerce.date(), + }) +) + +/** + * Create-time identity-related parameters + */ +export const InternetGatewayIpAddressCreate = z.preprocess( + processResponseBody, + z.object({ + address: z.string().ip(), + description: z.string(), + gateway: NameOrId, + name: Name, + }) +) + +/** + * A single page of results + */ +export const InternetGatewayIpAddressResultsPage = z.preprocess( + processResponseBody, + z.object({ items: InternetGatewayIpAddress.array(), nextPage: z.string().optional() }) +) + +/** + * An IP pool that is attached to an internet gateway + */ +export const InternetGatewayIpPool = z.preprocess( + processResponseBody, + z.object({ + description: z.string(), + id: z.string().uuid(), + internetGatewayId: z.string().uuid(), + ipPoolId: z.string().uuid(), + name: Name, + timeCreated: z.coerce.date(), + timeModified: z.coerce.date(), + }) +) + +/** + * Create-time identity-related parameters + */ +export const InternetGatewayIpPoolCreate = z.preprocess( + processResponseBody, + z.object({ description: z.string(), ipPool: NameOrId, name: Name }) +) + +/** + * A single page of results + */ +export const InternetGatewayIpPoolResultsPage = z.preprocess( + processResponseBody, + z.object({ items: InternetGatewayIpPool.array(), nextPage: z.string().optional() }) +) + +/** + * A single page of results + */ +export const InternetGatewayResultsPage = z.preprocess( + processResponseBody, + z.object({ items: InternetGateway.array(), nextPage: z.string().optional() }) ) /** @@ -4348,6 +4452,142 @@ export const InstanceStopParams = z.preprocess( }) ) +export const InternetGatewayIpAddressListParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + gateway: NameOrId.optional(), + limit: z.number().min(1).max(4294967295).optional(), + pageToken: z.string().optional(), + project: NameOrId.optional(), + sortBy: NameOrIdSortMode.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpAddressCreateParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + gateway: NameOrId, + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpAddressDeleteParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + address: NameOrId, + }), + query: z.object({ + cascade: SafeBoolean.optional(), + gateway: NameOrId.optional(), + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpPoolListParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + gateway: NameOrId.optional(), + limit: z.number().min(1).max(4294967295).optional(), + pageToken: z.string().optional(), + project: NameOrId.optional(), + sortBy: NameOrIdSortMode.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpPoolCreateParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + gateway: NameOrId, + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayIpPoolDeleteParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + pool: NameOrId, + }), + query: z.object({ + cascade: SafeBoolean.optional(), + gateway: NameOrId.optional(), + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayListParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + limit: z.number().min(1).max(4294967295).optional(), + pageToken: z.string().optional(), + project: NameOrId.optional(), + sortBy: NameOrIdSortMode.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayCreateParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({}), + query: z.object({ + project: NameOrId.optional(), + vpc: NameOrId, + }), + }) +) + +export const InternetGatewayViewParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + gateway: NameOrId, + }), + query: z.object({ + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + +export const InternetGatewayDeleteParams = z.preprocess( + processResponseBody, + z.object({ + path: z.object({ + gateway: NameOrId, + }), + query: z.object({ + cascade: SafeBoolean.optional(), + project: NameOrId.optional(), + vpc: NameOrId.optional(), + }), + }) +) + export const ProjectIpPoolListParams = z.preprocess( processResponseBody, z.object({ From 07d96975c2269cfe373c88ae16285d451b380b65 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Thu, 3 Oct 2024 17:46:04 -0700 Subject: [PATCH 02/20] update msw handlers --- mock-api/msw/handlers.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index c81f53d95..317bcfed0 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -1519,6 +1519,16 @@ export const handlers = makeHandlers({ certificateView: NotImplemented, instanceSerialConsoleStream: NotImplemented, instanceSshPublicKeyList: NotImplemented, + internetGatewayIpAddressCreate: NotImplemented, + internetGatewayIpAddressDelete: NotImplemented, + internetGatewayIpAddressList: NotImplemented, + internetGatewayIpPoolCreate: NotImplemented, + internetGatewayIpPoolDelete: NotImplemented, + internetGatewayIpPoolList: NotImplemented, + internetGatewayCreate: NotImplemented, + internetGatewayDelete: NotImplemented, + internetGatewayList: NotImplemented, + internetGatewayView: NotImplemented, ipPoolServiceRangeAdd: NotImplemented, ipPoolServiceRangeList: NotImplemented, ipPoolServiceRangeRemove: NotImplemented, From 954700504fa6c13c2b4993ca8a6cc61769083f7f Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Fri, 4 Oct 2024 09:06:40 -0700 Subject: [PATCH 03/20] Other changes from initial branch --- app/api/path-params.ts | 1 + app/pages/project/vpcs/VpcPage/VpcPage.tsx | 1 + .../vpcs/VpcPage/tabs/VpcGatewaysTab.tsx | 68 +++++++++ app/routes.tsx | 11 ++ app/util/path-builder.ts | 6 + mock-api/index.ts | 1 + mock-api/internet-gateway.ts | 129 ++++++++++++++++++ mock-api/msw/db.ts | 3 + mock-api/msw/handlers.ts | 6 +- 9 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 mock-api/internet-gateway.ts diff --git a/app/api/path-params.ts b/app/api/path-params.ts index eaeab465a..baa7674c6 100644 --- a/app/api/path-params.ts +++ b/app/api/path-params.ts @@ -19,6 +19,7 @@ export type VpcRouter = Merge export type VpcRouterRoute = Merge export type VpcSubnet = Merge export type FirewallRule = Merge +export type InternetGateway = Merge export type Silo = { silo?: string } export type IdentityProvider = Merge export type SystemUpdate = { version: string } diff --git a/app/pages/project/vpcs/VpcPage/VpcPage.tsx b/app/pages/project/vpcs/VpcPage/VpcPage.tsx index 5c5c5d912..b79fce4e8 100644 --- a/app/pages/project/vpcs/VpcPage/VpcPage.tsx +++ b/app/pages/project/vpcs/VpcPage/VpcPage.tsx @@ -103,6 +103,7 @@ export function VpcPage() { Firewall Rules Subnets Routers + Internet Gateways ) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx index 262139f28..44013b1c6 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx @@ -5,3 +5,71 @@ * * Copyright Oxide Computer Company */ + +import { createColumnHelper } from '@tanstack/react-table' +import { useMemo } from 'react' +import { Outlet, type LoaderFunctionArgs } from 'react-router-dom' + +import { apiQueryClient, usePrefetchedApiQuery, type InternetGateway } from '~/api' +import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' +import { makeLinkCell } from '~/table/cells/LinkCell' +import { Columns } from '~/table/columns/common' +import { useQueryTable } from '~/table/QueryTable' +import { CreateLink } from '~/ui/lib/CreateButton' +import { EmptyMessage } from '~/ui/lib/EmptyMessage' +import { ALL_ISH } from '~/util/consts' +import { pb } from '~/util/path-builder' + +const colHelper = createColumnHelper() + +VpcInternetGatewaysTab.loader = async ({ params }: LoaderFunctionArgs) => { + const { project, vpc } = getVpcSelector(params) + await apiQueryClient.prefetchQuery('internetGatewayList', { + query: { project, vpc, limit: ALL_ISH }, + }) + return null +} + +export function VpcInternetGatewaysTab() { + const vpcSelector = useVpcSelector() + const { project, vpc } = vpcSelector + const igs = usePrefetchedApiQuery('internetGatewayList', { + query: { project, vpc, limit: ALL_ISH }, + }) + const { Table } = useQueryTable('internetGatewayList', { + query: { project, vpc, limit: ALL_ISH }, + }) + + console.log({ igs }) + const emptyState = ( + + ) + + const staticColumns = useMemo( + () => [ + colHelper.accessor('name', { + cell: makeLinkCell((gateway) => pb.vpcInternetGateway({ ...vpcSelector, gateway })), + }), + colHelper.accessor('description', Columns.description), + colHelper.accessor('timeCreated', Columns.timeCreated), + ], + [vpcSelector] + ) + + return ( + <> +
+ + New internet gateway + +
+ + + + ) +} diff --git a/app/routes.tsx b/app/routes.tsx index 86b159f5f..f4a7d0017 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -68,6 +68,7 @@ import { InstancesPage } from './pages/project/instances/InstancesPage' import { SnapshotsPage } from './pages/project/snapshots/SnapshotsPage' import { RouterPage } from './pages/project/vpcs/RouterPage' import { VpcFirewallRulesTab } from './pages/project/vpcs/VpcPage/tabs/VpcFirewallRulesTab' +import { VpcInternetGatewaysTab } from './pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab' import { VpcRoutersTab } from './pages/project/vpcs/VpcPage/tabs/VpcRoutersTab' import { VpcSubnetsTab } from './pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab' import { VpcPage } from './pages/project/vpcs/VpcPage/VpcPage' @@ -412,6 +413,16 @@ export const routes = createRoutesFromElements( handle={{ crumb: 'New Router' }} /> + } + loader={VpcInternetGatewaysTab.loader} + > + + diff --git a/app/util/path-builder.ts b/app/util/path-builder.ts index 1709b19c5..6426584e9 100644 --- a/app/util/path-builder.ts +++ b/app/util/path-builder.ts @@ -25,6 +25,7 @@ type FirewallRule = Required type VpcRouter = Required type VpcRouterRoute = Required type VpcSubnet = Required +type VpcInternetGateway = Required // these are used as the basis for many routes but are not themselves routes we // ever want to link to. so we use this to build the routes but pb.project() is @@ -97,6 +98,11 @@ export const pb = { vpcSubnetsNew: (params: Vpc) => `${vpcBase(params)}/subnets-new`, vpcSubnetsEdit: (params: VpcSubnet) => `${pb.vpcSubnets(params)}/${params.subnet}/edit`, + vpcInternetGateways: (params: Vpc) => `${vpcBase(params)}/internet-gateways`, + vpcInternetGateway: (params: VpcInternetGateway) => + `${pb.vpcInternetGateways(params)}/${params.gateway}`, + vpcInternetGatewaysNew: (params: Vpc) => `${vpcBase(params)}/internet-gateways-new`, + floatingIps: (params: Project) => `${projectBase(params)}/floating-ips`, floatingIpsNew: (params: Project) => `${projectBase(params)}/floating-ips-new`, floatingIp: (params: FloatingIp) => `${pb.floatingIps(params)}/${params.floatingIp}`, diff --git a/mock-api/index.ts b/mock-api/index.ts index e03311145..0256808bf 100644 --- a/mock-api/index.ts +++ b/mock-api/index.ts @@ -11,6 +11,7 @@ export * from './external-ip' export * from './floating-ip' export * from './image' export * from './instance' +export * from './internet-gateway' export * from './ip-pool' export * from './network-interface' export * from './physical-disk' diff --git a/mock-api/internet-gateway.ts b/mock-api/internet-gateway.ts new file mode 100644 index 000000000..c8bb4d688 --- /dev/null +++ b/mock-api/internet-gateway.ts @@ -0,0 +1,129 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ +import type { + InternetGateway, + InternetGatewayIpAddress, + InternetGatewayIpPool, +} from '@oxide/api' + +import { ipPools } from './ip-pool' +import type { Json } from './json-type' +import { vpc, vpc2 } from './vpc' + +const time_created = new Date(2021, 0, 1).toISOString() +const time_modified = new Date(2021, 0, 2).toISOString() + +// An internet gateway for VPC 1 +const internetGateway1: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9a', + name: 'internet-gateway-1', + description: 'internet gateway 1', + vpc_id: vpc.id, + time_created, + time_modified, +} + +// Another internet gateway for VPC 1 +const internetGateway2: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9b', + name: 'internet-gateway-2', + description: 'internet gateway 2', + vpc_id: vpc.id, + time_created, + time_modified, +} + +// An internet gateway for VPC 2 +const internetGateway3: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9c', + name: 'internet-gateway-3', + description: 'internet gateway 3', + vpc_id: vpc2.id, + time_created, + time_modified, +} + +export const internetGateways: Json[] = [ + internetGateway1, + internetGateway2, + internetGateway3, +] + +const internetGatewayIpAddress1: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9d', + address: '87.114.25.166', + description: 'the IP address for an internet gateway', + internet_gateway_id: internetGateway1.id, + name: 'internet-gateway-ip-1', + time_created, + time_modified, +} + +const internetGatewayIpAddress2: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9e', + address: '292a:a05c:3b36:a053:9166:6510:2d6b:3322', + description: 'an IPv6 address for an internet gateway', + internet_gateway_id: internetGateway1.id, + name: 'internet-gateway-ip-2', + time_created, + time_modified, +} + +const internetGatewayIpAddress3: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9f', + address: '178.125.253.126', + description: 'an IPv4 address for internet gateway 2', + internet_gateway_id: internetGateway2.id, + name: 'internet-gateway-ip-3', + time_created, + time_modified, +} + +export const internetGatewayIpAddresses: Json[] = [ + internetGatewayIpAddress1, + internetGatewayIpAddress2, + internetGatewayIpAddress3, +] + +const [ipPool1, ipPool2, ipPool3] = ipPools + +const internetGatewayIpPool1: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9g', + name: 'internet-gateway-ip-pool-1', + description: 'an IP pool for an internet gateway', + internet_gateway_id: internetGateway1.id, + ip_pool_id: ipPool1.id, + time_created, + time_modified, +} + +const internetGatewayIpPool2: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9h', + name: 'internet-gateway-ip-pool-2', + description: 'a set of VPN IPs in an IP pool for an internet gateway', + internet_gateway_id: internetGateway1.id, + ip_pool_id: ipPool2.id, + time_created, + time_modified, +} + +const internetGatewayIpPool3: Json = { + id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9i', + name: 'internet-gateway-ip-pool-3', + description: 'another IP pool for an internet gateway', + internet_gateway_id: internetGateway2.id, + ip_pool_id: ipPool3.id, + time_created, + time_modified, +} + +export const internetGatewayIpPools: Json[] = [ + internetGatewayIpPool1, + internetGatewayIpPool2, + internetGatewayIpPool3, +] diff --git a/mock-api/msw/db.ts b/mock-api/msw/db.ts index f9e1b26ed..22e9079ae 100644 --- a/mock-api/msw/db.ts +++ b/mock-api/msw/db.ts @@ -404,6 +404,9 @@ const initDb = { images: [...mock.images], ephemeralIps: [...mock.ephemeralIps], instances: [...mock.instances], + internetGatewayIpAddresses: [...mock.internetGatewayIpAddresses], + internetGatewayIpPools: [...mock.internetGatewayIpPools], + internetGateways: [...mock.internetGateways], ipPools: [...mock.ipPools], ipPoolSilos: [...mock.ipPoolSilos], ipPoolRanges: [...mock.ipPoolRanges], diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index 317bcfed0..53aca842b 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -1155,6 +1155,11 @@ export const handlers = makeHandlers({ return { rules: R.sortBy(rules, (r) => r.name) } }, + internetGatewayList({ query }) { + const vpc = lookup.vpc(query) + const gateways = db.internetGateways.filter((g) => g.vpc_id === vpc.id) + return paginated(query, gateways) + }, vpcRouterList({ query }) { const vpc = lookup.vpc(query) const routers = db.vpcRouters.filter((r) => r.vpc_id === vpc.id) @@ -1527,7 +1532,6 @@ export const handlers = makeHandlers({ internetGatewayIpPoolList: NotImplemented, internetGatewayCreate: NotImplemented, internetGatewayDelete: NotImplemented, - internetGatewayList: NotImplemented, internetGatewayView: NotImplemented, ipPoolServiceRangeAdd: NotImplemented, ipPoolServiceRangeList: NotImplemented, From f7c4b10b040e789ac44b55e85d6a06c98f3c7674 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Fri, 4 Oct 2024 23:18:09 -0700 Subject: [PATCH 04/20] IP Pools tab in place --- app/api/path-params.ts | 2 + app/components/ErrorBoundary.tsx | 1 + app/hooks/use-params.ts | 3 + .../InternetGatewayPage.tsx | 66 +++++++++++++ .../tabs/InternetGatewayIpAddressesTab.tsx | 31 +++++++ .../tabs/InternetGatewayIpPoolsTab.tsx | 93 +++++++++++++++++++ .../vpcs/VpcPage/tabs/VpcGatewaysTab.tsx | 55 ++++++++--- app/pages/system/networking/IpPoolsPage.tsx | 11 +-- app/routes.tsx | 28 ++++++ app/table/cells/UtilizationCell.tsx | 17 ++++ app/util/path-builder.ts | 4 + mock-api/msw/db.ts | 38 ++++++++ mock-api/msw/handlers.ts | 18 +++- 13 files changed, 343 insertions(+), 24 deletions(-) create mode 100644 app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx create mode 100644 app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx create mode 100644 app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx create mode 100644 app/table/cells/UtilizationCell.tsx diff --git a/app/api/path-params.ts b/app/api/path-params.ts index baa7674c6..5cc088a19 100644 --- a/app/api/path-params.ts +++ b/app/api/path-params.ts @@ -20,6 +20,8 @@ export type VpcRouterRoute = Merge export type VpcSubnet = Merge export type FirewallRule = Merge export type InternetGateway = Merge +export type InternetGatewayIpAddress = Merge +export type InternetGatewayIpPool = Merge export type Silo = { silo?: string } export type IdentityProvider = Merge export type SystemUpdate = { version: string } diff --git a/app/components/ErrorBoundary.tsx b/app/components/ErrorBoundary.tsx index 91085e08f..52db88e5c 100644 --- a/app/components/ErrorBoundary.tsx +++ b/app/components/ErrorBoundary.tsx @@ -38,5 +38,6 @@ export const ErrorBoundary = (props: { children: React.ReactNode }) => ( export function RouterDataErrorBoundary() { // TODO: validate this unknown at runtime _before_ passing to ErrorFallback const error = useRouteError() as Props['error'] + console.error(error) return } diff --git a/app/hooks/use-params.ts b/app/hooks/use-params.ts index 348e48d3b..d55792c21 100644 --- a/app/hooks/use-params.ts +++ b/app/hooks/use-params.ts @@ -40,6 +40,7 @@ export const getFirewallRuleSelector = requireParams('project', 'vpc', 'rule') export const getVpcRouterSelector = requireParams('project', 'vpc', 'router') export const getVpcRouterRouteSelector = requireParams('project', 'vpc', 'router', 'route') export const getVpcSubnetSelector = requireParams('project', 'vpc', 'subnet') +export const getInternetGatewaySelector = requireParams('project', 'vpc', 'gateway') export const getSiloSelector = requireParams('silo') export const getSiloImageSelector = requireParams('image') export const getIdpSelector = requireParams('silo', 'provider') @@ -84,6 +85,8 @@ export const useVpcSelector = () => useSelectedParams(getVpcSelector) export const useVpcRouterSelector = () => useSelectedParams(getVpcRouterSelector) export const useVpcRouterRouteSelector = () => useSelectedParams(getVpcRouterRouteSelector) export const useVpcSubnetSelector = () => useSelectedParams(getVpcSubnetSelector) +export const useInternetGatewaySelector = () => + useSelectedParams(getInternetGatewaySelector) export const useFirewallRuleSelector = () => useSelectedParams(getFirewallRuleSelector) export const useSiloSelector = () => useSelectedParams(getSiloSelector) export const useSiloImageSelector = () => useSelectedParams(getSiloImageSelector) diff --git a/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx new file mode 100644 index 000000000..4575bcd25 --- /dev/null +++ b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx @@ -0,0 +1,66 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ + +import { type LoaderFunctionArgs } from 'react-router-dom' + +import { Networking24Icon } from '@oxide/design-system/icons/react' + +import { apiQueryClient, usePrefetchedApiQuery } from '~/api' +import { RouteTabs, Tab } from '~/components/RouteTabs' +import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' +import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' +import { ALL_ISH } from '~/util/consts' +import { pb } from '~/util/path-builder' + +InternetGatewayPage.loader = async function ({ params }: LoaderFunctionArgs) { + console.log('InternetGatewayPage.loader') + const { project, vpc, gateway } = getInternetGatewaySelector(params) + console.log({ project, vpc, gateway }) + const query = { project, vpc, gateway, limit: ALL_ISH } + await Promise.all([ + apiQueryClient.prefetchQuery('internetGatewayView', { + query: { project, vpc }, + path: { gateway }, + }), + apiQueryClient.prefetchQuery('internetGatewayIpAddressList', { query }), + apiQueryClient.prefetchQuery('internetGatewayIpPoolList', { query }), + ]) + return null +} + +export function InternetGatewayPage() { + const gatewaySelector = useInternetGatewaySelector() + const { project, vpc, gateway } = gatewaySelector + // const query = { project, vpc, limit: ALL_ISH } + + const { data: internetGateway } = usePrefetchedApiQuery('internetGatewayView', { + query: { project, vpc }, + path: { gateway }, + }) + + console.log(internetGateway) + // const { Table: IpPoolsTable } = useQueryTable('internetGatewayIpPoolsList', { + // query, + // }) + + // const { Table: IpAddressesTable } = useQueryTable('internetGatewayIpAddressList', { + // query, + // }) + + return ( + <> + + }>{gateway} + + + IP Pools + IP Addresses + + + ) +} diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx new file mode 100644 index 000000000..27e5e6c9a --- /dev/null +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx @@ -0,0 +1,31 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ + +import type { LoaderFunctionArgs } from 'react-router-dom' + +import { apiQueryClient } from '~/api' +import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' +import { ALL_ISH } from '~/util/consts' + +InternetGatewayIpAddressesTab.loader = async function ({ params }: LoaderFunctionArgs) { + const { project, vpc, gateway } = getInternetGatewaySelector(params) + await Promise.all([ + apiQueryClient.prefetchQuery('internetGatewayIpAddressList', { + query: { project, vpc, gateway, limit: ALL_ISH }, + }), + ]) + return null +} + +export function InternetGatewayIpAddressesTab() { + const gatewaySelector = useInternetGatewaySelector() + const { project, vpc, gateway } = gatewaySelector + // const query = { project, vpc, limit: ALL_ISH } + console.log({ project, vpc, gateway }) + return <>IP Addresses stuff will go here +} diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx new file mode 100644 index 000000000..6a573bb1b --- /dev/null +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx @@ -0,0 +1,93 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ + +import { createColumnHelper } from '@tanstack/react-table' +import { useMemo } from 'react' +import type { LoaderFunctionArgs } from 'react-router-dom' + +import { apiQueryClient, usePrefetchedApiQuery, type InternetGatewayIpPool } from '~/api' +import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' +import { DescriptionCell } from '~/table/cells/DescriptionCell' +import { EmptyCell } from '~/table/cells/EmptyCell' +import { UtilizationCell } from '~/table/cells/UtilizationCell' +import { Columns } from '~/table/columns/common' +import { useQueryTable } from '~/table/QueryTable' +import { EmptyMessage } from '~/ui/lib/EmptyMessage' +import { ALL_ISH } from '~/util/consts' + +InternetGatewayIpPoolsTab.loader = async function ({ params }: LoaderFunctionArgs) { + const { project, vpc, gateway } = getInternetGatewaySelector(params) + await Promise.all([ + apiQueryClient.prefetchQuery('internetGatewayIpPoolList', { + query: { project, vpc, gateway, limit: ALL_ISH }, + }), + // get IP Pools + apiQueryClient.prefetchQuery('ipPoolList', { query: { limit: ALL_ISH } }), + ]) + return null +} + +const colHelper = createColumnHelper() + +export function InternetGatewayIpPoolsTab() { + const { project, vpc, gateway } = useInternetGatewaySelector() + const { Table } = useQueryTable('internetGatewayIpPoolList', { + query: { project, vpc, gateway, limit: ALL_ISH }, + }) + const { data: ipPools } = usePrefetchedApiQuery('ipPoolList', { + query: { limit: ALL_ISH }, + }) + + const emptyState = ( + + ) + + const staticColumns = useMemo( + () => [ + colHelper.accessor('name', {}), + colHelper.accessor('description', Columns.description), + colHelper.accessor('ipPoolId', { + header: 'IP Pool Name', + cell: (info) => { + const ipPool = ipPools.items.find((item) => item.id === info.getValue()) + return ipPool?.name || + }, + }), + colHelper.accessor('ipPoolId', { + header: 'IP Pool Description', + cell: (info) => { + const ipPool = ipPools.items.find((item) => item.id === info.getValue()) + return + }, + }), + colHelper.accessor('ipPoolId', { + header: 'IP Pool Utilization', + cell: (info) => { + const ipPool = ipPools.items.find((item) => item.id === info.getValue()) + return + }, + }), + ], + [ipPools] + ) + + // const makeActions = (info) => { + // return [ + // + // ] + // } + + return ( + <> +
+ + ) +} diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx index 44013b1c6..d2887bf1d 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx @@ -10,12 +10,17 @@ import { createColumnHelper } from '@tanstack/react-table' import { useMemo } from 'react' import { Outlet, type LoaderFunctionArgs } from 'react-router-dom' -import { apiQueryClient, usePrefetchedApiQuery, type InternetGateway } from '~/api' +import { + apiQueryClient, + type InternetGateway, + // type InternetGatewayIpAddress, + // type InternetGatewayIpPool, +} from '~/api' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { makeLinkCell } from '~/table/cells/LinkCell' import { Columns } from '~/table/columns/common' import { useQueryTable } from '~/table/QueryTable' -import { CreateLink } from '~/ui/lib/CreateButton' +// import { CreateLink } from '~/ui/lib/CreateButton' import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { ALL_ISH } from '~/util/consts' import { pb } from '~/util/path-builder' @@ -24,23 +29,50 @@ const colHelper = createColumnHelper() VpcInternetGatewaysTab.loader = async ({ params }: LoaderFunctionArgs) => { const { project, vpc } = getVpcSelector(params) - await apiQueryClient.prefetchQuery('internetGatewayList', { - query: { project, vpc, limit: ALL_ISH }, - }) + const query = { project, vpc, limit: ALL_ISH } + await Promise.all([ + apiQueryClient.prefetchQuery('internetGatewayList', { query }), + apiQueryClient.prefetchQuery('internetGatewayIpAddressList', { query }), + apiQueryClient.prefetchQuery('internetGatewayIpPoolList', { query }), + ]) return null } export function VpcInternetGatewaysTab() { const vpcSelector = useVpcSelector() const { project, vpc } = vpcSelector - const igs = usePrefetchedApiQuery('internetGatewayList', { - query: { project, vpc, limit: ALL_ISH }, - }) + // const { data: internetGatewayIpAddresses } = usePrefetchedApiQuery( + // 'internetGatewayIpAddressList', + // { query } + // ) + // const { data: internetGatewayIpPools } = usePrefetchedApiQuery( + // 'internetGatewayIpPoolList', + // { query } + // ) const { Table } = useQueryTable('internetGatewayList', { query: { project, vpc, limit: ALL_ISH }, }) - console.log({ igs }) + // type PartitionedIpAddresses = Record + // const partitionedIpAddresses: PartitionedIpAddresses = useMemo(() => { + // if (!internetGatewayIpAddresses) return {} + // return internetGatewayIpAddresses.items.reduce((acc, ip: InternetGatewayIpAddress) => { + // acc[ip.internetGatewayId] = acc[ip.internetGatewayId] || [] + // acc[ip.internetGatewayId].push(ip) + // return acc + // }, {} as PartitionedIpAddresses) + // }, [internetGatewayIpAddresses]) + + // type PartitionedIpPools = Record + // const partitionedIpPools: PartitionedIpPools = useMemo(() => { + // if (!internetGatewayIpPools) return {} + // return internetGatewayIpPools.items.reduce((acc, ip: InternetGatewayIpPool) => { + // acc[ip.internetGatewayId] = acc[ip.internetGatewayId] || [] + // acc[ip.internetGatewayId].push(ip) + // return acc + // }, {} as PartitionedIpPools) + // }, [internetGatewayIpPools]) + const emptyState = (
- + {/* Add this back in when moving out of read-only mode */} + {/* New internet gateway - + */}
diff --git a/app/pages/system/networking/IpPoolsPage.tsx b/app/pages/system/networking/IpPoolsPage.tsx index 8084dc77f..b1ea91fb5 100644 --- a/app/pages/system/networking/IpPoolsPage.tsx +++ b/app/pages/system/networking/IpPoolsPage.tsx @@ -13,19 +13,17 @@ import { Outlet, useNavigate } from 'react-router-dom' import { apiQueryClient, useApiMutation, - useApiQuery, usePrefetchedApiQuery, type IpPool, } from '@oxide/api' import { IpGlobal16Icon, IpGlobal24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' -import { IpUtilCell } from '~/components/IpPoolUtilization' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' -import { SkeletonCell } from '~/table/cells/EmptyCell' import { makeLinkCell } from '~/table/cells/LinkCell' +import { UtilizationCell } from '~/table/cells/UtilizationCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' import { Columns } from '~/table/columns/common' import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable' @@ -46,13 +44,6 @@ const EmptyState = () => ( /> ) -function UtilizationCell({ pool }: { pool: string }) { - const { data } = useApiQuery('ipPoolUtilizationView', { path: { pool } }) - - if (!data) return - return -} - const colHelper = createColumnHelper() const staticColumns = [ diff --git a/app/routes.tsx b/app/routes.tsx index f4a7d0017..18ed21560 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -66,6 +66,9 @@ import { NetworkingTab } from './pages/project/instances/instance/tabs/Networkin import { StorageTab } from './pages/project/instances/instance/tabs/StorageTab' import { InstancesPage } from './pages/project/instances/InstancesPage' import { SnapshotsPage } from './pages/project/snapshots/SnapshotsPage' +import { InternetGatewayPage } from './pages/project/vpcs/InternetGatewayPage/InternetGatewayPage' +import { InternetGatewayIpAddressesTab } from './pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab' +import { InternetGatewayIpPoolsTab } from './pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab' import { RouterPage } from './pages/project/vpcs/RouterPage' import { VpcFirewallRulesTab } from './pages/project/vpcs/VpcPage/tabs/VpcFirewallRulesTab' import { VpcInternetGatewaysTab } from './pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab' @@ -94,6 +97,7 @@ import { pb } from './util/path-builder' const projectCrumb: CrumbFunc = (m) => m.params.project! const instanceCrumb: CrumbFunc = (m) => m.params.instance! const vpcCrumb: CrumbFunc = (m) => m.params.vpc! +const internetGatewayCrumb: CrumbFunc = (m) => m.params.gateway! const siloCrumb: CrumbFunc = (m) => m.params.silo! const poolCrumb: CrumbFunc = (m) => m.params.pool! @@ -445,6 +449,30 @@ export const routes = createRoutesFromElements( handle={{ crumb: 'Edit Route' }} /> + + } loader={InternetGatewayPage.loader}> + } + loader={InternetGatewayIpPoolsTab.loader} + /> + } + loader={InternetGatewayIpPoolsTab.loader} + handle={{ crumb: 'IP Pools' }} + /> + } + loader={InternetGatewayIpAddressesTab.loader} + handle={{ crumb: 'IP Addresses' }} + /> + + } loader={FloatingIpsPage.loader}> { + const { data } = useApiQuery('ipPoolUtilizationView', { path: { pool } }) + + if (!data) return + return +} diff --git a/app/util/path-builder.ts b/app/util/path-builder.ts index 6426584e9..5ed1e89cf 100644 --- a/app/util/path-builder.ts +++ b/app/util/path-builder.ts @@ -102,6 +102,10 @@ export const pb = { vpcInternetGateway: (params: VpcInternetGateway) => `${pb.vpcInternetGateways(params)}/${params.gateway}`, vpcInternetGatewaysNew: (params: Vpc) => `${vpcBase(params)}/internet-gateways-new`, + vpcInternetGatewayIpPools: (params: VpcInternetGateway) => + `${pb.vpcInternetGateway(params)}/ip-pools`, + vpcInternetGatewayIpAddresses: (params: VpcInternetGateway) => + `${pb.vpcInternetGateway(params)}/ip-addresses`, floatingIps: (params: Project) => `${projectBase(params)}/floating-ips`, floatingIpsNew: (params: Project) => `${projectBase(params)}/floating-ips-new`, diff --git a/mock-api/msw/db.ts b/mock-api/msw/db.ts index 22e9079ae..8474a88f2 100644 --- a/mock-api/msw/db.ts +++ b/mock-api/msw/db.ts @@ -214,6 +214,44 @@ export const lookup = { return subnet }, + internetGateway({ + gateway: id, + ...vpcSelector + }: PP.InternetGateway): Json { + if (!id) throw notFoundErr('no internet gateway specified') + + if (isUuid(id)) { + ensureNoParentSelectors('internet gateway', vpcSelector) + return lookupById(db.internetGateways, id) + } + + const vpc = lookup.vpc(vpcSelector) + const internetGateway = db.internetGateways.find( + (ig) => ig.vpc_id === vpc.id && ig.name === id + ) + if (!internetGateway) throw notFoundErr(`internet gateway '${id}'`) + + return internetGateway + }, + internetGatewayIpAddress({ + address: id, + ...gatewaySelector + }: PP.InternetGatewayIpAddress): Json { + if (!id) throw notFoundErr('no IP address specified') + + if (isUuid(id)) { + ensureNoParentSelectors('IP address', gatewaySelector) + return lookupById(db.internetGatewayIpAddresses, id) + } + + const gateway = lookup.internetGateway(gatewaySelector) + const ip = db.internetGatewayIpAddresses.find( + (i) => i.internet_gateway_id === gateway.id && i.name === id + ) + if (!ip) throw notFoundErr(`IP address '${id}'`) + + return ip + }, image({ image: id, project: projectId }: PP.Image): Json { if (!id) throw notFoundErr('no image specified') diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index 53aca842b..e0e391434 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -1160,6 +1160,21 @@ export const handlers = makeHandlers({ const gateways = db.internetGateways.filter((g) => g.vpc_id === vpc.id) return paginated(query, gateways) }, + internetGatewayView: ({ path, query }) => lookup.internetGateway({ ...path, ...query }), + internetGatewayIpPoolList({ query }) { + const gateway = lookup.internetGateway(query) + const pools = db.internetGatewayIpPools.filter( + (p) => p.internet_gateway_id === gateway.id + ) + return paginated(query, pools) + }, + internetGatewayIpAddressList({ query }) { + const gateway = lookup.internetGateway(query) + const addresses = db.internetGatewayIpAddresses.filter( + (a) => a.internet_gateway_id === gateway.id + ) + return paginated(query, addresses) + }, vpcRouterList({ query }) { const vpc = lookup.vpc(query) const routers = db.vpcRouters.filter((r) => r.vpc_id === vpc.id) @@ -1526,13 +1541,10 @@ export const handlers = makeHandlers({ instanceSshPublicKeyList: NotImplemented, internetGatewayIpAddressCreate: NotImplemented, internetGatewayIpAddressDelete: NotImplemented, - internetGatewayIpAddressList: NotImplemented, internetGatewayIpPoolCreate: NotImplemented, internetGatewayIpPoolDelete: NotImplemented, - internetGatewayIpPoolList: NotImplemented, internetGatewayCreate: NotImplemented, internetGatewayDelete: NotImplemented, - internetGatewayView: NotImplemented, ipPoolServiceRangeAdd: NotImplemented, ipPoolServiceRangeList: NotImplemented, ipPoolServiceRangeRemove: NotImplemented, From aa2f363a18cdfe48855140f3864f35d3870d1bcc Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Sun, 6 Oct 2024 10:20:06 -0700 Subject: [PATCH 05/20] IP Pool ID is copyable from IP Pools tab --- .../tabs/InternetGatewayIpPoolsTab.tsx | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx index 6a573bb1b..5b6d3e7fd 100644 --- a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx @@ -7,7 +7,7 @@ */ import { createColumnHelper } from '@tanstack/react-table' -import { useMemo } from 'react' +import { useCallback, useMemo } from 'react' import type { LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, usePrefetchedApiQuery, type InternetGatewayIpPool } from '~/api' @@ -15,6 +15,7 @@ import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/ import { DescriptionCell } from '~/table/cells/DescriptionCell' import { EmptyCell } from '~/table/cells/EmptyCell' import { UtilizationCell } from '~/table/cells/UtilizationCell' +import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' import { Columns } from '~/table/columns/common' import { useQueryTable } from '~/table/QueryTable' import { EmptyMessage } from '~/ui/lib/EmptyMessage' @@ -79,15 +80,23 @@ export function InternetGatewayIpPoolsTab() { [ipPools] ) - // const makeActions = (info) => { - // return [ - // - // ] - // } + // The user can copy the ID of the IP Pool attached to this internet gateway + const makeActions = useCallback( + (internetGatewayIpPool: InternetGatewayIpPool): MenuAction[] => [ + { + label: 'Copy IP pool ID', + onActivate() { + window.navigator.clipboard.writeText(internetGatewayIpPool.ipPoolId) + }, + }, + ], + [] + ) + const columns = useColsWithActions(staticColumns, makeActions) return ( <> -
+
) } From 302cd1971fc6f92c1e0713f796d2ae56d9889cf2 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Sun, 6 Oct 2024 14:46:46 -0700 Subject: [PATCH 06/20] Add content to IP Addresses tab and root page --- .../InternetGatewayPage.tsx | 74 +++++++++++++++---- .../tabs/InternetGatewayIpAddressesTab.tsx | 44 +++++++++-- .../tabs/InternetGatewayIpPoolsTab.tsx | 6 +- 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx index 4575bcd25..e25ebc698 100644 --- a/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx +++ b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx @@ -6,15 +6,23 @@ * Copyright Oxide Computer Company */ +import { useMemo } from 'react' import { type LoaderFunctionArgs } from 'react-router-dom' -import { Networking24Icon } from '@oxide/design-system/icons/react' +import { Networking16Icon, Networking24Icon } from '@oxide/design-system/icons/react' import { apiQueryClient, usePrefetchedApiQuery } from '~/api' +import { DocsPopover } from '~/components/DocsPopover' +import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { RouteTabs, Tab } from '~/components/RouteTabs' import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' +import { DescriptionCell } from '~/table/cells/DescriptionCell' +import { DateTime } from '~/ui/lib/DateTime' +import { Message } from '~/ui/lib/Message' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { ALL_ISH } from '~/util/consts' +import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' InternetGatewayPage.loader = async function ({ params }: LoaderFunctionArgs) { @@ -36,27 +44,67 @@ InternetGatewayPage.loader = async function ({ params }: LoaderFunctionArgs) { export function InternetGatewayPage() { const gatewaySelector = useInternetGatewaySelector() const { project, vpc, gateway } = gatewaySelector - // const query = { project, vpc, limit: ALL_ISH } - - const { data: internetGateway } = usePrefetchedApiQuery('internetGatewayView', { + const { + data: { id, description, name, timeCreated, timeModified }, + } = usePrefetchedApiQuery('internetGatewayView', { query: { project, vpc }, path: { gateway }, }) - console.log(internetGateway) - // const { Table: IpPoolsTable } = useQueryTable('internetGatewayIpPoolsList', { - // query, - // }) - - // const { Table: IpAddressesTable } = useQueryTable('internetGatewayIpAddressList', { - // query, - // }) + const actions = useMemo( + () => [ + { + label: 'Copy ID', + onActivate() { + window.navigator.clipboard.writeText(id || '') + }, + }, + ], + [id] + ) return ( <> - }>{gateway} + }>{name} +
+ } + summary="Internet gateways … 🚨🙈🚨🙉🚨🙊🚨 … just using emojis here so we spot it more easily in the PR; this copy needs eyes 👀" + links={[docLinks.vpcs, docLinks.firewallRules]} + /> + +
+ + + + + + + + + + + + + + + + + + This is a read-only copy of this internet gateway. Use the CLI to create and + update internet gateways. More functionality for internet gateways will be + included in future releases of the Oxide console. + + } + /> + IP Pools IP Addresses diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx index 27e5e6c9a..0017870fe 100644 --- a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx @@ -6,10 +6,17 @@ * Copyright Oxide Computer Company */ +import { createColumnHelper } from '@tanstack/react-table' +import { useMemo } from 'react' import type { LoaderFunctionArgs } from 'react-router-dom' -import { apiQueryClient } from '~/api' +import { apiQueryClient, type InternetGatewayIpAddress } from '~/api' import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' +import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' +import { Columns } from '~/table/columns/common' +import { useQueryTable } from '~/table/QueryTable' +import { CopyableIp } from '~/ui/lib/CopyableIp' +import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { ALL_ISH } from '~/util/consts' InternetGatewayIpAddressesTab.loader = async function ({ params }: LoaderFunctionArgs) { @@ -22,10 +29,35 @@ InternetGatewayIpAddressesTab.loader = async function ({ params }: LoaderFunctio return null } +const colHelper = createColumnHelper() + export function InternetGatewayIpAddressesTab() { - const gatewaySelector = useInternetGatewaySelector() - const { project, vpc, gateway } = gatewaySelector - // const query = { project, vpc, limit: ALL_ISH } - console.log({ project, vpc, gateway }) - return <>IP Addresses stuff will go here + const { project, vpc, gateway } = useInternetGatewaySelector() + const { Table } = useQueryTable('internetGatewayIpAddressList', { + query: { project, vpc, gateway, limit: ALL_ISH }, + }) + + const emptyState = ( + + ) + + const staticColumns = useMemo( + () => [ + colHelper.accessor('name', {}), + colHelper.accessor('description', Columns.description), + colHelper.accessor('address', { + header: 'Address', + cell: (info) => , + }), + ], + [] + ) + + const makeActions = (): MenuAction[] => [] + + const columns = useColsWithActions(staticColumns, makeActions) + return
} diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx index 5b6d3e7fd..50adae7d2 100644 --- a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx @@ -94,9 +94,5 @@ export function InternetGatewayIpPoolsTab() { ) const columns = useColsWithActions(staticColumns, makeActions) - return ( - <> -
- - ) + return
} From c038387cb8faf1cd3099464a31b685365d837238 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 7 Oct 2024 00:12:10 -0400 Subject: [PATCH 07/20] Update OMICRON_VERSION to sha with internet gateways --- OMICRON_VERSION | 2 +- app/api/__generated__/OMICRON_VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OMICRON_VERSION b/OMICRON_VERSION index d3996f17a..2ac1cc36f 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -a9fd9dbbbcdaa80b822c3fc14b4f1a20ac72021d +e51641064a9aeb62b6461055505c53e43fbbe58c diff --git a/app/api/__generated__/OMICRON_VERSION b/app/api/__generated__/OMICRON_VERSION index 258888bf6..998389f18 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 -a9fd9dbbbcdaa80b822c3fc14b4f1a20ac72021d +e51641064a9aeb62b6461055505c53e43fbbe58c From d778b70de262f23b57095655c17e2de823458c44 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 7 Oct 2024 00:24:43 -0400 Subject: [PATCH 08/20] small tweaks --- .../InternetGatewayPage/InternetGatewayPage.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx index e25ebc698..4dfe30db6 100644 --- a/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx +++ b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx @@ -77,11 +77,14 @@ export function InternetGatewayPage() { - + + + + @@ -94,13 +97,14 @@ export function InternetGatewayPage() { - This is a read-only copy of this internet gateway. Use the CLI to create and - update internet gateways. More functionality for internet gateways will be - included in future releases of the Oxide console. + This is a read-only copy of this internet gateway and its IP pools and + addresses. Use the CLI to create and update internet gateways. More + functionality for internet gateways will be included in future releases of the + Oxide console. } /> From 0a62befe1dc8cbdc7f108f0e4f26d6e64a119803 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 7 Oct 2024 00:27:40 -0400 Subject: [PATCH 09/20] remove commented-out code --- .../vpcs/VpcPage/tabs/VpcGatewaysTab.tsx | 42 +------------------ 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx index d2887bf1d..0544f6ec8 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx @@ -10,17 +10,11 @@ import { createColumnHelper } from '@tanstack/react-table' import { useMemo } from 'react' import { Outlet, type LoaderFunctionArgs } from 'react-router-dom' -import { - apiQueryClient, - type InternetGateway, - // type InternetGatewayIpAddress, - // type InternetGatewayIpPool, -} from '~/api' +import { apiQueryClient, type InternetGateway } from '~/api' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { makeLinkCell } from '~/table/cells/LinkCell' import { Columns } from '~/table/columns/common' import { useQueryTable } from '~/table/QueryTable' -// import { CreateLink } from '~/ui/lib/CreateButton' import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { ALL_ISH } from '~/util/consts' import { pb } from '~/util/path-builder' @@ -41,38 +35,10 @@ VpcInternetGatewaysTab.loader = async ({ params }: LoaderFunctionArgs) => { export function VpcInternetGatewaysTab() { const vpcSelector = useVpcSelector() const { project, vpc } = vpcSelector - // const { data: internetGatewayIpAddresses } = usePrefetchedApiQuery( - // 'internetGatewayIpAddressList', - // { query } - // ) - // const { data: internetGatewayIpPools } = usePrefetchedApiQuery( - // 'internetGatewayIpPoolList', - // { query } - // ) const { Table } = useQueryTable('internetGatewayList', { query: { project, vpc, limit: ALL_ISH }, }) - // type PartitionedIpAddresses = Record - // const partitionedIpAddresses: PartitionedIpAddresses = useMemo(() => { - // if (!internetGatewayIpAddresses) return {} - // return internetGatewayIpAddresses.items.reduce((acc, ip: InternetGatewayIpAddress) => { - // acc[ip.internetGatewayId] = acc[ip.internetGatewayId] || [] - // acc[ip.internetGatewayId].push(ip) - // return acc - // }, {} as PartitionedIpAddresses) - // }, [internetGatewayIpAddresses]) - - // type PartitionedIpPools = Record - // const partitionedIpPools: PartitionedIpPools = useMemo(() => { - // if (!internetGatewayIpPools) return {} - // return internetGatewayIpPools.items.reduce((acc, ip: InternetGatewayIpPool) => { - // acc[ip.internetGatewayId] = acc[ip.internetGatewayId] || [] - // acc[ip.internetGatewayId].push(ip) - // return acc - // }, {} as PartitionedIpPools) - // }, [internetGatewayIpPools]) - const emptyState = ( -
- {/* Add this back in when moving out of read-only mode */} - {/* - New internet gateway - */} -
From f3d8d41241edf3879dfefa644149cac173961d79 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 7 Oct 2024 00:30:43 -0400 Subject: [PATCH 10/20] Use proper truncate component --- .../project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx index 4dfe30db6..c6a3893c3 100644 --- a/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx +++ b/app/pages/project/vpcs/InternetGatewayPage/InternetGatewayPage.tsx @@ -21,6 +21,7 @@ import { DateTime } from '~/ui/lib/DateTime' import { Message } from '~/ui/lib/Message' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { PropertiesTable } from '~/ui/lib/PropertiesTable' +import { Truncate } from '~/ui/lib/Truncate' import { ALL_ISH } from '~/util/consts' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' @@ -83,7 +84,7 @@ export function InternetGatewayPage() { - + From bc1a1038d5ecc7ce646c08f7cd660cd5924d0de3 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 7 Oct 2024 01:20:09 -0400 Subject: [PATCH 11/20] adjust path-builder spec --- app/util/path-builder.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/util/path-builder.spec.ts b/app/util/path-builder.spec.ts index 76e39846e..62933f089 100644 --- a/app/util/path-builder.spec.ts +++ b/app/util/path-builder.spec.ts @@ -12,6 +12,7 @@ import { pb } from './path-builder' // params can be the same for all of them because they only use what they need const params = { floatingIp: 'f', + gateway: 'g', project: 'p', instance: 'i', vpc: 'v', @@ -94,6 +95,11 @@ test('path builder', () => { "vpcFirewallRuleEdit": "/projects/p/vpcs/v/firewall-rules/fr/edit", "vpcFirewallRules": "/projects/p/vpcs/v/firewall-rules", "vpcFirewallRulesNew": "/projects/p/vpcs/v/firewall-rules-new", + "vpcInternetGateway": "/projects/p/vpcs/v/internet-gateways/g", + "vpcInternetGatewayIpAddresses": "/projects/p/vpcs/v/internet-gateways/g/ip-addresses", + "vpcInternetGatewayIpPools": "/projects/p/vpcs/v/internet-gateways/g/ip-pools", + "vpcInternetGateways": "/projects/p/vpcs/v/internet-gateways", + "vpcInternetGatewaysNew": "/projects/p/vpcs/v/internet-gateways-new", "vpcRouter": "/projects/p/vpcs/v/routers/r", "vpcRouterEdit": "/projects/p/vpcs/v/routers/r/edit", "vpcRouterRouteEdit": "/projects/p/vpcs/v/routers/r/routes/rr/edit", From 786393960a307552b02eca601e1163bec39a0239 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 7 Oct 2024 06:12:42 -0400 Subject: [PATCH 12/20] Add breadcrumb nav for internet gateways --- app/components/TopBarPicker.tsx | 24 ++++++++++++++++++++++++ app/layouts/ProjectLayout.tsx | 4 +++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/components/TopBarPicker.tsx b/app/components/TopBarPicker.tsx index 9e3c51599..323af00cd 100644 --- a/app/components/TopBarPicker.tsx +++ b/app/components/TopBarPicker.tsx @@ -17,6 +17,7 @@ import { import { useInstanceSelector, + useInternetGatewaySelector, useIpPoolSelector, useSiloSelector, useVpcRouterSelector, @@ -295,6 +296,29 @@ export function VpcRouterPicker() { ) } +/** Used when drilling down into a VPC Internet Gateway from the Silo view. */ +export function VpcGatewayPicker() { + // picker only shows up when an internet gateway is in scope + const { project, vpc, gateway } = useInternetGatewaySelector() + const { data } = useApiQuery('internetGatewayList', { + query: { project, vpc, limit: PAGE_SIZE }, + }) + const items = (data?.items || []).map((g) => ({ + label: g.name, + to: pb.vpcInternetGateway({ vpc, project, gateway: g.name }), + })) + + return ( + + ) +} + const NoProjectLogo = () => (
diff --git a/app/layouts/ProjectLayout.tsx b/app/layouts/ProjectLayout.tsx index 5c186c213..9ce5f6811 100644 --- a/app/layouts/ProjectLayout.tsx +++ b/app/layouts/ProjectLayout.tsx @@ -30,6 +30,7 @@ import { InstancePicker, ProjectPicker, SiloSystemPicker, + VpcGatewayPicker, VpcPicker, VpcRouterPicker, } from '~/components/TopBarPicker' @@ -61,7 +62,7 @@ export function ProjectLayout({ overrideContentPane }: ProjectLayoutProps) { const projectSelector = useProjectSelector() const { data: project } = usePrefetchedApiQuery('projectView', { path: projectSelector }) - const { instance, router, vpc } = useParams() + const { gateway, instance, router, vpc } = useParams() const { pathname } = useLocation() useQuickActions( useMemo( @@ -94,6 +95,7 @@ export function ProjectLayout({ overrideContentPane }: ProjectLayoutProps) { {instance && } {vpc && } {router && } + {gateway && } From 721a900ac6d847b6017859912c51564bf7103130 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 7 Oct 2024 11:52:14 -0400 Subject: [PATCH 13/20] Update default internet gateway IP pool to reflect actual default values --- .../tabs/InternetGatewayIpAddressesTab.tsx | 4 +-- mock-api/internet-gateway.ts | 28 +++++++++++++++++-- mock-api/ip-pool.ts | 19 ++++++++++++- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx index 0017870fe..fb7f60486 100644 --- a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpAddressesTab.tsx @@ -39,8 +39,8 @@ export function InternetGatewayIpAddressesTab() { const emptyState = ( ) diff --git a/mock-api/internet-gateway.ts b/mock-api/internet-gateway.ts index c8bb4d688..a8859e4fd 100644 --- a/mock-api/internet-gateway.ts +++ b/mock-api/internet-gateway.ts @@ -90,13 +90,33 @@ export const internetGatewayIpAddresses: Json[] = [ internetGatewayIpAddress3, ] -const [ipPool1, ipPool2, ipPool3] = ipPools +const [defaultIpPool, ipPool1, ipPool2, ipPool3] = ipPools + +const defaultInternetGatewayIpPool1: Json = { + id: '1d5e5a1f-0b2b-4d5b-8b9d-2d4b3e0c6gb9', + name: 'default', + description: 'Default internet gateway IP pool', + internet_gateway_id: internetGateway1.id, + ip_pool_id: defaultIpPool.id, + time_created, + time_modified, +} + +const defaultInternetGatewayIpPool2: Json = { + id: 'd5e5a1f1-0b2b-4d5b-8b9d-2d4b3e0c6b9c', + name: 'default', + description: 'Default internet gateway IP pool', + internet_gateway_id: internetGateway2.id, + ip_pool_id: defaultIpPool.id, + time_created, + time_modified, +} const internetGatewayIpPool1: Json = { id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9g', name: 'internet-gateway-ip-pool-1', description: 'an IP pool for an internet gateway', - internet_gateway_id: internetGateway1.id, + internet_gateway_id: internetGateway2.id, ip_pool_id: ipPool1.id, time_created, time_modified, @@ -106,7 +126,7 @@ const internetGatewayIpPool2: Json = { id: 'f1d5e5a1-0b2b-4d5b-8b9d-2d4b3e0c6b9h', name: 'internet-gateway-ip-pool-2', description: 'a set of VPN IPs in an IP pool for an internet gateway', - internet_gateway_id: internetGateway1.id, + internet_gateway_id: internetGateway2.id, ip_pool_id: ipPool2.id, time_created, time_modified, @@ -123,6 +143,8 @@ const internetGatewayIpPool3: Json = { } export const internetGatewayIpPools: Json[] = [ + defaultInternetGatewayIpPool1, + defaultInternetGatewayIpPool2, internetGatewayIpPool1, internetGatewayIpPool2, internetGatewayIpPool3, diff --git a/mock-api/ip-pool.ts b/mock-api/ip-pool.ts index 534125ff8..e7725c6c4 100644 --- a/mock-api/ip-pool.ts +++ b/mock-api/ip-pool.ts @@ -11,6 +11,14 @@ import { type IpPool, type IpPoolRange, type IpPoolSiloLink } from '@oxide/api' import type { Json } from './json-type' import { defaultSilo } from './silo' +export const defaultIpPool: Json = { + id: 'cadb8535-e32f-4be2-9259-e45dec9fa3cd', + name: 'default', + description: 'default IP pool', + time_created: new Date().toISOString(), + time_modified: new Date().toISOString(), +} + export const ipPool1: Json = { id: '69b5c583-74a9-451a-823d-0741c1ec66e2', name: 'ip-pool-1', @@ -43,7 +51,7 @@ const ipPool4: Json = { time_modified: new Date().toISOString(), } -export const ipPools: Json[] = [ipPool1, ipPool2, ipPool3, ipPool4] +export const ipPools: Json[] = [defaultIpPool, ipPool1, ipPool2, ipPool3, ipPool4] export const ipPoolSilos: Json[] = [ { @@ -59,6 +67,15 @@ export const ipPoolSilos: Json[] = [ ] export const ipPoolRanges: Json = [ + { + id: 'f6b3b9b5-7e3d-4b8c-9f6b-9b7b5e3d8c4b', + ip_pool_id: defaultIpPool.id, + range: { + first: '170.20.26.11', + last: '170.20.26.254', + }, + time_created: new Date().toISOString(), + }, { id: 'bbfcf3f2-061e-4334-a0e7-dfcd8171f87e', ip_pool_id: ipPool1.id, From b72ae0e816f8990a471350e280458463b8741061 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 7 Oct 2024 12:11:50 -0400 Subject: [PATCH 14/20] update unrelated test --- test/e2e/ip-pools.e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/ip-pools.e2e.ts b/test/e2e/ip-pools.e2e.ts index df0de16b0..031090b7b 100644 --- a/test/e2e/ip-pools.e2e.ts +++ b/test/e2e/ip-pools.e2e.ts @@ -19,7 +19,7 @@ test('IP pool list', async ({ page }) => { const table = page.getByRole('table') - await expect(table.getByRole('row')).toHaveCount(5) // header + 4 rows + await expect(table.getByRole('row')).toHaveCount(6) // header + 5 rows await expectRowVisible(table, { name: 'ip-pool-1', Utilization: '6 / 24' }) await expectRowVisible(table, { From cdbd4a2ad33f4c4edf2de62d540ff2853538c751 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 9 Oct 2024 12:15:13 -0400 Subject: [PATCH 15/20] Remove n+1 query on IpPools --- .../project/floating-ips/FloatingIpsPage.tsx | 50 ++++++++----------- .../tabs/InternetGatewayIpPoolsTab.tsx | 25 ++-------- 2 files changed, 23 insertions(+), 52 deletions(-) diff --git a/app/pages/project/floating-ips/FloatingIpsPage.tsx b/app/pages/project/floating-ips/FloatingIpsPage.tsx index a8e35941a..35874c4ca 100644 --- a/app/pages/project/floating-ips/FloatingIpsPage.tsx +++ b/app/pages/project/floating-ips/FloatingIpsPage.tsx @@ -13,7 +13,6 @@ import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, useApiMutation, - useApiQuery, useApiQueryClient, usePrefetchedApiQuery, type FloatingIp, @@ -28,8 +27,8 @@ import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmAction } from '~/stores/confirm-action' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' -import { EmptyCell } from '~/table/cells/EmptyCell' import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell' +import { IpPoolCell } from '~/table/cells/IpPoolCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' import { Columns } from '~/table/columns/common' import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable' @@ -39,7 +38,6 @@ import { Message } from '~/ui/lib/Message' import { Modal } from '~/ui/lib/Modal' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { TableActions } from '~/ui/lib/Table' -import { Tooltip } from '~/ui/lib/Tooltip' import { ALL_ISH } from '~/util/consts' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' @@ -78,34 +76,7 @@ FloatingIpsPage.loader = async ({ params }: LoaderFunctionArgs) => { return null } -const IpPoolCell = ({ ipPoolId }: { ipPoolId: string }) => { - const pool = useApiQuery('projectIpPoolView', { path: { pool: ipPoolId } }).data - if (!pool) return - return pool.description ? ( - - {pool.name} - - ) : ( - <>{pool.name} - ) -} - const colHelper = createColumnHelper() -const staticCols = [ - colHelper.accessor('name', {}), - colHelper.accessor('description', Columns.description), - colHelper.accessor('ip', { - header: 'IP address', - }), - colHelper.accessor('ipPoolId', { - cell: (info) => , - header: 'IP pool', - }), - colHelper.accessor('instanceId', { - cell: (info) => , - header: 'Attached to instance', - }), -] export function FloatingIpsPage() { const [floatingIpToModify, setFloatingIpToModify] = useState(null) @@ -114,8 +85,27 @@ export function FloatingIpsPage() { const { data: instances } = usePrefetchedApiQuery('instanceList', { query: { project }, }) + const { data: ipPools } = usePrefetchedApiQuery('projectIpPoolList', { + query: { limit: ALL_ISH }, + }) const navigate = useNavigate() + const staticCols = [ + colHelper.accessor('name', {}), + colHelper.accessor('description', Columns.description), + colHelper.accessor('ip', { + header: 'IP address', + }), + colHelper.accessor('ipPoolId', { + cell: (info) => , + header: 'IP pool', + }), + colHelper.accessor('instanceId', { + cell: (info) => , + header: 'Attached to instance', + }), + ] + const { mutateAsync: floatingIpDetach } = useApiMutation('floatingIpDetach', { onSuccess() { queryClient.invalidateQueries('floatingIpList') diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx index 50adae7d2..0c97d9c17 100644 --- a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx @@ -12,9 +12,7 @@ import type { LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, usePrefetchedApiQuery, type InternetGatewayIpPool } from '~/api' import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' -import { DescriptionCell } from '~/table/cells/DescriptionCell' -import { EmptyCell } from '~/table/cells/EmptyCell' -import { UtilizationCell } from '~/table/cells/UtilizationCell' +import { IpPoolCell } from '~/table/cells/IpPoolCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' import { Columns } from '~/table/columns/common' import { useQueryTable } from '~/table/QueryTable' @@ -56,25 +54,8 @@ export function InternetGatewayIpPoolsTab() { colHelper.accessor('name', {}), colHelper.accessor('description', Columns.description), colHelper.accessor('ipPoolId', { - header: 'IP Pool Name', - cell: (info) => { - const ipPool = ipPools.items.find((item) => item.id === info.getValue()) - return ipPool?.name || - }, - }), - colHelper.accessor('ipPoolId', { - header: 'IP Pool Description', - cell: (info) => { - const ipPool = ipPools.items.find((item) => item.id === info.getValue()) - return - }, - }), - colHelper.accessor('ipPoolId', { - header: 'IP Pool Utilization', - cell: (info) => { - const ipPool = ipPools.items.find((item) => item.id === info.getValue()) - return - }, + header: 'IP Pool', + cell: (info) => , }), ], [ipPools] From a53479be846fa06571bea31ffad0532e1daddd9f Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 9 Oct 2024 12:20:54 -0400 Subject: [PATCH 16/20] Add IpPoolCell --- app/table/cells/IpPoolCell.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/table/cells/IpPoolCell.tsx diff --git a/app/table/cells/IpPoolCell.tsx b/app/table/cells/IpPoolCell.tsx new file mode 100644 index 000000000..cbf2a899c --- /dev/null +++ b/app/table/cells/IpPoolCell.tsx @@ -0,0 +1,29 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ +import { type IpPool } from '~/api' +import { Tooltip } from '~/ui/lib/Tooltip' + +import { EmptyCell } from './EmptyCell' + +export const IpPoolCell = ({ + ipPoolId, + ipPools, +}: { + ipPoolId: string + ipPools: IpPool[] +}) => { + const pool = ipPools.find((item) => item.id === ipPoolId) + if (!pool) return + return pool.description ? ( + + {pool.name} + + ) : ( + <>{pool.name} + ) +} From 55e49bccdf9511c86fbdad2409a9abd113c6d280 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 9 Oct 2024 18:13:02 -0400 Subject: [PATCH 17/20] Remove code that we'll add separately --- app/components/ErrorBoundary.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/ErrorBoundary.tsx b/app/components/ErrorBoundary.tsx index 52db88e5c..91085e08f 100644 --- a/app/components/ErrorBoundary.tsx +++ b/app/components/ErrorBoundary.tsx @@ -38,6 +38,5 @@ export const ErrorBoundary = (props: { children: React.ReactNode }) => ( export function RouterDataErrorBoundary() { // TODO: validate this unknown at runtime _before_ passing to ErrorFallback const error = useRouteError() as Props['error'] - console.error(error) return } From 89a6eabbdd0b77a5d2bc8a2a6b5c3f0a8262c24b Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 9 Oct 2024 18:13:19 -0400 Subject: [PATCH 18/20] Upgrade OMICRON_VERSION --- OMICRON_VERSION | 2 +- app/api/__generated__/Api.ts | 23 +++++++++++------------ app/api/__generated__/OMICRON_VERSION | 2 +- app/api/__generated__/validate.ts | 11 +++-------- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/OMICRON_VERSION b/OMICRON_VERSION index 2ac1cc36f..cb340659d 100644 --- a/OMICRON_VERSION +++ b/OMICRON_VERSION @@ -1 +1 @@ -e51641064a9aeb62b6461055505c53e43fbbe58c +0640bb277df110f3e740464bf4bacdf2bd24c897 diff --git a/app/api/__generated__/Api.ts b/app/api/__generated__/Api.ts index 5ac00ea45..02a553344 100644 --- a/app/api/__generated__/Api.ts +++ b/app/api/__generated__/Api.ts @@ -2039,7 +2039,6 @@ export type InternetGatewayIpAddress = { export type InternetGatewayIpAddressCreate = { address: string description: string - gateway: NameOrId name: Name } @@ -2730,7 +2729,7 @@ export type Route = { /** The route gateway. */ gw: string /** Local preference for route. Higher preference indictes precedence within and across protocols. */ - localPref?: number + ribPriority?: number /** VLAN id the gateway is reachable over. */ vid?: number } @@ -2807,7 +2806,7 @@ export type RouterRouteKind = export type RouterRoute = { /** human-readable free-form text about a resource */ description: string - /** Selects which traffic this routing rule will apply to. */ + /** Selects which traffic this routing rule will apply to */ destination: RouteDestination /** unique, immutable, system-controlled identifier for each resource */ id: string @@ -2815,7 +2814,7 @@ export type RouterRoute = { kind: RouterRouteKind /** unique, mutable, user-controlled identifier for each resource */ name: Name - /** The location that matched packets should be forwarded to. */ + /** The location that matched packets should be forwarded to */ target: RouteTarget /** timestamp when this resource was created */ timeCreated: Date @@ -3517,10 +3516,10 @@ export type SwitchPortRouteConfig = { gw: IpNet /** The interface name this route configuration is assigned to. */ interfaceName: string - /** Local preference indicating priority within and across protocols. */ - localPref?: number /** The port settings object this route configuration belongs to. */ portSettingsId: string + /** RIB Priority indicating priority within and across protocols. */ + ribPriority?: number /** The VLAN identifier for the route. Use this if the gateway is reachable over an 802.1Q tagged L2 segment. */ vlanId?: number } @@ -6206,7 +6205,7 @@ export class Api extends HttpClient { }) }, /** - * List addresses attached to an internet gateway. + * List IP addresses attached to internet gateway */ internetGatewayIpAddressList: ( { query = {} }: { query?: InternetGatewayIpAddressListQueryParams }, @@ -6220,7 +6219,7 @@ export class Api extends HttpClient { }) }, /** - * Attach ip pool to internet gateway + * Attach IP address to internet gateway */ internetGatewayIpAddressCreate: ( { @@ -6241,7 +6240,7 @@ export class Api extends HttpClient { }) }, /** - * Detach ip pool from internet gateway + * Detach IP address from internet gateway */ internetGatewayIpAddressDelete: ( { @@ -6261,7 +6260,7 @@ export class Api extends HttpClient { }) }, /** - * List IP pools attached to an internet gateway. + * List IP pools attached to internet gateway */ internetGatewayIpPoolList: ( { query = {} }: { query?: InternetGatewayIpPoolListQueryParams }, @@ -6275,7 +6274,7 @@ export class Api extends HttpClient { }) }, /** - * Attach ip pool to internet gateway + * Attach IP pool to internet gateway */ internetGatewayIpPoolCreate: ( { @@ -6296,7 +6295,7 @@ export class Api extends HttpClient { }) }, /** - * Detach ip pool from internet gateway + * Detach IP pool from internet gateway */ internetGatewayIpPoolDelete: ( { diff --git a/app/api/__generated__/OMICRON_VERSION b/app/api/__generated__/OMICRON_VERSION index 998389f18..7278501cd 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 -e51641064a9aeb62b6461055505c53e43fbbe58c +0640bb277df110f3e740464bf4bacdf2bd24c897 diff --git a/app/api/__generated__/validate.ts b/app/api/__generated__/validate.ts index b45ccf9a5..1fea712f6 100644 --- a/app/api/__generated__/validate.ts +++ b/app/api/__generated__/validate.ts @@ -1903,12 +1903,7 @@ export const InternetGatewayIpAddress = z.preprocess( */ export const InternetGatewayIpAddressCreate = z.preprocess( processResponseBody, - z.object({ - address: z.string().ip(), - description: z.string(), - gateway: NameOrId, - name: Name, - }) + z.object({ address: z.string().ip(), description: z.string(), name: Name }) ) /** @@ -2587,7 +2582,7 @@ export const Route = z.preprocess( z.object({ dst: IpNet, gw: z.string().ip(), - localPref: z.number().min(0).max(4294967295).optional(), + ribPriority: z.number().min(0).max(255).optional(), vid: z.number().min(0).max(65535).optional(), }) ) @@ -3242,8 +3237,8 @@ export const SwitchPortRouteConfig = z.preprocess( dst: IpNet, gw: IpNet, interfaceName: z.string(), - localPref: z.number().min(0).max(4294967295).optional(), portSettingsId: z.string().uuid(), + ribPriority: z.number().min(0).max(255).optional(), vlanId: z.number().min(0).max(65535).optional(), }) ) From 39c8ca0ac0725408e03c8dd23133a425ea5cf7ea Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 9 Oct 2024 18:22:41 -0400 Subject: [PATCH 19/20] No need to extract UtilizationCell now --- app/pages/system/networking/IpPoolsPage.tsx | 11 ++++++++++- app/table/cells/UtilizationCell.tsx | 17 ----------------- 2 files changed, 10 insertions(+), 18 deletions(-) delete mode 100644 app/table/cells/UtilizationCell.tsx diff --git a/app/pages/system/networking/IpPoolsPage.tsx b/app/pages/system/networking/IpPoolsPage.tsx index b1ea91fb5..8084dc77f 100644 --- a/app/pages/system/networking/IpPoolsPage.tsx +++ b/app/pages/system/networking/IpPoolsPage.tsx @@ -13,17 +13,19 @@ import { Outlet, useNavigate } from 'react-router-dom' import { apiQueryClient, useApiMutation, + useApiQuery, usePrefetchedApiQuery, type IpPool, } from '@oxide/api' import { IpGlobal16Icon, IpGlobal24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' +import { IpUtilCell } from '~/components/IpPoolUtilization' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' +import { SkeletonCell } from '~/table/cells/EmptyCell' import { makeLinkCell } from '~/table/cells/LinkCell' -import { UtilizationCell } from '~/table/cells/UtilizationCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' import { Columns } from '~/table/columns/common' import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable' @@ -44,6 +46,13 @@ const EmptyState = () => ( /> ) +function UtilizationCell({ pool }: { pool: string }) { + const { data } = useApiQuery('ipPoolUtilizationView', { path: { pool } }) + + if (!data) return + return +} + const colHelper = createColumnHelper() const staticColumns = [ diff --git a/app/table/cells/UtilizationCell.tsx b/app/table/cells/UtilizationCell.tsx deleted file mode 100644 index 6b594af53..000000000 --- a/app/table/cells/UtilizationCell.tsx +++ /dev/null @@ -1,17 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at https://mozilla.org/MPL/2.0/. - * - * Copyright Oxide Computer Company - */ -import { useApiQuery } from '~/api' -import { IpUtilCell } from '~/components/IpPoolUtilization' -import { EmptyCell } from '~/table/cells/EmptyCell' - -export const UtilizationCell = ({ pool }: { pool: string }) => { - const { data } = useApiQuery('ipPoolUtilizationView', { path: { pool } }) - - if (!data) return - return -} From 6f5e7ffe68c51bf4ef36efeec848c28dfaafe6c7 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 9 Oct 2024 23:41:32 -0400 Subject: [PATCH 20/20] Get IP Pool tab for Internet Gateways working --- app/components/ErrorBoundary.tsx | 1 + .../tabs/InternetGatewayIpPoolsTab.tsx | 43 +++++++++++-------- mock-api/ip-pool.ts | 7 ++- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/app/components/ErrorBoundary.tsx b/app/components/ErrorBoundary.tsx index 91085e08f..52db88e5c 100644 --- a/app/components/ErrorBoundary.tsx +++ b/app/components/ErrorBoundary.tsx @@ -38,5 +38,6 @@ export const ErrorBoundary = (props: { children: React.ReactNode }) => ( export function RouterDataErrorBoundary() { // TODO: validate this unknown at runtime _before_ passing to ErrorFallback const error = useRouteError() as Props['error'] + console.error(error) return } diff --git a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx index 0c97d9c17..609149221 100644 --- a/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx +++ b/app/pages/project/vpcs/InternetGatewayPage/tabs/InternetGatewayIpPoolsTab.tsx @@ -7,10 +7,10 @@ */ import { createColumnHelper } from '@tanstack/react-table' -import { useCallback, useMemo } from 'react' +import { useCallback } from 'react' import type { LoaderFunctionArgs } from 'react-router-dom' -import { apiQueryClient, usePrefetchedApiQuery, type InternetGatewayIpPool } from '~/api' +import { apiQueryClient, type InternetGatewayIpPool } from '~/api' import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' import { IpPoolCell } from '~/table/cells/IpPoolCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' @@ -25,8 +25,21 @@ InternetGatewayIpPoolsTab.loader = async function ({ params }: LoaderFunctionArg apiQueryClient.prefetchQuery('internetGatewayIpPoolList', { query: { project, vpc, gateway, limit: ALL_ISH }, }), - // get IP Pools - apiQueryClient.prefetchQuery('ipPoolList', { query: { limit: ALL_ISH } }), + // fetch IP Pools and preload into RQ cache so fetches by ID in + // IpPoolCell can be mostly instant yet gracefully fall back to + // fetching individually if we don't fetch them all here + apiQueryClient + .fetchQuery('projectIpPoolList', { query: { limit: ALL_ISH } }) + .then((pools) => { + console.log({ pools }) + for (const pool of pools.items) { + apiQueryClient.setQueryData( + 'projectIpPoolView', + { path: { pool: pool.id } }, + pool + ) + } + }), ]) return null } @@ -38,9 +51,6 @@ export function InternetGatewayIpPoolsTab() { const { Table } = useQueryTable('internetGatewayIpPoolList', { query: { project, vpc, gateway, limit: ALL_ISH }, }) - const { data: ipPools } = usePrefetchedApiQuery('ipPoolList', { - query: { limit: ALL_ISH }, - }) const emptyState = ( ) - const staticColumns = useMemo( - () => [ - colHelper.accessor('name', {}), - colHelper.accessor('description', Columns.description), - colHelper.accessor('ipPoolId', { - header: 'IP Pool', - cell: (info) => , - }), - ], - [ipPools] - ) + const staticColumns = [ + colHelper.accessor('name', {}), + colHelper.accessor('description', Columns.description), + colHelper.accessor('ipPoolId', { + header: 'IP Pool', + cell: (info) => , + }), + ] // The user can copy the ID of the IP Pool attached to this internet gateway const makeActions = useCallback( diff --git a/mock-api/ip-pool.ts b/mock-api/ip-pool.ts index e7725c6c4..d1ac26dd4 100644 --- a/mock-api/ip-pool.ts +++ b/mock-api/ip-pool.ts @@ -55,10 +55,15 @@ export const ipPools: Json[] = [defaultIpPool, ipPool1, ipPool2, ipPool3 export const ipPoolSilos: Json[] = [ { - ip_pool_id: ipPool1.id, + ip_pool_id: defaultIpPool.id, silo_id: defaultSilo.id, is_default: true, }, + { + ip_pool_id: ipPool1.id, + silo_id: defaultSilo.id, + is_default: false, + }, { ip_pool_id: ipPool2.id, silo_id: defaultSilo.id,