diff --git a/spec/openapi.dashboard-api.yaml b/spec/openapi.dashboard-api.yaml index acf6ac915..9d0a4fbac 100644 --- a/spec/openapi.dashboard-api.yaml +++ b/spec/openapi.dashboard-api.yaml @@ -81,6 +81,29 @@ components: format: uuid maxItems: 100 uniqueItems: true + teamID: + name: teamID + in: path + required: true + description: Identifier of the team. + schema: + type: string + format: uuid + userId: + name: userId + in: path + required: true + description: Identifier of the user. + schema: + type: string + format: uuid + teamSlug: + name: slug + in: query + required: true + description: Team slug to resolve. + schema: + type: string responses: "400": @@ -320,9 +343,242 @@ components: type: string description: Human-readable health check result. + UserTeamLimits: + type: object + required: + - maxLengthHours + - concurrentSandboxes + - concurrentTemplateBuilds + - maxVcpu + - maxRamMb + - diskMb + properties: + maxLengthHours: + type: integer + format: int64 + concurrentSandboxes: + type: integer + format: int32 + concurrentTemplateBuilds: + type: integer + format: int32 + maxVcpu: + type: integer + format: int32 + maxRamMb: + type: integer + format: int32 + diskMb: + type: integer + format: int32 + + UserTeam: + type: object + required: + - id + - name + - slug + - tier + - email + - profilePictureUrl + - isBlocked + - isBanned + - blockedReason + - isDefault + - limits + properties: + id: + type: string + format: uuid + name: + type: string + slug: + type: string + tier: + type: string + email: + type: string + profilePictureUrl: + type: string + nullable: true + isBlocked: + type: boolean + isBanned: + type: boolean + blockedReason: + type: string + nullable: true + isDefault: + type: boolean + limits: + $ref: "#/components/schemas/UserTeamLimits" + + UserTeamsResponse: + type: object + required: + - teams + properties: + teams: + type: array + items: + $ref: "#/components/schemas/UserTeam" + + TeamMember: + type: object + required: + - id + - email + - isDefault + - createdAt + properties: + id: + type: string + format: uuid + email: + type: string + isDefault: + type: boolean + addedBy: + type: string + format: uuid + nullable: true + createdAt: + type: string + format: date-time + nullable: true + + TeamMembersResponse: + type: object + required: + - members + properties: + members: + type: array + items: + $ref: "#/components/schemas/TeamMember" + + UpdateTeamRequest: + type: object + minProperties: 1 + properties: + name: + type: string + minLength: 1 + maxLength: 255 + profilePictureUrl: + type: string + nullable: true + + UpdateTeamResponse: + type: object + required: + - id + - name + properties: + id: + type: string + format: uuid + name: + type: string + profilePictureUrl: + type: string + nullable: true + + AddTeamMemberRequest: + type: object + required: + - email + properties: + email: + type: string + format: email + + DefaultTemplateAlias: + type: object + required: + - alias + properties: + alias: + type: string + namespace: + type: string + nullable: true + + DefaultTemplate: + type: object + required: + - id + - aliases + - buildId + - ramMb + - vcpu + - totalDiskSizeMb + - createdAt + - public + - buildCount + - spawnCount + properties: + id: + type: string + aliases: + type: array + items: + $ref: "#/components/schemas/DefaultTemplateAlias" + buildId: + type: string + format: uuid + ramMb: + type: integer + format: int64 + vcpu: + type: integer + format: int64 + totalDiskSizeMb: + type: integer + format: int64 + nullable: true + envdVersion: + type: string + nullable: true + createdAt: + type: string + format: date-time + public: + type: boolean + buildCount: + type: integer + format: int32 + spawnCount: + type: integer + format: int64 + + DefaultTemplatesResponse: + type: object + required: + - templates + properties: + templates: + type: array + items: + $ref: "#/components/schemas/DefaultTemplate" + + TeamResolveResponse: + type: object + required: + - id + - slug + properties: + id: + type: string + format: uuid + slug: + type: string + tags: - name: builds - name: sandboxes + - name: teams + - name: templates paths: /health: @@ -439,3 +695,169 @@ paths: $ref: "#/components/responses/404" "500": $ref: "#/components/responses/500" + + /teams: + get: + summary: List user teams + description: Returns all teams the authenticated user belongs to, with limits and default flag. + tags: [teams] + security: + - Supabase1TokenAuth: [] + responses: + "200": + description: Successfully returned user teams. + content: + application/json: + schema: + $ref: "#/components/schemas/UserTeamsResponse" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + + /teams/resolve: + get: + summary: Resolve team identity + description: Resolves a team slug to the team's identity, validating the user is a member. + tags: [teams] + security: + - Supabase1TokenAuth: [] + parameters: + - $ref: "#/components/parameters/teamSlug" + responses: + "200": + description: Successfully resolved team. + content: + application/json: + schema: + $ref: "#/components/schemas/TeamResolveResponse" + "401": + $ref: "#/components/responses/401" + "403": + $ref: "#/components/responses/403" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" + + /teams/{teamID}: + patch: + summary: Update team + tags: [teams] + security: + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + parameters: + - $ref: "#/components/parameters/teamID" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateTeamRequest" + responses: + "200": + description: Successfully updated team. + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateTeamResponse" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "403": + $ref: "#/components/responses/403" + "500": + $ref: "#/components/responses/500" + + /teams/{teamID}/members: + get: + summary: List team members + tags: [teams] + security: + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + parameters: + - $ref: "#/components/parameters/teamID" + responses: + "200": + description: Successfully returned team members. + content: + application/json: + schema: + $ref: "#/components/schemas/TeamMembersResponse" + "401": + $ref: "#/components/responses/401" + "403": + $ref: "#/components/responses/403" + "500": + $ref: "#/components/responses/500" + post: + summary: Add team member + tags: [teams] + security: + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + parameters: + - $ref: "#/components/parameters/teamID" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AddTeamMemberRequest" + responses: + "201": + description: Successfully added team member. + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "403": + $ref: "#/components/responses/403" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" + + /teams/{teamID}/members/{userId}: + delete: + summary: Remove team member + tags: [teams] + security: + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + parameters: + - $ref: "#/components/parameters/teamID" + - $ref: "#/components/parameters/userId" + responses: + "204": + description: Successfully removed team member. + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "403": + $ref: "#/components/responses/403" + "500": + $ref: "#/components/responses/500" + + /templates/defaults: + get: + summary: List default templates + description: Returns the list of default templates with their latest build info and aliases. + tags: [templates] + security: + - Supabase1TokenAuth: [] + responses: + "200": + description: Successfully returned default templates. + content: + application/json: + schema: + $ref: "#/components/schemas/DefaultTemplatesResponse" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" diff --git a/spec/openapi.infra.yaml b/spec/openapi.infra.yaml index c42d173c2..7d16523a1 100644 --- a/spec/openapi.infra.yaml +++ b/spec/openapi.infra.yaml @@ -297,6 +297,26 @@ components: enabled: $ref: "#/components/schemas/SandboxAutoResumeEnabled" + SandboxOnTimeout: + type: string + description: Action taken when the sandbox times out. + enum: + - kill + - pause + + SandboxLifecycle: + type: object + description: Sandbox lifecycle policy returned by sandbox info. + required: + - autoResume + - onTimeout + properties: + autoResume: + type: boolean + description: Whether the sandbox can auto-resume. + onTimeout: + $ref: "#/components/schemas/SandboxOnTimeout" + SandboxLog: description: Log entry with timestamp and line required: @@ -491,6 +511,10 @@ components: envdAccessToken: type: string description: Access token used for envd communication + allowInternetAccess: + type: boolean + nullable: true + description: Whether internet access was explicitly enabled or disabled for the sandbox. Null means it was not explicitly set. domain: type: string nullable: true @@ -505,6 +529,10 @@ components: $ref: "#/components/schemas/SandboxMetadata" state: $ref: "#/components/schemas/SandboxState" + network: + $ref: "#/components/schemas/SandboxNetworkConfig" + lifecycle: + $ref: "#/components/schemas/SandboxLifecycle" volumeMounts: type: array items: diff --git a/src/types/dashboard-api.types.ts b/src/types/dashboard-api.types.ts index c19444bd9..020cd4adf 100644 --- a/src/types/dashboard-api.types.ts +++ b/src/types/dashboard-api.types.ts @@ -218,6 +218,296 @@ export interface paths { patch?: never trace?: never } + '/teams': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * List user teams + * @description Returns all teams the authenticated user belongs to, with limits and default flag. + */ + get: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description Successfully returned user teams. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['UserTeamsResponse'] + } + } + 401: components['responses']['401'] + 500: components['responses']['500'] + } + } + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/teams/resolve': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Resolve team identity + * @description Resolves a team slug to the team's identity, validating the user is a member. + */ + get: { + parameters: { + query: { + /** @description Team slug to resolve. */ + slug: components['parameters']['teamSlug'] + } + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description Successfully resolved team. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['TeamResolveResponse'] + } + } + 401: components['responses']['401'] + 403: components['responses']['403'] + 404: components['responses']['404'] + 500: components['responses']['500'] + } + } + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/teams/{teamID}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + post?: never + delete?: never + options?: never + head?: never + /** Update team */ + patch: { + parameters: { + query?: never + header?: never + path: { + /** @description Identifier of the team. */ + teamID: components['parameters']['teamID'] + } + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['UpdateTeamRequest'] + } + } + responses: { + /** @description Successfully updated team. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['UpdateTeamResponse'] + } + } + 400: components['responses']['400'] + 401: components['responses']['401'] + 403: components['responses']['403'] + 500: components['responses']['500'] + } + } + trace?: never + } + '/teams/{teamID}/members': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** List team members */ + get: { + parameters: { + query?: never + header?: never + path: { + /** @description Identifier of the team. */ + teamID: components['parameters']['teamID'] + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Successfully returned team members. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['TeamMembersResponse'] + } + } + 401: components['responses']['401'] + 403: components['responses']['403'] + 500: components['responses']['500'] + } + } + put?: never + /** Add team member */ + post: { + parameters: { + query?: never + header?: never + path: { + /** @description Identifier of the team. */ + teamID: components['parameters']['teamID'] + } + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['AddTeamMemberRequest'] + } + } + responses: { + /** @description Successfully added team member. */ + 201: { + headers: { + [name: string]: unknown + } + content?: never + } + 400: components['responses']['400'] + 401: components['responses']['401'] + 403: components['responses']['403'] + 404: components['responses']['404'] + 500: components['responses']['500'] + } + } + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/teams/{teamID}/members/{userId}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + post?: never + /** Remove team member */ + delete: { + parameters: { + query?: never + header?: never + path: { + /** @description Identifier of the team. */ + teamID: components['parameters']['teamID'] + /** @description Identifier of the user. */ + userId: components['parameters']['userId'] + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Successfully removed team member. */ + 204: { + headers: { + [name: string]: unknown + } + content?: never + } + 400: components['responses']['400'] + 401: components['responses']['401'] + 403: components['responses']['403'] + 500: components['responses']['500'] + } + } + options?: never + head?: never + patch?: never + trace?: never + } + '/templates/defaults': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * List default templates + * @description Returns the list of default templates with their latest build info and aliases. + */ + get: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description Successfully returned default templates. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['DefaultTemplatesResponse'] + } + } + 401: components['responses']['401'] + 500: components['responses']['500'] + } + } + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } } export type webhooks = Record export interface components { @@ -343,6 +633,96 @@ export interface components { /** @description Human-readable health check result. */ message: string } + UserTeamLimits: { + /** Format: int64 */ + maxLengthHours: number + /** Format: int32 */ + concurrentSandboxes: number + /** Format: int32 */ + concurrentTemplateBuilds: number + /** Format: int32 */ + maxVcpu: number + /** Format: int32 */ + maxRamMb: number + /** Format: int32 */ + diskMb: number + } + UserTeam: { + /** Format: uuid */ + id: string + name: string + slug: string + tier: string + email: string + profilePictureUrl: string | null + isBlocked: boolean + isBanned: boolean + blockedReason: string | null + isDefault: boolean + limits: components['schemas']['UserTeamLimits'] + } + UserTeamsResponse: { + teams: components['schemas']['UserTeam'][] + } + TeamMember: { + /** Format: uuid */ + id: string + email: string + isDefault: boolean + /** Format: uuid */ + addedBy?: string | null + /** Format: date-time */ + createdAt: string | null + } + TeamMembersResponse: { + members: components['schemas']['TeamMember'][] + } + UpdateTeamRequest: { + name?: string + profilePictureUrl?: string | null + } + UpdateTeamResponse: { + /** Format: uuid */ + id: string + name: string + profilePictureUrl?: string | null + } + AddTeamMemberRequest: { + /** Format: email */ + email: string + } + DefaultTemplateAlias: { + alias: string + namespace?: string | null + } + DefaultTemplate: { + id: string + aliases: components['schemas']['DefaultTemplateAlias'][] + /** Format: uuid */ + buildId: string + /** Format: int64 */ + ramMb: number + /** Format: int64 */ + vcpu: number + /** Format: int64 */ + totalDiskSizeMb: number | null + envdVersion?: string | null + /** Format: date-time */ + createdAt: string + public: boolean + /** Format: int32 */ + buildCount: number + /** Format: int64 */ + spawnCount: number + } + DefaultTemplatesResponse: { + templates: components['schemas']['DefaultTemplate'][] + } + TeamResolveResponse: { + /** Format: uuid */ + id: string + slug: string + } } responses: { /** @description Bad request */ @@ -406,6 +786,12 @@ export interface components { build_statuses: components['schemas']['BuildStatus'][] /** @description Comma-separated list of build IDs to get statuses for. */ build_ids: string[] + /** @description Identifier of the team. */ + teamID: string + /** @description Identifier of the user. */ + userId: string + /** @description Team slug to resolve. */ + teamSlug: string } requestBodies: never headers: never diff --git a/src/types/infra-api.types.ts b/src/types/infra-api.types.ts index adca1c00a..5c6eb82b9 100644 --- a/src/types/infra-api.types.ts +++ b/src/types/infra-api.types.ts @@ -2264,6 +2264,17 @@ export interface components { SandboxAutoResumeConfig: { enabled: components['schemas']['SandboxAutoResumeEnabled'] } + /** + * @description Action taken when the sandbox times out. + * @enum {string} + */ + SandboxOnTimeout: 'kill' | 'pause' + /** @description Sandbox lifecycle policy returned by sandbox info. */ + SandboxLifecycle: { + /** @description Whether the sandbox can auto-resume. */ + autoResume: boolean + onTimeout: components['schemas']['SandboxOnTimeout'] + } /** @description Log entry with timestamp and line */ SandboxLog: { /** @@ -2395,6 +2406,8 @@ export interface components { envdVersion: components['schemas']['EnvdVersion'] /** @description Access token used for envd communication */ envdAccessToken?: string + /** @description Whether internet access was explicitly enabled or disabled for the sandbox. Null means it was not explicitly set. */ + allowInternetAccess?: boolean | null /** @description Base domain where the sandbox traffic is accessible */ domain?: string | null cpuCount: components['schemas']['CPUCount'] @@ -2402,6 +2415,8 @@ export interface components { diskSizeMB: components['schemas']['DiskSizeMB'] metadata?: components['schemas']['SandboxMetadata'] state: components['schemas']['SandboxState'] + network?: components['schemas']['SandboxNetworkConfig'] + lifecycle?: components['schemas']['SandboxLifecycle'] volumeMounts?: components['schemas']['SandboxVolumeMount'][] } ListedSandbox: {