mirror of https://github.com/coder/coder.git
chore(site): remove xstate (#10659)
This commit is contained in:
parent
ef70165a8a
commit
90b6e86555
|
@ -101,10 +101,6 @@ updates:
|
||||||
xterm:
|
xterm:
|
||||||
patterns:
|
patterns:
|
||||||
- "xterm*"
|
- "xterm*"
|
||||||
xstate:
|
|
||||||
patterns:
|
|
||||||
- "xstate"
|
|
||||||
- "@xstate*"
|
|
||||||
mui:
|
mui:
|
||||||
patterns:
|
patterns:
|
||||||
- "@mui*"
|
- "@mui*"
|
||||||
|
|
|
@ -20,7 +20,6 @@ yarn-error.log
|
||||||
|
|
||||||
# Front-end ignore patterns.
|
# Front-end ignore patterns.
|
||||||
.next/
|
.next/
|
||||||
site/**/*.typegen.ts
|
|
||||||
site/build-storybook.log
|
site/build-storybook.log
|
||||||
site/coverage/
|
site/coverage/
|
||||||
site/storybook-static/
|
site/storybook-static/
|
||||||
|
|
|
@ -23,7 +23,6 @@ yarn-error.log
|
||||||
|
|
||||||
# Front-end ignore patterns.
|
# Front-end ignore patterns.
|
||||||
.next/
|
.next/
|
||||||
site/**/*.typegen.ts
|
|
||||||
site/build-storybook.log
|
site/build-storybook.log
|
||||||
site/coverage/
|
site/coverage/
|
||||||
site/storybook-static/
|
site/storybook-static/
|
||||||
|
|
|
@ -170,7 +170,6 @@
|
||||||
"wsconncache",
|
"wsconncache",
|
||||||
"wsjson",
|
"wsjson",
|
||||||
"xerrors",
|
"xerrors",
|
||||||
"xstate",
|
|
||||||
"yamux"
|
"yamux"
|
||||||
],
|
],
|
||||||
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
|
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
|
||||||
|
|
|
@ -34,7 +34,6 @@ important ones:
|
||||||
- [react-router](https://reactrouter.com/en/main) for routing
|
- [react-router](https://reactrouter.com/en/main) for routing
|
||||||
- [TanStack Query v4](https://tanstack.com/query/v4/docs/react/overview) for
|
- [TanStack Query v4](https://tanstack.com/query/v4/docs/react/overview) for
|
||||||
fetching data
|
fetching data
|
||||||
- [XState](https://xstate.js.org/docs/) for handling complex state flows
|
|
||||||
- [axios](https://github.com/axios/axios) as fetching lib
|
- [axios](https://github.com/axios/axios) as fetching lib
|
||||||
- [Playwright](https://playwright.dev/) for end-to-end (E2E) testing
|
- [Playwright](https://playwright.dev/) for end-to-end (E2E) testing
|
||||||
- [Jest](https://jestjs.io/) for integration testing
|
- [Jest](https://jestjs.io/) for integration testing
|
||||||
|
@ -96,13 +95,7 @@ a `*.stories.ts` file.
|
||||||
|
|
||||||
We use
|
We use
|
||||||
[TanStack Query v4](https://tanstack.com/query/v4/docs/react/overview)(previously
|
[TanStack Query v4](https://tanstack.com/query/v4/docs/react/overview)(previously
|
||||||
known as react-query) to fetch data from the API. We also use
|
known as react-query) to fetch data from the API.
|
||||||
[XState](https://xstate.js.org/docs/) to handle complex flows with multiple
|
|
||||||
states and transitions.
|
|
||||||
|
|
||||||
> ℹ️ We recently changed how we are going to fetch data from the server so you
|
|
||||||
> will see a lot of fetches being made using XState machines but feel free to
|
|
||||||
> refactor it if you are already touching those files.
|
|
||||||
|
|
||||||
### Where to fetch data
|
### Where to fetch data
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,6 @@ yarn-error.log
|
||||||
|
|
||||||
# Front-end ignore patterns.
|
# Front-end ignore patterns.
|
||||||
.next/
|
.next/
|
||||||
**/*.typegen.ts
|
|
||||||
build-storybook.log
|
build-storybook.log
|
||||||
coverage/
|
coverage/
|
||||||
storybook-static/
|
storybook-static/
|
||||||
|
|
|
@ -23,7 +23,6 @@ yarn-error.log
|
||||||
|
|
||||||
# Front-end ignore patterns.
|
# Front-end ignore patterns.
|
||||||
.next/
|
.next/
|
||||||
**/*.typegen.ts
|
|
||||||
build-storybook.log
|
build-storybook.log
|
||||||
coverage/
|
coverage/
|
||||||
storybook-static/
|
storybook-static/
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "pnpm typegen",
|
|
||||||
"build": "NODE_ENV=production pnpm vite build",
|
"build": "NODE_ENV=production pnpm vite build",
|
||||||
"check:all": "pnpm format:check && pnpm lint && pnpm test",
|
"check:all": "pnpm format:check && pnpm lint && pnpm test",
|
||||||
"chromatic": "chromatic",
|
"chromatic": "chromatic",
|
||||||
|
@ -13,7 +12,7 @@
|
||||||
"format:check": "prettier --cache --check '../**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
"format:check": "prettier --cache --check '../**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
||||||
"format:write": "prettier --cache --write '../**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
"format:write": "prettier --cache --write '../**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
||||||
"format:write:only": "prettier --cache --write",
|
"format:write:only": "prettier --cache --write",
|
||||||
"lint": "pnpm typegen && pnpm run lint:types && pnpm exec jest --selectProjects lint",
|
"lint": "pnpm run lint:types && pnpm exec jest --selectProjects lint",
|
||||||
"lint:fix": "FIX=true pnpm lint",
|
"lint:fix": "FIX=true pnpm lint",
|
||||||
"lint:types": "tsc --noEmit",
|
"lint:types": "tsc --noEmit",
|
||||||
"playwright:install": "playwright install --with-deps chromium",
|
"playwright:install": "playwright install --with-deps chromium",
|
||||||
|
@ -25,9 +24,8 @@
|
||||||
"test:ci": "jest --selectProjects test --silent",
|
"test:ci": "jest --selectProjects test --silent",
|
||||||
"test:coverage": "jest --selectProjects test --collectCoverage",
|
"test:coverage": "jest --selectProjects test --collectCoverage",
|
||||||
"test:watch": "jest --selectProjects test --watch",
|
"test:watch": "jest --selectProjects test --watch",
|
||||||
"typegen": "xstate typegen 'src/**/*.ts'",
|
|
||||||
"stats": "STATS=true pnpm build && npx http-server ./stats -p 8081 -c-1",
|
"stats": "STATS=true pnpm build && npx http-server ./stats -p 8081 -c-1",
|
||||||
"deadcode": "ts-prune | grep -v \".stories\\|.typegen\\|.config\\|e2e\\|__mocks__\\|used in module\\|testHelpers\\|typesGenerated\" || echo \"No deadcode found.\""
|
"deadcode": "ts-prune | grep -v \".stories\\|.config\\|e2e\\|__mocks__\\|used in module\\|testHelpers\\|typesGenerated\" || echo \"No deadcode found.\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emoji-mart/data": "1.1.2",
|
"@emoji-mart/data": "1.1.2",
|
||||||
|
@ -46,8 +44,6 @@
|
||||||
"@mui/system": "5.14.0",
|
"@mui/system": "5.14.0",
|
||||||
"@mui/utils": "5.14.11",
|
"@mui/utils": "5.14.11",
|
||||||
"@vitejs/plugin-react": "4.1.0",
|
"@vitejs/plugin-react": "4.1.0",
|
||||||
"@xstate/inspect": "0.8.0",
|
|
||||||
"@xstate/react": "3.2.1",
|
|
||||||
"ansi-to-html": "0.7.2",
|
"ansi-to-html": "0.7.2",
|
||||||
"axios": "1.6.0",
|
"axios": "1.6.0",
|
||||||
"canvas": "2.11.0",
|
"canvas": "2.11.0",
|
||||||
|
@ -94,7 +90,6 @@
|
||||||
"unique-names-generator": "4.7.1",
|
"unique-names-generator": "4.7.1",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"vite": "4.5.0",
|
"vite": "4.5.0",
|
||||||
"xstate": "4.38.1",
|
|
||||||
"xterm": "5.2.0",
|
"xterm": "5.2.0",
|
||||||
"xterm-addon-canvas": "0.5.0",
|
"xterm-addon-canvas": "0.5.0",
|
||||||
"xterm-addon-fit": "0.8.0",
|
"xterm-addon-fit": "0.8.0",
|
||||||
|
@ -138,7 +133,6 @@
|
||||||
"@types/uuid": "9.0.2",
|
"@types/uuid": "9.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "6.9.1",
|
"@typescript-eslint/eslint-plugin": "6.9.1",
|
||||||
"@typescript-eslint/parser": "6.9.1",
|
"@typescript-eslint/parser": "6.9.1",
|
||||||
"@xstate/cli": "0.5.2",
|
|
||||||
"chromatic": "7.6.0",
|
"chromatic": "7.6.0",
|
||||||
"eslint": "8.52.0",
|
"eslint": "8.52.0",
|
||||||
"eslint-config-prettier": "9.0.0",
|
"eslint-config-prettier": "9.0.0",
|
||||||
|
|
|
@ -57,12 +57,6 @@ dependencies:
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: 4.1.0
|
specifier: 4.1.0
|
||||||
version: 4.1.0(vite@4.5.0)
|
version: 4.1.0(vite@4.5.0)
|
||||||
'@xstate/inspect':
|
|
||||||
specifier: 0.8.0
|
|
||||||
version: 0.8.0(ws@8.14.2)(xstate@4.38.1)
|
|
||||||
'@xstate/react':
|
|
||||||
specifier: 3.2.1
|
|
||||||
version: 3.2.1(@types/react@18.2.6)(react@18.2.0)(xstate@4.38.1)
|
|
||||||
ansi-to-html:
|
ansi-to-html:
|
||||||
specifier: 0.7.2
|
specifier: 0.7.2
|
||||||
version: 0.7.2
|
version: 0.7.2
|
||||||
|
@ -201,9 +195,6 @@ dependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: 4.5.0
|
specifier: 4.5.0
|
||||||
version: 4.5.0(@types/node@18.18.1)
|
version: 4.5.0(@types/node@18.18.1)
|
||||||
xstate:
|
|
||||||
specifier: 4.38.1
|
|
||||||
version: 4.38.1
|
|
||||||
xterm:
|
xterm:
|
||||||
specifier: 5.2.0
|
specifier: 5.2.0
|
||||||
version: 5.2.0
|
version: 5.2.0
|
||||||
|
@ -329,9 +320,6 @@ devDependencies:
|
||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
specifier: 6.9.1
|
specifier: 6.9.1
|
||||||
version: 6.9.1(eslint@8.52.0)(typescript@5.2.2)
|
version: 6.9.1(eslint@8.52.0)(typescript@5.2.2)
|
||||||
'@xstate/cli':
|
|
||||||
specifier: 0.5.2
|
|
||||||
version: 0.5.2
|
|
||||||
chromatic:
|
chromatic:
|
||||||
specifier: 7.6.0
|
specifier: 7.6.0
|
||||||
version: 7.6.0
|
version: 7.6.0
|
||||||
|
@ -474,29 +462,6 @@ packages:
|
||||||
resolution: {integrity: sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==}
|
resolution: {integrity: sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
/@babel/core@7.22.9:
|
|
||||||
resolution: {integrity: sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
dependencies:
|
|
||||||
'@ampproject/remapping': 2.2.1
|
|
||||||
'@babel/code-frame': 7.22.13
|
|
||||||
'@babel/generator': 7.23.0
|
|
||||||
'@babel/helper-compilation-targets': 7.22.15
|
|
||||||
'@babel/helper-module-transforms': 7.23.0(@babel/core@7.22.9)
|
|
||||||
'@babel/helpers': 7.23.2
|
|
||||||
'@babel/parser': 7.23.0
|
|
||||||
'@babel/template': 7.22.15
|
|
||||||
'@babel/traverse': 7.23.2
|
|
||||||
'@babel/types': 7.23.0
|
|
||||||
convert-source-map: 1.9.0
|
|
||||||
debug: 4.3.4
|
|
||||||
gensync: 1.0.0-beta.2
|
|
||||||
json5: 2.2.3
|
|
||||||
semver: 7.5.3
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@babel/core@7.23.0:
|
/@babel/core@7.23.0:
|
||||||
resolution: {integrity: sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==}
|
resolution: {integrity: sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
@ -668,20 +633,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.23.0
|
'@babel/types': 7.23.0
|
||||||
|
|
||||||
/@babel/helper-module-transforms@7.23.0(@babel/core@7.22.9):
|
|
||||||
resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
peerDependencies:
|
|
||||||
'@babel/core': ^7.0.0
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.22.9
|
|
||||||
'@babel/helper-environment-visitor': 7.22.20
|
|
||||||
'@babel/helper-module-imports': 7.22.15
|
|
||||||
'@babel/helper-simple-access': 7.22.5
|
|
||||||
'@babel/helper-split-export-declaration': 7.22.6
|
|
||||||
'@babel/helper-validator-identifier': 7.22.20
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@babel/helper-module-transforms@7.23.0(@babel/core@7.23.0):
|
/@babel/helper-module-transforms@7.23.0(@babel/core@7.23.0):
|
||||||
resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==}
|
resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
@ -5799,81 +5750,6 @@ packages:
|
||||||
resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==}
|
resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@xstate/cli@0.5.2:
|
|
||||||
resolution: {integrity: sha512-KA0BJMd80Z3lp1MmVqlUpHkjLkKcq4Z09P19It4iJ6IX8Hzwo5lmRTZwX938UCiAjWWSA2jl6nfrJnfBR21riA==}
|
|
||||||
hasBin: true
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.22.9
|
|
||||||
'@xstate/machine-extractor': 0.10.0(xstate@4.38.2)
|
|
||||||
'@xstate/tools-shared': 3.0.1(xstate@4.38.2)
|
|
||||||
chokidar: 3.5.3
|
|
||||||
commander: 8.3.0
|
|
||||||
prettier: 2.8.8
|
|
||||||
xstate: 4.38.2
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@xstate/inspect@0.8.0(ws@8.14.2)(xstate@4.38.1):
|
|
||||||
resolution: {integrity: sha512-wSkFeOnp+7dhn+zTThO0M4D2FEqZN9lGIWowJu5JLa2ojjtlzRwK8SkjcHZ4rLX8VnMev7kGjgQLrGs8kxy+hw==}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/ws': ^8.0.0
|
|
||||||
ws: ^8.0.0
|
|
||||||
xstate: ^4.37.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/ws':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
fast-safe-stringify: 2.1.1
|
|
||||||
ws: 8.14.2
|
|
||||||
xstate: 4.38.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@xstate/machine-extractor@0.10.0(xstate@4.38.2):
|
|
||||||
resolution: {integrity: sha512-jsnYU9Y0DfFQCisY0IGxjmFrU6y3aqdBXBNohasxHiKHfuItkk4AUAQ+07MykJ+RiczXIYqYQNu5DvZAfCqKCA==}
|
|
||||||
peerDependencies:
|
|
||||||
xstate: ^4
|
|
||||||
dependencies:
|
|
||||||
'@babel/parser': 7.23.0
|
|
||||||
'@babel/traverse': 7.23.2
|
|
||||||
'@babel/types': 7.23.0
|
|
||||||
recast: 0.23.4
|
|
||||||
xstate: 4.38.2
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@xstate/react@3.2.1(@types/react@18.2.6)(react@18.2.0)(xstate@4.38.1):
|
|
||||||
resolution: {integrity: sha512-L/mqYRxyBWVdIdSaXBHacfvS8NKn3sTKbPb31aRADbE9spsJ1p+tXil0GVQHPlzrmjGeozquLrxuYGiXsFNU7g==}
|
|
||||||
peerDependencies:
|
|
||||||
'@xstate/fsm': ^2.0.0
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
xstate: ^4.36.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@xstate/fsm':
|
|
||||||
optional: true
|
|
||||||
xstate:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
react: 18.2.0
|
|
||||||
use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.6)(react@18.2.0)
|
|
||||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
|
||||||
xstate: 4.38.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@xstate/tools-shared@3.0.1(xstate@4.38.2):
|
|
||||||
resolution: {integrity: sha512-XW00KB72i4XiQPiB0e4P7Fsn9TvYBxqVR0HNGGEkmvQ7l8FZM2FpzBDAriVH67XRUgI1crfNyisxXmGlpB5WYg==}
|
|
||||||
peerDependencies:
|
|
||||||
xstate: ^4
|
|
||||||
dependencies:
|
|
||||||
'@xstate/machine-extractor': 0.10.0(xstate@4.38.2)
|
|
||||||
xstate: 4.38.2
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.18.20):
|
/@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.18.20):
|
||||||
resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==}
|
resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==}
|
||||||
engines: {node: '>=14.15.0'}
|
engines: {node: '>=14.15.0'}
|
||||||
|
@ -8169,10 +8045,6 @@ packages:
|
||||||
resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==}
|
resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/fast-safe-stringify@2.1.1:
|
|
||||||
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/fast-shallow-equal@1.0.0:
|
/fast-shallow-equal@1.0.0:
|
||||||
resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==}
|
resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -13477,19 +13349,6 @@ packages:
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/use-isomorphic-layout-effect@1.1.2(@types/react@18.2.6)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': '*'
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 18.2.6
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/use-resize-observer@9.1.0(react-dom@18.2.0)(react@18.2.0):
|
/use-resize-observer@9.1.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==}
|
resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -13924,14 +13783,6 @@ packages:
|
||||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/xstate@4.38.1:
|
|
||||||
resolution: {integrity: sha512-1gBUcFWBj/rv/pRcP2Bedl5sNRGX2d36CaOx9z7fE9uSiHaOEHIWzLg1B853q2xdUHUA9pEiWKjLZ3can4SJaQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/xstate@4.38.2:
|
|
||||||
resolution: {integrity: sha512-Fba/DwEPDLneHT3tbJ9F3zafbQXszOlyCJyQqqdzmtlY/cwE2th462KK48yaANf98jHlP6lJvxfNtN0LFKXPQg==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/xtend@4.0.2:
|
/xtend@4.0.2:
|
||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
|
|
@ -525,7 +525,7 @@ export const postWorkspaceBuild = async (
|
||||||
export const startWorkspace = (
|
export const startWorkspace = (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
templateVersionId: string,
|
templateVersionId: string,
|
||||||
logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"],
|
logLevel?: TypesGen.ProvisionerLogLevel,
|
||||||
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
||||||
) =>
|
) =>
|
||||||
postWorkspaceBuild(workspaceId, {
|
postWorkspaceBuild(workspaceId, {
|
||||||
|
@ -536,16 +536,21 @@ export const startWorkspace = (
|
||||||
});
|
});
|
||||||
export const stopWorkspace = (
|
export const stopWorkspace = (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"],
|
logLevel?: TypesGen.ProvisionerLogLevel,
|
||||||
) =>
|
) =>
|
||||||
postWorkspaceBuild(workspaceId, {
|
postWorkspaceBuild(workspaceId, {
|
||||||
transition: "stop",
|
transition: "stop",
|
||||||
log_level: logLevel,
|
log_level: logLevel,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type DeleteWorkspaceOptions = Pick<
|
||||||
|
TypesGen.CreateWorkspaceBuildRequest,
|
||||||
|
"log_level" & "orphan"
|
||||||
|
>;
|
||||||
|
|
||||||
export const deleteWorkspace = (
|
export const deleteWorkspace = (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
options?: Pick<TypesGen.CreateWorkspaceBuildRequest, "log_level" & "orphan">,
|
options?: DeleteWorkspaceOptions,
|
||||||
) =>
|
) =>
|
||||||
postWorkspaceBuild(workspaceId, {
|
postWorkspaceBuild(workspaceId, {
|
||||||
transition: "delete",
|
transition: "delete",
|
||||||
|
@ -1207,10 +1212,15 @@ export const removeLicense = async (licenseId: number): Promise<void> => {
|
||||||
|
|
||||||
export class MissingBuildParameters extends Error {
|
export class MissingBuildParameters extends Error {
|
||||||
parameters: TypesGen.TemplateVersionParameter[] = [];
|
parameters: TypesGen.TemplateVersionParameter[] = [];
|
||||||
|
versionId: string;
|
||||||
|
|
||||||
constructor(parameters: TypesGen.TemplateVersionParameter[]) {
|
constructor(
|
||||||
|
parameters: TypesGen.TemplateVersionParameter[],
|
||||||
|
versionId: string,
|
||||||
|
) {
|
||||||
super("Missing build parameters.");
|
super("Missing build parameters.");
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
|
this.versionId = versionId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1239,7 +1249,7 @@ export const changeWorkspaceVersion = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
if (missingParameters.length > 0) {
|
if (missingParameters.length > 0) {
|
||||||
throw new MissingBuildParameters(missingParameters);
|
throw new MissingBuildParameters(missingParameters, templateVersionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return postWorkspaceBuild(workspace.id, {
|
return postWorkspaceBuild(workspace.id, {
|
||||||
|
@ -1277,7 +1287,7 @@ export const updateWorkspace = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
if (missingParameters.length > 0) {
|
if (missingParameters.length > 0) {
|
||||||
throw new MissingBuildParameters(missingParameters);
|
throw new MissingBuildParameters(missingParameters, activeVersionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return postWorkspaceBuild(workspace.id, {
|
return postWorkspaceBuild(workspace.id, {
|
||||||
|
|
|
@ -27,3 +27,10 @@ export const health = () => {
|
||||||
queryFn: API.getHealth,
|
queryFn: API.getHealth,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deploymentSSHConfig = () => {
|
||||||
|
return {
|
||||||
|
queryKey: ["deployment", "sshConfig"],
|
||||||
|
queryFn: API.getDeploymentSSHConfig,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -29,6 +29,11 @@ export const workspaceBuildByNumber = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const workspaceBuildsKey = (workspaceId: string) => [
|
||||||
|
"workspaceBuilds",
|
||||||
|
workspaceId,
|
||||||
|
];
|
||||||
|
|
||||||
export const infiniteWorkspaceBuilds = (
|
export const infiniteWorkspaceBuilds = (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
req?: WorkspaceBuildsRequest,
|
req?: WorkspaceBuildsRequest,
|
||||||
|
@ -36,7 +41,7 @@ export const infiniteWorkspaceBuilds = (
|
||||||
const limit = req?.limit ?? 25;
|
const limit = req?.limit ?? 25;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryKey: ["workspaceBuilds", workspaceId, req],
|
queryKey: [...workspaceBuildsKey(workspaceId), req],
|
||||||
getNextPageParam: (lastPage, pages) => {
|
getNextPageParam: (lastPage, pages) => {
|
||||||
if (lastPage.length < limit) {
|
if (lastPage.length < limit) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
@ -9,7 +9,10 @@ import {
|
||||||
type CreateWorkspaceRequest,
|
type CreateWorkspaceRequest,
|
||||||
type WorkspacesResponse,
|
type WorkspacesResponse,
|
||||||
type WorkspacesRequest,
|
type WorkspacesRequest,
|
||||||
|
WorkspaceBuild,
|
||||||
|
ProvisionerLogLevel,
|
||||||
} from "api/typesGenerated";
|
} from "api/typesGenerated";
|
||||||
|
import { workspaceBuildsKey } from "./workspaceBuilds";
|
||||||
|
|
||||||
export const workspaceByOwnerAndNameKey = (owner: string, name: string) => [
|
export const workspaceByOwnerAndNameKey = (owner: string, name: string) => [
|
||||||
"workspace",
|
"workspace",
|
||||||
|
@ -18,13 +21,11 @@ export const workspaceByOwnerAndNameKey = (owner: string, name: string) => [
|
||||||
"settings",
|
"settings",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const workspaceByOwnerAndName = (
|
export const workspaceByOwnerAndName = (owner: string, name: string) => {
|
||||||
owner: string,
|
|
||||||
name: string,
|
|
||||||
): QueryOptions<Workspace> => {
|
|
||||||
return {
|
return {
|
||||||
queryKey: workspaceByOwnerAndNameKey(owner, name),
|
queryKey: workspaceByOwnerAndNameKey(owner, name),
|
||||||
queryFn: () => API.getWorkspaceByOwnerAndName(owner, name),
|
queryFn: () =>
|
||||||
|
API.getWorkspaceByOwnerAndName(owner, name, { include_deleted: true }),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,3 +125,135 @@ export const increaseDeadline = (workspace: Workspace) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const changeVersion = (
|
||||||
|
workspace: Workspace,
|
||||||
|
queryClient: QueryClient,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
mutationFn: ({
|
||||||
|
versionId,
|
||||||
|
buildParameters,
|
||||||
|
}: {
|
||||||
|
versionId: string;
|
||||||
|
buildParameters?: WorkspaceBuildParameter[];
|
||||||
|
}) => {
|
||||||
|
return API.changeWorkspaceVersion(workspace, versionId, buildParameters);
|
||||||
|
},
|
||||||
|
onSuccess: async (build: WorkspaceBuild) => {
|
||||||
|
await updateWorkspaceBuild(build, queryClient);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateWorkspace = (
|
||||||
|
workspace: Workspace,
|
||||||
|
queryClient: QueryClient,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
mutationFn: (buildParameters?: WorkspaceBuildParameter[]) => {
|
||||||
|
return API.updateWorkspace(workspace, buildParameters);
|
||||||
|
},
|
||||||
|
onSuccess: async (build: WorkspaceBuild) => {
|
||||||
|
await updateWorkspaceBuild(build, queryClient);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteWorkspace = (
|
||||||
|
workspace: Workspace,
|
||||||
|
queryClient: QueryClient,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
mutationFn: (options: API.DeleteWorkspaceOptions) => {
|
||||||
|
return API.deleteWorkspace(workspace.id, options);
|
||||||
|
},
|
||||||
|
onSuccess: async (build: WorkspaceBuild) => {
|
||||||
|
await updateWorkspaceBuild(build, queryClient);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopWorkspace = (
|
||||||
|
workspace: Workspace,
|
||||||
|
queryClient: QueryClient,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
mutationFn: ({ logLevel }: { logLevel?: ProvisionerLogLevel }) => {
|
||||||
|
return API.stopWorkspace(workspace.id, logLevel);
|
||||||
|
},
|
||||||
|
onSuccess: async (build: WorkspaceBuild) => {
|
||||||
|
await updateWorkspaceBuild(build, queryClient);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startWorkspace = (
|
||||||
|
workspace: Workspace,
|
||||||
|
queryClient: QueryClient,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
mutationFn: ({
|
||||||
|
buildParameters,
|
||||||
|
logLevel,
|
||||||
|
}: {
|
||||||
|
buildParameters?: WorkspaceBuildParameter[];
|
||||||
|
logLevel?: ProvisionerLogLevel;
|
||||||
|
}) => {
|
||||||
|
return API.startWorkspace(
|
||||||
|
workspace.id,
|
||||||
|
workspace.latest_build.template_version_id,
|
||||||
|
logLevel,
|
||||||
|
buildParameters,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSuccess: async (build: WorkspaceBuild) => {
|
||||||
|
await updateWorkspaceBuild(build, queryClient);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cancelBuild = (workspace: Workspace, queryClient: QueryClient) => {
|
||||||
|
return {
|
||||||
|
mutationFn: () => {
|
||||||
|
return API.cancelWorkspaceBuild(workspace.latest_build.id);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: workspaceBuildsKey(workspace.id),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const activate = (workspace: Workspace, queryClient: QueryClient) => {
|
||||||
|
return {
|
||||||
|
mutationFn: () => {
|
||||||
|
return API.updateWorkspaceDormancy(workspace.id, false);
|
||||||
|
},
|
||||||
|
onSuccess: (updatedWorkspace: Workspace) => {
|
||||||
|
queryClient.setQueryData(
|
||||||
|
workspaceByOwnerAndNameKey(workspace.owner_name, workspace.name),
|
||||||
|
updatedWorkspace,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateWorkspaceBuild = async (
|
||||||
|
build: WorkspaceBuild,
|
||||||
|
queryClient: QueryClient,
|
||||||
|
) => {
|
||||||
|
const workspaceKey = workspaceByOwnerAndNameKey(
|
||||||
|
build.workspace_owner_name,
|
||||||
|
build.workspace_name,
|
||||||
|
);
|
||||||
|
const previousData = queryClient.getQueryData(workspaceKey) as Workspace;
|
||||||
|
queryClient.setQueryData(workspaceKey, {
|
||||||
|
...previousData,
|
||||||
|
latest_build: build,
|
||||||
|
});
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: workspaceBuildsKey(build.workspace_id),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { TemplateVersionWarnings } from "./TemplateVersionWarnings";
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
|
||||||
|
|
||||||
const meta: Meta<typeof TemplateVersionWarnings> = {
|
|
||||||
title: "components/TemplateVersionWarnings",
|
|
||||||
component: TemplateVersionWarnings,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof TemplateVersionWarnings>;
|
|
||||||
|
|
||||||
export const UnsupportedWorkspaces: Story = {
|
|
||||||
args: {
|
|
||||||
warnings: ["UNSUPPORTED_WORKSPACES"],
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { FC } from "react";
|
|
||||||
import * as TypesGen from "api/typesGenerated";
|
|
||||||
import { Alert } from "components/Alert/Alert";
|
|
||||||
|
|
||||||
export interface TemplateVersionWarningsProps {
|
|
||||||
warnings?: TypesGen.TemplateVersionWarning[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TemplateVersionWarnings: FC<
|
|
||||||
React.PropsWithChildren<TemplateVersionWarningsProps>
|
|
||||||
> = (props) => {
|
|
||||||
const { warnings = [] } = props;
|
|
||||||
|
|
||||||
if (!warnings.includes("UNSUPPORTED_WORKSPACES")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div data-testid="error-unsupported-workspaces">
|
|
||||||
<Alert severity="error">
|
|
||||||
This template uses legacy parameters which are not supported anymore.
|
|
||||||
Contact your administrator for assistance.
|
|
||||||
</Alert>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,23 +1,6 @@
|
||||||
import { inspect } from "@xstate/inspect";
|
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { Interpreter } from "xstate";
|
|
||||||
import { App } from "./App";
|
import { App } from "./App";
|
||||||
|
|
||||||
// if this is a development build and the developer wants to inspect
|
|
||||||
// helpful to see realtime changes on the services
|
|
||||||
if (
|
|
||||||
process.env.NODE_ENV === "development" &&
|
|
||||||
process.env.INSPECT_XSTATE === "true"
|
|
||||||
) {
|
|
||||||
// configure the XState inspector to open in a new tab
|
|
||||||
inspect({
|
|
||||||
url: "https://stately.ai/viz?inspect",
|
|
||||||
iframe: false,
|
|
||||||
});
|
|
||||||
// configure all XServices to use the inspector
|
|
||||||
Interpreter.defaultOptions.devTools = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the entry point for the app - where everything start.
|
// This is the entry point for the app - where everything start.
|
||||||
// In the future, we'll likely bring in more bootstrapping logic -
|
// In the future, we'll likely bring in more bootstrapping logic -
|
||||||
// like: https://github.com/coder/m/blob/50898bd4803df7639bd181e484c74ac5d84da474/product/coder/site/pages/_app.tsx#L32
|
// like: https://github.com/coder/m/blob/50898bd4803df7639bd181e484c74ac5d84da474/product/coder/site/pages/_app.tsx#L32
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Meta, StoryObj } from "@storybook/react";
|
||||||
import {
|
import {
|
||||||
MockTemplate,
|
MockTemplate,
|
||||||
MockTemplateVersion,
|
MockTemplateVersion,
|
||||||
MockTemplateVersion3,
|
|
||||||
MockWorkspaceResource,
|
MockWorkspaceResource,
|
||||||
MockWorkspaceVolumeResource,
|
MockWorkspaceVolumeResource,
|
||||||
} from "testHelpers/entities";
|
} from "testHelpers/entities";
|
||||||
|
@ -31,11 +30,3 @@ export const NoIcon: Story = {
|
||||||
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
|
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithDeprecatedParameters: Story = {
|
|
||||||
args: {
|
|
||||||
template: MockTemplate,
|
|
||||||
activeVersion: MockTemplateVersion3,
|
|
||||||
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { Loader } from "components/Loader/Loader";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable";
|
import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable";
|
||||||
import { TemplateStats } from "./TemplateStats";
|
import { TemplateStats } from "./TemplateStats";
|
||||||
import { TemplateVersionWarnings } from "components/TemplateVersionWarnings/TemplateVersionWarnings";
|
|
||||||
|
|
||||||
export interface TemplateSummaryPageViewProps {
|
export interface TemplateSummaryPageViewProps {
|
||||||
resources?: WorkspaceResource[];
|
resources?: WorkspaceResource[];
|
||||||
|
@ -45,7 +44,6 @@ export const TemplateSummaryPageView: FC<TemplateSummaryPageViewProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<TemplateVersionWarnings warnings={activeVersion.warnings} />
|
|
||||||
<TemplateStats template={template} activeVersion={activeVersion} />
|
<TemplateStats template={template} activeVersion={activeVersion} />
|
||||||
<TemplateResourcesTable resources={getStartedResources(resources)} />
|
<TemplateResourcesTable resources={getStartedResources(resources)} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
@ -77,6 +77,7 @@ describe("TerminalPage", () => {
|
||||||
expect(API.getWorkspaceByOwnerAndName).toHaveBeenCalledWith(
|
expect(API.getWorkspaceByOwnerAndName).toHaveBeenCalledWith(
|
||||||
MockUser.username,
|
MockUser.username,
|
||||||
MockWorkspace.name,
|
MockWorkspace.name,
|
||||||
|
{ include_deleted: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
|
|
|
@ -747,10 +747,3 @@ function makeFailedBuildLogs(): ProvisionerJobLog[] {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UnsupportedWorkspace: Story = {
|
|
||||||
args: {
|
|
||||||
...Running.args,
|
|
||||||
templateWarnings: ["UNSUPPORTED_WORKSPACES"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
PageHeaderTitle,
|
PageHeaderTitle,
|
||||||
PageHeaderSubtitle,
|
PageHeaderSubtitle,
|
||||||
} from "components/PageHeader/FullWidthPageHeader";
|
} from "components/PageHeader/FullWidthPageHeader";
|
||||||
import { TemplateVersionWarnings } from "components/TemplateVersionWarnings/TemplateVersionWarnings";
|
|
||||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||||
import { DormantWorkspaceBanner } from "components/WorkspaceDeletion";
|
import { DormantWorkspaceBanner } from "components/WorkspaceDeletion";
|
||||||
import { Avatar } from "components/Avatar/Avatar";
|
import { Avatar } from "components/Avatar/Avatar";
|
||||||
|
@ -55,7 +54,6 @@ export interface WorkspaceProps {
|
||||||
isRestarting: boolean;
|
isRestarting: boolean;
|
||||||
workspace: TypesGen.Workspace;
|
workspace: TypesGen.Workspace;
|
||||||
resources?: TypesGen.WorkspaceResource[];
|
resources?: TypesGen.WorkspaceResource[];
|
||||||
templateWarnings?: TypesGen.TemplateVersionWarning[];
|
|
||||||
canUpdateWorkspace: boolean;
|
canUpdateWorkspace: boolean;
|
||||||
updateMessage?: string;
|
updateMessage?: string;
|
||||||
canRetryDebugMode: boolean;
|
canRetryDebugMode: boolean;
|
||||||
|
@ -105,9 +103,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||||
buildInfo,
|
buildInfo,
|
||||||
sshPrefix,
|
sshPrefix,
|
||||||
template,
|
template,
|
||||||
quotaBudget,
|
|
||||||
handleBuildRetry,
|
handleBuildRetry,
|
||||||
templateWarnings,
|
|
||||||
buildLogs,
|
buildLogs,
|
||||||
onLoadMoreBuilds,
|
onLoadMoreBuilds,
|
||||||
isLoadingMoreBuilds,
|
isLoadingMoreBuilds,
|
||||||
|
@ -198,7 +194,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||||
|
|
||||||
<WorkspaceStats
|
<WorkspaceStats
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
quotaBudget={quotaBudget}
|
|
||||||
handleUpdate={handleUpdate}
|
handleUpdate={handleUpdate}
|
||||||
canUpdateWorkspace={canUpdateWorkspace}
|
canUpdateWorkspace={canUpdateWorkspace}
|
||||||
maxDeadlineDecrease={scheduleProps.maxDeadlineDecrease}
|
maxDeadlineDecrease={scheduleProps.maxDeadlineDecrease}
|
||||||
|
@ -294,8 +289,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||||
onDismiss={() => saveLocal("dismissedWorkspace", workspace.id)}
|
onDismiss={() => saveLocal("dismissedWorkspace", workspace.id)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TemplateVersionWarnings warnings={templateWarnings} />
|
|
||||||
|
|
||||||
{showAlertPendingInQueue && (
|
{showAlertPendingInQueue && (
|
||||||
<Alert severity="info">
|
<Alert severity="info">
|
||||||
<AlertTitle>Workspace build is pending</AlertTitle>
|
<AlertTitle>Workspace build is pending</AlertTitle>
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
MockTemplateVersionParameter1,
|
MockTemplateVersionParameter1,
|
||||||
MockTemplateVersionParameter2,
|
MockTemplateVersionParameter2,
|
||||||
MockBuilds,
|
MockBuilds,
|
||||||
MockTemplateVersion3,
|
|
||||||
MockUser,
|
MockUser,
|
||||||
MockDeploymentConfig,
|
MockDeploymentConfig,
|
||||||
MockWorkspaceBuildDelete,
|
MockWorkspaceBuildDelete,
|
||||||
|
@ -259,10 +258,10 @@ describe("WorkspacePage", () => {
|
||||||
const updateWorkspaceSpy = jest
|
const updateWorkspaceSpy = jest
|
||||||
.spyOn(api, "updateWorkspace")
|
.spyOn(api, "updateWorkspace")
|
||||||
.mockRejectedValueOnce(
|
.mockRejectedValueOnce(
|
||||||
new api.MissingBuildParameters([
|
new api.MissingBuildParameters(
|
||||||
MockTemplateVersionParameter1,
|
[MockTemplateVersionParameter1, MockTemplateVersionParameter2],
|
||||||
MockTemplateVersionParameter2,
|
MockOutdatedWorkspace.template_active_version_id,
|
||||||
]),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
|
@ -324,20 +323,6 @@ describe("WorkspacePage", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows the template warning", async () => {
|
|
||||||
server.use(
|
|
||||||
rest.get(
|
|
||||||
"/api/v2/templateversions/:templateVersionId",
|
|
||||||
async (req, res, ctx) => {
|
|
||||||
return res(ctx.status(200), ctx.json(MockTemplateVersion3));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await renderWorkspacePage();
|
|
||||||
await screen.findByTestId("error-unsupported-workspaces");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("restart the workspace with one time parameters when having the confirmation dialog", async () => {
|
it("restart the workspace with one time parameters when having the confirmation dialog", async () => {
|
||||||
window.localStorage.removeItem(`${MockUser.id}_ignoredWarnings`);
|
window.localStorage.removeItem(`${MockUser.id}_ignoredWarnings`);
|
||||||
jest.spyOn(api, "getWorkspaceParameters").mockResolvedValue({
|
jest.spyOn(api, "getWorkspaceParameters").mockResolvedValue({
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import { useMachine } from "@xstate/react";
|
|
||||||
import { Loader } from "components/Loader/Loader";
|
import { Loader } from "components/Loader/Loader";
|
||||||
import { FC } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { workspaceMachine } from "xServices/workspace/workspaceXService";
|
|
||||||
import { WorkspaceReadyPage } from "./WorkspaceReadyPage";
|
import { WorkspaceReadyPage } from "./WorkspaceReadyPage";
|
||||||
import { RequirePermission } from "components/RequirePermission/RequirePermission";
|
|
||||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||||
import { useOrganizationId } from "hooks";
|
import { useOrganizationId } from "hooks";
|
||||||
import { isAxiosError } from "axios";
|
|
||||||
import { Margins } from "components/Margins/Margins";
|
import { Margins } from "components/Margins/Margins";
|
||||||
import {
|
import { useInfiniteQuery, useQuery, useQueryClient } from "react-query";
|
||||||
workspaceQuota,
|
|
||||||
workspaceResolveAutostart,
|
|
||||||
} from "api/queries/workspaceQuota";
|
|
||||||
import { useInfiniteQuery, useQuery } from "react-query";
|
|
||||||
import { infiniteWorkspaceBuilds } from "api/queries/workspaceBuilds";
|
import { infiniteWorkspaceBuilds } from "api/queries/workspaceBuilds";
|
||||||
|
import { templateByName } from "api/queries/templates";
|
||||||
|
import { workspaceByOwnerAndName } from "api/queries/workspaces";
|
||||||
|
import { checkAuthorization } from "api/queries/authCheck";
|
||||||
|
import { WorkspacePermissions, workspaceChecks } from "./permissions";
|
||||||
|
import { watchWorkspace } from "api/api";
|
||||||
|
import { Workspace } from "api/typesGenerated";
|
||||||
|
import { useEffectEvent } from "hooks/hookPolyfills";
|
||||||
|
|
||||||
export const WorkspacePage: FC = () => {
|
export const WorkspacePage: FC = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const params = useParams() as {
|
const params = useParams() as {
|
||||||
username: string;
|
username: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
|
@ -24,31 +24,81 @@ export const WorkspacePage: FC = () => {
|
||||||
const workspaceName = params.workspace;
|
const workspaceName = params.workspace;
|
||||||
const username = params.username.replace("@", "");
|
const username = params.username.replace("@", "");
|
||||||
const orgId = useOrganizationId();
|
const orgId = useOrganizationId();
|
||||||
const [workspaceState, workspaceSend] = useMachine(workspaceMachine, {
|
|
||||||
context: {
|
// Workspace
|
||||||
orgId,
|
const workspaceQueryOptions = workspaceByOwnerAndName(
|
||||||
workspaceName,
|
username,
|
||||||
username,
|
workspaceName,
|
||||||
},
|
);
|
||||||
actions: {
|
const workspaceQuery = useQuery(workspaceQueryOptions);
|
||||||
refreshBuilds: async () => {
|
const workspace = workspaceQuery.data;
|
||||||
await buildsQuery.refetch();
|
|
||||||
},
|
// Template
|
||||||
},
|
const templateQuery = useQuery({
|
||||||
|
...templateByName(orgId, workspace?.template_name ?? ""),
|
||||||
|
enabled: workspace !== undefined,
|
||||||
});
|
});
|
||||||
const { workspace, error } = workspaceState.context;
|
const template = templateQuery.data;
|
||||||
const quotaQuery = useQuery(workspaceQuota(username));
|
|
||||||
const pageError = error ?? quotaQuery.error;
|
// Permissions
|
||||||
|
const checks =
|
||||||
|
workspace && template ? workspaceChecks(workspace, template) : {};
|
||||||
|
const permissionsQuery = useQuery({
|
||||||
|
...checkAuthorization({ checks }),
|
||||||
|
enabled: workspace !== undefined && template !== undefined,
|
||||||
|
});
|
||||||
|
const permissions = permissionsQuery.data as WorkspacePermissions | undefined;
|
||||||
|
|
||||||
|
// Builds
|
||||||
const buildsQuery = useInfiniteQuery({
|
const buildsQuery = useInfiniteQuery({
|
||||||
...infiniteWorkspaceBuilds(workspace?.id ?? ""),
|
...infiniteWorkspaceBuilds(workspace?.id ?? ""),
|
||||||
enabled: Boolean(workspace),
|
enabled: workspace !== undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const canAutostartResponse = useQuery(
|
// Watch workspace changes
|
||||||
workspaceResolveAutostart(workspace?.id ?? ""),
|
const updateWorkspaceData = useEffectEvent(
|
||||||
);
|
async (newWorkspaceData: Workspace) => {
|
||||||
|
queryClient.setQueryData(
|
||||||
|
workspaceQueryOptions.queryKey,
|
||||||
|
newWorkspaceData,
|
||||||
|
);
|
||||||
|
|
||||||
const canAutostart = !canAutostartResponse.data?.parameter_mismatch ?? false;
|
const hasNewBuild =
|
||||||
|
newWorkspaceData.latest_build.id !== workspace!.latest_build.id;
|
||||||
|
const lastBuildHasChanged =
|
||||||
|
newWorkspaceData.latest_build.status !== workspace!.latest_build.status;
|
||||||
|
|
||||||
|
if (hasNewBuild || lastBuildHasChanged) {
|
||||||
|
await buildsQuery.refetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const workspaceId = workspace?.id;
|
||||||
|
useEffect(() => {
|
||||||
|
if (!workspaceId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventSource = watchWorkspace(workspaceId);
|
||||||
|
|
||||||
|
eventSource.addEventListener("data", async (event) => {
|
||||||
|
const newWorkspaceData = JSON.parse(event.data) as Workspace;
|
||||||
|
await updateWorkspaceData(newWorkspaceData);
|
||||||
|
});
|
||||||
|
|
||||||
|
eventSource.addEventListener("error", (event) => {
|
||||||
|
console.error("Error on getting workspace changes.", event);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
eventSource.close();
|
||||||
|
};
|
||||||
|
}, [updateWorkspaceData, workspaceId]);
|
||||||
|
|
||||||
|
// Page statuses
|
||||||
|
const pageError =
|
||||||
|
workspaceQuery.error ?? templateQuery.error ?? permissionsQuery.error;
|
||||||
|
const isLoading = !workspace || !template || !permissions;
|
||||||
|
|
||||||
if (pageError) {
|
if (pageError) {
|
||||||
return (
|
return (
|
||||||
|
@ -58,30 +108,23 @@ export const WorkspacePage: FC = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!workspace || !workspaceState.matches("ready") || !quotaQuery.isSuccess) {
|
if (isLoading) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RequirePermission
|
<WorkspaceReadyPage
|
||||||
isFeatureVisible={
|
workspace={workspace}
|
||||||
!(isAxiosError(pageError) && pageError.response?.status === 404)
|
template={template}
|
||||||
}
|
permissions={permissions}
|
||||||
>
|
builds={buildsQuery.data?.pages.flat()}
|
||||||
<WorkspaceReadyPage
|
buildsError={buildsQuery.error}
|
||||||
workspaceState={workspaceState}
|
isLoadingMoreBuilds={buildsQuery.isFetchingNextPage}
|
||||||
quota={quotaQuery.data}
|
onLoadMoreBuilds={async () => {
|
||||||
workspaceSend={workspaceSend}
|
await buildsQuery.fetchNextPage();
|
||||||
builds={buildsQuery.data?.pages.flat()}
|
}}
|
||||||
buildsError={buildsQuery.error}
|
hasMoreBuilds={Boolean(buildsQuery.hasNextPage)}
|
||||||
isLoadingMoreBuilds={buildsQuery.isFetchingNextPage}
|
/>
|
||||||
onLoadMoreBuilds={async () => {
|
|
||||||
await buildsQuery.fetchNextPage();
|
|
||||||
}}
|
|
||||||
hasMoreBuilds={Boolean(buildsQuery.hasNextPage)}
|
|
||||||
canAutostart={canAutostart}
|
|
||||||
/>
|
|
||||||
</RequirePermission>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,18 +9,13 @@ import {
|
||||||
getMaxDeadlineChange,
|
getMaxDeadlineChange,
|
||||||
getMinDeadline,
|
getMinDeadline,
|
||||||
} from "utils/schedule";
|
} from "utils/schedule";
|
||||||
import { StateFrom } from "xstate";
|
|
||||||
import { Workspace, WorkspaceErrors } from "./Workspace";
|
import { Workspace, WorkspaceErrors } from "./Workspace";
|
||||||
import { pageTitle } from "utils/page";
|
import { pageTitle } from "utils/page";
|
||||||
import { getFaviconByStatus, hasJobError } from "utils/workspace";
|
import { hasJobError } from "utils/workspace";
|
||||||
import {
|
|
||||||
WorkspaceEvent,
|
|
||||||
workspaceMachine,
|
|
||||||
} from "xServices/workspace/workspaceXService";
|
|
||||||
import { UpdateBuildParametersDialog } from "./UpdateBuildParametersDialog";
|
import { UpdateBuildParametersDialog } from "./UpdateBuildParametersDialog";
|
||||||
import { ChangeVersionDialog } from "./ChangeVersionDialog";
|
import { ChangeVersionDialog } from "./ChangeVersionDialog";
|
||||||
import { useMutation, useQuery } from "react-query";
|
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||||
import { restartWorkspace } from "api/api";
|
import { MissingBuildParameters, restartWorkspace } from "api/api";
|
||||||
import {
|
import {
|
||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
ConfirmDialogProps,
|
ConfirmDialogProps,
|
||||||
|
@ -31,90 +26,76 @@ import { templateVersion, templateVersions } from "api/queries/templates";
|
||||||
import { Alert } from "components/Alert/Alert";
|
import { Alert } from "components/Alert/Alert";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
|
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
|
||||||
import { decreaseDeadline, increaseDeadline } from "api/queries/workspaces";
|
import {
|
||||||
|
activate,
|
||||||
|
changeVersion,
|
||||||
|
decreaseDeadline,
|
||||||
|
deleteWorkspace,
|
||||||
|
increaseDeadline,
|
||||||
|
updateWorkspace,
|
||||||
|
stopWorkspace,
|
||||||
|
startWorkspace,
|
||||||
|
cancelBuild,
|
||||||
|
} from "api/queries/workspaces";
|
||||||
import { getErrorMessage } from "api/errors";
|
import { getErrorMessage } from "api/errors";
|
||||||
import { displaySuccess, displayError } from "components/GlobalSnackbar/utils";
|
import { displaySuccess, displayError } from "components/GlobalSnackbar/utils";
|
||||||
|
import { deploymentConfig, deploymentSSHConfig } from "api/queries/deployment";
|
||||||
|
import { WorkspacePermissions } from "./permissions";
|
||||||
|
import { workspaceResolveAutostart } from "api/queries/workspaceQuota";
|
||||||
import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog";
|
import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
interface WorkspaceReadyPageProps {
|
interface WorkspaceReadyPageProps {
|
||||||
workspaceState: StateFrom<typeof workspaceMachine>;
|
template: TypesGen.Template;
|
||||||
workspaceSend: (event: WorkspaceEvent) => void;
|
workspace: TypesGen.Workspace;
|
||||||
quota?: TypesGen.WorkspaceQuota;
|
permissions: WorkspacePermissions;
|
||||||
builds: TypesGen.WorkspaceBuild[] | undefined;
|
builds: TypesGen.WorkspaceBuild[] | undefined;
|
||||||
buildsError: unknown;
|
buildsError: unknown;
|
||||||
onLoadMoreBuilds: () => void;
|
onLoadMoreBuilds: () => void;
|
||||||
isLoadingMoreBuilds: boolean;
|
isLoadingMoreBuilds: boolean;
|
||||||
hasMoreBuilds: boolean;
|
hasMoreBuilds: boolean;
|
||||||
canAutostart: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceReadyPage = ({
|
export const WorkspaceReadyPage = ({
|
||||||
workspaceState,
|
workspace,
|
||||||
workspaceSend,
|
template,
|
||||||
quota,
|
permissions,
|
||||||
builds,
|
builds,
|
||||||
buildsError,
|
buildsError,
|
||||||
onLoadMoreBuilds,
|
onLoadMoreBuilds,
|
||||||
isLoadingMoreBuilds,
|
isLoadingMoreBuilds,
|
||||||
hasMoreBuilds,
|
hasMoreBuilds,
|
||||||
canAutostart,
|
|
||||||
}: WorkspaceReadyPageProps): JSX.Element => {
|
}: WorkspaceReadyPageProps): JSX.Element => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const { buildInfo } = useDashboard();
|
const { buildInfo } = useDashboard();
|
||||||
const featureVisibility = useFeatureVisibility();
|
const featureVisibility = useFeatureVisibility();
|
||||||
const {
|
|
||||||
workspace,
|
|
||||||
template,
|
|
||||||
templateVersion: currentVersion,
|
|
||||||
deploymentValues,
|
|
||||||
buildError,
|
|
||||||
cancellationError,
|
|
||||||
sshPrefix,
|
|
||||||
permissions,
|
|
||||||
missedParameters,
|
|
||||||
} = workspaceState.context;
|
|
||||||
if (workspace === undefined) {
|
if (workspace === undefined) {
|
||||||
throw Error("Workspace is undefined");
|
throw Error("Workspace is undefined");
|
||||||
}
|
}
|
||||||
const deadline = getDeadline(workspace);
|
|
||||||
const canUpdateWorkspace = Boolean(permissions?.updateWorkspace);
|
|
||||||
const canUpdateTemplate = Boolean(permissions?.updateTemplate);
|
|
||||||
const canRetryDebugMode =
|
|
||||||
Boolean(permissions?.viewDeploymentValues) &&
|
|
||||||
Boolean(deploymentValues?.enable_terraform_debug_mode);
|
|
||||||
const favicon = getFaviconByStatus(workspace.latest_build);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [changeVersionDialogOpen, setChangeVersionDialogOpen] = useState(false);
|
|
||||||
const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false);
|
|
||||||
const [confirmingRestart, setConfirmingRestart] = useState<{
|
|
||||||
open: boolean;
|
|
||||||
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
|
||||||
}>({ open: false });
|
|
||||||
|
|
||||||
const { data: allVersions } = useQuery({
|
// Debug mode
|
||||||
...templateVersions(workspace.template_id),
|
const { data: deploymentValues } = useQuery({
|
||||||
enabled: changeVersionDialogOpen,
|
...deploymentConfig(),
|
||||||
|
enabled: permissions?.viewDeploymentValues,
|
||||||
});
|
});
|
||||||
const { data: latestVersion } = useQuery({
|
const canRetryDebugMode = Boolean(
|
||||||
...templateVersion(workspace.template_active_version_id),
|
deploymentValues?.config.enable_terraform_debug_mode,
|
||||||
enabled: workspace.outdated,
|
);
|
||||||
});
|
|
||||||
const [faviconTheme, setFaviconTheme] = useState<"light" | "dark">("dark");
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window === "undefined" || !window.matchMedia) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDark = window.matchMedia("(prefers-color-scheme: dark)");
|
// Build logs
|
||||||
// We want the favicon the opposite of the theme.
|
|
||||||
setFaviconTheme(isDark.matches ? "light" : "dark");
|
|
||||||
}, []);
|
|
||||||
const buildLogs = useWorkspaceBuildLogs(workspace.latest_build.id);
|
const buildLogs = useWorkspaceBuildLogs(workspace.latest_build.id);
|
||||||
const shouldDisplayBuildLogs =
|
const shouldDisplayBuildLogs =
|
||||||
hasJobError(workspace) ||
|
hasJobError(workspace) ||
|
||||||
["canceling", "deleting", "pending", "starting", "stopping"].includes(
|
["canceling", "deleting", "pending", "starting", "stopping"].includes(
|
||||||
workspace.latest_build.status,
|
workspace.latest_build.status,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Restart
|
||||||
|
const [confirmingRestart, setConfirmingRestart] = useState<{
|
||||||
|
open: boolean;
|
||||||
|
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
||||||
|
}>({ open: false });
|
||||||
const {
|
const {
|
||||||
mutate: mutateRestartWorkspace,
|
mutate: mutateRestartWorkspace,
|
||||||
error: restartBuildError,
|
error: restartBuildError,
|
||||||
|
@ -123,6 +104,8 @@ export const WorkspaceReadyPage = ({
|
||||||
mutationFn: restartWorkspace,
|
mutationFn: restartWorkspace,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Schedule controls
|
||||||
|
const deadline = getDeadline(workspace);
|
||||||
const onDeadlineChangeSuccess = () => {
|
const onDeadlineChangeSuccess = () => {
|
||||||
displaySuccess("Updated workspace shutdown time.");
|
displaySuccess("Updated workspace shutdown time.");
|
||||||
};
|
};
|
||||||
|
@ -142,6 +125,77 @@ export const WorkspaceReadyPage = ({
|
||||||
onError: onDeadlineChangeFails,
|
onError: onDeadlineChangeFails,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Auto start
|
||||||
|
const canAutostartResponse = useQuery(
|
||||||
|
workspaceResolveAutostart(workspace.id),
|
||||||
|
);
|
||||||
|
const canAutostart = !canAutostartResponse.data?.parameter_mismatch ?? false;
|
||||||
|
|
||||||
|
// SSH Prefix
|
||||||
|
const sshPrefixQuery = useQuery(deploymentSSHConfig());
|
||||||
|
|
||||||
|
// Favicon
|
||||||
|
const favicon = getFaviconByStatus(workspace.latest_build);
|
||||||
|
const [faviconTheme, setFaviconTheme] = useState<"light" | "dark">("dark");
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === "undefined" || !window.matchMedia) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDark = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
// We want the favicon the opposite of the theme.
|
||||||
|
setFaviconTheme(isDark.matches ? "light" : "dark");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Change version
|
||||||
|
const canChangeVersions = Boolean(permissions?.updateTemplate);
|
||||||
|
const [changeVersionDialogOpen, setChangeVersionDialogOpen] = useState(false);
|
||||||
|
const changeVersionMutation = useMutation(
|
||||||
|
changeVersion(workspace, queryClient),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Versions
|
||||||
|
const { data: allVersions } = useQuery({
|
||||||
|
...templateVersions(workspace.template_id),
|
||||||
|
enabled: changeVersionDialogOpen,
|
||||||
|
});
|
||||||
|
const { data: latestVersion } = useQuery({
|
||||||
|
...templateVersion(workspace.template_active_version_id),
|
||||||
|
enabled: workspace.outdated,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update workspace
|
||||||
|
const canUpdateWorkspace = Boolean(permissions?.updateWorkspace);
|
||||||
|
const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false);
|
||||||
|
const updateWorkspaceMutation = useMutation(
|
||||||
|
updateWorkspace(workspace, queryClient),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete workspace
|
||||||
|
const canDeleteWorkspace = Boolean(permissions?.updateWorkspace);
|
||||||
|
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);
|
||||||
|
const deleteWorkspaceMutation = useMutation(
|
||||||
|
deleteWorkspace(workspace, queryClient),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Activate workspace
|
||||||
|
const activateWorkspaceMutation = useMutation(
|
||||||
|
activate(workspace, queryClient),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Stop workspace
|
||||||
|
const stopWorkspaceMutation = useMutation(
|
||||||
|
stopWorkspace(workspace, queryClient),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start workspace
|
||||||
|
const startWorkspaceMutation = useMutation(
|
||||||
|
startWorkspace(workspace, queryClient),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cancel build
|
||||||
|
const cancelBuildMutation = useMutation(cancelBuild(workspace, queryClient));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
@ -168,27 +222,50 @@ export const WorkspaceReadyPage = ({
|
||||||
deadline,
|
deadline,
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
isUpdating={workspaceState.matches("ready.build.requestingUpdate")}
|
isUpdating={updateWorkspaceMutation.isLoading}
|
||||||
isRestarting={isRestarting}
|
isRestarting={isRestarting}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
handleStart={(buildParameters) =>
|
handleStart={(buildParameters) => {
|
||||||
workspaceSend({ type: "START", buildParameters })
|
startWorkspaceMutation.mutate({ buildParameters });
|
||||||
}
|
}}
|
||||||
handleStop={() => workspaceSend({ type: "STOP" })}
|
handleStop={() => {
|
||||||
handleDelete={() => workspaceSend({ type: "ASK_DELETE" })}
|
stopWorkspaceMutation.mutate({});
|
||||||
|
}}
|
||||||
|
handleDelete={() => {
|
||||||
|
setIsConfirmingDelete(true);
|
||||||
|
}}
|
||||||
handleRestart={(buildParameters) => {
|
handleRestart={(buildParameters) => {
|
||||||
setConfirmingRestart({ open: true, buildParameters });
|
setConfirmingRestart({ open: true, buildParameters });
|
||||||
}}
|
}}
|
||||||
handleUpdate={() => {
|
handleUpdate={() => {
|
||||||
setIsConfirmingUpdate(true);
|
setIsConfirmingUpdate(true);
|
||||||
}}
|
}}
|
||||||
handleCancel={() => workspaceSend({ type: "CANCEL" })}
|
handleCancel={cancelBuildMutation.mutate}
|
||||||
handleSettings={() => navigate("settings")}
|
handleSettings={() => navigate("settings")}
|
||||||
handleBuildRetry={() => workspaceSend({ type: "RETRY_BUILD" })}
|
handleBuildRetry={() => {
|
||||||
|
switch (workspace.latest_build.transition) {
|
||||||
|
case "start":
|
||||||
|
startWorkspaceMutation.mutate({ logLevel: "debug" });
|
||||||
|
break;
|
||||||
|
case "stop":
|
||||||
|
stopWorkspaceMutation.mutate({ logLevel: "debug" });
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
deleteWorkspaceMutation.mutate({ logLevel: "debug" });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}}
|
||||||
handleChangeVersion={() => {
|
handleChangeVersion={() => {
|
||||||
setChangeVersionDialogOpen(true);
|
setChangeVersionDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
handleDormantActivate={() => workspaceSend({ type: "ACTIVATE" })}
|
handleDormantActivate={async () => {
|
||||||
|
try {
|
||||||
|
await activateWorkspaceMutation.mutateAsync();
|
||||||
|
} catch (e) {
|
||||||
|
const message = getErrorMessage(e, "Error activate workspace.");
|
||||||
|
displayError(message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
resources={workspace.latest_build.resources}
|
resources={workspace.latest_build.resources}
|
||||||
builds={builds}
|
builds={builds}
|
||||||
onLoadMoreBuilds={onLoadMoreBuilds}
|
onLoadMoreBuilds={onLoadMoreBuilds}
|
||||||
|
@ -197,19 +274,22 @@ export const WorkspaceReadyPage = ({
|
||||||
canUpdateWorkspace={canUpdateWorkspace}
|
canUpdateWorkspace={canUpdateWorkspace}
|
||||||
updateMessage={latestVersion?.message}
|
updateMessage={latestVersion?.message}
|
||||||
canRetryDebugMode={canRetryDebugMode}
|
canRetryDebugMode={canRetryDebugMode}
|
||||||
canChangeVersions={canUpdateTemplate}
|
canChangeVersions={canChangeVersions}
|
||||||
hideSSHButton={featureVisibility["browser_only"]}
|
hideSSHButton={featureVisibility["browser_only"]}
|
||||||
hideVSCodeDesktopButton={featureVisibility["browser_only"]}
|
hideVSCodeDesktopButton={featureVisibility["browser_only"]}
|
||||||
workspaceErrors={{
|
workspaceErrors={{
|
||||||
[WorkspaceErrors.GET_BUILDS_ERROR]: buildsError,
|
[WorkspaceErrors.GET_BUILDS_ERROR]: buildsError,
|
||||||
[WorkspaceErrors.BUILD_ERROR]: buildError || restartBuildError,
|
[WorkspaceErrors.BUILD_ERROR]:
|
||||||
[WorkspaceErrors.CANCELLATION_ERROR]: cancellationError,
|
restartBuildError ??
|
||||||
|
startWorkspaceMutation.error ??
|
||||||
|
stopWorkspaceMutation.error ??
|
||||||
|
deleteWorkspaceMutation.error ??
|
||||||
|
updateWorkspaceMutation.error,
|
||||||
|
[WorkspaceErrors.CANCELLATION_ERROR]: cancelBuildMutation.error,
|
||||||
}}
|
}}
|
||||||
buildInfo={buildInfo}
|
buildInfo={buildInfo}
|
||||||
sshPrefix={sshPrefix}
|
sshPrefix={sshPrefixQuery.data?.hostname_prefix}
|
||||||
template={template}
|
template={template}
|
||||||
quotaBudget={quota?.budget}
|
|
||||||
templateWarnings={currentVersion?.warnings}
|
|
||||||
buildLogs={
|
buildLogs={
|
||||||
shouldDisplayBuildLogs && (
|
shouldDisplayBuildLogs && (
|
||||||
<WorkspaceBuildLogsSection logs={buildLogs} />
|
<WorkspaceBuildLogsSection logs={buildLogs} />
|
||||||
|
@ -219,24 +299,50 @@ export const WorkspaceReadyPage = ({
|
||||||
/>
|
/>
|
||||||
<WorkspaceDeleteDialog
|
<WorkspaceDeleteDialog
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
canUpdateTemplate={canUpdateTemplate}
|
canUpdateTemplate={canDeleteWorkspace}
|
||||||
isOpen={workspaceState.matches({ ready: { build: "askingDelete" } })}
|
isOpen={isConfirmingDelete}
|
||||||
onCancel={() => workspaceSend({ type: "CANCEL_DELETE" })}
|
onCancel={() => {
|
||||||
|
setIsConfirmingDelete(false);
|
||||||
|
}}
|
||||||
onConfirm={(orphan) => {
|
onConfirm={(orphan) => {
|
||||||
workspaceSend({ type: "DELETE", orphan });
|
deleteWorkspaceMutation.mutate({ orphan });
|
||||||
|
setIsConfirmingDelete(false);
|
||||||
}}
|
}}
|
||||||
workspaceBuildDateStr={dayjs(workspace.created_at).fromNow()}
|
workspaceBuildDateStr={dayjs(workspace.created_at).fromNow()}
|
||||||
/>
|
/>
|
||||||
<UpdateBuildParametersDialog
|
<UpdateBuildParametersDialog
|
||||||
missedParameters={missedParameters ?? []}
|
missedParameters={
|
||||||
open={workspaceState.matches(
|
changeVersionMutation.error instanceof MissingBuildParameters
|
||||||
"ready.build.askingForMissedBuildParameters",
|
? changeVersionMutation.error.parameters
|
||||||
)}
|
: []
|
||||||
|
}
|
||||||
|
open={changeVersionMutation.error instanceof MissingBuildParameters}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
workspaceSend({ type: "CANCEL" });
|
changeVersionMutation.reset();
|
||||||
}}
|
}}
|
||||||
onUpdate={(buildParameters) => {
|
onUpdate={(buildParameters) => {
|
||||||
workspaceSend({ type: "UPDATE", buildParameters });
|
if (changeVersionMutation.error instanceof MissingBuildParameters) {
|
||||||
|
changeVersionMutation.mutate({
|
||||||
|
versionId: changeVersionMutation.error.versionId,
|
||||||
|
buildParameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<UpdateBuildParametersDialog
|
||||||
|
missedParameters={
|
||||||
|
updateWorkspaceMutation.error instanceof MissingBuildParameters
|
||||||
|
? updateWorkspaceMutation.error.parameters
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
open={updateWorkspaceMutation.error instanceof MissingBuildParameters}
|
||||||
|
onClose={() => {
|
||||||
|
updateWorkspaceMutation.reset();
|
||||||
|
}}
|
||||||
|
onUpdate={(buildParameters) => {
|
||||||
|
if (updateWorkspaceMutation.error instanceof MissingBuildParameters) {
|
||||||
|
updateWorkspaceMutation.mutate(buildParameters);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ChangeVersionDialog
|
<ChangeVersionDialog
|
||||||
|
@ -251,16 +357,13 @@ export const WorkspaceReadyPage = ({
|
||||||
}}
|
}}
|
||||||
onConfirm={(templateVersion) => {
|
onConfirm={(templateVersion) => {
|
||||||
setChangeVersionDialogOpen(false);
|
setChangeVersionDialogOpen(false);
|
||||||
workspaceSend({
|
changeVersionMutation.mutate({ versionId: templateVersion.id });
|
||||||
type: "CHANGE_VERSION",
|
|
||||||
templateVersionId: templateVersion.id,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<WarningDialog
|
<WarningDialog
|
||||||
open={isConfirmingUpdate}
|
open={isConfirmingUpdate}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
workspaceSend({ type: "UPDATE" });
|
updateWorkspaceMutation.mutate(undefined);
|
||||||
setIsConfirmingUpdate(false);
|
setIsConfirmingUpdate(false);
|
||||||
}}
|
}}
|
||||||
onClose={() => setIsConfirmingUpdate(false)}
|
onClose={() => setIsConfirmingUpdate(false)}
|
||||||
|
@ -310,3 +413,38 @@ const WarningDialog: FC<
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
return <ConfirmDialog type="info" hideCancel={false} {...props} />;
|
return <ConfirmDialog type="info" hideCancel={false} {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// You can see the favicon designs here: https://www.figma.com/file/YIGBkXUcnRGz2ZKNmLaJQf/Coder-v2-Design?node-id=560%3A620
|
||||||
|
type FaviconType =
|
||||||
|
| "favicon"
|
||||||
|
| "favicon-success"
|
||||||
|
| "favicon-error"
|
||||||
|
| "favicon-warning"
|
||||||
|
| "favicon-running";
|
||||||
|
|
||||||
|
const getFaviconByStatus = (build: TypesGen.WorkspaceBuild): FaviconType => {
|
||||||
|
switch (build.status) {
|
||||||
|
case undefined:
|
||||||
|
return "favicon";
|
||||||
|
case "running":
|
||||||
|
return "favicon-success";
|
||||||
|
case "starting":
|
||||||
|
return "favicon-running";
|
||||||
|
case "stopping":
|
||||||
|
return "favicon-running";
|
||||||
|
case "stopped":
|
||||||
|
return "favicon";
|
||||||
|
case "deleting":
|
||||||
|
return "favicon";
|
||||||
|
case "deleted":
|
||||||
|
return "favicon";
|
||||||
|
case "canceling":
|
||||||
|
return "favicon-warning";
|
||||||
|
case "canceled":
|
||||||
|
return "favicon";
|
||||||
|
case "failed":
|
||||||
|
return "favicon-error";
|
||||||
|
case "pending":
|
||||||
|
return "favicon";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -24,6 +24,8 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
usePopover,
|
usePopover,
|
||||||
} from "components/Popover/Popover";
|
} from "components/Popover/Popover";
|
||||||
|
import { workspaceQuota } from "api/queries/workspaceQuota";
|
||||||
|
import { useQuery } from "react-query";
|
||||||
|
|
||||||
const Language = {
|
const Language = {
|
||||||
workspaceDetails: "Workspace Details",
|
workspaceDetails: "Workspace Details",
|
||||||
|
@ -37,7 +39,6 @@ export interface WorkspaceStatsProps {
|
||||||
maxDeadlineIncrease: number;
|
maxDeadlineIncrease: number;
|
||||||
maxDeadlineDecrease: number;
|
maxDeadlineDecrease: number;
|
||||||
canUpdateWorkspace: boolean;
|
canUpdateWorkspace: boolean;
|
||||||
quotaBudget?: number;
|
|
||||||
onDeadlinePlus: (hours: number) => void;
|
onDeadlinePlus: (hours: number) => void;
|
||||||
onDeadlineMinus: (hours: number) => void;
|
onDeadlineMinus: (hours: number) => void;
|
||||||
handleUpdate: () => void;
|
handleUpdate: () => void;
|
||||||
|
@ -45,7 +46,6 @@ export interface WorkspaceStatsProps {
|
||||||
|
|
||||||
export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
|
export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
|
||||||
workspace,
|
workspace,
|
||||||
quotaBudget,
|
|
||||||
maxDeadlineDecrease,
|
maxDeadlineDecrease,
|
||||||
maxDeadlineIncrease,
|
maxDeadlineIncrease,
|
||||||
canUpdateWorkspace,
|
canUpdateWorkspace,
|
||||||
|
@ -56,6 +56,8 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
|
||||||
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace);
|
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace);
|
||||||
const deadlinePlusEnabled = maxDeadlineIncrease >= 1;
|
const deadlinePlusEnabled = maxDeadlineIncrease >= 1;
|
||||||
const deadlineMinusEnabled = maxDeadlineDecrease >= 1;
|
const deadlineMinusEnabled = maxDeadlineDecrease >= 1;
|
||||||
|
const quotaQuery = useQuery(workspaceQuota(workspace.owner_name));
|
||||||
|
const quotaBudget = quotaQuery.data?.budget;
|
||||||
|
|
||||||
const paperStyles = css`
|
const paperStyles = css`
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Workspace, Template } from "api/typesGenerated";
|
||||||
|
|
||||||
|
export const workspaceChecks = (workspace: Workspace, template: Template) =>
|
||||||
|
({
|
||||||
|
readWorkspace: {
|
||||||
|
object: {
|
||||||
|
resource_type: "workspace",
|
||||||
|
resource_id: workspace.id,
|
||||||
|
owner_id: workspace.owner_id,
|
||||||
|
},
|
||||||
|
action: "read",
|
||||||
|
},
|
||||||
|
updateWorkspace: {
|
||||||
|
object: {
|
||||||
|
resource_type: "workspace",
|
||||||
|
resource_id: workspace.id,
|
||||||
|
owner_id: workspace.owner_id,
|
||||||
|
},
|
||||||
|
action: "update",
|
||||||
|
},
|
||||||
|
updateTemplate: {
|
||||||
|
object: {
|
||||||
|
resource_type: "template",
|
||||||
|
resource_id: template.id,
|
||||||
|
},
|
||||||
|
action: "update",
|
||||||
|
},
|
||||||
|
viewDeploymentValues: {
|
||||||
|
object: {
|
||||||
|
resource_type: "deployment_config",
|
||||||
|
},
|
||||||
|
action: "read",
|
||||||
|
},
|
||||||
|
}) as const;
|
||||||
|
|
||||||
|
export type WorkspacePermissions = Record<
|
||||||
|
keyof ReturnType<typeof workspaceChecks>,
|
||||||
|
boolean
|
||||||
|
>;
|
|
@ -400,20 +400,6 @@ You can add instructions here
|
||||||
archived: false,
|
archived: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MockTemplateVersion3: TypesGen.TemplateVersion = {
|
|
||||||
id: "test-template-version-3",
|
|
||||||
created_at: "2022-05-17T17:39:01.382927298Z",
|
|
||||||
updated_at: "2022-05-17T17:39:01.382927298Z",
|
|
||||||
template_id: "test-template",
|
|
||||||
job: MockProvisionerJob,
|
|
||||||
name: "test-version-3",
|
|
||||||
message: "first version",
|
|
||||||
readme: "README",
|
|
||||||
created_by: MockUser,
|
|
||||||
warnings: ["UNSUPPORTED_WORKSPACES"],
|
|
||||||
archived: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MockTemplate: TypesGen.Template = {
|
export const MockTemplate: TypesGen.Template = {
|
||||||
id: "test-template",
|
id: "test-template",
|
||||||
created_at: "2022-05-17T17:39:01.382927298Z",
|
created_at: "2022-05-17T17:39:01.382927298Z",
|
||||||
|
|
|
@ -147,44 +147,6 @@ export const defaultWorkspaceExtension = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// You can see the favicon designs here: https://www.figma.com/file/YIGBkXUcnRGz2ZKNmLaJQf/Coder-v2-Design?node-id=560%3A620
|
|
||||||
|
|
||||||
type FaviconType =
|
|
||||||
| "favicon"
|
|
||||||
| "favicon-success"
|
|
||||||
| "favicon-error"
|
|
||||||
| "favicon-warning"
|
|
||||||
| "favicon-running";
|
|
||||||
|
|
||||||
export const getFaviconByStatus = (
|
|
||||||
build: TypesGen.WorkspaceBuild,
|
|
||||||
): FaviconType => {
|
|
||||||
switch (build.status) {
|
|
||||||
case undefined:
|
|
||||||
return "favicon";
|
|
||||||
case "running":
|
|
||||||
return "favicon-success";
|
|
||||||
case "starting":
|
|
||||||
return "favicon-running";
|
|
||||||
case "stopping":
|
|
||||||
return "favicon-running";
|
|
||||||
case "stopped":
|
|
||||||
return "favicon";
|
|
||||||
case "deleting":
|
|
||||||
return "favicon";
|
|
||||||
case "deleted":
|
|
||||||
return "favicon";
|
|
||||||
case "canceling":
|
|
||||||
return "favicon-warning";
|
|
||||||
case "canceled":
|
|
||||||
return "favicon";
|
|
||||||
case "failed":
|
|
||||||
return "favicon-error";
|
|
||||||
case "pending":
|
|
||||||
return "favicon";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDisplayWorkspaceTemplateName = (
|
export const getDisplayWorkspaceTemplateName = (
|
||||||
workspace: TypesGen.Workspace,
|
workspace: TypesGen.Workspace,
|
||||||
): string => {
|
): string => {
|
||||||
|
|
|
@ -1,714 +0,0 @@
|
||||||
import { getErrorMessage } from "api/errors";
|
|
||||||
import { assign, createMachine } from "xstate";
|
|
||||||
import * as API from "api/api";
|
|
||||||
import * as TypesGen from "api/typesGenerated";
|
|
||||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
|
||||||
|
|
||||||
type Permissions = Record<keyof ReturnType<typeof permissionsToCheck>, boolean>;
|
|
||||||
|
|
||||||
export interface WorkspaceContext {
|
|
||||||
// Initial data
|
|
||||||
orgId: string;
|
|
||||||
username: string;
|
|
||||||
workspaceName: string;
|
|
||||||
|
|
||||||
error?: unknown;
|
|
||||||
// our server side events instance
|
|
||||||
eventSource?: EventSource;
|
|
||||||
workspace?: TypesGen.Workspace;
|
|
||||||
template?: TypesGen.Template;
|
|
||||||
permissions?: Permissions;
|
|
||||||
templateVersion?: TypesGen.TemplateVersion;
|
|
||||||
deploymentValues?: TypesGen.DeploymentValues;
|
|
||||||
build?: TypesGen.WorkspaceBuild;
|
|
||||||
// Builds
|
|
||||||
builds?: TypesGen.WorkspaceBuild[];
|
|
||||||
getBuildsError?: unknown;
|
|
||||||
missedParameters?: TypesGen.TemplateVersionParameter[];
|
|
||||||
// error creating a new WorkspaceBuild
|
|
||||||
buildError?: unknown;
|
|
||||||
cancellationMessage?: TypesGen.Response;
|
|
||||||
cancellationError?: unknown;
|
|
||||||
// debug
|
|
||||||
createBuildLogLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"];
|
|
||||||
// SSH Config
|
|
||||||
sshPrefix?: string;
|
|
||||||
// Change version
|
|
||||||
templateVersionIdToChange?: TypesGen.TemplateVersion["id"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WorkspaceEvent =
|
|
||||||
| { type: "REFRESH_WORKSPACE"; data: TypesGen.ServerSentEvent["data"] }
|
|
||||||
| { type: "START"; buildParameters?: TypesGen.WorkspaceBuildParameter[] }
|
|
||||||
| { type: "STOP" }
|
|
||||||
| { type: "ASK_DELETE" }
|
|
||||||
| {
|
|
||||||
type: "DELETE";
|
|
||||||
orphan: TypesGen.CreateWorkspaceBuildRequest["orphan"];
|
|
||||||
}
|
|
||||||
| { type: "CANCEL_DELETE" }
|
|
||||||
| { type: "UPDATE"; buildParameters?: TypesGen.WorkspaceBuildParameter[] }
|
|
||||||
| {
|
|
||||||
type: "CHANGE_VERSION";
|
|
||||||
templateVersionId: TypesGen.TemplateVersion["id"];
|
|
||||||
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
|
||||||
}
|
|
||||||
| { type: "CANCEL" }
|
|
||||||
| {
|
|
||||||
type: "REFRESH_TIMELINE";
|
|
||||||
}
|
|
||||||
| { type: "EVENT_SOURCE_ERROR"; error: unknown }
|
|
||||||
| { type: "INCREASE_DEADLINE"; hours: number }
|
|
||||||
| { type: "DECREASE_DEADLINE"; hours: number }
|
|
||||||
| {
|
|
||||||
type: "RETRY_BUILD";
|
|
||||||
orphan?: TypesGen.CreateWorkspaceBuildRequest["orphan"];
|
|
||||||
}
|
|
||||||
| { type: "ACTIVATE" };
|
|
||||||
|
|
||||||
export const checks = {
|
|
||||||
readWorkspace: "readWorkspace",
|
|
||||||
updateWorkspace: "updateWorkspace",
|
|
||||||
updateTemplate: "updateTemplate",
|
|
||||||
viewDeploymentValues: "viewDeploymentValues",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const permissionsToCheck = (
|
|
||||||
workspace: TypesGen.Workspace,
|
|
||||||
template: TypesGen.Template,
|
|
||||||
) =>
|
|
||||||
({
|
|
||||||
[checks.readWorkspace]: {
|
|
||||||
object: {
|
|
||||||
resource_type: "workspace",
|
|
||||||
resource_id: workspace.id,
|
|
||||||
owner_id: workspace.owner_id,
|
|
||||||
},
|
|
||||||
action: "read",
|
|
||||||
},
|
|
||||||
[checks.updateWorkspace]: {
|
|
||||||
object: {
|
|
||||||
resource_type: "workspace",
|
|
||||||
resource_id: workspace.id,
|
|
||||||
owner_id: workspace.owner_id,
|
|
||||||
},
|
|
||||||
action: "update",
|
|
||||||
},
|
|
||||||
[checks.updateTemplate]: {
|
|
||||||
object: {
|
|
||||||
resource_type: "template",
|
|
||||||
resource_id: template.id,
|
|
||||||
},
|
|
||||||
action: "update",
|
|
||||||
},
|
|
||||||
[checks.viewDeploymentValues]: {
|
|
||||||
object: {
|
|
||||||
resource_type: "deployment_config",
|
|
||||||
},
|
|
||||||
action: "read",
|
|
||||||
},
|
|
||||||
}) as const;
|
|
||||||
|
|
||||||
export const workspaceMachine = createMachine(
|
|
||||||
{
|
|
||||||
id: "workspaceState",
|
|
||||||
predictableActionArguments: true,
|
|
||||||
tsTypes: {} as import("./workspaceXService.typegen").Typegen0,
|
|
||||||
schema: {
|
|
||||||
context: {} as WorkspaceContext,
|
|
||||||
events: {} as WorkspaceEvent,
|
|
||||||
services: {} as {
|
|
||||||
loadInitialWorkspaceData: {
|
|
||||||
data: Awaited<ReturnType<typeof loadInitialWorkspaceData>>;
|
|
||||||
};
|
|
||||||
updateWorkspace: {
|
|
||||||
data: TypesGen.WorkspaceBuild;
|
|
||||||
};
|
|
||||||
changeWorkspaceVersion: {
|
|
||||||
data: TypesGen.WorkspaceBuild;
|
|
||||||
};
|
|
||||||
startWorkspace: {
|
|
||||||
data: TypesGen.WorkspaceBuild;
|
|
||||||
};
|
|
||||||
stopWorkspace: {
|
|
||||||
data: TypesGen.WorkspaceBuild;
|
|
||||||
};
|
|
||||||
deleteWorkspace: {
|
|
||||||
data: TypesGen.WorkspaceBuild;
|
|
||||||
};
|
|
||||||
cancelWorkspace: {
|
|
||||||
data: TypesGen.Response;
|
|
||||||
};
|
|
||||||
activateWorkspace: {
|
|
||||||
data: TypesGen.Response;
|
|
||||||
};
|
|
||||||
listening: {
|
|
||||||
data: TypesGen.ServerSentEvent;
|
|
||||||
};
|
|
||||||
getBuilds: {
|
|
||||||
data: TypesGen.WorkspaceBuild[];
|
|
||||||
};
|
|
||||||
getSSHPrefix: {
|
|
||||||
data: TypesGen.SSHConfigResponse;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
initial: "loadInitialData",
|
|
||||||
states: {
|
|
||||||
loadInitialData: {
|
|
||||||
entry: ["clearContext"],
|
|
||||||
invoke: {
|
|
||||||
src: "loadInitialWorkspaceData",
|
|
||||||
id: "loadInitialWorkspaceData",
|
|
||||||
onDone: [{ target: "ready", actions: ["assignInitialData"] }],
|
|
||||||
onError: [
|
|
||||||
{
|
|
||||||
actions: "assignError",
|
|
||||||
target: "error",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ready: {
|
|
||||||
type: "parallel",
|
|
||||||
on: {
|
|
||||||
REFRESH_TIMELINE: {
|
|
||||||
actions: ["refreshBuilds"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
states: {
|
|
||||||
listening: {
|
|
||||||
initial: "gettingEvents",
|
|
||||||
states: {
|
|
||||||
gettingEvents: {
|
|
||||||
entry: ["initializeEventSource"],
|
|
||||||
exit: "closeEventSource",
|
|
||||||
invoke: {
|
|
||||||
src: "listening",
|
|
||||||
id: "listening",
|
|
||||||
},
|
|
||||||
on: {
|
|
||||||
REFRESH_WORKSPACE: {
|
|
||||||
actions: ["refreshWorkspace"],
|
|
||||||
},
|
|
||||||
EVENT_SOURCE_ERROR: {
|
|
||||||
target: "error",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
entry: "logWatchWorkspaceWarning",
|
|
||||||
after: {
|
|
||||||
"2000": {
|
|
||||||
target: "gettingEvents",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
initial: "idle",
|
|
||||||
states: {
|
|
||||||
idle: {
|
|
||||||
on: {
|
|
||||||
START: "requestingStart",
|
|
||||||
STOP: "requestingStop",
|
|
||||||
ASK_DELETE: "askingDelete",
|
|
||||||
UPDATE: "requestingUpdate",
|
|
||||||
CHANGE_VERSION: {
|
|
||||||
target: "requestingChangeVersion",
|
|
||||||
actions: ["assignTemplateVersionIdToChange"],
|
|
||||||
},
|
|
||||||
CANCEL: "requestingCancel",
|
|
||||||
RETRY_BUILD: [
|
|
||||||
{
|
|
||||||
target: "requestingStart",
|
|
||||||
cond: "lastBuildWasStarting",
|
|
||||||
actions: ["enableDebugMode"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: "requestingStop",
|
|
||||||
cond: "lastBuildWasStopping",
|
|
||||||
actions: ["enableDebugMode"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: "requestingDelete",
|
|
||||||
cond: "lastBuildWasDeleting",
|
|
||||||
actions: ["enableDebugMode"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
ACTIVATE: "requestingActivate",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
askingDelete: {
|
|
||||||
on: {
|
|
||||||
DELETE: {
|
|
||||||
target: "requestingDelete",
|
|
||||||
},
|
|
||||||
CANCEL_DELETE: {
|
|
||||||
target: "idle",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestingUpdate: {
|
|
||||||
entry: ["clearBuildError"],
|
|
||||||
invoke: {
|
|
||||||
src: "updateWorkspace",
|
|
||||||
onDone: {
|
|
||||||
target: "idle",
|
|
||||||
actions: ["assignBuild"],
|
|
||||||
},
|
|
||||||
onError: [
|
|
||||||
{
|
|
||||||
target: "askingForMissedBuildParameters",
|
|
||||||
cond: "isMissingBuildParameterError",
|
|
||||||
actions: ["assignMissedParameters"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: "idle",
|
|
||||||
actions: ["assignBuildError"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestingChangeVersion: {
|
|
||||||
entry: ["clearBuildError"],
|
|
||||||
invoke: {
|
|
||||||
src: "changeWorkspaceVersion",
|
|
||||||
onDone: {
|
|
||||||
target: "idle",
|
|
||||||
actions: ["assignBuild", "clearTemplateVersionIdToChange"],
|
|
||||||
},
|
|
||||||
onError: [
|
|
||||||
{
|
|
||||||
target: "askingForMissedBuildParameters",
|
|
||||||
cond: "isMissingBuildParameterError",
|
|
||||||
actions: ["assignMissedParameters"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: "idle",
|
|
||||||
actions: ["assignBuildError"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
askingForMissedBuildParameters: {
|
|
||||||
on: {
|
|
||||||
CANCEL: "idle",
|
|
||||||
UPDATE: [
|
|
||||||
{
|
|
||||||
target: "requestingChangeVersion",
|
|
||||||
cond: "isChangingVersion",
|
|
||||||
},
|
|
||||||
{ target: "requestingUpdate" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestingStart: {
|
|
||||||
entry: ["clearBuildError"],
|
|
||||||
invoke: {
|
|
||||||
src: "startWorkspace",
|
|
||||||
id: "startWorkspace",
|
|
||||||
onDone: [
|
|
||||||
{
|
|
||||||
actions: ["assignBuild", "disableDebugMode"],
|
|
||||||
target: "idle",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
onError: [
|
|
||||||
{
|
|
||||||
actions: "assignBuildError",
|
|
||||||
target: "idle",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestingStop: {
|
|
||||||
entry: ["clearBuildError"],
|
|
||||||
invoke: {
|
|
||||||
src: "stopWorkspace",
|
|
||||||
id: "stopWorkspace",
|
|
||||||
onDone: [
|
|
||||||
{
|
|
||||||
actions: ["assignBuild", "disableDebugMode"],
|
|
||||||
target: "idle",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
onError: [
|
|
||||||
{
|
|
||||||
actions: "assignBuildError",
|
|
||||||
target: "idle",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestingDelete: {
|
|
||||||
entry: ["clearBuildError"],
|
|
||||||
invoke: {
|
|
||||||
src: "deleteWorkspace",
|
|
||||||
id: "deleteWorkspace",
|
|
||||||
onDone: [
|
|
||||||
{
|
|
||||||
actions: ["assignBuild", "disableDebugMode"],
|
|
||||||
target: "idle",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
onError: [
|
|
||||||
{
|
|
||||||
actions: "assignBuildError",
|
|
||||||
target: "idle",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestingCancel: {
|
|
||||||
entry: ["clearCancellationMessage", "clearCancellationError"],
|
|
||||||
invoke: {
|
|
||||||
src: "cancelWorkspace",
|
|
||||||
id: "cancelWorkspace",
|
|
||||||
onDone: [
|
|
||||||
{
|
|
||||||
actions: [
|
|
||||||
"assignCancellationMessage",
|
|
||||||
"displayCancellationMessage",
|
|
||||||
],
|
|
||||||
target: "idle",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
onError: [
|
|
||||||
{
|
|
||||||
actions: "assignCancellationError",
|
|
||||||
target: "idle",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestingActivate: {
|
|
||||||
entry: ["clearBuildError"],
|
|
||||||
invoke: {
|
|
||||||
src: "activateWorkspace",
|
|
||||||
id: "activateWorkspace",
|
|
||||||
onDone: "idle",
|
|
||||||
onError: {
|
|
||||||
target: "idle",
|
|
||||||
actions: ["displayActivateError"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sshConfig: {
|
|
||||||
initial: "gettingSshConfig",
|
|
||||||
states: {
|
|
||||||
gettingSshConfig: {
|
|
||||||
invoke: {
|
|
||||||
src: "getSSHPrefix",
|
|
||||||
onDone: {
|
|
||||||
target: "success",
|
|
||||||
actions: ["assignSSHPrefix"],
|
|
||||||
},
|
|
||||||
onError: {
|
|
||||||
target: "error",
|
|
||||||
actions: ["displaySSHPrefixError"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
type: "final",
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
type: "final",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
type: "final",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
actions: {
|
|
||||||
// Clear data about an old workspace when looking at a new one
|
|
||||||
clearContext: () =>
|
|
||||||
assign({
|
|
||||||
workspace: undefined,
|
|
||||||
template: undefined,
|
|
||||||
build: undefined,
|
|
||||||
permissions: undefined,
|
|
||||||
eventSource: undefined,
|
|
||||||
}),
|
|
||||||
assignInitialData: assign({
|
|
||||||
workspace: (_, event) => event.data.workspace,
|
|
||||||
template: (_, event) => event.data.template,
|
|
||||||
templateVersion: (_, event) => event.data.templateVersion,
|
|
||||||
permissions: (_, event) => event.data.permissions as Permissions,
|
|
||||||
deploymentValues: (_, event) => event.data.deploymentValues,
|
|
||||||
}),
|
|
||||||
assignError: assign({
|
|
||||||
error: (_, event) => event.data,
|
|
||||||
}),
|
|
||||||
assignBuild: assign({
|
|
||||||
build: (_, event) => event.data,
|
|
||||||
}),
|
|
||||||
assignBuildError: assign({
|
|
||||||
buildError: (_, event) => event.data,
|
|
||||||
}),
|
|
||||||
clearBuildError: assign({
|
|
||||||
buildError: (_) => undefined,
|
|
||||||
}),
|
|
||||||
assignCancellationMessage: assign({
|
|
||||||
cancellationMessage: (_, event) => event.data,
|
|
||||||
}),
|
|
||||||
clearCancellationMessage: assign({
|
|
||||||
cancellationMessage: (_) => undefined,
|
|
||||||
}),
|
|
||||||
displayCancellationMessage: (context) => {
|
|
||||||
if (context.cancellationMessage) {
|
|
||||||
displaySuccess(context.cancellationMessage.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
assignCancellationError: assign({
|
|
||||||
cancellationError: (_, event) => event.data,
|
|
||||||
}),
|
|
||||||
clearCancellationError: assign({
|
|
||||||
cancellationError: (_) => undefined,
|
|
||||||
}),
|
|
||||||
// SSE related actions
|
|
||||||
// open a new EventSource so we can stream SSE
|
|
||||||
initializeEventSource: assign({
|
|
||||||
eventSource: (context) =>
|
|
||||||
context.workspace && API.watchWorkspace(context.workspace.id),
|
|
||||||
}),
|
|
||||||
closeEventSource: (context) =>
|
|
||||||
context.eventSource && context.eventSource.close(),
|
|
||||||
refreshWorkspace: assign({
|
|
||||||
workspace: (_, event) => event.data,
|
|
||||||
}),
|
|
||||||
logWatchWorkspaceWarning: (_, event) => {
|
|
||||||
console.error("Watch workspace error:", event);
|
|
||||||
},
|
|
||||||
// SSH
|
|
||||||
assignSSHPrefix: assign({
|
|
||||||
sshPrefix: (_, { data }) => data.hostname_prefix,
|
|
||||||
}),
|
|
||||||
displaySSHPrefixError: (_, { data }) => {
|
|
||||||
const message = getErrorMessage(
|
|
||||||
data,
|
|
||||||
"Error getting the deployment ssh configuration.",
|
|
||||||
);
|
|
||||||
displayError(message);
|
|
||||||
},
|
|
||||||
displayActivateError: (_, { data }) => {
|
|
||||||
const message = getErrorMessage(data, "Error activate workspace.");
|
|
||||||
displayError(message);
|
|
||||||
},
|
|
||||||
assignMissedParameters: assign({
|
|
||||||
missedParameters: (_, { data }) => {
|
|
||||||
if (!(data instanceof API.MissingBuildParameters)) {
|
|
||||||
throw new Error("data is not a MissingBuildParameters error");
|
|
||||||
}
|
|
||||||
return data.parameters;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// Debug mode when build fails
|
|
||||||
enableDebugMode: assign({ createBuildLogLevel: (_) => "debug" as const }),
|
|
||||||
disableDebugMode: assign({ createBuildLogLevel: (_) => undefined }),
|
|
||||||
// Change version
|
|
||||||
assignTemplateVersionIdToChange: assign({
|
|
||||||
templateVersionIdToChange: (_, { templateVersionId }) =>
|
|
||||||
templateVersionId,
|
|
||||||
}),
|
|
||||||
clearTemplateVersionIdToChange: assign({
|
|
||||||
templateVersionIdToChange: (_) => undefined,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
guards: {
|
|
||||||
isMissingBuildParameterError: (_, { data }) => {
|
|
||||||
return data instanceof API.MissingBuildParameters;
|
|
||||||
},
|
|
||||||
lastBuildWasStarting: ({ workspace }) => {
|
|
||||||
return workspace?.latest_build.transition === "start";
|
|
||||||
},
|
|
||||||
lastBuildWasStopping: ({ workspace }) => {
|
|
||||||
return workspace?.latest_build.transition === "stop";
|
|
||||||
},
|
|
||||||
lastBuildWasDeleting: ({ workspace }) => {
|
|
||||||
return workspace?.latest_build.transition === "delete";
|
|
||||||
},
|
|
||||||
isChangingVersion: ({ templateVersionIdToChange }) =>
|
|
||||||
Boolean(templateVersionIdToChange),
|
|
||||||
},
|
|
||||||
services: {
|
|
||||||
loadInitialWorkspaceData,
|
|
||||||
updateWorkspace:
|
|
||||||
({ workspace }, { buildParameters }) =>
|
|
||||||
async (send) => {
|
|
||||||
if (!workspace) {
|
|
||||||
throw new Error("Workspace is not set");
|
|
||||||
}
|
|
||||||
const build = await API.updateWorkspace(workspace, buildParameters);
|
|
||||||
send({ type: "REFRESH_TIMELINE" });
|
|
||||||
return build;
|
|
||||||
},
|
|
||||||
changeWorkspaceVersion:
|
|
||||||
({ workspace, templateVersionIdToChange }, { buildParameters }) =>
|
|
||||||
async (send) => {
|
|
||||||
if (!workspace) {
|
|
||||||
throw new Error("Workspace is not set");
|
|
||||||
}
|
|
||||||
if (!templateVersionIdToChange) {
|
|
||||||
throw new Error("Template version id to change is not set");
|
|
||||||
}
|
|
||||||
const build = await API.changeWorkspaceVersion(
|
|
||||||
workspace,
|
|
||||||
templateVersionIdToChange,
|
|
||||||
buildParameters,
|
|
||||||
);
|
|
||||||
send({ type: "REFRESH_TIMELINE" });
|
|
||||||
return build;
|
|
||||||
},
|
|
||||||
startWorkspace: (context, data) => async (send) => {
|
|
||||||
if (context.workspace) {
|
|
||||||
const startWorkspacePromise = await API.startWorkspace(
|
|
||||||
context.workspace.id,
|
|
||||||
context.workspace.latest_build.template_version_id,
|
|
||||||
context.createBuildLogLevel,
|
|
||||||
"buildParameters" in data ? data.buildParameters : undefined,
|
|
||||||
);
|
|
||||||
send({ type: "REFRESH_TIMELINE" });
|
|
||||||
return startWorkspacePromise;
|
|
||||||
} else {
|
|
||||||
throw Error("Cannot start workspace without workspace id");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stopWorkspace: (context) => async (send) => {
|
|
||||||
if (context.workspace) {
|
|
||||||
const stopWorkspacePromise = await API.stopWorkspace(
|
|
||||||
context.workspace.id,
|
|
||||||
context.createBuildLogLevel,
|
|
||||||
);
|
|
||||||
send({ type: "REFRESH_TIMELINE" });
|
|
||||||
return stopWorkspacePromise;
|
|
||||||
} else {
|
|
||||||
throw Error("Cannot stop workspace without workspace id");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deleteWorkspace: (context, data) => async (send) => {
|
|
||||||
if (context.workspace) {
|
|
||||||
const deleteWorkspacePromise = await API.deleteWorkspace(
|
|
||||||
context.workspace.id,
|
|
||||||
{
|
|
||||||
log_level: context.createBuildLogLevel,
|
|
||||||
orphan: data.orphan,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
send({ type: "REFRESH_TIMELINE" });
|
|
||||||
return deleteWorkspacePromise;
|
|
||||||
} else {
|
|
||||||
throw Error("Cannot delete workspace without workspace id");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cancelWorkspace: (context) => async (send) => {
|
|
||||||
if (context.workspace) {
|
|
||||||
const cancelWorkspacePromise = await API.cancelWorkspaceBuild(
|
|
||||||
context.workspace.latest_build.id,
|
|
||||||
);
|
|
||||||
send({ type: "REFRESH_TIMELINE" });
|
|
||||||
return cancelWorkspacePromise;
|
|
||||||
} else {
|
|
||||||
throw Error("Cannot cancel workspace without build id");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
activateWorkspace: (context) => async (send) => {
|
|
||||||
if (context.workspace) {
|
|
||||||
const activateWorkspacePromise = await API.updateWorkspaceDormancy(
|
|
||||||
context.workspace.id,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
send({ type: "REFRESH_WORKSPACE", data: activateWorkspacePromise });
|
|
||||||
return activateWorkspacePromise;
|
|
||||||
} else {
|
|
||||||
throw Error("Cannot activate workspace without workspace id");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
listening: (context) => (send) => {
|
|
||||||
if (!context.eventSource) {
|
|
||||||
send({ type: "EVENT_SOURCE_ERROR", error: "error initializing sse" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.eventSource.addEventListener("data", (event) => {
|
|
||||||
const newWorkspaceData = JSON.parse(event.data) as TypesGen.Workspace;
|
|
||||||
// refresh our workspace with each SSE
|
|
||||||
send({ type: "REFRESH_WORKSPACE", data: newWorkspaceData });
|
|
||||||
|
|
||||||
const currentWorkspace = context.workspace!;
|
|
||||||
const hasNewBuild =
|
|
||||||
newWorkspaceData.latest_build.id !==
|
|
||||||
currentWorkspace.latest_build.id;
|
|
||||||
const lastBuildHasChanged =
|
|
||||||
newWorkspaceData.latest_build.status !==
|
|
||||||
currentWorkspace.latest_build.status;
|
|
||||||
|
|
||||||
if (hasNewBuild || lastBuildHasChanged) {
|
|
||||||
send({ type: "REFRESH_TIMELINE" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// handle any error events returned by our sse
|
|
||||||
context.eventSource.addEventListener("error", (event) => {
|
|
||||||
send({ type: "EVENT_SOURCE_ERROR", error: event });
|
|
||||||
});
|
|
||||||
|
|
||||||
// handle any sse implementation exceptions
|
|
||||||
context.eventSource.onerror = () => {
|
|
||||||
send({ type: "EVENT_SOURCE_ERROR", error: "sse error" });
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
context.eventSource?.close();
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getSSHPrefix: async () => {
|
|
||||||
return API.getDeploymentSSHConfig();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
async function loadInitialWorkspaceData({
|
|
||||||
orgId,
|
|
||||||
username,
|
|
||||||
workspaceName,
|
|
||||||
}: WorkspaceContext) {
|
|
||||||
const workspace = await API.getWorkspaceByOwnerAndName(
|
|
||||||
username,
|
|
||||||
workspaceName,
|
|
||||||
{
|
|
||||||
include_deleted: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const template = await API.getTemplateByName(orgId, workspace.template_name);
|
|
||||||
const [templateVersion, permissions] = await Promise.all([
|
|
||||||
API.getTemplateVersion(template.active_version_id),
|
|
||||||
API.checkAuthorization({
|
|
||||||
checks: permissionsToCheck(workspace, template),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const canViewDeploymentValues = Boolean(
|
|
||||||
(permissions as Permissions)?.viewDeploymentValues,
|
|
||||||
);
|
|
||||||
const deploymentValues = canViewDeploymentValues
|
|
||||||
? (await API.getDeploymentConfig())?.config
|
|
||||||
: undefined;
|
|
||||||
return {
|
|
||||||
workspace,
|
|
||||||
template,
|
|
||||||
templateVersion,
|
|
||||||
permissions,
|
|
||||||
deploymentValues,
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Reference in New Issue