diff --git a/app/api/__generated__/validate.ts b/app/api/__generated__/validate.ts index ad806adfb4..6e6a221e6d 100644 --- a/app/api/__generated__/validate.ts +++ b/app/api/__generated__/validate.ts @@ -8,9 +8,8 @@ * Copyright Oxide Computer Company */ -import { z, ZodType } from 'zod' - -import { processResponseBody, uniqueItems } from './util' +import { z, ZodType } from "zod"; +import { processResponseBody, uniqueItems } from "./util"; /** * Zod only supports string enums at the moment. A previous issue was opened @@ -20,10 +19,13 @@ import { processResponseBody, uniqueItems } from './util' * TODO: PR an update for zod to support other native enum types */ const IntEnum = (values: T) => - z.number().refine((v) => values.includes(v)) as ZodType + z.number().refine((v) => values.includes(v)) as ZodType; /** Helper to ensure booleans provided as strings end up with the correct value */ -const SafeBoolean = z.preprocess((v) => (v === 'false' ? false : v), z.coerce.boolean()) +const SafeBoolean = z.preprocess( + (v) => (v === "false" ? false : v), + z.coerce.boolean(), +); /** * An IPv4 subnet @@ -35,9 +37,9 @@ export const Ipv4Net = z.preprocess( z .string() .regex( - /^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\/([0-9]|1[0-9]|2[0-9]|3[0-2])$/ - ) -) + /^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\/([0-9]|1[0-9]|2[0-9]|3[0-2])$/, + ), +); /** * An IPv6 subnet @@ -49,11 +51,14 @@ export const Ipv6Net = z.preprocess( z .string() .regex( - /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/ - ) -) + /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/, + ), +); -export const IpNet = z.preprocess(processResponseBody, z.union([Ipv4Net, Ipv6Net])) +export const IpNet = z.preprocess( + processResponseBody, + z.union([Ipv4Net, Ipv6Net]), +); /** * A name unique within the parent collection @@ -67,14 +72,14 @@ export const Name = z.preprocess( .min(1) .max(63) .regex( - /^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z]([a-zA-Z0-9-]*[a-zA-Z0-9]+)?$/ - ) -) + /^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z]([a-zA-Z0-9-]*[a-zA-Z0-9]+)?$/, + ), +); export const NameOrId = z.preprocess( processResponseBody, - z.union([z.string().uuid(), Name]) -) + z.union([z.string().uuid(), Name]), +); /** * An address tied to an address lot. @@ -85,21 +90,24 @@ export const Address = z.preprocess( address: IpNet, addressLot: NameOrId, vlanId: z.number().min(0).max(65535).optional(), - }) -) + }), +); /** * A set of addresses associated with a port configuration. */ export const AddressConfig = z.preprocess( processResponseBody, - z.object({ addresses: Address.array() }) -) + z.object({ addresses: Address.array() }), +); /** * The kind associated with an address lot. */ -export const AddressLotKind = z.preprocess(processResponseBody, z.enum(['infra', 'pool'])) +export const AddressLotKind = z.preprocess( + processResponseBody, + z.enum(["infra", "pool"]), +); /** * Represents an address lot object, containing the id of the lot that can be used in other API calls. @@ -113,8 +121,8 @@ export const AddressLot = z.preprocess( name: Name, timeCreated: z.coerce.date(), timeModified: z.coerce.date(), - }) -) + }), +); /** * An address lot block is a part of an address lot and contains a range of addresses. The range is inclusive. @@ -125,24 +133,24 @@ export const AddressLotBlock = z.preprocess( firstAddress: z.string().ip(), id: z.string().uuid(), lastAddress: z.string().ip(), - }) -) + }), +); /** * Parameters for creating an address lot block. Fist and last addresses are inclusive. */ export const AddressLotBlockCreate = z.preprocess( processResponseBody, - z.object({ firstAddress: z.string().ip(), lastAddress: z.string().ip() }) -) + z.object({ firstAddress: z.string().ip(), lastAddress: z.string().ip() }), +); /** * A single page of results */ export const AddressLotBlockResultsPage = z.preprocess( processResponseBody, - z.object({ items: AddressLotBlock.array(), nextPage: z.string().optional() }) -) + z.object({ items: AddressLotBlock.array(), nextPage: z.string().optional() }), +); /** * Parameters for creating an address lot. @@ -154,34 +162,37 @@ export const AddressLotCreate = z.preprocess( description: z.string(), kind: AddressLotKind, name: Name, - }) -) + }), +); /** * An address lot and associated blocks resulting from creating an address lot. */ export const AddressLotCreateResponse = z.preprocess( processResponseBody, - z.object({ blocks: AddressLotBlock.array(), lot: AddressLot }) -) + z.object({ blocks: AddressLotBlock.array(), lot: AddressLot }), +); /** * A single page of results */ export const AddressLotResultsPage = z.preprocess( processResponseBody, - z.object({ items: AddressLot.array(), nextPage: z.string().optional() }) -) + z.object({ items: AddressLot.array(), nextPage: z.string().optional() }), +); -export const BgpMessageHistory = z.preprocess(processResponseBody, z.record(z.unknown())) +export const BgpMessageHistory = z.preprocess( + processResponseBody, + z.record(z.unknown()), +); /** * Identifies switch physical location */ export const SwitchLocation = z.preprocess( processResponseBody, - z.enum(['switch0', 'switch1']) -) + z.enum(["switch0", "switch1"]), +); /** * BGP message history for a particular switch. @@ -191,16 +202,16 @@ export const SwitchBgpHistory = z.preprocess( z.object({ history: z.record(z.string().min(1), BgpMessageHistory), switch: SwitchLocation, - }) -) + }), +); /** * BGP message history for rack switches. */ export const AggregateBgpMessageHistory = z.preprocess( processResponseBody, - z.object({ switchHistories: SwitchBgpHistory.array() }) -) + z.object({ switchHistories: SwitchBgpHistory.array() }), +); /** * Description of source IPs allowed to reach rack services. @@ -208,10 +219,10 @@ export const AggregateBgpMessageHistory = z.preprocess( export const AllowedSourceIps = z.preprocess( processResponseBody, z.union([ - z.object({ allow: z.enum(['any']) }), - z.object({ allow: z.enum(['list']), ips: IpNet.array() }), - ]) -) + z.object({ allow: z.enum(["any"]) }), + z.object({ allow: z.enum(["list"]), ips: IpNet.array() }), + ]), +); /** * Allowlist of IPs or subnets that can make requests to user-facing services. @@ -222,16 +233,16 @@ export const AllowList = z.preprocess( allowedIps: AllowedSourceIps, timeCreated: z.coerce.date(), timeModified: z.coerce.date(), - }) -) + }), +); /** * Parameters for updating allowed source IPs */ export const AllowListUpdate = z.preprocess( processResponseBody, - z.object({ allowedIps: AllowedSourceIps }) -) + z.object({ allowedIps: AllowedSourceIps }), +); /** * Authorization scope for a timeseries. @@ -240,32 +251,36 @@ export const AllowListUpdate = z.preprocess( */ export const AuthzScope = z.preprocess( processResponseBody, - z.enum(['fleet', 'silo', 'project', 'viewable_to_all']) -) + z.enum(["fleet", "silo", "project", "viewable_to_all"]), +); /** * Properties that uniquely identify an Oxide hardware component */ export const Baseboard = z.preprocess( processResponseBody, - z.object({ part: z.string(), revision: z.number(), serial: z.string() }) -) + z.object({ + part: z.string(), + revision: z.number().min(0).max(4294967295), + serial: z.string(), + }), +); /** * BFD connection mode. */ +export const BfdModeEnumArray = ["single_hop", "multi_hop"] as const; export const BfdMode = z.preprocess( processResponseBody, - z.enum(['single_hop', 'multi_hop']) -) - + z.enum(BfdModeEnumArray), +); /** * Information needed to disable a BFD session */ export const BfdSessionDisable = z.preprocess( processResponseBody, - z.object({ remote: z.string().ip(), switch: Name }) -) + z.object({ remote: z.string().ip(), switch: Name }), +); /** * Information about a bidirectional forwarding detection (BFD) session. @@ -279,13 +294,13 @@ export const BfdSessionEnable = z.preprocess( remote: z.string().ip(), requiredRx: z.number().min(0), switch: Name, - }) -) + }), +); export const BfdState = z.preprocess( processResponseBody, - z.enum(['admin_down', 'down', 'init', 'up']) -) + z.enum(["admin_down", "down", "init", "up"]), +); export const BfdStatus = z.preprocess( processResponseBody, @@ -297,8 +312,8 @@ export const BfdStatus = z.preprocess( requiredRx: z.number().min(0), state: BfdState, switch: Name, - }) -) + }), +); /** * Represents a BGP announce set by id. The id can be used with other API calls to view and manage the announce set. @@ -311,16 +326,16 @@ export const BgpAnnounceSet = z.preprocess( name: Name, timeCreated: z.coerce.date(), timeModified: z.coerce.date(), - }) -) + }), +); /** * A BGP announcement tied to a particular address lot block. */ export const BgpAnnouncementCreate = z.preprocess( processResponseBody, - z.object({ addressLotBlock: NameOrId, network: IpNet }) -) + z.object({ addressLotBlock: NameOrId, network: IpNet }), +); /** * Parameters for creating a named set of BGP announcements. @@ -331,8 +346,8 @@ export const BgpAnnounceSetCreate = z.preprocess( announcement: BgpAnnouncementCreate.array(), description: z.string(), name: Name, - }) -) + }), +); /** * A BGP announcement tied to an address lot block. @@ -343,8 +358,8 @@ export const BgpAnnouncement = z.preprocess( addressLotBlockId: z.string().uuid(), announceSetId: z.string().uuid(), network: IpNet, - }) -) + }), +); /** * A base BGP configuration. @@ -359,8 +374,8 @@ export const BgpConfig = z.preprocess( timeCreated: z.coerce.date(), timeModified: z.coerce.date(), vrf: z.string().optional(), - }) -) + }), +); /** * Parameters for creating a BGP configuration. This includes and autonomous system number (ASN) and a virtual routing and forwarding (VRF) identifier. @@ -373,16 +388,16 @@ export const BgpConfigCreate = z.preprocess( description: z.string(), name: Name, vrf: Name.optional(), - }) -) + }), +); /** * A single page of results */ export const BgpConfigResultsPage = z.preprocess( processResponseBody, - z.object({ items: BgpConfig.array(), nextPage: z.string().optional() }) -) + z.object({ items: BgpConfig.array(), nextPage: z.string().optional() }), +); /** * A route imported from a BGP peer. @@ -391,11 +406,11 @@ export const BgpImportedRouteIpv4 = z.preprocess( processResponseBody, z.object({ id: z.number().min(0).max(4294967295), - nexthop: z.string().ip({ version: 'v4' }), + nexthop: z.string().ip({ version: "v4" }), prefix: Ipv4Net, switch: SwitchLocation, - }) -) + }), +); /** * Define policy relating to the import and export of prefixes from a BGP peer. @@ -403,10 +418,10 @@ export const BgpImportedRouteIpv4 = z.preprocess( export const ImportExportPolicy = z.preprocess( processResponseBody, z.union([ - z.object({ type: z.enum(['no_filtering']) }), - z.object({ type: z.enum(['allow']), value: IpNet.array() }), - ]) -) + z.object({ type: z.enum(["no_filtering"]) }), + z.object({ type: z.enum(["allow"]), value: IpNet.array() }), + ]), +); /** * A BGP peer configuration for an interface. Includes the set of announcements that will be advertised to the peer identified by `addr`. The `bgp_config` parameter is a reference to global BGP parameters. The `interface_name` indicates what interface the peer should be contacted on. @@ -432,13 +447,13 @@ export const BgpPeer = z.preprocess( multiExitDiscriminator: z.number().min(0).max(4294967295).optional(), remoteAsn: z.number().min(0).max(4294967295).optional(), vlanId: z.number().min(0).max(65535).optional(), - }) -) + }), +); export const BgpPeerConfig = z.preprocess( processResponseBody, - z.object({ peers: BgpPeer.array() }) -) + z.object({ peers: BgpPeer.array() }), +); /** * The current state of a BGP peer. @@ -446,15 +461,15 @@ export const BgpPeerConfig = z.preprocess( export const BgpPeerState = z.preprocess( processResponseBody, z.enum([ - 'idle', - 'connect', - 'active', - 'open_sent', - 'open_confirm', - 'session_setup', - 'established', - ]) -) + "idle", + "connect", + "active", + "open_sent", + "open_confirm", + "session_setup", + "established", + ]), +); /** * The current status of a BGP peer. @@ -468,8 +483,8 @@ export const BgpPeerStatus = z.preprocess( state: BgpPeerState, stateDurationMillis: z.number().min(0), switch: SwitchLocation, - }) -) + }), +); /** * A type storing a range over `T`. @@ -479,11 +494,11 @@ export const BgpPeerStatus = z.preprocess( export const BinRangedouble = z.preprocess( processResponseBody, z.union([ - z.object({ end: z.number(), type: z.enum(['range_to']) }), - z.object({ end: z.number(), start: z.number(), type: z.enum(['range']) }), - z.object({ start: z.number(), type: z.enum(['range_from']) }), - ]) -) + z.object({ end: z.number(), type: z.enum(["range_to"]) }), + z.object({ end: z.number(), start: z.number(), type: z.enum(["range"]) }), + z.object({ start: z.number(), type: z.enum(["range_from"]) }), + ]), +); /** * A type storing a range over `T`. @@ -493,11 +508,11 @@ export const BinRangedouble = z.preprocess( export const BinRangefloat = z.preprocess( processResponseBody, z.union([ - z.object({ end: z.number(), type: z.enum(['range_to']) }), - z.object({ end: z.number(), start: z.number(), type: z.enum(['range']) }), - z.object({ start: z.number(), type: z.enum(['range_from']) }), - ]) -) + z.object({ end: z.number(), type: z.enum(["range_to"]) }), + z.object({ end: z.number(), start: z.number(), type: z.enum(["range"]) }), + z.object({ start: z.number(), type: z.enum(["range_from"]) }), + ]), +); /** * A type storing a range over `T`. @@ -507,15 +522,21 @@ export const BinRangefloat = z.preprocess( export const BinRangeint16 = z.preprocess( processResponseBody, z.union([ - z.object({ end: z.number().min(-32767).max(32767), type: z.enum(['range_to']) }), z.object({ end: z.number().min(-32767).max(32767), + type: z.enum(["range_to"]), + }), + z.object({ + end: z.number().min(-32767).max(32767), + start: z.number().min(-32767).max(32767), + type: z.enum(["range"]), + }), + z.object({ start: z.number().min(-32767).max(32767), - type: z.enum(['range']), + type: z.enum(["range_from"]), }), - z.object({ start: z.number().min(-32767).max(32767), type: z.enum(['range_from']) }), - ]) -) + ]), +); /** * A type storing a range over `T`. @@ -527,19 +548,19 @@ export const BinRangeint32 = z.preprocess( z.union([ z.object({ end: z.number().min(-2147483647).max(2147483647), - type: z.enum(['range_to']), + type: z.enum(["range_to"]), }), z.object({ end: z.number().min(-2147483647).max(2147483647), start: z.number().min(-2147483647).max(2147483647), - type: z.enum(['range']), + type: z.enum(["range"]), }), z.object({ start: z.number().min(-2147483647).max(2147483647), - type: z.enum(['range_from']), + type: z.enum(["range_from"]), }), - ]) -) + ]), +); /** * A type storing a range over `T`. @@ -549,11 +570,11 @@ export const BinRangeint32 = z.preprocess( export const BinRangeint64 = z.preprocess( processResponseBody, z.union([ - z.object({ end: z.number(), type: z.enum(['range_to']) }), - z.object({ end: z.number(), start: z.number(), type: z.enum(['range']) }), - z.object({ start: z.number(), type: z.enum(['range_from']) }), - ]) -) + z.object({ end: z.number(), type: z.enum(["range_to"]) }), + z.object({ end: z.number(), start: z.number(), type: z.enum(["range"]) }), + z.object({ start: z.number(), type: z.enum(["range_from"]) }), + ]), +); /** * A type storing a range over `T`. @@ -563,15 +584,21 @@ export const BinRangeint64 = z.preprocess( export const BinRangeint8 = z.preprocess( processResponseBody, z.union([ - z.object({ end: z.number().min(-127).max(127), type: z.enum(['range_to']) }), + z.object({ + end: z.number().min(-127).max(127), + type: z.enum(["range_to"]), + }), z.object({ end: z.number().min(-127).max(127), start: z.number().min(-127).max(127), - type: z.enum(['range']), + type: z.enum(["range"]), }), - z.object({ start: z.number().min(-127).max(127), type: z.enum(['range_from']) }), - ]) -) + z.object({ + start: z.number().min(-127).max(127), + type: z.enum(["range_from"]), + }), + ]), +); /** * A type storing a range over `T`. @@ -581,15 +608,18 @@ export const BinRangeint8 = z.preprocess( export const BinRangeuint16 = z.preprocess( processResponseBody, z.union([ - z.object({ end: z.number().min(0).max(65535), type: z.enum(['range_to']) }), + z.object({ end: z.number().min(0).max(65535), type: z.enum(["range_to"]) }), z.object({ end: z.number().min(0).max(65535), start: z.number().min(0).max(65535), - type: z.enum(['range']), + type: z.enum(["range"]), + }), + z.object({ + start: z.number().min(0).max(65535), + type: z.enum(["range_from"]), }), - z.object({ start: z.number().min(0).max(65535), type: z.enum(['range_from']) }), - ]) -) + ]), +); /** * A type storing a range over `T`. @@ -599,15 +629,21 @@ export const BinRangeuint16 = z.preprocess( export const BinRangeuint32 = z.preprocess( processResponseBody, z.union([ - z.object({ end: z.number().min(0).max(4294967295), type: z.enum(['range_to']) }), + z.object({ + end: z.number().min(0).max(4294967295), + type: z.enum(["range_to"]), + }), z.object({ end: z.number().min(0).max(4294967295), start: z.number().min(0).max(4294967295), - type: z.enum(['range']), + type: z.enum(["range"]), }), - z.object({ start: z.number().min(0).max(4294967295), type: z.enum(['range_from']) }), - ]) -) + z.object({ + start: z.number().min(0).max(4294967295), + type: z.enum(["range_from"]), + }), + ]), +); /** * A type storing a range over `T`. @@ -617,11 +653,15 @@ export const BinRangeuint32 = z.preprocess( export const BinRangeuint64 = z.preprocess( processResponseBody, z.union([ - z.object({ end: z.number().min(0), type: z.enum(['range_to']) }), - z.object({ end: z.number().min(0), start: z.number().min(0), type: z.enum(['range']) }), - z.object({ start: z.number().min(0), type: z.enum(['range_from']) }), - ]) -) + z.object({ end: z.number().min(0), type: z.enum(["range_to"]) }), + z.object({ + end: z.number().min(0), + start: z.number().min(0), + type: z.enum(["range"]), + }), + z.object({ start: z.number().min(0), type: z.enum(["range_from"]) }), + ]), +); /** * A type storing a range over `T`. @@ -631,116 +671,119 @@ export const BinRangeuint64 = z.preprocess( export const BinRangeuint8 = z.preprocess( processResponseBody, z.union([ - z.object({ end: z.number().min(0).max(255), type: z.enum(['range_to']) }), + z.object({ end: z.number().min(0).max(255), type: z.enum(["range_to"]) }), z.object({ end: z.number().min(0).max(255), start: z.number().min(0).max(255), - type: z.enum(['range']), + type: z.enum(["range"]), + }), + z.object({ + start: z.number().min(0).max(255), + type: z.enum(["range_from"]), }), - z.object({ start: z.number().min(0).max(255), type: z.enum(['range_from']) }), - ]) -) + ]), +); /** * Type storing bin edges and a count of samples within it. */ export const Bindouble = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangedouble }) -) + z.object({ count: z.number().min(0), range: BinRangedouble }), +); /** * Type storing bin edges and a count of samples within it. */ export const Binfloat = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangefloat }) -) + z.object({ count: z.number().min(0), range: BinRangefloat }), +); /** * Type storing bin edges and a count of samples within it. */ export const Binint16 = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangeint16 }) -) + z.object({ count: z.number().min(0), range: BinRangeint16 }), +); /** * Type storing bin edges and a count of samples within it. */ export const Binint32 = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangeint32 }) -) + z.object({ count: z.number().min(0), range: BinRangeint32 }), +); /** * Type storing bin edges and a count of samples within it. */ export const Binint64 = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangeint64 }) -) + z.object({ count: z.number().min(0), range: BinRangeint64 }), +); /** * Type storing bin edges and a count of samples within it. */ export const Binint8 = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangeint8 }) -) + z.object({ count: z.number().min(0), range: BinRangeint8 }), +); /** * Type storing bin edges and a count of samples within it. */ export const Binuint16 = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangeuint16 }) -) + z.object({ count: z.number().min(0), range: BinRangeuint16 }), +); /** * Type storing bin edges and a count of samples within it. */ export const Binuint32 = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangeuint32 }) -) + z.object({ count: z.number().min(0), range: BinRangeuint32 }), +); /** * Type storing bin edges and a count of samples within it. */ export const Binuint64 = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangeuint64 }) -) + z.object({ count: z.number().min(0), range: BinRangeuint64 }), +); /** * Type storing bin edges and a count of samples within it. */ export const Binuint8 = z.preprocess( processResponseBody, - z.object({ count: z.number().min(0), range: BinRangeuint8 }) -) + z.object({ count: z.number().min(0), range: BinRangeuint8 }), +); /** * disk block size in bytes */ +export const BlockSizeEnumArray = [512, 2048, 4096] as const; export const BlockSize = z.preprocess( processResponseBody, - IntEnum([512, 2048, 4096] as const) -) - + z.enum(BlockSizeEnumArray), +); /** * Byte count to express memory or storage capacity. */ -export const ByteCount = z.preprocess(processResponseBody, z.number().min(0)) +export const ByteCount = z.preprocess(processResponseBody, z.number().min(0)); /** * The service intended to use this certificate. */ export const ServiceUsingCertificate = z.preprocess( processResponseBody, - z.enum(['external_api']) -) + z.enum(["external_api"]), +); /** * View of a Certificate @@ -755,8 +798,8 @@ export const Certificate = z.preprocess( service: ServiceUsingCertificate, timeCreated: z.coerce.date(), timeModified: z.coerce.date(), - }) -) + }), +); /** * Create-time parameters for a `Certificate` @@ -769,48 +812,48 @@ export const CertificateCreate = z.preprocess( key: z.string(), name: Name, service: ServiceUsingCertificate, - }) -) + }), +); /** * A single page of results */ export const CertificateResultsPage = z.preprocess( processResponseBody, - z.object({ items: Certificate.array(), nextPage: z.string().optional() }) -) + z.object({ items: Certificate.array(), nextPage: z.string().optional() }), +); /** * A cumulative or counter data type. */ export const Cumulativedouble = z.preprocess( processResponseBody, - z.object({ startTime: z.coerce.date(), value: z.number() }) -) + z.object({ startTime: z.coerce.date(), value: z.number() }), +); /** * A cumulative or counter data type. */ export const Cumulativefloat = z.preprocess( processResponseBody, - z.object({ startTime: z.coerce.date(), value: z.number() }) -) + z.object({ startTime: z.coerce.date(), value: z.number() }), +); /** * A cumulative or counter data type. */ export const Cumulativeint64 = z.preprocess( processResponseBody, - z.object({ startTime: z.coerce.date(), value: z.number() }) -) + z.object({ startTime: z.coerce.date(), value: z.number() }), +); /** * A cumulative or counter data type. */ export const Cumulativeuint64 = z.preprocess( processResponseBody, - z.object({ startTime: z.coerce.date(), value: z.number().min(0) }) -) + z.object({ startTime: z.coerce.date(), value: z.number().min(0) }), +); /** * Info about the current user @@ -822,8 +865,8 @@ export const CurrentUser = z.preprocess( id: z.string().uuid(), siloId: z.string().uuid(), siloName: Name, - }) -) + }), +); /** * Structure for estimating the p-quantile of a population. @@ -839,8 +882,8 @@ export const Quantile = z.preprocess( markerHeights: z.number().array(), markerPositions: z.number().min(0).array(), p: z.number(), - }) -) + }), +); /** * Histogram metric @@ -862,8 +905,8 @@ export const Histogramint8 = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * Histogram metric @@ -885,8 +928,8 @@ export const Histogramuint8 = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * Histogram metric @@ -908,8 +951,8 @@ export const Histogramint16 = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * Histogram metric @@ -931,8 +974,8 @@ export const Histogramuint16 = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * Histogram metric @@ -954,8 +997,8 @@ export const Histogramint32 = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * Histogram metric @@ -977,8 +1020,8 @@ export const Histogramuint32 = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * Histogram metric @@ -1000,8 +1043,8 @@ export const Histogramint64 = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * Histogram metric @@ -1023,8 +1066,8 @@ export const Histogramuint64 = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * Histogram metric @@ -1046,8 +1089,8 @@ export const Histogramfloat = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * Histogram metric @@ -1069,49 +1112,49 @@ export const Histogramdouble = z.preprocess( squaredMean: z.number(), startTime: z.coerce.date(), sumOfSamples: z.number(), - }) -) + }), +); /** * The type of an individual datum of a metric. */ +export const DatumTypeEnumArray = [ + "bool", + "i8", + "u8", + "i16", + "u16", + "i32", + "u32", + "i64", + "u64", + "f32", + "f64", + "string", + "bytes", + "cumulative_i64", + "cumulative_u64", + "cumulative_f32", + "cumulative_f64", + "histogram_i8", + "histogram_u8", + "histogram_i16", + "histogram_u16", + "histogram_i32", + "histogram_u32", + "histogram_i64", + "histogram_u64", + "histogram_f32", + "histogram_f64", +] as const; export const DatumType = z.preprocess( processResponseBody, - z.enum([ - 'bool', - 'i8', - 'u8', - 'i16', - 'u16', - 'i32', - 'u32', - 'i64', - 'u64', - 'f32', - 'f64', - 'string', - 'bytes', - 'cumulative_i64', - 'cumulative_u64', - 'cumulative_f32', - 'cumulative_f64', - 'histogram_i8', - 'histogram_u8', - 'histogram_i16', - 'histogram_u16', - 'histogram_i32', - 'histogram_u32', - 'histogram_i64', - 'histogram_u64', - 'histogram_f32', - 'histogram_f64', - ]) -) - + z.enum(DatumTypeEnumArray), +); export const MissingDatum = z.preprocess( processResponseBody, - z.object({ datumType: DatumType, startTime: z.coerce.date().optional() }) -) + z.object({ datumType: DatumType, startTime: z.coerce.date().optional() }), +); /** * A `Datum` is a single sampled data point from a metric. @@ -1119,61 +1162,77 @@ export const MissingDatum = z.preprocess( export const Datum = z.preprocess( processResponseBody, z.union([ - z.object({ datum: SafeBoolean, type: z.enum(['bool']) }), - z.object({ datum: z.number().min(-127).max(127), type: z.enum(['i8']) }), - z.object({ datum: z.number().min(0).max(255), type: z.enum(['u8']) }), - z.object({ datum: z.number().min(-32767).max(32767), type: z.enum(['i16']) }), - z.object({ datum: z.number().min(0).max(65535), type: z.enum(['u16']) }), - z.object({ datum: z.number().min(-2147483647).max(2147483647), type: z.enum(['i32']) }), - z.object({ datum: z.number().min(0).max(4294967295), type: z.enum(['u32']) }), - z.object({ datum: z.number(), type: z.enum(['i64']) }), - z.object({ datum: z.number().min(0), type: z.enum(['u64']) }), - z.object({ datum: z.number(), type: z.enum(['f32']) }), - z.object({ datum: z.number(), type: z.enum(['f64']) }), - z.object({ datum: z.string(), type: z.enum(['string']) }), - z.object({ datum: z.number().min(0).max(255).array(), type: z.enum(['bytes']) }), - z.object({ datum: Cumulativeint64, type: z.enum(['cumulative_i64']) }), - z.object({ datum: Cumulativeuint64, type: z.enum(['cumulative_u64']) }), - z.object({ datum: Cumulativefloat, type: z.enum(['cumulative_f32']) }), - z.object({ datum: Cumulativedouble, type: z.enum(['cumulative_f64']) }), - z.object({ datum: Histogramint8, type: z.enum(['histogram_i8']) }), - z.object({ datum: Histogramuint8, type: z.enum(['histogram_u8']) }), - z.object({ datum: Histogramint16, type: z.enum(['histogram_i16']) }), - z.object({ datum: Histogramuint16, type: z.enum(['histogram_u16']) }), - z.object({ datum: Histogramint32, type: z.enum(['histogram_i32']) }), - z.object({ datum: Histogramuint32, type: z.enum(['histogram_u32']) }), - z.object({ datum: Histogramint64, type: z.enum(['histogram_i64']) }), - z.object({ datum: Histogramuint64, type: z.enum(['histogram_u64']) }), - z.object({ datum: Histogramfloat, type: z.enum(['histogram_f32']) }), - z.object({ datum: Histogramdouble, type: z.enum(['histogram_f64']) }), - z.object({ datum: MissingDatum, type: z.enum(['missing']) }), - ]) -) + z.object({ datum: SafeBoolean, type: z.enum(["bool"]) }), + z.object({ datum: z.number().min(-127).max(127), type: z.enum(["i8"]) }), + z.object({ datum: z.number().min(0).max(255), type: z.enum(["u8"]) }), + z.object({ + datum: z.number().min(-32767).max(32767), + type: z.enum(["i16"]), + }), + z.object({ datum: z.number().min(0).max(65535), type: z.enum(["u16"]) }), + z.object({ + datum: z.number().min(-2147483647).max(2147483647), + type: z.enum(["i32"]), + }), + z.object({ + datum: z.number().min(0).max(4294967295), + type: z.enum(["u32"]), + }), + z.object({ datum: z.number(), type: z.enum(["i64"]) }), + z.object({ datum: z.number().min(0), type: z.enum(["u64"]) }), + z.object({ datum: z.number(), type: z.enum(["f32"]) }), + z.object({ datum: z.number(), type: z.enum(["f64"]) }), + z.object({ datum: z.string(), type: z.enum(["string"]) }), + z.object({ + datum: z.number().min(0).max(255).array(), + type: z.enum(["bytes"]), + }), + z.object({ datum: Cumulativeint64, type: z.enum(["cumulative_i64"]) }), + z.object({ datum: Cumulativeuint64, type: z.enum(["cumulative_u64"]) }), + z.object({ datum: Cumulativefloat, type: z.enum(["cumulative_f32"]) }), + z.object({ datum: Cumulativedouble, type: z.enum(["cumulative_f64"]) }), + z.object({ datum: Histogramint8, type: z.enum(["histogram_i8"]) }), + z.object({ datum: Histogramuint8, type: z.enum(["histogram_u8"]) }), + z.object({ datum: Histogramint16, type: z.enum(["histogram_i16"]) }), + z.object({ datum: Histogramuint16, type: z.enum(["histogram_u16"]) }), + z.object({ datum: Histogramint32, type: z.enum(["histogram_i32"]) }), + z.object({ datum: Histogramuint32, type: z.enum(["histogram_u32"]) }), + z.object({ datum: Histogramint64, type: z.enum(["histogram_i64"]) }), + z.object({ datum: Histogramuint64, type: z.enum(["histogram_u64"]) }), + z.object({ datum: Histogramfloat, type: z.enum(["histogram_f32"]) }), + z.object({ datum: Histogramdouble, type: z.enum(["histogram_f64"]) }), + z.object({ datum: MissingDatum, type: z.enum(["missing"]) }), + ]), +); export const DerEncodedKeyPair = z.preprocess( processResponseBody, - z.object({ privateKey: z.string(), publicCert: z.string() }) -) + z.object({ privateKey: z.string(), publicCert: z.string() }), +); export const DeviceAccessTokenRequest = z.preprocess( processResponseBody, - z.object({ clientId: z.string().uuid(), deviceCode: z.string(), grantType: z.string() }) -) + z.object({ + clientId: z.string().uuid(), + deviceCode: z.string(), + grantType: z.string(), + }), +); export const DeviceAuthRequest = z.preprocess( processResponseBody, - z.object({ clientId: z.string().uuid() }) -) + z.object({ clientId: z.string().uuid() }), +); export const DeviceAuthVerify = z.preprocess( processResponseBody, - z.object({ userCode: z.string() }) -) + z.object({ userCode: z.string() }), +); export const Digest = z.preprocess( processResponseBody, - z.object({ type: z.enum(['sha256']), value: z.string() }) -) + z.object({ type: z.enum(["sha256"]), value: z.string() }), +); /** * State of a Disk @@ -1181,20 +1240,20 @@ export const Digest = z.preprocess( export const DiskState = z.preprocess( processResponseBody, z.union([ - z.object({ state: z.enum(['creating']) }), - z.object({ state: z.enum(['detached']) }), - z.object({ state: z.enum(['import_ready']) }), - z.object({ state: z.enum(['importing_from_url']) }), - z.object({ state: z.enum(['importing_from_bulk_writes']) }), - z.object({ state: z.enum(['finalizing']) }), - z.object({ state: z.enum(['maintenance']) }), - z.object({ instance: z.string().uuid(), state: z.enum(['attaching']) }), - z.object({ instance: z.string().uuid(), state: z.enum(['attached']) }), - z.object({ instance: z.string().uuid(), state: z.enum(['detaching']) }), - z.object({ state: z.enum(['destroyed']) }), - z.object({ state: z.enum(['faulted']) }), - ]) -) + z.object({ state: z.enum(["creating"]) }), + z.object({ state: z.enum(["detached"]) }), + z.object({ state: z.enum(["import_ready"]) }), + z.object({ state: z.enum(["importing_from_url"]) }), + z.object({ state: z.enum(["importing_from_bulk_writes"]) }), + z.object({ state: z.enum(["finalizing"]) }), + z.object({ state: z.enum(["maintenance"]) }), + z.object({ instance: z.string().uuid(), state: z.enum(["attaching"]) }), + z.object({ instance: z.string().uuid(), state: z.enum(["attached"]) }), + z.object({ instance: z.string().uuid(), state: z.enum(["detaching"]) }), + z.object({ state: z.enum(["destroyed"]) }), + z.object({ state: z.enum(["faulted"]) }), + ]), +); /** * View of a Disk @@ -1214,8 +1273,8 @@ export const Disk = z.preprocess( state: DiskState, timeCreated: z.coerce.date(), timeModified: z.coerce.date(), - }) -) + }), +); /** * Different sources for a disk @@ -1223,30 +1282,38 @@ export const Disk = z.preprocess( export const DiskSource = z.preprocess( processResponseBody, z.union([ - z.object({ blockSize: BlockSize, type: z.enum(['blank']) }), - z.object({ snapshotId: z.string().uuid(), type: z.enum(['snapshot']) }), - z.object({ imageId: z.string().uuid(), type: z.enum(['image']) }), - z.object({ blockSize: BlockSize, type: z.enum(['importing_blocks']) }), - ]) -) + z.object({ blockSize: BlockSize, type: z.enum(["blank"]) }), + z.object({ snapshotId: z.string().uuid(), type: z.enum(["snapshot"]) }), + z.object({ imageId: z.string().uuid(), type: z.enum(["image"]) }), + z.object({ blockSize: BlockSize, type: z.enum(["importing_blocks"]) }), + ]), +); /** * Create-time parameters for a `Disk` */ export const DiskCreate = z.preprocess( processResponseBody, - z.object({ description: z.string(), diskSource: DiskSource, name: Name, size: ByteCount }) -) + z.object({ + description: z.string(), + diskSource: DiskSource, + name: Name, + size: ByteCount, + }), +); -export const DiskPath = z.preprocess(processResponseBody, z.object({ disk: NameOrId })) +export const DiskPath = z.preprocess( + processResponseBody, + z.object({ disk: NameOrId }), +); /** * A single page of results */ export const DiskResultsPage = z.preprocess( processResponseBody, - z.object({ items: Disk.array(), nextPage: z.string().optional() }) -) + z.object({ items: Disk.array(), nextPage: z.string().optional() }), +); /** * A distribution is a sequence of bins and counts in those bins, and some statistical information tracked to compute the mean, standard deviation, and quantile estimates. @@ -1265,8 +1332,8 @@ export const Distributiondouble = z.preprocess( p99: Quantile.optional(), squaredMean: z.number(), sumOfSamples: z.number(), - }) -) + }), +); /** * A distribution is a sequence of bins and counts in those bins, and some statistical information tracked to compute the mean, standard deviation, and quantile estimates. @@ -1285,43 +1352,47 @@ export const Distributionint64 = z.preprocess( p99: Quantile.optional(), squaredMean: z.number(), sumOfSamples: z.number(), - }) -) + }), +); /** * Parameters for creating an ephemeral IP address for an instance. */ export const EphemeralIpCreate = z.preprocess( processResponseBody, - z.object({ pool: NameOrId.optional() }) -) + z.object({ pool: NameOrId.optional() }), +); /** * Error information from a response. */ export const Error = z.preprocess( processResponseBody, - z.object({ errorCode: z.string().optional(), message: z.string(), requestId: z.string() }) -) + z.object({ + errorCode: z.string().optional(), + message: z.string(), + requestId: z.string(), + }), +); export const ExternalIp = z.preprocess( processResponseBody, z.union([ - z.object({ ip: z.string().ip(), kind: z.enum(['ephemeral']) }), + z.object({ ip: z.string().ip(), kind: z.enum(["ephemeral"]) }), z.object({ description: z.string(), id: z.string().uuid(), instanceId: z.string().uuid().optional(), ip: z.string().ip(), ipPoolId: z.string().uuid(), - kind: z.enum(['floating']), + kind: z.enum(["floating"]), name: Name, projectId: z.string().uuid(), timeCreated: z.coerce.date(), timeModified: z.coerce.date(), }), - ]) -) + ]), +); /** * Parameters for creating an external IP address for instances. @@ -1329,45 +1400,48 @@ export const ExternalIp = z.preprocess( export const ExternalIpCreate = z.preprocess( processResponseBody, z.union([ - z.object({ pool: NameOrId.optional(), type: z.enum(['ephemeral']) }), - z.object({ floatingIp: NameOrId, type: z.enum(['floating']) }), - ]) -) + z.object({ pool: NameOrId.optional(), type: z.enum(["ephemeral"]) }), + z.object({ floatingIp: NameOrId, type: z.enum(["floating"]) }), + ]), +); /** * A single page of results */ export const ExternalIpResultsPage = z.preprocess( processResponseBody, - z.object({ items: ExternalIp.array(), nextPage: z.string().optional() }) -) + z.object({ items: ExternalIp.array(), nextPage: z.string().optional() }), +); /** * The `FieldType` identifies the data type of a target or metric field. */ +export const FieldTypeEnumArray = [ + "string", + "i8", + "u8", + "i16", + "u16", + "i32", + "u32", + "i64", + "u64", + "ip_addr", + "uuid", + "bool", +] as const; export const FieldType = z.preprocess( processResponseBody, - z.enum([ - 'string', - 'i8', - 'u8', - 'i16', - 'u16', - 'i32', - 'u32', - 'i64', - 'u64', - 'ip_addr', - 'uuid', - 'bool', - ]) -) - + z.enum(FieldTypeEnumArray), +); /** * The source from which a field is derived, the target or metric. */ -export const FieldSource = z.preprocess(processResponseBody, z.enum(['target', 'metric'])) - +export const FieldSourceEnumArray = ["target", "metric"] as const; +export const FieldSource = z.preprocess( + processResponseBody, + z.enum(FieldSourceEnumArray), +); /** * The name and type information for a field of a timeseries schema. */ @@ -1378,8 +1452,8 @@ export const FieldSchema = z.preprocess( fieldType: FieldType, name: z.string(), source: FieldSource, - }) -) + }), +); /** * The `FieldValue` contains the value of a target or metric field. @@ -1387,42 +1461,51 @@ export const FieldSchema = z.preprocess( export const FieldValue = z.preprocess( processResponseBody, z.union([ - z.object({ type: z.enum(['string']), value: z.string() }), - z.object({ type: z.enum(['i8']), value: z.number().min(-127).max(127) }), - z.object({ type: z.enum(['u8']), value: z.number().min(0).max(255) }), - z.object({ type: z.enum(['i16']), value: z.number().min(-32767).max(32767) }), - z.object({ type: z.enum(['u16']), value: z.number().min(0).max(65535) }), - z.object({ type: z.enum(['i32']), value: z.number().min(-2147483647).max(2147483647) }), - z.object({ type: z.enum(['u32']), value: z.number().min(0).max(4294967295) }), - z.object({ type: z.enum(['i64']), value: z.number() }), - z.object({ type: z.enum(['u64']), value: z.number().min(0) }), - z.object({ type: z.enum(['ip_addr']), value: z.string().ip() }), - z.object({ type: z.enum(['uuid']), value: z.string().uuid() }), - z.object({ type: z.enum(['bool']), value: SafeBoolean }), - ]) -) + z.object({ type: z.enum(["string"]), value: z.string() }), + z.object({ type: z.enum(["i8"]), value: z.number().min(-127).max(127) }), + z.object({ type: z.enum(["u8"]), value: z.number().min(0).max(255) }), + z.object({ + type: z.enum(["i16"]), + value: z.number().min(-32767).max(32767), + }), + z.object({ type: z.enum(["u16"]), value: z.number().min(0).max(65535) }), + z.object({ + type: z.enum(["i32"]), + value: z.number().min(-2147483647).max(2147483647), + }), + z.object({ + type: z.enum(["u32"]), + value: z.number().min(0).max(4294967295), + }), + z.object({ type: z.enum(["i64"]), value: z.number() }), + z.object({ type: z.enum(["u64"]), value: z.number().min(0) }), + z.object({ type: z.enum(["ip_addr"]), value: z.string().ip() }), + z.object({ type: z.enum(["uuid"]), value: z.string().uuid() }), + z.object({ type: z.enum(["bool"]), value: SafeBoolean }), + ]), +); /** * Parameters for finalizing a disk */ export const FinalizeDisk = z.preprocess( processResponseBody, - z.object({ snapshotName: Name.optional() }) -) + z.object({ snapshotName: Name.optional() }), +); +export const FleetRoleEnumArray = ["admin", "collaborator", "viewer"] as const; export const FleetRole = z.preprocess( processResponseBody, - z.enum(['admin', 'collaborator', 'viewer']) -) - + z.enum(FleetRoleEnumArray), +); /** * Describes what kind of identity is described by an id */ +export const IdentityTypeEnumArray = ["silo_user", "silo_group"] as const; export const IdentityType = z.preprocess( processResponseBody, - z.enum(['silo_user', 'silo_group']) -) - + z.enum(IdentityTypeEnumArray), +); /** * Describes the assignment of a particular role on a particular resource to a particular identity (user, group, etc.) * @@ -1434,8 +1517,8 @@ export const FleetRoleRoleAssignment = z.preprocess( identityId: z.string().uuid(), identityType: IdentityType, roleName: FleetRole, - }) -) + }), +); /** * Policy for a particular resource @@ -1444,8 +1527,8 @@ export const FleetRoleRoleAssignment = z.preprocess( */ export const FleetRolePolicy = z.preprocess( processResponseBody, - z.object({ roleAssignments: FleetRoleRoleAssignment.array() }) -) + z.object({ roleAssignments: FleetRoleRoleAssignment.array() }), +); /** * A Floating IP is a well-known IP address which can be attached and detached from instances. @@ -1462,21 +1545,24 @@ export const FloatingIp = z.preprocess( projectId: z.string().uuid(), timeCreated: z.coerce.date(), timeModified: z.coerce.date(), - }) -) + }), +); /** * The type of resource that a floating IP is attached to */ -export const FloatingIpParentKind = z.preprocess(processResponseBody, z.enum(['instance'])) - +export const FloatingIpParentKindEnumArray = ["instance"] as const; +export const FloatingIpParentKind = z.preprocess( + processResponseBody, + z.enum(FloatingIpParentKindEnumArray), +); /** * Parameters for attaching a floating IP address to another resource */ export const FloatingIpAttach = z.preprocess( processResponseBody, - z.object({ kind: FloatingIpParentKind, parent: NameOrId }) -) + z.object({ kind: FloatingIpParentKind, parent: NameOrId }), +); /** * Parameters for creating a new floating IP address for instances. @@ -1488,40 +1574,44 @@ export const FloatingIpCreate = z.preprocess( ip: z.string().ip().optional(), name: Name, pool: NameOrId.optional(), - }) -) + }), +); /** * A single page of results */ export const FloatingIpResultsPage = z.preprocess( processResponseBody, - z.object({ items: FloatingIp.array(), nextPage: z.string().optional() }) -) + z.object({ items: FloatingIp.array(), nextPage: z.string().optional() }), +); /** * Updateable identity-related parameters */ export const FloatingIpUpdate = z.preprocess( processResponseBody, - z.object({ description: z.string().optional(), name: Name.optional() }) -) + z.object({ description: z.string().optional(), name: Name.optional() }), +); /** * View of a Group */ export const Group = z.preprocess( processResponseBody, - z.object({ displayName: z.string(), id: z.string().uuid(), siloId: z.string().uuid() }) -) + z.object({ + displayName: z.string(), + id: z.string().uuid(), + siloId: z.string().uuid(), + }), +); /** * A single page of results */ export const GroupResultsPage = z.preprocess( processResponseBody, - z.object({ items: Group.array(), nextPage: z.string().optional() }) -) + z.object({ items: Group.array(), nextPage: z.string().optional() }), +); /** * An RFC-1035-compliant hostname @@ -1534,10 +1624,15 @@ export const Hostname = z.preprocess( .string() .min(1) .max(253) - .regex(/^([a-zA-Z0-9]+[a-zA-Z0-9\-]*(?

diff --git a/app/components/RefetchIntervalPicker.tsx b/app/components/RefetchIntervalPicker.tsx index 5f68213bf3..0f99410cc7 100644 --- a/app/components/RefetchIntervalPicker.tsx +++ b/app/components/RefetchIntervalPicker.tsx @@ -8,7 +8,7 @@ import cn from 'classnames' import { useEffect, useState } from 'react' -import { Refresh16Icon, Time16Icon } from '@oxide/design-system/icons/react' +import { Refresh16Icon } from '@oxide/design-system/icons/react' import { Listbox, type ListboxItem } from '~/ui/lib/Listbox' import { SpinnerLoader } from '~/ui/lib/Spinner' @@ -37,9 +37,17 @@ type Props = { enabled: boolean isLoading: boolean fn: () => void + className?: string + variant?: 'table' | 'metrics' } -export function useIntervalPicker({ enabled, isLoading, fn }: Props) { +export function useIntervalPicker({ + enabled, + isLoading, + fn, + className, + variant = 'metrics', +}: Props) { const [intervalPreset, setIntervalPreset] = useState('10s') const [lastFetched, setLastFetched] = useState(new Date()) @@ -53,18 +61,15 @@ export function useIntervalPicker({ enabled, isLoading, fn }: Props) { return { intervalMs: (enabled && intervalPresets[intervalPreset]) || undefined, intervalPicker: ( -
-
- Refreshed{' '} - {toLocaleTimeString(lastFetched)} -
+
+
+ Updated {toLocaleTimeString(lastFetched)} +
), } diff --git a/app/components/TableFilter.tsx b/app/components/TableFilter.tsx new file mode 100644 index 0000000000..2bfe83f4f6 --- /dev/null +++ b/app/components/TableFilter.tsx @@ -0,0 +1,324 @@ +/* + * 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 { CloseButton, Popover, PopoverButton, PopoverPanel } from '@headlessui/react' +import type { Column, ColumnFiltersState } from '@tanstack/react-table' +import cn from 'classnames' +import { isEqual } from 'lodash' +import { useEffect, useState, type Dispatch, type SetStateAction } from 'react' + +import { + AddRoundel12Icon, + Close12Icon, + Filter12Icon, +} from '@oxide/design-system/icons/react' + +import { defaultColumnFilters } from '~/table/QueryTable' +import { Button, buttonStyle } from '~/ui/lib/Button' +import { DateRangePicker } from '~/ui/lib/DateRangePicker' +import { Listbox } from '~/ui/lib/Listbox' +import { NumberInput } from '~/ui/lib/NumberInput' +import { TextInput } from '~/ui/lib/TextInput' +import { titleCase } from '~/util/str' +import { GiB, KiB, MiB, TiB } from '~/util/units' + +export function TableFilter({ + disabled, + columnFilters, + setColumnFilters, + localFilters, + setLocalFilters, + columnOptions, +}: { + disabled: boolean + localFilters: ColumnFiltersState + setLocalFilters: Dispatch> + columnFilters: ColumnFiltersState + setColumnFilters: Dispatch> + columnOptions: Column[] +}) { + const [popoverElement, setPopoverElement] = useState() + const setPopoverRef = (element: HTMLDivElement | null) => { + setPopoverElement(element) + } + + const addFilter = () => { + setLocalFilters([ + ...localFilters, + { id: columnOptions[0].columnDef.id || '', value: '' }, + ]) + } + + const updateFilter = (index: number, key: 'id' | 'value', value: string) => { + const newFilters = [...localFilters] + newFilters[index] = { ...newFilters[index], [key]: value } + if (key === 'id') { + newFilters[index].value = '' // We want to reset the value when the filter column changes + } + setLocalFilters(newFilters) + } + + const deleteFilter = (index: number) => { + const newFilters = localFilters.filter((_, i) => i !== index) + if (newFilters.length > 0) { + setLocalFilters(newFilters) + } else { + setLocalFilters([...defaultColumnFilters]) + } + } + + const applyFilters = () => { + // we don't want to include the empty filter awaiting input + // in the actual columnFilter used against the table + const trimmedLocalFilters = localFilters.filter((filter) => filter.id !== '') + setColumnFilters(trimmedLocalFilters) + } + + useEffect(() => { + if (!popoverElement) { + if (columnFilters.length > 0) { + setLocalFilters(columnFilters) + } else { + setLocalFilters([...defaultColumnFilters]) + } + } + }, [columnFilters, popoverElement, setLocalFilters]) + + const clearFilters = () => { + setLocalFilters([...defaultColumnFilters]) + setColumnFilters([]) + } + + const isFiltering = columnFilters.length > 0 + const isDefault = + isEqual(localFilters, defaultColumnFilters) && columnFilters.length === 0 + + const hasChanges = !isEqual(localFilters, columnFilters) && !isDefault + + return ( + + svg]:text-disabled' + )} + disabled={disabled} + > + + {isFiltering && ( +
{columnFilters.length}
+ )} +
+ +
+ Filter Snapshots + + Clear + +
+
+
+ {localFilters.map((filter, index) => { + const columnOption = columnOptions.find((option) => option.id === filter.id) + const availableOptions = columnOptions.filter( + (col) => !localFilters.some((f) => f.id === col.id) || col.id === filter.id + ) + const isRange = !!( + columnOption && columnOption.columnDef.meta?.filterVariant === 'range' + ) + return ( +
+
+ ({ + value: col.id, + label: titleCase( + typeof col.columnDef.header === 'string' + ? col.columnDef.header + : col.id + ), + })) || [] + } + onChange={(value) => updateFilter(index, 'id', value)} + className={cn(isRange ? 'w-1/3' : 'w-1/2', 'flex-shrink-0')} + /> + updateFilter(index, 'value', value)} + /> +
+ +
+ ) + })} +
+
+ + + + Apply + +
+
+
+
+ ) +} + +const DynamicFilterInput = ({ + column, + filterValue, + onFilterChange, +}: { + column: Column | undefined + filterValue: string | number[] + onFilterChange: (value: string | number[]) => void +}) => { + if (!column) { + return ( + + ) + } + + const { filterVariant, options } = column.columnDef.meta ?? {} + + if (filterVariant === 'range') { + return + } else if (filterVariant === 'select' && options) { + return ( + ({ value: val, label: titleCase(val) }))} + onChange={(val) => onFilterChange(val)} + className="w-1/2" + /> + ) + } else if (filterVariant === 'datetime') { + return ( + { + onFilterChange(range) + }} + label="Label" + hideTimeZone + hourCycle={24} + disableTime + className="w-1/2 bg-default" + /> + ) + } else { + return ( + onFilterChange(e.target.value)} + className="w-1/2 rounded border shadow" + placeholder="Filter value" + /> + ) + } +} + +const units = [ + { label: 'KiB', value: KiB }, + { label: 'MiB', value: MiB }, + { label: 'TiB', value: TiB }, + { label: 'GiB', value: GiB }, +] + +const FilterNumberRange = ({ + filterValue, + onChange, +}: { + filterValue: string | number[] + onChange: (value: string | number[]) => void +}) => { + const [unit, setUnit] = useState(MiB.toString()) + const numberUnit = parseInt(unit) + + let min = undefined + let max = undefined + if (Array.isArray(filterValue) && filterValue.length === 2) { + min = typeof filterValue[0] === 'number' ? filterValue[0] / numberUnit : undefined + max = typeof filterValue[1] === 'number' ? filterValue[1] / numberUnit : undefined + } + + console.log(filterValue) + + return ( +
+ ({ label: unit.label, value: unit.value.toString() }))} + onChange={(val) => { + setUnit(val) + }} + className="w-1/3" + /> + onChange([value * numberUnit, max ?? NaN])} + className="w-1/3" + placeholder="Min" + formatOptions={{ + minimumFractionDigits: 0, + maximumFractionDigits: 2, + }} + /> + onChange([min ?? NaN, value * numberUnit])} + className="w-1/3" + placeholder="Max" + formatOptions={{ + minimumFractionDigits: 0, + maximumFractionDigits: 2, + }} + /> +
+ ) +} diff --git a/app/components/form/fields/DateTimeRangePicker.tsx b/app/components/form/fields/DateTimeRangePicker.tsx index 291f7c7020..b112bbdd15 100644 --- a/app/components/form/fields/DateTimeRangePicker.tsx +++ b/app/components/form/fields/DateTimeRangePicker.tsx @@ -79,7 +79,7 @@ export function useDateTimeRangePicker({ } } -type DateTimeRange = { start: DateValue; end: DateValue } +export type DateTimeRange = { start: DateValue; end: DateValue } type DateTimeRangePickerProps = { range: DateTimeRange @@ -89,6 +89,7 @@ type DateTimeRangePickerProps = { onRangeChange?: (preset: RangeKeyAll) => void minValue?: DateValue | undefined maxValue?: DateValue | undefined + disableTime?: boolean } export function DateTimeRangePicker({ @@ -99,6 +100,7 @@ export function DateTimeRangePicker({ minValue, maxValue, onRangeChange, + disableTime = false, }: DateTimeRangePickerProps) { return (
@@ -126,6 +128,7 @@ export function DateTimeRangePicker({ maxValue={maxValue} hideTimeZone className="[&_.rounded-l]:!rounded-l-none [&_button]:!border-l-0" + disableTime={disableTime} />
diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index 27fecb8c71..2408f0cb61 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -33,7 +33,7 @@ import { addToast } from '~/stores/toast' import { EmptyCell, SkeletonCell } from '~/table/cells/EmptyCell' import { LinkCell } from '~/table/cells/LinkCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' -import { Columns, DescriptionCell } from '~/table/columns/common' +import { Columns, TruncateCell } from '~/table/columns/common' import { Table } from '~/table/Table' import { Badge } from '~/ui/lib/Badge' import { CopyableIp } from '~/ui/lib/CopyableIp' @@ -263,7 +263,7 @@ export function NetworkingTab() { }), ipColHelper.accessor((row) => ('description' in row ? row.description : undefined), { header: 'description', - cell: (info) => , + cell: (info) => , }), ] diff --git a/app/pages/project/snapshots/SnapshotsPage.tsx b/app/pages/project/snapshots/SnapshotsPage.tsx index cced8b2804..174ac11746 100644 --- a/app/pages/project/snapshots/SnapshotsPage.tsx +++ b/app/pages/project/snapshots/SnapshotsPage.tsx @@ -18,6 +18,7 @@ import { } from '@oxide/api' import { Snapshots16Icon, Snapshots24Icon } from '@oxide/design-system/icons/react' +import { SnapshotStateEnumArray } from '~/api/__generated__/validate' import { DocsPopover } from '~/components/DocsPopover' import { SnapshotStatusBadge } from '~/components/StatusBadge' import { getProjectSelector, useProjectSelector } from '~/hooks' @@ -30,7 +31,6 @@ import { Badge } from '~/ui/lib/Badge' import { CreateLink } from '~/ui/lib/CreateButton' import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' -import { TableActions } from '~/ui/lib/Table' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' @@ -84,17 +84,20 @@ SnapshotsPage.loader = async ({ params }: LoaderFunctionArgs) => { const colHelper = createColumnHelper() const staticCols = [ - colHelper.accessor('name', {}), - colHelper.accessor('description', Columns.description), + colHelper.accessor('name', Columns.name), colHelper.accessor('diskId', { header: 'disk', cell: (info) => , + size: 125, }), colHelper.accessor('state', { cell: (info) => , + size: 125, + meta: { filterVariant: 'select', options: [...SnapshotStateEnumArray] }, }), colHelper.accessor('size', Columns.size), colHelper.accessor('timeCreated', Columns.timeCreated), + colHelper.accessor('description', Columns.description), ] export function SnapshotsPage() { @@ -143,10 +146,11 @@ export function SnapshotsPage() { links={[docLinks.snapshots]} /> - - New snapshot - - } /> +
} + actions={New snapshot} + /> ) diff --git a/app/table/QueryTable.tsx b/app/table/QueryTable.tsx index f4906c3d80..3999bd857c 100644 --- a/app/table/QueryTable.tsx +++ b/app/table/QueryTable.tsx @@ -7,11 +7,23 @@ */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { hashKey, type UseQueryOptions } from '@tanstack/react-query' -import { getCoreRowModel, useReactTable, type ColumnDef } from '@tanstack/react-table' -import React, { useCallback, useMemo, type ComponentType } from 'react' +import { hashKey, useIsFetching, type UseQueryOptions } from '@tanstack/react-query' +import { + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, + type ColumnDef, + type ColumnFiltersState, + type RowData, +} from '@tanstack/react-table' +import cn from 'classnames' +import React, { useCallback, useMemo, useRef, useState, type ComponentType } from 'react' +import { useSearchParams } from 'react-router-dom' import { + apiQueryClient, useApiQuery, type ApiError, type ApiListMethods, @@ -19,11 +31,13 @@ import { type Result, type ResultItem, } from '@oxide/api' +import { Close12Icon, Search16Icon } from '@oxide/design-system/icons/react' import { Pagination } from '~/components/Pagination' -import { usePagination } from '~/hooks/use-pagination' +import { useIntervalPicker } from '~/components/RefetchIntervalPicker' +import { TableFilter } from '~/components/TableFilter' import { EmptyMessage } from '~/ui/lib/EmptyMessage' -import { TableEmptyBox } from '~/ui/lib/Table' +import { TableActions, TableEmptyBox } from '~/ui/lib/Table' import { Table } from './Table' @@ -58,11 +72,27 @@ type QueryTableProps = { pageSize?: number rowHeight?: 'small' | 'large' emptyState: React.ReactElement + actions?: React.ReactElement columns: ColumnDef[] } +declare module '@tanstack/react-table' { + // allows us to define custom properties for our columns + interface ColumnMeta { + filterVariant?: 'text' | 'range' | 'select' | 'datetime' + options?: string[] + } +} + export const PAGE_SIZE = 25 +export const defaultColumnFilters = [ + { + id: '', + value: '', + }, +] + // eslint-disable-next-line @typescript-eslint/no-explicit-any const makeQueryTable = >( query: any, @@ -76,53 +106,238 @@ const makeQueryTable = >( rowHeight = 'small', emptyState, columns, + actions, }: QueryTableProps) { - const { currentPage, goToNextPage, goToPrevPage, hasPrev } = usePagination() + const [globalFilter, setGlobalFilter] = useState('') + const [searching, setSearching] = useState(false) + const searchInputRef = useRef(null) + + // localFilters are the state used for the filter popover + // columnFilters are the applied state used to do the actual filtering + const [localFilters, setLocalFilters] = useState(defaultColumnFilters) + const [columnFilters, setColumnFilters] = useState([]) const { data, isLoading } = useApiQuery( query, { path: params.path, - query: { ...params.query, page_token: currentPage, limit: pageSize }, + query: { ...params.query, limit: 500000 }, }, options ) + const { intervalPicker } = useIntervalPicker({ + enabled: true, + isLoading: useIsFetching({ queryKey: [query] }) > 0, + fn: () => apiQueryClient.invalidateQueries(query), + variant: 'table', + }) + const tableData: any[] = useMemo(() => (data as any)?.items || [], [data]) const getRowId = useCallback((row: any) => row.name, []) + const [searchParams, setSearchParams] = useSearchParams() + const page = searchParams.get('page') + const currentPage = page ? Number(page) : 0 + + const nextPage = () => { + const maxPage = table.getPageCount() - 1 + if (currentPage < maxPage) { + updatePage(currentPage + 1) + } + } + + const prevPage = () => { + if (currentPage > 0) { + updatePage(currentPage - 1) + } + } + + const updatePage = (p: number) => { + if (p === 0) { + // first page does not need to be in the URL + searchParams.delete('page') + } else { + searchParams.set('page', p.toString()) + } + setSearchParams(searchParams, { replace: true }) + } + const table = useReactTable({ columns, data: tableData, getRowId, getCoreRowModel: getCoreRowModel(), - manualPagination: true, + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + enableSortingRemoval: false, + getPaginationRowModel: getPaginationRowModel(), + onGlobalFilterChange: setGlobalFilter, + globalFilterFn: 'includesString', + initialState: { + sorting: [ + { + id: 'timeCreated', + desc: true, + }, + ], + }, + state: { + pagination: { + pageIndex: currentPage, + pageSize, + }, + globalFilter, + columnFilters, + }, + onPaginationChange: (updater) => { + if (typeof updater === 'function') { + const newState = updater({ pageIndex: currentPage, pageSize }) + updatePage(newState.pageIndex) + } + }, + manualPagination: false, }) if (debug) console.table((data as { items?: any[] })?.items || data) - if (isLoading) return null - const isEmpty = tableData.length === 0 && !hasPrev - if (isEmpty) { - return ( - {emptyState || } - ) + const filteredLength = table.getPrePaginationRowModel().rows.length + const noResults = tableData.length === 0 + const isEmpty = noResults || filteredLength === 0 + + const resetSearch = () => { + setGlobalFilter('') + setSearching(false) + } + + const clearAllFilters = () => { + setGlobalFilter('') + setSearching(false) + setLocalFilters([...defaultColumnFilters]) + setColumnFilters([]) } + const isFilteringColumn = columnFilters.length > 0 + return ( <> -
+ +
{intervalPicker}
+
+
+ {(globalFilter || isFilteringColumn) && ( +
+ {filteredLength.toLocaleString()} matches +
+ )} +
+ + + setGlobalFilter(el.target.value)} + onBlur={() => { + if (globalFilter === '') { + setSearching(false) + } + }} + /> + +
+
+ !!item.accessorFn)} // If it doesnt have an accessorFn it probably isn't filterable + /> + {actions} +
+
+ {isEmpty ? ( + + {noResults ? ( + emptyState || + ) : ( + } + title="No matches" + body={ + isFilteringColumn + ? 'Could not find items that match filters' + : `Could not find item "${globalFilter}"` + } + buttonText={isFilteringColumn ? 'Clear filters' : 'Clear search'} + onClick={clearAllFilters} + /> + )} + + ) : ( +
+ )} ) } + +const SearchIcon12 = ({ className }: { className?: string }) => ( + + + +) diff --git a/app/table/Table.tsx b/app/table/Table.tsx index 759ea6800a..f2517de398 100644 --- a/app/table/Table.tsx +++ b/app/table/Table.tsx @@ -31,14 +31,45 @@ export const Table = ({ {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map((header) => ( - - {flexRender(header.column.columnDef.header, header.getContext())} - - ))} + {headerGroup.headers.map((header) => { + const sortDir = header.column.getIsSorted() + + return ( + +
+ {flexRender(header.column.columnDef.header, header.getContext())} +
+ + +
+
+
+ ) + })}
))}
@@ -73,6 +104,10 @@ export const Table = ({ {...(i === 0 ? firstCellProps : {})} className={cell.column.columnDef.meta?.tdClassName} height={rowHeight} + style={{ + minWidth: `${cell.column.getSize()}px`, + maxWidth: `${cell.column.getSize()}px`, + }} > {flexRender(cell.column.columnDef.cell, cell.getContext())} @@ -83,3 +118,19 @@ export const Table = ({ ) + +const SortArrow = ({ className }: { className?: string }) => ( + + + +) diff --git a/app/table/columns/common.tsx b/app/table/columns/common.tsx index c8de914199..729f7f06eb 100644 --- a/app/table/columns/common.tsx +++ b/app/table/columns/common.tsx @@ -6,8 +6,11 @@ * Copyright Oxide Computer Company */ +import { parseAbsolute } from '@internationalized/date' +import { type Row } from '@tanstack/react-table' import { filesize } from 'filesize' +import type { DateTimeRange } from '~/components/form/fields/DateTimeRangePicker' import { DateTime } from '~/ui/lib/DateTime' import { Truncate } from '~/ui/lib/Truncate' @@ -30,16 +33,62 @@ function sizeCell(info: Info) { ) } -export const DescriptionCell = ({ text }: { text?: string }) => - text ? : +export const TruncateCell = ({ text, length = 48 }: { text?: string; length?: number }) => + text ? : /** Columns used in a bunch of tables */ export const Columns = { /** Truncates text if too long, full text in tooltip */ + name: { + cell: (info: Info) => , + size: 200, + }, description: { - cell: (info: Info) => , + cell: (info: Info) => ( + + ), + size: 225, + }, + size: { + cell: sizeCell, + disableGlobalFilter: true, + size: 125, + meta: { filterVariant: 'range' as const }, + }, + timeCreated: { + header: 'created', + cell: dateCell, + disableGlobalFilter: true, + meta: { filterVariant: 'datetime' as const }, + filterFn: dateTimeFilter, }, - size: { cell: sizeCell }, - timeCreated: { header: 'created', cell: dateCell }, - timeModified: { header: 'modified', cell: dateCell }, + timeModified: { + header: 'modified', + cell: dateCell, + disableGlobalFilter: true, + meta: { filterVariant: 'datetime' as const }, + filterFn: dateTimeFilter, + }, +} + +function dateTimeFilter( + row: Row, + columnId: string, + filterValue: DateTimeRange +): boolean { + const rowDate: Date = row.getValue(columnId) + const isoString = rowDate.toISOString() + + const rowValue = parseAbsolute(isoString, 'UTC') + const { start, end } = filterValue + + // we filter the dateTime by day ignorning the hours + // and minutes to avoid requiring that within the UI + // may revise if we think the user requires that granularity + const startOfDay = start.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }) + const endOfDay = end.set({ hour: 23, minute: 59, second: 59, millisecond: 999 }) + + const rowValueDate = rowValue.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }) + + return rowValueDate.compare(startOfDay) >= 0 && rowValueDate.compare(endOfDay) <= 0 } diff --git a/app/ui/lib/DateRangePicker.tsx b/app/ui/lib/DateRangePicker.tsx index 8f04817ef1..8c4a5a7938 100644 --- a/app/ui/lib/DateRangePicker.tsx +++ b/app/ui/lib/DateRangePicker.tsx @@ -5,7 +5,7 @@ * * Copyright Oxide Computer Company */ -import { getLocalTimeZone } from '@internationalized/date' +import { getLocalTimeZone, Time } from '@internationalized/date' import type { TimeValue } from '@react-types/datepicker' import cn from 'classnames' import { useMemo, useRef } from 'react' @@ -22,6 +22,7 @@ import { RangeCalendar } from './RangeCalendar' interface DateRangePickerProps extends DateRangePickerStateOptions { label: string className?: string + disableTime?: boolean } export function DateRangePicker(props: DateRangePickerProps) { @@ -35,15 +36,11 @@ export function DateRangePicker(props: DateRangePickerProps) { const formatter = useDateFormatter({ dateStyle: 'short', - timeStyle: 'short', - hourCycle: 'h24', + ...(props.disableTime ? {} : { timeStyle: 'short', hourCycle: 'h24' }), }) const label = useMemo(() => { - // This is here to make TS happy. This should be impossible in practice - // because we always pass a value to this component and there is no way to - // unset the value through the UI. - if (!state.dateRange) return 'No range selected' + if (!state.dateRange) return Select date range return formatter.formatRange( state.dateRange.start.toDate(getLocalTimeZone()), @@ -56,19 +53,24 @@ export function DateRangePicker(props: DateRangePickerProps) { aria-label={props.label} className={cn('relative flex-col text-left', props.className)} > -
+
@@ -90,23 +92,25 @@ export function DateRangePicker(props: DateRangePickerProps) { -
- state.setTime('start', v)} - hourCycle={24} - className="shrink-0 grow basis-0" - /> -
- state.setTime('end', v)} - hourCycle={24} - className="shrink-0 grow basis-0" - /> -
+ {!props.disableTime && ( +
+ state.setTime('start', v)} + hourCycle={24} + className="shrink-0 grow basis-0" + /> +
+ state.setTime('end', v)} + hourCycle={24} + className="shrink-0 grow basis-0" + /> +
+ )}
)} diff --git a/app/ui/lib/DateTime.tsx b/app/ui/lib/DateTime.tsx index 828d1bf1ed..d2deb4e564 100644 --- a/app/ui/lib/DateTime.tsx +++ b/app/ui/lib/DateTime.tsx @@ -9,7 +9,7 @@ import { toLocaleDateString, toLocaleTimeString } from '~/util/date' export const DateTime = ({ date, locale }: { date: Date; locale?: string }) => ( -