From a27e95e1a4997dd3c2352873bd144edb2813c544 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 24 May 2024 14:58:28 +0000 Subject: [PATCH 1/4] chore: add vendored version of experimental Coder SDK --- plugins/backstage-plugin-coder/package.json | 3 + .../src/api/vendoredSdk/README.md | 20 + .../src/api/vendoredSdk/api/api.ts | 1940 ++++++++++++ .../src/api/vendoredSdk/api/errors.ts | 124 + .../src/api/vendoredSdk/api/typesGenerated.ts | 2599 +++++++++++++++++ .../src/api/vendoredSdk/index.ts | 8 + .../src/api/vendoredSdk/utils/delay.ts | 4 + yarn.lock | 30 +- 8 files changed, 4713 insertions(+), 15 deletions(-) create mode 100644 plugins/backstage-plugin-coder/src/api/vendoredSdk/README.md create mode 100644 plugins/backstage-plugin-coder/src/api/vendoredSdk/api/api.ts create mode 100644 plugins/backstage-plugin-coder/src/api/vendoredSdk/api/errors.ts create mode 100644 plugins/backstage-plugin-coder/src/api/vendoredSdk/api/typesGenerated.ts create mode 100644 plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts create mode 100644 plugins/backstage-plugin-coder/src/api/vendoredSdk/utils/delay.ts diff --git a/plugins/backstage-plugin-coder/package.json b/plugins/backstage-plugin-coder/package.json index e21caf74..1d21b960 100644 --- a/plugins/backstage-plugin-coder/package.json +++ b/plugins/backstage-plugin-coder/package.json @@ -42,6 +42,8 @@ "@material-ui/lab": "4.0.0-alpha.61", "@tanstack/react-query": "4.36.1", "axios": "^1.6.8", + "dayjs": "^1.11.11", + "ua-parser-js": "^1.0.37", "use-sync-external-store": "^1.2.1", "valibot": "^0.28.1" }, @@ -57,6 +59,7 @@ "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^14.2.1", "@testing-library/user-event": "^14.0.0", + "@types/ua-parser-js": "^0.7.39", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/backstage-plugin-coder/src/api/vendoredSdk/README.md b/plugins/backstage-plugin-coder/src/api/vendoredSdk/README.md new file mode 100644 index 00000000..354acb1c --- /dev/null +++ b/plugins/backstage-plugin-coder/src/api/vendoredSdk/README.md @@ -0,0 +1,20 @@ +# Coder SDK - Experimental Vendored Version + +This is a vendored version of the main API files from the +[core Coder OSS repo](https://github.com/coder/coder/tree/main/site/src/api). All files (aside from test files) have been copied over directly, with only a +few changes made to satisfy default Backstage ESLint rules. + +While there is a risk of this getting out of sync with the versions of the +files in Coder OSS, the Coder API itself should be treated as stable. Breaking +changes are only made when absolutely necessary. + +## General approach + +- Copy over relevant files from Coder OSS and place them in relevant folders + - As much as possible, the file structure of the vendored files should match the file structure of Coder OSS to make it easier to copy updated files over. +- Have a single file at the top level of this directory that exports out the files for consumption elsewhere in the plugin. No plugin code should interact with the vendored files directly. + +## Eventual plans + +Coder has eventual plans to create a true SDK published through NPM. Once +that is published, all of this vendored code should be removed in favor of it. diff --git a/plugins/backstage-plugin-coder/src/api/vendoredSdk/api/api.ts b/plugins/backstage-plugin-coder/src/api/vendoredSdk/api/api.ts new file mode 100644 index 00000000..e0eafd1d --- /dev/null +++ b/plugins/backstage-plugin-coder/src/api/vendoredSdk/api/api.ts @@ -0,0 +1,1940 @@ +/** + * @file Coder is starting to import the Coder API file into more and more + * external projects, as a "pseudo-SDK". We are not at a stage where we are + * ready to commit to maintaining a public SDK, but we need equivalent + * functionality in other places. + * + * Message somebody from Team Blueberry if you need more context, but so far, + * these projects are importing the file: + * + * - The Coder VS Code extension + * @see {@link https://github.com/coder/vscode-coder} + * - The Coder Backstage plugin + * @see {@link https://github.com/coder/backstage-plugins} + * + * It is important that this file not do any aliased imports, or else the other + * consumers could break (particularly for platforms that limit how much you can + * touch their configuration files, like Backstage). Relative imports are still + * safe, though. + * + * For example, `utils/delay` must be imported using `../utils/delay` instead. + */ +import globalAxios, { type AxiosInstance, isAxiosError } from 'axios'; +import type dayjs from 'dayjs'; +import userAgentParser from 'ua-parser-js'; +import { delay } from '../utils/delay'; +import * as TypesGen from './typesGenerated'; + +const getMissingParameters = ( + oldBuildParameters: TypesGen.WorkspaceBuildParameter[], + newBuildParameters: TypesGen.WorkspaceBuildParameter[], + templateParameters: TypesGen.TemplateVersionParameter[], +) => { + const missingParameters: TypesGen.TemplateVersionParameter[] = []; + const requiredParameters: TypesGen.TemplateVersionParameter[] = []; + + templateParameters.forEach(p => { + // It is mutable and required. Mutable values can be changed after so we + // don't need to ask them if they are not required. + const isMutableAndRequired = p.mutable && p.required; + // Is immutable, so we can check if it is its first time on the build + const isImmutable = !p.mutable; + + if (isMutableAndRequired || isImmutable) { + requiredParameters.push(p); + } + }); + + for (const parameter of requiredParameters) { + // Check if there is a new value + let buildParameter = newBuildParameters.find( + p => p.name === parameter.name, + ); + + // If not, get the old one + if (!buildParameter) { + buildParameter = oldBuildParameters.find(p => p.name === parameter.name); + } + + // If there is a value from the new or old one, it is not missed + if (buildParameter) { + continue; + } + + missingParameters.push(parameter); + } + + // Check if parameter "options" changed and we can't use old build parameters. + templateParameters.forEach(templateParameter => { + if (templateParameter.options.length === 0) { + return; + } + + // Check if there is a new value + let buildParameter = newBuildParameters.find( + p => p.name === templateParameter.name, + ); + + // If not, get the old one + if (!buildParameter) { + buildParameter = oldBuildParameters.find( + p => p.name === templateParameter.name, + ); + } + + if (!buildParameter) { + return; + } + + const matchingOption = templateParameter.options.find( + option => option.value === buildParameter?.value, + ); + if (!matchingOption) { + missingParameters.push(templateParameter); + } + }); + return missingParameters; +}; + +/** + * + * @param agentId + * @returns An EventSource that emits agent metadata event objects + * (ServerSentEvent) + */ +export const watchAgentMetadata = (agentId: string): EventSource => { + return new EventSource( + `${location.protocol}//${location.host}/api/v2/workspaceagents/${agentId}/watch-metadata`, + { withCredentials: true }, + ); +}; + +/** + * @returns {EventSource} An EventSource that emits workspace event objects + * (ServerSentEvent) + */ +export const watchWorkspace = (workspaceId: string): EventSource => { + return new EventSource( + `${location.protocol}//${location.host}/api/v2/workspaces/${workspaceId}/watch`, + { withCredentials: true }, + ); +}; + +export const getURLWithSearchParams = ( + basePath: string, + options?: SearchParamOptions, +): string => { + if (!options) { + return basePath; + } + + const searchParams = new URLSearchParams(); + const keys = Object.keys(options) as (keyof SearchParamOptions)[]; + keys.forEach(key => { + const value = options[key]; + if (value !== undefined && value !== '') { + searchParams.append(key, value.toString()); + } + }); + + const searchString = searchParams.toString(); + return searchString ? `${basePath}?${searchString}` : basePath; +}; + +// withDefaultFeatures sets all unspecified features to not_entitled and +// disabled. +export const withDefaultFeatures = ( + fs: Partial, +): TypesGen.Entitlements['features'] => { + for (const feature of TypesGen.FeatureNames) { + // Skip fields that are already filled. + if (fs[feature] !== undefined) { + continue; + } + + fs[feature] = { + enabled: false, + entitlement: 'not_entitled', + }; + } + + return fs as TypesGen.Entitlements['features']; +}; + +type WatchBuildLogsByTemplateVersionIdOptions = { + after?: number; + onMessage: (log: TypesGen.ProvisionerJobLog) => void; + onDone?: () => void; + onError: (error: Error) => void; +}; + +export const watchBuildLogsByTemplateVersionId = ( + versionId: string, + { + onMessage, + onDone, + onError, + after, + }: WatchBuildLogsByTemplateVersionIdOptions, +) => { + const searchParams = new URLSearchParams({ follow: 'true' }); + if (after !== undefined) { + searchParams.append('after', after.toString()); + } + + const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; + const socket = new WebSocket( + `${proto}//${ + location.host + }/api/v2/templateversions/${versionId}/logs?${searchParams.toString()}`, + ); + + socket.binaryType = 'blob'; + + socket.addEventListener('message', event => + onMessage(JSON.parse(event.data) as TypesGen.ProvisionerJobLog), + ); + + socket.addEventListener('error', () => { + onError(new Error('Connection for logs failed.')); + socket.close(); + }); + + socket.addEventListener('close', () => { + // When the socket closes, logs have finished streaming! + onDone?.(); + }); + + return socket; +}; + +export const watchWorkspaceAgentLogs = ( + agentId: string, + { after, onMessage, onDone, onError }: WatchWorkspaceAgentLogsOptions, +) => { + // WebSocket compression in Safari (confirmed in 16.5) is broken when + // the server sends large messages. The following error is seen: + // + // WebSocket connection to 'wss://.../logs?follow&after=0' failed: The operation couldn’t be completed. Protocol error + // + const noCompression = + userAgentParser(navigator.userAgent).browser.name === 'Safari' + ? '&no_compression' + : ''; + + const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; + const socket = new WebSocket( + `${proto}//${location.host}/api/v2/workspaceagents/${agentId}/logs?follow&after=${after}${noCompression}`, + ); + socket.binaryType = 'blob'; + + socket.addEventListener('message', event => { + const logs = JSON.parse(event.data) as TypesGen.WorkspaceAgentLog[]; + onMessage(logs); + }); + + socket.addEventListener('error', () => { + onError(new Error('socket errored')); + }); + + socket.addEventListener('close', () => { + onDone?.(); + }); + + return socket; +}; + +type WatchWorkspaceAgentLogsOptions = { + after: number; + onMessage: (logs: TypesGen.WorkspaceAgentLog[]) => void; + onDone?: () => void; + onError: (error: Error) => void; +}; + +type WatchBuildLogsByBuildIdOptions = { + after?: number; + onMessage: (log: TypesGen.ProvisionerJobLog) => void; + onDone?: () => void; + onError?: (error: Error) => void; +}; +export const watchBuildLogsByBuildId = ( + buildId: string, + { onMessage, onDone, onError, after }: WatchBuildLogsByBuildIdOptions, +) => { + const searchParams = new URLSearchParams({ follow: 'true' }); + if (after !== undefined) { + searchParams.append('after', after.toString()); + } + const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; + const socket = new WebSocket( + `${proto}//${ + location.host + }/api/v2/workspacebuilds/${buildId}/logs?${searchParams.toString()}`, + ); + socket.binaryType = 'blob'; + + socket.addEventListener('message', event => + onMessage(JSON.parse(event.data) as TypesGen.ProvisionerJobLog), + ); + + socket.addEventListener('error', () => { + onError?.(new Error('Connection for logs failed.')); + socket.close(); + }); + + socket.addEventListener('close', () => { + // When the socket closes, logs have finished streaming! + onDone?.(); + }); + + return socket; +}; + +// This is the base header that is used for several requests. This is defined as +// a readonly value, but only copies of it should be passed into the API calls, +// because Axios is able to mutate the headers +const BASE_CONTENT_TYPE_JSON = { + 'Content-Type': 'application/json', +} as const satisfies HeadersInit; + +type TemplateOptions = Readonly<{ + readonly deprecated?: boolean; +}>; + +type SearchParamOptions = TypesGen.Pagination & { + q?: string; +}; + +type RestartWorkspaceParameters = Readonly<{ + workspace: TypesGen.Workspace; + buildParameters?: TypesGen.WorkspaceBuildParameter[]; +}>; + +export type DeleteWorkspaceOptions = Pick< + TypesGen.CreateWorkspaceBuildRequest, + 'log_level' & 'orphan' +>; + +type Claims = { + license_expires: number; + account_type?: string; + account_id?: string; + trial: boolean; + all_features: boolean; + version: number; + features: Record; + require_telemetry?: boolean; +}; + +export type GetLicensesResponse = Omit & { + claims: Claims; + expires_at: string; +}; + +export type InsightsParams = { + start_time: string; + end_time: string; + template_ids: string; +}; + +export type InsightsTemplateParams = InsightsParams & { + interval: 'day' | 'week'; +}; + +export type GetJFrogXRayScanParams = { + workspaceId: string; + agentId: string; +}; + +export class MissingBuildParameters extends Error { + parameters: TypesGen.TemplateVersionParameter[] = []; + versionId: string; + + constructor( + parameters: TypesGen.TemplateVersionParameter[], + versionId: string, + ) { + super('Missing build parameters.'); + this.parameters = parameters; + this.versionId = versionId; + } +} + +/** + * This is the container for all API methods. It's split off to make it more + * clear where API methods should go, but it is eventually merged into the Api + * class with a more flat hierarchy + * + * All public methods should be defined as arrow functions to ensure that they + * can be passed around the React UI without losing their `this` context. + * + * This is one of the few cases where you have to worry about the difference + * between traditional methods and arrow function properties. Arrow functions + * disable JS's dynamic scope, and force all `this` references to resolve via + * lexical scope. + */ +class ApiMethods { + constructor(protected readonly axios: AxiosInstance) {} + + login = async ( + email: string, + password: string, + ): Promise => { + const payload = JSON.stringify({ email, password }); + const response = await this.axios.post( + '/api/v2/users/login', + payload, + { headers: { ...BASE_CONTENT_TYPE_JSON } }, + ); + + return response.data; + }; + + convertToOAUTH = async (request: TypesGen.ConvertLoginRequest) => { + const response = await this.axios.post( + '/api/v2/users/me/convert-login', + request, + ); + + return response.data; + }; + + logout = async (): Promise => { + return this.axios.post('/api/v2/users/logout'); + }; + + getAuthenticatedUser = async () => { + const response = await this.axios.get('/api/v2/users/me'); + return response.data; + }; + + getUserParameters = async (templateID: string) => { + const response = await this.axios.get( + `/api/v2/users/me/autofill-parameters?template_id=${templateID}`, + ); + + return response.data; + }; + + getAuthMethods = async (): Promise => { + const response = await this.axios.get( + '/api/v2/users/authmethods', + ); + + return response.data; + }; + + getUserLoginType = async (): Promise => { + const response = await this.axios.get( + '/api/v2/users/me/login-type', + ); + + return response.data; + }; + + checkAuthorization = async ( + params: TypesGen.AuthorizationRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/authcheck`, + params, + ); + + return response.data; + }; + + getApiKey = async (): Promise => { + const response = await this.axios.post( + '/api/v2/users/me/keys', + ); + + return response.data; + }; + + getTokens = async ( + params: TypesGen.TokensFilter, + ): Promise => { + const response = await this.axios.get( + `/api/v2/users/me/keys/tokens`, + { params }, + ); + + return response.data; + }; + + deleteToken = async (keyId: string): Promise => { + await this.axios.delete(`/api/v2/users/me/keys/${keyId}`); + }; + + createToken = async ( + params: TypesGen.CreateTokenRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/users/me/keys/tokens`, + params, + ); + + return response.data; + }; + + getTokenConfig = async (): Promise => { + const response = await this.axios.get( + '/api/v2/users/me/keys/tokens/tokenconfig', + ); + + return response.data; + }; + + getUsers = async ( + options: TypesGen.UsersRequest, + signal?: AbortSignal, + ): Promise => { + const url = getURLWithSearchParams('/api/v2/users', options); + const response = await this.axios.get( + url.toString(), + { signal }, + ); + + return response.data; + }; + + getOrganization = async ( + organizationId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/organizations/${organizationId}`, + ); + + return response.data; + }; + + getOrganizations = async (): Promise => { + const response = await this.axios.get( + '/api/v2/users/me/organizations', + ); + return response.data; + }; + + getTemplate = async (templateId: string): Promise => { + const response = await this.axios.get( + `/api/v2/templates/${templateId}`, + ); + + return response.data; + }; + + getTemplates = async ( + organizationId: string, + options?: TemplateOptions, + ): Promise => { + const params: Record = {}; + if (options?.deprecated !== undefined) { + // Just want to check if it isn't undefined. If it has + // a boolean value, convert it to a string and include + // it as a param. + params.deprecated = String(options.deprecated); + } + + const response = await this.axios.get( + `/api/v2/organizations/${organizationId}/templates`, + { params }, + ); + + return response.data; + }; + + getTemplateByName = async ( + organizationId: string, + name: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/organizations/${organizationId}/templates/${name}`, + ); + + return response.data; + }; + + getTemplateVersion = async ( + versionId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/templateversions/${versionId}`, + ); + + return response.data; + }; + + getTemplateVersionResources = async ( + versionId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/templateversions/${versionId}/resources`, + ); + + return response.data; + }; + + getTemplateVersionVariables = async ( + versionId: string, + ): Promise => { + // Defined as separate variable to avoid wonky Prettier formatting because + // the type definition is so long + type VerArray = TypesGen.TemplateVersionVariable[]; + + const response = await this.axios.get( + `/api/v2/templateversions/${versionId}/variables`, + ); + + return response.data; + }; + + getTemplateVersions = async ( + templateId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/templates/${templateId}/versions`, + ); + return response.data; + }; + + getTemplateVersionByName = async ( + organizationId: string, + templateName: string, + versionName: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/organizations/${organizationId}/templates/${templateName}/versions/${versionName}`, + ); + + return response.data; + }; + + getPreviousTemplateVersionByName = async ( + organizationId: string, + templateName: string, + versionName: string, + ) => { + try { + const response = await this.axios.get( + `/api/v2/organizations/${organizationId}/templates/${templateName}/versions/${versionName}/previous`, + ); + + return response.data; + } catch (error) { + // When there is no previous version, like the first version of a + // template, the API returns 404 so in this case we can safely return + // undefined + const is404 = + isAxiosError(error) && error.response && error.response.status === 404; + + if (is404) { + return undefined; + } + + throw error; + } + }; + + createTemplateVersion = async ( + organizationId: string, + data: TypesGen.CreateTemplateVersionRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/organizations/${organizationId}/templateversions`, + data, + ); + + return response.data; + }; + + getTemplateVersionExternalAuth = async ( + versionId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/templateversions/${versionId}/external-auth`, + ); + + return response.data; + }; + + getTemplateVersionRichParameters = async ( + versionId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/templateversions/${versionId}/rich-parameters`, + ); + return response.data; + }; + + createTemplate = async ( + organizationId: string, + data: TypesGen.CreateTemplateRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/organizations/${organizationId}/templates`, + data, + ); + + return response.data; + }; + + updateActiveTemplateVersion = async ( + templateId: string, + data: TypesGen.UpdateActiveTemplateVersion, + ) => { + const response = await this.axios.patch( + `/api/v2/templates/${templateId}/versions`, + data, + ); + return response.data; + }; + + patchTemplateVersion = async ( + templateVersionId: string, + data: TypesGen.PatchTemplateVersionRequest, + ) => { + const response = await this.axios.patch( + `/api/v2/templateversions/${templateVersionId}`, + data, + ); + + return response.data; + }; + + archiveTemplateVersion = async (templateVersionId: string) => { + const response = await this.axios.post( + `/api/v2/templateversions/${templateVersionId}/archive`, + ); + + return response.data; + }; + + unarchiveTemplateVersion = async (templateVersionId: string) => { + const response = await this.axios.post( + `/api/v2/templateversions/${templateVersionId}/unarchive`, + ); + return response.data; + }; + + updateTemplateMeta = async ( + templateId: string, + data: TypesGen.UpdateTemplateMeta, + ): Promise => { + const response = await this.axios.patch( + `/api/v2/templates/${templateId}`, + data, + ); + + // On 304 response there is no data payload. + if (response.status === 304) { + return null; + } + + return response.data; + }; + + deleteTemplate = async (templateId: string): Promise => { + const response = await this.axios.delete( + `/api/v2/templates/${templateId}`, + ); + + return response.data; + }; + + getWorkspace = async ( + workspaceId: string, + params?: TypesGen.WorkspaceOptions, + ): Promise => { + const response = await this.axios.get( + `/api/v2/workspaces/${workspaceId}`, + { params }, + ); + + return response.data; + }; + + getWorkspaces = async ( + options: TypesGen.WorkspacesRequest, + ): Promise => { + const url = getURLWithSearchParams('/api/v2/workspaces', options); + const response = await this.axios.get(url); + return response.data; + }; + + getWorkspaceByOwnerAndName = async ( + username = 'me', + workspaceName: string, + params?: TypesGen.WorkspaceOptions, + ): Promise => { + const response = await this.axios.get( + `/api/v2/users/${username}/workspace/${workspaceName}`, + { params }, + ); + + return response.data; + }; + + getWorkspaceBuildByNumber = async ( + username = 'me', + workspaceName: string, + buildNumber: number, + ): Promise => { + const response = await this.axios.get( + `/api/v2/users/${username}/workspace/${workspaceName}/builds/${buildNumber}`, + ); + + return response.data; + }; + + waitForBuild = (build: TypesGen.WorkspaceBuild) => { + return new Promise((res, reject) => { + void (async () => { + let latestJobInfo: TypesGen.ProvisionerJob | undefined = undefined; + + while ( + // eslint-disable-next-line no-loop-func -- Not great, but should be harmless + !['succeeded', 'canceled'].some(status => + latestJobInfo?.status.includes(status), + ) + ) { + const { job } = await this.getWorkspaceBuildByNumber( + build.workspace_owner_name, + build.workspace_name, + build.build_number, + ); + + latestJobInfo = job; + if (latestJobInfo.status === 'failed') { + return reject(latestJobInfo); + } + + await delay(1000); + } + + return res(latestJobInfo); + })(); + }); + }; + + postWorkspaceBuild = async ( + workspaceId: string, + data: TypesGen.CreateWorkspaceBuildRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/workspaces/${workspaceId}/builds`, + data, + ); + + return response.data; + }; + + startWorkspace = ( + workspaceId: string, + templateVersionId: string, + logLevel?: TypesGen.ProvisionerLogLevel, + buildParameters?: TypesGen.WorkspaceBuildParameter[], + ) => { + return this.postWorkspaceBuild(workspaceId, { + transition: 'start', + template_version_id: templateVersionId, + log_level: logLevel, + rich_parameter_values: buildParameters, + }); + }; + + stopWorkspace = ( + workspaceId: string, + logLevel?: TypesGen.ProvisionerLogLevel, + ) => { + return this.postWorkspaceBuild(workspaceId, { + transition: 'stop', + log_level: logLevel, + }); + }; + + deleteWorkspace = (workspaceId: string, options?: DeleteWorkspaceOptions) => { + return this.postWorkspaceBuild(workspaceId, { + transition: 'delete', + ...options, + }); + }; + + cancelWorkspaceBuild = async ( + workspaceBuildId: TypesGen.WorkspaceBuild['id'], + ): Promise => { + const response = await this.axios.patch( + `/api/v2/workspacebuilds/${workspaceBuildId}/cancel`, + ); + + return response.data; + }; + + updateWorkspaceDormancy = async ( + workspaceId: string, + dormant: boolean, + ): Promise => { + const data: TypesGen.UpdateWorkspaceDormancy = { dormant }; + const response = await this.axios.put( + `/api/v2/workspaces/${workspaceId}/dormant`, + data, + ); + + return response.data; + }; + + updateWorkspaceAutomaticUpdates = async ( + workspaceId: string, + automaticUpdates: TypesGen.AutomaticUpdates, + ): Promise => { + const req: TypesGen.UpdateWorkspaceAutomaticUpdatesRequest = { + automatic_updates: automaticUpdates, + }; + + const response = await this.axios.put( + `/api/v2/workspaces/${workspaceId}/autoupdates`, + req, + ); + + return response.data; + }; + + restartWorkspace = async ({ + workspace, + buildParameters, + }: RestartWorkspaceParameters): Promise => { + const stopBuild = await this.stopWorkspace(workspace.id); + const awaitedStopBuild = await this.waitForBuild(stopBuild); + + // If the restart is canceled halfway through, make sure we bail + if (awaitedStopBuild?.status === 'canceled') { + return; + } + + const startBuild = await this.startWorkspace( + workspace.id, + workspace.latest_build.template_version_id, + undefined, + buildParameters, + ); + + await this.waitForBuild(startBuild); + }; + + cancelTemplateVersionBuild = async ( + templateVersionId: TypesGen.TemplateVersion['id'], + ): Promise => { + const response = await this.axios.patch( + `/api/v2/templateversions/${templateVersionId}/cancel`, + ); + + return response.data; + }; + + createUser = async ( + user: TypesGen.CreateUserRequest, + ): Promise => { + const response = await this.axios.post( + '/api/v2/users', + user, + ); + + return response.data; + }; + + createWorkspace = async ( + organizationId: string, + userId = 'me', + workspace: TypesGen.CreateWorkspaceRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/organizations/${organizationId}/members/${userId}/workspaces`, + workspace, + ); + + return response.data; + }; + + patchWorkspace = async ( + workspaceId: string, + data: TypesGen.UpdateWorkspaceRequest, + ): Promise => { + await this.axios.patch(`/api/v2/workspaces/${workspaceId}`, data); + }; + + getBuildInfo = async (): Promise => { + const response = await this.axios.get('/api/v2/buildinfo'); + return response.data; + }; + + getUpdateCheck = async (): Promise => { + const response = await this.axios.get('/api/v2/updatecheck'); + return response.data; + }; + + putWorkspaceAutostart = async ( + workspaceID: string, + autostart: TypesGen.UpdateWorkspaceAutostartRequest, + ): Promise => { + const payload = JSON.stringify(autostart); + await this.axios.put( + `/api/v2/workspaces/${workspaceID}/autostart`, + payload, + { headers: { ...BASE_CONTENT_TYPE_JSON } }, + ); + }; + + putWorkspaceAutostop = async ( + workspaceID: string, + ttl: TypesGen.UpdateWorkspaceTTLRequest, + ): Promise => { + const payload = JSON.stringify(ttl); + await this.axios.put(`/api/v2/workspaces/${workspaceID}/ttl`, payload, { + headers: { ...BASE_CONTENT_TYPE_JSON }, + }); + }; + + updateProfile = async ( + userId: string, + data: TypesGen.UpdateUserProfileRequest, + ): Promise => { + const response = await this.axios.put( + `/api/v2/users/${userId}/profile`, + data, + ); + return response.data; + }; + + updateAppearanceSettings = async ( + userId: string, + data: TypesGen.UpdateUserAppearanceSettingsRequest, + ): Promise => { + const response = await this.axios.put( + `/api/v2/users/${userId}/appearance`, + data, + ); + return response.data; + }; + + getUserQuietHoursSchedule = async ( + userId: TypesGen.User['id'], + ): Promise => { + const response = await this.axios.get( + `/api/v2/users/${userId}/quiet-hours`, + ); + return response.data; + }; + + updateUserQuietHoursSchedule = async ( + userId: TypesGen.User['id'], + data: TypesGen.UpdateUserQuietHoursScheduleRequest, + ): Promise => { + const response = await this.axios.put( + `/api/v2/users/${userId}/quiet-hours`, + data, + ); + + return response.data; + }; + + activateUser = async ( + userId: TypesGen.User['id'], + ): Promise => { + const response = await this.axios.put( + `/api/v2/users/${userId}/status/activate`, + ); + return response.data; + }; + + suspendUser = async (userId: TypesGen.User['id']): Promise => { + const response = await this.axios.put( + `/api/v2/users/${userId}/status/suspend`, + ); + + return response.data; + }; + + deleteUser = async (userId: TypesGen.User['id']): Promise => { + await this.axios.delete(`/api/v2/users/${userId}`); + }; + + // API definition: + // https://github.com/coder/coder/blob/db665e7261f3c24a272ccec48233a3e276878239/coderd/users.go#L33-L53 + hasFirstUser = async (): Promise => { + try { + // If it is success, it is true + await this.axios.get('/api/v2/users/first'); + return true; + } catch (error) { + // If it returns a 404, it is false + if (isAxiosError(error) && error.response?.status === 404) { + return false; + } + + throw error; + } + }; + + createFirstUser = async ( + req: TypesGen.CreateFirstUserRequest, + ): Promise => { + const response = await this.axios.post(`/api/v2/users/first`, req); + return response.data; + }; + + updateUserPassword = async ( + userId: TypesGen.User['id'], + updatePassword: TypesGen.UpdateUserPasswordRequest, + ): Promise => { + await this.axios.put(`/api/v2/users/${userId}/password`, updatePassword); + }; + + getRoles = async (): Promise> => { + const response = await this.axios.get( + `/api/v2/users/roles`, + ); + + return response.data; + }; + + updateUserRoles = async ( + roles: TypesGen.SlimRole['name'][], + userId: TypesGen.User['id'], + ): Promise => { + const response = await this.axios.put( + `/api/v2/users/${userId}/roles`, + { roles }, + ); + + return response.data; + }; + + getUserSSHKey = async (userId = 'me'): Promise => { + const response = await this.axios.get( + `/api/v2/users/${userId}/gitsshkey`, + ); + + return response.data; + }; + + regenerateUserSSHKey = async (userId = 'me'): Promise => { + const response = await this.axios.put( + `/api/v2/users/${userId}/gitsshkey`, + ); + + return response.data; + }; + + getWorkspaceBuilds = async ( + workspaceId: string, + req?: TypesGen.WorkspaceBuildsRequest, + ) => { + const response = await this.axios.get( + getURLWithSearchParams(`/api/v2/workspaces/${workspaceId}/builds`, req), + ); + + return response.data; + }; + + getWorkspaceBuildLogs = async ( + buildId: string, + before: Date, + ): Promise => { + const response = await this.axios.get( + `/api/v2/workspacebuilds/${buildId}/logs?before=${before.getTime()}`, + ); + + return response.data; + }; + + getWorkspaceAgentLogs = async ( + agentID: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/workspaceagents/${agentID}/logs`, + ); + + return response.data; + }; + + putWorkspaceExtension = async ( + workspaceId: string, + newDeadline: dayjs.Dayjs, + ): Promise => { + await this.axios.put(`/api/v2/workspaces/${workspaceId}/extend`, { + deadline: newDeadline, + }); + }; + + refreshEntitlements = async (): Promise => { + await this.axios.post('/api/v2/licenses/refresh-entitlements'); + }; + + getEntitlements = async (): Promise => { + try { + const response = await this.axios.get( + '/api/v2/entitlements', + ); + + return response.data; + } catch (ex) { + if (isAxiosError(ex) && ex.response?.status === 404) { + return { + errors: [], + features: withDefaultFeatures({}), + has_license: false, + require_telemetry: false, + trial: false, + warnings: [], + refreshed_at: '', + }; + } + throw ex; + } + }; + + getExperiments = async (): Promise => { + try { + const response = await this.axios.get( + '/api/v2/experiments', + ); + + return response.data; + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + return []; + } + + throw error; + } + }; + + getAvailableExperiments = + async (): Promise => { + try { + const response = await this.axios.get('/api/v2/experiments/available'); + + return response.data; + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + return { safe: [] }; + } + throw error; + } + }; + + getExternalAuthProvider = async ( + provider: string, + ): Promise => { + const res = await this.axios.get(`/api/v2/external-auth/${provider}`); + return res.data; + }; + + getExternalAuthDevice = async ( + provider: string, + ): Promise => { + const resp = await this.axios.get( + `/api/v2/external-auth/${provider}/device`, + ); + return resp.data; + }; + + exchangeExternalAuthDevice = async ( + provider: string, + req: TypesGen.ExternalAuthDeviceExchange, + ): Promise => { + const resp = await this.axios.post( + `/api/v2/external-auth/${provider}/device`, + req, + ); + + return resp.data; + }; + + getUserExternalAuthProviders = + async (): Promise => { + const resp = await this.axios.get(`/api/v2/external-auth`); + return resp.data; + }; + + unlinkExternalAuthProvider = async (provider: string): Promise => { + const resp = await this.axios.delete(`/api/v2/external-auth/${provider}`); + return resp.data; + }; + + getOAuth2ProviderApps = async ( + filter?: TypesGen.OAuth2ProviderAppFilter, + ): Promise => { + const params = filter?.user_id + ? new URLSearchParams({ user_id: filter.user_id }).toString() + : ''; + + const resp = await this.axios.get(`/api/v2/oauth2-provider/apps?${params}`); + return resp.data; + }; + + getOAuth2ProviderApp = async ( + id: string, + ): Promise => { + const resp = await this.axios.get(`/api/v2/oauth2-provider/apps/${id}`); + return resp.data; + }; + + postOAuth2ProviderApp = async ( + data: TypesGen.PostOAuth2ProviderAppRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/oauth2-provider/apps`, + data, + ); + return response.data; + }; + + putOAuth2ProviderApp = async ( + id: string, + data: TypesGen.PutOAuth2ProviderAppRequest, + ): Promise => { + const response = await this.axios.put( + `/api/v2/oauth2-provider/apps/${id}`, + data, + ); + return response.data; + }; + + deleteOAuth2ProviderApp = async (id: string): Promise => { + await this.axios.delete(`/api/v2/oauth2-provider/apps/${id}`); + }; + + getOAuth2ProviderAppSecrets = async ( + id: string, + ): Promise => { + const resp = await this.axios.get( + `/api/v2/oauth2-provider/apps/${id}/secrets`, + ); + return resp.data; + }; + + postOAuth2ProviderAppSecret = async ( + id: string, + ): Promise => { + const resp = await this.axios.post( + `/api/v2/oauth2-provider/apps/${id}/secrets`, + ); + return resp.data; + }; + + deleteOAuth2ProviderAppSecret = async ( + appId: string, + secretId: string, + ): Promise => { + await this.axios.delete( + `/api/v2/oauth2-provider/apps/${appId}/secrets/${secretId}`, + ); + }; + + revokeOAuth2ProviderApp = async (appId: string): Promise => { + await this.axios.delete(`/oauth2/tokens?client_id=${appId}`); + }; + + getAuditLogs = async ( + options: TypesGen.AuditLogsRequest, + ): Promise => { + const url = getURLWithSearchParams('/api/v2/audit', options); + const response = await this.axios.get(url); + return response.data; + }; + + getTemplateDAUs = async ( + templateId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/templates/${templateId}/daus`, + ); + + return response.data; + }; + + getDeploymentDAUs = async ( + // Default to user's local timezone. + // As /api/v2/insights/daus only accepts whole-number values for tz_offset + // we truncate the tz offset down to the closest hour. + offset = Math.trunc(new Date().getTimezoneOffset() / 60), + ): Promise => { + const response = await this.axios.get( + `/api/v2/insights/daus?tz_offset=${offset}`, + ); + + return response.data; + }; + + getTemplateACLAvailable = async ( + templateId: string, + options: TypesGen.UsersRequest, + ): Promise => { + const url = getURLWithSearchParams( + `/api/v2/templates/${templateId}/acl/available`, + options, + ).toString(); + + const response = await this.axios.get(url); + return response.data; + }; + + getTemplateACL = async ( + templateId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/templates/${templateId}/acl`, + ); + + return response.data; + }; + + updateTemplateACL = async ( + templateId: string, + data: TypesGen.UpdateTemplateACL, + ): Promise<{ message: string }> => { + const response = await this.axios.patch( + `/api/v2/templates/${templateId}/acl`, + data, + ); + + return response.data; + }; + + getApplicationsHost = async (): Promise => { + const response = await this.axios.get(`/api/v2/applications/host`); + return response.data; + }; + + getGroups = async (organizationId: string): Promise => { + const response = await this.axios.get( + `/api/v2/organizations/${organizationId}/groups`, + ); + + return response.data; + }; + + createGroup = async ( + organizationId: string, + data: TypesGen.CreateGroupRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/organizations/${organizationId}/groups`, + data, + ); + return response.data; + }; + + getGroup = async (groupId: string): Promise => { + const response = await this.axios.get(`/api/v2/groups/${groupId}`); + return response.data; + }; + + patchGroup = async ( + groupId: string, + data: TypesGen.PatchGroupRequest, + ): Promise => { + const response = await this.axios.patch(`/api/v2/groups/${groupId}`, data); + return response.data; + }; + + addMember = async (groupId: string, userId: string) => { + return this.patchGroup(groupId, { + name: '', + add_users: [userId], + remove_users: [], + }); + }; + + removeMember = async (groupId: string, userId: string) => { + return this.patchGroup(groupId, { + name: '', + display_name: '', + add_users: [], + remove_users: [userId], + }); + }; + + deleteGroup = async (groupId: string): Promise => { + await this.axios.delete(`/api/v2/groups/${groupId}`); + }; + + getWorkspaceQuota = async ( + username: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/workspace-quota/${encodeURIComponent(username)}`, + ); + return response.data; + }; + + getAgentListeningPorts = async ( + agentID: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/workspaceagents/${agentID}/listening-ports`, + ); + return response.data; + }; + + getWorkspaceAgentSharedPorts = async ( + workspaceID: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/workspaces/${workspaceID}/port-share`, + ); + return response.data; + }; + + upsertWorkspaceAgentSharedPort = async ( + workspaceID: string, + req: TypesGen.UpsertWorkspaceAgentPortShareRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/workspaces/${workspaceID}/port-share`, + req, + ); + return response.data; + }; + + deleteWorkspaceAgentSharedPort = async ( + workspaceID: string, + req: TypesGen.DeleteWorkspaceAgentPortShareRequest, + ): Promise => { + const response = await this.axios.delete( + `/api/v2/workspaces/${workspaceID}/port-share`, + { data: req }, + ); + + return response.data; + }; + + // getDeploymentSSHConfig is used by the VSCode-Extension. + getDeploymentSSHConfig = async (): Promise => { + const response = await this.axios.get(`/api/v2/deployment/ssh`); + return response.data; + }; + + getDeploymentConfig = async (): Promise => { + const response = await this.axios.get(`/api/v2/deployment/config`); + return response.data; + }; + + getDeploymentStats = async (): Promise => { + const response = await this.axios.get(`/api/v2/deployment/stats`); + return response.data; + }; + + getReplicas = async (): Promise => { + const response = await this.axios.get(`/api/v2/replicas`); + return response.data; + }; + + getFile = async (fileId: string): Promise => { + const response = await this.axios.get( + `/api/v2/files/${fileId}`, + { responseType: 'arraybuffer' }, + ); + + return response.data; + }; + + getWorkspaceProxyRegions = async (): Promise< + TypesGen.RegionsResponse + > => { + const response = await this.axios.get< + TypesGen.RegionsResponse + >(`/api/v2/regions`); + + return response.data; + }; + + getWorkspaceProxies = async (): Promise< + TypesGen.RegionsResponse + > => { + const response = await this.axios.get< + TypesGen.RegionsResponse + >(`/api/v2/workspaceproxies`); + + return response.data; + }; + + createWorkspaceProxy = async ( + b: TypesGen.CreateWorkspaceProxyRequest, + ): Promise => { + const response = await this.axios.post(`/api/v2/workspaceproxies`, b); + return response.data; + }; + + getAppearance = async (): Promise => { + try { + const response = await this.axios.get(`/api/v2/appearance`); + return response.data || {}; + } catch (ex) { + if (isAxiosError(ex) && ex.response?.status === 404) { + return { + application_name: '', + logo_url: '', + notification_banners: [], + service_banner: { + enabled: false, + }, + }; + } + + throw ex; + } + }; + + updateAppearance = async ( + b: TypesGen.AppearanceConfig, + ): Promise => { + const response = await this.axios.put(`/api/v2/appearance`, b); + return response.data; + }; + + getTemplateExamples = async ( + organizationId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/organizations/${organizationId}/templates/examples`, + ); + + return response.data; + }; + + uploadFile = async (file: File): Promise => { + const response = await this.axios.post('/api/v2/files', file, { + headers: { 'Content-Type': 'application/x-tar' }, + }); + + return response.data; + }; + + getTemplateVersionLogs = async ( + versionId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/templateversions/${versionId}/logs`, + ); + return response.data; + }; + + updateWorkspaceVersion = async ( + workspace: TypesGen.Workspace, + ): Promise => { + const template = await this.getTemplate(workspace.template_id); + return this.startWorkspace(workspace.id, template.active_version_id); + }; + + getWorkspaceBuildParameters = async ( + workspaceBuildId: TypesGen.WorkspaceBuild['id'], + ): Promise => { + const response = await this.axios.get( + `/api/v2/workspacebuilds/${workspaceBuildId}/parameters`, + ); + + return response.data; + }; + + getLicenses = async (): Promise => { + const response = await this.axios.get(`/api/v2/licenses`); + return response.data; + }; + + createLicense = async ( + data: TypesGen.AddLicenseRequest, + ): Promise => { + const response = await this.axios.post(`/api/v2/licenses`, data); + return response.data; + }; + + removeLicense = async (licenseId: number): Promise => { + await this.axios.delete(`/api/v2/licenses/${licenseId}`); + }; + + /** Steps to change the workspace version + * - Get the latest template to access the latest active version + * - Get the current build parameters + * - Get the template parameters + * - Update the build parameters and check if there are missed parameters for + * the new version + * - If there are missing parameters raise an error + * - Create a build with the version and updated build parameters + */ + changeWorkspaceVersion = async ( + workspace: TypesGen.Workspace, + templateVersionId: string, + newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], + ): Promise => { + const [currentBuildParameters, templateParameters] = await Promise.all([ + this.getWorkspaceBuildParameters(workspace.latest_build.id), + this.getTemplateVersionRichParameters(templateVersionId), + ]); + + const missingParameters = getMissingParameters( + currentBuildParameters, + newBuildParameters, + templateParameters, + ); + + if (missingParameters.length > 0) { + throw new MissingBuildParameters(missingParameters, templateVersionId); + } + + return this.postWorkspaceBuild(workspace.id, { + transition: 'start', + template_version_id: templateVersionId, + rich_parameter_values: newBuildParameters, + }); + }; + + /** Steps to update the workspace + * - Get the latest template to access the latest active version + * - Get the current build parameters + * - Get the template parameters + * - Update the build parameters and check if there are missed parameters for + * the newest version + * - If there are missing parameters raise an error + * - Create a build with the latest version and updated build parameters + */ + updateWorkspace = async ( + workspace: TypesGen.Workspace, + newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], + ): Promise => { + const [template, oldBuildParameters] = await Promise.all([ + this.getTemplate(workspace.template_id), + this.getWorkspaceBuildParameters(workspace.latest_build.id), + ]); + + const activeVersionId = template.active_version_id; + const templateParameters = await this.getTemplateVersionRichParameters( + activeVersionId, + ); + + const missingParameters = getMissingParameters( + oldBuildParameters, + newBuildParameters, + templateParameters, + ); + + if (missingParameters.length > 0) { + throw new MissingBuildParameters(missingParameters, activeVersionId); + } + + return this.postWorkspaceBuild(workspace.id, { + transition: 'start', + template_version_id: activeVersionId, + rich_parameter_values: newBuildParameters, + }); + }; + + getWorkspaceResolveAutostart = async ( + workspaceId: string, + ): Promise => { + const response = await this.axios.get( + `/api/v2/workspaces/${workspaceId}/resolve-autostart`, + ); + return response.data; + }; + + issueReconnectingPTYSignedToken = async ( + params: TypesGen.IssueReconnectingPTYSignedTokenRequest, + ): Promise => { + const response = await this.axios.post( + '/api/v2/applications/reconnecting-pty-signed-token', + params, + ); + + return response.data; + }; + + getWorkspaceParameters = async (workspace: TypesGen.Workspace) => { + const latestBuild = workspace.latest_build; + const [templateVersionRichParameters, buildParameters] = await Promise.all([ + this.getTemplateVersionRichParameters(latestBuild.template_version_id), + this.getWorkspaceBuildParameters(latestBuild.id), + ]); + + return { + templateVersionRichParameters, + buildParameters, + }; + }; + + getInsightsUserLatency = async ( + filters: InsightsParams, + ): Promise => { + const params = new URLSearchParams(filters); + const response = await this.axios.get( + `/api/v2/insights/user-latency?${params}`, + ); + + return response.data; + }; + + getInsightsUserActivity = async ( + filters: InsightsParams, + ): Promise => { + const params = new URLSearchParams(filters); + const response = await this.axios.get( + `/api/v2/insights/user-activity?${params}`, + ); + + return response.data; + }; + + getInsightsTemplate = async ( + params: InsightsTemplateParams, + ): Promise => { + const searchParams = new URLSearchParams(params); + const response = await this.axios.get( + `/api/v2/insights/templates?${searchParams}`, + ); + + return response.data; + }; + + getHealth = async (force: boolean = false) => { + const params = new URLSearchParams({ force: force.toString() }); + const response = await this.axios.get( + `/api/v2/debug/health?${params}`, + ); + return response.data; + }; + + getHealthSettings = async (): Promise => { + const res = await this.axios.get( + `/api/v2/debug/health/settings`, + ); + + return res.data; + }; + + updateHealthSettings = async (data: TypesGen.UpdateHealthSettings) => { + const response = await this.axios.put( + `/api/v2/debug/health/settings`, + data, + ); + + return response.data; + }; + + putFavoriteWorkspace = async (workspaceID: string) => { + await this.axios.put(`/api/v2/workspaces/${workspaceID}/favorite`); + }; + + deleteFavoriteWorkspace = async (workspaceID: string) => { + await this.axios.delete(`/api/v2/workspaces/${workspaceID}/favorite`); + }; + + getJFrogXRayScan = async (options: GetJFrogXRayScanParams) => { + const searchParams = new URLSearchParams({ + workspace_id: options.workspaceId, + agent_id: options.agentId, + }); + + try { + const res = await this.axios.get( + `/api/v2/integrations/jfrog/xray-scan?${searchParams}`, + ); + + return res.data; + } catch (error) { + if (isAxiosError(error) && error.response?.status === 404) { + // react-query library does not allow undefined to be returned as a + // query result + return null; + } + + throw error; + } + }; +} + +// This is a hard coded CSRF token/cookie pair for local development. In prod, +// the GoLang webserver generates a random cookie with a new token for each +// document request. For local development, we don't use the Go webserver for +// static files, so this is the 'hack' to make local development work with +// remote apis. The CSRF cookie for this token is "JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4=" +const csrfToken = + 'KNKvagCBEHZK7ihe2t7fj6VeJ0UyTDco1yVUJE8N06oNqxLu5Zx1vRxZbgfC0mJJgeGkVjgs08mgPbcWPBkZ1A=='; + +// Always attach CSRF token to all requests. In puppeteer the document is +// undefined. In those cases, just do nothing. +const tokenMetadataElement = + typeof document !== 'undefined' + ? document.head.querySelector('meta[property="csrf-token"]') + : null; + +function getConfiguredAxiosInstance(): AxiosInstance { + const instance = globalAxios.create(); + + // Adds 304 for the default axios validateStatus function + // https://github.com/axios/axios#handling-errors Check status here + // https://httpstatusdogs.com/ + instance.defaults.validateStatus = status => { + return (status >= 200 && status < 300) || status === 304; + }; + + const metadataIsAvailable = + tokenMetadataElement !== null && + tokenMetadataElement.getAttribute('content') !== null; + + if (metadataIsAvailable) { + if (process.env.NODE_ENV === 'development') { + // Development mode uses a hard-coded CSRF token + instance.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken; + instance.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken; + tokenMetadataElement.setAttribute('content', csrfToken); + } else { + instance.defaults.headers.common['X-CSRF-TOKEN'] = + tokenMetadataElement.getAttribute('content') ?? ''; + } + } else { + // Do not write error logs if we are in a FE unit test. + if (process.env.JEST_WORKER_ID === undefined) { + // eslint-disable-next-line no-console -- Function should never run in vendored version of SDK + console.error('CSRF token not found'); + } + } + + return instance; +} + +// Other non-API methods defined here to make it a little easier to find them. +interface ClientApi extends ApiMethods { + getCsrfToken: () => string; + setSessionToken: (token: string) => void; + setHost: (host: string | undefined) => void; + getAxiosInstance: () => AxiosInstance; +} + +export class Api extends ApiMethods implements ClientApi { + constructor() { + const scopedAxiosInstance = getConfiguredAxiosInstance(); + super(scopedAxiosInstance); + } + + // As with ApiMethods, all public methods should be defined with arrow + // function syntax to ensure they can be passed around the React UI without + // losing/detaching their `this` context! + + getCsrfToken = (): string => { + return csrfToken; + }; + + setSessionToken = (token: string): void => { + this.axios.defaults.headers.common['Coder-Session-Token'] = token; + }; + + setHost = (host: string | undefined): void => { + this.axios.defaults.baseURL = host; + }; + + getAxiosInstance = (): AxiosInstance => { + return this.axios; + }; +} + +export const API = new Api(); diff --git a/plugins/backstage-plugin-coder/src/api/vendoredSdk/api/errors.ts b/plugins/backstage-plugin-coder/src/api/vendoredSdk/api/errors.ts new file mode 100644 index 00000000..6d401a11 --- /dev/null +++ b/plugins/backstage-plugin-coder/src/api/vendoredSdk/api/errors.ts @@ -0,0 +1,124 @@ +import { type AxiosError, type AxiosResponse, isAxiosError } from 'axios'; + +const Language = { + errorsByCode: { + defaultErrorCode: 'Invalid value', + }, +}; + +export interface FieldError { + field: string; + detail: string; +} + +export type FieldErrors = Record; + +export interface ApiErrorResponse { + message: string; + detail?: string; + validations?: FieldError[]; +} + +export type ApiError = AxiosError & { + response: AxiosResponse; +}; + +export const isApiErrorResponse = (err: unknown): err is ApiErrorResponse => { + return ( + typeof err === 'object' && + err !== null && + 'message' in err && + typeof err.message === 'string' && + (!('detail' in err) || + err.detail === undefined || + typeof err.detail === 'string') && + (!('validations' in err) || + err.validations === undefined || + Array.isArray(err.validations)) + ); +}; + +export const isApiError = (err: unknown): err is ApiError => { + return ( + isAxiosError(err) && + err.response !== undefined && + isApiErrorResponse(err.response.data) + ); +}; + +export const hasApiFieldErrors = (error: ApiError): boolean => + Array.isArray(error.response.data.validations); + +export const isApiValidationError = (error: unknown): error is ApiError => { + return isApiError(error) && hasApiFieldErrors(error); +}; + +export const hasError = (error: unknown) => + error !== undefined && error !== null; + +export const mapApiErrorToFieldErrors = ( + apiErrorResponse: ApiErrorResponse, +): FieldErrors => { + const result: FieldErrors = {}; + + if (apiErrorResponse.validations) { + for (const error of apiErrorResponse.validations) { + result[error.field] = + error.detail || Language.errorsByCode.defaultErrorCode; + } + } + + return result; +}; + +/** + * + * @param error + * @param defaultMessage + * @returns error's message if ApiError or Error, else defaultMessage + */ +export const getErrorMessage = ( + error: unknown, + defaultMessage: string, +): string => { + // if error is API error + // 404s result in the default message being returned + if (isApiError(error) && error.response.data.message) { + return error.response.data.message; + } + if (isApiErrorResponse(error)) { + return error.message; + } + // if error is a non-empty string + if (error && typeof error === 'string') { + return error; + } + return defaultMessage; +}; + +/** + * + * @param error + * @returns a combined validation error message if the error is an ApiError + * and contains validation messages for different form fields. + */ +export const getValidationErrorMessage = (error: unknown): string => { + const validationErrors = + isApiError(error) && error.response.data.validations + ? error.response.data.validations + : []; + return validationErrors.map(error => error.detail).join('\n'); +}; + +export const getErrorDetail = (error: unknown): string | undefined | null => { + if (error instanceof Error) { + return 'Please check the developer console for more details.'; + } + if (isApiError(error)) { + return error.response.data.detail; + } + if (isApiErrorResponse(error)) { + return error.detail; + } + return null; +}; diff --git a/plugins/backstage-plugin-coder/src/api/vendoredSdk/api/typesGenerated.ts b/plugins/backstage-plugin-coder/src/api/vendoredSdk/api/typesGenerated.ts new file mode 100644 index 00000000..2e3b4f04 --- /dev/null +++ b/plugins/backstage-plugin-coder/src/api/vendoredSdk/api/typesGenerated.ts @@ -0,0 +1,2599 @@ +// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. + +// The code below is generated from codersdk. + +// From codersdk/templates.go +export interface ACLAvailable { + readonly users: readonly ReducedUser[]; + readonly groups: readonly Group[]; +} + +// From codersdk/apikey.go +export interface APIKey { + readonly id: string; + readonly user_id: string; + readonly last_used: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + readonly login_type: LoginType; + readonly scope: APIKeyScope; + readonly token_name: string; + readonly lifetime_seconds: number; +} + +// From codersdk/apikey.go +export interface APIKeyWithOwner extends APIKey { + readonly username: string; +} + +// From codersdk/licenses.go +export interface AddLicenseRequest { + readonly license: string; +} + +// From codersdk/templates.go +export interface AgentStatsReportResponse { + readonly num_comms: number; + readonly rx_bytes: number; + readonly tx_bytes: number; +} + +// From codersdk/deployment.go +export interface AppHostResponse { + readonly host: string; +} + +// From codersdk/deployment.go +export interface AppearanceConfig { + readonly application_name: string; + readonly logo_url: string; + readonly service_banner: BannerConfig; + readonly notification_banners: readonly BannerConfig[]; + readonly support_links?: readonly LinkConfig[]; +} + +// From codersdk/templates.go +export interface ArchiveTemplateVersionsRequest { + readonly all: boolean; +} + +// From codersdk/templates.go +export interface ArchiveTemplateVersionsResponse { + readonly template_id: string; + readonly archived_ids: readonly string[]; +} + +// From codersdk/roles.go +export interface AssignableRoles extends Role { + readonly assignable: boolean; + readonly built_in: boolean; +} + +// From codersdk/audit.go +export type AuditDiff = Record; + +// From codersdk/audit.go +export interface AuditDiffField { + // Empty interface{} type, cannot resolve the type. + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- interface{} + readonly old?: any; + // Empty interface{} type, cannot resolve the type. + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- interface{} + readonly new?: any; + readonly secret: boolean; +} + +// From codersdk/audit.go +export interface AuditLog { + readonly id: string; + readonly request_id: string; + readonly time: string; + readonly organization_id: string; + // Named type "net/netip.Addr" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type + readonly ip: any; + readonly user_agent: string; + readonly resource_type: ResourceType; + readonly resource_id: string; + readonly resource_target: string; + readonly resource_icon: string; + readonly action: AuditAction; + readonly diff: AuditDiff; + readonly status_code: number; + readonly additional_fields: Record; + readonly description: string; + readonly resource_link: string; + readonly is_deleted: boolean; + readonly user?: User; +} + +// From codersdk/audit.go +export interface AuditLogResponse { + readonly audit_logs: readonly AuditLog[]; + readonly count: number; +} + +// From codersdk/audit.go +export interface AuditLogsRequest extends Pagination { + readonly q?: string; +} + +// From codersdk/users.go +export interface AuthMethod { + readonly enabled: boolean; +} + +// From codersdk/users.go +export interface AuthMethods { + readonly terms_of_service_url?: string; + readonly password: AuthMethod; + readonly github: AuthMethod; + readonly oidc: OIDCAuthMethod; +} + +// From codersdk/authorization.go +export interface AuthorizationCheck { + readonly object: AuthorizationObject; + readonly action: RBACAction; +} + +// From codersdk/authorization.go +export interface AuthorizationObject { + readonly resource_type: RBACResource; + readonly owner_id?: string; + readonly organization_id?: string; + readonly resource_id?: string; +} + +// From codersdk/authorization.go +export interface AuthorizationRequest { + readonly checks: Record; +} + +// From codersdk/authorization.go +export type AuthorizationResponse = Record; + +// From codersdk/deployment.go +export interface AvailableExperiments { + readonly safe: readonly Experiment[]; +} + +// From codersdk/deployment.go +export interface BannerConfig { + readonly enabled: boolean; + readonly message?: string; + readonly background_color?: string; +} + +// From codersdk/deployment.go +export interface BuildInfoResponse { + readonly external_url: string; + readonly version: string; + readonly dashboard_url: string; + readonly workspace_proxy: boolean; + readonly agent_api_version: string; + readonly upgrade_message: string; + readonly deployment_id: string; +} + +// From codersdk/insights.go +export interface ConnectionLatency { + readonly p50: number; + readonly p95: number; +} + +// From codersdk/users.go +export interface ConvertLoginRequest { + readonly to_type: LoginType; + readonly password: string; +} + +// From codersdk/users.go +export interface CreateFirstUserRequest { + readonly email: string; + readonly username: string; + readonly password: string; + readonly trial: boolean; + readonly trial_info: CreateFirstUserTrialInfo; +} + +// From codersdk/users.go +export interface CreateFirstUserResponse { + readonly user_id: string; + readonly organization_id: string; +} + +// From codersdk/users.go +export interface CreateFirstUserTrialInfo { + readonly first_name: string; + readonly last_name: string; + readonly phone_number: string; + readonly job_title: string; + readonly company_name: string; + readonly country: string; + readonly developers: string; +} + +// From codersdk/groups.go +export interface CreateGroupRequest { + readonly name: string; + readonly display_name: string; + readonly avatar_url: string; + readonly quota_allowance: number; +} + +// From codersdk/organizations.go +export interface CreateOrganizationRequest { + readonly name: string; +} + +// From codersdk/organizations.go +export interface CreateTemplateRequest { + readonly name: string; + readonly display_name?: string; + readonly description?: string; + readonly icon?: string; + readonly template_version_id: string; + readonly default_ttl_ms?: number; + readonly activity_bump_ms?: number; + readonly autostop_requirement?: TemplateAutostopRequirement; + readonly autostart_requirement?: TemplateAutostartRequirement; + readonly allow_user_cancel_workspace_jobs?: boolean; + readonly allow_user_autostart?: boolean; + readonly allow_user_autostop?: boolean; + readonly failure_ttl_ms?: number; + readonly dormant_ttl_ms?: number; + readonly delete_ttl_ms?: number; + readonly disable_everyone_group_access: boolean; + readonly require_active_version: boolean; +} + +// From codersdk/templateversions.go +export interface CreateTemplateVersionDryRunRequest { + readonly workspace_name: string; + readonly rich_parameter_values: readonly WorkspaceBuildParameter[]; + readonly user_variable_values?: readonly VariableValue[]; +} + +// From codersdk/organizations.go +export interface CreateTemplateVersionRequest { + readonly name?: string; + readonly message?: string; + readonly template_id?: string; + readonly storage_method: ProvisionerStorageMethod; + readonly file_id?: string; + readonly example_id?: string; + readonly provisioner: ProvisionerType; + readonly tags: Record; + readonly user_variable_values?: readonly VariableValue[]; +} + +// From codersdk/audit.go +export interface CreateTestAuditLogRequest { + readonly action?: AuditAction; + readonly resource_type?: ResourceType; + readonly resource_id?: string; + readonly additional_fields?: Record; + readonly time?: string; + readonly build_reason?: BuildReason; +} + +// From codersdk/apikey.go +export interface CreateTokenRequest { + readonly lifetime: number; + readonly scope: APIKeyScope; + readonly token_name: string; +} + +// From codersdk/users.go +export interface CreateUserRequest { + readonly email: string; + readonly username: string; + readonly password: string; + readonly login_type: LoginType; + readonly disable_login: boolean; + readonly organization_id: string; +} + +// From codersdk/workspaces.go +export interface CreateWorkspaceBuildRequest { + readonly template_version_id?: string; + readonly transition: WorkspaceTransition; + readonly dry_run?: boolean; + readonly state?: string; + readonly orphan?: boolean; + readonly rich_parameter_values?: readonly WorkspaceBuildParameter[]; + readonly log_level?: ProvisionerLogLevel; +} + +// From codersdk/workspaceproxy.go +export interface CreateWorkspaceProxyRequest { + readonly name: string; + readonly display_name: string; + readonly icon: string; +} + +// From codersdk/organizations.go +export interface CreateWorkspaceRequest { + readonly template_id?: string; + readonly template_version_id?: string; + readonly name: string; + readonly autostart_schedule?: string; + readonly ttl_ms?: number; + readonly rich_parameter_values?: readonly WorkspaceBuildParameter[]; + readonly automatic_updates?: AutomaticUpdates; +} + +// From codersdk/deployment.go +export interface DAUEntry { + readonly date: string; + readonly amount: number; +} + +// From codersdk/deployment.go +export interface DAURequest { + readonly TZHourOffset: number; +} + +// From codersdk/deployment.go +export interface DAUsResponse { + readonly entries: readonly DAUEntry[]; + readonly tz_hour_offset: number; +} + +// From codersdk/deployment.go +export interface DERP { + readonly server: DERPServerConfig; + readonly config: DERPConfig; +} + +// From codersdk/deployment.go +export interface DERPConfig { + readonly block_direct: boolean; + readonly force_websockets: boolean; + readonly url: string; + readonly path: string; +} + +// From codersdk/workspaceagents.go +export interface DERPRegion { + readonly preferred: boolean; + readonly latency_ms: number; +} + +// From codersdk/deployment.go +export interface DERPServerConfig { + readonly enable: boolean; + readonly region_id: number; + readonly region_code: string; + readonly region_name: string; + readonly stun_addresses: string[]; + readonly relay_url: string; +} + +// From codersdk/deployment.go +export interface DangerousConfig { + readonly allow_path_app_sharing: boolean; + readonly allow_path_app_site_owner_access: boolean; + readonly allow_all_cors: boolean; +} + +// From codersdk/workspaceagentportshare.go +export interface DeleteWorkspaceAgentPortShareRequest { + readonly agent_name: string; + readonly port: number; +} + +// From codersdk/deployment.go +export interface DeploymentConfig { + readonly config?: DeploymentValues; + readonly options?: SerpentOptionSet; +} + +// From codersdk/deployment.go +export interface DeploymentStats { + readonly aggregated_from: string; + readonly collected_at: string; + readonly next_update_at: string; + readonly workspaces: WorkspaceDeploymentStats; + readonly session_count: SessionCountDeploymentStats; +} + +// From codersdk/deployment.go +export interface DeploymentValues { + readonly verbose?: boolean; + readonly access_url?: string; + readonly wildcard_access_url?: string; + readonly docs_url?: string; + readonly redirect_to_access_url?: boolean; + readonly http_address?: string; + readonly autobuild_poll_interval?: number; + readonly job_hang_detector_interval?: number; + readonly derp?: DERP; + readonly prometheus?: PrometheusConfig; + readonly pprof?: PprofConfig; + readonly proxy_trusted_headers?: string[]; + readonly proxy_trusted_origins?: string[]; + readonly cache_directory?: string; + readonly in_memory_database?: boolean; + readonly pg_connection_url?: string; + readonly pg_auth?: string; + readonly oauth2?: OAuth2Config; + readonly oidc?: OIDCConfig; + readonly telemetry?: TelemetryConfig; + readonly tls?: TLSConfig; + readonly trace?: TraceConfig; + readonly secure_auth_cookie?: boolean; + readonly strict_transport_security?: number; + readonly strict_transport_security_options?: string[]; + readonly ssh_keygen_algorithm?: string; + readonly metrics_cache_refresh_interval?: number; + readonly agent_stat_refresh_interval?: number; + readonly agent_fallback_troubleshooting_url?: string; + readonly browser_only?: boolean; + readonly scim_api_key?: string; + readonly external_token_encryption_keys?: string[]; + readonly provisioner?: ProvisionerConfig; + readonly rate_limit?: RateLimitConfig; + readonly experiments?: string[]; + readonly update_check?: boolean; + readonly swagger?: SwaggerConfig; + readonly logging?: LoggingConfig; + readonly dangerous?: DangerousConfig; + readonly disable_path_apps?: boolean; + readonly session_lifetime?: SessionLifetime; + readonly disable_password_auth?: boolean; + readonly support?: SupportConfig; + readonly external_auth?: readonly ExternalAuthConfig[]; + readonly config_ssh?: SSHConfig; + readonly wgtunnel_host?: string; + readonly disable_owner_workspace_exec?: boolean; + readonly proxy_health_status_interval?: number; + readonly enable_terraform_debug_mode?: boolean; + readonly user_quiet_hours_schedule?: UserQuietHoursScheduleConfig; + readonly web_terminal_renderer?: string; + readonly allow_workspace_renames?: boolean; + readonly healthcheck?: HealthcheckConfig; + readonly cli_upgrade_message?: string; + readonly terms_of_service_url?: string; + readonly config?: string; + readonly write_config?: boolean; + readonly address?: string; +} + +// From codersdk/deployment.go +export interface Entitlements { + readonly features: Record; + readonly warnings: readonly string[]; + readonly errors: readonly string[]; + readonly has_license: boolean; + readonly trial: boolean; + readonly require_telemetry: boolean; + readonly refreshed_at: string; +} + +// From codersdk/deployment.go +export type Experiments = readonly Experiment[]; + +// From codersdk/externalauth.go +export interface ExternalAuth { + readonly authenticated: boolean; + readonly device: boolean; + readonly display_name: string; + readonly user?: ExternalAuthUser; + readonly app_installable: boolean; + readonly installations: readonly ExternalAuthAppInstallation[]; + readonly app_install_url: string; +} + +// From codersdk/externalauth.go +export interface ExternalAuthAppInstallation { + readonly id: number; + readonly account: ExternalAuthUser; + readonly configure_url: string; +} + +// From codersdk/deployment.go +export interface ExternalAuthConfig { + readonly type: string; + readonly client_id: string; + readonly id: string; + readonly auth_url: string; + readonly token_url: string; + readonly validate_url: string; + readonly app_install_url: string; + readonly app_installations_url: string; + readonly no_refresh: boolean; + readonly scopes: readonly string[]; + readonly extra_token_keys: readonly string[]; + readonly device_flow: boolean; + readonly device_code_url: string; + readonly regex: string; + readonly display_name: string; + readonly display_icon: string; +} + +// From codersdk/externalauth.go +export interface ExternalAuthDevice { + readonly device_code: string; + readonly user_code: string; + readonly verification_uri: string; + readonly expires_in: number; + readonly interval: number; +} + +// From codersdk/externalauth.go +export interface ExternalAuthDeviceExchange { + readonly device_code: string; +} + +// From codersdk/externalauth.go +export interface ExternalAuthLink { + readonly provider_id: string; + readonly created_at: string; + readonly updated_at: string; + readonly has_refresh_token: boolean; + readonly expires: string; + readonly authenticated: boolean; + readonly validate_error: string; +} + +// From codersdk/externalauth.go +export interface ExternalAuthLinkProvider { + readonly id: string; + readonly type: string; + readonly device: boolean; + readonly display_name: string; + readonly display_icon: string; + readonly allow_refresh: boolean; + readonly allow_validate: boolean; +} + +// From codersdk/externalauth.go +export interface ExternalAuthUser { + readonly login: string; + readonly avatar_url: string; + readonly profile_url: string; + readonly name: string; +} + +// From codersdk/deployment.go +export interface Feature { + readonly entitlement: Entitlement; + readonly enabled: boolean; + readonly limit?: number; + readonly actual?: number; +} + +// From codersdk/apikey.go +export interface GenerateAPIKeyResponse { + readonly key: string; +} + +// From codersdk/users.go +export interface GetUsersResponse { + readonly users: readonly User[]; + readonly count: number; +} + +// From codersdk/gitsshkey.go +export interface GitSSHKey { + readonly user_id: string; + readonly created_at: string; + readonly updated_at: string; + readonly public_key: string; +} + +// From codersdk/groups.go +export interface Group { + readonly id: string; + readonly name: string; + readonly display_name: string; + readonly organization_id: string; + readonly members: readonly ReducedUser[]; + readonly avatar_url: string; + readonly quota_allowance: number; + readonly source: GroupSource; +} + +// From codersdk/workspaceapps.go +export interface Healthcheck { + readonly url: string; + readonly interval: number; + readonly threshold: number; +} + +// From codersdk/deployment.go +export interface HealthcheckConfig { + readonly refresh: number; + readonly threshold_database: number; +} + +// From codersdk/workspaceagents.go +export interface IssueReconnectingPTYSignedTokenRequest { + readonly url: string; + readonly agentID: string; +} + +// From codersdk/workspaceagents.go +export interface IssueReconnectingPTYSignedTokenResponse { + readonly signed_token: string; +} + +// From codersdk/jfrog.go +export interface JFrogXrayScan { + readonly workspace_id: string; + readonly agent_id: string; + readonly critical: number; + readonly high: number; + readonly medium: number; + readonly results_url: string; +} + +// From codersdk/licenses.go +export interface License { + readonly id: number; + readonly uuid: string; + readonly uploaded_at: string; + // Empty interface{} type, cannot resolve the type. + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- interface{} + readonly claims: Record; +} + +// From codersdk/deployment.go +export interface LinkConfig { + readonly name: string; + readonly target: string; + readonly icon: string; +} + +// From codersdk/externalauth.go +export interface ListUserExternalAuthResponse { + readonly providers: readonly ExternalAuthLinkProvider[]; + readonly links: readonly ExternalAuthLink[]; +} + +// From codersdk/deployment.go +export interface LoggingConfig { + readonly log_filter: string[]; + readonly human: string; + readonly json: string; + readonly stackdriver: string; +} + +// From codersdk/users.go +export interface LoginWithPasswordRequest { + readonly email: string; + readonly password: string; +} + +// From codersdk/users.go +export interface LoginWithPasswordResponse { + readonly session_token: string; +} + +// From codersdk/users.go +export interface MinimalUser { + readonly id: string; + readonly username: string; + readonly avatar_url: string; +} + +// From codersdk/oauth2.go +export interface OAuth2AppEndpoints { + readonly authorization: string; + readonly token: string; + readonly device_authorization: string; +} + +// From codersdk/deployment.go +export interface OAuth2Config { + readonly github: OAuth2GithubConfig; +} + +// From codersdk/deployment.go +export interface OAuth2GithubConfig { + readonly client_id: string; + readonly client_secret: string; + readonly allowed_orgs: string[]; + readonly allowed_teams: string[]; + readonly allow_signups: boolean; + readonly allow_everyone: boolean; + readonly enterprise_base_url: string; +} + +// From codersdk/oauth2.go +export interface OAuth2ProviderApp { + readonly id: string; + readonly name: string; + readonly callback_url: string; + readonly icon: string; + readonly endpoints: OAuth2AppEndpoints; +} + +// From codersdk/oauth2.go +export interface OAuth2ProviderAppFilter { + readonly user_id?: string; +} + +// From codersdk/oauth2.go +export interface OAuth2ProviderAppSecret { + readonly id: string; + readonly last_used_at?: string; + readonly client_secret_truncated: string; +} + +// From codersdk/oauth2.go +export interface OAuth2ProviderAppSecretFull { + readonly id: string; + readonly client_secret_full: string; +} + +// From codersdk/users.go +export interface OAuthConversionResponse { + readonly state_string: string; + readonly expires_at: string; + readonly to_type: LoginType; + readonly user_id: string; +} + +// From codersdk/users.go +export interface OIDCAuthMethod extends AuthMethod { + readonly signInText: string; + readonly iconUrl: string; +} + +// From codersdk/deployment.go +export interface OIDCConfig { + readonly allow_signups: boolean; + readonly client_id: string; + readonly client_secret: string; + readonly client_key_file: string; + readonly client_cert_file: string; + readonly email_domain: string[]; + readonly issuer_url: string; + readonly scopes: string[]; + readonly ignore_email_verified: boolean; + readonly username_field: string; + readonly email_field: string; + readonly auth_url_params: Record; + readonly ignore_user_info: boolean; + readonly group_auto_create: boolean; + readonly group_regex_filter: string; + readonly group_allow_list: string[]; + readonly groups_field: string; + readonly group_mapping: Record; + readonly user_role_field: string; + readonly user_role_mapping: Record; + readonly user_roles_default: string[]; + readonly sign_in_text: string; + readonly icon_url: string; + readonly signups_disabled_text: string; +} + +// From codersdk/organizations.go +export interface Organization { + readonly id: string; + readonly name: string; + readonly created_at: string; + readonly updated_at: string; + readonly is_default: boolean; +} + +// From codersdk/organizations.go +export interface OrganizationMember { + readonly user_id: string; + readonly organization_id: string; + readonly created_at: string; + readonly updated_at: string; + readonly roles: readonly SlimRole[]; +} + +// From codersdk/pagination.go +export interface Pagination { + readonly after_id?: string; + readonly limit?: number; + readonly offset?: number; +} + +// From codersdk/groups.go +export interface PatchGroupRequest { + readonly add_users: readonly string[]; + readonly remove_users: readonly string[]; + readonly name: string; + readonly display_name?: string; + readonly avatar_url?: string; + readonly quota_allowance?: number; +} + +// From codersdk/templateversions.go +export interface PatchTemplateVersionRequest { + readonly name: string; + readonly message?: string; +} + +// From codersdk/workspaceproxy.go +export interface PatchWorkspaceProxy { + readonly id: string; + readonly name: string; + readonly display_name: string; + readonly icon: string; + readonly regenerate_token: boolean; +} + +// From codersdk/roles.go +export interface Permission { + readonly negate: boolean; + readonly resource_type: RBACResource; + readonly action: RBACAction; +} + +// From codersdk/oauth2.go +export interface PostOAuth2ProviderAppRequest { + readonly name: string; + readonly callback_url: string; + readonly icon: string; +} + +// From codersdk/deployment.go +export interface PprofConfig { + readonly enable: boolean; + readonly address: string; +} + +// From codersdk/deployment.go +export interface PrometheusConfig { + readonly enable: boolean; + readonly address: string; + readonly collect_agent_stats: boolean; + readonly collect_db_metrics: boolean; + readonly aggregate_agent_stats_by: string[]; +} + +// From codersdk/deployment.go +export interface ProvisionerConfig { + readonly daemons: number; + readonly daemon_types: string[]; + readonly daemon_poll_interval: number; + readonly daemon_poll_jitter: number; + readonly force_cancel_interval: number; + readonly daemon_psk: string; +} + +// From codersdk/provisionerdaemons.go +export interface ProvisionerDaemon { + readonly id: string; + readonly created_at: string; + readonly last_seen_at?: string; + readonly name: string; + readonly version: string; + readonly api_version: string; + readonly provisioners: readonly ProvisionerType[]; + readonly tags: Record; +} + +// From codersdk/provisionerdaemons.go +export interface ProvisionerJob { + readonly id: string; + readonly created_at: string; + readonly started_at?: string; + readonly completed_at?: string; + readonly canceled_at?: string; + readonly error?: string; + readonly error_code?: JobErrorCode; + readonly status: ProvisionerJobStatus; + readonly worker_id?: string; + readonly file_id: string; + readonly tags: Record; + readonly queue_position: number; + readonly queue_size: number; +} + +// From codersdk/provisionerdaemons.go +export interface ProvisionerJobLog { + readonly id: number; + readonly created_at: string; + readonly log_source: LogSource; + readonly log_level: LogLevel; + readonly stage: string; + readonly output: string; +} + +// From codersdk/workspaceproxy.go +export interface ProxyHealthReport { + readonly errors: readonly string[]; + readonly warnings: readonly string[]; +} + +// From codersdk/workspaces.go +export interface PutExtendWorkspaceRequest { + readonly deadline: string; +} + +// From codersdk/oauth2.go +export interface PutOAuth2ProviderAppRequest { + readonly name: string; + readonly callback_url: string; + readonly icon: string; +} + +// From codersdk/deployment.go +export interface RateLimitConfig { + readonly disable_all: boolean; + readonly api: number; +} + +// From codersdk/users.go +export interface ReducedUser extends MinimalUser { + readonly name: string; + readonly email: string; + readonly created_at: string; + readonly last_seen_at: string; + readonly status: UserStatus; + readonly login_type: LoginType; + readonly theme_preference: string; +} + +// From codersdk/workspaceproxy.go +export interface Region { + readonly id: string; + readonly name: string; + readonly display_name: string; + readonly icon_url: string; + readonly healthy: boolean; + readonly path_app_url: string; + readonly wildcard_hostname: string; +} + +// From codersdk/workspaceproxy.go +export interface RegionsResponse { + readonly regions: readonly R[]; +} + +// From codersdk/replicas.go +export interface Replica { + readonly id: string; + readonly hostname: string; + readonly created_at: string; + readonly relay_address: string; + readonly region_id: number; + readonly error: string; + readonly database_latency: number; +} + +// From codersdk/workspaces.go +export interface ResolveAutostartResponse { + readonly parameter_mismatch: boolean; +} + +// From codersdk/client.go +export interface Response { + readonly message: string; + readonly detail?: string; + readonly validations?: readonly ValidationError[]; +} + +// From codersdk/roles.go +export interface Role { + readonly name: string; + readonly organization_id: string; + readonly display_name: string; + readonly site_permissions: readonly Permission[]; + readonly organization_permissions: Record; + readonly user_permissions: readonly Permission[]; +} + +// From codersdk/deployment.go +export interface SSHConfig { + readonly DeploymentName: string; + readonly SSHConfigOptions: string[]; +} + +// From codersdk/deployment.go +export interface SSHConfigResponse { + readonly hostname_prefix: string; + readonly ssh_config_options: Record; +} + +// From codersdk/serversentevents.go +export interface ServerSentEvent { + readonly type: ServerSentEventType; + // Empty interface{} type, cannot resolve the type. + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- interface{} + readonly data: any; +} + +// From codersdk/deployment.go +export interface ServiceBannerConfig { + readonly enabled: boolean; + readonly message?: string; + readonly background_color?: string; +} + +// From codersdk/deployment.go +export interface SessionCountDeploymentStats { + readonly vscode: number; + readonly ssh: number; + readonly jetbrains: number; + readonly reconnecting_pty: number; +} + +// From codersdk/deployment.go +export interface SessionLifetime { + readonly disable_expiry_refresh?: boolean; + readonly default_duration: number; + readonly max_token_lifetime?: number; +} + +// From codersdk/roles.go +export interface SlimRole { + readonly name: string; + readonly display_name: string; +} + +// From codersdk/deployment.go +export interface SupportConfig { + readonly links: readonly LinkConfig[]; +} + +// From codersdk/deployment.go +export interface SwaggerConfig { + readonly enable: boolean; +} + +// From codersdk/deployment.go +export interface TLSConfig { + readonly enable: boolean; + readonly address: string; + readonly redirect_http: boolean; + readonly cert_file: string[]; + readonly client_auth: string; + readonly client_ca_file: string; + readonly key_file: string[]; + readonly min_version: string; + readonly client_cert_file: string; + readonly client_key_file: string; + readonly supported_ciphers: string[]; + readonly allow_insecure_ciphers: boolean; +} + +// From codersdk/deployment.go +export interface TelemetryConfig { + readonly enable: boolean; + readonly trace: boolean; + readonly url: string; +} + +// From codersdk/templates.go +export interface Template { + readonly id: string; + readonly created_at: string; + readonly updated_at: string; + readonly organization_id: string; + readonly name: string; + readonly display_name: string; + readonly provisioner: ProvisionerType; + readonly active_version_id: string; + readonly active_user_count: number; + readonly build_time_stats: TemplateBuildTimeStats; + readonly description: string; + readonly deprecated: boolean; + readonly deprecation_message: string; + readonly icon: string; + readonly default_ttl_ms: number; + readonly activity_bump_ms: number; + readonly autostop_requirement: TemplateAutostopRequirement; + readonly autostart_requirement: TemplateAutostartRequirement; + readonly created_by_id: string; + readonly created_by_name: string; + readonly allow_user_autostart: boolean; + readonly allow_user_autostop: boolean; + readonly allow_user_cancel_workspace_jobs: boolean; + readonly failure_ttl_ms: number; + readonly time_til_dormant_ms: number; + readonly time_til_dormant_autodelete_ms: number; + readonly require_active_version: boolean; + readonly max_port_share_level: WorkspaceAgentPortShareLevel; +} + +// From codersdk/templates.go +export interface TemplateACL { + readonly users: readonly TemplateUser[]; + readonly group: readonly TemplateGroup[]; +} + +// From codersdk/insights.go +export interface TemplateAppUsage { + readonly template_ids: readonly string[]; + readonly type: TemplateAppsType; + readonly display_name: string; + readonly slug: string; + readonly icon: string; + readonly seconds: number; + readonly times_used: number; +} + +// From codersdk/templates.go +export interface TemplateAutostartRequirement { + readonly days_of_week: readonly string[]; +} + +// From codersdk/templates.go +export interface TemplateAutostopRequirement { + readonly days_of_week: readonly string[]; + readonly weeks: number; +} + +// From codersdk/templates.go +export type TemplateBuildTimeStats = Record< + WorkspaceTransition, + TransitionStats +>; + +// From codersdk/templates.go +export interface TemplateExample { + readonly id: string; + readonly url: string; + readonly name: string; + readonly description: string; + readonly icon: string; + readonly tags: readonly string[]; + readonly markdown: string; +} + +// From codersdk/templates.go +export interface TemplateGroup extends Group { + readonly role: TemplateRole; +} + +// From codersdk/insights.go +export interface TemplateInsightsIntervalReport { + readonly start_time: string; + readonly end_time: string; + readonly template_ids: readonly string[]; + readonly interval: InsightsReportInterval; + readonly active_users: number; +} + +// From codersdk/insights.go +export interface TemplateInsightsReport { + readonly start_time: string; + readonly end_time: string; + readonly template_ids: readonly string[]; + readonly active_users: number; + readonly apps_usage: readonly TemplateAppUsage[]; + readonly parameters_usage: readonly TemplateParameterUsage[]; +} + +// From codersdk/insights.go +export interface TemplateInsightsRequest { + readonly start_time: string; + readonly end_time: string; + readonly template_ids: readonly string[]; + readonly interval: InsightsReportInterval; + readonly sections: readonly TemplateInsightsSection[]; +} + +// From codersdk/insights.go +export interface TemplateInsightsResponse { + readonly report?: TemplateInsightsReport; + readonly interval_reports?: readonly TemplateInsightsIntervalReport[]; +} + +// From codersdk/insights.go +export interface TemplateParameterUsage { + readonly template_ids: readonly string[]; + readonly display_name: string; + readonly name: string; + readonly type: string; + readonly description: string; + readonly options?: readonly TemplateVersionParameterOption[]; + readonly values: readonly TemplateParameterValue[]; +} + +// From codersdk/insights.go +export interface TemplateParameterValue { + readonly value: string; + readonly count: number; +} + +// From codersdk/templates.go +export interface TemplateUser extends User { + readonly role: TemplateRole; +} + +// From codersdk/templateversions.go +export interface TemplateVersion { + readonly id: string; + readonly template_id?: string; + readonly organization_id?: string; + readonly created_at: string; + readonly updated_at: string; + readonly name: string; + readonly message: string; + readonly job: ProvisionerJob; + readonly readme: string; + readonly created_by: MinimalUser; + readonly archived: boolean; + readonly warnings?: readonly TemplateVersionWarning[]; +} + +// From codersdk/templateversions.go +export interface TemplateVersionExternalAuth { + readonly id: string; + readonly type: string; + readonly display_name: string; + readonly display_icon: string; + readonly authenticate_url: string; + readonly authenticated: boolean; + readonly optional?: boolean; +} + +// From codersdk/templateversions.go +export interface TemplateVersionParameter { + readonly name: string; + readonly display_name?: string; + readonly description: string; + readonly description_plaintext: string; + readonly type: string; + readonly mutable: boolean; + readonly default_value: string; + readonly icon: string; + readonly options: readonly TemplateVersionParameterOption[]; + readonly validation_error?: string; + readonly validation_regex?: string; + readonly validation_min?: number; + readonly validation_max?: number; + readonly validation_monotonic?: ValidationMonotonicOrder; + readonly required: boolean; + readonly ephemeral: boolean; +} + +// From codersdk/templateversions.go +export interface TemplateVersionParameterOption { + readonly name: string; + readonly description: string; + readonly value: string; + readonly icon: string; +} + +// From codersdk/templateversions.go +export interface TemplateVersionVariable { + readonly name: string; + readonly description: string; + readonly type: string; + readonly value: string; + readonly default_value: string; + readonly required: boolean; + readonly sensitive: boolean; +} + +// From codersdk/templates.go +export interface TemplateVersionsByTemplateRequest extends Pagination { + readonly template_id: string; + readonly include_archived: boolean; +} + +// From codersdk/apikey.go +export interface TokenConfig { + readonly max_token_lifetime: number; +} + +// From codersdk/apikey.go +export interface TokensFilter { + readonly include_all: boolean; +} + +// From codersdk/deployment.go +export interface TraceConfig { + readonly enable: boolean; + readonly honeycomb_api_key: string; + readonly capture_logs: boolean; + readonly data_dog: boolean; +} + +// From codersdk/templates.go +export interface TransitionStats { + readonly P50?: number; + readonly P95?: number; +} + +// From codersdk/templates.go +export interface UpdateActiveTemplateVersion { + readonly id: string; +} + +// From codersdk/deployment.go +export interface UpdateAppearanceConfig { + readonly application_name: string; + readonly logo_url: string; + readonly service_banner: BannerConfig; + readonly notification_banners: readonly BannerConfig[]; +} + +// From codersdk/updatecheck.go +export interface UpdateCheckResponse { + readonly current: boolean; + readonly version: string; + readonly url: string; +} + +// From codersdk/organizations.go +export interface UpdateOrganizationRequest { + readonly name: string; +} + +// From codersdk/users.go +export interface UpdateRoles { + readonly roles: readonly string[]; +} + +// From codersdk/templates.go +export interface UpdateTemplateACL { + readonly user_perms?: Record; + readonly group_perms?: Record; +} + +// From codersdk/templates.go +export interface UpdateTemplateMeta { + readonly name?: string; + readonly display_name?: string; + readonly description?: string; + readonly icon?: string; + readonly default_ttl_ms?: number; + readonly activity_bump_ms?: number; + readonly autostop_requirement?: TemplateAutostopRequirement; + readonly autostart_requirement?: TemplateAutostartRequirement; + readonly allow_user_autostart?: boolean; + readonly allow_user_autostop?: boolean; + readonly allow_user_cancel_workspace_jobs?: boolean; + readonly failure_ttl_ms?: number; + readonly time_til_dormant_ms?: number; + readonly time_til_dormant_autodelete_ms?: number; + readonly update_workspace_last_used_at: boolean; + readonly update_workspace_dormant_at: boolean; + readonly require_active_version?: boolean; + readonly deprecation_message?: string; + readonly disable_everyone_group_access: boolean; + readonly max_port_share_level?: WorkspaceAgentPortShareLevel; +} + +// From codersdk/users.go +export interface UpdateUserAppearanceSettingsRequest { + readonly theme_preference: string; +} + +// From codersdk/users.go +export interface UpdateUserPasswordRequest { + readonly old_password: string; + readonly password: string; +} + +// From codersdk/users.go +export interface UpdateUserProfileRequest { + readonly username: string; + readonly name: string; +} + +// From codersdk/users.go +export interface UpdateUserQuietHoursScheduleRequest { + readonly schedule: string; +} + +// From codersdk/workspaces.go +export interface UpdateWorkspaceAutomaticUpdatesRequest { + readonly automatic_updates: AutomaticUpdates; +} + +// From codersdk/workspaces.go +export interface UpdateWorkspaceAutostartRequest { + readonly schedule?: string; +} + +// From codersdk/workspaces.go +export interface UpdateWorkspaceDormancy { + readonly dormant: boolean; +} + +// From codersdk/workspaceproxy.go +export interface UpdateWorkspaceProxyResponse { + readonly proxy: WorkspaceProxy; + readonly proxy_token: string; +} + +// From codersdk/workspaces.go +export interface UpdateWorkspaceRequest { + readonly name?: string; +} + +// From codersdk/workspaces.go +export interface UpdateWorkspaceTTLRequest { + readonly ttl_ms?: number; +} + +// From codersdk/files.go +export interface UploadResponse { + readonly hash: string; +} + +// From codersdk/workspaceagentportshare.go +export interface UpsertWorkspaceAgentPortShareRequest { + readonly agent_name: string; + readonly port: number; + readonly share_level: WorkspaceAgentPortShareLevel; + readonly protocol: WorkspaceAgentPortShareProtocol; +} + +// From codersdk/users.go +export interface User extends ReducedUser { + readonly organization_ids: readonly string[]; + readonly roles: readonly SlimRole[]; +} + +// From codersdk/insights.go +export interface UserActivity { + readonly template_ids: readonly string[]; + readonly user_id: string; + readonly username: string; + readonly avatar_url: string; + readonly seconds: number; +} + +// From codersdk/insights.go +export interface UserActivityInsightsReport { + readonly start_time: string; + readonly end_time: string; + readonly template_ids: readonly string[]; + readonly users: readonly UserActivity[]; +} + +// From codersdk/insights.go +export interface UserActivityInsightsRequest { + readonly start_time: string; + readonly end_time: string; + readonly template_ids: readonly string[]; +} + +// From codersdk/insights.go +export interface UserActivityInsightsResponse { + readonly report: UserActivityInsightsReport; +} + +// From codersdk/insights.go +export interface UserLatency { + readonly template_ids: readonly string[]; + readonly user_id: string; + readonly username: string; + readonly avatar_url: string; + readonly latency_ms: ConnectionLatency; +} + +// From codersdk/insights.go +export interface UserLatencyInsightsReport { + readonly start_time: string; + readonly end_time: string; + readonly template_ids: readonly string[]; + readonly users: readonly UserLatency[]; +} + +// From codersdk/insights.go +export interface UserLatencyInsightsRequest { + readonly start_time: string; + readonly end_time: string; + readonly template_ids: readonly string[]; +} + +// From codersdk/insights.go +export interface UserLatencyInsightsResponse { + readonly report: UserLatencyInsightsReport; +} + +// From codersdk/users.go +export interface UserLoginType { + readonly login_type: LoginType; +} + +// From codersdk/users.go +export interface UserParameter { + readonly name: string; + readonly value: string; +} + +// From codersdk/deployment.go +export interface UserQuietHoursScheduleConfig { + readonly default_schedule: string; + readonly allow_user_custom: boolean; +} + +// From codersdk/users.go +export interface UserQuietHoursScheduleResponse { + readonly raw_schedule: string; + readonly user_set: boolean; + readonly user_can_set: boolean; + readonly time: string; + readonly timezone: string; + readonly next: string; +} + +// From codersdk/users.go +export interface UserRoles { + readonly roles: readonly string[]; + readonly organization_roles: Record; +} + +// From codersdk/users.go +export interface UsersRequest extends Pagination { + readonly q?: string; +} + +// From codersdk/client.go +export interface ValidationError { + readonly field: string; + readonly detail: string; +} + +// From codersdk/organizations.go +export interface VariableValue { + readonly name: string; + readonly value: string; +} + +// From codersdk/workspaces.go +export interface Workspace { + readonly id: string; + readonly created_at: string; + readonly updated_at: string; + readonly owner_id: string; + readonly owner_name: string; + readonly owner_avatar_url: string; + readonly organization_id: string; + readonly template_id: string; + readonly template_name: string; + readonly template_display_name: string; + readonly template_icon: string; + readonly template_allow_user_cancel_workspace_jobs: boolean; + readonly template_active_version_id: string; + readonly template_require_active_version: boolean; + readonly latest_build: WorkspaceBuild; + readonly outdated: boolean; + readonly name: string; + readonly autostart_schedule?: string; + readonly ttl_ms?: number; + readonly last_used_at: string; + readonly deleting_at?: string; + readonly dormant_at?: string; + readonly health: WorkspaceHealth; + readonly automatic_updates: AutomaticUpdates; + readonly allow_renames: boolean; + readonly favorite: boolean; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgent { + readonly id: string; + readonly created_at: string; + readonly updated_at: string; + readonly first_connected_at?: string; + readonly last_connected_at?: string; + readonly disconnected_at?: string; + readonly started_at?: string; + readonly ready_at?: string; + readonly status: WorkspaceAgentStatus; + readonly lifecycle_state: WorkspaceAgentLifecycle; + readonly name: string; + readonly resource_id: string; + readonly instance_id?: string; + readonly architecture: string; + readonly environment_variables: Record; + readonly operating_system: string; + readonly logs_length: number; + readonly logs_overflowed: boolean; + readonly directory?: string; + readonly expanded_directory?: string; + readonly version: string; + readonly api_version: string; + readonly apps: readonly WorkspaceApp[]; + readonly latency?: Record; + readonly connection_timeout_seconds: number; + readonly troubleshooting_url: string; + readonly subsystems: readonly AgentSubsystem[]; + readonly health: WorkspaceAgentHealth; + readonly display_apps: readonly DisplayApp[]; + readonly log_sources: readonly WorkspaceAgentLogSource[]; + readonly scripts: readonly WorkspaceAgentScript[]; + readonly startup_script_behavior: WorkspaceAgentStartupScriptBehavior; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentHealth { + readonly healthy: boolean; + readonly reason?: string; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentListeningPort { + readonly process_name: string; + readonly network: string; + readonly port: number; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentListeningPortsResponse { + readonly ports: readonly WorkspaceAgentListeningPort[]; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentLog { + readonly id: number; + readonly created_at: string; + readonly output: string; + readonly level: LogLevel; + readonly source_id: string; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentLogSource { + readonly workspace_agent_id: string; + readonly id: string; + readonly created_at: string; + readonly display_name: string; + readonly icon: string; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentMetadata { + readonly result: WorkspaceAgentMetadataResult; + readonly description: WorkspaceAgentMetadataDescription; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentMetadataDescription { + readonly display_name: string; + readonly key: string; + readonly script: string; + readonly interval: number; + readonly timeout: number; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentMetadataResult { + readonly collected_at: string; + readonly age: number; + readonly value: string; + readonly error: string; +} + +// From codersdk/workspaceagentportshare.go +export interface WorkspaceAgentPortShare { + readonly workspace_id: string; + readonly agent_name: string; + readonly port: number; + readonly share_level: WorkspaceAgentPortShareLevel; + readonly protocol: WorkspaceAgentPortShareProtocol; +} + +// From codersdk/workspaceagentportshare.go +export interface WorkspaceAgentPortShares { + readonly shares: readonly WorkspaceAgentPortShare[]; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentScript { + readonly log_source_id: string; + readonly log_path: string; + readonly script: string; + readonly cron: string; + readonly run_on_start: boolean; + readonly run_on_stop: boolean; + readonly start_blocks_login: boolean; + readonly timeout: number; +} + +// From codersdk/workspaceapps.go +export interface WorkspaceApp { + readonly id: string; + readonly url: string; + readonly external: boolean; + readonly slug: string; + readonly display_name: string; + readonly command?: string; + readonly icon?: string; + readonly subdomain: boolean; + readonly subdomain_name?: string; + readonly sharing_level: WorkspaceAppSharingLevel; + readonly healthcheck: Healthcheck; + readonly health: WorkspaceAppHealth; +} + +// From codersdk/workspacebuilds.go +export interface WorkspaceBuild { + readonly id: string; + readonly created_at: string; + readonly updated_at: string; + readonly workspace_id: string; + readonly workspace_name: string; + readonly workspace_owner_id: string; + readonly workspace_owner_name: string; + readonly workspace_owner_avatar_url: string; + readonly template_version_id: string; + readonly template_version_name: string; + readonly build_number: number; + readonly transition: WorkspaceTransition; + readonly initiator_id: string; + readonly initiator_name: string; + readonly job: ProvisionerJob; + readonly reason: BuildReason; + readonly resources: readonly WorkspaceResource[]; + readonly deadline?: string; + readonly max_deadline?: string; + readonly status: WorkspaceStatus; + readonly daily_cost: number; +} + +// From codersdk/workspacebuilds.go +export interface WorkspaceBuildParameter { + readonly name: string; + readonly value: string; +} + +// From codersdk/workspaces.go +export interface WorkspaceBuildsRequest extends Pagination { + readonly since?: string; +} + +// From codersdk/deployment.go +export interface WorkspaceConnectionLatencyMS { + readonly P50: number; + readonly P95: number; +} + +// From codersdk/deployment.go +export interface WorkspaceDeploymentStats { + readonly pending: number; + readonly building: number; + readonly running: number; + readonly failed: number; + readonly stopped: number; + readonly connection_latency_ms: WorkspaceConnectionLatencyMS; + readonly rx_bytes: number; + readonly tx_bytes: number; +} + +// From codersdk/workspaces.go +export interface WorkspaceFilter { + readonly q?: string; +} + +// From codersdk/workspaces.go +export interface WorkspaceHealth { + readonly healthy: boolean; + readonly failing_agents: readonly string[]; +} + +// From codersdk/workspaces.go +export interface WorkspaceOptions { + readonly include_deleted?: boolean; +} + +// From codersdk/workspaceproxy.go +export interface WorkspaceProxy extends Region { + readonly derp_enabled: boolean; + readonly derp_only: boolean; + readonly status?: WorkspaceProxyStatus; + readonly created_at: string; + readonly updated_at: string; + readonly deleted: boolean; + readonly version: string; +} + +// From codersdk/deployment.go +export interface WorkspaceProxyBuildInfo { + readonly workspace_proxy: boolean; + readonly dashboard_url: string; +} + +// From codersdk/workspaceproxy.go +export interface WorkspaceProxyStatus { + readonly status: ProxyHealthStatus; + readonly report?: ProxyHealthReport; + readonly checked_at: string; +} + +// From codersdk/workspaces.go +export interface WorkspaceQuota { + readonly credits_consumed: number; + readonly budget: number; +} + +// From codersdk/workspacebuilds.go +export interface WorkspaceResource { + readonly id: string; + readonly created_at: string; + readonly job_id: string; + readonly workspace_transition: WorkspaceTransition; + readonly type: string; + readonly name: string; + readonly hide: boolean; + readonly icon: string; + readonly agents?: readonly WorkspaceAgent[]; + readonly metadata?: readonly WorkspaceResourceMetadata[]; + readonly daily_cost: number; +} + +// From codersdk/workspacebuilds.go +export interface WorkspaceResourceMetadata { + readonly key: string; + readonly value: string; + readonly sensitive: boolean; +} + +// From codersdk/workspaces.go +export interface WorkspacesRequest extends Pagination { + readonly q?: string; +} + +// From codersdk/workspaces.go +export interface WorkspacesResponse { + readonly workspaces: readonly Workspace[]; + readonly count: number; +} + +// From codersdk/apikey.go +export type APIKeyScope = 'all' | 'application_connect'; +export const APIKeyScopes: APIKeyScope[] = ['all', 'application_connect']; + +// From codersdk/workspaceagents.go +export type AgentSubsystem = 'envbox' | 'envbuilder' | 'exectrace'; +export const AgentSubsystems: AgentSubsystem[] = [ + 'envbox', + 'envbuilder', + 'exectrace', +]; + +// From codersdk/audit.go +export type AuditAction = + | 'create' + | 'delete' + | 'login' + | 'logout' + | 'register' + | 'start' + | 'stop' + | 'write'; +export const AuditActions: AuditAction[] = [ + 'create', + 'delete', + 'login', + 'logout', + 'register', + 'start', + 'stop', + 'write', +]; + +// From codersdk/workspaces.go +export type AutomaticUpdates = 'always' | 'never'; +export const AutomaticUpdateses: AutomaticUpdates[] = ['always', 'never']; + +// From codersdk/workspacebuilds.go +export type BuildReason = 'autostart' | 'autostop' | 'initiator'; +export const BuildReasons: BuildReason[] = [ + 'autostart', + 'autostop', + 'initiator', +]; + +// From codersdk/workspaceagents.go +export type DisplayApp = + | 'port_forwarding_helper' + | 'ssh_helper' + | 'vscode' + | 'vscode_insiders' + | 'web_terminal'; +export const DisplayApps: DisplayApp[] = [ + 'port_forwarding_helper', + 'ssh_helper', + 'vscode', + 'vscode_insiders', + 'web_terminal', +]; + +// From codersdk/externalauth.go +export type EnhancedExternalAuthProvider = + | 'azure-devops' + | 'azure-devops-entra' + | 'bitbucket-cloud' + | 'bitbucket-server' + | 'gitea' + | 'github' + | 'gitlab' + | 'jfrog' + | 'slack'; +export const EnhancedExternalAuthProviders: EnhancedExternalAuthProvider[] = [ + 'azure-devops', + 'azure-devops-entra', + 'bitbucket-cloud', + 'bitbucket-server', + 'gitea', + 'github', + 'gitlab', + 'jfrog', + 'slack', +]; + +// From codersdk/deployment.go +export type Entitlement = 'entitled' | 'grace_period' | 'not_entitled'; +export const entitlements: Entitlement[] = [ + 'entitled', + 'grace_period', + 'not_entitled', +]; + +// From codersdk/deployment.go +export type Experiment = + | 'auto-fill-parameters' + | 'custom-roles' + | 'example' + | 'multi-organization'; +export const experiments: Experiment[] = [ + 'auto-fill-parameters', + 'custom-roles', + 'example', + 'multi-organization', +]; + +// From codersdk/deployment.go +export type FeatureName = + | 'access_control' + | 'advanced_template_scheduling' + | 'appearance' + | 'audit_log' + | 'browser_only' + | 'control_shared_ports' + | 'custom_roles' + | 'external_provisioner_daemons' + | 'external_token_encryption' + | 'high_availability' + | 'multiple_external_auth' + | 'scim' + | 'template_rbac' + | 'user_limit' + | 'user_role_management' + | 'workspace_batch_actions' + | 'workspace_proxy'; +export const FeatureNames: FeatureName[] = [ + 'access_control', + 'advanced_template_scheduling', + 'appearance', + 'audit_log', + 'browser_only', + 'control_shared_ports', + 'custom_roles', + 'external_provisioner_daemons', + 'external_token_encryption', + 'high_availability', + 'multiple_external_auth', + 'scim', + 'template_rbac', + 'user_limit', + 'user_role_management', + 'workspace_batch_actions', + 'workspace_proxy', +]; + +// From codersdk/groups.go +export type GroupSource = 'oidc' | 'user'; +export const GroupSources: GroupSource[] = ['oidc', 'user']; + +// From codersdk/insights.go +export type InsightsReportInterval = 'day' | 'week'; +export const InsightsReportIntervals: InsightsReportInterval[] = [ + 'day', + 'week', +]; + +// From codersdk/provisionerdaemons.go +export type JobErrorCode = 'REQUIRED_TEMPLATE_VARIABLES'; +export const JobErrorCodes: JobErrorCode[] = ['REQUIRED_TEMPLATE_VARIABLES']; + +// From codersdk/provisionerdaemons.go +export type LogLevel = 'debug' | 'error' | 'info' | 'trace' | 'warn'; +export const LogLevels: LogLevel[] = [ + 'debug', + 'error', + 'info', + 'trace', + 'warn', +]; + +// From codersdk/provisionerdaemons.go +export type LogSource = 'provisioner' | 'provisioner_daemon'; +export const LogSources: LogSource[] = ['provisioner', 'provisioner_daemon']; + +// From codersdk/apikey.go +export type LoginType = '' | 'github' | 'none' | 'oidc' | 'password' | 'token'; +export const LoginTypes: LoginType[] = [ + '', + 'github', + 'none', + 'oidc', + 'password', + 'token', +]; + +// From codersdk/oauth2.go +export type OAuth2ProviderGrantType = 'authorization_code' | 'refresh_token'; +export const OAuth2ProviderGrantTypes: OAuth2ProviderGrantType[] = [ + 'authorization_code', + 'refresh_token', +]; + +// From codersdk/oauth2.go +export type OAuth2ProviderResponseType = 'code'; +export const OAuth2ProviderResponseTypes: OAuth2ProviderResponseType[] = [ + 'code', +]; + +// From codersdk/deployment.go +export type PostgresAuth = 'awsiamrds' | 'password'; +export const PostgresAuths: PostgresAuth[] = ['awsiamrds', 'password']; + +// From codersdk/provisionerdaemons.go +export type ProvisionerJobStatus = + | 'canceled' + | 'canceling' + | 'failed' + | 'pending' + | 'running' + | 'succeeded' + | 'unknown'; +export const ProvisionerJobStatuses: ProvisionerJobStatus[] = [ + 'canceled', + 'canceling', + 'failed', + 'pending', + 'running', + 'succeeded', + 'unknown', +]; + +// From codersdk/workspaces.go +export type ProvisionerLogLevel = 'debug'; +export const ProvisionerLogLevels: ProvisionerLogLevel[] = ['debug']; + +// From codersdk/organizations.go +export type ProvisionerStorageMethod = 'file'; +export const ProvisionerStorageMethods: ProvisionerStorageMethod[] = ['file']; + +// From codersdk/organizations.go +export type ProvisionerType = 'echo' | 'terraform'; +export const ProvisionerTypes: ProvisionerType[] = ['echo', 'terraform']; + +// From codersdk/workspaceproxy.go +export type ProxyHealthStatus = + | 'ok' + | 'unhealthy' + | 'unreachable' + | 'unregistered'; +export const ProxyHealthStatuses: ProxyHealthStatus[] = [ + 'ok', + 'unhealthy', + 'unreachable', + 'unregistered', +]; + +// From codersdk/rbacresources_gen.go +export type RBACAction = + | 'application_connect' + | 'assign' + | 'create' + | 'delete' + | 'read' + | 'read_personal' + | 'ssh' + | 'start' + | 'stop' + | 'update' + | 'update_personal' + | 'use' + | 'view_insights'; +export const RBACActions: RBACAction[] = [ + 'application_connect', + 'assign', + 'create', + 'delete', + 'read', + 'read_personal', + 'ssh', + 'start', + 'stop', + 'update', + 'update_personal', + 'use', + 'view_insights', +]; + +// From codersdk/rbacresources_gen.go +export type RBACResource = + | '*' + | 'api_key' + | 'assign_org_role' + | 'assign_role' + | 'audit_log' + | 'debug_info' + | 'deployment_config' + | 'deployment_stats' + | 'file' + | 'group' + | 'license' + | 'oauth2_app' + | 'oauth2_app_code_token' + | 'oauth2_app_secret' + | 'organization' + | 'organization_member' + | 'provisioner_daemon' + | 'replicas' + | 'system' + | 'tailnet_coordinator' + | 'template' + | 'user' + | 'workspace' + | 'workspace_dormant' + | 'workspace_proxy'; +export const RBACResources: RBACResource[] = [ + '*', + 'api_key', + 'assign_org_role', + 'assign_role', + 'audit_log', + 'debug_info', + 'deployment_config', + 'deployment_stats', + 'file', + 'group', + 'license', + 'oauth2_app', + 'oauth2_app_code_token', + 'oauth2_app_secret', + 'organization', + 'organization_member', + 'provisioner_daemon', + 'replicas', + 'system', + 'tailnet_coordinator', + 'template', + 'user', + 'workspace', + 'workspace_dormant', + 'workspace_proxy', +]; + +// From codersdk/audit.go +export type ResourceType = + | 'api_key' + | 'convert_login' + | 'git_ssh_key' + | 'group' + | 'health_settings' + | 'license' + | 'oauth2_provider_app' + | 'oauth2_provider_app_secret' + | 'organization' + | 'template' + | 'template_version' + | 'user' + | 'workspace' + | 'workspace_build' + | 'workspace_proxy'; +export const ResourceTypes: ResourceType[] = [ + 'api_key', + 'convert_login', + 'git_ssh_key', + 'group', + 'health_settings', + 'license', + 'oauth2_provider_app', + 'oauth2_provider_app_secret', + 'organization', + 'template', + 'template_version', + 'user', + 'workspace', + 'workspace_build', + 'workspace_proxy', +]; + +// From codersdk/serversentevents.go +export type ServerSentEventType = 'data' | 'error' | 'ping'; +export const ServerSentEventTypes: ServerSentEventType[] = [ + 'data', + 'error', + 'ping', +]; + +// From codersdk/insights.go +export type TemplateAppsType = 'app' | 'builtin'; +export const TemplateAppsTypes: TemplateAppsType[] = ['app', 'builtin']; + +// From codersdk/insights.go +export type TemplateInsightsSection = 'interval_reports' | 'report'; +export const TemplateInsightsSections: TemplateInsightsSection[] = [ + 'interval_reports', + 'report', +]; + +// From codersdk/templates.go +export type TemplateRole = '' | 'admin' | 'use'; +export const TemplateRoles: TemplateRole[] = ['', 'admin', 'use']; + +// From codersdk/templateversions.go +export type TemplateVersionWarning = 'UNSUPPORTED_WORKSPACES'; +export const TemplateVersionWarnings: TemplateVersionWarning[] = [ + 'UNSUPPORTED_WORKSPACES', +]; + +// From codersdk/users.go +export type UserStatus = 'active' | 'dormant' | 'suspended'; +export const UserStatuses: UserStatus[] = ['active', 'dormant', 'suspended']; + +// From codersdk/templateversions.go +export type ValidationMonotonicOrder = 'decreasing' | 'increasing'; +export const ValidationMonotonicOrders: ValidationMonotonicOrder[] = [ + 'decreasing', + 'increasing', +]; + +// From codersdk/workspaceagents.go +export type WorkspaceAgentLifecycle = + | 'created' + | 'off' + | 'ready' + | 'shutdown_error' + | 'shutdown_timeout' + | 'shutting_down' + | 'start_error' + | 'start_timeout' + | 'starting'; +export const WorkspaceAgentLifecycles: WorkspaceAgentLifecycle[] = [ + 'created', + 'off', + 'ready', + 'shutdown_error', + 'shutdown_timeout', + 'shutting_down', + 'start_error', + 'start_timeout', + 'starting', +]; + +// From codersdk/workspaceagentportshare.go +export type WorkspaceAgentPortShareLevel = 'authenticated' | 'owner' | 'public'; +export const WorkspaceAgentPortShareLevels: WorkspaceAgentPortShareLevel[] = [ + 'authenticated', + 'owner', + 'public', +]; + +// From codersdk/workspaceagentportshare.go +export type WorkspaceAgentPortShareProtocol = 'http' | 'https'; +export const WorkspaceAgentPortShareProtocols: WorkspaceAgentPortShareProtocol[] = + ['http', 'https']; + +// From codersdk/workspaceagents.go +export type WorkspaceAgentStartupScriptBehavior = 'blocking' | 'non-blocking'; +export const WorkspaceAgentStartupScriptBehaviors: WorkspaceAgentStartupScriptBehavior[] = + ['blocking', 'non-blocking']; + +// From codersdk/workspaceagents.go +export type WorkspaceAgentStatus = + | 'connected' + | 'connecting' + | 'disconnected' + | 'timeout'; +export const WorkspaceAgentStatuses: WorkspaceAgentStatus[] = [ + 'connected', + 'connecting', + 'disconnected', + 'timeout', +]; + +// From codersdk/workspaceapps.go +export type WorkspaceAppHealth = + | 'disabled' + | 'healthy' + | 'initializing' + | 'unhealthy'; +export const WorkspaceAppHealths: WorkspaceAppHealth[] = [ + 'disabled', + 'healthy', + 'initializing', + 'unhealthy', +]; + +// From codersdk/workspaceapps.go +export type WorkspaceAppSharingLevel = 'authenticated' | 'owner' | 'public'; +export const WorkspaceAppSharingLevels: WorkspaceAppSharingLevel[] = [ + 'authenticated', + 'owner', + 'public', +]; + +// From codersdk/workspacebuilds.go +export type WorkspaceStatus = + | 'canceled' + | 'canceling' + | 'deleted' + | 'deleting' + | 'failed' + | 'pending' + | 'running' + | 'starting' + | 'stopped' + | 'stopping'; +export const WorkspaceStatuses: WorkspaceStatus[] = [ + 'canceled', + 'canceling', + 'deleted', + 'deleting', + 'failed', + 'pending', + 'running', + 'starting', + 'stopped', + 'stopping', +]; + +// From codersdk/workspacebuilds.go +export type WorkspaceTransition = 'delete' | 'start' | 'stop'; +export const WorkspaceTransitions: WorkspaceTransition[] = [ + 'delete', + 'start', + 'stop', +]; + +// From codersdk/workspaceproxy.go +export type RegionTypes = Region | WorkspaceProxy; + +// The code below is generated from codersdk/healthsdk. + +// From healthsdk/healthsdk.go +export interface AccessURLReport extends BaseReport { + readonly healthy: boolean; + readonly access_url: string; + readonly reachable: boolean; + readonly status_code: number; + readonly healthz_response: string; +} + +// From healthsdk/healthsdk.go +export interface BaseReport { + readonly error?: string; + readonly severity: HealthSeverity; + readonly warnings: readonly HealthMessage[]; + readonly dismissed: boolean; +} + +// From healthsdk/healthsdk.go +export interface DERPHealthReport extends BaseReport { + readonly healthy: boolean; + readonly regions: Record; + // Named type "tailscale.com/net/netcheck.Report" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type + readonly netcheck?: any; + readonly netcheck_err?: string; + readonly netcheck_logs: readonly string[]; +} + +// From healthsdk/healthsdk.go +export interface DERPNodeReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: readonly HealthMessage[]; + readonly error?: string; + // Named type "tailscale.com/tailcfg.DERPNode" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type + readonly node?: any; + // Named type "tailscale.com/derp.ServerInfoMessage" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type + readonly node_info: any; + readonly can_exchange_messages: boolean; + readonly round_trip_ping: string; + readonly round_trip_ping_ms: number; + readonly uses_websocket: boolean; + readonly client_logs: readonly (readonly string[])[]; + readonly client_errs: readonly (readonly string[])[]; + readonly stun: STUNReport; +} + +// From healthsdk/healthsdk.go +export interface DERPRegionReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: readonly HealthMessage[]; + readonly error?: string; + // Named type "tailscale.com/tailcfg.DERPRegion" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type + readonly region?: any; + readonly node_reports: readonly DERPNodeReport[]; +} + +// From healthsdk/healthsdk.go +export interface DatabaseReport extends BaseReport { + readonly healthy: boolean; + readonly reachable: boolean; + readonly latency: string; + readonly latency_ms: number; + readonly threshold_ms: number; +} + +// From healthsdk/healthsdk.go +export interface HealthSettings { + readonly dismissed_healthchecks: readonly HealthSection[]; +} + +// From healthsdk/healthsdk.go +export interface HealthcheckReport { + readonly time: string; + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly failing_sections: readonly HealthSection[]; + readonly derp: DERPHealthReport; + readonly access_url: AccessURLReport; + readonly websocket: WebsocketReport; + readonly database: DatabaseReport; + readonly workspace_proxy: WorkspaceProxyReport; + readonly provisioner_daemons: ProvisionerDaemonsReport; + readonly coder_version: string; +} + +// From healthsdk/healthsdk.go +export interface ProvisionerDaemonsReport extends BaseReport { + readonly items: readonly ProvisionerDaemonsReportItem[]; +} + +// From healthsdk/healthsdk.go +export interface ProvisionerDaemonsReportItem { + readonly provisioner_daemon: ProvisionerDaemon; + readonly warnings: readonly HealthMessage[]; +} + +// From healthsdk/healthsdk.go +export interface STUNReport { + readonly Enabled: boolean; + readonly CanSTUN: boolean; + readonly Error?: string; +} + +// From healthsdk/healthsdk.go +export interface UpdateHealthSettings { + readonly dismissed_healthchecks: readonly HealthSection[]; +} + +// From healthsdk/healthsdk.go +export interface WebsocketReport extends BaseReport { + readonly healthy: boolean; + readonly body: string; + readonly code: number; +} + +// From healthsdk/healthsdk.go +export interface WorkspaceProxyReport extends BaseReport { + readonly healthy: boolean; + readonly workspace_proxies: RegionsResponse; +} + +// From healthsdk/healthsdk.go +export type HealthSection = + | 'AccessURL' + | 'DERP' + | 'Database' + | 'ProvisionerDaemons' + | 'Websocket' + | 'WorkspaceProxy'; +export const HealthSections: HealthSection[] = [ + 'AccessURL', + 'DERP', + 'Database', + 'ProvisionerDaemons', + 'Websocket', + 'WorkspaceProxy', +]; + +// The code below is generated from coderd/healthcheck/health. + +// From health/model.go +export interface HealthMessage { + readonly code: HealthCode; + readonly message: string; +} + +// From health/model.go +export type HealthCode = + | 'EACS01' + | 'EACS02' + | 'EACS03' + | 'EACS04' + | 'EDB01' + | 'EDB02' + | 'EDERP01' + | 'EDERP02' + | 'EPD01' + | 'EPD02' + | 'EPD03' + | 'EUNKNOWN' + | 'EWP01' + | 'EWP02' + | 'EWP04' + | 'EWS01' + | 'EWS02' + | 'EWS03'; +export const HealthCodes: HealthCode[] = [ + 'EACS01', + 'EACS02', + 'EACS03', + 'EACS04', + 'EDB01', + 'EDB02', + 'EDERP01', + 'EDERP02', + 'EPD01', + 'EPD02', + 'EPD03', + 'EUNKNOWN', + 'EWP01', + 'EWP02', + 'EWP04', + 'EWS01', + 'EWS02', + 'EWS03', +]; + +// From health/model.go +export type HealthSeverity = 'error' | 'ok' | 'warning'; +export const HealthSeveritys: HealthSeverity[] = ['error', 'ok', 'warning']; + +// The code below is generated from github.com/coder/serpent. + +// From serpent/serpent.go +export type SerpentAnnotations = Record; + +// From serpent/serpent.go +export interface SerpentGroup { + readonly parent?: SerpentGroup; + readonly name?: string; + readonly yaml?: string; + readonly description?: string; +} + +// From serpent/option.go +export interface SerpentOption { + readonly name?: string; + readonly description?: string; + readonly required?: boolean; + readonly flag?: string; + readonly flag_shorthand?: string; + readonly env?: string; + readonly yaml?: string; + readonly default?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Golang interface, unable to resolve type. + readonly value?: any; + readonly annotations?: SerpentAnnotations; + readonly group?: SerpentGroup; + readonly use_instead?: readonly SerpentOption[]; + readonly hidden?: boolean; + readonly value_source?: SerpentValueSource; +} + +// From serpent/option.go +export type SerpentOptionSet = readonly SerpentOption[]; + +// From serpent/option.go +export type SerpentValueSource = '' | 'default' | 'env' | 'flag' | 'yaml'; +export const SerpentValueSources: SerpentValueSource[] = [ + '', + 'default', + 'env', + 'flag', + 'yaml', +]; diff --git a/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts b/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts new file mode 100644 index 00000000..df99ae32 --- /dev/null +++ b/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts @@ -0,0 +1,8 @@ +/** + * Right now the file is doing barrel exports. But if something more + * sophisticated is needed down the line, those changes should be handled in + * this file, to provide some degree of insulation between the vendored files + * and the rest of the plugin logic. + */ +export * from './api/api'; +export type * from './api/typesGenerated'; diff --git a/plugins/backstage-plugin-coder/src/api/vendoredSdk/utils/delay.ts b/plugins/backstage-plugin-coder/src/api/vendoredSdk/utils/delay.ts new file mode 100644 index 00000000..b915a7fb --- /dev/null +++ b/plugins/backstage-plugin-coder/src/api/vendoredSdk/utils/delay.ts @@ -0,0 +1,4 @@ +export const delay = (ms: number): Promise => + new Promise(res => { + setTimeout(res, ms); + }); diff --git a/yarn.lock b/yarn.lock index e7553d7d..c287f84a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8919,6 +8919,11 @@ dependencies: "@types/node" "*" +"@types/ua-parser-js@^0.7.39": + version "0.7.39" + resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz#832c58e460c9435e4e34bb866e85e9146e12cdbb" + integrity sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg== + "@types/unist@^2", "@types/unist@^2.0.0": version "2.0.10" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc" @@ -11859,6 +11864,11 @@ dateformat@^3.0.3: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +dayjs@^1.11.11: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== + debounce@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" @@ -20248,14 +20258,6 @@ react-dom@^18.0.2: loose-envify "^1.1.0" scheduler "^0.23.0" -react-dom@^18.3.1: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" - integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== - dependencies: - loose-envify "^1.1.0" - scheduler "^0.23.2" - react-double-scrollbar@0.0.15: version "0.0.15" resolved "https://registry.yarnpkg.com/react-double-scrollbar/-/react-double-scrollbar-0.0.15.tgz#e915ab8cb3b959877075f49436debfdb04288fe4" @@ -21188,13 +21190,6 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" -scheduler@^0.23.2: - version "0.23.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" - integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== - dependencies: - loose-envify "^1.1.0" - schema-utils@2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" @@ -22953,6 +22948,11 @@ typescript@~5.2.0: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +ua-parser-js@^1.0.37: + version "1.0.37" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f" + integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ== + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" From 5e7e01f464ad29ac750fb936b05a6c04da16b4b0 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 24 May 2024 17:57:39 +0000 Subject: [PATCH 2/4] fix: improve data hiding for CoderSdk --- .../src/api/vendoredSdk/index.ts | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts b/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts index df99ae32..bcacd351 100644 --- a/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts +++ b/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts @@ -1,8 +1,31 @@ -/** - * Right now the file is doing barrel exports. But if something more - * sophisticated is needed down the line, those changes should be handled in - * this file, to provide some degree of insulation between the vendored files - * and the rest of the plugin logic. - */ -export * from './api/api'; export type * from './api/typesGenerated'; +export type { + DeleteWorkspaceOptions, + GetLicensesResponse, + InsightsParams, + InsightsTemplateParams, +} from './api/api'; +import { Api } from './api/api'; + +// Union of all API properties that won't ever be relevant to Backstage users. +// Not a huge deal that they still exist at runtime; mainly concerned about +// whether they pollute Intellisense when someone is using the SDK. Most of +// these properties don't deal with APIs and are mainly helpers in Core +type PropertyToHide = + | 'getJFrogXRayScan' + | 'getCsrfToken' + | 'setSessionToken' + | 'setHost' + | 'getAvailableExperiments' + | 'login' + | 'logout'; + +// Wanted to have a CoderSdk class (mainly re-exporting the Api class as itself +// with the extra properties omitted). But because classes are wonky and exist +// as both runtime values and times, it didn't seem possible, even with things +// like class declarations. Making a new function is good enough for now, though +export type CoderSdk = Omit; +export function makeCoderSdk(): CoderSdk { + const api = new Api(); + return api as CoderSdk; +} From 937f6f51572dbbca45b9172e92159b8b976a081a Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 24 May 2024 17:59:45 +0000 Subject: [PATCH 3/4] docs: update typo --- plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts b/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts index bcacd351..b64f8419 100644 --- a/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts +++ b/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts @@ -22,7 +22,7 @@ type PropertyToHide = // Wanted to have a CoderSdk class (mainly re-exporting the Api class as itself // with the extra properties omitted). But because classes are wonky and exist -// as both runtime values and times, it didn't seem possible, even with things +// as both runtime values and types, it didn't seem possible, even with things // like class declarations. Making a new function is good enough for now, though export type CoderSdk = Omit; export function makeCoderSdk(): CoderSdk { From 977b2ebb829d2e3e99acca16efc9274e42bbd472 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 31 May 2024 15:30:22 +0000 Subject: [PATCH 4/4] fix: add additional properties to hide from SDK --- .../backstage-plugin-coder/src/api/vendoredSdk/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts b/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts index b64f8419..f8451116 100644 --- a/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts +++ b/plugins/backstage-plugin-coder/src/api/vendoredSdk/index.ts @@ -18,7 +18,12 @@ type PropertyToHide = | 'setHost' | 'getAvailableExperiments' | 'login' - | 'logout'; + | 'logout' + | 'convertToOAUTH' + | 'waitForBuild' + | 'addMember' + | 'removeMember' + | 'getWorkspaceParameters'; // Wanted to have a CoderSdk class (mainly re-exporting the Api class as itself // with the extra properties omitted). But because classes are wonky and exist