From 0f2f539bb11f71255c027b530d39c34d1e2dd65d Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 28 Aug 2023 16:11:38 +0000 Subject: [PATCH 1/9] docs: add offline gateway configuration steps --- docs/ides/gateway.md | 86 ++++++++++++++++++++++++ docs/images/gateway/offline-gateway.png | Bin 0 -> 129510 bytes 2 files changed, 86 insertions(+) create mode 100644 docs/images/gateway/offline-gateway.png diff --git a/docs/ides/gateway.md b/docs/ides/gateway.md index eb27158a45b2b..6de0d88864bd4 100644 --- a/docs/ides/gateway.md +++ b/docs/ides/gateway.md @@ -188,3 +188,89 @@ In networks that restrict access to the internet, you will need to leverage the JetBrains Client Installer to download and save the IDE clients locally. Please see the [JetBrains documentation for more information](https://www.jetbrains.com/help/idea/fully-offline-mode.html). + +### Configuration Steps + +The Coder team built a POC of the JetBrains Gateway Offline Mode solution. Here are +the steps we took (and "gotchas"): + +### 1. Deploy the server and install the Client Downloader + +We deployed a simple Ubuntu VM and installed the JetBrains Client Downloader binary. Note +that the server must be a Linux-based distribution. + +```shell +wget https://download.jetbrains.com/idea/code-with-me/backend/jetbrains-clients-downloader-linux-x86_64-1867.tar.gz && \ +tar -xzvf jetbrains-clients-downloader-linux-x86_64-1867.tar.gz +``` + +### 2. Install backends and clients + +JetBrains Gateway requires both a backend to be installed on the remote host (your Coder workspace) +and a client to be installed on your local machine. You can host both on the server +in this example. + +See here for the full [JetBrains product list and builds](https://data.services.jetbrains.com/products). +Below the full list of supported `--platforms-filter` values: + +```console +windows-x64, windows-aarch64, linux-x64, linux-aarch64, osx-x64, osx-aarch64 +``` + +To install both backends and clients, you will need to run two commands. + +**Backends** + +```shell +./jetbrains-clients-downloader-linux-x86_64-1867/bin4/jetbrains-clients-downloader --products-filter --build-filter --platforms-filter linux-x64 --download-backends ~/backends +``` + +**Clients** + +This is the same command as above, with the `--download-backends` flag removed. + +```shell +./jetbrains-clients-downloader-linux-x86_64-1867/bin4/jetbrains-clients-downloader --products-filter --build-filter --platforms-filter linux-x64 ~/clients +``` + +We now have both clients and backends installed in + +### 3. Install a web server + +You will need to run a web server in order to serve requests to the backend and client +files. We installed `nginx` and setup an FQDN and routed all requests to `/`. See below: + +```console +server { + listen 80 default_server; + listen [::]:80 default_server; + + root /var/www/html; + + index index.html index.htm index.nginx-debian.html; + + server_name _; + + location / { + root /home/ubuntu; + } +} +``` + +Then, configure your DNS entry to point to the IP address of the server. For the +purposes of the POC, we did not configure TLS, although that is a supported option. + +### 4. Setup SSH connection with JetBrains Gateway + +With the server now configured, you can now configure your local machine to use +Gateway. Here is the documentation to [setup SSH config via the Coder CLI](../ides.md#ssh-configuration). +On the Gateway side, follow our guide here until step 16. + +Instead of downloading from jetbrains.com, we will point Gateway to our server endpoint. +Select `Installation options...` and select `Use download link`. Note that the URL +must explicitly reference the archive file: + +![Offline Gateway](../images/gateway/offline-gateway.png) + +Click `Download IDE and Connect`. Gateway should now download the backend and clients +from the server into your remote workspace and local machine, respectively. diff --git a/docs/images/gateway/offline-gateway.png b/docs/images/gateway/offline-gateway.png new file mode 100644 index 0000000000000000000000000000000000000000..754c6c6ae568c4ad595cf0dddf3140050b321253 GIT binary patch literal 129510 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

^aPPi z%%}YvZUH|QWH_I0`EFy|W!dHK6l6GCpfjzT5DFUwh{&JjZ z969j0a47ENmWDUrYi=-Uew1uRMrLIn!&v=>e;m4HQ;KvWesCqfGr~#B7%0Hp?In!*=HwJl8ZBLo zua8$~k-CfCTTi+cxQnBH8b?>u;;SaWu)t2mT7-4-OL`jw550`0aJFU(NG_uqSvvmpMOuX78n~G;NDU(K-@6Cj&q!(AtSZ@Ys23~@lpx!=NV@uCBdm%Sb#z_u(LX`Pk>TfHXp7Xt8#y72wG-))o zx*j58V%KANV?x4x7)P%Q@lIaBO~{Hix%cNFLkE#YO}|i?+3Aba$a0LpZP*HIKdyYX zOgdI&o|bP=d|wi?d7;y4L|@EMQd1&nA%&mLG~aEhB*4v-41}NYoZufR^X)Q8G*i@} z!rRc9W215GilWSw)9r2o@f|T^n6#b#-e1)TiNza{7q>UOkd@Y9<|+_dH3P19zhsy1 zhthL@B@BLqs>R(^{ya4F99}S0@^UP%#1w5YhB?t*PSLNl;Kp?ZqHe1iMMS-$OowMv zFWGB`Fl8JyL~7C)8O+;x<7ytl^rjcT+3-KHUGOd*35ThKsA@@R>Eed-bi`QL$zv5& zRo^>i-Y5ZZ!*z#xH*n<~#Qorj=Dy<72G$NjXwbz!Cjun{Zz4+8uJ{+|aOatm${ts< z%-40(kW0{*DFau728jiJCsW;7jl+Y^)4dHobt3-Iz+k_B5hvk&eC!^N-elH#^oTvp zx?Y*|x+vMGj8lzk4rrk~&vtBz-Z+|fraG-{@jN%~FFcg|`hQ3#KP0HL)LhflYgzl{$n|& z0cm{PHaNUhJItybsxC5NZo1v>OV(bEs-6Qqxeaw;<$Yn^WG-=$S}Ge)QAP^T+q4$` zKLf#1)Hq+7458uHh%lZMRB>AW;@P8^3lPvtbSVWbE$V#K-#k$)c|O9dwRwVj(RRy4 z20|W4hxoZ$^rLu)^!a|teIF1A)KQddd>i*6XVkUO7Espc4&RQbTf%yfTesKUk^$@c z!hbyP`wgD01f{)Wjdjs5W1Ld5fcUIt9jdaH-2 zLuoTOtodwG_&?*4!ivlI@88CSefRs{R_*F#+efHb@yZ=%#;eweJx~&LQycR73dFmJct?d6t zrxtocU6=@%gL@D^9P2W>It8s~|O}W2kj~xNfOwmk3luvCiZFQcKvP zf-4cVOI}|6lVjqve87dzfcFrRf3)W}o^} zC|CuT0RG_gIN20(e`9uOKf_dGnL|zI+xiznZJ)Iw&e7L}NdKSTMT|KfXL zz(d@gt%;28)huzvlBdmHi>a4hbQBoYtSxkCX=%M>O4G%<#!c6KETOG`mnhMt$K<>loQJ@}0O?4US2-%<>aF$)L)ak~Y1l8^vbH{!oYm0a#U zRbUtVtvB#PNReK@DA-~)%&xP&{a(q*DdsiiN>!u2mI0S|?`!SF#YIQ$1m->eE|EW* z{Ef5+`DR>dUK#7zJHXrByKNo(H``)R_0;{heRkf%R7a|8#uAo)2$(jRlacD}zfnBvIR*K2 z&*{&Il5G;1@e-5VmXnxsO9X*KRD=P%T56)WD-MzZ85-MNL;Xcap8gd2q|MvNR8{Kg7 literal 0 HcmV?d00001 From 5911805d651894d51b4704c15b7f7526b154570f Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 28 Aug 2023 16:21:04 +0000 Subject: [PATCH 2/9] grammar --- docs/ides/gateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ides/gateway.md b/docs/ides/gateway.md index 6de0d88864bd4..fd1f08721c825 100644 --- a/docs/ides/gateway.md +++ b/docs/ides/gateway.md @@ -211,7 +211,7 @@ and a client to be installed on your local machine. You can host both on the ser in this example. See here for the full [JetBrains product list and builds](https://data.services.jetbrains.com/products). -Below the full list of supported `--platforms-filter` values: +Below is the full list of supported `--platforms-filter` values: ```console windows-x64, windows-aarch64, linux-x64, linux-aarch64, osx-x64, osx-aarch64 From 0737be02ad9e773355dfc811e483ff4cf2e39b2b Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 28 Aug 2023 16:45:22 +0000 Subject: [PATCH 3/9] make: fmt & grammar --- docs/ides/gateway.md | 43 +- site/src/api/api.ts | 1467 ----------------- .../templateVersionXService.ts | 179 -- 3 files changed, 24 insertions(+), 1665 deletions(-) delete mode 100644 site/src/api/api.ts delete mode 100644 site/src/xServices/templateVersion/templateVersionXService.ts diff --git a/docs/ides/gateway.md b/docs/ides/gateway.md index fd1f08721c825..03b5d598265bd 100644 --- a/docs/ides/gateway.md +++ b/docs/ides/gateway.md @@ -191,13 +191,13 @@ see the ### Configuration Steps -The Coder team built a POC of the JetBrains Gateway Offline Mode solution. Here are -the steps we took (and "gotchas"): +The Coder team built a POC of the JetBrains Gateway Offline Mode solution. Here +are the steps we took (and "gotchas"): ### 1. Deploy the server and install the Client Downloader -We deployed a simple Ubuntu VM and installed the JetBrains Client Downloader binary. Note -that the server must be a Linux-based distribution. +We deployed a simple Ubuntu VM and installed the JetBrains Client Downloader +binary. Note that the server must be a Linux-based distribution. ```shell wget https://download.jetbrains.com/idea/code-with-me/backend/jetbrains-clients-downloader-linux-x86_64-1867.tar.gz && \ @@ -206,11 +206,12 @@ tar -xzvf jetbrains-clients-downloader-linux-x86_64-1867.tar.gz ### 2. Install backends and clients -JetBrains Gateway requires both a backend to be installed on the remote host (your Coder workspace) -and a client to be installed on your local machine. You can host both on the server -in this example. +JetBrains Gateway requires both a backend to be installed on the remote host +(your Coder workspace) and a client to be installed on your local machine. You +can host both on the server in this example. -See here for the full [JetBrains product list and builds](https://data.services.jetbrains.com/products). +See here for the full +[JetBrains product list and builds](https://data.services.jetbrains.com/products). Below is the full list of supported `--platforms-filter` values: ```console @@ -233,12 +234,13 @@ This is the same command as above, with the `--download-backends` flag removed. ./jetbrains-clients-downloader-linux-x86_64-1867/bin4/jetbrains-clients-downloader --products-filter --build-filter --platforms-filter linux-x64 ~/clients ``` -We now have both clients and backends installed in +We now have both clients and backends installed. ### 3. Install a web server -You will need to run a web server in order to serve requests to the backend and client -files. We installed `nginx` and setup an FQDN and routed all requests to `/`. See below: +You will need to run a web server in order to serve requests to the backend and +client files. We installed `nginx` and setup an FQDN and routed all requests to +`/`. See below: ```console server { @@ -258,19 +260,22 @@ server { ``` Then, configure your DNS entry to point to the IP address of the server. For the -purposes of the POC, we did not configure TLS, although that is a supported option. +purposes of the POC, we did not configure TLS, although that is a supported +option. ### 4. Setup SSH connection with JetBrains Gateway With the server now configured, you can now configure your local machine to use -Gateway. Here is the documentation to [setup SSH config via the Coder CLI](../ides.md#ssh-configuration). -On the Gateway side, follow our guide here until step 16. +Gateway. Here is the documentation to +[setup SSH config via the Coder CLI](../ides.md#ssh-configuration). On the +Gateway side, follow our guide here until step 16. -Instead of downloading from jetbrains.com, we will point Gateway to our server endpoint. -Select `Installation options...` and select `Use download link`. Note that the URL -must explicitly reference the archive file: +Instead of downloading from jetbrains.com, we will point Gateway to our server +endpoint. Select `Installation options...` and select `Use download link`. Note +that the URL must explicitly reference the archive file: ![Offline Gateway](../images/gateway/offline-gateway.png) -Click `Download IDE and Connect`. Gateway should now download the backend and clients -from the server into your remote workspace and local machine, respectively. +Click `Download IDE and Connect`. Gateway should now download the backend and +clients from the server into your remote workspace and local machine, +respectively. diff --git a/site/src/api/api.ts b/site/src/api/api.ts deleted file mode 100644 index 3567e4f977332..0000000000000 --- a/site/src/api/api.ts +++ /dev/null @@ -1,1467 +0,0 @@ -import axios from "axios" -import dayjs from "dayjs" -import * as Types from "./types" -import { DeploymentConfig } from "./types" -import * as TypesGen from "./typesGenerated" -import { delay } from "utils/delay" -import userAgentParser from "ua-parser-js" - -// Adds 304 for the default axios validateStatus function -// https://github.com/axios/axios#handling-errors Check status here -// https://httpstatusdogs.com/ -axios.defaults.validateStatus = (status) => { - return (status >= 200 && status < 300) || status === 304 -} - -export const hardCodedCSRFCookie = (): string => { - // 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==" - axios.defaults.headers.common["X-CSRF-TOKEN"] = csrfToken - return csrfToken -} - -// 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"] -} - -// Always attach CSRF token to all requests. In puppeteer the document is -// undefined. In those cases, just do nothing. -const token = - typeof document !== "undefined" - ? document.head.querySelector('meta[property="csrf-token"]') - : null - -if (token !== null && token.getAttribute("content") !== null) { - if (process.env.NODE_ENV === "development") { - // Development mode uses a hard-coded CSRF token - axios.defaults.headers.common["X-CSRF-TOKEN"] = hardCodedCSRFCookie() - token.setAttribute("content", hardCodedCSRFCookie()) - } else { - axios.defaults.headers.common["X-CSRF-TOKEN"] = - token.getAttribute("content") ?? "" - } -} else { - // Do not write error logs if we are in a FE unit test. - if (process.env.JEST_WORKER_ID === undefined) { - console.error("CSRF token not found") - } -} - -const CONTENT_TYPE_JSON = { - "Content-Type": "application/json", -} - -export const provisioners: TypesGen.ProvisionerDaemon[] = [ - { - id: "terraform", - name: "Terraform", - created_at: "", - provisioners: [], - tags: {}, - }, - { - id: "cdr-basic", - name: "Basic", - created_at: "", - provisioners: [], - tags: {}, - }, -] - -export const login = async ( - email: string, - password: string, -): Promise => { - const payload = JSON.stringify({ - email, - password, - }) - - const response = await axios.post( - "/api/v2/users/login", - payload, - { - headers: { ...CONTENT_TYPE_JSON }, - }, - ) - - return response.data -} - -export const convertToOAUTH = async (request: TypesGen.ConvertLoginRequest) => { - const response = await axios.post( - "/api/v2/users/me/convert-login", - request, - ) - return response.data -} - -export const logout = async (): Promise => { - await axios.post("/api/v2/users/logout") -} - -export const getAuthenticatedUser = async (): Promise< - TypesGen.User | undefined -> => { - try { - const response = await axios.get("/api/v2/users/me") - return response.data - } catch (error) { - if (axios.isAxiosError(error) && error.response?.status === 401) { - return undefined - } - - throw error - } -} - -export const getAuthMethods = async (): Promise => { - const response = await axios.get( - "/api/v2/users/authmethods", - ) - return response.data -} - -export const getUserLoginType = async (): Promise => { - const response = await axios.get( - "/api/v2/users/me/login-type", - ) - return response.data -} - -export const checkAuthorization = async ( - params: TypesGen.AuthorizationRequest, -): Promise => { - const response = await axios.post( - `/api/v2/authcheck`, - params, - ) - return response.data -} - -export const getApiKey = async (): Promise => { - const response = await axios.post( - "/api/v2/users/me/keys", - ) - return response.data -} - -export const getTokens = async ( - params: TypesGen.TokensFilter, -): Promise => { - const response = await axios.get( - `/api/v2/users/me/keys/tokens`, - { - params, - }, - ) - return response.data -} - -export const deleteToken = async (keyId: string): Promise => { - await axios.delete("/api/v2/users/me/keys/" + keyId) -} - -export const createToken = async ( - params: TypesGen.CreateTokenRequest, -): Promise => { - const response = await axios.post(`/api/v2/users/me/keys/tokens`, params) - return response.data -} - -export const getTokenConfig = async (): Promise => { - const response = await axios.get("/api/v2/users/me/keys/tokens/tokenconfig") - return response.data -} - -export const getUsers = async ( - options: TypesGen.UsersRequest, -): Promise => { - const url = getURLWithSearchParams("/api/v2/users", options) - const response = await axios.get(url.toString()) - return response.data -} - -export const getOrganization = async ( - organizationId: string, -): Promise => { - const response = await axios.get( - `/api/v2/organizations/${organizationId}`, - ) - return response.data -} - -export const getOrganizations = async (): Promise => { - const response = await axios.get( - "/api/v2/users/me/organizations", - ) - return response.data -} - -export const getTemplate = async ( - templateId: string, -): Promise => { - const response = await axios.get( - `/api/v2/templates/${templateId}`, - ) - return response.data -} - -export const getTemplates = async ( - organizationId: string, -): Promise => { - const response = await axios.get( - `/api/v2/organizations/${organizationId}/templates`, - ) - return response.data -} - -export const getTemplateByName = async ( - organizationId: string, - name: string, -): Promise => { - const response = await axios.get( - `/api/v2/organizations/${organizationId}/templates/${name}`, - ) - return response.data -} - -export const getTemplateVersion = async ( - versionId: string, -): Promise => { - const response = await axios.get( - `/api/v2/templateversions/${versionId}`, - ) - return response.data -} - -export const getTemplateVersionResources = async ( - versionId: string, -): Promise => { - const response = await axios.get( - `/api/v2/templateversions/${versionId}/resources`, - ) - return response.data -} - -export const getTemplateVersionVariables = async ( - versionId: string, -): Promise => { - const response = await axios.get( - `/api/v2/templateversions/${versionId}/variables`, - ) - return response.data -} - -export const getTemplateVersions = async ( - templateId: string, -): Promise => { - const response = await axios.get( - `/api/v2/templates/${templateId}/versions`, - ) - return response.data -} - -export const getTemplateVersionByName = async ( - organizationId: string, - templateName: string, - versionName: string, -): Promise => { - const response = await axios.get( - `/api/v2/organizations/${organizationId}/templates/${templateName}/versions/${versionName}`, - ) - return response.data -} - -export type GetPreviousTemplateVersionByNameResponse = - | TypesGen.TemplateVersion - | undefined - -export const getPreviousTemplateVersionByName = async ( - organizationId: string, - templateName: string, - versionName: string, -): Promise => { - try { - const response = await 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 - if ( - axios.isAxiosError(error) && - error.response && - error.response.status === 404 - ) { - return undefined - } - - throw error - } -} - -export const createTemplateVersion = async ( - organizationId: string, - data: TypesGen.CreateTemplateVersionRequest, -): Promise => { - const response = await axios.post( - `/api/v2/organizations/${organizationId}/templateversions`, - data, - ) - return response.data -} - -export const getTemplateVersionGitAuth = async ( - versionId: string, -): Promise => { - const response = await axios.get( - `/api/v2/templateversions/${versionId}/gitauth`, - ) - return response.data -} - -export const getTemplateVersionRichParameters = async ( - versionId: string, -): Promise => { - const response = await axios.get( - `/api/v2/templateversions/${versionId}/rich-parameters`, - ) - return response.data -} - -export const createTemplate = async ( - organizationId: string, - data: TypesGen.CreateTemplateRequest, -): Promise => { - const response = await axios.post( - `/api/v2/organizations/${organizationId}/templates`, - data, - ) - return response.data -} - -export const updateActiveTemplateVersion = async ( - templateId: string, - data: TypesGen.UpdateActiveTemplateVersion, -): Promise => { - const response = await axios.patch( - `/api/v2/templates/${templateId}/versions`, - data, - ) - return response.data -} - -export const patchTemplateVersion = async ( - templateVersionId: string, - data: TypesGen.PatchTemplateVersionRequest, -) => { - const response = await axios.patch( - `/api/v2/templateversions/${templateVersionId}`, - data, - ) - return response.data -} - -export const updateTemplateMeta = async ( - templateId: string, - data: TypesGen.UpdateTemplateMeta, -): Promise => { - const response = await axios.patch( - `/api/v2/templates/${templateId}`, - data, - ) - return response.data -} - -export const deleteTemplate = async ( - templateId: string, -): Promise => { - const response = await axios.delete( - `/api/v2/templates/${templateId}`, - ) - return response.data -} - -export const getWorkspace = async ( - workspaceId: string, - params?: TypesGen.WorkspaceOptions, -): Promise => { - const response = await axios.get( - `/api/v2/workspaces/${workspaceId}`, - { - params, - }, - ) - return response.data -} - -/** - * - * @param workspaceId - * @returns 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 }, - ) -} - -interface SearchParamOptions extends TypesGen.Pagination { - q?: string -} - -export const getURLWithSearchParams = ( - basePath: string, - options?: SearchParamOptions, -): string => { - if (options) { - 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 - } else { - return basePath - } -} - -export const getWorkspaces = async ( - options: TypesGen.WorkspacesRequest, -): Promise => { - const url = getURLWithSearchParams("/api/v2/workspaces", options) - const response = await axios.get(url) - return response.data -} - -export const getWorkspaceByOwnerAndName = async ( - username = "me", - workspaceName: string, - params?: TypesGen.WorkspaceOptions, -): Promise => { - const response = await axios.get( - `/api/v2/users/${username}/workspace/${workspaceName}`, - { - params, - }, - ) - return response.data -} - -export function waitForBuild(build: TypesGen.WorkspaceBuild) { - return new Promise((res, reject) => { - void (async () => { - let latestJobInfo: TypesGen.ProvisionerJob | undefined = undefined - - while ( - !["succeeded", "canceled"].some( - (status) => latestJobInfo?.status.includes(status), - ) - ) { - const { job } = await 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) - })() - }) -} - -export const postWorkspaceBuild = async ( - workspaceId: string, - data: TypesGen.CreateWorkspaceBuildRequest, -): Promise => { - const response = await axios.post( - `/api/v2/workspaces/${workspaceId}/builds`, - data, - ) - return response.data -} - -export const startWorkspace = ( - workspaceId: string, - templateVersionId: string, - logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], - buildParameters?: TypesGen.WorkspaceBuildParameter[], -) => - postWorkspaceBuild(workspaceId, { - transition: "start", - template_version_id: templateVersionId, - log_level: logLevel, - rich_parameter_values: buildParameters, - }) -export const stopWorkspace = ( - workspaceId: string, - logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], -) => - postWorkspaceBuild(workspaceId, { - transition: "stop", - log_level: logLevel, - }) - -export const deleteWorkspace = ( - workspaceId: string, - logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], -) => - postWorkspaceBuild(workspaceId, { - transition: "delete", - log_level: logLevel, - }) - -export const cancelWorkspaceBuild = async ( - workspaceBuildId: TypesGen.WorkspaceBuild["id"], -): Promise => { - const response = await axios.patch( - `/api/v2/workspacebuilds/${workspaceBuildId}/cancel`, - ) - return response.data -} - -export const updateWorkspaceDormancy = async ( - workspaceId: string, - dormant: boolean, -): Promise => { - const data: TypesGen.UpdateWorkspaceDormancy = { - dormant: dormant, - } - - const response = await axios.put( - `/api/v2/workspaces/${workspaceId}/dormant`, - data, - ) - return response.data -} - -export const restartWorkspace = async ({ - workspace, - buildParameters, -}: { - workspace: TypesGen.Workspace - buildParameters?: TypesGen.WorkspaceBuildParameter[] -}) => { - const stopBuild = await stopWorkspace(workspace.id) - const awaitedStopBuild = await waitForBuild(stopBuild) - - // If the restart is canceled halfway through, make sure we bail - if (awaitedStopBuild?.status === "canceled") { - return - } - - const startBuild = await startWorkspace( - workspace.id, - workspace.latest_build.template_version_id, - undefined, - buildParameters, - ) - await waitForBuild(startBuild) -} - -export const cancelTemplateVersionBuild = async ( - templateVersionId: TypesGen.TemplateVersion["id"], -): Promise => { - const response = await axios.patch( - `/api/v2/templateversions/${templateVersionId}/cancel`, - ) - return response.data -} - -export const createUser = async ( - user: TypesGen.CreateUserRequest, -): Promise => { - const response = await axios.post("/api/v2/users", user) - return response.data -} - -export const createWorkspace = async ( - organizationId: string, - userId = "me", - workspace: TypesGen.CreateWorkspaceRequest, -): Promise => { - const response = await axios.post( - `/api/v2/organizations/${organizationId}/members/${userId}/workspaces`, - workspace, - ) - return response.data -} - -export const patchWorkspace = async ( - workspaceId: string, - data: TypesGen.UpdateWorkspaceRequest, -) => { - await axios.patch(`/api/v2/workspaces/${workspaceId}`, data) -} - -export const getBuildInfo = async (): Promise => { - const response = await axios.get("/api/v2/buildinfo") - return response.data -} - -export const getUpdateCheck = - async (): Promise => { - const response = await axios.get("/api/v2/updatecheck") - return response.data - } - -export const putWorkspaceAutostart = async ( - workspaceID: string, - autostart: TypesGen.UpdateWorkspaceAutostartRequest, -): Promise => { - const payload = JSON.stringify(autostart) - await axios.put(`/api/v2/workspaces/${workspaceID}/autostart`, payload, { - headers: { ...CONTENT_TYPE_JSON }, - }) -} - -export const putWorkspaceAutostop = async ( - workspaceID: string, - ttl: TypesGen.UpdateWorkspaceTTLRequest, -): Promise => { - const payload = JSON.stringify(ttl) - await axios.put(`/api/v2/workspaces/${workspaceID}/ttl`, payload, { - headers: { ...CONTENT_TYPE_JSON }, - }) -} - -export const updateProfile = async ( - userId: string, - data: TypesGen.UpdateUserProfileRequest, -): Promise => { - const response = await axios.put(`/api/v2/users/${userId}/profile`, data) - return response.data -} - -export const activateUser = async ( - userId: TypesGen.User["id"], -): Promise => { - const response = await axios.put( - `/api/v2/users/${userId}/status/activate`, - ) - return response.data -} - -export const suspendUser = async ( - userId: TypesGen.User["id"], -): Promise => { - const response = await axios.put( - `/api/v2/users/${userId}/status/suspend`, - ) - return response.data -} - -export const deleteUser = async ( - userId: TypesGen.User["id"], -): Promise => { - return await axios.delete(`/api/v2/users/${userId}`) -} - -// API definition: -// https://github.com/coder/coder/blob/db665e7261f3c24a272ccec48233a3e276878239/coderd/users.go#L33-L53 -export const hasFirstUser = async (): Promise => { - try { - // If it is success, it is true - await axios.get("/api/v2/users/first") - return true - } catch (error) { - // If it returns a 404, it is false - if (axios.isAxiosError(error) && error.response?.status === 404) { - return false - } - - throw error - } -} - -export const createFirstUser = async ( - req: TypesGen.CreateFirstUserRequest, -): Promise => { - const response = await axios.post(`/api/v2/users/first`, req) - return response.data -} - -export const updateUserPassword = async ( - userId: TypesGen.User["id"], - updatePassword: TypesGen.UpdateUserPasswordRequest, -): Promise => - axios.put(`/api/v2/users/${userId}/password`, updatePassword) - -export const getSiteRoles = async (): Promise< - Array -> => { - const response = await axios.get>( - `/api/v2/users/roles`, - ) - return response.data -} - -export const updateUserRoles = async ( - roles: TypesGen.Role["name"][], - userId: TypesGen.User["id"], -): Promise => { - const response = await axios.put( - `/api/v2/users/${userId}/roles`, - { roles }, - ) - return response.data -} - -export const getUserSSHKey = async ( - userId = "me", -): Promise => { - const response = await axios.get( - `/api/v2/users/${userId}/gitsshkey`, - ) - return response.data -} - -export const regenerateUserSSHKey = async ( - userId = "me", -): Promise => { - const response = await axios.put( - `/api/v2/users/${userId}/gitsshkey`, - ) - return response.data -} - -export const getWorkspaceBuilds = async ( - workspaceId: string, - since: Date, -): Promise => { - const response = await axios.get( - `/api/v2/workspaces/${workspaceId}/builds?since=${since.toISOString()}`, - ) - return response.data -} - -export const getWorkspaceBuildByNumber = async ( - username = "me", - workspaceName: string, - buildNumber: number, -): Promise => { - const response = await axios.get( - `/api/v2/users/${username}/workspace/${workspaceName}/builds/${buildNumber}`, - ) - return response.data -} - -export const getWorkspaceBuildLogs = async ( - buildname: string, - before: Date, -): Promise => { - const response = await axios.get( - `/api/v2/workspacebuilds/${buildname}/logs?before=${before.getTime()}`, - ) - return response.data -} - -export const getWorkspaceAgentLogs = async ( - agentID: string, -): Promise => { - const response = await axios.get( - `/api/v2/workspaceagents/${agentID}/logs`, - ) - return response.data -} - -export const putWorkspaceExtension = async ( - workspaceId: string, - newDeadline: dayjs.Dayjs, -): Promise => { - await axios.put(`/api/v2/workspaces/${workspaceId}/extend`, { - deadline: newDeadline, - }) -} - -export const refreshEntitlements = async (): Promise => { - await axios.post("/api/v2/licenses/refresh-entitlements") -} - -export const getEntitlements = async (): Promise => { - try { - const response = await axios.get("/api/v2/entitlements") - return response.data - } catch (ex) { - if (axios.isAxiosError(ex) && ex.response?.status === 404) { - return { - errors: [], - features: withDefaultFeatures({}), - has_license: false, - require_telemetry: false, - trial: false, - warnings: [], - refreshed_at: "", - } - } - throw ex - } -} - -export const getExperiments = async (): Promise => { - try { - const response = await axios.get("/api/v2/experiments") - return response.data - } catch (error) { - if (axios.isAxiosError(error) && error.response?.status === 404) { - return [] - } - throw error - } -} - -export const getGitAuthProvider = async ( - provider: string, -): Promise => { - const resp = await axios.get(`/api/v2/gitauth/${provider}`) - return resp.data -} - -export const getGitAuthDevice = async ( - provider: string, -): Promise => { - const resp = await axios.get(`/api/v2/gitauth/${provider}/device`) - return resp.data -} - -export const exchangeGitAuthDevice = async ( - provider: string, - req: TypesGen.GitAuthDeviceExchange, -): Promise => { - const resp = await axios.post(`/api/v2/gitauth/${provider}/device`, req) - return resp.data -} - -export const getAuditLogs = async ( - options: TypesGen.AuditLogsRequest, -): Promise => { - const url = getURLWithSearchParams("/api/v2/audit", options) - const response = await axios.get(url) - return response.data -} - -export const getTemplateDAUs = async ( - templateId: string, -): Promise => { - const response = await axios.get(`/api/v2/templates/${templateId}/daus`) - return response.data -} - -export const getDeploymentDAUs = async ( - // Default to user's local timezone - offset = new Date().getTimezoneOffset() / 60, -): Promise => { - const response = await axios.get(`/api/v2/insights/daus?tz_offset=${offset}`) - return response.data -} - -export const getTemplateACLAvailable = async ( - templateId: string, - options: TypesGen.UsersRequest, -): Promise => { - const url = getURLWithSearchParams( - `/api/v2/templates/${templateId}/acl/available`, - options, - ) - const response = await axios.get(url.toString()) - return response.data -} - -export const getTemplateACL = async ( - templateId: string, -): Promise => { - const response = await axios.get(`/api/v2/templates/${templateId}/acl`) - return response.data -} - -export const updateTemplateACL = async ( - templateId: string, - data: TypesGen.UpdateTemplateACL, -): Promise => { - const response = await axios.patch( - `/api/v2/templates/${templateId}/acl`, - data, - ) - return response.data -} - -export const getApplicationsHost = - async (): Promise => { - const response = await axios.get(`/api/v2/applications/host`) - return response.data - } - -export const getGroups = async ( - organizationId: string, -): Promise => { - const response = await axios.get( - `/api/v2/organizations/${organizationId}/groups`, - ) - return response.data -} - -export const createGroup = async ( - organizationId: string, - data: TypesGen.CreateGroupRequest, -): Promise => { - const response = await axios.post( - `/api/v2/organizations/${organizationId}/groups`, - data, - ) - return response.data -} - -export const getGroup = async (groupId: string): Promise => { - const response = await axios.get(`/api/v2/groups/${groupId}`) - return response.data -} - -export const patchGroup = async ( - groupId: string, - data: TypesGen.PatchGroupRequest, -): Promise => { - const response = await axios.patch(`/api/v2/groups/${groupId}`, data) - return response.data -} - -export const deleteGroup = async (groupId: string): Promise => { - await axios.delete(`/api/v2/groups/${groupId}`) -} - -export const getWorkspaceQuota = async ( - userID: string, -): Promise => { - const response = await axios.get(`/api/v2/workspace-quota/${userID}`) - return response.data -} - -export const getAgentListeningPorts = async ( - agentID: string, -): Promise => { - const response = await axios.get( - `/api/v2/workspaceagents/${agentID}/listening-ports`, - ) - return response.data -} - -// getDeploymentSSHConfig is used by the VSCode-Extension. -export const getDeploymentSSHConfig = - async (): Promise => { - const response = await axios.get(`/api/v2/deployment/ssh`) - return response.data - } - -export const getDeploymentValues = async (): Promise => { - const response = await axios.get(`/api/v2/deployment/config`) - return response.data -} - -export const getDeploymentStats = - async (): Promise => { - const response = await axios.get(`/api/v2/deployment/stats`) - return response.data - } - -export const getReplicas = async (): Promise => { - const response = await axios.get(`/api/v2/replicas`) - return response.data -} - -export const getFile = async (fileId: string): Promise => { - const response = await axios.get(`/api/v2/files/${fileId}`, { - responseType: "arraybuffer", - }) - return response.data -} - -export const getWorkspaceProxyRegions = async (): Promise< - TypesGen.RegionsResponse -> => { - const response = await axios.get>( - `/api/v2/regions`, - ) - return response.data -} - -export const getWorkspaceProxies = async (): Promise< - TypesGen.RegionsResponse -> => { - const response = await axios.get< - TypesGen.RegionsResponse - >(`/api/v2/workspaceproxies`) - return response.data -} - -export const getAppearance = async (): Promise => { - try { - const response = await axios.get(`/api/v2/appearance`) - return response.data || {} - } catch (ex) { - if (axios.isAxiosError(ex) && ex.response?.status === 404) { - return { - logo_url: "", - service_banner: { - enabled: false, - }, - } - } - throw ex - } -} - -export const updateAppearance = async ( - b: TypesGen.AppearanceConfig, -): Promise => { - const response = await axios.put(`/api/v2/appearance`, b) - return response.data -} - -export const getTemplateExamples = async ( - organizationId: string, -): Promise => { - const response = await axios.get( - `/api/v2/organizations/${organizationId}/templates/examples`, - ) - return response.data -} - -export const uploadTemplateFile = async ( - file: File, -): Promise => { - const response = await axios.post("/api/v2/files", file, { - headers: { - "Content-Type": "application/x-tar", - }, - }) - return response.data -} - -export const getTemplateVersionLogs = async ( - versionId: string, -): Promise => { - const response = await axios.get( - `/api/v2/templateversions/${versionId}/logs`, - ) - return response.data -} - -export const updateWorkspaceVersion = async ( - workspace: TypesGen.Workspace, -): Promise => { - const template = await getTemplate(workspace.template_id) - return startWorkspace(workspace.id, template.active_version_id) -} - -export const getWorkspaceBuildParameters = async ( - workspaceBuildId: TypesGen.WorkspaceBuild["id"], -): Promise => { - const response = await axios.get( - `/api/v2/workspacebuilds/${workspaceBuildId}/parameters`, - ) - return response.data -} -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 const getLicenses = async (): Promise => { - const response = await axios.get(`/api/v2/licenses`) - return response.data -} - -export const createLicense = async ( - data: TypesGen.AddLicenseRequest, -): Promise => { - const response = await axios.post(`/api/v2/licenses`, data) - return response.data -} - -export const removeLicense = async (licenseId: number): Promise => { - await axios.delete(`/api/v2/licenses/${licenseId}`) -} - -export class MissingBuildParameters extends Error { - parameters: TypesGen.TemplateVersionParameter[] = [] - - constructor(parameters: TypesGen.TemplateVersionParameter[]) { - super("Missing build parameters.") - this.parameters = parameters - } -} - -/** 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 - */ -export const changeWorkspaceVersion = async ( - workspace: TypesGen.Workspace, - templateVersionId: string, - newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], -): Promise => { - const [currentBuildParameters, templateParameters] = await Promise.all([ - getWorkspaceBuildParameters(workspace.latest_build.id), - getTemplateVersionRichParameters(templateVersionId), - ]) - - const missingParameters = getMissingParameters( - currentBuildParameters, - newBuildParameters, - templateParameters, - ) - - if (missingParameters.length > 0) { - throw new MissingBuildParameters(missingParameters) - } - - return 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 - */ -export const updateWorkspace = async ( - workspace: TypesGen.Workspace, - newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], -): Promise => { - const [template, oldBuildParameters] = await Promise.all([ - getTemplate(workspace.template_id), - getWorkspaceBuildParameters(workspace.latest_build.id), - ]) - const activeVersionId = template.active_version_id - const templateParameters = await getTemplateVersionRichParameters( - activeVersionId, - ) - const missingParameters = getMissingParameters( - oldBuildParameters, - newBuildParameters, - templateParameters, - ) - - if (missingParameters.length > 0) { - throw new MissingBuildParameters(missingParameters) - } - - return postWorkspaceBuild(workspace.id, { - transition: "start", - template_version_id: activeVersionId, - rich_parameter_values: newBuildParameters, - }) -} - -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 }, - ) -} - -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 -} - -type WatchWorkspaceAgentLogsOptions = { - after: number - onMessage: (logs: TypesGen.WorkspaceAgentLog[]) => void - onDone: () => void - onError: (error: Error) => void -} - -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 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 -} - -export const issueReconnectingPTYSignedToken = async ( - params: TypesGen.IssueReconnectingPTYSignedTokenRequest, -): Promise => { - const response = await axios.post( - "/api/v2/applications/reconnecting-pty-signed-token", - params, - ) - return response.data -} - -export const getWorkspaceParameters = async (workspace: TypesGen.Workspace) => { - const latestBuild = workspace.latest_build - const [templateVersionRichParameters, buildParameters] = await Promise.all([ - getTemplateVersionRichParameters(latestBuild.template_version_id), - getWorkspaceBuildParameters(latestBuild.id), - ]) - return { - templateVersionRichParameters, - buildParameters, - } -} - -type InsightsFilter = { - start_time: string - end_time: string - template_ids: string -} - -export const getInsightsUserLatency = async ( - filters: InsightsFilter, -): Promise => { - const params = new URLSearchParams(filters) - const response = await axios.get(`/api/v2/insights/user-latency?${params}`) - return response.data -} - -export const getInsightsTemplate = async ( - filters: InsightsFilter, -): Promise => { - const params = new URLSearchParams({ - ...filters, - interval: "day", - }) - const response = await axios.get(`/api/v2/insights/templates?${params}`) - return response.data -} - -export const getHealth = () => { - return axios.get<{ - healthy: boolean - time: string - coder_version: string - derp: { healthy: boolean } - access_url: { healthy: boolean } - websocket: { healthy: boolean } - database: { healthy: boolean } - }>("/api/v2/debug/health") -} diff --git a/site/src/xServices/templateVersion/templateVersionXService.ts b/site/src/xServices/templateVersion/templateVersionXService.ts deleted file mode 100644 index 407324a0f2e9c..0000000000000 --- a/site/src/xServices/templateVersion/templateVersionXService.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { - getPreviousTemplateVersionByName, - GetPreviousTemplateVersionByNameResponse, - getTemplateByName, - getTemplateVersionByName, -} from "api/api" -import { Template, TemplateVersion } from "api/typesGenerated" -import { - getTemplateVersionFiles, - TemplateVersionFiles, -} from "utils/templateVersion" -import { assign, createMachine } from "xstate" - -export interface TemplateVersionMachineContext { - orgId: string - templateName: string - versionName: string - template?: Template - currentVersion?: TemplateVersion - currentFiles?: TemplateVersionFiles - error?: unknown - // Get file diffs - previousVersion?: TemplateVersion - previousFiles?: TemplateVersionFiles -} - -export const templateVersionMachine = createMachine( - { - predictableActionArguments: true, - id: "templateVersion", - schema: { - context: {} as TemplateVersionMachineContext, - services: {} as { - loadVersions: { - data: { - currentVersion: GetPreviousTemplateVersionByNameResponse - previousVersion: GetPreviousTemplateVersionByNameResponse - } - } - loadTemplate: { - data: { - template: Template - } - } - loadFiles: { - data: { - currentFiles: TemplateVersionFiles - previousFiles: TemplateVersionFiles - } - } - }, - }, - tsTypes: {} as import("./templateVersionXService.typegen").Typegen0, - initial: "initialInfo", - states: { - initialInfo: { - type: "parallel", - states: { - versions: { - initial: "loadingVersions", - states: { - loadingVersions: { - invoke: { - src: "loadVersions", - onDone: [ - { - actions: "assignVersions", - target: "success", - }, - ], - }, - }, - success: { - type: "final", - }, - }, - }, - template: { - initial: "loadingTemplate", - states: { - loadingTemplate: { - invoke: { - src: "loadTemplate", - onDone: [ - { - actions: "assignTemplate", - target: "success", - }, - ], - }, - }, - success: { - type: "final", - }, - }, - }, - }, - onDone: { - target: "loadingFiles", - }, - }, - loadingFiles: { - invoke: { - src: "loadFiles", - onDone: { - target: "done.ok", - actions: ["assignFiles"], - }, - onError: { - target: "done.error", - actions: ["assignError"], - }, - }, - }, - done: { - states: { - ok: { type: "final" }, - error: { type: "final" }, - }, - }, - }, - }, - { - actions: { - assignError: assign({ - error: (_, { data }) => data, - }), - assignTemplate: assign({ - template: (_, { data }) => data.template, - }), - assignVersions: assign({ - currentVersion: (_, { data }) => data.currentVersion, - previousVersion: (_, { data }) => data.previousVersion, - }), - assignFiles: assign({ - currentFiles: (_, { data }) => data.currentFiles, - previousFiles: (_, { data }) => data.previousFiles, - }), - }, - services: { - loadVersions: async ({ orgId, templateName, versionName }) => { - const [currentVersion, previousVersion] = await Promise.all([ - getTemplateVersionByName(orgId, templateName, versionName), - getPreviousTemplateVersionByName(orgId, templateName, versionName), - ]) - - return { - currentVersion, - previousVersion, - } - }, - loadTemplate: async ({ orgId, templateName }) => { - const template = await getTemplateByName(orgId, templateName) - - return { - template, - } - }, - loadFiles: async ({ currentVersion, previousVersion }) => { - if (!currentVersion) { - throw new Error("Version is not defined") - } - const loadFilesPromises: ReturnType[] = - [] - loadFilesPromises.push(getTemplateVersionFiles(currentVersion)) - if (previousVersion) { - loadFilesPromises.push(getTemplateVersionFiles(previousVersion)) - } - const [currentFiles, previousFiles] = await Promise.all( - loadFilesPromises, - ) - return { - currentFiles, - previousFiles, - } - }, - }, - }, -) From b18f6d2742c26f36102fade6003845e53ffbf5f3 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 28 Aug 2023 16:46:32 +0000 Subject: [PATCH 4/9] Revert "make: fmt & grammar" This reverts commit 0737be02ad9e773355dfc811e483ff4cf2e39b2b. --- docs/ides/gateway.md | 43 +- site/src/api/api.ts | 1467 +++++++++++++++++ .../templateVersionXService.ts | 179 ++ 3 files changed, 1665 insertions(+), 24 deletions(-) create mode 100644 site/src/api/api.ts create mode 100644 site/src/xServices/templateVersion/templateVersionXService.ts diff --git a/docs/ides/gateway.md b/docs/ides/gateway.md index 03b5d598265bd..fd1f08721c825 100644 --- a/docs/ides/gateway.md +++ b/docs/ides/gateway.md @@ -191,13 +191,13 @@ see the ### Configuration Steps -The Coder team built a POC of the JetBrains Gateway Offline Mode solution. Here -are the steps we took (and "gotchas"): +The Coder team built a POC of the JetBrains Gateway Offline Mode solution. Here are +the steps we took (and "gotchas"): ### 1. Deploy the server and install the Client Downloader -We deployed a simple Ubuntu VM and installed the JetBrains Client Downloader -binary. Note that the server must be a Linux-based distribution. +We deployed a simple Ubuntu VM and installed the JetBrains Client Downloader binary. Note +that the server must be a Linux-based distribution. ```shell wget https://download.jetbrains.com/idea/code-with-me/backend/jetbrains-clients-downloader-linux-x86_64-1867.tar.gz && \ @@ -206,12 +206,11 @@ tar -xzvf jetbrains-clients-downloader-linux-x86_64-1867.tar.gz ### 2. Install backends and clients -JetBrains Gateway requires both a backend to be installed on the remote host -(your Coder workspace) and a client to be installed on your local machine. You -can host both on the server in this example. +JetBrains Gateway requires both a backend to be installed on the remote host (your Coder workspace) +and a client to be installed on your local machine. You can host both on the server +in this example. -See here for the full -[JetBrains product list and builds](https://data.services.jetbrains.com/products). +See here for the full [JetBrains product list and builds](https://data.services.jetbrains.com/products). Below is the full list of supported `--platforms-filter` values: ```console @@ -234,13 +233,12 @@ This is the same command as above, with the `--download-backends` flag removed. ./jetbrains-clients-downloader-linux-x86_64-1867/bin4/jetbrains-clients-downloader --products-filter --build-filter --platforms-filter linux-x64 ~/clients ``` -We now have both clients and backends installed. +We now have both clients and backends installed in ### 3. Install a web server -You will need to run a web server in order to serve requests to the backend and -client files. We installed `nginx` and setup an FQDN and routed all requests to -`/`. See below: +You will need to run a web server in order to serve requests to the backend and client +files. We installed `nginx` and setup an FQDN and routed all requests to `/`. See below: ```console server { @@ -260,22 +258,19 @@ server { ``` Then, configure your DNS entry to point to the IP address of the server. For the -purposes of the POC, we did not configure TLS, although that is a supported -option. +purposes of the POC, we did not configure TLS, although that is a supported option. ### 4. Setup SSH connection with JetBrains Gateway With the server now configured, you can now configure your local machine to use -Gateway. Here is the documentation to -[setup SSH config via the Coder CLI](../ides.md#ssh-configuration). On the -Gateway side, follow our guide here until step 16. +Gateway. Here is the documentation to [setup SSH config via the Coder CLI](../ides.md#ssh-configuration). +On the Gateway side, follow our guide here until step 16. -Instead of downloading from jetbrains.com, we will point Gateway to our server -endpoint. Select `Installation options...` and select `Use download link`. Note -that the URL must explicitly reference the archive file: +Instead of downloading from jetbrains.com, we will point Gateway to our server endpoint. +Select `Installation options...` and select `Use download link`. Note that the URL +must explicitly reference the archive file: ![Offline Gateway](../images/gateway/offline-gateway.png) -Click `Download IDE and Connect`. Gateway should now download the backend and -clients from the server into your remote workspace and local machine, -respectively. +Click `Download IDE and Connect`. Gateway should now download the backend and clients +from the server into your remote workspace and local machine, respectively. diff --git a/site/src/api/api.ts b/site/src/api/api.ts new file mode 100644 index 0000000000000..3567e4f977332 --- /dev/null +++ b/site/src/api/api.ts @@ -0,0 +1,1467 @@ +import axios from "axios" +import dayjs from "dayjs" +import * as Types from "./types" +import { DeploymentConfig } from "./types" +import * as TypesGen from "./typesGenerated" +import { delay } from "utils/delay" +import userAgentParser from "ua-parser-js" + +// Adds 304 for the default axios validateStatus function +// https://github.com/axios/axios#handling-errors Check status here +// https://httpstatusdogs.com/ +axios.defaults.validateStatus = (status) => { + return (status >= 200 && status < 300) || status === 304 +} + +export const hardCodedCSRFCookie = (): string => { + // 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==" + axios.defaults.headers.common["X-CSRF-TOKEN"] = csrfToken + return csrfToken +} + +// 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"] +} + +// Always attach CSRF token to all requests. In puppeteer the document is +// undefined. In those cases, just do nothing. +const token = + typeof document !== "undefined" + ? document.head.querySelector('meta[property="csrf-token"]') + : null + +if (token !== null && token.getAttribute("content") !== null) { + if (process.env.NODE_ENV === "development") { + // Development mode uses a hard-coded CSRF token + axios.defaults.headers.common["X-CSRF-TOKEN"] = hardCodedCSRFCookie() + token.setAttribute("content", hardCodedCSRFCookie()) + } else { + axios.defaults.headers.common["X-CSRF-TOKEN"] = + token.getAttribute("content") ?? "" + } +} else { + // Do not write error logs if we are in a FE unit test. + if (process.env.JEST_WORKER_ID === undefined) { + console.error("CSRF token not found") + } +} + +const CONTENT_TYPE_JSON = { + "Content-Type": "application/json", +} + +export const provisioners: TypesGen.ProvisionerDaemon[] = [ + { + id: "terraform", + name: "Terraform", + created_at: "", + provisioners: [], + tags: {}, + }, + { + id: "cdr-basic", + name: "Basic", + created_at: "", + provisioners: [], + tags: {}, + }, +] + +export const login = async ( + email: string, + password: string, +): Promise => { + const payload = JSON.stringify({ + email, + password, + }) + + const response = await axios.post( + "/api/v2/users/login", + payload, + { + headers: { ...CONTENT_TYPE_JSON }, + }, + ) + + return response.data +} + +export const convertToOAUTH = async (request: TypesGen.ConvertLoginRequest) => { + const response = await axios.post( + "/api/v2/users/me/convert-login", + request, + ) + return response.data +} + +export const logout = async (): Promise => { + await axios.post("/api/v2/users/logout") +} + +export const getAuthenticatedUser = async (): Promise< + TypesGen.User | undefined +> => { + try { + const response = await axios.get("/api/v2/users/me") + return response.data + } catch (error) { + if (axios.isAxiosError(error) && error.response?.status === 401) { + return undefined + } + + throw error + } +} + +export const getAuthMethods = async (): Promise => { + const response = await axios.get( + "/api/v2/users/authmethods", + ) + return response.data +} + +export const getUserLoginType = async (): Promise => { + const response = await axios.get( + "/api/v2/users/me/login-type", + ) + return response.data +} + +export const checkAuthorization = async ( + params: TypesGen.AuthorizationRequest, +): Promise => { + const response = await axios.post( + `/api/v2/authcheck`, + params, + ) + return response.data +} + +export const getApiKey = async (): Promise => { + const response = await axios.post( + "/api/v2/users/me/keys", + ) + return response.data +} + +export const getTokens = async ( + params: TypesGen.TokensFilter, +): Promise => { + const response = await axios.get( + `/api/v2/users/me/keys/tokens`, + { + params, + }, + ) + return response.data +} + +export const deleteToken = async (keyId: string): Promise => { + await axios.delete("/api/v2/users/me/keys/" + keyId) +} + +export const createToken = async ( + params: TypesGen.CreateTokenRequest, +): Promise => { + const response = await axios.post(`/api/v2/users/me/keys/tokens`, params) + return response.data +} + +export const getTokenConfig = async (): Promise => { + const response = await axios.get("/api/v2/users/me/keys/tokens/tokenconfig") + return response.data +} + +export const getUsers = async ( + options: TypesGen.UsersRequest, +): Promise => { + const url = getURLWithSearchParams("/api/v2/users", options) + const response = await axios.get(url.toString()) + return response.data +} + +export const getOrganization = async ( + organizationId: string, +): Promise => { + const response = await axios.get( + `/api/v2/organizations/${organizationId}`, + ) + return response.data +} + +export const getOrganizations = async (): Promise => { + const response = await axios.get( + "/api/v2/users/me/organizations", + ) + return response.data +} + +export const getTemplate = async ( + templateId: string, +): Promise => { + const response = await axios.get( + `/api/v2/templates/${templateId}`, + ) + return response.data +} + +export const getTemplates = async ( + organizationId: string, +): Promise => { + const response = await axios.get( + `/api/v2/organizations/${organizationId}/templates`, + ) + return response.data +} + +export const getTemplateByName = async ( + organizationId: string, + name: string, +): Promise => { + const response = await axios.get( + `/api/v2/organizations/${organizationId}/templates/${name}`, + ) + return response.data +} + +export const getTemplateVersion = async ( + versionId: string, +): Promise => { + const response = await axios.get( + `/api/v2/templateversions/${versionId}`, + ) + return response.data +} + +export const getTemplateVersionResources = async ( + versionId: string, +): Promise => { + const response = await axios.get( + `/api/v2/templateversions/${versionId}/resources`, + ) + return response.data +} + +export const getTemplateVersionVariables = async ( + versionId: string, +): Promise => { + const response = await axios.get( + `/api/v2/templateversions/${versionId}/variables`, + ) + return response.data +} + +export const getTemplateVersions = async ( + templateId: string, +): Promise => { + const response = await axios.get( + `/api/v2/templates/${templateId}/versions`, + ) + return response.data +} + +export const getTemplateVersionByName = async ( + organizationId: string, + templateName: string, + versionName: string, +): Promise => { + const response = await axios.get( + `/api/v2/organizations/${organizationId}/templates/${templateName}/versions/${versionName}`, + ) + return response.data +} + +export type GetPreviousTemplateVersionByNameResponse = + | TypesGen.TemplateVersion + | undefined + +export const getPreviousTemplateVersionByName = async ( + organizationId: string, + templateName: string, + versionName: string, +): Promise => { + try { + const response = await 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 + if ( + axios.isAxiosError(error) && + error.response && + error.response.status === 404 + ) { + return undefined + } + + throw error + } +} + +export const createTemplateVersion = async ( + organizationId: string, + data: TypesGen.CreateTemplateVersionRequest, +): Promise => { + const response = await axios.post( + `/api/v2/organizations/${organizationId}/templateversions`, + data, + ) + return response.data +} + +export const getTemplateVersionGitAuth = async ( + versionId: string, +): Promise => { + const response = await axios.get( + `/api/v2/templateversions/${versionId}/gitauth`, + ) + return response.data +} + +export const getTemplateVersionRichParameters = async ( + versionId: string, +): Promise => { + const response = await axios.get( + `/api/v2/templateversions/${versionId}/rich-parameters`, + ) + return response.data +} + +export const createTemplate = async ( + organizationId: string, + data: TypesGen.CreateTemplateRequest, +): Promise => { + const response = await axios.post( + `/api/v2/organizations/${organizationId}/templates`, + data, + ) + return response.data +} + +export const updateActiveTemplateVersion = async ( + templateId: string, + data: TypesGen.UpdateActiveTemplateVersion, +): Promise => { + const response = await axios.patch( + `/api/v2/templates/${templateId}/versions`, + data, + ) + return response.data +} + +export const patchTemplateVersion = async ( + templateVersionId: string, + data: TypesGen.PatchTemplateVersionRequest, +) => { + const response = await axios.patch( + `/api/v2/templateversions/${templateVersionId}`, + data, + ) + return response.data +} + +export const updateTemplateMeta = async ( + templateId: string, + data: TypesGen.UpdateTemplateMeta, +): Promise => { + const response = await axios.patch( + `/api/v2/templates/${templateId}`, + data, + ) + return response.data +} + +export const deleteTemplate = async ( + templateId: string, +): Promise => { + const response = await axios.delete( + `/api/v2/templates/${templateId}`, + ) + return response.data +} + +export const getWorkspace = async ( + workspaceId: string, + params?: TypesGen.WorkspaceOptions, +): Promise => { + const response = await axios.get( + `/api/v2/workspaces/${workspaceId}`, + { + params, + }, + ) + return response.data +} + +/** + * + * @param workspaceId + * @returns 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 }, + ) +} + +interface SearchParamOptions extends TypesGen.Pagination { + q?: string +} + +export const getURLWithSearchParams = ( + basePath: string, + options?: SearchParamOptions, +): string => { + if (options) { + 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 + } else { + return basePath + } +} + +export const getWorkspaces = async ( + options: TypesGen.WorkspacesRequest, +): Promise => { + const url = getURLWithSearchParams("/api/v2/workspaces", options) + const response = await axios.get(url) + return response.data +} + +export const getWorkspaceByOwnerAndName = async ( + username = "me", + workspaceName: string, + params?: TypesGen.WorkspaceOptions, +): Promise => { + const response = await axios.get( + `/api/v2/users/${username}/workspace/${workspaceName}`, + { + params, + }, + ) + return response.data +} + +export function waitForBuild(build: TypesGen.WorkspaceBuild) { + return new Promise((res, reject) => { + void (async () => { + let latestJobInfo: TypesGen.ProvisionerJob | undefined = undefined + + while ( + !["succeeded", "canceled"].some( + (status) => latestJobInfo?.status.includes(status), + ) + ) { + const { job } = await 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) + })() + }) +} + +export const postWorkspaceBuild = async ( + workspaceId: string, + data: TypesGen.CreateWorkspaceBuildRequest, +): Promise => { + const response = await axios.post( + `/api/v2/workspaces/${workspaceId}/builds`, + data, + ) + return response.data +} + +export const startWorkspace = ( + workspaceId: string, + templateVersionId: string, + logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], + buildParameters?: TypesGen.WorkspaceBuildParameter[], +) => + postWorkspaceBuild(workspaceId, { + transition: "start", + template_version_id: templateVersionId, + log_level: logLevel, + rich_parameter_values: buildParameters, + }) +export const stopWorkspace = ( + workspaceId: string, + logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], +) => + postWorkspaceBuild(workspaceId, { + transition: "stop", + log_level: logLevel, + }) + +export const deleteWorkspace = ( + workspaceId: string, + logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], +) => + postWorkspaceBuild(workspaceId, { + transition: "delete", + log_level: logLevel, + }) + +export const cancelWorkspaceBuild = async ( + workspaceBuildId: TypesGen.WorkspaceBuild["id"], +): Promise => { + const response = await axios.patch( + `/api/v2/workspacebuilds/${workspaceBuildId}/cancel`, + ) + return response.data +} + +export const updateWorkspaceDormancy = async ( + workspaceId: string, + dormant: boolean, +): Promise => { + const data: TypesGen.UpdateWorkspaceDormancy = { + dormant: dormant, + } + + const response = await axios.put( + `/api/v2/workspaces/${workspaceId}/dormant`, + data, + ) + return response.data +} + +export const restartWorkspace = async ({ + workspace, + buildParameters, +}: { + workspace: TypesGen.Workspace + buildParameters?: TypesGen.WorkspaceBuildParameter[] +}) => { + const stopBuild = await stopWorkspace(workspace.id) + const awaitedStopBuild = await waitForBuild(stopBuild) + + // If the restart is canceled halfway through, make sure we bail + if (awaitedStopBuild?.status === "canceled") { + return + } + + const startBuild = await startWorkspace( + workspace.id, + workspace.latest_build.template_version_id, + undefined, + buildParameters, + ) + await waitForBuild(startBuild) +} + +export const cancelTemplateVersionBuild = async ( + templateVersionId: TypesGen.TemplateVersion["id"], +): Promise => { + const response = await axios.patch( + `/api/v2/templateversions/${templateVersionId}/cancel`, + ) + return response.data +} + +export const createUser = async ( + user: TypesGen.CreateUserRequest, +): Promise => { + const response = await axios.post("/api/v2/users", user) + return response.data +} + +export const createWorkspace = async ( + organizationId: string, + userId = "me", + workspace: TypesGen.CreateWorkspaceRequest, +): Promise => { + const response = await axios.post( + `/api/v2/organizations/${organizationId}/members/${userId}/workspaces`, + workspace, + ) + return response.data +} + +export const patchWorkspace = async ( + workspaceId: string, + data: TypesGen.UpdateWorkspaceRequest, +) => { + await axios.patch(`/api/v2/workspaces/${workspaceId}`, data) +} + +export const getBuildInfo = async (): Promise => { + const response = await axios.get("/api/v2/buildinfo") + return response.data +} + +export const getUpdateCheck = + async (): Promise => { + const response = await axios.get("/api/v2/updatecheck") + return response.data + } + +export const putWorkspaceAutostart = async ( + workspaceID: string, + autostart: TypesGen.UpdateWorkspaceAutostartRequest, +): Promise => { + const payload = JSON.stringify(autostart) + await axios.put(`/api/v2/workspaces/${workspaceID}/autostart`, payload, { + headers: { ...CONTENT_TYPE_JSON }, + }) +} + +export const putWorkspaceAutostop = async ( + workspaceID: string, + ttl: TypesGen.UpdateWorkspaceTTLRequest, +): Promise => { + const payload = JSON.stringify(ttl) + await axios.put(`/api/v2/workspaces/${workspaceID}/ttl`, payload, { + headers: { ...CONTENT_TYPE_JSON }, + }) +} + +export const updateProfile = async ( + userId: string, + data: TypesGen.UpdateUserProfileRequest, +): Promise => { + const response = await axios.put(`/api/v2/users/${userId}/profile`, data) + return response.data +} + +export const activateUser = async ( + userId: TypesGen.User["id"], +): Promise => { + const response = await axios.put( + `/api/v2/users/${userId}/status/activate`, + ) + return response.data +} + +export const suspendUser = async ( + userId: TypesGen.User["id"], +): Promise => { + const response = await axios.put( + `/api/v2/users/${userId}/status/suspend`, + ) + return response.data +} + +export const deleteUser = async ( + userId: TypesGen.User["id"], +): Promise => { + return await axios.delete(`/api/v2/users/${userId}`) +} + +// API definition: +// https://github.com/coder/coder/blob/db665e7261f3c24a272ccec48233a3e276878239/coderd/users.go#L33-L53 +export const hasFirstUser = async (): Promise => { + try { + // If it is success, it is true + await axios.get("/api/v2/users/first") + return true + } catch (error) { + // If it returns a 404, it is false + if (axios.isAxiosError(error) && error.response?.status === 404) { + return false + } + + throw error + } +} + +export const createFirstUser = async ( + req: TypesGen.CreateFirstUserRequest, +): Promise => { + const response = await axios.post(`/api/v2/users/first`, req) + return response.data +} + +export const updateUserPassword = async ( + userId: TypesGen.User["id"], + updatePassword: TypesGen.UpdateUserPasswordRequest, +): Promise => + axios.put(`/api/v2/users/${userId}/password`, updatePassword) + +export const getSiteRoles = async (): Promise< + Array +> => { + const response = await axios.get>( + `/api/v2/users/roles`, + ) + return response.data +} + +export const updateUserRoles = async ( + roles: TypesGen.Role["name"][], + userId: TypesGen.User["id"], +): Promise => { + const response = await axios.put( + `/api/v2/users/${userId}/roles`, + { roles }, + ) + return response.data +} + +export const getUserSSHKey = async ( + userId = "me", +): Promise => { + const response = await axios.get( + `/api/v2/users/${userId}/gitsshkey`, + ) + return response.data +} + +export const regenerateUserSSHKey = async ( + userId = "me", +): Promise => { + const response = await axios.put( + `/api/v2/users/${userId}/gitsshkey`, + ) + return response.data +} + +export const getWorkspaceBuilds = async ( + workspaceId: string, + since: Date, +): Promise => { + const response = await axios.get( + `/api/v2/workspaces/${workspaceId}/builds?since=${since.toISOString()}`, + ) + return response.data +} + +export const getWorkspaceBuildByNumber = async ( + username = "me", + workspaceName: string, + buildNumber: number, +): Promise => { + const response = await axios.get( + `/api/v2/users/${username}/workspace/${workspaceName}/builds/${buildNumber}`, + ) + return response.data +} + +export const getWorkspaceBuildLogs = async ( + buildname: string, + before: Date, +): Promise => { + const response = await axios.get( + `/api/v2/workspacebuilds/${buildname}/logs?before=${before.getTime()}`, + ) + return response.data +} + +export const getWorkspaceAgentLogs = async ( + agentID: string, +): Promise => { + const response = await axios.get( + `/api/v2/workspaceagents/${agentID}/logs`, + ) + return response.data +} + +export const putWorkspaceExtension = async ( + workspaceId: string, + newDeadline: dayjs.Dayjs, +): Promise => { + await axios.put(`/api/v2/workspaces/${workspaceId}/extend`, { + deadline: newDeadline, + }) +} + +export const refreshEntitlements = async (): Promise => { + await axios.post("/api/v2/licenses/refresh-entitlements") +} + +export const getEntitlements = async (): Promise => { + try { + const response = await axios.get("/api/v2/entitlements") + return response.data + } catch (ex) { + if (axios.isAxiosError(ex) && ex.response?.status === 404) { + return { + errors: [], + features: withDefaultFeatures({}), + has_license: false, + require_telemetry: false, + trial: false, + warnings: [], + refreshed_at: "", + } + } + throw ex + } +} + +export const getExperiments = async (): Promise => { + try { + const response = await axios.get("/api/v2/experiments") + return response.data + } catch (error) { + if (axios.isAxiosError(error) && error.response?.status === 404) { + return [] + } + throw error + } +} + +export const getGitAuthProvider = async ( + provider: string, +): Promise => { + const resp = await axios.get(`/api/v2/gitauth/${provider}`) + return resp.data +} + +export const getGitAuthDevice = async ( + provider: string, +): Promise => { + const resp = await axios.get(`/api/v2/gitauth/${provider}/device`) + return resp.data +} + +export const exchangeGitAuthDevice = async ( + provider: string, + req: TypesGen.GitAuthDeviceExchange, +): Promise => { + const resp = await axios.post(`/api/v2/gitauth/${provider}/device`, req) + return resp.data +} + +export const getAuditLogs = async ( + options: TypesGen.AuditLogsRequest, +): Promise => { + const url = getURLWithSearchParams("/api/v2/audit", options) + const response = await axios.get(url) + return response.data +} + +export const getTemplateDAUs = async ( + templateId: string, +): Promise => { + const response = await axios.get(`/api/v2/templates/${templateId}/daus`) + return response.data +} + +export const getDeploymentDAUs = async ( + // Default to user's local timezone + offset = new Date().getTimezoneOffset() / 60, +): Promise => { + const response = await axios.get(`/api/v2/insights/daus?tz_offset=${offset}`) + return response.data +} + +export const getTemplateACLAvailable = async ( + templateId: string, + options: TypesGen.UsersRequest, +): Promise => { + const url = getURLWithSearchParams( + `/api/v2/templates/${templateId}/acl/available`, + options, + ) + const response = await axios.get(url.toString()) + return response.data +} + +export const getTemplateACL = async ( + templateId: string, +): Promise => { + const response = await axios.get(`/api/v2/templates/${templateId}/acl`) + return response.data +} + +export const updateTemplateACL = async ( + templateId: string, + data: TypesGen.UpdateTemplateACL, +): Promise => { + const response = await axios.patch( + `/api/v2/templates/${templateId}/acl`, + data, + ) + return response.data +} + +export const getApplicationsHost = + async (): Promise => { + const response = await axios.get(`/api/v2/applications/host`) + return response.data + } + +export const getGroups = async ( + organizationId: string, +): Promise => { + const response = await axios.get( + `/api/v2/organizations/${organizationId}/groups`, + ) + return response.data +} + +export const createGroup = async ( + organizationId: string, + data: TypesGen.CreateGroupRequest, +): Promise => { + const response = await axios.post( + `/api/v2/organizations/${organizationId}/groups`, + data, + ) + return response.data +} + +export const getGroup = async (groupId: string): Promise => { + const response = await axios.get(`/api/v2/groups/${groupId}`) + return response.data +} + +export const patchGroup = async ( + groupId: string, + data: TypesGen.PatchGroupRequest, +): Promise => { + const response = await axios.patch(`/api/v2/groups/${groupId}`, data) + return response.data +} + +export const deleteGroup = async (groupId: string): Promise => { + await axios.delete(`/api/v2/groups/${groupId}`) +} + +export const getWorkspaceQuota = async ( + userID: string, +): Promise => { + const response = await axios.get(`/api/v2/workspace-quota/${userID}`) + return response.data +} + +export const getAgentListeningPorts = async ( + agentID: string, +): Promise => { + const response = await axios.get( + `/api/v2/workspaceagents/${agentID}/listening-ports`, + ) + return response.data +} + +// getDeploymentSSHConfig is used by the VSCode-Extension. +export const getDeploymentSSHConfig = + async (): Promise => { + const response = await axios.get(`/api/v2/deployment/ssh`) + return response.data + } + +export const getDeploymentValues = async (): Promise => { + const response = await axios.get(`/api/v2/deployment/config`) + return response.data +} + +export const getDeploymentStats = + async (): Promise => { + const response = await axios.get(`/api/v2/deployment/stats`) + return response.data + } + +export const getReplicas = async (): Promise => { + const response = await axios.get(`/api/v2/replicas`) + return response.data +} + +export const getFile = async (fileId: string): Promise => { + const response = await axios.get(`/api/v2/files/${fileId}`, { + responseType: "arraybuffer", + }) + return response.data +} + +export const getWorkspaceProxyRegions = async (): Promise< + TypesGen.RegionsResponse +> => { + const response = await axios.get>( + `/api/v2/regions`, + ) + return response.data +} + +export const getWorkspaceProxies = async (): Promise< + TypesGen.RegionsResponse +> => { + const response = await axios.get< + TypesGen.RegionsResponse + >(`/api/v2/workspaceproxies`) + return response.data +} + +export const getAppearance = async (): Promise => { + try { + const response = await axios.get(`/api/v2/appearance`) + return response.data || {} + } catch (ex) { + if (axios.isAxiosError(ex) && ex.response?.status === 404) { + return { + logo_url: "", + service_banner: { + enabled: false, + }, + } + } + throw ex + } +} + +export const updateAppearance = async ( + b: TypesGen.AppearanceConfig, +): Promise => { + const response = await axios.put(`/api/v2/appearance`, b) + return response.data +} + +export const getTemplateExamples = async ( + organizationId: string, +): Promise => { + const response = await axios.get( + `/api/v2/organizations/${organizationId}/templates/examples`, + ) + return response.data +} + +export const uploadTemplateFile = async ( + file: File, +): Promise => { + const response = await axios.post("/api/v2/files", file, { + headers: { + "Content-Type": "application/x-tar", + }, + }) + return response.data +} + +export const getTemplateVersionLogs = async ( + versionId: string, +): Promise => { + const response = await axios.get( + `/api/v2/templateversions/${versionId}/logs`, + ) + return response.data +} + +export const updateWorkspaceVersion = async ( + workspace: TypesGen.Workspace, +): Promise => { + const template = await getTemplate(workspace.template_id) + return startWorkspace(workspace.id, template.active_version_id) +} + +export const getWorkspaceBuildParameters = async ( + workspaceBuildId: TypesGen.WorkspaceBuild["id"], +): Promise => { + const response = await axios.get( + `/api/v2/workspacebuilds/${workspaceBuildId}/parameters`, + ) + return response.data +} +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 const getLicenses = async (): Promise => { + const response = await axios.get(`/api/v2/licenses`) + return response.data +} + +export const createLicense = async ( + data: TypesGen.AddLicenseRequest, +): Promise => { + const response = await axios.post(`/api/v2/licenses`, data) + return response.data +} + +export const removeLicense = async (licenseId: number): Promise => { + await axios.delete(`/api/v2/licenses/${licenseId}`) +} + +export class MissingBuildParameters extends Error { + parameters: TypesGen.TemplateVersionParameter[] = [] + + constructor(parameters: TypesGen.TemplateVersionParameter[]) { + super("Missing build parameters.") + this.parameters = parameters + } +} + +/** 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 + */ +export const changeWorkspaceVersion = async ( + workspace: TypesGen.Workspace, + templateVersionId: string, + newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], +): Promise => { + const [currentBuildParameters, templateParameters] = await Promise.all([ + getWorkspaceBuildParameters(workspace.latest_build.id), + getTemplateVersionRichParameters(templateVersionId), + ]) + + const missingParameters = getMissingParameters( + currentBuildParameters, + newBuildParameters, + templateParameters, + ) + + if (missingParameters.length > 0) { + throw new MissingBuildParameters(missingParameters) + } + + return 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 + */ +export const updateWorkspace = async ( + workspace: TypesGen.Workspace, + newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], +): Promise => { + const [template, oldBuildParameters] = await Promise.all([ + getTemplate(workspace.template_id), + getWorkspaceBuildParameters(workspace.latest_build.id), + ]) + const activeVersionId = template.active_version_id + const templateParameters = await getTemplateVersionRichParameters( + activeVersionId, + ) + const missingParameters = getMissingParameters( + oldBuildParameters, + newBuildParameters, + templateParameters, + ) + + if (missingParameters.length > 0) { + throw new MissingBuildParameters(missingParameters) + } + + return postWorkspaceBuild(workspace.id, { + transition: "start", + template_version_id: activeVersionId, + rich_parameter_values: newBuildParameters, + }) +} + +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 }, + ) +} + +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 +} + +type WatchWorkspaceAgentLogsOptions = { + after: number + onMessage: (logs: TypesGen.WorkspaceAgentLog[]) => void + onDone: () => void + onError: (error: Error) => void +} + +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 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 +} + +export const issueReconnectingPTYSignedToken = async ( + params: TypesGen.IssueReconnectingPTYSignedTokenRequest, +): Promise => { + const response = await axios.post( + "/api/v2/applications/reconnecting-pty-signed-token", + params, + ) + return response.data +} + +export const getWorkspaceParameters = async (workspace: TypesGen.Workspace) => { + const latestBuild = workspace.latest_build + const [templateVersionRichParameters, buildParameters] = await Promise.all([ + getTemplateVersionRichParameters(latestBuild.template_version_id), + getWorkspaceBuildParameters(latestBuild.id), + ]) + return { + templateVersionRichParameters, + buildParameters, + } +} + +type InsightsFilter = { + start_time: string + end_time: string + template_ids: string +} + +export const getInsightsUserLatency = async ( + filters: InsightsFilter, +): Promise => { + const params = new URLSearchParams(filters) + const response = await axios.get(`/api/v2/insights/user-latency?${params}`) + return response.data +} + +export const getInsightsTemplate = async ( + filters: InsightsFilter, +): Promise => { + const params = new URLSearchParams({ + ...filters, + interval: "day", + }) + const response = await axios.get(`/api/v2/insights/templates?${params}`) + return response.data +} + +export const getHealth = () => { + return axios.get<{ + healthy: boolean + time: string + coder_version: string + derp: { healthy: boolean } + access_url: { healthy: boolean } + websocket: { healthy: boolean } + database: { healthy: boolean } + }>("/api/v2/debug/health") +} diff --git a/site/src/xServices/templateVersion/templateVersionXService.ts b/site/src/xServices/templateVersion/templateVersionXService.ts new file mode 100644 index 0000000000000..407324a0f2e9c --- /dev/null +++ b/site/src/xServices/templateVersion/templateVersionXService.ts @@ -0,0 +1,179 @@ +import { + getPreviousTemplateVersionByName, + GetPreviousTemplateVersionByNameResponse, + getTemplateByName, + getTemplateVersionByName, +} from "api/api" +import { Template, TemplateVersion } from "api/typesGenerated" +import { + getTemplateVersionFiles, + TemplateVersionFiles, +} from "utils/templateVersion" +import { assign, createMachine } from "xstate" + +export interface TemplateVersionMachineContext { + orgId: string + templateName: string + versionName: string + template?: Template + currentVersion?: TemplateVersion + currentFiles?: TemplateVersionFiles + error?: unknown + // Get file diffs + previousVersion?: TemplateVersion + previousFiles?: TemplateVersionFiles +} + +export const templateVersionMachine = createMachine( + { + predictableActionArguments: true, + id: "templateVersion", + schema: { + context: {} as TemplateVersionMachineContext, + services: {} as { + loadVersions: { + data: { + currentVersion: GetPreviousTemplateVersionByNameResponse + previousVersion: GetPreviousTemplateVersionByNameResponse + } + } + loadTemplate: { + data: { + template: Template + } + } + loadFiles: { + data: { + currentFiles: TemplateVersionFiles + previousFiles: TemplateVersionFiles + } + } + }, + }, + tsTypes: {} as import("./templateVersionXService.typegen").Typegen0, + initial: "initialInfo", + states: { + initialInfo: { + type: "parallel", + states: { + versions: { + initial: "loadingVersions", + states: { + loadingVersions: { + invoke: { + src: "loadVersions", + onDone: [ + { + actions: "assignVersions", + target: "success", + }, + ], + }, + }, + success: { + type: "final", + }, + }, + }, + template: { + initial: "loadingTemplate", + states: { + loadingTemplate: { + invoke: { + src: "loadTemplate", + onDone: [ + { + actions: "assignTemplate", + target: "success", + }, + ], + }, + }, + success: { + type: "final", + }, + }, + }, + }, + onDone: { + target: "loadingFiles", + }, + }, + loadingFiles: { + invoke: { + src: "loadFiles", + onDone: { + target: "done.ok", + actions: ["assignFiles"], + }, + onError: { + target: "done.error", + actions: ["assignError"], + }, + }, + }, + done: { + states: { + ok: { type: "final" }, + error: { type: "final" }, + }, + }, + }, + }, + { + actions: { + assignError: assign({ + error: (_, { data }) => data, + }), + assignTemplate: assign({ + template: (_, { data }) => data.template, + }), + assignVersions: assign({ + currentVersion: (_, { data }) => data.currentVersion, + previousVersion: (_, { data }) => data.previousVersion, + }), + assignFiles: assign({ + currentFiles: (_, { data }) => data.currentFiles, + previousFiles: (_, { data }) => data.previousFiles, + }), + }, + services: { + loadVersions: async ({ orgId, templateName, versionName }) => { + const [currentVersion, previousVersion] = await Promise.all([ + getTemplateVersionByName(orgId, templateName, versionName), + getPreviousTemplateVersionByName(orgId, templateName, versionName), + ]) + + return { + currentVersion, + previousVersion, + } + }, + loadTemplate: async ({ orgId, templateName }) => { + const template = await getTemplateByName(orgId, templateName) + + return { + template, + } + }, + loadFiles: async ({ currentVersion, previousVersion }) => { + if (!currentVersion) { + throw new Error("Version is not defined") + } + const loadFilesPromises: ReturnType[] = + [] + loadFilesPromises.push(getTemplateVersionFiles(currentVersion)) + if (previousVersion) { + loadFilesPromises.push(getTemplateVersionFiles(previousVersion)) + } + const [currentFiles, previousFiles] = await Promise.all( + loadFilesPromises, + ) + return { + currentFiles, + previousFiles, + } + }, + }, + }, +) From d1d7dab5ce2dccf5b8b25d6dfd7ad1c4f6766cc5 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 28 Aug 2023 16:49:51 +0000 Subject: [PATCH 5/9] fix: grammar --- docs/ides/gateway.md | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/docs/ides/gateway.md b/docs/ides/gateway.md index fd1f08721c825..03b5d598265bd 100644 --- a/docs/ides/gateway.md +++ b/docs/ides/gateway.md @@ -191,13 +191,13 @@ see the ### Configuration Steps -The Coder team built a POC of the JetBrains Gateway Offline Mode solution. Here are -the steps we took (and "gotchas"): +The Coder team built a POC of the JetBrains Gateway Offline Mode solution. Here +are the steps we took (and "gotchas"): ### 1. Deploy the server and install the Client Downloader -We deployed a simple Ubuntu VM and installed the JetBrains Client Downloader binary. Note -that the server must be a Linux-based distribution. +We deployed a simple Ubuntu VM and installed the JetBrains Client Downloader +binary. Note that the server must be a Linux-based distribution. ```shell wget https://download.jetbrains.com/idea/code-with-me/backend/jetbrains-clients-downloader-linux-x86_64-1867.tar.gz && \ @@ -206,11 +206,12 @@ tar -xzvf jetbrains-clients-downloader-linux-x86_64-1867.tar.gz ### 2. Install backends and clients -JetBrains Gateway requires both a backend to be installed on the remote host (your Coder workspace) -and a client to be installed on your local machine. You can host both on the server -in this example. +JetBrains Gateway requires both a backend to be installed on the remote host +(your Coder workspace) and a client to be installed on your local machine. You +can host both on the server in this example. -See here for the full [JetBrains product list and builds](https://data.services.jetbrains.com/products). +See here for the full +[JetBrains product list and builds](https://data.services.jetbrains.com/products). Below is the full list of supported `--platforms-filter` values: ```console @@ -233,12 +234,13 @@ This is the same command as above, with the `--download-backends` flag removed. ./jetbrains-clients-downloader-linux-x86_64-1867/bin4/jetbrains-clients-downloader --products-filter --build-filter --platforms-filter linux-x64 ~/clients ``` -We now have both clients and backends installed in +We now have both clients and backends installed. ### 3. Install a web server -You will need to run a web server in order to serve requests to the backend and client -files. We installed `nginx` and setup an FQDN and routed all requests to `/`. See below: +You will need to run a web server in order to serve requests to the backend and +client files. We installed `nginx` and setup an FQDN and routed all requests to +`/`. See below: ```console server { @@ -258,19 +260,22 @@ server { ``` Then, configure your DNS entry to point to the IP address of the server. For the -purposes of the POC, we did not configure TLS, although that is a supported option. +purposes of the POC, we did not configure TLS, although that is a supported +option. ### 4. Setup SSH connection with JetBrains Gateway With the server now configured, you can now configure your local machine to use -Gateway. Here is the documentation to [setup SSH config via the Coder CLI](../ides.md#ssh-configuration). -On the Gateway side, follow our guide here until step 16. +Gateway. Here is the documentation to +[setup SSH config via the Coder CLI](../ides.md#ssh-configuration). On the +Gateway side, follow our guide here until step 16. -Instead of downloading from jetbrains.com, we will point Gateway to our server endpoint. -Select `Installation options...` and select `Use download link`. Note that the URL -must explicitly reference the archive file: +Instead of downloading from jetbrains.com, we will point Gateway to our server +endpoint. Select `Installation options...` and select `Use download link`. Note +that the URL must explicitly reference the archive file: ![Offline Gateway](../images/gateway/offline-gateway.png) -Click `Download IDE and Connect`. Gateway should now download the backend and clients -from the server into your remote workspace and local machine, respectively. +Click `Download IDE and Connect`. Gateway should now download the backend and +clients from the server into your remote workspace and local machine, +respectively. From aa602e455ceee089b723ea22ed4ed566ab8d6ab7 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 29 Aug 2023 16:15:50 +0000 Subject: [PATCH 6/9] add: multi platforms syntax --- docs/ides/gateway.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ides/gateway.md b/docs/ides/gateway.md index 03b5d598265bd..14037c63d3fa9 100644 --- a/docs/ides/gateway.md +++ b/docs/ides/gateway.md @@ -223,7 +223,7 @@ To install both backends and clients, you will need to run two commands. **Backends** ```shell -./jetbrains-clients-downloader-linux-x86_64-1867/bin4/jetbrains-clients-downloader --products-filter --build-filter --platforms-filter linux-x64 --download-backends ~/backends +./jetbrains-clients-downloader-linux-x86_64-1867/bin4/jetbrains-clients-downloader --products-filter --build-filter --platforms-filter linux-x64,windows-x64,osx-x64 --download-backends ~/backends ``` **Clients** @@ -231,7 +231,7 @@ To install both backends and clients, you will need to run two commands. This is the same command as above, with the `--download-backends` flag removed. ```shell -./jetbrains-clients-downloader-linux-x86_64-1867/bin4/jetbrains-clients-downloader --products-filter --build-filter --platforms-filter linux-x64 ~/clients +./jetbrains-clients-downloader-linux-x86_64-1867/bin4/jetbrains-clients-downloader --products-filter --build-filter --platforms-filter linux-x64,windows-x64,osx-x64 ~/clients ``` We now have both clients and backends installed. From 4cb306d9967eb1eed1e8a5f839261e33900a0a75 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 29 Aug 2023 20:11:02 +0000 Subject: [PATCH 7/9] add: client-side files --- docs/ides/gateway.md | 59 +++++++++++++++++- .../gateway/jetbrains-offline-windows.png | Bin 0 -> 45299 bytes 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 docs/images/gateway/jetbrains-offline-windows.png diff --git a/docs/ides/gateway.md b/docs/ides/gateway.md index 14037c63d3fa9..be65b45f4e015 100644 --- a/docs/ides/gateway.md +++ b/docs/ides/gateway.md @@ -263,7 +263,64 @@ Then, configure your DNS entry to point to the IP address of the server. For the purposes of the POC, we did not configure TLS, although that is a supported option. -### 4. Setup SSH connection with JetBrains Gateway +### 4. Add Client Files + +You will need to add the following files on your local machine in order for Gateway +to pull the backend and client from the server. + +```shell +$ cat productsInfoUrl + +file://Users/YourUsername/backends//products.json + +$ cat clientDownloadUrl + +https://internal.site/clients/ + +$ cat jreDownloadUrl + +https://internal.site/jre/ + +$ cat pgpPublicKeyUrl + +https://internal.site/KEYS +``` + +The location of these files will depend upon your local operating system: + +**macOS** + +```console +# User-specific settings +/Users/UserName/Library/Application Support/JetBrains/RemoteDev +# System-wide settings +/Library/Application Support/JetBrains/RemoteDev/ +``` + +**Linux** + +```console +# User-specific settings +$HOME/.config/JetBrains/RemoteDev +# System-wide settings +/etc/xdg/JetBrains/RemoteDev/ +``` + +**Windows** + +```console +# User-specific settings +HKEY_CURRENT_USER registry +# System-wide settings +HKEY_LOCAL_MACHINE registry +``` + +Additionally, create a string for each setting with its appropriate value in +`SOFTWARE\JetBrains\RemoteDev`: + +![Alt text](../images/gateway/jetbrains-offline-windows.png) + +### 5. Setup SSH connection with JetBrains Gateway With the server now configured, you can now configure your local machine to use Gateway. Here is the documentation to diff --git a/docs/images/gateway/jetbrains-offline-windows.png b/docs/images/gateway/jetbrains-offline-windows.png new file mode 100644 index 0000000000000000000000000000000000000000..23e8d42014773656609634590af548368a80e215 GIT binary patch literal 45299 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

By)Dbpx1gsYUJa;k+7T5)-OGW7dz? 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 ZCzrqH8$!JHI8Ok7igIeQ1rMLT`9G`y8u0)C literal 0 HcmV?d00001 From 48021a1a29834195e89a3950caea48c2c9d5326c Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 29 Aug 2023 20:18:08 +0000 Subject: [PATCH 8/9] make: fmt --- docs/ides/gateway.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ides/gateway.md b/docs/ides/gateway.md index be65b45f4e015..a16dc240d18ea 100644 --- a/docs/ides/gateway.md +++ b/docs/ides/gateway.md @@ -265,8 +265,8 @@ option. ### 4. Add Client Files -You will need to add the following files on your local machine in order for Gateway -to pull the backend and client from the server. +You will need to add the following files on your local machine in order for +Gateway to pull the backend and client from the server. ```shell $ cat productsInfoUrl From e1d153f03920e65151dfed1ebab099d41d316545 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 29 Aug 2023 20:54:55 +0000 Subject: [PATCH 9/9] add: client file contexts --- docs/ides/gateway.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ides/gateway.md b/docs/ides/gateway.md index a16dc240d18ea..0d5549bafe92b 100644 --- a/docs/ides/gateway.md +++ b/docs/ides/gateway.md @@ -269,19 +269,19 @@ You will need to add the following files on your local machine in order for Gateway to pull the backend and client from the server. ```shell -$ cat productsInfoUrl +$ cat productsInfoUrl # a path to products.json that was generated by the backend's downloader (it could be http://, https://, or file://) -file://Users/YourUsername/backends//products.json +https://internal.site/backends//products.json -$ cat clientDownloadUrl +$ cat clientDownloadUrl # a path for clients that you got from the clients' downloader (it could be http://, https://, or file://) https://internal.site/clients/ -$ cat jreDownloadUrl +$ cat jreDownloadUrl # a path for JBR that you got from the clients' downloader (it could be http://, https://, or file://) https://internal.site/jre/ -$ cat pgpPublicKeyUrl +$ cat pgpPublicKeyUrl # a URL to the KEYS file that was downloaded with the clients builds. https://internal.site/KEYS ```