feat(ws): automate generation of types and HTTP client layer from Swagger definitions (#496)
Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com>
This commit is contained in:
parent
2d5b8304d7
commit
dcf6b93a46
|
@ -77,3 +77,25 @@ Automatically fix linting issues:
|
|||
```bash
|
||||
npm run test:fix
|
||||
```
|
||||
|
||||
### API Types & Client Generation
|
||||
|
||||
The TypeScript types and the HTTP client layer for interacting with the backend APIs are automatically generated from the backend's `swagger.json` file. This ensures the frontend remains aligned with the backend API contract at all times.
|
||||
|
||||
#### Generated Code Location
|
||||
|
||||
All generated files live in the `src/generated` directory.
|
||||
|
||||
⚠️ Do not manually edit any files in this folder.
|
||||
|
||||
#### Updating the Generated Code
|
||||
|
||||
To update the generated code, first update the `swagger.version` file in the `scripts` directory to the desired commit hash of the backend's `swagger.json` file.
|
||||
|
||||
Then run the following command to update the generated code:
|
||||
|
||||
```bash
|
||||
npm run generate:api
|
||||
```
|
||||
|
||||
Finally, make any necessary adaptations based on the changes in the generated code.
|
|
@ -18,6 +18,7 @@
|
|||
"@patternfly/react-table": "^6.2.0",
|
||||
"@patternfly/react-tokens": "^6.2.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"axios": "^1.10.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"eslint-plugin-local-rules": "^3.0.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
|
@ -109,7 +110,8 @@
|
|||
"eslint-plugin-no-relative-import-paths": "^1.5.2",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0"
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"swagger-typescript-api": "^13.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
|
@ -1950,6 +1952,34 @@
|
|||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@biomejs/js-api": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/js-api/-/js-api-1.0.0.tgz",
|
||||
"integrity": "sha512-69OfQ7+09AtiCIg+k+aU3rEsGit5o/SJWCS3BeBH/2nJYdJGi0cIx+ybka8i1EK69aNcZxYO1y1iAAEmYMq1HA==",
|
||||
"optional": true,
|
||||
"peerDependencies": {
|
||||
"@biomejs/wasm-bundler": "^2.0.0",
|
||||
"@biomejs/wasm-nodejs": "^2.0.0",
|
||||
"@biomejs/wasm-web": "^2.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@biomejs/wasm-bundler": {
|
||||
"optional": true
|
||||
},
|
||||
"@biomejs/wasm-nodejs": {
|
||||
"optional": true
|
||||
},
|
||||
"@biomejs/wasm-web": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/wasm-nodejs": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/wasm-nodejs/-/wasm-nodejs-2.0.5.tgz",
|
||||
"integrity": "sha512-pihpBMylewgDdGFZHRkgmc3OajuGIJPXhvfYuKCNK/CWyJMrYEFmPKs8Iq1kY0sYMmGlTbD4K2udV03KYa+r0Q==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@colors/colors": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||
|
@ -3088,6 +3118,12 @@
|
|||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/schemasafe": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz",
|
||||
"integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.14",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||
|
@ -5734,6 +5770,12 @@
|
|||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/swagger-schema-official": {
|
||||
"version": "2.0.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/swagger-schema-official/-/swagger-schema-official-2.0.25.tgz",
|
||||
"integrity": "sha512-T92Xav+Gf/Ik1uPW581nA+JftmjWPgskw/WBf4TJzxRG/SJ+DfNnNE+WuZ4mrXuzflQMqMkm1LSYjzYW7MB1Cg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/testing-library__jest-dom": {
|
||||
"version": "5.14.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.8.tgz",
|
||||
|
@ -6656,8 +6698,7 @@
|
|||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
|
@ -6719,6 +6760,21 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
|
||||
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
|
@ -7415,6 +7471,62 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/c12": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-3.0.4.tgz",
|
||||
"integrity": "sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.3",
|
||||
"confbox": "^0.2.2",
|
||||
"defu": "^6.1.4",
|
||||
"dotenv": "^16.5.0",
|
||||
"exsolve": "^1.0.5",
|
||||
"giget": "^2.0.0",
|
||||
"jiti": "^2.4.2",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"pkg-types": "^2.1.0",
|
||||
"rc9": "^2.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"magicast": "^0.3.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"magicast": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/cachedir": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz",
|
||||
|
@ -7501,6 +7613,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-me-maybe": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
|
||||
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
|
@ -7818,6 +7936,15 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/citty": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
||||
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"consola": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/cjs-module-lexer": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz",
|
||||
|
@ -7945,7 +8072,7 @@
|
|||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
|
@ -8023,7 +8150,6 @@
|
|||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
|
@ -8263,6 +8389,12 @@
|
|||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
|
||||
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/connect-history-api-fallback": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz",
|
||||
|
@ -8272,6 +8404,15 @@
|
|||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
|
||||
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/console-clear": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz",
|
||||
|
@ -9780,11 +9921,16 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
|
@ -9807,6 +9953,12 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/destr": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
|
@ -10014,11 +10166,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.6",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.6.tgz",
|
||||
"integrity": "sha512-JhcR/+KIjkkjiU8yEpaB/USlzVi3i5whwOjpIRNGi9svKEXZSe+Qp6IWAjFjv+2GViAoDRCUv/QLNziQxsLqDg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"version": "16.5.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
|
||||
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
@ -10418,11 +10569,17 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
|
||||
"integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
|
@ -11279,6 +11436,18 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eta": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz",
|
||||
"integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/eta-dev/eta?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
|
@ -11482,6 +11651,12 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz",
|
||||
"integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
|
@ -11599,6 +11774,12 @@
|
|||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/fastest-levenshtein": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz",
|
||||
|
@ -11988,7 +12169,6 @@
|
|||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
|
@ -12234,7 +12414,6 @@
|
|||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
|
@ -12393,7 +12572,7 @@
|
|||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
|
@ -12500,6 +12679,23 @@
|
|||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/giget": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.0",
|
||||
"defu": "^6.1.4",
|
||||
"node-fetch-native": "^1.6.6",
|
||||
"nypm": "^0.6.0",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"bin": {
|
||||
"giget": "dist/cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
|
@ -13082,6 +13278,12 @@
|
|||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/http2-client": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz",
|
||||
"integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
|
@ -15841,6 +16043,15 @@
|
|||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
||||
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
@ -16797,7 +17008,6 @@
|
|||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
|
@ -16806,7 +17016,6 @@
|
|||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
|
@ -17675,6 +17884,44 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch-h2": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz",
|
||||
"integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"http2-client": "^1.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch-native": {
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz",
|
||||
"integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
|
@ -17704,6 +17951,15 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/node-readfiles": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz",
|
||||
"integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"es6-promise": "^3.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
|
@ -18090,6 +18346,95 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.0.tgz",
|
||||
"integrity": "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.0",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.0.0",
|
||||
"tinyexec": "^0.3.2"
|
||||
},
|
||||
"bin": {
|
||||
"nypm": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.16.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/oas-kit-common": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz",
|
||||
"integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fast-safe-stringify": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/oas-linter": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz",
|
||||
"integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@exodus/schemasafe": "^1.0.0-rc.2",
|
||||
"should": "^13.2.1",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/oas-resolver": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz",
|
||||
"integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-fetch-h2": "^2.3.0",
|
||||
"oas-kit-common": "^1.0.8",
|
||||
"reftools": "^1.1.9",
|
||||
"yaml": "^1.10.0",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "resolve.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/oas-schema-walker": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz",
|
||||
"integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==",
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/oas-validator": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz",
|
||||
"integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"call-me-maybe": "^1.0.1",
|
||||
"oas-kit-common": "^1.0.8",
|
||||
"oas-linter": "^3.2.2",
|
||||
"oas-resolver": "^2.5.6",
|
||||
"oas-schema-walker": "^1.1.5",
|
||||
"reftools": "^1.1.9",
|
||||
"should": "^13.2.1",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
@ -18222,6 +18567,12 @@
|
|||
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ohash": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
||||
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
|
@ -18582,6 +18933,12 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/peek-readable": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
|
||||
|
@ -18602,6 +18959,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
|
@ -18670,6 +19033,17 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz",
|
||||
"integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"confbox": "^0.2.1",
|
||||
"exsolve": "^1.0.1",
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||
|
@ -19561,6 +19935,16 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rc9": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
|
||||
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
|
@ -19808,6 +20192,15 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/reftools": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz",
|
||||
"integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==",
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerate": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
|
@ -19985,7 +20378,7 @@
|
|||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
@ -20864,6 +21257,60 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/should": {
|
||||
"version": "13.2.3",
|
||||
"resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz",
|
||||
"integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"should-equal": "^2.0.0",
|
||||
"should-format": "^3.0.3",
|
||||
"should-type": "^1.4.0",
|
||||
"should-type-adaptors": "^1.0.1",
|
||||
"should-util": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/should-equal": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
|
||||
"integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"should-type": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/should-format": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
|
||||
"integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"should-type": "^1.3.0",
|
||||
"should-type-adaptors": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/should-type": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
|
||||
"integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/should-type-adaptors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
|
||||
"integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"should-type": "^1.3.0",
|
||||
"should-util": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/should-util": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz",
|
||||
"integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
|
@ -21647,6 +22094,85 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-schema-official": {
|
||||
"version": "2.0.0-bab6bed",
|
||||
"resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz",
|
||||
"integrity": "sha512-rCC0NWGKr/IJhtRuPq/t37qvZHI/mH4I4sxflVM+qgVe5Z2uOCivzWaVbuioJaB61kvm5UvB7b49E+oBY0M8jA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/swagger-typescript-api": {
|
||||
"version": "13.2.7",
|
||||
"resolved": "https://registry.npmjs.org/swagger-typescript-api/-/swagger-typescript-api-13.2.7.tgz",
|
||||
"integrity": "sha512-rfqqoRFpZJPl477M/snMJPM90EvI8WqhuUHSF5ecC2r/w376T29+QXNJFVPsJmbFu5rBc/8m3vhArtMctjONdw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@biomejs/js-api": "1.0.0",
|
||||
"@biomejs/wasm-nodejs": "2.0.5",
|
||||
"@types/swagger-schema-official": "^2.0.25",
|
||||
"c12": "^3.0.4",
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.2",
|
||||
"eta": "^2.2.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "^5.1.5",
|
||||
"swagger-schema-official": "2.0.0-bab6bed",
|
||||
"swagger2openapi": "^7.0.8",
|
||||
"typescript": "~5.8.3"
|
||||
},
|
||||
"bin": {
|
||||
"sta": "dist/cli.js",
|
||||
"swagger-typescript-api": "dist/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-typescript-api/node_modules/nanoid": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz",
|
||||
"integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger2openapi": {
|
||||
"version": "7.0.8",
|
||||
"resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz",
|
||||
"integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"call-me-maybe": "^1.0.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-fetch-h2": "^2.3.0",
|
||||
"node-readfiles": "^0.2.0",
|
||||
"oas-kit-common": "^1.0.8",
|
||||
"oas-resolver": "^2.5.6",
|
||||
"oas-schema-walker": "^1.1.5",
|
||||
"oas-validator": "^5.0.8",
|
||||
"reftools": "^1.1.9",
|
||||
"yaml": "^1.10.0",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"boast": "boast.js",
|
||||
"oas-validate": "oas-validate.js",
|
||||
"swagger2openapi": "swagger2openapi.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
|
@ -21874,6 +22400,12 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
|
||||
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "6.1.58",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.58.tgz",
|
||||
|
@ -21969,6 +22501,12 @@
|
|||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/tree-dump": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz",
|
||||
|
@ -22443,9 +22981,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
@ -22812,6 +23350,12 @@
|
|||
"minimalistic-assert": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.95.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz",
|
||||
|
@ -23381,6 +23925,16 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -23571,7 +24125,7 @@
|
|||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
|
@ -23643,7 +24197,7 @@
|
|||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
|
@ -23658,7 +24212,7 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
|
@ -23670,7 +24224,7 @@
|
|||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
|
@ -23784,7 +24338,7 @@
|
|||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
|
@ -23809,7 +24363,7 @@
|
|||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
|
@ -23827,7 +24381,7 @@
|
|||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"build:bundle-analyze": "webpack-bundle-analyzer ./bundle.stats.json",
|
||||
"build:clean": "rimraf ./dist",
|
||||
"build:prod": "webpack --config ./config/webpack.prod.js",
|
||||
"generate:api": "./scripts/generate-api.sh && npm run prettier",
|
||||
"start:dev": "cross-env STYLE_THEME=$npm_config_theme webpack serve --hot --color --config ./config/webpack.dev.js",
|
||||
"start:dev:mock": "cross-env MOCK_API_ENABLED=true STYLE_THEME=$npm_config_theme npm run start:dev",
|
||||
"test": "run-s prettier:check test:lint test:unit test:cypress-ci",
|
||||
|
@ -113,6 +114,7 @@
|
|||
"@patternfly/react-table": "^6.2.0",
|
||||
"@patternfly/react-tokens": "^6.2.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"axios": "^1.10.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"eslint-plugin-local-rules": "^3.0.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
|
@ -138,6 +140,7 @@
|
|||
"eslint-plugin-no-relative-import-paths": "^1.5.2",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0"
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"swagger-typescript-api": "^13.2.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
GENERATED_DIR="./src/generated"
|
||||
HASH_FILE="./scripts/swagger.version"
|
||||
SWAGGER_COMMIT_HASH=$(cat "$HASH_FILE")
|
||||
SWAGGER_JSON_PATH="../backend/openapi/swagger.json"
|
||||
TMP_SWAGGER=".tmp-swagger.json"
|
||||
|
||||
if ! git cat-file -e "${SWAGGER_COMMIT_HASH}:${SWAGGER_JSON_PATH}"; then
|
||||
echo "❌ Swagger file not found at commit $SWAGGER_COMMIT_HASH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git show "${SWAGGER_COMMIT_HASH}:${SWAGGER_JSON_PATH}" >"$TMP_SWAGGER"
|
||||
|
||||
swagger-typescript-api generate \
|
||||
-p "$TMP_SWAGGER" \
|
||||
-o "$GENERATED_DIR" \
|
||||
--extract-request-body \
|
||||
--responses \
|
||||
--clean-output \
|
||||
--axios \
|
||||
--unwrap-response-data \
|
||||
--modular
|
||||
|
||||
rm "$TMP_SWAGGER"
|
|
@ -0,0 +1 @@
|
|||
4f0a29dec0d3c9f0d0f02caab4dc84101bfef8b0
|
|
@ -1,7 +1,7 @@
|
|||
import { NamespacesNamespace } from '~/generated/data-contracts';
|
||||
import { buildMockNamespace } from '~/shared/mock/mockBuilder';
|
||||
import { Namespace } from '~/shared/api/backendApiTypes';
|
||||
|
||||
export const mockNamespaces: Namespace[] = [
|
||||
export const mockNamespaces: NamespacesNamespace[] = [
|
||||
buildMockNamespace({ name: 'default' }),
|
||||
buildMockNamespace({ name: 'kubeflow' }),
|
||||
buildMockNamespace({ name: 'custom-namespace' }),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { ResponseBody } from '~/shared/api/types';
|
||||
interface Envelope<T> {
|
||||
data: T;
|
||||
}
|
||||
|
||||
export const mockBFFResponse = <T>(data: T): ResponseBody<T> => ({
|
||||
export const mockBFFResponse = <T>(data: T): Envelope<T> => ({
|
||||
data,
|
||||
});
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { WorkspaceState } from '~/shared/api/backendApiTypes';
|
||||
import type { Workspace, WorkspaceKindInfo } from '~/shared/api/backendApiTypes';
|
||||
import type { WorkspacesWorkspace, WorkspacesWorkspaceKindInfo } from '~/generated/data-contracts';
|
||||
import { WorkspacesWorkspaceState } from '~/generated/data-contracts';
|
||||
|
||||
const generateMockWorkspace = (
|
||||
name: string,
|
||||
namespace: string,
|
||||
state: WorkspaceState,
|
||||
state: WorkspacesWorkspaceState,
|
||||
paused: boolean,
|
||||
imageConfigId: string,
|
||||
imageConfigDisplayName: string,
|
||||
podConfigId: string,
|
||||
podConfigDisplayName: string,
|
||||
pvcName: string,
|
||||
): Workspace => {
|
||||
): WorkspacesWorkspace => {
|
||||
const pausedTime = new Date(2025, 0, 1).getTime();
|
||||
const lastActivityTime = new Date(2025, 0, 2).getTime();
|
||||
const lastUpdateTime = new Date(2025, 0, 3).getTime();
|
||||
|
@ -19,16 +19,16 @@ const generateMockWorkspace = (
|
|||
return {
|
||||
name,
|
||||
namespace,
|
||||
workspaceKind: { name: 'jupyterlab' } as WorkspaceKindInfo,
|
||||
workspaceKind: { name: 'jupyterlab' } as WorkspacesWorkspaceKindInfo,
|
||||
deferUpdates: paused,
|
||||
paused,
|
||||
pausedTime,
|
||||
pendingRestart: Math.random() < 0.5, //to generate randomly True/False value
|
||||
state,
|
||||
stateMessage:
|
||||
state === WorkspaceState.WorkspaceStateRunning
|
||||
state === WorkspacesWorkspaceState.WorkspaceStateRunning
|
||||
? 'Workspace is running smoothly.'
|
||||
: state === WorkspaceState.WorkspaceStatePaused
|
||||
: state === WorkspacesWorkspaceState.WorkspaceStatePaused
|
||||
? 'Workspace is paused.'
|
||||
: 'Workspace is operational.',
|
||||
podTemplate: {
|
||||
|
@ -104,11 +104,11 @@ const generateMockWorkspaces = (numWorkspaces: number, byNamespace = false) => {
|
|||
for (let i = 1; i <= numWorkspaces; i++) {
|
||||
const state =
|
||||
i % 3 === 0
|
||||
? WorkspaceState.WorkspaceStateError
|
||||
? WorkspacesWorkspaceState.WorkspaceStateError
|
||||
: i % 2 === 0
|
||||
? WorkspaceState.WorkspaceStatePaused
|
||||
: WorkspaceState.WorkspaceStateRunning;
|
||||
const paused = state === WorkspaceState.WorkspaceStatePaused;
|
||||
? WorkspacesWorkspaceState.WorkspaceStatePaused
|
||||
: WorkspacesWorkspaceState.WorkspaceStateRunning;
|
||||
const paused = state === WorkspacesWorkspaceState.WorkspaceStatePaused;
|
||||
const name = `workspace-${i}`;
|
||||
const namespace = namespaces[i % namespaces.length];
|
||||
const pvcName = `data-pvc-${i}`;
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import type { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import {
|
||||
WorkspacekindsRedirectMessageLevel,
|
||||
type WorkspacekindsWorkspaceKind,
|
||||
} from '~/generated/data-contracts';
|
||||
|
||||
// Factory function to create a valid WorkspaceKind
|
||||
function createMockWorkspaceKind(overrides: Partial<WorkspaceKind> = {}): WorkspaceKind {
|
||||
function createMockWorkspaceKind(
|
||||
overrides: Partial<WorkspacekindsWorkspaceKind> = {},
|
||||
): WorkspacekindsWorkspaceKind {
|
||||
return {
|
||||
name: 'jupyter-lab',
|
||||
displayName: 'JupyterLab Notebook',
|
||||
|
@ -27,14 +32,15 @@ function createMockWorkspaceKind(overrides: Partial<WorkspaceKind> = {}): Worksp
|
|||
values: [
|
||||
{
|
||||
id: 'jupyterlab_scipy_180',
|
||||
description: 'JupyterLab with SciPy 1.8.0',
|
||||
displayName: 'jupyter-scipy:v1.8.0',
|
||||
labels: { pythonVersion: '3.11' },
|
||||
labels: [{ key: 'pythonVersion', value: '3.11' }],
|
||||
hidden: true,
|
||||
redirect: {
|
||||
to: 'jupyterlab_scipy_190',
|
||||
message: {
|
||||
text: 'This update will change...',
|
||||
level: 'Info',
|
||||
level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelInfo,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -45,9 +51,13 @@ function createMockWorkspaceKind(overrides: Partial<WorkspaceKind> = {}): Worksp
|
|||
values: [
|
||||
{
|
||||
id: 'tiny_cpu',
|
||||
hidden: false,
|
||||
displayName: 'Tiny CPU',
|
||||
description: 'Pod with 0.1 CPU, 128 Mb RAM',
|
||||
labels: { cpu: '100m', memory: '128Mi' },
|
||||
labels: [
|
||||
{ key: 'cpu', value: '100m' },
|
||||
{ key: 'memory', value: '128Mi' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import type { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { mockNamespaces } from '~/__mocks__/mockNamespaces';
|
||||
import { mockBFFResponse } from '~/__mocks__/utils';
|
||||
import { home } from '~/__tests__/cypress/cypress/pages/home';
|
||||
import {
|
||||
mockWorkspaces,
|
||||
mockWorkspacesByNS,
|
||||
} from '~/__tests__/cypress/cypress/tests/mocked/workspace.mock';
|
||||
import { mockNamespaces } from '~/__mocks__/mockNamespaces';
|
||||
import { mockBFFResponse } from '~/__mocks__/utils';
|
||||
import type { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
// Helper function to validate the content of a single workspace row in the table
|
||||
const validateWorkspaceRow = (workspace: Workspace, index: number) => {
|
||||
const validateWorkspaceRow = (workspace: WorkspacesWorkspace, index: number) => {
|
||||
// Validate the workspace name
|
||||
cy.findByTestId(`workspace-row-${index}`)
|
||||
.find('[data-testid="workspace-name"]')
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { WorkspaceKind, WorkspaceOptionRedirect } from '~/shared/api/backendApiTypes';
|
||||
import {
|
||||
WorkspacekindsOptionRedirect,
|
||||
WorkspacekindsWorkspaceKind,
|
||||
} from '~/generated/data-contracts';
|
||||
|
||||
type KindLogoDict = Record<string, string>;
|
||||
|
||||
|
@ -7,7 +10,9 @@ type KindLogoDict = Record<string, string>;
|
|||
* @param {WorkspaceKind[]} workspaceKinds - The list of workspace kinds.
|
||||
* @returns {KindLogoDict} A dictionary with kind names as keys and logo URLs as values.
|
||||
*/
|
||||
export function buildKindLogoDictionary(workspaceKinds: WorkspaceKind[] | []): KindLogoDict {
|
||||
export function buildKindLogoDictionary(
|
||||
workspaceKinds: WorkspacekindsWorkspaceKind[] | [],
|
||||
): KindLogoDict {
|
||||
const kindLogoDict: KindLogoDict = {};
|
||||
|
||||
for (const workspaceKind of workspaceKinds) {
|
||||
|
@ -20,7 +25,7 @@ export function buildKindLogoDictionary(workspaceKinds: WorkspaceKind[] | []): K
|
|||
return kindLogoDict;
|
||||
}
|
||||
|
||||
type WorkspaceRedirectStatus = Record<string, WorkspaceOptionRedirect | undefined>;
|
||||
type WorkspaceRedirectStatus = Record<string, WorkspacekindsOptionRedirect | undefined>;
|
||||
|
||||
/**
|
||||
* Builds a dictionary of workspace kinds to redirect statuses.
|
||||
|
@ -28,7 +33,7 @@ type WorkspaceRedirectStatus = Record<string, WorkspaceOptionRedirect | undefine
|
|||
* @returns {WorkspaceRedirectStatus} A dictionary with kind names as keys and redirect status objects as values.
|
||||
*/
|
||||
export function buildWorkspaceRedirectStatus(
|
||||
workspaceKinds: WorkspaceKind[] | [],
|
||||
workspaceKinds: WorkspacekindsWorkspaceKind[] | [],
|
||||
): WorkspaceRedirectStatus {
|
||||
const workspaceRedirectStatus: WorkspaceRedirectStatus = {};
|
||||
for (const workspaceKind of workspaceKinds) {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import React from 'react';
|
||||
import { Alert } from '@patternfly/react-core/dist/esm/components/Alert';
|
||||
import { List, ListItem } from '@patternfly/react-core/dist/esm/components/List';
|
||||
import { ValidationError } from '~/shared/api/backendApiTypes';
|
||||
import { ErrorEnvelopeException } from '~/shared/api/apiUtils';
|
||||
import { ApiValidationError } from '~/generated/data-contracts';
|
||||
|
||||
interface ValidationErrorAlertProps {
|
||||
title: string;
|
||||
errors: (ValidationError | ErrorEnvelopeException)[];
|
||||
errors: ApiValidationError[];
|
||||
}
|
||||
|
||||
export const ValidationErrorAlert: React.FC<ValidationErrorAlertProps> = ({ title, errors }) => {
|
||||
|
@ -18,7 +17,9 @@ export const ValidationErrorAlert: React.FC<ValidationErrorAlertProps> = ({ titl
|
|||
<Alert variant="danger" title={title} isInline>
|
||||
<List>
|
||||
{errors.map((error, index) => (
|
||||
<ListItem key={index}>{error.message}</ListItem>
|
||||
<ListItem key={index}>
|
||||
{error.message}: '{error.field}'
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Alert>
|
||||
|
|
|
@ -44,7 +44,6 @@ import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/
|
|||
import { TimesCircleIcon } from '@patternfly/react-icons/dist/esm/icons/times-circle-icon';
|
||||
import { QuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/question-circle-icon';
|
||||
import { formatDistanceToNow } from 'date-fns/formatDistanceToNow';
|
||||
import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes';
|
||||
import { DataFieldKey, defineDataFields, SortableDataFieldKey } from '~/app/filterableDataHelper';
|
||||
import { useTypedNavigate } from '~/app/routerHelper';
|
||||
import {
|
||||
|
@ -62,6 +61,7 @@ import {
|
|||
} from '~/shared/utilities/WorkspaceUtils';
|
||||
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
|
||||
import CustomEmptyState from '~/shared/components/CustomEmptyState';
|
||||
import { WorkspacesWorkspace, WorkspacesWorkspaceState } from '~/generated/data-contracts';
|
||||
|
||||
const {
|
||||
fields: wsTableColumns,
|
||||
|
@ -84,11 +84,11 @@ export type WorkspaceTableColumnKeys = DataFieldKey<typeof wsTableColumns>;
|
|||
type WorkspaceTableSortableColumnKeys = SortableDataFieldKey<typeof wsTableColumns>;
|
||||
|
||||
interface WorkspaceTableProps {
|
||||
workspaces: Workspace[];
|
||||
workspaces: WorkspacesWorkspace[];
|
||||
canCreateWorkspaces?: boolean;
|
||||
canExpandRows?: boolean;
|
||||
hiddenColumns?: WorkspaceTableColumnKeys[];
|
||||
rowActions?: (workspace: Workspace) => IActions;
|
||||
rowActions?: (workspace: WorkspacesWorkspace) => IActions;
|
||||
}
|
||||
|
||||
const allFiltersConfig = {
|
||||
|
@ -233,7 +233,7 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
|
|||
[clearAllFilters],
|
||||
);
|
||||
|
||||
const filterableProperties: Record<FilterKey, (ws: Workspace) => string> = useMemo(
|
||||
const filterableProperties: Record<FilterKey, (ws: WorkspacesWorkspace) => string> = useMemo(
|
||||
() => ({
|
||||
name: (ws) => ws.name,
|
||||
kind: (ws) => ws.workspaceKind.name,
|
||||
|
@ -245,7 +245,7 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
|
|||
[],
|
||||
);
|
||||
|
||||
const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) =>
|
||||
const setWorkspaceExpanded = (workspace: WorkspacesWorkspace, isExpanding = true) =>
|
||||
setExpandedWorkspacesNames((prevExpanded) => {
|
||||
const newExpandedWorkspacesNames = prevExpanded.filter(
|
||||
(wsName) => wsName !== workspace.name,
|
||||
|
@ -255,7 +255,7 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
|
|||
: newExpandedWorkspacesNames;
|
||||
});
|
||||
|
||||
const isWorkspaceExpanded = (workspace: Workspace) =>
|
||||
const isWorkspaceExpanded = (workspace: WorkspacesWorkspace) =>
|
||||
expandedWorkspacesNames.includes(workspace.name);
|
||||
|
||||
const filteredWorkspaces = useMemo(() => {
|
||||
|
@ -289,7 +289,7 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
|
|||
// Column sorting
|
||||
|
||||
const getSortableRowValues = (
|
||||
workspace: Workspace,
|
||||
workspace: WorkspacesWorkspace,
|
||||
): Record<WorkspaceTableSortableColumnKeys, string | number> => ({
|
||||
name: workspace.name,
|
||||
kind: workspace.workspaceKind.name,
|
||||
|
@ -374,19 +374,19 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
|
|||
}
|
||||
};
|
||||
|
||||
const extractStateColor = (state: WorkspaceState) => {
|
||||
const extractStateColor = (state: WorkspacesWorkspaceState) => {
|
||||
switch (state) {
|
||||
case WorkspaceState.WorkspaceStateRunning:
|
||||
case WorkspacesWorkspaceState.WorkspaceStateRunning:
|
||||
return 'green';
|
||||
case WorkspaceState.WorkspaceStatePending:
|
||||
case WorkspacesWorkspaceState.WorkspaceStatePending:
|
||||
return 'orange';
|
||||
case WorkspaceState.WorkspaceStateTerminating:
|
||||
case WorkspacesWorkspaceState.WorkspaceStateTerminating:
|
||||
return 'yellow';
|
||||
case WorkspaceState.WorkspaceStateError:
|
||||
case WorkspacesWorkspaceState.WorkspaceStateError:
|
||||
return 'red';
|
||||
case WorkspaceState.WorkspaceStatePaused:
|
||||
case WorkspacesWorkspaceState.WorkspaceStatePaused:
|
||||
return 'purple';
|
||||
case WorkspaceState.WorkspaceStateUnknown:
|
||||
case WorkspacesWorkspaceState.WorkspaceStateUnknown:
|
||||
default:
|
||||
return 'grey';
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import { useNamespaceContext } from '~/app/context/NamespaceContextProvider';
|
|||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
|
||||
import { useTypedNavigate } from '~/app/routerHelper';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import DeleteModal from '~/shared/components/DeleteModal';
|
||||
import { WorkspaceStartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal';
|
||||
import { WorkspaceRestartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal';
|
||||
import { WorkspaceStopActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
export enum ActionType {
|
||||
ViewDetails = 'ViewDetails',
|
||||
|
@ -25,7 +25,7 @@ export enum ActionType {
|
|||
|
||||
export interface WorkspaceAction {
|
||||
action: ActionType;
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
onActionDone?: () => void;
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,8 @@ export const WorkspaceActionsContextProvider: React.FC<WorkspaceActionsContextPr
|
|||
}, []);
|
||||
|
||||
const createActionRequester =
|
||||
(actionType: ActionType) => (args: { workspace: Workspace; onActionDone?: () => void }) =>
|
||||
(actionType: ActionType) =>
|
||||
(args: { workspace: WorkspacesWorkspace; onActionDone?: () => void }) =>
|
||||
setActiveWsAction({ action: actionType, ...args });
|
||||
|
||||
const requestViewDetailsAction = createActionRequester(ActionType.ViewDetails);
|
||||
|
@ -113,7 +114,7 @@ export const WorkspaceActionsContextProvider: React.FC<WorkspaceActionsContextPr
|
|||
}
|
||||
|
||||
try {
|
||||
await api.deleteWorkspace({}, selectedNamespace, activeWsAction.workspace.name);
|
||||
await api.workspaces.deleteWorkspace(selectedNamespace, activeWsAction.workspace.name);
|
||||
// TODO: alert user about success
|
||||
console.info(`Workspace '${activeWsAction.workspace.name}' deleted successfully`);
|
||||
activeWsAction.onActionDone?.();
|
||||
|
@ -179,9 +180,13 @@ export const WorkspaceActionsContextProvider: React.FC<WorkspaceActionsContextPr
|
|||
onClose={onCloseActionAlertDialog}
|
||||
workspace={activeWsAction.workspace}
|
||||
onStart={async () =>
|
||||
api.pauseWorkspace({}, selectedNamespace, activeWsAction.workspace.name, {
|
||||
data: { paused: false },
|
||||
})
|
||||
api.workspaces.updateWorkspacePauseState(
|
||||
selectedNamespace,
|
||||
activeWsAction.workspace.name,
|
||||
{
|
||||
data: { paused: false },
|
||||
},
|
||||
)
|
||||
}
|
||||
onActionDone={activeWsAction.onActionDone}
|
||||
onUpdateAndStart={async () => {
|
||||
|
@ -202,9 +207,13 @@ export const WorkspaceActionsContextProvider: React.FC<WorkspaceActionsContextPr
|
|||
onClose={onCloseActionAlertDialog}
|
||||
workspace={activeWsAction.workspace}
|
||||
onStop={async () =>
|
||||
api.pauseWorkspace({}, selectedNamespace, activeWsAction.workspace.name, {
|
||||
data: { paused: true },
|
||||
})
|
||||
api.workspaces.updateWorkspacePauseState(
|
||||
selectedNamespace,
|
||||
activeWsAction.workspace.name,
|
||||
{
|
||||
data: { paused: true },
|
||||
},
|
||||
)
|
||||
}
|
||||
onActionDone={activeWsAction.onActionDone}
|
||||
onUpdateAndStop={async () => {
|
||||
|
|
|
@ -1,103 +1,18 @@
|
|||
import { useCallback } from 'react';
|
||||
import { NotebookAPIs } from '~/shared/api/notebookApi';
|
||||
import {
|
||||
createWorkspace,
|
||||
createWorkspaceKind,
|
||||
deleteWorkspace,
|
||||
deleteWorkspaceKind,
|
||||
getHealthCheck,
|
||||
getWorkspace,
|
||||
getWorkspaceKind,
|
||||
listAllWorkspaces,
|
||||
listNamespaces,
|
||||
listWorkspaceKinds,
|
||||
listWorkspaces,
|
||||
patchWorkspace,
|
||||
patchWorkspaceKind,
|
||||
pauseWorkspace,
|
||||
updateWorkspace,
|
||||
updateWorkspaceKind,
|
||||
} from '~/shared/api/notebookService';
|
||||
import { NotebookApis, notebookApisImpl } from '~/shared/api/notebookApi';
|
||||
import { APIState } from '~/shared/api/types';
|
||||
import useAPIState from '~/shared/api/useAPIState';
|
||||
import {
|
||||
mockCreateWorkspace,
|
||||
mockCreateWorkspaceKind,
|
||||
mockDeleteWorkspace,
|
||||
mockDeleteWorkspaceKind,
|
||||
mockGetHealthCheck,
|
||||
mockGetWorkspace,
|
||||
mockGetWorkspaceKind,
|
||||
mockListAllWorkspaces,
|
||||
mockListNamespaces,
|
||||
mockListWorkspaceKinds,
|
||||
mockListWorkspaces,
|
||||
mockPatchWorkspace,
|
||||
mockPatchWorkspaceKind,
|
||||
mockPauseWorkspace,
|
||||
mockUpdateWorkspace,
|
||||
mockUpdateWorkspaceKind,
|
||||
} from '~/shared/mock/mockNotebookService';
|
||||
import { mockNotebookApisImpl } from '~/shared/mock/mockNotebookApis';
|
||||
|
||||
export type NotebookAPIState = APIState<NotebookAPIs>;
|
||||
export type NotebookAPIState = APIState<NotebookApis>;
|
||||
|
||||
const MOCK_API_ENABLED = process.env.WEBPACK_REPLACE__mockApiEnabled === 'true';
|
||||
|
||||
const useNotebookAPIState = (
|
||||
hostPath: string | null,
|
||||
): [apiState: NotebookAPIState, refreshAPIState: () => void] => {
|
||||
const createApi = useCallback(
|
||||
(path: string): NotebookAPIs => ({
|
||||
// Health
|
||||
getHealthCheck: getHealthCheck(path),
|
||||
// Namespace
|
||||
listNamespaces: listNamespaces(path),
|
||||
// Workspace
|
||||
listAllWorkspaces: listAllWorkspaces(path),
|
||||
listWorkspaces: listWorkspaces(path),
|
||||
createWorkspace: createWorkspace(path),
|
||||
getWorkspace: getWorkspace(path),
|
||||
updateWorkspace: updateWorkspace(path),
|
||||
patchWorkspace: patchWorkspace(path),
|
||||
deleteWorkspace: deleteWorkspace(path),
|
||||
pauseWorkspace: pauseWorkspace(path),
|
||||
// WorkspaceKind
|
||||
listWorkspaceKinds: listWorkspaceKinds(path),
|
||||
createWorkspaceKind: createWorkspaceKind(path),
|
||||
getWorkspaceKind: getWorkspaceKind(path),
|
||||
patchWorkspaceKind: patchWorkspaceKind(path),
|
||||
deleteWorkspaceKind: deleteWorkspaceKind(path),
|
||||
updateWorkspaceKind: updateWorkspaceKind(path),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const createMockApi = useCallback(
|
||||
(path: string): NotebookAPIs => ({
|
||||
// Health
|
||||
getHealthCheck: mockGetHealthCheck(path),
|
||||
// Namespace
|
||||
listNamespaces: mockListNamespaces(path),
|
||||
// Workspace
|
||||
listAllWorkspaces: mockListAllWorkspaces(path),
|
||||
listWorkspaces: mockListWorkspaces(path),
|
||||
createWorkspace: mockCreateWorkspace(path),
|
||||
getWorkspace: mockGetWorkspace(path),
|
||||
updateWorkspace: mockUpdateWorkspace(path),
|
||||
patchWorkspace: mockPatchWorkspace(path),
|
||||
deleteWorkspace: mockDeleteWorkspace(path),
|
||||
pauseWorkspace: mockPauseWorkspace(path),
|
||||
// WorkspaceKind
|
||||
listWorkspaceKinds: mockListWorkspaceKinds(path),
|
||||
createWorkspaceKind: mockCreateWorkspaceKind(path),
|
||||
getWorkspaceKind: mockGetWorkspaceKind(path),
|
||||
patchWorkspaceKind: mockPatchWorkspaceKind(path),
|
||||
deleteWorkspaceKind: mockDeleteWorkspaceKind(path),
|
||||
updateWorkspaceKind: mockUpdateWorkspaceKind(path),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const createApi = useCallback((path: string) => notebookApisImpl(path), []);
|
||||
const createMockApi = useCallback(() => mockNotebookApisImpl(), []);
|
||||
return useAPIState(hostPath, MOCK_API_ENABLED ? createMockApi : createApi);
|
||||
};
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@ import { renderHook } from '~/__tests__/unit/testUtils/hooks';
|
|||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind';
|
||||
import {
|
||||
Workspace,
|
||||
WorkspaceImageConfigValue,
|
||||
WorkspaceKind,
|
||||
WorkspaceKindInfo,
|
||||
WorkspacePodConfigValue,
|
||||
} from '~/shared/api/backendApiTypes';
|
||||
import { NotebookAPIs } from '~/shared/api/notebookApi';
|
||||
WorkspacekindsImageConfigValue,
|
||||
WorkspacekindsPodConfigValue,
|
||||
WorkspacekindsWorkspaceKind,
|
||||
WorkspacesWorkspace,
|
||||
WorkspacesWorkspaceKindInfo,
|
||||
} from '~/generated/data-contracts';
|
||||
import { NotebookApis } from '~/shared/api/notebookApi';
|
||||
import { buildMockWorkspace, buildMockWorkspaceKind } from '~/shared/mock/mockBuilder';
|
||||
|
||||
jest.mock('~/app/hooks/useNotebookAPI', () => ({
|
||||
|
@ -18,7 +18,7 @@ jest.mock('~/app/hooks/useNotebookAPI', () => ({
|
|||
|
||||
const mockUseNotebookAPI = useNotebookAPI as jest.MockedFunction<typeof useNotebookAPI>;
|
||||
|
||||
const baseWorkspaceKindInfoTest: WorkspaceKindInfo = {
|
||||
const baseWorkspaceKindInfoTest: WorkspacesWorkspaceKindInfo = {
|
||||
name: 'jupyter',
|
||||
missing: false,
|
||||
icon: { url: '' },
|
||||
|
@ -31,7 +31,7 @@ const baseWorkspaceTest = buildMockWorkspace({
|
|||
workspaceKind: baseWorkspaceKindInfoTest,
|
||||
});
|
||||
|
||||
const baseImageConfigTest: WorkspaceImageConfigValue = {
|
||||
const baseImageConfigTest: WorkspacekindsImageConfigValue = {
|
||||
id: 'image',
|
||||
displayName: 'Image',
|
||||
description: 'Test image',
|
||||
|
@ -40,7 +40,7 @@ const baseImageConfigTest: WorkspaceImageConfigValue = {
|
|||
clusterMetrics: undefined,
|
||||
};
|
||||
|
||||
const basePodConfigTest: WorkspacePodConfigValue = {
|
||||
const basePodConfigTest: WorkspacekindsPodConfigValue = {
|
||||
id: 'podConfig',
|
||||
displayName: 'Pod Config',
|
||||
description: 'Test pod config',
|
||||
|
@ -53,23 +53,34 @@ describe('useWorkspaceCountPerKind', () => {
|
|||
const mockListAllWorkspaces = jest.fn();
|
||||
const mockListWorkspaceKinds = jest.fn();
|
||||
|
||||
const mockApi: Partial<NotebookAPIs> = {
|
||||
listAllWorkspaces: mockListAllWorkspaces,
|
||||
listWorkspaceKinds: mockListWorkspaceKinds,
|
||||
const mockApi: Partial<NotebookApis> = {
|
||||
workspaces: {
|
||||
listAllWorkspaces: mockListAllWorkspaces,
|
||||
listWorkspacesByNamespace: jest.fn(),
|
||||
createWorkspace: jest.fn(),
|
||||
updateWorkspacePauseState: jest.fn(),
|
||||
getWorkspace: jest.fn(),
|
||||
deleteWorkspace: jest.fn(),
|
||||
},
|
||||
workspaceKinds: {
|
||||
listWorkspaceKinds: mockListWorkspaceKinds,
|
||||
createWorkspaceKind: jest.fn(),
|
||||
getWorkspaceKind: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseNotebookAPI.mockReturnValue({
|
||||
api: mockApi as NotebookAPIs,
|
||||
api: mockApi as NotebookApis,
|
||||
apiAvailable: true,
|
||||
refreshAllAPI: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty object initially', () => {
|
||||
mockListAllWorkspaces.mockResolvedValue([]);
|
||||
mockListWorkspaceKinds.mockResolvedValue([]);
|
||||
mockListAllWorkspaces.mockResolvedValue({ ok: true, data: [] });
|
||||
mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: [] });
|
||||
|
||||
const { result } = renderHook(() => useWorkspaceCountPerKind());
|
||||
|
||||
|
@ -79,7 +90,7 @@ describe('useWorkspaceCountPerKind', () => {
|
|||
});
|
||||
|
||||
it('should fetch and calculate workspace counts on mount', async () => {
|
||||
const mockWorkspaces: Workspace[] = [
|
||||
const mockWorkspaces: WorkspacesWorkspace[] = [
|
||||
{
|
||||
...baseWorkspaceTest,
|
||||
name: 'workspace1',
|
||||
|
@ -100,7 +111,7 @@ describe('useWorkspaceCountPerKind', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const mockWorkspaceKinds: WorkspaceKind[] = [
|
||||
const mockWorkspaceKinds: WorkspacekindsWorkspaceKind[] = [
|
||||
buildMockWorkspaceKind({
|
||||
name: 'jupyter1',
|
||||
clusterMetrics: { workspacesCount: 10 },
|
||||
|
@ -173,8 +184,8 @@ describe('useWorkspaceCountPerKind', () => {
|
|||
}),
|
||||
];
|
||||
|
||||
mockListAllWorkspaces.mockResolvedValue(mockWorkspaces);
|
||||
mockListWorkspaceKinds.mockResolvedValue(mockWorkspaceKinds);
|
||||
mockListAllWorkspaces.mockResolvedValue({ ok: true, data: mockWorkspaces });
|
||||
mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: mockWorkspaceKinds });
|
||||
|
||||
const { result } = renderHook(() => useWorkspaceCountPerKind());
|
||||
|
||||
|
@ -211,8 +222,8 @@ describe('useWorkspaceCountPerKind', () => {
|
|||
});
|
||||
|
||||
it('should handle missing cluster metrics gracefully', async () => {
|
||||
const mockEmptyWorkspaces: Workspace[] = [];
|
||||
const mockWorkspaceKinds: WorkspaceKind[] = [
|
||||
const mockEmptyWorkspaces: WorkspacesWorkspace[] = [];
|
||||
const mockWorkspaceKinds: WorkspacekindsWorkspaceKind[] = [
|
||||
buildMockWorkspaceKind({
|
||||
name: 'no-metrics',
|
||||
clusterMetrics: undefined,
|
||||
|
@ -251,8 +262,8 @@ describe('useWorkspaceCountPerKind', () => {
|
|||
}),
|
||||
];
|
||||
|
||||
mockListAllWorkspaces.mockResolvedValue(mockEmptyWorkspaces);
|
||||
mockListWorkspaceKinds.mockResolvedValue(mockWorkspaceKinds);
|
||||
mockListAllWorkspaces.mockResolvedValue({ ok: true, data: mockEmptyWorkspaces });
|
||||
mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: mockWorkspaceKinds });
|
||||
|
||||
const { result } = renderHook(() => useWorkspaceCountPerKind());
|
||||
|
||||
|
@ -290,7 +301,8 @@ describe('useWorkspaceCountPerKind', () => {
|
|||
});
|
||||
|
||||
it('should handle empty workspace kinds array', async () => {
|
||||
mockListWorkspaceKinds.mockResolvedValue([]);
|
||||
mockListAllWorkspaces.mockResolvedValue({ ok: true, data: [] });
|
||||
mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: [] });
|
||||
|
||||
const { result } = renderHook(() => useWorkspaceCountPerKind());
|
||||
|
||||
|
@ -300,7 +312,7 @@ describe('useWorkspaceCountPerKind', () => {
|
|||
});
|
||||
|
||||
it('should handle workspaces with no matching kinds', async () => {
|
||||
const mockWorkspaces: Workspace[] = [baseWorkspaceTest];
|
||||
const mockWorkspaces: WorkspacesWorkspace[] = [baseWorkspaceTest];
|
||||
const workspaceKind = buildMockWorkspaceKind({
|
||||
name: 'nomatch',
|
||||
clusterMetrics: { workspacesCount: 0 },
|
||||
|
@ -320,10 +332,10 @@ describe('useWorkspaceCountPerKind', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const mockWorkspaceKinds: WorkspaceKind[] = [workspaceKind];
|
||||
const mockWorkspaceKinds: WorkspacekindsWorkspaceKind[] = [workspaceKind];
|
||||
|
||||
mockListAllWorkspaces.mockResolvedValue(mockWorkspaces);
|
||||
mockListWorkspaceKinds.mockResolvedValue(mockWorkspaceKinds);
|
||||
mockListAllWorkspaces.mockResolvedValue({ ok: true, data: mockWorkspaces });
|
||||
mockListWorkspaceKinds.mockResolvedValue({ ok: true, data: mockWorkspaceKinds });
|
||||
|
||||
const { result } = renderHook(() => useWorkspaceCountPerKind());
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import { ApiNamespaceListEnvelope } from '~/generated/data-contracts';
|
||||
import useFetchState, {
|
||||
FetchState,
|
||||
FetchStateCallbackPromise,
|
||||
} from '~/shared/utilities/useFetchState';
|
||||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import { Namespace } from '~/shared/api/backendApiTypes';
|
||||
|
||||
const useNamespaces = (): FetchState<Namespace[] | null> => {
|
||||
const useNamespaces = (): FetchState<ApiNamespaceListEnvelope['data'] | null> => {
|
||||
const { api, apiAvailable } = useNotebookAPI();
|
||||
|
||||
const call = useCallback<FetchStateCallbackPromise<Namespace[] | null>>(
|
||||
(opts) => {
|
||||
if (!apiAvailable) {
|
||||
return Promise.reject(new Error('API not yet available'));
|
||||
}
|
||||
const call = useCallback<
|
||||
FetchStateCallbackPromise<ApiNamespaceListEnvelope['data'] | null>
|
||||
>(async () => {
|
||||
if (!apiAvailable) {
|
||||
throw new Error('API not yet available');
|
||||
}
|
||||
|
||||
return api.listNamespaces(opts);
|
||||
},
|
||||
[api, apiAvailable],
|
||||
);
|
||||
const envelope = await api.namespaces.listNamespaces();
|
||||
return envelope.data;
|
||||
}, [api, apiAvailable]);
|
||||
|
||||
return useFetchState(call, null);
|
||||
};
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import { Workspace, WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceCountPerOption } from '~/app/types';
|
||||
import { NotebookAPIs } from '~/shared/api/notebookApi';
|
||||
import { WorkspacekindsWorkspaceKind, WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
import { NotebookApis } from '~/shared/api/notebookApi';
|
||||
|
||||
export type WorkspaceCountPerKind = Record<WorkspaceKind['name'], WorkspaceCountPerOption>;
|
||||
export type WorkspaceCountPerKind = Record<
|
||||
WorkspacekindsWorkspaceKind['name'],
|
||||
WorkspaceCountPerOption
|
||||
>;
|
||||
|
||||
export const useWorkspaceCountPerKind = (): WorkspaceCountPerKind => {
|
||||
const { api } = useNotebookAPI();
|
||||
|
@ -27,18 +30,18 @@ export const useWorkspaceCountPerKind = (): WorkspaceCountPerKind => {
|
|||
return workspaceCountPerKind;
|
||||
};
|
||||
|
||||
async function loadWorkspaceCounts(api: NotebookAPIs): Promise<WorkspaceCountPerKind> {
|
||||
async function loadWorkspaceCounts(api: NotebookApis): Promise<WorkspaceCountPerKind> {
|
||||
const [workspaces, workspaceKinds] = await Promise.all([
|
||||
api.listAllWorkspaces({}),
|
||||
api.listWorkspaceKinds({}),
|
||||
api.workspaces.listAllWorkspaces({}),
|
||||
api.workspaceKinds.listWorkspaceKinds({}),
|
||||
]);
|
||||
|
||||
return extractCountPerKind({ workspaceKinds, workspaces });
|
||||
return extractCountPerKind({ workspaceKinds: workspaceKinds.data, workspaces: workspaces.data });
|
||||
}
|
||||
|
||||
function extractCountByNamespace(args: {
|
||||
kind: WorkspaceKind;
|
||||
workspaces: Workspace[];
|
||||
kind: WorkspacekindsWorkspaceKind;
|
||||
workspaces: WorkspacesWorkspace[];
|
||||
}): WorkspaceCountPerOption['countByNamespace'] {
|
||||
const { kind, workspaces } = args;
|
||||
return workspaces.reduce<WorkspaceCountPerOption['countByNamespace']>(
|
||||
|
@ -53,7 +56,7 @@ function extractCountByNamespace(args: {
|
|||
}
|
||||
|
||||
function extractCountByImage(
|
||||
workspaceKind: WorkspaceKind,
|
||||
workspaceKind: WorkspacekindsWorkspaceKind,
|
||||
): WorkspaceCountPerOption['countByImage'] {
|
||||
return workspaceKind.podTemplate.options.imageConfig.values.reduce<
|
||||
WorkspaceCountPerOption['countByImage']
|
||||
|
@ -64,7 +67,7 @@ function extractCountByImage(
|
|||
}
|
||||
|
||||
function extractCountByPodConfig(
|
||||
workspaceKind: WorkspaceKind,
|
||||
workspaceKind: WorkspacekindsWorkspaceKind,
|
||||
): WorkspaceCountPerOption['countByPodConfig'] {
|
||||
return workspaceKind.podTemplate.options.podConfig.values.reduce<
|
||||
WorkspaceCountPerOption['countByPodConfig']
|
||||
|
@ -74,13 +77,13 @@ function extractCountByPodConfig(
|
|||
}, {});
|
||||
}
|
||||
|
||||
function extractTotalCount(workspaceKind: WorkspaceKind): number {
|
||||
function extractTotalCount(workspaceKind: WorkspacekindsWorkspaceKind): number {
|
||||
return workspaceKind.clusterMetrics?.workspacesCount ?? 0;
|
||||
}
|
||||
|
||||
function extractCountPerKind(args: {
|
||||
workspaceKinds: WorkspaceKind[];
|
||||
workspaces: Workspace[];
|
||||
workspaceKinds: WorkspacekindsWorkspaceKind[];
|
||||
workspaces: WorkspacesWorkspace[];
|
||||
}): WorkspaceCountPerKind {
|
||||
const { workspaceKinds, workspaces } = args;
|
||||
|
||||
|
|
|
@ -26,48 +26,49 @@ const useWorkspaceFormData = (args: {
|
|||
const { namespace, workspaceName } = args;
|
||||
const { api, apiAvailable } = useNotebookAPI();
|
||||
|
||||
const call = useCallback<FetchStateCallbackPromise<WorkspaceFormData>>(
|
||||
async (opts) => {
|
||||
if (!apiAvailable) {
|
||||
throw new Error('API not yet available');
|
||||
}
|
||||
const call = useCallback<FetchStateCallbackPromise<WorkspaceFormData>>(async () => {
|
||||
if (!apiAvailable) {
|
||||
throw new Error('API not yet available');
|
||||
}
|
||||
|
||||
if (!namespace || !workspaceName) {
|
||||
return EMPTY_FORM_DATA;
|
||||
}
|
||||
if (!namespace || !workspaceName) {
|
||||
return EMPTY_FORM_DATA;
|
||||
}
|
||||
|
||||
const workspace = await api.getWorkspace(opts, namespace, workspaceName);
|
||||
const workspaceKind = await api.getWorkspaceKind(opts, workspace.workspaceKind.name);
|
||||
const imageConfig = workspace.podTemplate.options.imageConfig.current;
|
||||
const podConfig = workspace.podTemplate.options.podConfig.current;
|
||||
const workspaceEnvelope = await api.workspaces.getWorkspace(namespace, workspaceName);
|
||||
const workspace = workspaceEnvelope.data;
|
||||
const workspaceKindEnvelope = await api.workspaceKinds.getWorkspaceKind(
|
||||
workspace.workspaceKind.name,
|
||||
);
|
||||
const workspaceKind = workspaceKindEnvelope.data;
|
||||
const imageConfig = workspace.podTemplate.options.imageConfig.current;
|
||||
const podConfig = workspace.podTemplate.options.podConfig.current;
|
||||
|
||||
return {
|
||||
kind: workspaceKind,
|
||||
image: {
|
||||
id: imageConfig.id,
|
||||
displayName: imageConfig.displayName,
|
||||
description: imageConfig.description,
|
||||
hidden: false,
|
||||
labels: [],
|
||||
},
|
||||
podConfig: {
|
||||
id: podConfig.id,
|
||||
displayName: podConfig.displayName,
|
||||
description: podConfig.description,
|
||||
hidden: false,
|
||||
labels: [],
|
||||
},
|
||||
properties: {
|
||||
workspaceName: workspace.name,
|
||||
deferUpdates: workspace.deferUpdates,
|
||||
volumes: workspace.podTemplate.volumes.data.map((volume) => ({ ...volume })),
|
||||
secrets: workspace.podTemplate.volumes.secrets?.map((secret) => ({ ...secret })) ?? [],
|
||||
homeDirectory: workspace.podTemplate.volumes.home?.mountPath ?? '',
|
||||
},
|
||||
};
|
||||
},
|
||||
[api, apiAvailable, namespace, workspaceName],
|
||||
);
|
||||
return {
|
||||
kind: workspaceKind,
|
||||
image: {
|
||||
id: imageConfig.id,
|
||||
displayName: imageConfig.displayName,
|
||||
description: imageConfig.description,
|
||||
hidden: false,
|
||||
labels: [],
|
||||
},
|
||||
podConfig: {
|
||||
id: podConfig.id,
|
||||
displayName: podConfig.displayName,
|
||||
description: podConfig.description,
|
||||
hidden: false,
|
||||
labels: [],
|
||||
},
|
||||
properties: {
|
||||
workspaceName: workspace.name,
|
||||
deferUpdates: workspace.deferUpdates,
|
||||
volumes: workspace.podTemplate.volumes.data.map((volume) => ({ ...volume })),
|
||||
secrets: workspace.podTemplate.volumes.secrets?.map((secret) => ({ ...secret })) ?? [],
|
||||
homeDirectory: workspace.podTemplate.volumes.home?.mountPath ?? '',
|
||||
},
|
||||
};
|
||||
}, [api, apiAvailable, namespace, workspaceName]);
|
||||
|
||||
return useFetchState(call, EMPTY_FORM_DATA);
|
||||
};
|
||||
|
|
|
@ -4,21 +4,27 @@ import useFetchState, {
|
|||
FetchStateCallbackPromise,
|
||||
} from '~/shared/utilities/useFetchState';
|
||||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import { ApiWorkspaceKindEnvelope } from '~/generated/data-contracts';
|
||||
|
||||
const useWorkspaceKindByName = (kind: string): FetchState<WorkspaceKind | null> => {
|
||||
const useWorkspaceKindByName = (
|
||||
kind: string | undefined,
|
||||
): FetchState<ApiWorkspaceKindEnvelope['data'] | null> => {
|
||||
const { api, apiAvailable } = useNotebookAPI();
|
||||
|
||||
const call = useCallback<FetchStateCallbackPromise<WorkspaceKind | null>>(
|
||||
(opts) => {
|
||||
if (!apiAvailable) {
|
||||
return Promise.reject(new Error('API not yet available'));
|
||||
}
|
||||
const call = useCallback<
|
||||
FetchStateCallbackPromise<ApiWorkspaceKindEnvelope['data'] | null>
|
||||
>(async () => {
|
||||
if (!apiAvailable) {
|
||||
return Promise.reject(new Error('API not yet available'));
|
||||
}
|
||||
|
||||
return api.getWorkspaceKind(opts, kind);
|
||||
},
|
||||
[api, apiAvailable, kind],
|
||||
);
|
||||
if (!kind) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const envelope = await api.workspaceKinds.getWorkspaceKind(kind);
|
||||
return envelope.data;
|
||||
}, [api, apiAvailable, kind]);
|
||||
|
||||
return useFetchState(call, null);
|
||||
};
|
||||
|
|
|
@ -3,20 +3,23 @@ import useFetchState, {
|
|||
FetchState,
|
||||
FetchStateCallbackPromise,
|
||||
} from '~/shared/utilities/useFetchState';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import {
|
||||
ApiWorkspaceKindListEnvelope,
|
||||
WorkspacekindsWorkspaceKind,
|
||||
} from '~/generated/data-contracts';
|
||||
|
||||
const useWorkspaceKinds = (): FetchState<WorkspaceKind[]> => {
|
||||
const useWorkspaceKinds = (): FetchState<WorkspacekindsWorkspaceKind[]> => {
|
||||
const { api, apiAvailable } = useNotebookAPI();
|
||||
const call = useCallback<FetchStateCallbackPromise<WorkspaceKind[]>>(
|
||||
(opts) => {
|
||||
if (!apiAvailable) {
|
||||
return Promise.reject(new Error('API not yet available'));
|
||||
}
|
||||
return api.listWorkspaceKinds(opts);
|
||||
},
|
||||
[api, apiAvailable],
|
||||
);
|
||||
const call = useCallback<
|
||||
FetchStateCallbackPromise<ApiWorkspaceKindListEnvelope['data']>
|
||||
>(async () => {
|
||||
if (!apiAvailable) {
|
||||
return Promise.reject(new Error('API not yet available'));
|
||||
}
|
||||
const envelope = await api.workspaceKinds.listWorkspaceKinds();
|
||||
return envelope.data;
|
||||
}, [api, apiAvailable]);
|
||||
|
||||
return useFetchState(call, []);
|
||||
};
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import { useCallback } from 'react';
|
||||
import { IActions } from '@patternfly/react-table/dist/esm/components/Table';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { useWorkspaceActionsContext, WorkspaceAction } from '~/app/context/WorkspaceActionsContext';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
export type WorkspaceRowActionId = 'viewDetails' | 'edit' | 'delete' | 'start' | 'stop' | 'restart';
|
||||
|
||||
interface WorkspaceRowAction {
|
||||
id: WorkspaceRowActionId;
|
||||
onActionDone?: WorkspaceAction['onActionDone'];
|
||||
isVisible?: boolean | ((workspace: Workspace) => boolean);
|
||||
isVisible?: boolean | ((workspace: WorkspacesWorkspace) => boolean);
|
||||
}
|
||||
|
||||
type WorkspaceRowActionItem = WorkspaceRowAction | { id: 'separator' };
|
||||
|
||||
export const useWorkspaceRowActions = (
|
||||
actionsToInclude: WorkspaceRowActionItem[],
|
||||
): ((workspace: Workspace) => IActions) => {
|
||||
): ((workspace: WorkspacesWorkspace) => IActions) => {
|
||||
const actionsContext = useWorkspaceActionsContext();
|
||||
|
||||
return useCallback(
|
||||
(workspace: Workspace): IActions => {
|
||||
(workspace: WorkspacesWorkspace): IActions => {
|
||||
const actions: IActions = [];
|
||||
|
||||
for (const item of actionsToInclude) {
|
||||
|
@ -47,7 +47,7 @@ export const useWorkspaceRowActions = (
|
|||
function buildAction(
|
||||
id: WorkspaceRowActionId,
|
||||
onActionDone: WorkspaceAction['onActionDone'] | undefined,
|
||||
workspace: Workspace,
|
||||
workspace: WorkspacesWorkspace,
|
||||
actionsContext: ReturnType<typeof useWorkspaceActionsContext>,
|
||||
): IActions[number] {
|
||||
const map: Record<WorkspaceRowActionId, () => IActions[number]> = {
|
||||
|
|
|
@ -4,21 +4,23 @@ import useFetchState, {
|
|||
FetchStateCallbackPromise,
|
||||
} from '~/shared/utilities/useFetchState';
|
||||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { ApiWorkspaceListEnvelope } from '~/generated/data-contracts';
|
||||
|
||||
export const useWorkspacesByNamespace = (namespace: string): FetchState<Workspace[]> => {
|
||||
export const useWorkspacesByNamespace = (
|
||||
namespace: string,
|
||||
): FetchState<ApiWorkspaceListEnvelope['data']> => {
|
||||
const { api, apiAvailable } = useNotebookAPI();
|
||||
|
||||
const call = useCallback<FetchStateCallbackPromise<Workspace[]>>(
|
||||
(opts) => {
|
||||
if (!apiAvailable) {
|
||||
return Promise.reject(new Error('API not yet available'));
|
||||
}
|
||||
const call = useCallback<
|
||||
FetchStateCallbackPromise<ApiWorkspaceListEnvelope['data']>
|
||||
>(async () => {
|
||||
if (!apiAvailable) {
|
||||
return Promise.reject(new Error('API not yet available'));
|
||||
}
|
||||
|
||||
return api.listWorkspaces(opts, namespace);
|
||||
},
|
||||
[api, apiAvailable, namespace],
|
||||
);
|
||||
const envelope = await api.workspaces.listWorkspacesByNamespace(namespace);
|
||||
return envelope.data;
|
||||
}, [api, apiAvailable, namespace]);
|
||||
|
||||
return useFetchState(call, []);
|
||||
};
|
||||
|
@ -28,34 +30,33 @@ export const useWorkspacesByKind = (args: {
|
|||
namespace?: string;
|
||||
imageId?: string;
|
||||
podConfigId?: string;
|
||||
}): FetchState<Workspace[]> => {
|
||||
}): FetchState<ApiWorkspaceListEnvelope['data']> => {
|
||||
const { kind, namespace, imageId, podConfigId } = args;
|
||||
const { api, apiAvailable } = useNotebookAPI();
|
||||
const call = useCallback<FetchStateCallbackPromise<Workspace[]>>(
|
||||
async (opts) => {
|
||||
if (!apiAvailable) {
|
||||
throw new Error('API not yet available');
|
||||
}
|
||||
if (!kind) {
|
||||
throw new Error('Workspace kind is required');
|
||||
}
|
||||
const call = useCallback<
|
||||
FetchStateCallbackPromise<ApiWorkspaceListEnvelope['data']>
|
||||
>(async () => {
|
||||
if (!apiAvailable) {
|
||||
throw new Error('API not yet available');
|
||||
}
|
||||
if (!kind) {
|
||||
throw new Error('Workspace kind is required');
|
||||
}
|
||||
|
||||
const workspaces = await api.listAllWorkspaces(opts);
|
||||
const envelope = await api.workspaces.listAllWorkspaces();
|
||||
|
||||
return workspaces.filter((workspace) => {
|
||||
const matchesKind = workspace.workspaceKind.name === kind;
|
||||
const matchesNamespace = namespace ? workspace.namespace === namespace : true;
|
||||
const matchesImage = imageId
|
||||
? workspace.podTemplate.options.imageConfig.current.id === imageId
|
||||
: true;
|
||||
const matchesPodConfig = podConfigId
|
||||
? workspace.podTemplate.options.podConfig.current.id === podConfigId
|
||||
: true;
|
||||
return envelope.data.filter((workspace) => {
|
||||
const matchesKind = workspace.workspaceKind.name === kind;
|
||||
const matchesNamespace = namespace ? workspace.namespace === namespace : true;
|
||||
const matchesImage = imageId
|
||||
? workspace.podTemplate.options.imageConfig.current.id === imageId
|
||||
: true;
|
||||
const matchesPodConfig = podConfigId
|
||||
? workspace.podTemplate.options.podConfig.current.id === podConfigId
|
||||
: true;
|
||||
|
||||
return matchesKind && matchesNamespace && matchesImage && matchesPodConfig;
|
||||
});
|
||||
},
|
||||
[apiAvailable, api, kind, namespace, imageId, podConfigId],
|
||||
);
|
||||
return matchesKind && matchesNamespace && matchesImage && matchesPodConfig;
|
||||
});
|
||||
}, [apiAvailable, api, kind, namespace, imageId, podConfigId]);
|
||||
return useFetchState(call, []);
|
||||
};
|
||||
|
|
|
@ -11,12 +11,12 @@ import inlineEditStyles from '@patternfly/react-styles/css/components/InlineEdit
|
|||
import { css } from '@patternfly/react-styles';
|
||||
import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
|
||||
import { TrashAltIcon } from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon';
|
||||
import { WorkspaceOptionLabel } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacekindsOptionLabel } from '~/generated/data-contracts';
|
||||
|
||||
interface EditableRowInterface {
|
||||
data: WorkspaceOptionLabel;
|
||||
columnNames: ColumnNames<WorkspaceOptionLabel>;
|
||||
saveChanges: (editedData: WorkspaceOptionLabel) => void;
|
||||
data: WorkspacekindsOptionLabel;
|
||||
columnNames: ColumnNames<WorkspacekindsOptionLabel>;
|
||||
saveChanges: (editedData: WorkspacekindsOptionLabel) => void;
|
||||
ariaLabel: string;
|
||||
deleteRow: () => void;
|
||||
}
|
||||
|
@ -70,8 +70,8 @@ const EditableRow: React.FC<EditableRowInterface> = ({
|
|||
type ColumnNames<T> = { [K in keyof T]: string };
|
||||
|
||||
interface EditableLabelsProps {
|
||||
rows: WorkspaceOptionLabel[];
|
||||
setRows: (value: WorkspaceOptionLabel[]) => void;
|
||||
rows: WorkspacekindsOptionLabel[];
|
||||
setRows: (value: WorkspacekindsOptionLabel[]) => void;
|
||||
title?: string;
|
||||
description?: string;
|
||||
buttonLabel?: string;
|
||||
|
@ -84,7 +84,7 @@ export const EditableLabels: React.FC<EditableLabelsProps> = ({
|
|||
description,
|
||||
buttonLabel = 'Label',
|
||||
}) => {
|
||||
const columnNames: ColumnNames<WorkspaceOptionLabel> = {
|
||||
const columnNames: ColumnNames<WorkspacekindsOptionLabel> = {
|
||||
key: 'Key',
|
||||
value: 'Value',
|
||||
};
|
||||
|
|
|
@ -9,13 +9,14 @@ import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/ex
|
|||
import { EmptyState, EmptyStateBody } from '@patternfly/react-core/dist/esm/components/EmptyState';
|
||||
import { ValidationErrorAlert } from '~/app/components/ValidationErrorAlert';
|
||||
import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName';
|
||||
import { WorkspaceKind, ValidationError } from '~/shared/api/backendApiTypes';
|
||||
import { useTypedNavigate, useTypedParams } from '~/app/routerHelper';
|
||||
import { useCurrentRouteKey } from '~/app/hooks/useCurrentRouteKey';
|
||||
import useGenericObjectState from '~/app/hooks/useGenericObjectState';
|
||||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import { WorkspaceKindFormData } from '~/app/types';
|
||||
import { ErrorEnvelopeException } from '~/shared/api/apiUtils';
|
||||
import { safeApiCall } from '~/shared/api/apiUtils';
|
||||
import { CONTENT_TYPE_KEY, ContentType } from '~/shared/utilities/const';
|
||||
import { ApiValidationError, WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload';
|
||||
import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties';
|
||||
import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage';
|
||||
|
@ -31,7 +32,7 @@ export enum WorkspaceKindFormView {
|
|||
export type ValidationStatus = 'success' | 'error' | 'default';
|
||||
export type FormMode = 'edit' | 'create';
|
||||
|
||||
const convertToFormData = (initialData: WorkspaceKind): WorkspaceKindFormData => {
|
||||
const convertToFormData = (initialData: WorkspacekindsWorkspaceKind): WorkspaceKindFormData => {
|
||||
const { podTemplate, ...properties } = initialData;
|
||||
const { options, ...spec } = podTemplate;
|
||||
const { podConfig, imageConfig } = options;
|
||||
|
@ -51,11 +52,12 @@ export const WorkspaceKindForm: React.FC = () => {
|
|||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [validated, setValidated] = useState<ValidationStatus>('default');
|
||||
const mode: FormMode = useCurrentRouteKey() === 'workspaceKindCreate' ? 'create' : 'edit';
|
||||
const [specErrors, setSpecErrors] = useState<(ValidationError | ErrorEnvelopeException)[]>([]);
|
||||
const [specErrors, setSpecErrors] = useState<ApiValidationError[]>([]);
|
||||
|
||||
const { kind } = useTypedParams<'workspaceKindEdit'>();
|
||||
const [initialFormData, initialFormDataLoaded, initialFormDataError] =
|
||||
useWorkspaceKindByName(kind);
|
||||
const routeParams = useTypedParams<'workspaceKindEdit' | 'workspaceKindCreate'>();
|
||||
const [initialFormData, initialFormDataLoaded, initialFormDataError] = useWorkspaceKindByName(
|
||||
routeParams?.kind,
|
||||
);
|
||||
|
||||
const [data, setData, resetData, replaceData] = useGenericObjectState<WorkspaceKindFormData>(
|
||||
initialFormData ? convertToFormData(initialFormData) : EMPTY_WORKSPACE_KIND_FORM_DATA,
|
||||
|
@ -73,32 +75,45 @@ export const WorkspaceKindForm: React.FC = () => {
|
|||
// TODO: Complete handleCreate with API call to create a new WS kind
|
||||
try {
|
||||
if (mode === 'create') {
|
||||
const newWorkspaceKind = await api.createWorkspaceKind({ directYAML: true }, yamlValue);
|
||||
// TODO: alert user about success
|
||||
console.info('New workspace kind created:', JSON.stringify(newWorkspaceKind));
|
||||
navigate('workspaceKinds');
|
||||
const createResult = await safeApiCall(() =>
|
||||
api.workspaceKinds.createWorkspaceKind(yamlValue, {
|
||||
headers: {
|
||||
[CONTENT_TYPE_KEY]: ContentType.YAML,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (createResult.ok) {
|
||||
// TODO: alert user about success
|
||||
console.info('New workspace kind created:', JSON.stringify(createResult.data));
|
||||
navigate('workspaceKinds');
|
||||
} else {
|
||||
const validationErrors = createResult.errorEnvelope.error.cause?.validation_errors;
|
||||
if (validationErrors && validationErrors.length > 0) {
|
||||
setSpecErrors((prev) => [...prev, ...validationErrors]);
|
||||
setValidated('error');
|
||||
return;
|
||||
}
|
||||
// TODO: alert user about generic error with no validation errors
|
||||
setValidated('error');
|
||||
console.error(
|
||||
`Error while creating workspace kind: ${JSON.stringify(createResult.errorEnvelope)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: Finish when WSKind API is finalized
|
||||
// const updatedWorkspace = await api.updateWorkspaceKind({}, kind, { data: {} });
|
||||
// console.info('Workspace Kind updated:', JSON.stringify(updatedWorkspace));
|
||||
// navigate('workspaceKinds');
|
||||
} catch (err) {
|
||||
if (err instanceof ErrorEnvelopeException) {
|
||||
const validationErrors = err.envelope.error?.cause?.validation_errors;
|
||||
if (validationErrors && validationErrors.length > 0) {
|
||||
setSpecErrors((prev) => [...prev, ...validationErrors]);
|
||||
setValidated('error');
|
||||
return;
|
||||
}
|
||||
setSpecErrors((prev) => [...prev, err]);
|
||||
setValidated('error');
|
||||
}
|
||||
// TODO: alert user about error
|
||||
console.error(`Error ${mode === 'edit' ? 'editing' : 'creating'} workspace kind: ${err}`);
|
||||
// TODO: alert user about unexpected error
|
||||
console.error(
|
||||
`Unexpected error while ${mode === 'edit' ? 'editing' : 'creating'} workspace kind: ${err}`,
|
||||
);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}, [navigate, mode, api, yamlValue]);
|
||||
}, [api, mode, navigate, yamlValue]);
|
||||
|
||||
const canSubmit = useMemo(
|
||||
() => !isSubmitting && validated === 'success',
|
||||
|
|
|
@ -11,12 +11,11 @@ import {
|
|||
import { Radio } from '@patternfly/react-core/dist/esm/components/Radio';
|
||||
import { Dropdown, DropdownItem } from '@patternfly/react-core/dist/esm/components/Dropdown';
|
||||
import { EllipsisVIcon } from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
|
||||
|
||||
import { WorkspaceKindImageConfigValue } from '~/app/types';
|
||||
import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts';
|
||||
|
||||
interface PaginatedTableProps {
|
||||
rows: WorkspaceKindImageConfigValue[] | WorkspacePodConfigValue[];
|
||||
rows: WorkspaceKindImageConfigValue[] | WorkspacekindsPodConfigValue[];
|
||||
defaultId: string;
|
||||
setDefaultId: (id: string) => void;
|
||||
handleEdit: (index: number) => void;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { ImagePullPolicy, WorkspaceKindImagePort, WorkspaceKindPodConfigValue } from '~/app/types';
|
||||
import { WorkspaceOptionLabel, WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';
|
||||
import {
|
||||
WorkspacekindsOptionLabel,
|
||||
WorkspacekindsPodConfigValue,
|
||||
} from '~/generated/data-contracts';
|
||||
import { PodResourceEntry } from './podConfig/WorkspaceKindFormResource';
|
||||
|
||||
// Simple ID generator to avoid PatternFly dependency in tests
|
||||
|
@ -79,7 +82,7 @@ export const emptyImage = {
|
|||
description: '',
|
||||
hidden: false,
|
||||
imagePullPolicy: ImagePullPolicy.IfNotPresent,
|
||||
labels: [] as WorkspaceOptionLabel[],
|
||||
labels: [] as WorkspacekindsOptionLabel[],
|
||||
image: '',
|
||||
ports: [
|
||||
{
|
||||
|
@ -94,7 +97,7 @@ export const emptyImage = {
|
|||
},
|
||||
};
|
||||
|
||||
export const emptyPodConfig: WorkspacePodConfigValue = {
|
||||
export const emptyPodConfig: WorkspacekindsPodConfigValue = {
|
||||
id: '',
|
||||
displayName: '',
|
||||
description: '',
|
||||
|
|
|
@ -10,13 +10,13 @@ import {
|
|||
} from '@patternfly/react-core/dist/esm/components/FormSelect';
|
||||
import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput';
|
||||
import {
|
||||
WorkspaceOptionRedirect,
|
||||
WorkspaceRedirectMessageLevel,
|
||||
} from '~/shared/api/backendApiTypes';
|
||||
WorkspacekindsOptionRedirect,
|
||||
WorkspacekindsRedirectMessageLevel,
|
||||
} from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspaceKindFormImageRedirectProps {
|
||||
redirect: WorkspaceOptionRedirect;
|
||||
setRedirect: (obj: WorkspaceOptionRedirect) => void;
|
||||
redirect: WorkspacekindsOptionRedirect;
|
||||
setRedirect: (obj: WorkspacekindsOptionRedirect) => void;
|
||||
}
|
||||
|
||||
export const WorkspaceKindFormImageRedirect: React.FC<WorkspaceKindFormImageRedirectProps> = ({
|
||||
|
@ -26,18 +26,18 @@ export const WorkspaceKindFormImageRedirect: React.FC<WorkspaceKindFormImageRedi
|
|||
const redirectMsgOptions = [
|
||||
{ value: 'please choose', label: 'Select one', disabled: true },
|
||||
{
|
||||
value: WorkspaceRedirectMessageLevel.RedirectMessageLevelInfo,
|
||||
label: WorkspaceRedirectMessageLevel.RedirectMessageLevelInfo,
|
||||
value: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelInfo,
|
||||
label: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelInfo,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
value: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
label: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
value: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
label: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
value: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger,
|
||||
label: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger,
|
||||
value: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelDanger,
|
||||
label: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelDanger,
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
|
@ -71,7 +71,7 @@ export const WorkspaceKindFormImageRedirect: React.FC<WorkspaceKindFormImageRedi
|
|||
...redirect,
|
||||
message: {
|
||||
text: redirect.message?.level || '',
|
||||
level: val as WorkspaceRedirectMessageLevel,
|
||||
level: val as WorkspacekindsRedirectMessageLevel,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -100,7 +100,8 @@ export const WorkspaceKindFormImageRedirect: React.FC<WorkspaceKindFormImageRedi
|
|||
...redirect,
|
||||
message: {
|
||||
level:
|
||||
redirect.message?.level || WorkspaceRedirectMessageLevel.RedirectMessageLevelInfo,
|
||||
redirect.message?.level ||
|
||||
WorkspacekindsRedirectMessageLevel.RedirectMessageLevelInfo,
|
||||
text: val,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -11,9 +11,9 @@ import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput'
|
|||
import { Switch } from '@patternfly/react-core/dist/esm/components/Switch';
|
||||
import { HelperText } from '@patternfly/react-core/dist/esm/components/HelperText';
|
||||
import { WorkspaceKindPodConfigValue } from '~/app/types';
|
||||
import { WorkspaceOptionLabel } from '~/shared/api/backendApiTypes';
|
||||
import { EditableLabels } from '~/app/pages/WorkspaceKinds/Form/EditableLabels';
|
||||
import { getResources } from '~/app/pages/WorkspaceKinds/Form/helpers';
|
||||
import { WorkspacekindsOptionLabel } from '~/generated/data-contracts';
|
||||
import { WorkspaceKindFormResource, PodResourceEntry } from './WorkspaceKindFormResource';
|
||||
|
||||
interface WorkspaceKindFormPodConfigModalProps {
|
||||
|
@ -36,7 +36,7 @@ export const WorkspaceKindFormPodConfigModal: React.FC<WorkspaceKindFormPodConfi
|
|||
const initialResources = useMemo(() => getResources(currConfig), [currConfig]);
|
||||
|
||||
const [resources, setResources] = useState<PodResourceEntry[]>(initialResources);
|
||||
const [labels, setLabels] = useState<WorkspaceOptionLabel[]>(currConfig.labels);
|
||||
const [labels, setLabels] = useState<WorkspacekindsOptionLabel[]>(currConfig.labels);
|
||||
const [id, setId] = useState(currConfig.id);
|
||||
const [displayName, setDisplayName] = useState(currConfig.displayName);
|
||||
const [description, setDescription] = useState(currConfig.description);
|
||||
|
|
|
@ -10,9 +10,9 @@ import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/comp
|
|||
import { Switch } from '@patternfly/react-core/dist/esm/components/Switch';
|
||||
import { WorkspaceKindPodTemplateData } from '~/app/types';
|
||||
import { EditableLabels } from '~/app/pages/WorkspaceKinds/Form/EditableLabels';
|
||||
import { WorkspacePodVolumeMount } from '~/shared/api/backendApiTypes';
|
||||
import { ResourceInputWrapper } from '~/app/pages/WorkspaceKinds/Form/podConfig/ResourceInputWrapper';
|
||||
import { WorkspaceFormPropertiesVolumes } from '~/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes';
|
||||
import { WorkspacesPodVolumeMount } from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspaceKindFormPodTemplateProps {
|
||||
podTemplate: WorkspaceKindPodTemplateData;
|
||||
|
@ -24,7 +24,7 @@ export const WorkspaceKindFormPodTemplate: React.FC<WorkspaceKindFormPodTemplate
|
|||
updatePodTemplate,
|
||||
}) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [volumes, setVolumes] = useState<WorkspacePodVolumeMount[]>([]);
|
||||
const [volumes, setVolumes] = useState<WorkspacesPodVolumeMount[]>([]);
|
||||
|
||||
const toggleCullingEnabled = useCallback(
|
||||
(checked: boolean) => {
|
||||
|
@ -42,7 +42,7 @@ export const WorkspaceKindFormPodTemplate: React.FC<WorkspaceKindFormPodTemplate
|
|||
);
|
||||
|
||||
const handleVolumes = useCallback(
|
||||
(newVolumes: WorkspacePodVolumeMount[]) => {
|
||||
(newVolumes: WorkspacesPodVolumeMount[]) => {
|
||||
setVolumes(newVolumes);
|
||||
updatePodTemplate({
|
||||
...podTemplate,
|
||||
|
|
|
@ -36,7 +36,6 @@ import {
|
|||
IActions,
|
||||
} from '@patternfly/react-table/dist/esm/components/Table';
|
||||
import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
|
||||
import { useWorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind';
|
||||
import { WorkspaceKindsColumns } from '~/app/types';
|
||||
|
@ -45,6 +44,7 @@ import CustomEmptyState from '~/shared/components/CustomEmptyState';
|
|||
import WithValidImage from '~/shared/components/WithValidImage';
|
||||
import ImageFallback from '~/shared/components/ImageFallback';
|
||||
import { useTypedNavigate } from '~/app/routerHelper';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
import { WorkspaceKindDetails } from './details/WorkspaceKindDetails';
|
||||
|
||||
export enum ActionType {
|
||||
|
@ -74,7 +74,8 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
|||
}, [navigate]);
|
||||
const [workspaceKinds, workspaceKindsLoaded, workspaceKindsError] = useWorkspaceKinds();
|
||||
const workspaceCountPerKind = useWorkspaceCountPerKind();
|
||||
const [selectedWorkspaceKind, setSelectedWorkspaceKind] = useState<WorkspaceKind | null>(null);
|
||||
const [selectedWorkspaceKind, setSelectedWorkspaceKind] =
|
||||
useState<WorkspacekindsWorkspaceKind | null>(null);
|
||||
const [activeActionType, setActiveActionType] = useState<ActionType | null>(null);
|
||||
|
||||
// Column sorting
|
||||
|
@ -82,7 +83,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
|||
const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc' | null>(null);
|
||||
|
||||
const getSortableRowValues = useCallback(
|
||||
(workspaceKind: WorkspaceKind): (string | boolean | number)[] => {
|
||||
(workspaceKind: WorkspacekindsWorkspaceKind): (string | boolean | number)[] => {
|
||||
const {
|
||||
icon,
|
||||
name,
|
||||
|
@ -151,7 +152,7 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
|||
}, []);
|
||||
|
||||
const onFilter = useCallback(
|
||||
(workspaceKind: WorkspaceKind) => {
|
||||
(workspaceKind: WorkspacekindsWorkspaceKind) => {
|
||||
let nameRegex: RegExp;
|
||||
let descriptionRegex: RegExp;
|
||||
|
||||
|
@ -275,13 +276,13 @@ export const WorkspaceKinds: React.FunctionComponent = () => {
|
|||
|
||||
// Actions
|
||||
|
||||
const viewDetailsClick = useCallback((workspaceKind: WorkspaceKind) => {
|
||||
const viewDetailsClick = useCallback((workspaceKind: WorkspacekindsWorkspaceKind) => {
|
||||
setSelectedWorkspaceKind(workspaceKind);
|
||||
setActiveActionType(ActionType.ViewDetails);
|
||||
}, []);
|
||||
|
||||
const workspaceKindsDefaultActions = useCallback(
|
||||
(workspaceKind: WorkspaceKind): IActions => [
|
||||
(workspaceKind: WorkspacekindsWorkspaceKind): IActions => [
|
||||
{
|
||||
id: 'view-details',
|
||||
title: 'View Details',
|
||||
|
|
|
@ -14,15 +14,15 @@ import {
|
|||
TabContent,
|
||||
} from '@patternfly/react-core/dist/esm/components/Tabs';
|
||||
import { Title } from '@patternfly/react-core/dist/esm/components/Title';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind';
|
||||
import { WorkspaceKindDetailsNamespaces } from '~/app/pages/WorkspaceKinds/details/WorkspaceKindDetailsNamespaces';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
import { WorkspaceKindDetailsOverview } from './WorkspaceKindDetailsOverview';
|
||||
import { WorkspaceKindDetailsImages } from './WorkspaceKindDetailsImages';
|
||||
import { WorkspaceKindDetailsPodConfigs } from './WorkspaceKindDetailsPodConfigs';
|
||||
|
||||
type WorkspaceKindDetailsProps = {
|
||||
workspaceKind: WorkspaceKind;
|
||||
workspaceKind: WorkspacekindsWorkspaceKind;
|
||||
workspaceCountPerKind: WorkspaceCountPerKind;
|
||||
onCloseClick: React.MouseEventHandler;
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
import { WorkspaceKindDetailsTable } from './WorkspaceKindDetailsTable';
|
||||
|
||||
type WorkspaceDetailsImagesProps = {
|
||||
workspaceKind: WorkspaceKind;
|
||||
workspaceKind: WorkspacekindsWorkspaceKind;
|
||||
workspaceCountPerKind: WorkspaceCountPerKind;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
import { WorkspaceKindDetailsTable } from './WorkspaceKindDetailsTable';
|
||||
|
||||
type WorkspaceDetailsNamespacesProps = {
|
||||
workspaceKind: WorkspaceKind;
|
||||
workspaceKind: WorkspacekindsWorkspaceKind;
|
||||
workspaceCountPerKind: WorkspaceCountPerKind;
|
||||
};
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@ import {
|
|||
DescriptionListDescription,
|
||||
} from '@patternfly/react-core/dist/esm/components/DescriptionList';
|
||||
import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import ImageFallback from '~/shared/components/ImageFallback';
|
||||
import WithValidImage from '~/shared/components/WithValidImage';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
|
||||
type WorkspaceDetailsOverviewProps = {
|
||||
workspaceKind: WorkspaceKind;
|
||||
workspaceKind: WorkspacekindsWorkspaceKind;
|
||||
};
|
||||
|
||||
export const WorkspaceKindDetailsOverview: React.FunctionComponent<
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceCountPerKind } from '~/app/hooks/useWorkspaceCountPerKind';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
import { WorkspaceKindDetailsTable } from './WorkspaceKindDetailsTable';
|
||||
|
||||
type WorkspaceDetailsPodConfigsProps = {
|
||||
workspaceKind: WorkspaceKind;
|
||||
workspaceKind: WorkspacekindsWorkspaceKind;
|
||||
workspaceCountPerKind: WorkspaceCountPerKind;
|
||||
};
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
|
|||
import { Flex, FlexItem } from '@patternfly/react-core/dist/esm/layouts/Flex';
|
||||
import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack';
|
||||
import { t_global_spacer_md as MediumPadding } from '@patternfly/react-tokens';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import {
|
||||
countGpusFromWorkspaces,
|
||||
filterIdleWorkspacesWithGpu,
|
||||
|
@ -20,11 +19,12 @@ import {
|
|||
groupWorkspacesByNamespaceAndGpu,
|
||||
YesNoValue,
|
||||
} from '~/shared/utilities/WorkspaceUtils';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
const TOP_GPU_CONSUMERS_LIMIT = 2;
|
||||
|
||||
interface WorkspaceKindSummaryExpandableCardProps {
|
||||
workspaces: Workspace[];
|
||||
workspaces: WorkspacesWorkspace[];
|
||||
isExpanded: boolean;
|
||||
onExpandToggle: () => void;
|
||||
onAddFilter: (columnKey: string, value: string) => void;
|
||||
|
|
|
@ -15,10 +15,10 @@ import { List, ListItem } from '@patternfly/react-core/dist/esm/components/List'
|
|||
import { Tooltip } from '@patternfly/react-core/dist/esm/components/Tooltip';
|
||||
import { DatabaseIcon } from '@patternfly/react-icons/dist/esm/icons/database-icon';
|
||||
import { LockedIcon } from '@patternfly/react-icons/dist/esm/icons/locked-icon';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
interface DataVolumesListProps {
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
}
|
||||
|
||||
export const DataVolumesList: React.FC<DataVolumesListProps> = ({ workspace }) => {
|
||||
|
|
|
@ -14,14 +14,14 @@ import {
|
|||
TabContent,
|
||||
} from '@patternfly/react-core/dist/esm/components/Tabs';
|
||||
import { Title } from '@patternfly/react-core/dist/esm/components/Title';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceDetailsOverview } from '~/app/pages/Workspaces/Details/WorkspaceDetailsOverview';
|
||||
import { WorkspaceDetailsActions } from '~/app/pages/Workspaces/Details/WorkspaceDetailsActions';
|
||||
import { WorkspaceDetailsActivity } from '~/app/pages/Workspaces/Details/WorkspaceDetailsActivity';
|
||||
import { WorkspaceDetailsPodTemplate } from '~/app/pages/Workspaces/Details/WorkspaceDetailsPodTemplate';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
type WorkspaceDetailsProps = {
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
onCloseClick: React.MouseEventHandler;
|
||||
// TODO: Uncomment when edit action is fully supported
|
||||
// onEditClick: React.MouseEventHandler;
|
||||
|
|
|
@ -7,12 +7,12 @@ import {
|
|||
DescriptionListDescription,
|
||||
} from '@patternfly/react-core/dist/esm/components/DescriptionList';
|
||||
import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
const DATE_FORMAT = 'PPpp';
|
||||
|
||||
type WorkspaceDetailsActivityProps = {
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
};
|
||||
|
||||
export const WorkspaceDetailsActivity: React.FunctionComponent<WorkspaceDetailsActivityProps> = ({
|
||||
|
|
|
@ -6,10 +6,10 @@ import {
|
|||
DescriptionListDescription,
|
||||
} from '@patternfly/react-core/dist/esm/components/DescriptionList';
|
||||
import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
type WorkspaceDetailsOverviewProps = {
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
};
|
||||
|
||||
export const WorkspaceDetailsOverview: React.FunctionComponent<WorkspaceDetailsOverviewProps> = ({
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import React from 'react';
|
||||
import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table/dist/esm/components/Table';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceTableColumnKeys } from '~/app/components/WorkspaceTable';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
import { WorkspaceStorage } from './WorkspaceStorage';
|
||||
import { WorkspacePackageDetails } from './WorkspacePackageDetails';
|
||||
import { WorkspaceConfigDetails } from './WorkspaceConfigDetails';
|
||||
|
||||
interface ExpandedWorkspaceRowProps {
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
visibleColumnKeys: WorkspaceTableColumnKeys[];
|
||||
canExpandRows: boolean;
|
||||
}
|
||||
|
|
|
@ -26,14 +26,14 @@ import { WorkspaceFormKindSelection } from '~/app/pages/Workspaces/Form/kind/Wor
|
|||
import { WorkspaceFormPodConfigSelection } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigSelection';
|
||||
import { WorkspaceFormPropertiesSelection } from '~/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesSelection';
|
||||
import { WorkspaceFormData } from '~/app/types';
|
||||
import {
|
||||
WorkspaceCreate,
|
||||
WorkspaceKind,
|
||||
WorkspaceImageConfigValue,
|
||||
WorkspacePodConfigValue,
|
||||
} from '~/shared/api/backendApiTypes';
|
||||
import useWorkspaceFormData from '~/app/hooks/useWorkspaceFormData';
|
||||
import { useTypedNavigate } from '~/app/routerHelper';
|
||||
import {
|
||||
WorkspacekindsImageConfigValue,
|
||||
WorkspacekindsPodConfigValue,
|
||||
WorkspacekindsWorkspaceKind,
|
||||
WorkspacesWorkspaceCreate,
|
||||
} from '~/generated/data-contracts';
|
||||
import { useWorkspaceFormLocationData } from '~/app/hooks/useWorkspaceFormLocationData';
|
||||
import { WorkspaceFormKindDetails } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindDetails';
|
||||
import { WorkspaceFormImageDetails } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails';
|
||||
|
@ -151,7 +151,7 @@ const WorkspaceForm: React.FC = () => {
|
|||
}
|
||||
|
||||
// TODO: Prepare WorkspaceUpdate data accordingly when BE supports it
|
||||
const submitData: WorkspaceCreate = {
|
||||
const submitData: WorkspacesWorkspaceCreate = {
|
||||
name: data.properties.workspaceName,
|
||||
kind: data.kind.name,
|
||||
deferUpdates: data.properties.deferUpdates,
|
||||
|
@ -177,15 +177,13 @@ const WorkspaceForm: React.FC = () => {
|
|||
|
||||
try {
|
||||
if (mode === 'edit') {
|
||||
const updateWorkspace = await api.updateWorkspace({}, submitData.name, namespace, {
|
||||
// TODO: call api to update workspace when implemented in backend
|
||||
} else {
|
||||
const workspaceEnvelope = await api.workspaces.createWorkspace(namespace, {
|
||||
data: submitData,
|
||||
});
|
||||
// TODO: alert user about success
|
||||
console.info('Workspace updated:', JSON.stringify(updateWorkspace));
|
||||
} else {
|
||||
const newWorkspace = await api.createWorkspace({}, namespace, { data: submitData });
|
||||
// TODO: alert user about success
|
||||
console.info('New workspace created:', JSON.stringify(newWorkspace));
|
||||
console.info('New workspace created:', JSON.stringify(workspaceEnvelope.data));
|
||||
}
|
||||
|
||||
navigate('workspaces');
|
||||
|
@ -202,7 +200,7 @@ const WorkspaceForm: React.FC = () => {
|
|||
}, [navigate]);
|
||||
|
||||
const handleKindSelect = useCallback(
|
||||
(kind: WorkspaceKind | undefined) => {
|
||||
(kind: WorkspacekindsWorkspaceKind | undefined) => {
|
||||
if (kind) {
|
||||
resetData();
|
||||
setData('kind', kind);
|
||||
|
@ -213,7 +211,7 @@ const WorkspaceForm: React.FC = () => {
|
|||
);
|
||||
|
||||
const handleImageSelect = useCallback(
|
||||
(image: WorkspaceImageConfigValue | undefined) => {
|
||||
(image: WorkspacekindsImageConfigValue | undefined) => {
|
||||
if (image) {
|
||||
setData('image', image);
|
||||
setDrawerExpanded(true);
|
||||
|
@ -223,7 +221,7 @@ const WorkspaceForm: React.FC = () => {
|
|||
);
|
||||
|
||||
const handlePodConfigSelect = useCallback(
|
||||
(podConfig: WorkspacePodConfigValue | undefined) => {
|
||||
(podConfig: WorkspacekindsPodConfigValue | undefined) => {
|
||||
if (podConfig) {
|
||||
setData('podConfig', podConfig);
|
||||
setDrawerExpanded(true);
|
||||
|
|
|
@ -6,11 +6,11 @@ import {
|
|||
DescriptionListDescription,
|
||||
} from '@patternfly/react-core/dist/esm/components/DescriptionList';
|
||||
import { Title } from '@patternfly/react-core/dist/esm/components/Title';
|
||||
import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';
|
||||
import { formatLabelKey } from '~/shared/utilities/WorkspaceUtils';
|
||||
import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts';
|
||||
|
||||
type WorkspaceFormImageDetailsProps = {
|
||||
workspaceImage?: WorkspacePodConfigValue;
|
||||
workspaceImage?: WorkspacekindsPodConfigValue;
|
||||
};
|
||||
|
||||
export const WorkspaceFormImageDetails: React.FunctionComponent<WorkspaceFormImageDetailsProps> = ({
|
||||
|
|
|
@ -9,9 +9,9 @@ import { Gallery } from '@patternfly/react-core/dist/esm/layouts/Gallery';
|
|||
import { PageSection } from '@patternfly/react-core/dist/esm/components/Page';
|
||||
import { Toolbar, ToolbarContent } from '@patternfly/react-core/dist/esm/components/Toolbar';
|
||||
import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter';
|
||||
import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes';
|
||||
import CustomEmptyState from '~/shared/components/CustomEmptyState';
|
||||
import { defineDataFields, FilterableDataFieldKey } from '~/app/filterableDataHelper';
|
||||
import { WorkspacekindsImageConfigValue } from '~/generated/data-contracts';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { fields, filterableLabelMap } = defineDataFields({
|
||||
|
@ -21,10 +21,10 @@ const { fields, filterableLabelMap } = defineDataFields({
|
|||
type FilterableDataFieldKeys = FilterableDataFieldKey<typeof fields>;
|
||||
|
||||
type WorkspaceFormImageListProps = {
|
||||
images: WorkspaceImageConfigValue[];
|
||||
images: WorkspacekindsImageConfigValue[];
|
||||
selectedLabels: Map<string, Set<string>>;
|
||||
selectedImage: WorkspaceImageConfigValue | undefined;
|
||||
onSelect: (workspaceImage: WorkspaceImageConfigValue | undefined) => void;
|
||||
selectedImage: WorkspacekindsImageConfigValue | undefined;
|
||||
onSelect: (workspaceImage: WorkspacekindsImageConfigValue | undefined) => void;
|
||||
};
|
||||
|
||||
export const WorkspaceFormImageList: React.FunctionComponent<WorkspaceFormImageListProps> = ({
|
||||
|
@ -37,7 +37,7 @@ export const WorkspaceFormImageList: React.FunctionComponent<WorkspaceFormImageL
|
|||
const filterRef = useRef<FilterRef>(null);
|
||||
|
||||
const getFilteredWorkspaceImagesByLabels = useCallback(
|
||||
(unfilteredImages: WorkspaceImageConfigValue[]) =>
|
||||
(unfilteredImages: WorkspacekindsImageConfigValue[]) =>
|
||||
unfilteredImages.filter((image) =>
|
||||
image.labels.reduce((accumulator, label) => {
|
||||
if (selectedLabels.has(label.key)) {
|
||||
|
|
|
@ -3,12 +3,12 @@ import { Content } from '@patternfly/react-core/dist/esm/components/Content';
|
|||
import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split';
|
||||
import { WorkspaceFormImageList } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageList';
|
||||
import { FilterByLabels } from '~/app/pages/Workspaces/Form/labelFilter/FilterByLabels';
|
||||
import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacekindsImageConfigValue } from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspaceFormImageSelectionProps {
|
||||
images: WorkspaceImageConfigValue[];
|
||||
selectedImage: WorkspaceImageConfigValue | undefined;
|
||||
onSelect: (image: WorkspaceImageConfigValue | undefined) => void;
|
||||
images: WorkspacekindsImageConfigValue[];
|
||||
selectedImage: WorkspacekindsImageConfigValue | undefined;
|
||||
onSelect: (image: WorkspacekindsImageConfigValue | undefined) => void;
|
||||
}
|
||||
|
||||
const WorkspaceFormImageSelection: React.FunctionComponent<WorkspaceFormImageSelectionProps> = ({
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Title } from '@patternfly/react-core/dist/esm/components/Title';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
|
||||
type WorkspaceFormKindDetailsProps = {
|
||||
workspaceKind?: WorkspaceKind;
|
||||
workspaceKind?: WorkspacekindsWorkspaceKind;
|
||||
};
|
||||
|
||||
export const WorkspaceFormKindDetails: React.FunctionComponent<WorkspaceFormKindDetailsProps> = ({
|
||||
|
|
|
@ -8,12 +8,12 @@ import {
|
|||
import { Gallery } from '@patternfly/react-core/dist/esm/layouts/Gallery';
|
||||
import { PageSection } from '@patternfly/react-core/dist/esm/components/Page';
|
||||
import { Toolbar, ToolbarContent } from '@patternfly/react-core/dist/esm/components/Toolbar';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter';
|
||||
import CustomEmptyState from '~/shared/components/CustomEmptyState';
|
||||
import ImageFallback from '~/shared/components/ImageFallback';
|
||||
import WithValidImage from '~/shared/components/WithValidImage';
|
||||
import { defineDataFields, FilterableDataFieldKey } from '~/app/filterableDataHelper';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { fields, filterableLabelMap } = defineDataFields({
|
||||
|
@ -23,9 +23,9 @@ const { fields, filterableLabelMap } = defineDataFields({
|
|||
type FilterableDataFieldKeys = FilterableDataFieldKey<typeof fields>;
|
||||
|
||||
type WorkspaceFormKindListProps = {
|
||||
allWorkspaceKinds: WorkspaceKind[];
|
||||
selectedKind: WorkspaceKind | undefined;
|
||||
onSelect: (workspaceKind: WorkspaceKind | undefined) => void;
|
||||
allWorkspaceKinds: WorkspacekindsWorkspaceKind[];
|
||||
selectedKind: WorkspacekindsWorkspaceKind | undefined;
|
||||
onSelect: (workspaceKind: WorkspacekindsWorkspaceKind | undefined) => void;
|
||||
};
|
||||
|
||||
export const WorkspaceFormKindList: React.FunctionComponent<WorkspaceFormKindListProps> = ({
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { Content } from '@patternfly/react-core/dist/esm/components/Content';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
|
||||
import { WorkspaceFormKindList } from '~/app/pages/Workspaces/Form/kind/WorkspaceFormKindList';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspaceFormKindSelectionProps {
|
||||
selectedKind: WorkspaceKind | undefined;
|
||||
onSelect: (kind: WorkspaceKind | undefined) => void;
|
||||
selectedKind: WorkspacekindsWorkspaceKind | undefined;
|
||||
onSelect: (kind: WorkspacekindsWorkspaceKind | undefined) => void;
|
||||
}
|
||||
|
||||
const WorkspaceFormKindSelection: React.FunctionComponent<WorkspaceFormKindSelectionProps> = ({
|
||||
|
|
|
@ -5,11 +5,11 @@ import {
|
|||
FilterSidePanelCategoryItem,
|
||||
} from '@patternfly/react-catalog-view-extension';
|
||||
import '@patternfly/react-catalog-view-extension/dist/css/react-catalog-view-extension.css';
|
||||
import { WorkspaceOptionLabel } from '~/shared/api/backendApiTypes';
|
||||
import { formatLabelKey } from '~/shared/utilities/WorkspaceUtils';
|
||||
import { WorkspacesOptionLabel } from '~/generated/data-contracts';
|
||||
|
||||
type FilterByLabelsProps = {
|
||||
labelledObjects: WorkspaceOptionLabel[];
|
||||
labelledObjects: WorkspacesOptionLabel[];
|
||||
selectedLabels: Map<string, Set<string>>;
|
||||
onSelect: (labels: Map<string, Set<string>>) => void;
|
||||
};
|
||||
|
|
|
@ -7,11 +7,11 @@ import {
|
|||
} from '@patternfly/react-core/dist/esm/components/DescriptionList';
|
||||
import { Title } from '@patternfly/react-core/dist/esm/components/Title';
|
||||
import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
|
||||
import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';
|
||||
import { formatLabelKey } from '~/shared/utilities/WorkspaceUtils';
|
||||
import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts';
|
||||
|
||||
type WorkspaceFormPodConfigDetailsProps = {
|
||||
workspacePodConfig?: WorkspacePodConfigValue;
|
||||
workspacePodConfig?: WorkspacekindsPodConfigValue;
|
||||
};
|
||||
|
||||
export const WorkspaceFormPodConfigDetails: React.FunctionComponent<
|
||||
|
|
|
@ -8,10 +8,10 @@ import {
|
|||
import { Gallery } from '@patternfly/react-core/dist/esm/layouts/Gallery';
|
||||
import { PageSection } from '@patternfly/react-core/dist/esm/components/Page';
|
||||
import { Toolbar, ToolbarContent } from '@patternfly/react-core/dist/esm/components/Toolbar';
|
||||
import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';
|
||||
import Filter, { FilteredColumn, FilterRef } from '~/shared/components/Filter';
|
||||
import CustomEmptyState from '~/shared/components/CustomEmptyState';
|
||||
import { defineDataFields, FilterableDataFieldKey } from '~/app/filterableDataHelper';
|
||||
import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { fields, filterableLabelMap } = defineDataFields({
|
||||
|
@ -21,10 +21,10 @@ const { fields, filterableLabelMap } = defineDataFields({
|
|||
type FilterableDataFieldKeys = FilterableDataFieldKey<typeof fields>;
|
||||
|
||||
type WorkspaceFormPodConfigListProps = {
|
||||
podConfigs: WorkspacePodConfigValue[];
|
||||
podConfigs: WorkspacekindsPodConfigValue[];
|
||||
selectedLabels: Map<string, Set<string>>;
|
||||
selectedPodConfig: WorkspacePodConfigValue | undefined;
|
||||
onSelect: (workspacePodConfig: WorkspacePodConfigValue | undefined) => void;
|
||||
selectedPodConfig: WorkspacekindsPodConfigValue | undefined;
|
||||
onSelect: (workspacePodConfig: WorkspacekindsPodConfigValue | undefined) => void;
|
||||
};
|
||||
|
||||
export const WorkspaceFormPodConfigList: React.FunctionComponent<
|
||||
|
@ -34,7 +34,7 @@ export const WorkspaceFormPodConfigList: React.FunctionComponent<
|
|||
const filterRef = useRef<FilterRef>(null);
|
||||
|
||||
const getFilteredWorkspacePodConfigsByLabels = useCallback(
|
||||
(unfilteredPodConfigs: WorkspacePodConfigValue[]) =>
|
||||
(unfilteredPodConfigs: WorkspacekindsPodConfigValue[]) =>
|
||||
unfilteredPodConfigs.filter((podConfig) =>
|
||||
podConfig.labels.reduce((accumulator, label) => {
|
||||
if (selectedLabels.has(label.key)) {
|
||||
|
|
|
@ -3,12 +3,12 @@ import { Content } from '@patternfly/react-core/dist/esm/components/Content';
|
|||
import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split';
|
||||
import { WorkspaceFormPodConfigList } from '~/app/pages/Workspaces/Form/podConfig/WorkspaceFormPodConfigList';
|
||||
import { FilterByLabels } from '~/app/pages/Workspaces/Form/labelFilter/FilterByLabels';
|
||||
import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacekindsPodConfigValue } from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspaceFormPodConfigSelectionProps {
|
||||
podConfigs: WorkspacePodConfigValue[];
|
||||
selectedPodConfig: WorkspacePodConfigValue | undefined;
|
||||
onSelect: (podConfig: WorkspacePodConfigValue | undefined) => void;
|
||||
podConfigs: WorkspacekindsPodConfigValue[];
|
||||
selectedPodConfig: WorkspacekindsPodConfigValue | undefined;
|
||||
onSelect: (podConfig: WorkspacekindsPodConfigValue | undefined) => void;
|
||||
}
|
||||
|
||||
const WorkspaceFormPodConfigSelection: React.FunctionComponent<
|
||||
|
|
|
@ -24,11 +24,11 @@ import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggl
|
|||
import { Form, FormGroup } from '@patternfly/react-core/dist/esm/components/Form';
|
||||
import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText';
|
||||
import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
|
||||
import { WorkspacePodSecretMount } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacesPodSecretMount } from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspaceFormPropertiesSecretsProps {
|
||||
secrets: WorkspacePodSecretMount[];
|
||||
setSecrets: (secrets: WorkspacePodSecretMount[]) => void;
|
||||
secrets: WorkspacesPodSecretMount[];
|
||||
setSecrets: (secrets: WorkspacesPodSecretMount[]) => void;
|
||||
}
|
||||
|
||||
const DEFAULT_MODE_OCTAL = (420).toString(8);
|
||||
|
@ -39,7 +39,7 @@ export const WorkspaceFormPropertiesSecrets: React.FC<WorkspaceFormPropertiesSec
|
|||
}) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [formData, setFormData] = useState<WorkspacePodSecretMount>({
|
||||
const [formData, setFormData] = useState<WorkspacesPodSecretMount>({
|
||||
secretName: '',
|
||||
mountPath: '',
|
||||
defaultMode: parseInt(DEFAULT_MODE_OCTAL, 8),
|
||||
|
|
|
@ -8,12 +8,12 @@ import { Split, SplitItem } from '@patternfly/react-core/dist/esm/layouts/Split'
|
|||
import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput';
|
||||
import { WorkspaceFormImageDetails } from '~/app/pages/Workspaces/Form/image/WorkspaceFormImageDetails';
|
||||
import { WorkspaceFormPropertiesVolumes } from '~/app/pages/Workspaces/Form/properties/WorkspaceFormPropertiesVolumes';
|
||||
import { WorkspacekindsImageConfigValue } from '~/generated/data-contracts';
|
||||
import { WorkspaceFormProperties } from '~/app/types';
|
||||
import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceFormPropertiesSecrets } from './WorkspaceFormPropertiesSecrets';
|
||||
|
||||
interface WorkspaceFormPropertiesSelectionProps {
|
||||
selectedImage: WorkspaceImageConfigValue | undefined;
|
||||
selectedImage: WorkspacekindsImageConfigValue | undefined;
|
||||
selectedProperties: WorkspaceFormProperties;
|
||||
onSelect: (properties: WorkspaceFormProperties) => void;
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ import {
|
|||
Tr,
|
||||
} from '@patternfly/react-table/dist/esm/components/Table';
|
||||
import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
|
||||
import { WorkspacePodVolumeMount } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacesPodVolumeMount } from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspaceFormPropertiesVolumesProps {
|
||||
volumes: WorkspacePodVolumeMount[];
|
||||
setVolumes: (volumes: WorkspacePodVolumeMount[]) => void;
|
||||
volumes: WorkspacesPodVolumeMount[];
|
||||
setVolumes: (volumes: WorkspacesPodVolumeMount[]) => void;
|
||||
}
|
||||
|
||||
export const WorkspaceFormPropertiesVolumes: React.FC<WorkspaceFormPropertiesVolumesProps> = ({
|
||||
|
@ -36,7 +36,7 @@ export const WorkspaceFormPropertiesVolumes: React.FC<WorkspaceFormPropertiesVol
|
|||
}) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [formData, setFormData] = useState<WorkspacePodVolumeMount>({
|
||||
const [formData, setFormData] = useState<WorkspacesPodVolumeMount>({
|
||||
pvcName: '',
|
||||
mountPath: '',
|
||||
readOnly: false,
|
||||
|
|
|
@ -5,11 +5,11 @@ import {
|
|||
DescriptionListGroup,
|
||||
DescriptionListDescription,
|
||||
} from '@patternfly/react-core/dist/esm/components/DescriptionList';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { formatResourceFromWorkspace } from '~/shared/utilities/WorkspaceUtils';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspaceConfigDetailsProps {
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
}
|
||||
|
||||
export const WorkspaceConfigDetails: React.FC<WorkspaceConfigDetailsProps> = ({ workspace }) => (
|
||||
|
|
|
@ -9,10 +9,10 @@ import {
|
|||
MenuToggleElement,
|
||||
MenuToggleAction,
|
||||
} from '@patternfly/react-core/dist/esm/components/MenuToggle';
|
||||
import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacesWorkspace, WorkspacesWorkspaceState } from '~/generated/data-contracts';
|
||||
|
||||
type WorkspaceConnectActionProps = {
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
};
|
||||
|
||||
export const WorkspaceConnectAction: React.FunctionComponent<WorkspaceConnectActionProps> = ({
|
||||
|
@ -57,7 +57,7 @@ export const WorkspaceConnectAction: React.FunctionComponent<WorkspaceConnectAct
|
|||
variant="secondary"
|
||||
onClick={onToggleClick}
|
||||
isExpanded={open}
|
||||
isDisabled={workspace.state !== WorkspaceState.WorkspaceStateRunning}
|
||||
isDisabled={workspace.state !== WorkspacesWorkspaceState.WorkspaceStateRunning}
|
||||
splitButtonItems={[
|
||||
<MenuToggleAction
|
||||
id="connect-endpoint-button"
|
||||
|
|
|
@ -6,11 +6,11 @@ import {
|
|||
DescriptionListGroup,
|
||||
} from '@patternfly/react-core/dist/esm/components/DescriptionList';
|
||||
import { ListItem, List } from '@patternfly/react-core/dist/esm/components/List';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { extractPackageLabels, formatLabelKey } from '~/shared/utilities/WorkspaceUtils';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspacePackageDetailsProps {
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
}
|
||||
|
||||
export const WorkspacePackageDetails: React.FC<WorkspacePackageDetailsProps> = ({ workspace }) => {
|
||||
|
|
|
@ -5,11 +5,11 @@ import {
|
|||
DescriptionListGroup,
|
||||
DescriptionListDescription,
|
||||
} from '@patternfly/react-core/dist/esm/components/DescriptionList';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { DataVolumesList } from '~/app/pages/Workspaces/DataVolumesList';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
interface WorkspaceStorageProps {
|
||||
workspace: Workspace;
|
||||
workspace: WorkspacesWorkspace;
|
||||
}
|
||||
|
||||
export const WorkspaceStorage: React.FC<WorkspaceStorageProps> = ({ workspace }) => (
|
||||
|
|
|
@ -5,12 +5,12 @@ import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'
|
|||
import WorkspaceTable from '~/app/components/WorkspaceTable';
|
||||
import { useNamespaceContext } from '~/app/context/NamespaceContextProvider';
|
||||
import { useWorkspacesByNamespace } from '~/app/hooks/useWorkspaces';
|
||||
import { WorkspaceState } from '~/shared/api/backendApiTypes';
|
||||
import { DEFAULT_POLLING_RATE_MS } from '~/app/const';
|
||||
import { LoadingSpinner } from '~/app/components/LoadingSpinner';
|
||||
import { LoadError } from '~/app/components/LoadError';
|
||||
import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions';
|
||||
import { usePolling } from '~/app/hooks/usePolling';
|
||||
import { WorkspacesWorkspaceState } from '~/generated/data-contracts';
|
||||
|
||||
export const Workspaces: React.FunctionComponent = () => {
|
||||
const { selectedNamespace } = useNamespaceContext();
|
||||
|
@ -27,17 +27,17 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
{ id: 'separator' },
|
||||
{
|
||||
id: 'stop',
|
||||
isVisible: (w) => w.state === WorkspaceState.WorkspaceStateRunning,
|
||||
isVisible: (w) => w.state === WorkspacesWorkspaceState.WorkspaceStateRunning,
|
||||
onActionDone: refreshWorkspaces,
|
||||
},
|
||||
{
|
||||
id: 'start',
|
||||
isVisible: (w) => w.state !== WorkspaceState.WorkspaceStateRunning,
|
||||
isVisible: (w) => w.state !== WorkspacesWorkspaceState.WorkspaceStateRunning,
|
||||
onActionDone: refreshWorkspaces,
|
||||
},
|
||||
{
|
||||
id: 'restart',
|
||||
isVisible: (w) => w.state === WorkspaceState.WorkspaceStateRunning,
|
||||
isVisible: (w) => w.state === WorkspacesWorkspaceState.WorkspaceStateRunning,
|
||||
onActionDone: refreshWorkspaces,
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/ex
|
|||
import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon';
|
||||
import { InfoCircleIcon } from '@patternfly/react-icons/dist/esm/icons/info-circle-icon';
|
||||
import useWorkspaceKindByName from '~/app/hooks/useWorkspaceKindByName';
|
||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
|
||||
const getLevelIcon = (level: string | undefined) => {
|
||||
switch (level) {
|
||||
|
@ -48,9 +48,9 @@ export const WorkspaceRedirectInformationView: React.FC<WorkspaceRedirectInforma
|
|||
const [activeKey, setActiveKey] = useState<string | number>(0);
|
||||
const [workspaceKind, workspaceKindLoaded] = useWorkspaceKindByName(kind);
|
||||
const [imageConfig, setImageConfig] =
|
||||
useState<WorkspaceKind['podTemplate']['options']['imageConfig']>();
|
||||
useState<WorkspacekindsWorkspaceKind['podTemplate']['options']['imageConfig']>();
|
||||
const [podConfig, setPodConfig] =
|
||||
useState<WorkspaceKind['podTemplate']['options']['podConfig']>();
|
||||
useState<WorkspacekindsWorkspaceKind['podTemplate']['options']['podConfig']>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspaceKindLoaded) {
|
||||
|
|
|
@ -8,13 +8,13 @@ import {
|
|||
ModalHeader,
|
||||
} from '@patternfly/react-core/dist/esm/components/Modal';
|
||||
import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs';
|
||||
import { Workspace } from '~/shared/api/backendApiTypes';
|
||||
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
|
||||
import { WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
interface RestartActionAlertProps {
|
||||
onClose: () => void;
|
||||
isOpen: boolean;
|
||||
workspace: Workspace | null;
|
||||
workspace: WorkspacesWorkspace | null;
|
||||
}
|
||||
|
||||
export const WorkspaceRestartActionModal: React.FC<RestartActionAlertProps> = ({
|
||||
|
|
|
@ -8,14 +8,14 @@ import {
|
|||
} from '@patternfly/react-core/dist/esm/components/Modal';
|
||||
import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs';
|
||||
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
|
||||
import { Workspace, WorkspacePauseState } from '~/shared/api/backendApiTypes';
|
||||
import { ActionButton } from '~/shared/components/ActionButton';
|
||||
import { ApiWorkspaceActionPauseEnvelope, WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
interface StartActionAlertProps {
|
||||
onClose: () => void;
|
||||
isOpen: boolean;
|
||||
workspace: Workspace | null;
|
||||
onStart: () => Promise<WorkspacePauseState | void>;
|
||||
workspace: WorkspacesWorkspace | null;
|
||||
onStart: () => Promise<ApiWorkspaceActionPauseEnvelope>;
|
||||
onUpdateAndStart: () => Promise<void>;
|
||||
onActionDone?: () => void;
|
||||
}
|
||||
|
@ -33,10 +33,16 @@ export const WorkspaceStartActionModal: React.FC<StartActionAlertProps> = ({
|
|||
const [actionOnGoing, setActionOnGoing] = useState<StartAction | null>(null);
|
||||
|
||||
const executeAction = useCallback(
|
||||
(args: { action: StartAction; callback: () => ReturnType<typeof onStart> }) => {
|
||||
setActionOnGoing(args.action);
|
||||
async <T,>({
|
||||
action,
|
||||
callback,
|
||||
}: {
|
||||
action: StartAction;
|
||||
callback: () => Promise<T>;
|
||||
}): Promise<T> => {
|
||||
setActionOnGoing(action);
|
||||
try {
|
||||
return args.callback();
|
||||
return await callback();
|
||||
} finally {
|
||||
setActionOnGoing(null);
|
||||
}
|
||||
|
@ -48,7 +54,7 @@ export const WorkspaceStartActionModal: React.FC<StartActionAlertProps> = ({
|
|||
try {
|
||||
const response = await executeAction({ action: 'start', callback: onStart });
|
||||
// TODO: alert user about success
|
||||
console.info('Workspace started successfully:', JSON.stringify(response));
|
||||
console.info('Workspace started successfully:', JSON.stringify(response.data));
|
||||
onActionDone?.();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
|
|
|
@ -9,14 +9,14 @@ import {
|
|||
} from '@patternfly/react-core/dist/esm/components/Modal';
|
||||
import { TabTitleText } from '@patternfly/react-core/dist/esm/components/Tabs';
|
||||
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
|
||||
import { Workspace, WorkspacePauseState } from '~/shared/api/backendApiTypes';
|
||||
import { ActionButton } from '~/shared/components/ActionButton';
|
||||
import { ApiWorkspaceActionPauseEnvelope, WorkspacesWorkspace } from '~/generated/data-contracts';
|
||||
|
||||
interface StopActionAlertProps {
|
||||
onClose: () => void;
|
||||
isOpen: boolean;
|
||||
workspace: Workspace | null;
|
||||
onStop: () => Promise<WorkspacePauseState | void>;
|
||||
workspace: WorkspacesWorkspace | null;
|
||||
onStop: () => Promise<ApiWorkspaceActionPauseEnvelope>;
|
||||
onUpdateAndStop: () => Promise<void>;
|
||||
onActionDone?: () => void;
|
||||
}
|
||||
|
@ -35,10 +35,16 @@ export const WorkspaceStopActionModal: React.FC<StopActionAlertProps> = ({
|
|||
const [actionOnGoing, setActionOnGoing] = useState<StopAction | null>(null);
|
||||
|
||||
const executeAction = useCallback(
|
||||
(args: { action: StopAction; callback: () => ReturnType<typeof onStop> }) => {
|
||||
setActionOnGoing(args.action);
|
||||
async <T,>({
|
||||
action,
|
||||
callback,
|
||||
}: {
|
||||
action: StopAction;
|
||||
callback: () => Promise<T>;
|
||||
}): Promise<T> => {
|
||||
setActionOnGoing(action);
|
||||
try {
|
||||
return args.callback();
|
||||
return await callback();
|
||||
} finally {
|
||||
setActionOnGoing(null);
|
||||
}
|
||||
|
@ -50,7 +56,7 @@ export const WorkspaceStopActionModal: React.FC<StopActionAlertProps> = ({
|
|||
try {
|
||||
const response = await executeAction({ action: 'stop', callback: onStop });
|
||||
// TODO: alert user about success
|
||||
console.info('Workspace stopped successfully:', JSON.stringify(response));
|
||||
console.info('Workspace stopped successfully:', JSON.stringify(response.data));
|
||||
onActionDone?.();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import {
|
||||
WorkspaceImageConfigValue,
|
||||
WorkspaceKind,
|
||||
WorkspacePodConfigValue,
|
||||
WorkspacePodVolumeMount,
|
||||
WorkspacePodSecretMount,
|
||||
Workspace,
|
||||
WorkspaceImageRef,
|
||||
WorkspacePodVolumeMounts,
|
||||
WorkspaceKindPodMetadata,
|
||||
} from '~/shared/api/backendApiTypes';
|
||||
WorkspacekindsImageConfigValue,
|
||||
WorkspacekindsImageRef,
|
||||
WorkspacekindsPodConfigValue,
|
||||
WorkspacekindsPodMetadata,
|
||||
WorkspacekindsPodVolumeMounts,
|
||||
WorkspacekindsWorkspaceKind,
|
||||
WorkspacesPodSecretMount,
|
||||
WorkspacesPodVolumeMount,
|
||||
WorkspacesWorkspace,
|
||||
} from '~/generated/data-contracts';
|
||||
|
||||
export interface WorkspaceColumnDefinition {
|
||||
name: string;
|
||||
|
@ -27,22 +27,22 @@ export interface WorkspaceFormProperties {
|
|||
workspaceName: string;
|
||||
deferUpdates: boolean;
|
||||
homeDirectory: string;
|
||||
volumes: WorkspacePodVolumeMount[];
|
||||
secrets: WorkspacePodSecretMount[];
|
||||
volumes: WorkspacesPodVolumeMount[];
|
||||
secrets: WorkspacesPodSecretMount[];
|
||||
}
|
||||
|
||||
export interface WorkspaceFormData {
|
||||
kind: WorkspaceKind | undefined;
|
||||
image: WorkspaceImageConfigValue | undefined;
|
||||
podConfig: WorkspacePodConfigValue | undefined;
|
||||
kind: WorkspacekindsWorkspaceKind | undefined;
|
||||
image: WorkspacekindsImageConfigValue | undefined;
|
||||
podConfig: WorkspacekindsPodConfigValue | undefined;
|
||||
properties: WorkspaceFormProperties;
|
||||
}
|
||||
|
||||
export interface WorkspaceCountPerOption {
|
||||
count: number;
|
||||
countByImage: Record<WorkspaceImageConfigValue['id'], number>;
|
||||
countByPodConfig: Record<WorkspacePodConfigValue['id'], number>;
|
||||
countByNamespace: Record<Workspace['namespace'], number>;
|
||||
countByImage: Record<WorkspacekindsImageConfigValue['id'], number>;
|
||||
countByPodConfig: Record<WorkspacekindsPodConfigValue['id'], number>;
|
||||
countByNamespace: Record<WorkspacesWorkspace['namespace'], number>;
|
||||
}
|
||||
|
||||
export interface WorkspaceKindProperties {
|
||||
|
@ -51,11 +51,11 @@ export interface WorkspaceKindProperties {
|
|||
deprecated: boolean;
|
||||
deprecationMessage: string;
|
||||
hidden: boolean;
|
||||
icon: WorkspaceImageRef;
|
||||
logo: WorkspaceImageRef;
|
||||
icon: WorkspacekindsImageRef;
|
||||
logo: WorkspacekindsImageRef;
|
||||
}
|
||||
|
||||
export interface WorkspaceKindImageConfigValue extends WorkspaceImageConfigValue {
|
||||
export interface WorkspaceKindImageConfigValue extends WorkspacekindsImageConfigValue {
|
||||
imagePullPolicy?: ImagePullPolicy.IfNotPresent | ImagePullPolicy.Always | ImagePullPolicy.Never;
|
||||
ports?: WorkspaceKindImagePort[];
|
||||
image?: string;
|
||||
|
@ -74,7 +74,7 @@ export interface WorkspaceKindImagePort {
|
|||
protocol: 'HTTP'; // ONLY HTTP is supported at the moment, per https://github.com/thesuperzapper/kubeflow-notebooks-v2-design/blob/main/crds/workspace-kind.yaml#L275
|
||||
}
|
||||
|
||||
export interface WorkspaceKindPodConfigValue extends WorkspacePodConfigValue {
|
||||
export interface WorkspaceKindPodConfigValue extends WorkspacekindsPodConfigValue {
|
||||
resources?: {
|
||||
requests: {
|
||||
[key: string]: string;
|
||||
|
@ -105,10 +105,10 @@ export interface WorkspaceKindPodCulling {
|
|||
}
|
||||
|
||||
export interface WorkspaceKindPodTemplateData {
|
||||
podMetadata: WorkspaceKindPodMetadata;
|
||||
volumeMounts: WorkspacePodVolumeMounts;
|
||||
podMetadata: WorkspacekindsPodMetadata;
|
||||
volumeMounts: WorkspacekindsPodVolumeMounts;
|
||||
culling?: WorkspaceKindPodCulling;
|
||||
extraVolumeMounts?: WorkspacePodVolumeMount[];
|
||||
extraVolumeMounts?: WorkspacesPodVolumeMount[];
|
||||
}
|
||||
|
||||
export interface WorkspaceKindFormData {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { ApiErrorEnvelope, HealthCheckHealthCheck } from './data-contracts';
|
||||
import { HttpClient, RequestParams } from './http-client';
|
||||
|
||||
export class Healthcheck<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
|
||||
/**
|
||||
* @description Provides a healthcheck response indicating the status of key services.
|
||||
*
|
||||
* @tags healthcheck
|
||||
* @name GetHealthcheck
|
||||
* @summary Returns the health status of the application
|
||||
* @request GET:/healthcheck
|
||||
* @response `200` `HealthCheckHealthCheck` Successful healthcheck response
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error
|
||||
*/
|
||||
getHealthcheck = (params: RequestParams = {}) =>
|
||||
this.request<HealthCheckHealthCheck, ApiErrorEnvelope>({
|
||||
path: `/healthcheck`,
|
||||
method: 'GET',
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { ApiErrorEnvelope, ApiNamespaceListEnvelope } from './data-contracts';
|
||||
import { HttpClient, RequestParams } from './http-client';
|
||||
|
||||
export class Namespaces<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
|
||||
/**
|
||||
* @description Provides a list of all namespaces that the user has access to
|
||||
*
|
||||
* @tags namespaces
|
||||
* @name ListNamespaces
|
||||
* @summary Returns a list of all namespaces
|
||||
* @request GET:/namespaces
|
||||
* @response `200` `ApiNamespaceListEnvelope` Successful namespaces response
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error
|
||||
*/
|
||||
listNamespaces = (params: RequestParams = {}) =>
|
||||
this.request<ApiNamespaceListEnvelope, ApiErrorEnvelope>({
|
||||
path: `/namespaces`,
|
||||
method: 'GET',
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import {
|
||||
ApiErrorEnvelope,
|
||||
ApiWorkspaceKindEnvelope,
|
||||
ApiWorkspaceKindListEnvelope,
|
||||
CreateWorkspaceKindPayload,
|
||||
} from './data-contracts';
|
||||
import { ContentType, HttpClient, RequestParams } from './http-client';
|
||||
|
||||
export class Workspacekinds<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
|
||||
/**
|
||||
* @description Returns a list of all available workspace kinds. Workspace kinds define the different types of workspaces that can be created in the system.
|
||||
*
|
||||
* @tags workspacekinds
|
||||
* @name ListWorkspaceKinds
|
||||
* @summary List workspace kinds
|
||||
* @request GET:/workspacekinds
|
||||
* @response `200` `ApiWorkspaceKindListEnvelope` Successful operation. Returns a list of all available workspace kinds.
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required.
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to list workspace kinds.
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server.
|
||||
*/
|
||||
listWorkspaceKinds = (params: RequestParams = {}) =>
|
||||
this.request<ApiWorkspaceKindListEnvelope, ApiErrorEnvelope>({
|
||||
path: `/workspacekinds`,
|
||||
method: 'GET',
|
||||
type: ContentType.Json,
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* @description Creates a new workspace kind.
|
||||
*
|
||||
* @tags workspacekinds
|
||||
* @name CreateWorkspaceKind
|
||||
* @summary Create workspace kind
|
||||
* @request POST:/workspacekinds
|
||||
* @response `201` `ApiWorkspaceKindEnvelope` WorkspaceKind created successfully
|
||||
* @response `400` `ApiErrorEnvelope` Bad Request.
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required.
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to create WorkspaceKind.
|
||||
* @response `409` `ApiErrorEnvelope` Conflict. WorkspaceKind with the same name already exists.
|
||||
* @response `413` `ApiErrorEnvelope` Request Entity Too Large. The request body is too large.
|
||||
* @response `415` `ApiErrorEnvelope` Unsupported Media Type. Content-Type header is not correct.
|
||||
* @response `422` `ApiErrorEnvelope` Unprocessable Entity. Validation error.
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server.
|
||||
*/
|
||||
createWorkspaceKind = (body: CreateWorkspaceKindPayload, params: RequestParams = {}) =>
|
||||
this.request<ApiWorkspaceKindEnvelope, ApiErrorEnvelope>({
|
||||
path: `/workspacekinds`,
|
||||
method: 'POST',
|
||||
body: body,
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* @description Returns details of a specific workspace kind identified by its name. Workspace kinds define the available types of workspaces that can be created.
|
||||
*
|
||||
* @tags workspacekinds
|
||||
* @name GetWorkspaceKind
|
||||
* @summary Get workspace kind
|
||||
* @request GET:/workspacekinds/{name}
|
||||
* @response `200` `ApiWorkspaceKindEnvelope` Successful operation. Returns the requested workspace kind details.
|
||||
* @response `400` `ApiErrorEnvelope` Bad Request. Invalid workspace kind name format.
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required.
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to access the workspace kind.
|
||||
* @response `404` `ApiErrorEnvelope` Not Found. Workspace kind does not exist.
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server.
|
||||
*/
|
||||
getWorkspaceKind = (name: string, params: RequestParams = {}) =>
|
||||
this.request<ApiWorkspaceKindEnvelope, ApiErrorEnvelope>({
|
||||
path: `/workspacekinds/${name}`,
|
||||
method: 'GET',
|
||||
type: ContentType.Json,
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import {
|
||||
ApiErrorEnvelope,
|
||||
ApiWorkspaceActionPauseEnvelope,
|
||||
ApiWorkspaceCreateEnvelope,
|
||||
ApiWorkspaceEnvelope,
|
||||
ApiWorkspaceListEnvelope,
|
||||
} from './data-contracts';
|
||||
import { ContentType, HttpClient, RequestParams } from './http-client';
|
||||
|
||||
export class Workspaces<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
|
||||
/**
|
||||
* @description Returns a list of all workspaces across all namespaces.
|
||||
*
|
||||
* @tags workspaces
|
||||
* @name ListAllWorkspaces
|
||||
* @summary List all workspaces
|
||||
* @request GET:/workspaces
|
||||
* @response `200` `ApiWorkspaceListEnvelope` Successful operation. Returns a list of all workspaces.
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required.
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to list workspaces.
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server.
|
||||
*/
|
||||
listAllWorkspaces = (params: RequestParams = {}) =>
|
||||
this.request<ApiWorkspaceListEnvelope, ApiErrorEnvelope>({
|
||||
path: `/workspaces`,
|
||||
method: 'GET',
|
||||
type: ContentType.Json,
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* @description Returns a list of workspaces in a specific namespace.
|
||||
*
|
||||
* @tags workspaces
|
||||
* @name ListWorkspacesByNamespace
|
||||
* @summary List workspaces by namespace
|
||||
* @request GET:/workspaces/{namespace}
|
||||
* @response `200` `ApiWorkspaceListEnvelope` Successful operation. Returns a list of workspaces in the specified namespace.
|
||||
* @response `400` `ApiErrorEnvelope` Bad Request. Invalid namespace format.
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required.
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to list workspaces.
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server.
|
||||
*/
|
||||
listWorkspacesByNamespace = (namespace: string, params: RequestParams = {}) =>
|
||||
this.request<ApiWorkspaceListEnvelope, ApiErrorEnvelope>({
|
||||
path: `/workspaces/${namespace}`,
|
||||
method: 'GET',
|
||||
type: ContentType.Json,
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* @description Creates a new workspace in the specified namespace.
|
||||
*
|
||||
* @tags workspaces
|
||||
* @name CreateWorkspace
|
||||
* @summary Create workspace
|
||||
* @request POST:/workspaces/{namespace}
|
||||
* @response `201` `ApiWorkspaceEnvelope` Workspace created successfully
|
||||
* @response `400` `ApiErrorEnvelope` Bad Request. Invalid request body or namespace format.
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required.
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to create workspace.
|
||||
* @response `409` `ApiErrorEnvelope` Conflict. Workspace with the same name already exists.
|
||||
* @response `413` `ApiErrorEnvelope` Request Entity Too Large. The request body is too large.
|
||||
* @response `415` `ApiErrorEnvelope` Unsupported Media Type. Content-Type header is not correct.
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server.
|
||||
*/
|
||||
createWorkspace = (
|
||||
namespace: string,
|
||||
body: ApiWorkspaceCreateEnvelope,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<ApiWorkspaceEnvelope, ApiErrorEnvelope>({
|
||||
path: `/workspaces/${namespace}`,
|
||||
method: 'POST',
|
||||
body: body,
|
||||
type: ContentType.Json,
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* @description Pauses or unpauses a workspace, stopping or resuming all associated pods.
|
||||
*
|
||||
* @tags workspaces
|
||||
* @name UpdateWorkspacePauseState
|
||||
* @summary Pause or unpause a workspace
|
||||
* @request POST:/workspaces/{namespace}/{workspaceName}/actions/pause
|
||||
* @response `200` `ApiWorkspaceActionPauseEnvelope` Successful action. Returns the current pause state.
|
||||
* @response `400` `ApiErrorEnvelope` Bad Request.
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required.
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to access the workspace.
|
||||
* @response `404` `ApiErrorEnvelope` Not Found. Workspace does not exist.
|
||||
* @response `413` `ApiErrorEnvelope` Request Entity Too Large. The request body is too large.
|
||||
* @response `415` `ApiErrorEnvelope` Unsupported Media Type. Content-Type header is not correct.
|
||||
* @response `422` `ApiErrorEnvelope` Unprocessable Entity. Workspace is not in appropriate state.
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server.
|
||||
*/
|
||||
updateWorkspacePauseState = (
|
||||
namespace: string,
|
||||
workspaceName: string,
|
||||
body: ApiWorkspaceActionPauseEnvelope,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<ApiWorkspaceActionPauseEnvelope, ApiErrorEnvelope>({
|
||||
path: `/workspaces/${namespace}/${workspaceName}/actions/pause`,
|
||||
method: 'POST',
|
||||
body: body,
|
||||
type: ContentType.Json,
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* @description Returns details of a specific workspace identified by namespace and workspace name.
|
||||
*
|
||||
* @tags workspaces
|
||||
* @name GetWorkspace
|
||||
* @summary Get workspace
|
||||
* @request GET:/workspaces/{namespace}/{workspace_name}
|
||||
* @response `200` `ApiWorkspaceEnvelope` Successful operation. Returns the requested workspace details.
|
||||
* @response `400` `ApiErrorEnvelope` Bad Request. Invalid namespace or workspace name format.
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required.
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to access the workspace.
|
||||
* @response `404` `ApiErrorEnvelope` Not Found. Workspace does not exist.
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server.
|
||||
*/
|
||||
getWorkspace = (namespace: string, workspaceName: string, params: RequestParams = {}) =>
|
||||
this.request<ApiWorkspaceEnvelope, ApiErrorEnvelope>({
|
||||
path: `/workspaces/${namespace}/${workspaceName}`,
|
||||
method: 'GET',
|
||||
type: ContentType.Json,
|
||||
format: 'json',
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* @description Deletes a specific workspace identified by namespace and workspace name.
|
||||
*
|
||||
* @tags workspaces
|
||||
* @name DeleteWorkspace
|
||||
* @summary Delete workspace
|
||||
* @request DELETE:/workspaces/{namespace}/{workspace_name}
|
||||
* @response `204` `void` Workspace deleted successfully
|
||||
* @response `400` `ApiErrorEnvelope` Bad Request. Invalid namespace or workspace name format.
|
||||
* @response `401` `ApiErrorEnvelope` Unauthorized. Authentication is required.
|
||||
* @response `403` `ApiErrorEnvelope` Forbidden. User does not have permission to delete the workspace.
|
||||
* @response `404` `ApiErrorEnvelope` Not Found. Workspace does not exist.
|
||||
* @response `500` `ApiErrorEnvelope` Internal server error. An unexpected error occurred on the server.
|
||||
*/
|
||||
deleteWorkspace = (namespace: string, workspaceName: string, params: RequestParams = {}) =>
|
||||
this.request<void, ApiErrorEnvelope>({
|
||||
path: `/workspaces/${namespace}/${workspaceName}`,
|
||||
method: 'DELETE',
|
||||
type: ContentType.Json,
|
||||
...params,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export enum WorkspacesWorkspaceState {
|
||||
WorkspaceStateRunning = 'Running',
|
||||
WorkspaceStateTerminating = 'Terminating',
|
||||
WorkspaceStatePaused = 'Paused',
|
||||
WorkspaceStatePending = 'Pending',
|
||||
WorkspaceStateError = 'Error',
|
||||
WorkspaceStateUnknown = 'Unknown',
|
||||
}
|
||||
|
||||
export enum WorkspacesRedirectMessageLevel {
|
||||
RedirectMessageLevelInfo = 'Info',
|
||||
RedirectMessageLevelWarning = 'Warning',
|
||||
RedirectMessageLevelDanger = 'Danger',
|
||||
}
|
||||
|
||||
export enum WorkspacesProbeResult {
|
||||
ProbeResultSuccess = 'Success',
|
||||
ProbeResultFailure = 'Failure',
|
||||
ProbeResultTimeout = 'Timeout',
|
||||
}
|
||||
|
||||
export enum WorkspacekindsRedirectMessageLevel {
|
||||
RedirectMessageLevelInfo = 'Info',
|
||||
RedirectMessageLevelWarning = 'Warning',
|
||||
RedirectMessageLevelDanger = 'Danger',
|
||||
}
|
||||
|
||||
export enum HealthCheckServiceStatus {
|
||||
ServiceStatusHealthy = 'Healthy',
|
||||
ServiceStatusUnhealthy = 'Unhealthy',
|
||||
}
|
||||
|
||||
export enum FieldErrorType {
|
||||
ErrorTypeNotFound = 'FieldValueNotFound',
|
||||
ErrorTypeRequired = 'FieldValueRequired',
|
||||
ErrorTypeDuplicate = 'FieldValueDuplicate',
|
||||
ErrorTypeInvalid = 'FieldValueInvalid',
|
||||
ErrorTypeNotSupported = 'FieldValueNotSupported',
|
||||
ErrorTypeForbidden = 'FieldValueForbidden',
|
||||
ErrorTypeTooLong = 'FieldValueTooLong',
|
||||
ErrorTypeTooMany = 'FieldValueTooMany',
|
||||
ErrorTypeInternal = 'InternalError',
|
||||
ErrorTypeTypeInvalid = 'FieldValueTypeInvalid',
|
||||
}
|
||||
|
||||
export interface ActionsWorkspaceActionPause {
|
||||
paused: boolean;
|
||||
}
|
||||
|
||||
export interface ApiErrorCause {
|
||||
validation_errors?: ApiValidationError[];
|
||||
}
|
||||
|
||||
export interface ApiErrorEnvelope {
|
||||
error: ApiHTTPError;
|
||||
}
|
||||
|
||||
export interface ApiHTTPError {
|
||||
cause?: ApiErrorCause;
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ApiNamespaceListEnvelope {
|
||||
data: NamespacesNamespace[];
|
||||
}
|
||||
|
||||
export interface ApiValidationError {
|
||||
field: string;
|
||||
message: string;
|
||||
type: FieldErrorType;
|
||||
}
|
||||
|
||||
export interface ApiWorkspaceActionPauseEnvelope {
|
||||
data: ActionsWorkspaceActionPause;
|
||||
}
|
||||
|
||||
export interface ApiWorkspaceCreateEnvelope {
|
||||
data: WorkspacesWorkspaceCreate;
|
||||
}
|
||||
|
||||
export interface ApiWorkspaceEnvelope {
|
||||
data: WorkspacesWorkspace;
|
||||
}
|
||||
|
||||
export interface ApiWorkspaceKindEnvelope {
|
||||
data: WorkspacekindsWorkspaceKind;
|
||||
}
|
||||
|
||||
export interface ApiWorkspaceKindListEnvelope {
|
||||
data: WorkspacekindsWorkspaceKind[];
|
||||
}
|
||||
|
||||
export interface ApiWorkspaceListEnvelope {
|
||||
data: WorkspacesWorkspace[];
|
||||
}
|
||||
|
||||
export interface HealthCheckHealthCheck {
|
||||
status: HealthCheckServiceStatus;
|
||||
systemInfo: HealthCheckSystemInfo;
|
||||
}
|
||||
|
||||
export interface HealthCheckSystemInfo {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface NamespacesNamespace {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsImageConfig {
|
||||
default: string;
|
||||
values: WorkspacekindsImageConfigValue[];
|
||||
}
|
||||
|
||||
export interface WorkspacekindsImageConfigValue {
|
||||
clusterMetrics?: WorkspacekindsClusterMetrics;
|
||||
description: string;
|
||||
displayName: string;
|
||||
hidden: boolean;
|
||||
id: string;
|
||||
labels: WorkspacekindsOptionLabel[];
|
||||
redirect?: WorkspacekindsOptionRedirect;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsImageRef {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsOptionLabel {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsOptionRedirect {
|
||||
message?: WorkspacekindsRedirectMessage;
|
||||
to: string;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsPodConfig {
|
||||
default: string;
|
||||
values: WorkspacekindsPodConfigValue[];
|
||||
}
|
||||
|
||||
export interface WorkspacekindsPodConfigValue {
|
||||
clusterMetrics?: WorkspacekindsClusterMetrics;
|
||||
description: string;
|
||||
displayName: string;
|
||||
hidden: boolean;
|
||||
id: string;
|
||||
labels: WorkspacekindsOptionLabel[];
|
||||
redirect?: WorkspacekindsOptionRedirect;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsPodMetadata {
|
||||
annotations: Record<string, string>;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsPodTemplate {
|
||||
options: WorkspacekindsPodTemplateOptions;
|
||||
podMetadata: WorkspacekindsPodMetadata;
|
||||
volumeMounts: WorkspacekindsPodVolumeMounts;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsPodTemplateOptions {
|
||||
imageConfig: WorkspacekindsImageConfig;
|
||||
podConfig: WorkspacekindsPodConfig;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsPodVolumeMounts {
|
||||
home: string;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsRedirectMessage {
|
||||
level: WorkspacekindsRedirectMessageLevel;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsWorkspaceKind {
|
||||
clusterMetrics?: WorkspacekindsClusterMetrics;
|
||||
deprecated: boolean;
|
||||
deprecationMessage: string;
|
||||
description: string;
|
||||
displayName: string;
|
||||
hidden: boolean;
|
||||
icon: WorkspacekindsImageRef;
|
||||
logo: WorkspacekindsImageRef;
|
||||
name: string;
|
||||
podTemplate: WorkspacekindsPodTemplate;
|
||||
}
|
||||
|
||||
export interface WorkspacekindsClusterMetrics {
|
||||
workspacesCount: number;
|
||||
}
|
||||
|
||||
export interface WorkspacesActivity {
|
||||
/** Unix Epoch time */
|
||||
lastActivity: number;
|
||||
lastProbe?: WorkspacesLastProbeInfo;
|
||||
/** Unix Epoch time */
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
export interface WorkspacesHttpService {
|
||||
displayName: string;
|
||||
httpPath: string;
|
||||
}
|
||||
|
||||
export interface WorkspacesImageConfig {
|
||||
current: WorkspacesOptionInfo;
|
||||
desired?: WorkspacesOptionInfo;
|
||||
redirectChain?: WorkspacesRedirectStep[];
|
||||
}
|
||||
|
||||
export interface WorkspacesImageRef {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface WorkspacesLastProbeInfo {
|
||||
/** Unix Epoch time in milliseconds */
|
||||
endTimeMs: number;
|
||||
message: string;
|
||||
result: WorkspacesProbeResult;
|
||||
/** Unix Epoch time in milliseconds */
|
||||
startTimeMs: number;
|
||||
}
|
||||
|
||||
export interface WorkspacesOptionInfo {
|
||||
description: string;
|
||||
displayName: string;
|
||||
id: string;
|
||||
labels: WorkspacesOptionLabel[];
|
||||
}
|
||||
|
||||
export interface WorkspacesOptionLabel {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodConfig {
|
||||
current: WorkspacesOptionInfo;
|
||||
desired?: WorkspacesOptionInfo;
|
||||
redirectChain?: WorkspacesRedirectStep[];
|
||||
}
|
||||
|
||||
export interface WorkspacesPodMetadata {
|
||||
annotations: Record<string, string>;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodMetadataMutate {
|
||||
annotations: Record<string, string>;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodSecretInfo {
|
||||
defaultMode?: number;
|
||||
mountPath: string;
|
||||
secretName: string;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodSecretMount {
|
||||
defaultMode?: number;
|
||||
mountPath: string;
|
||||
secretName: string;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodTemplate {
|
||||
options: WorkspacesPodTemplateOptions;
|
||||
podMetadata: WorkspacesPodMetadata;
|
||||
volumes: WorkspacesPodVolumes;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodTemplateMutate {
|
||||
options: WorkspacesPodTemplateOptionsMutate;
|
||||
podMetadata: WorkspacesPodMetadataMutate;
|
||||
volumes: WorkspacesPodVolumesMutate;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodTemplateOptions {
|
||||
imageConfig: WorkspacesImageConfig;
|
||||
podConfig: WorkspacesPodConfig;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodTemplateOptionsMutate {
|
||||
imageConfig: string;
|
||||
podConfig: string;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodVolumeInfo {
|
||||
mountPath: string;
|
||||
pvcName: string;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodVolumeMount {
|
||||
mountPath: string;
|
||||
pvcName: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface WorkspacesPodVolumes {
|
||||
data: WorkspacesPodVolumeInfo[];
|
||||
home?: WorkspacesPodVolumeInfo;
|
||||
secrets?: WorkspacesPodSecretInfo[];
|
||||
}
|
||||
|
||||
export interface WorkspacesPodVolumesMutate {
|
||||
data: WorkspacesPodVolumeMount[];
|
||||
home?: string;
|
||||
secrets?: WorkspacesPodSecretMount[];
|
||||
}
|
||||
|
||||
export interface WorkspacesRedirectMessage {
|
||||
level: WorkspacesRedirectMessageLevel;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface WorkspacesRedirectStep {
|
||||
message?: WorkspacesRedirectMessage;
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
}
|
||||
|
||||
export interface WorkspacesService {
|
||||
httpService?: WorkspacesHttpService;
|
||||
}
|
||||
|
||||
export interface WorkspacesWorkspace {
|
||||
activity: WorkspacesActivity;
|
||||
deferUpdates: boolean;
|
||||
name: string;
|
||||
namespace: string;
|
||||
paused: boolean;
|
||||
pausedTime: number;
|
||||
pendingRestart: boolean;
|
||||
podTemplate: WorkspacesPodTemplate;
|
||||
services: WorkspacesService[];
|
||||
state: WorkspacesWorkspaceState;
|
||||
stateMessage: string;
|
||||
workspaceKind: WorkspacesWorkspaceKindInfo;
|
||||
}
|
||||
|
||||
export interface WorkspacesWorkspaceCreate {
|
||||
deferUpdates: boolean;
|
||||
kind: string;
|
||||
name: string;
|
||||
paused: boolean;
|
||||
podTemplate: WorkspacesPodTemplateMutate;
|
||||
}
|
||||
|
||||
export interface WorkspacesWorkspaceKindInfo {
|
||||
icon: WorkspacesImageRef;
|
||||
logo: WorkspacesImageRef;
|
||||
missing: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/** Kubernetes YAML manifest of a WorkspaceKind */
|
||||
export type CreateWorkspaceKindPayload = string;
|
|
@ -0,0 +1,163 @@
|
|||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import type { AxiosInstance, AxiosRequestConfig, HeadersDefaults, ResponseType } from 'axios';
|
||||
import axios from 'axios';
|
||||
|
||||
export type QueryParamsType = Record<string | number, any>;
|
||||
|
||||
export interface FullRequestParams
|
||||
extends Omit<AxiosRequestConfig, 'data' | 'params' | 'url' | 'responseType'> {
|
||||
/** set parameter to `true` for call `securityWorker` for this request */
|
||||
secure?: boolean;
|
||||
/** request path */
|
||||
path: string;
|
||||
/** content type of request body */
|
||||
type?: ContentType;
|
||||
/** query params */
|
||||
query?: QueryParamsType;
|
||||
/** format of response (i.e. response.json() -> format: "json") */
|
||||
format?: ResponseType;
|
||||
/** request body */
|
||||
body?: unknown;
|
||||
}
|
||||
|
||||
export type RequestParams = Omit<FullRequestParams, 'body' | 'method' | 'query' | 'path'>;
|
||||
|
||||
export interface ApiConfig<SecurityDataType = unknown>
|
||||
extends Omit<AxiosRequestConfig, 'data' | 'cancelToken'> {
|
||||
securityWorker?: (
|
||||
securityData: SecurityDataType | null,
|
||||
) => Promise<AxiosRequestConfig | void> | AxiosRequestConfig | void;
|
||||
secure?: boolean;
|
||||
format?: ResponseType;
|
||||
}
|
||||
|
||||
export enum ContentType {
|
||||
Json = 'application/json',
|
||||
JsonApi = 'application/vnd.api+json',
|
||||
FormData = 'multipart/form-data',
|
||||
UrlEncoded = 'application/x-www-form-urlencoded',
|
||||
Text = 'text/plain',
|
||||
}
|
||||
|
||||
export class HttpClient<SecurityDataType = unknown> {
|
||||
public instance: AxiosInstance;
|
||||
private securityData: SecurityDataType | null = null;
|
||||
private securityWorker?: ApiConfig<SecurityDataType>['securityWorker'];
|
||||
private secure?: boolean;
|
||||
private format?: ResponseType;
|
||||
|
||||
constructor({
|
||||
securityWorker,
|
||||
secure,
|
||||
format,
|
||||
...axiosConfig
|
||||
}: ApiConfig<SecurityDataType> = {}) {
|
||||
this.instance = axios.create({
|
||||
...axiosConfig,
|
||||
baseURL: axiosConfig.baseURL || 'http://localhost:4000/api/v1',
|
||||
});
|
||||
this.secure = secure;
|
||||
this.format = format;
|
||||
this.securityWorker = securityWorker;
|
||||
}
|
||||
|
||||
public setSecurityData = (data: SecurityDataType | null) => {
|
||||
this.securityData = data;
|
||||
};
|
||||
|
||||
protected mergeRequestParams(
|
||||
params1: AxiosRequestConfig,
|
||||
params2?: AxiosRequestConfig,
|
||||
): AxiosRequestConfig {
|
||||
const method = params1.method || (params2 && params2.method);
|
||||
|
||||
return {
|
||||
...this.instance.defaults,
|
||||
...params1,
|
||||
...(params2 || {}),
|
||||
headers: {
|
||||
...((method &&
|
||||
this.instance.defaults.headers[method.toLowerCase() as keyof HeadersDefaults]) ||
|
||||
{}),
|
||||
...(params1.headers || {}),
|
||||
...((params2 && params2.headers) || {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected stringifyFormItem(formItem: unknown) {
|
||||
if (typeof formItem === 'object' && formItem !== null) {
|
||||
return JSON.stringify(formItem);
|
||||
} else {
|
||||
return `${formItem}`;
|
||||
}
|
||||
}
|
||||
|
||||
protected createFormData(input: Record<string, unknown>): FormData {
|
||||
if (input instanceof FormData) {
|
||||
return input;
|
||||
}
|
||||
return Object.keys(input || {}).reduce((formData, key) => {
|
||||
const property = input[key];
|
||||
const propertyContent: any[] = property instanceof Array ? property : [property];
|
||||
|
||||
for (const formItem of propertyContent) {
|
||||
const isFileType = formItem instanceof Blob || formItem instanceof File;
|
||||
formData.append(key, isFileType ? formItem : this.stringifyFormItem(formItem));
|
||||
}
|
||||
|
||||
return formData;
|
||||
}, new FormData());
|
||||
}
|
||||
|
||||
public request = async <T = any, _E = any>({
|
||||
secure,
|
||||
path,
|
||||
type,
|
||||
query,
|
||||
format,
|
||||
body,
|
||||
...params
|
||||
}: FullRequestParams): Promise<T> => {
|
||||
const secureParams =
|
||||
((typeof secure === 'boolean' ? secure : this.secure) &&
|
||||
this.securityWorker &&
|
||||
(await this.securityWorker(this.securityData))) ||
|
||||
{};
|
||||
const requestParams = this.mergeRequestParams(params, secureParams);
|
||||
const responseFormat = format || this.format || undefined;
|
||||
|
||||
if (type === ContentType.FormData && body && body !== null && typeof body === 'object') {
|
||||
body = this.createFormData(body as Record<string, unknown>);
|
||||
}
|
||||
|
||||
if (type === ContentType.Text && body && body !== null && typeof body !== 'string') {
|
||||
body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
return this.instance
|
||||
.request({
|
||||
...requestParams,
|
||||
headers: {
|
||||
...(requestParams.headers || {}),
|
||||
...(type ? { 'Content-Type': type } : {}),
|
||||
},
|
||||
params: query,
|
||||
responseType: responseFormat,
|
||||
data: body,
|
||||
url: path,
|
||||
})
|
||||
.then((response) => response.data);
|
||||
};
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
import { mockNamespaces } from '~/__mocks__/mockNamespaces';
|
||||
import { mockBFFResponse } from '~/__mocks__/utils';
|
||||
import { ErrorEnvelopeException } from '~/shared/api/apiUtils';
|
||||
import { ErrorEnvelope } from '~/shared/api/backendApiTypes';
|
||||
import { handleRestFailures } from '~/shared/api/errorUtils';
|
||||
import { NotReadyError } from '~/shared/utilities/useFetchState';
|
||||
|
||||
describe('handleRestFailures', () => {
|
||||
it('should successfully return namespaces', async () => {
|
||||
const result = await handleRestFailures(Promise.resolve(mockBFFResponse(mockNamespaces)));
|
||||
expect(result.data).toStrictEqual(mockNamespaces);
|
||||
});
|
||||
|
||||
it('should handle and throw notebook errors', async () => {
|
||||
const errorEnvelope: ErrorEnvelope = {
|
||||
error: {
|
||||
code: '<error_code>',
|
||||
message: '<error_message>',
|
||||
},
|
||||
};
|
||||
const expectedError = new ErrorEnvelopeException(errorEnvelope);
|
||||
await expect(handleRestFailures(Promise.reject(errorEnvelope))).rejects.toThrow(expectedError);
|
||||
});
|
||||
|
||||
it('should handle common state errors ', async () => {
|
||||
await expect(handleRestFailures(Promise.reject(new NotReadyError('error')))).rejects.toThrow(
|
||||
'error',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle other errors', async () => {
|
||||
await expect(handleRestFailures(Promise.reject(new Error('error')))).rejects.toThrow(
|
||||
'Error communicating with server',
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
import { BFF_API_VERSION } from '~/app/const';
|
||||
import { restGET, wrapRequest } from '~/shared/api/apiUtils';
|
||||
import { listNamespaces } from '~/shared/api/notebookService';
|
||||
|
||||
const mockRestResponse = { data: {} };
|
||||
const mockRestPromise = Promise.resolve(mockRestResponse);
|
||||
|
||||
jest.mock('~/shared/api/apiUtils', () => ({
|
||||
restCREATE: jest.fn(() => mockRestPromise),
|
||||
restGET: jest.fn(() => mockRestPromise),
|
||||
restPATCH: jest.fn(() => mockRestPromise),
|
||||
isNotebookResponse: jest.fn(() => true),
|
||||
extractNotebookResponse: jest.fn(() => mockRestResponse),
|
||||
wrapRequest: jest.fn(() => mockRestPromise),
|
||||
}));
|
||||
|
||||
const wrapRequestMock = jest.mocked(wrapRequest);
|
||||
const restGETMock = jest.mocked(restGET);
|
||||
const APIOptionsMock = {};
|
||||
|
||||
describe('getNamespaces', () => {
|
||||
it('should call restGET and handleRestFailures to fetch namespaces', async () => {
|
||||
const response = await listNamespaces(`/api/${BFF_API_VERSION}/namespaces`)(APIOptionsMock);
|
||||
expect(response).toEqual(mockRestResponse);
|
||||
expect(restGETMock).toHaveBeenCalledTimes(1);
|
||||
expect(restGETMock).toHaveBeenCalledWith(
|
||||
`/api/${BFF_API_VERSION}/namespaces`,
|
||||
`/namespaces`,
|
||||
{},
|
||||
APIOptionsMock,
|
||||
);
|
||||
expect(wrapRequestMock).toHaveBeenCalledTimes(1);
|
||||
expect(wrapRequestMock).toHaveBeenCalledWith(mockRestPromise);
|
||||
});
|
||||
});
|
|
@ -1,243 +1,34 @@
|
|||
import { ErrorEnvelope } from '~/shared/api/backendApiTypes';
|
||||
import { handleRestFailures } from '~/shared/api/errorUtils';
|
||||
import { APIOptions, ResponseBody } from '~/shared/api/types';
|
||||
import { EitherOrNone } from '~/shared/typeHelpers';
|
||||
import { AUTH_HEADER, DEV_MODE } from '~/shared/utilities/const';
|
||||
import axios from 'axios';
|
||||
import { ApiErrorEnvelope } from '~/generated/data-contracts';
|
||||
import { ApiCallResult } from '~/shared/api/types';
|
||||
|
||||
export const mergeRequestInit = (
|
||||
opts: APIOptions = {},
|
||||
specificOpts: RequestInit = {},
|
||||
): RequestInit => ({
|
||||
...specificOpts,
|
||||
...(opts.signal && { signal: opts.signal }),
|
||||
headers: {
|
||||
...(opts.headers ?? {}),
|
||||
...(specificOpts.headers ?? {}),
|
||||
},
|
||||
});
|
||||
|
||||
type CallRestJSONOptions = {
|
||||
queryParams?: Record<string, unknown>;
|
||||
parseJSON?: boolean;
|
||||
directYAML?: boolean;
|
||||
} & EitherOrNone<
|
||||
{
|
||||
fileContents: string;
|
||||
},
|
||||
{
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
const callRestJSON = <T>(
|
||||
host: string,
|
||||
path: string,
|
||||
requestInit: RequestInit,
|
||||
{ data, fileContents, queryParams, parseJSON = true, directYAML = false }: CallRestJSONOptions,
|
||||
): Promise<T> => {
|
||||
const { method, ...otherOptions } = requestInit;
|
||||
|
||||
const sanitizedQueryParams = queryParams
|
||||
? Object.entries(queryParams).reduce((acc, [key, value]) => {
|
||||
if (value) {
|
||||
return { ...acc, [key]: value };
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
: null;
|
||||
|
||||
const searchParams = sanitizedQueryParams
|
||||
? new URLSearchParams(sanitizedQueryParams).toString()
|
||||
: null;
|
||||
|
||||
let requestData: string | undefined;
|
||||
let contentType: string | undefined;
|
||||
let formData: FormData | undefined;
|
||||
if (fileContents) {
|
||||
if (directYAML) {
|
||||
requestData = fileContents;
|
||||
contentType = 'application/yaml';
|
||||
} else {
|
||||
formData = new FormData();
|
||||
formData.append(
|
||||
'uploadfile',
|
||||
new Blob([fileContents], { type: 'application/x-yaml' }),
|
||||
'uploadedFile.yml',
|
||||
);
|
||||
}
|
||||
} else if (data) {
|
||||
// It's OK for contentType and requestData to BOTH be undefined for e.g. a GET request or POST with no body.
|
||||
contentType = 'application/json;charset=UTF-8';
|
||||
requestData = JSON.stringify(data);
|
||||
function isApiErrorEnvelope(data: unknown): data is ApiErrorEnvelope {
|
||||
if (typeof data !== 'object' || data === null || !('error' in data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return fetch(`${host}${path}${searchParams ? `?${searchParams}` : ''}`, {
|
||||
...otherOptions,
|
||||
headers: {
|
||||
...otherOptions.headers,
|
||||
...(DEV_MODE && { [AUTH_HEADER]: localStorage.getItem(AUTH_HEADER) }),
|
||||
...(contentType && { 'Content-Type': contentType }),
|
||||
},
|
||||
method,
|
||||
body: formData ?? requestData,
|
||||
}).then((response) =>
|
||||
response.text().then((fetchedData) => {
|
||||
if (parseJSON) {
|
||||
return JSON.parse(fetchedData);
|
||||
}
|
||||
return fetchedData;
|
||||
}),
|
||||
);
|
||||
};
|
||||
const { error } = data as { error: unknown };
|
||||
|
||||
export const restGET = <T>(
|
||||
host: string,
|
||||
path: string,
|
||||
queryParams: Record<string, unknown> = {},
|
||||
options?: APIOptions,
|
||||
): Promise<T> =>
|
||||
callRestJSON<T>(host, path, mergeRequestInit(options, { method: 'GET' }), {
|
||||
queryParams,
|
||||
parseJSON: options?.parseJSON,
|
||||
});
|
||||
|
||||
/** Standard POST */
|
||||
export const restCREATE = <T>(
|
||||
host: string,
|
||||
path: string,
|
||||
data: Record<string, unknown>,
|
||||
queryParams: Record<string, unknown> = {},
|
||||
options?: APIOptions,
|
||||
): Promise<T> =>
|
||||
callRestJSON<T>(host, path, mergeRequestInit(options, { method: 'POST' }), {
|
||||
data,
|
||||
queryParams,
|
||||
parseJSON: options?.parseJSON,
|
||||
});
|
||||
|
||||
/** POST -- but with file content instead of body data */
|
||||
export const restFILE = <T>(
|
||||
host: string,
|
||||
path: string,
|
||||
fileContents: string,
|
||||
queryParams: Record<string, unknown> = {},
|
||||
options?: APIOptions,
|
||||
): Promise<T> =>
|
||||
callRestJSON<T>(host, path, mergeRequestInit(options, { method: 'POST' }), {
|
||||
fileContents,
|
||||
queryParams,
|
||||
parseJSON: options?.parseJSON,
|
||||
directYAML: options?.directYAML,
|
||||
});
|
||||
|
||||
/** POST -- but no body data -- targets simple endpoints */
|
||||
export const restENDPOINT = <T>(
|
||||
host: string,
|
||||
path: string,
|
||||
queryParams: Record<string, unknown> = {},
|
||||
options?: APIOptions,
|
||||
): Promise<T> =>
|
||||
callRestJSON<T>(host, path, mergeRequestInit(options, { method: 'POST' }), {
|
||||
queryParams,
|
||||
parseJSON: options?.parseJSON,
|
||||
});
|
||||
|
||||
export const restUPDATE = <T>(
|
||||
host: string,
|
||||
path: string,
|
||||
data: Record<string, unknown>,
|
||||
queryParams: Record<string, unknown> = {},
|
||||
options?: APIOptions,
|
||||
): Promise<T> =>
|
||||
callRestJSON<T>(host, path, mergeRequestInit(options, { method: 'PUT' }), {
|
||||
data,
|
||||
queryParams,
|
||||
parseJSON: options?.parseJSON,
|
||||
});
|
||||
|
||||
export const restPATCH = <T>(
|
||||
host: string,
|
||||
path: string,
|
||||
data: Record<string, unknown>,
|
||||
options?: APIOptions,
|
||||
): Promise<T> =>
|
||||
callRestJSON<T>(host, path, mergeRequestInit(options, { method: 'PATCH' }), {
|
||||
data,
|
||||
parseJSON: options?.parseJSON,
|
||||
});
|
||||
|
||||
export const restDELETE = <T>(
|
||||
host: string,
|
||||
path: string,
|
||||
data: Record<string, unknown>,
|
||||
queryParams: Record<string, unknown> = {},
|
||||
options?: APIOptions,
|
||||
): Promise<T> =>
|
||||
callRestJSON<T>(host, path, mergeRequestInit(options, { method: 'DELETE' }), {
|
||||
data,
|
||||
queryParams,
|
||||
parseJSON: options?.parseJSON,
|
||||
});
|
||||
|
||||
export const isNotebookResponse = <T>(response: unknown): response is ResponseBody<T> => {
|
||||
if (typeof response === 'object' && response !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const notebookBody = response as { data?: T };
|
||||
return notebookBody.data !== undefined;
|
||||
if (typeof error !== 'object' || error === null || !('message' in error)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isErrorEnvelope = (e: unknown): e is ErrorEnvelope =>
|
||||
typeof e === 'object' &&
|
||||
e !== null &&
|
||||
'error' in e &&
|
||||
typeof (e as Record<string, unknown>).error === 'object' &&
|
||||
(e as { error: unknown }).error !== null &&
|
||||
typeof (e as { error: { message: unknown } }).error.message === 'string';
|
||||
const { message } = error as { message?: unknown };
|
||||
|
||||
export function extractNotebookResponse<T>(response: unknown): T {
|
||||
// Check if this is an error envelope first
|
||||
if (isErrorEnvelope(response)) {
|
||||
throw new ErrorEnvelopeException(response);
|
||||
}
|
||||
if (isNotebookResponse<T>(response)) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error('Invalid response format');
|
||||
return typeof message === 'string';
|
||||
}
|
||||
|
||||
export function extractErrorEnvelope(error: unknown): ErrorEnvelope {
|
||||
if (isErrorEnvelope(error)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
const message =
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : 'Unexpected error';
|
||||
|
||||
return {
|
||||
error: {
|
||||
message,
|
||||
code: 'UNKNOWN_ERROR',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function wrapRequest<T>(promise: Promise<T>, extractData = true): Promise<T> {
|
||||
export async function safeApiCall<T>(fn: () => Promise<T>): Promise<ApiCallResult<T>> {
|
||||
try {
|
||||
const res = await handleRestFailures<T>(promise);
|
||||
return extractData ? extractNotebookResponse<T>(res) : res;
|
||||
} catch (error) {
|
||||
if (error instanceof ErrorEnvelopeException) {
|
||||
throw error;
|
||||
const data = await fn();
|
||||
return { ok: true, data };
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError<ApiErrorEnvelope>(error)) {
|
||||
const apiError = error.response?.data;
|
||||
if (apiError && isApiErrorEnvelope(apiError)) {
|
||||
return { ok: false, errorEnvelope: apiError };
|
||||
}
|
||||
}
|
||||
throw new ErrorEnvelopeException(extractErrorEnvelope(error));
|
||||
}
|
||||
}
|
||||
|
||||
export class ErrorEnvelopeException extends Error {
|
||||
constructor(public envelope: ErrorEnvelope) {
|
||||
super(envelope.error?.message ?? 'Unknown error');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,322 +0,0 @@
|
|||
export enum WorkspaceServiceStatus {
|
||||
ServiceStatusHealthy = 'Healthy',
|
||||
ServiceStatusUnhealthy = 'Unhealthy',
|
||||
}
|
||||
|
||||
export interface WorkspaceSystemInfo {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface HealthCheckResponse {
|
||||
status: WorkspaceServiceStatus;
|
||||
systemInfo: WorkspaceSystemInfo;
|
||||
}
|
||||
|
||||
export interface Namespace {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceImageRef {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface WorkspacePodConfigValue {
|
||||
id: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
labels: WorkspaceOptionLabel[];
|
||||
hidden: boolean;
|
||||
redirect?: WorkspaceOptionRedirect;
|
||||
clusterMetrics?: WorkspaceKindClusterMetrics;
|
||||
}
|
||||
|
||||
export interface WorkspaceKindPodConfig {
|
||||
default: string;
|
||||
values: WorkspacePodConfigValue[];
|
||||
}
|
||||
|
||||
export interface WorkspaceKindPodMetadata {
|
||||
labels: Record<string, string>;
|
||||
annotations: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface WorkspacePodVolumeMounts {
|
||||
home: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceOptionLabel {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export enum WorkspaceRedirectMessageLevel {
|
||||
RedirectMessageLevelInfo = 'Info',
|
||||
RedirectMessageLevelWarning = 'Warning',
|
||||
RedirectMessageLevelDanger = 'Danger',
|
||||
}
|
||||
|
||||
export interface WorkspaceRedirectMessage {
|
||||
text: string;
|
||||
level: WorkspaceRedirectMessageLevel;
|
||||
}
|
||||
|
||||
export interface WorkspaceOptionRedirect {
|
||||
to: string;
|
||||
message?: WorkspaceRedirectMessage;
|
||||
}
|
||||
|
||||
export interface WorkspaceImageConfigValue {
|
||||
id: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
labels: WorkspaceOptionLabel[];
|
||||
hidden: boolean;
|
||||
redirect?: WorkspaceOptionRedirect;
|
||||
clusterMetrics?: WorkspaceKindClusterMetrics;
|
||||
}
|
||||
|
||||
export interface WorkspaceKindImageConfig {
|
||||
default: string;
|
||||
values: WorkspaceImageConfigValue[];
|
||||
}
|
||||
|
||||
export interface WorkspaceKindPodTemplateOptions {
|
||||
imageConfig: WorkspaceKindImageConfig;
|
||||
podConfig: WorkspaceKindPodConfig;
|
||||
}
|
||||
|
||||
export interface WorkspaceKindPodTemplate {
|
||||
podMetadata: WorkspaceKindPodMetadata;
|
||||
volumeMounts: WorkspacePodVolumeMounts;
|
||||
options: WorkspaceKindPodTemplateOptions;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export type WorkspaceKindCreate = string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface WorkspaceKindUpdate {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface WorkspaceKindPatch {}
|
||||
|
||||
export interface WorkspaceKind {
|
||||
name: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
deprecated: boolean;
|
||||
deprecationMessage: string;
|
||||
hidden: boolean;
|
||||
icon: WorkspaceImageRef;
|
||||
logo: WorkspaceImageRef;
|
||||
clusterMetrics?: WorkspaceKindClusterMetrics;
|
||||
podTemplate: WorkspaceKindPodTemplate;
|
||||
}
|
||||
|
||||
export interface WorkspaceKindClusterMetrics {
|
||||
workspacesCount: number;
|
||||
}
|
||||
|
||||
export enum WorkspaceState {
|
||||
WorkspaceStateRunning = 'Running',
|
||||
WorkspaceStateTerminating = 'Terminating',
|
||||
WorkspaceStatePaused = 'Paused',
|
||||
WorkspaceStatePending = 'Pending',
|
||||
WorkspaceStateError = 'Error',
|
||||
WorkspaceStateUnknown = 'Unknown',
|
||||
}
|
||||
|
||||
export interface WorkspaceKindInfo {
|
||||
name: string;
|
||||
missing: boolean;
|
||||
icon: WorkspaceImageRef;
|
||||
logo: WorkspaceImageRef;
|
||||
}
|
||||
|
||||
export interface WorkspacePodMetadata {
|
||||
labels: Record<string, string>;
|
||||
annotations: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface WorkspacePodVolumeInfo {
|
||||
pvcName: string;
|
||||
mountPath: string;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export interface WorkspacePodSecretInfo {
|
||||
secretName: string;
|
||||
mountPath: string;
|
||||
defaultMode?: number;
|
||||
}
|
||||
|
||||
export interface WorkspaceOptionInfo {
|
||||
id: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
labels: WorkspaceOptionLabel[];
|
||||
}
|
||||
|
||||
export interface WorkspaceRedirectStep {
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
message?: WorkspaceRedirectMessage;
|
||||
}
|
||||
|
||||
export interface WorkspaceImageConfig {
|
||||
current: WorkspaceOptionInfo;
|
||||
desired?: WorkspaceOptionInfo;
|
||||
redirectChain?: WorkspaceRedirectStep[];
|
||||
}
|
||||
|
||||
export interface WorkspacePodConfig {
|
||||
current: WorkspaceOptionInfo;
|
||||
desired?: WorkspaceOptionInfo;
|
||||
redirectChain?: WorkspaceRedirectStep[];
|
||||
}
|
||||
|
||||
export interface WorkspacePodTemplateOptions {
|
||||
imageConfig: WorkspaceImageConfig;
|
||||
podConfig: WorkspacePodConfig;
|
||||
}
|
||||
|
||||
export interface WorkspacePodVolumes {
|
||||
home?: WorkspacePodVolumeInfo;
|
||||
data: WorkspacePodVolumeInfo[];
|
||||
secrets?: WorkspacePodSecretInfo[];
|
||||
}
|
||||
|
||||
export interface WorkspacePodTemplate {
|
||||
podMetadata: WorkspacePodMetadata;
|
||||
volumes: WorkspacePodVolumes;
|
||||
options: WorkspacePodTemplateOptions;
|
||||
}
|
||||
|
||||
export enum WorkspaceProbeResult {
|
||||
ProbeResultSuccess = 'Success',
|
||||
ProbeResultFailure = 'Failure',
|
||||
ProbeResultTimeout = 'Timeout',
|
||||
}
|
||||
|
||||
export interface WorkspaceLastProbeInfo {
|
||||
startTimeMs: number;
|
||||
endTimeMs: number;
|
||||
result: WorkspaceProbeResult;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceActivity {
|
||||
lastActivity: number;
|
||||
lastUpdate: number;
|
||||
lastProbe?: WorkspaceLastProbeInfo;
|
||||
}
|
||||
|
||||
export interface WorkspaceHttpService {
|
||||
displayName: string;
|
||||
httpPath: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceService {
|
||||
httpService?: WorkspaceHttpService;
|
||||
}
|
||||
|
||||
export interface WorkspacePodMetadataMutate {
|
||||
labels: Record<string, string>;
|
||||
annotations: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface WorkspacePodVolumeMount {
|
||||
pvcName: string;
|
||||
mountPath: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface WorkspacePodSecretMount {
|
||||
secretName: string;
|
||||
mountPath: string;
|
||||
defaultMode?: number;
|
||||
}
|
||||
|
||||
export interface WorkspacePodVolumesMutate {
|
||||
home?: string;
|
||||
data?: WorkspacePodVolumeMount[];
|
||||
secrets?: WorkspacePodSecretMount[];
|
||||
}
|
||||
|
||||
export interface WorkspacePodTemplateOptionsMutate {
|
||||
imageConfig: string;
|
||||
podConfig: string;
|
||||
}
|
||||
|
||||
export interface WorkspacePodTemplateMutate {
|
||||
podMetadata: WorkspacePodMetadataMutate;
|
||||
volumes: WorkspacePodVolumesMutate;
|
||||
options: WorkspacePodTemplateOptionsMutate;
|
||||
}
|
||||
|
||||
export interface Workspace {
|
||||
name: string;
|
||||
namespace: string;
|
||||
workspaceKind: WorkspaceKindInfo;
|
||||
deferUpdates: boolean;
|
||||
paused: boolean;
|
||||
pausedTime: number;
|
||||
pendingRestart: boolean;
|
||||
state: WorkspaceState;
|
||||
stateMessage: string;
|
||||
podTemplate: WorkspacePodTemplate;
|
||||
activity: WorkspaceActivity;
|
||||
services: WorkspaceService[];
|
||||
}
|
||||
|
||||
export interface WorkspaceCreate {
|
||||
name: string;
|
||||
kind: string;
|
||||
paused: boolean;
|
||||
deferUpdates: boolean;
|
||||
podTemplate: WorkspacePodTemplateMutate;
|
||||
}
|
||||
|
||||
// TODO: Update this type when applicable; meanwhile, it inherits from WorkspaceCreate
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface WorkspaceUpdate extends WorkspaceCreate {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface WorkspacePatch {}
|
||||
|
||||
export interface WorkspacePauseState {
|
||||
paused: boolean;
|
||||
}
|
||||
|
||||
export enum FieldErrorType {
|
||||
FieldValueRequired = 'FieldValueRequired',
|
||||
FieldValueInvalid = 'FieldValueInvalid',
|
||||
FieldValueNotSupported = 'FieldValueNotSupported',
|
||||
FieldValueDuplicate = 'FieldValueDuplicate',
|
||||
FieldValueTooLong = 'FieldValueTooLong',
|
||||
FieldValueForbidden = 'FieldValueForbidden',
|
||||
FieldValueNotFound = 'FieldValueNotFound',
|
||||
FieldValueConflict = 'FieldValueConflict',
|
||||
FieldValueTooShort = 'FieldValueTooShort',
|
||||
FieldValueUnknown = 'FieldValueUnknown',
|
||||
}
|
||||
|
||||
export interface ValidationError {
|
||||
type: FieldErrorType;
|
||||
field: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ErrorCause {
|
||||
validation_errors?: ValidationError[]; // TODO: backend is not using camelCase for this field
|
||||
}
|
||||
|
||||
export type HTTPError = {
|
||||
code: string;
|
||||
message: string;
|
||||
cause?: ErrorCause;
|
||||
};
|
||||
|
||||
export type ErrorEnvelope = {
|
||||
error: HTTPError | null;
|
||||
};
|
|
@ -1,47 +0,0 @@
|
|||
import {
|
||||
CreateWorkspace,
|
||||
CreateWorkspaceKind,
|
||||
DeleteWorkspace,
|
||||
DeleteWorkspaceKind,
|
||||
GetHealthCheck,
|
||||
GetWorkspace,
|
||||
GetWorkspaceKind,
|
||||
ListAllWorkspaces,
|
||||
ListNamespaces,
|
||||
ListWorkspaceKinds,
|
||||
ListWorkspaces,
|
||||
PatchWorkspace,
|
||||
PatchWorkspaceKind,
|
||||
PauseWorkspace,
|
||||
UpdateWorkspace,
|
||||
UpdateWorkspaceKind,
|
||||
} from '~/shared/api/notebookApi';
|
||||
import { APIOptions } from '~/shared/api/types';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type KubeflowSpecificAPICall = (opts: APIOptions, ...args: any[]) => Promise<unknown>;
|
||||
type KubeflowAPICall<ActualCall extends KubeflowSpecificAPICall> = (hostPath: string) => ActualCall;
|
||||
|
||||
// Health
|
||||
export type GetHealthCheckAPI = KubeflowAPICall<GetHealthCheck>;
|
||||
|
||||
// Namespace
|
||||
export type ListNamespacesAPI = KubeflowAPICall<ListNamespaces>;
|
||||
|
||||
// Workspace
|
||||
export type ListAllWorkspacesAPI = KubeflowAPICall<ListAllWorkspaces>;
|
||||
export type ListWorkspacesAPI = KubeflowAPICall<ListWorkspaces>;
|
||||
export type CreateWorkspaceAPI = KubeflowAPICall<CreateWorkspace>;
|
||||
export type GetWorkspaceAPI = KubeflowAPICall<GetWorkspace>;
|
||||
export type UpdateWorkspaceAPI = KubeflowAPICall<UpdateWorkspace>;
|
||||
export type PatchWorkspaceAPI = KubeflowAPICall<PatchWorkspace>;
|
||||
export type DeleteWorkspaceAPI = KubeflowAPICall<DeleteWorkspace>;
|
||||
export type PauseWorkspaceAPI = KubeflowAPICall<PauseWorkspace>;
|
||||
|
||||
// WorkspaceKind
|
||||
export type ListWorkspaceKindsAPI = KubeflowAPICall<ListWorkspaceKinds>;
|
||||
export type CreateWorkspaceKindAPI = KubeflowAPICall<CreateWorkspaceKind>;
|
||||
export type GetWorkspaceKindAPI = KubeflowAPICall<GetWorkspaceKind>;
|
||||
export type UpdateWorkspaceKindAPI = KubeflowAPICall<UpdateWorkspaceKind>;
|
||||
export type PatchWorkspaceKindAPI = KubeflowAPICall<PatchWorkspaceKind>;
|
||||
export type DeleteWorkspaceKindAPI = KubeflowAPICall<DeleteWorkspaceKind>;
|
|
@ -1,16 +0,0 @@
|
|||
import { ErrorEnvelopeException, isErrorEnvelope } from '~/shared/api//apiUtils';
|
||||
import { isCommonStateError } from '~/shared/utilities/useFetchState';
|
||||
|
||||
export const handleRestFailures = <T>(promise: Promise<T>): Promise<T> =>
|
||||
promise.catch((e) => {
|
||||
if (isErrorEnvelope(e)) {
|
||||
throw new ErrorEnvelopeException(e);
|
||||
}
|
||||
if (isCommonStateError(e)) {
|
||||
// Common state errors are handled by useFetchState at storage level, let them deal with it
|
||||
throw e;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Unknown API error', e);
|
||||
throw new Error('Error communicating with server');
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* -----------------------------------------------------------------------------
|
||||
* Experimental API Extensions
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
* This file contains manually implemented API endpoints that are not yet
|
||||
* available in the official Swagger specification provided by the backend.
|
||||
*
|
||||
* The structure, naming, and typing follow the same conventions as the
|
||||
* `swagger-typescript-api` generated clients (HttpClient, RequestParams, etc.)
|
||||
* under `src/generated` folder to ensure consistency across the codebase
|
||||
* and future compatibility.
|
||||
*
|
||||
* These endpoints are "experimental" in the sense that they either:
|
||||
* - Reflect endpoints that exist but are not documented in the Swagger spec.
|
||||
* - Represent planned or internal APIs not yet formalized by the backend.
|
||||
*
|
||||
* Once the backend Swagger specification includes these endpoints, code in this
|
||||
* file should be removed, and the corresponding generated modules should be
|
||||
* used instead.
|
||||
*/
|
||||
|
||||
// NOTE: All available endpoints are already implemented in the generated code.
|
|
@ -1,95 +1,23 @@
|
|||
import {
|
||||
HealthCheckResponse,
|
||||
Namespace,
|
||||
Workspace,
|
||||
WorkspaceCreate,
|
||||
WorkspaceKind,
|
||||
WorkspaceKindPatch,
|
||||
WorkspaceKindUpdate,
|
||||
WorkspacePatch,
|
||||
WorkspacePauseState,
|
||||
WorkspaceUpdate,
|
||||
} from '~/shared/api/backendApiTypes';
|
||||
import { APIOptions, RequestData } from '~/shared/api/types';
|
||||
import { Healthcheck } from '~/generated/Healthcheck';
|
||||
import { Namespaces } from '~/generated/Namespaces';
|
||||
import { Workspacekinds } from '~/generated/Workspacekinds';
|
||||
import { Workspaces } from '~/generated/Workspaces';
|
||||
import { ApiInstance } from '~/shared/api/types';
|
||||
|
||||
// Health
|
||||
export type GetHealthCheck = (opts: APIOptions) => Promise<HealthCheckResponse>;
|
||||
export interface NotebookApis {
|
||||
healthCheck: ApiInstance<typeof Healthcheck>;
|
||||
namespaces: ApiInstance<typeof Namespaces>;
|
||||
workspaces: ApiInstance<typeof Workspaces>;
|
||||
workspaceKinds: ApiInstance<typeof Workspacekinds>;
|
||||
}
|
||||
|
||||
// Namespace
|
||||
export type ListNamespaces = (opts: APIOptions) => Promise<Namespace[]>;
|
||||
export const notebookApisImpl = (path: string): NotebookApis => {
|
||||
const commonConfig = { baseURL: path };
|
||||
|
||||
// Workspace
|
||||
export type ListAllWorkspaces = (opts: APIOptions) => Promise<Workspace[]>;
|
||||
export type ListWorkspaces = (opts: APIOptions, namespace: string) => Promise<Workspace[]>;
|
||||
export type GetWorkspace = (
|
||||
opts: APIOptions,
|
||||
namespace: string,
|
||||
workspace: string,
|
||||
) => Promise<Workspace>;
|
||||
export type CreateWorkspace = (
|
||||
opts: APIOptions,
|
||||
namespace: string,
|
||||
data: RequestData<WorkspaceCreate>,
|
||||
) => Promise<Workspace>;
|
||||
export type UpdateWorkspace = (
|
||||
opts: APIOptions,
|
||||
namespace: string,
|
||||
workspace: string,
|
||||
data: RequestData<WorkspaceUpdate>,
|
||||
) => Promise<Workspace>;
|
||||
export type PatchWorkspace = (
|
||||
opts: APIOptions,
|
||||
namespace: string,
|
||||
workspace: string,
|
||||
data: RequestData<WorkspacePatch>,
|
||||
) => Promise<Workspace>;
|
||||
export type DeleteWorkspace = (
|
||||
opts: APIOptions,
|
||||
namespace: string,
|
||||
workspace: string,
|
||||
) => Promise<void>;
|
||||
export type PauseWorkspace = (
|
||||
opts: APIOptions,
|
||||
namespace: string,
|
||||
workspace: string,
|
||||
data: RequestData<WorkspacePauseState>,
|
||||
) => Promise<WorkspacePauseState>;
|
||||
|
||||
// WorkspaceKind
|
||||
export type ListWorkspaceKinds = (opts: APIOptions) => Promise<WorkspaceKind[]>;
|
||||
export type GetWorkspaceKind = (opts: APIOptions, kind: string) => Promise<WorkspaceKind>;
|
||||
export type CreateWorkspaceKind = (opts: APIOptions, data: string) => Promise<WorkspaceKind>;
|
||||
export type UpdateWorkspaceKind = (
|
||||
opts: APIOptions,
|
||||
kind: string,
|
||||
data: RequestData<WorkspaceKindUpdate>,
|
||||
) => Promise<WorkspaceKind>;
|
||||
export type PatchWorkspaceKind = (
|
||||
opts: APIOptions,
|
||||
kind: string,
|
||||
data: RequestData<WorkspaceKindPatch>,
|
||||
) => Promise<WorkspaceKind>;
|
||||
export type DeleteWorkspaceKind = (opts: APIOptions, kind: string) => Promise<void>;
|
||||
|
||||
export type NotebookAPIs = {
|
||||
// Health
|
||||
getHealthCheck: GetHealthCheck;
|
||||
// Namespace
|
||||
listNamespaces: ListNamespaces;
|
||||
// Workspace
|
||||
listAllWorkspaces: ListAllWorkspaces;
|
||||
listWorkspaces: ListWorkspaces;
|
||||
getWorkspace: GetWorkspace;
|
||||
createWorkspace: CreateWorkspace;
|
||||
updateWorkspace: UpdateWorkspace;
|
||||
patchWorkspace: PatchWorkspace;
|
||||
deleteWorkspace: DeleteWorkspace;
|
||||
pauseWorkspace: PauseWorkspace;
|
||||
// WorkspaceKind
|
||||
listWorkspaceKinds: ListWorkspaceKinds;
|
||||
getWorkspaceKind: GetWorkspaceKind;
|
||||
createWorkspaceKind: CreateWorkspaceKind;
|
||||
updateWorkspaceKind: UpdateWorkspaceKind;
|
||||
patchWorkspaceKind: PatchWorkspaceKind;
|
||||
deleteWorkspaceKind: DeleteWorkspaceKind;
|
||||
return {
|
||||
healthCheck: new Healthcheck(commonConfig),
|
||||
namespaces: new Namespaces(commonConfig),
|
||||
workspaces: new Workspaces(commonConfig),
|
||||
workspaceKinds: new Workspacekinds(commonConfig),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
import {
|
||||
restCREATE,
|
||||
restDELETE,
|
||||
restFILE,
|
||||
restGET,
|
||||
restPATCH,
|
||||
restUPDATE,
|
||||
wrapRequest,
|
||||
} from '~/shared/api/apiUtils';
|
||||
import {
|
||||
CreateWorkspaceAPI,
|
||||
CreateWorkspaceKindAPI,
|
||||
DeleteWorkspaceAPI,
|
||||
DeleteWorkspaceKindAPI,
|
||||
GetHealthCheckAPI,
|
||||
GetWorkspaceAPI,
|
||||
GetWorkspaceKindAPI,
|
||||
ListAllWorkspacesAPI,
|
||||
ListNamespacesAPI,
|
||||
ListWorkspaceKindsAPI,
|
||||
ListWorkspacesAPI,
|
||||
PatchWorkspaceAPI,
|
||||
PatchWorkspaceKindAPI,
|
||||
PauseWorkspaceAPI,
|
||||
UpdateWorkspaceAPI,
|
||||
UpdateWorkspaceKindAPI,
|
||||
} from '~/shared/api/callTypes';
|
||||
|
||||
export const getHealthCheck: GetHealthCheckAPI = (hostPath) => (opts) =>
|
||||
wrapRequest(restGET(hostPath, `/healthcheck`, {}, opts), false);
|
||||
|
||||
export const listNamespaces: ListNamespacesAPI = (hostPath) => (opts) =>
|
||||
wrapRequest(restGET(hostPath, `/namespaces`, {}, opts));
|
||||
|
||||
export const listAllWorkspaces: ListAllWorkspacesAPI = (hostPath) => (opts) =>
|
||||
wrapRequest(restGET(hostPath, `/workspaces`, {}, opts));
|
||||
|
||||
export const listWorkspaces: ListWorkspacesAPI = (hostPath) => (opts, namespace) =>
|
||||
wrapRequest(restGET(hostPath, `/workspaces/${namespace}`, {}, opts));
|
||||
|
||||
export const getWorkspace: GetWorkspaceAPI = (hostPath) => (opts, namespace, workspace) =>
|
||||
wrapRequest(restGET(hostPath, `/workspaces/${namespace}/${workspace}`, {}, opts));
|
||||
|
||||
export const createWorkspace: CreateWorkspaceAPI = (hostPath) => (opts, namespace, data) =>
|
||||
wrapRequest(restCREATE(hostPath, `/workspaces/${namespace}`, data, {}, opts));
|
||||
|
||||
export const updateWorkspace: UpdateWorkspaceAPI =
|
||||
(hostPath) => (opts, namespace, workspace, data) =>
|
||||
wrapRequest(restUPDATE(hostPath, `/workspaces/${namespace}/${workspace}`, data, {}, opts));
|
||||
|
||||
export const patchWorkspace: PatchWorkspaceAPI = (hostPath) => (opts, namespace, workspace, data) =>
|
||||
wrapRequest(restPATCH(hostPath, `/workspaces/${namespace}/${workspace}`, data, opts));
|
||||
|
||||
export const deleteWorkspace: DeleteWorkspaceAPI = (hostPath) => (opts, namespace, workspace) =>
|
||||
wrapRequest(restDELETE(hostPath, `/workspaces/${namespace}/${workspace}`, {}, {}, opts), false);
|
||||
|
||||
export const pauseWorkspace: PauseWorkspaceAPI = (hostPath) => (opts, namespace, workspace, data) =>
|
||||
wrapRequest(
|
||||
restCREATE(hostPath, `/workspaces/${namespace}/${workspace}/actions/pause`, data, {}, opts),
|
||||
);
|
||||
|
||||
export const listWorkspaceKinds: ListWorkspaceKindsAPI = (hostPath) => (opts) =>
|
||||
wrapRequest(restGET(hostPath, `/workspacekinds`, {}, opts));
|
||||
|
||||
export const getWorkspaceKind: GetWorkspaceKindAPI = (hostPath) => (opts, kind) =>
|
||||
wrapRequest(restGET(hostPath, `/workspacekinds/${kind}`, {}, opts));
|
||||
|
||||
export const createWorkspaceKind: CreateWorkspaceKindAPI = (hostPath) => (opts, data) =>
|
||||
wrapRequest(restFILE(hostPath, `/workspacekinds`, data, {}, opts));
|
||||
|
||||
export const updateWorkspaceKind: UpdateWorkspaceKindAPI = (hostPath) => (opts, kind, data) =>
|
||||
wrapRequest(restUPDATE(hostPath, `/workspacekinds/${kind}`, data, {}, opts));
|
||||
|
||||
export const patchWorkspaceKind: PatchWorkspaceKindAPI = (hostPath) => (opts, kind, data) =>
|
||||
wrapRequest(restPATCH(hostPath, `/workspacekinds/${kind}`, data, opts));
|
||||
|
||||
export const deleteWorkspaceKind: DeleteWorkspaceKindAPI = (hostPath) => (opts, kind) =>
|
||||
wrapRequest(restDELETE(hostPath, `/workspacekinds/${kind}`, {}, {}, opts), false);
|
|
@ -1,9 +1,11 @@
|
|||
import { ApiErrorEnvelope } from '~/generated/data-contracts';
|
||||
import { ApiConfig, HttpClient } from '~/generated/http-client';
|
||||
|
||||
export type APIOptions = {
|
||||
dryRun?: boolean;
|
||||
signal?: AbortSignal;
|
||||
parseJSON?: boolean;
|
||||
headers?: Record<string, string>;
|
||||
directYAML?: boolean;
|
||||
};
|
||||
|
||||
export type APIState<T> = {
|
||||
|
@ -13,11 +15,14 @@ export type APIState<T> = {
|
|||
api: T;
|
||||
};
|
||||
|
||||
export type ResponseBody<T> = {
|
||||
data: T;
|
||||
metadata?: Record<string, unknown>;
|
||||
export type RemoveHttpClient<T> = Omit<T, keyof HttpClient<unknown>>;
|
||||
|
||||
export type WithExperimental<TBase, TExperimental> = TBase & {
|
||||
experimental: TExperimental;
|
||||
};
|
||||
|
||||
export type RequestData<T> = {
|
||||
data: T;
|
||||
};
|
||||
export type ApiClass = abstract new (config?: ApiConfig) => object;
|
||||
export type ApiInstance<T extends ApiClass> = RemoveHttpClient<InstanceType<T>>;
|
||||
export type ApiCallResult<T> =
|
||||
| { ok: true; data: T }
|
||||
| { ok: false; errorEnvelope: ApiErrorEnvelope };
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
import {
|
||||
HealthCheckResponse,
|
||||
Namespace,
|
||||
Workspace,
|
||||
WorkspaceKind,
|
||||
WorkspaceKindInfo,
|
||||
WorkspacePauseState,
|
||||
WorkspaceRedirectMessageLevel,
|
||||
WorkspaceServiceStatus,
|
||||
WorkspaceState,
|
||||
} from '~/shared/api/backendApiTypes';
|
||||
ActionsWorkspaceActionPause,
|
||||
HealthCheckHealthCheck,
|
||||
HealthCheckServiceStatus,
|
||||
NamespacesNamespace,
|
||||
WorkspacekindsRedirectMessageLevel,
|
||||
WorkspacekindsWorkspaceKind,
|
||||
WorkspacesWorkspace,
|
||||
WorkspacesWorkspaceKindInfo,
|
||||
WorkspacesWorkspaceState,
|
||||
} from '~/generated/data-contracts';
|
||||
|
||||
export const buildMockHealthCheckResponse = (
|
||||
healthCheckResponse?: Partial<HealthCheckResponse>,
|
||||
): HealthCheckResponse => ({
|
||||
status: WorkspaceServiceStatus.ServiceStatusHealthy,
|
||||
healthCheckResponse?: Partial<HealthCheckHealthCheck>,
|
||||
): HealthCheckHealthCheck => ({
|
||||
status: HealthCheckServiceStatus.ServiceStatusHealthy,
|
||||
systemInfo: { version: '1.0.0' },
|
||||
...healthCheckResponse,
|
||||
});
|
||||
|
||||
export const buildMockNamespace = (namespace?: Partial<Namespace>): Namespace => ({
|
||||
export const buildMockNamespace = (
|
||||
namespace?: Partial<NamespacesNamespace>,
|
||||
): NamespacesNamespace => ({
|
||||
name: 'default',
|
||||
...namespace,
|
||||
});
|
||||
|
||||
export const buildMockWorkspaceKindInfo = (
|
||||
workspaceKindInfo?: Partial<WorkspaceKindInfo>,
|
||||
): WorkspaceKindInfo => ({
|
||||
workspaceKindInfo?: Partial<WorkspacesWorkspaceKindInfo>,
|
||||
): WorkspacesWorkspaceKindInfo => ({
|
||||
name: 'jupyterlab',
|
||||
missing: false,
|
||||
icon: {
|
||||
|
@ -37,14 +39,16 @@ export const buildMockWorkspaceKindInfo = (
|
|||
...workspaceKindInfo,
|
||||
});
|
||||
|
||||
export const buildMockWorkspace = (workspace?: Partial<Workspace>): Workspace => ({
|
||||
export const buildMockWorkspace = (
|
||||
workspace?: Partial<WorkspacesWorkspace>,
|
||||
): WorkspacesWorkspace => ({
|
||||
name: 'My First Jupyter Notebook',
|
||||
namespace: 'default',
|
||||
workspaceKind: buildMockWorkspaceKindInfo(),
|
||||
paused: true,
|
||||
deferUpdates: true,
|
||||
pausedTime: new Date(2025, 3, 1).getTime(),
|
||||
state: WorkspaceState.WorkspaceStateRunning,
|
||||
state: WorkspacesWorkspaceState.WorkspaceStateRunning,
|
||||
stateMessage: 'Workspace is running',
|
||||
podTemplate: {
|
||||
podMetadata: {
|
||||
|
@ -133,7 +137,9 @@ export const buildMockWorkspace = (workspace?: Partial<Workspace>): Workspace =>
|
|||
...workspace,
|
||||
});
|
||||
|
||||
export const buildMockWorkspaceKind = (workspaceKind?: Partial<WorkspaceKind>): WorkspaceKind => ({
|
||||
export const buildMockWorkspaceKind = (
|
||||
workspaceKind?: Partial<WorkspacekindsWorkspaceKind>,
|
||||
): WorkspacekindsWorkspaceKind => ({
|
||||
name: 'jupyterlab',
|
||||
displayName: 'JupyterLab Notebook',
|
||||
description: 'A Workspace which runs JupyterLab in a Pod',
|
||||
|
@ -182,7 +188,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial<WorkspaceKind>):
|
|||
to: 'jupyterlab_scipy_190',
|
||||
message: {
|
||||
text: 'This update will change...',
|
||||
level: WorkspaceRedirectMessageLevel.RedirectMessageLevelInfo,
|
||||
level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelInfo,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -199,7 +205,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial<WorkspaceKind>):
|
|||
to: 'jupyterlab_scipy_200',
|
||||
message: {
|
||||
text: 'This update will change...',
|
||||
level: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
},
|
||||
},
|
||||
clusterMetrics: {
|
||||
|
@ -219,7 +225,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial<WorkspaceKind>):
|
|||
to: 'jupyterlab_scipy_210',
|
||||
message: {
|
||||
text: 'This update will change...',
|
||||
level: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
},
|
||||
},
|
||||
clusterMetrics: {
|
||||
|
@ -239,7 +245,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial<WorkspaceKind>):
|
|||
to: 'jupyterlab_scipy_220',
|
||||
message: {
|
||||
text: 'This update will change...',
|
||||
level: WorkspaceRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelWarning,
|
||||
},
|
||||
},
|
||||
clusterMetrics: {
|
||||
|
@ -264,7 +270,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial<WorkspaceKind>):
|
|||
to: 'small_cpu',
|
||||
message: {
|
||||
text: 'This update will change...',
|
||||
level: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger,
|
||||
level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelDanger,
|
||||
},
|
||||
},
|
||||
clusterMetrics: {
|
||||
|
@ -285,7 +291,7 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial<WorkspaceKind>):
|
|||
to: 'large_cpu',
|
||||
message: {
|
||||
text: 'This update will change...',
|
||||
level: WorkspaceRedirectMessageLevel.RedirectMessageLevelDanger,
|
||||
level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelDanger,
|
||||
},
|
||||
},
|
||||
clusterMetrics: {
|
||||
|
@ -299,9 +305,9 @@ export const buildMockWorkspaceKind = (workspaceKind?: Partial<WorkspaceKind>):
|
|||
...workspaceKind,
|
||||
});
|
||||
|
||||
export const buildMockPauseStateResponse = (
|
||||
pauseState?: Partial<WorkspacePauseState>,
|
||||
): WorkspacePauseState => ({
|
||||
export const buildMockActionsWorkspaceActionPause = (
|
||||
pauseState?: Partial<ActionsWorkspaceActionPause>,
|
||||
): ActionsWorkspaceActionPause => ({
|
||||
paused: true,
|
||||
...pauseState,
|
||||
});
|
||||
|
@ -309,9 +315,9 @@ export const buildMockPauseStateResponse = (
|
|||
export const buildMockWorkspaceList = (args: {
|
||||
count: number;
|
||||
namespace: string;
|
||||
kind: WorkspaceKindInfo;
|
||||
}): Workspace[] => {
|
||||
const states = Object.values(WorkspaceState);
|
||||
kind: WorkspacesWorkspaceKindInfo;
|
||||
}): WorkspacesWorkspace[] => {
|
||||
const states = Object.values(WorkspacesWorkspaceState);
|
||||
const imageConfigs = [
|
||||
{
|
||||
id: 'jupyterlab_scipy_190',
|
||||
|
@ -345,7 +351,7 @@ export const buildMockWorkspaceList = (args: {
|
|||
{ id: 'large_cpu', displayName: 'Large CPU' },
|
||||
];
|
||||
|
||||
const workspaces: Workspace[] = [];
|
||||
const workspaces: WorkspacesWorkspace[] = [];
|
||||
for (let i = 1; i <= args.count; i++) {
|
||||
const state = states[(i - 1) % states.length];
|
||||
const labels = {
|
||||
|
@ -368,7 +374,7 @@ export const buildMockWorkspaceList = (args: {
|
|||
workspaceKind: args.kind,
|
||||
state,
|
||||
stateMessage: `Workspace is in ${state} state`,
|
||||
paused: state === WorkspaceState.WorkspaceStatePaused,
|
||||
paused: state === WorkspacesWorkspaceState.WorkspaceStatePaused,
|
||||
pendingRestart: booleanValue,
|
||||
podTemplate: {
|
||||
podMetadata: { labels, annotations },
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import { ApiErrorEnvelope, FieldErrorType } from '~/generated/data-contracts';
|
||||
import { NotebookApis } from '~/shared/api/notebookApi';
|
||||
import {
|
||||
mockAllWorkspaces,
|
||||
mockedHealthCheckResponse,
|
||||
mockNamespaces,
|
||||
mockWorkspace1,
|
||||
mockWorkspaceKind1,
|
||||
mockWorkspaceKinds,
|
||||
} from '~/shared/mock/mockNotebookServiceData';
|
||||
import { buildAxiosError, isInvalidYaml } from '~/shared/mock/mockUtils';
|
||||
|
||||
const delay = (ms: number) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
|
||||
export const mockNotebookApisImpl = (): NotebookApis => ({
|
||||
healthCheck: {
|
||||
getHealthcheck: async () => mockedHealthCheckResponse,
|
||||
},
|
||||
namespaces: {
|
||||
listNamespaces: async () => ({ data: mockNamespaces }),
|
||||
},
|
||||
workspaces: {
|
||||
listAllWorkspaces: async () => ({ data: mockAllWorkspaces }),
|
||||
listWorkspacesByNamespace: async (namespace) => ({
|
||||
data: mockAllWorkspaces.filter((w) => w.namespace === namespace),
|
||||
}),
|
||||
getWorkspace: async (namespace, workspace) => ({
|
||||
data: mockAllWorkspaces.find((w) => w.name === workspace && w.namespace === namespace)!,
|
||||
}),
|
||||
createWorkspace: async () => ({ data: mockWorkspace1 }),
|
||||
deleteWorkspace: async () => {
|
||||
await delay(1500);
|
||||
},
|
||||
updateWorkspacePauseState: async (_namespace, _workspaceName, body) => {
|
||||
await delay(1500);
|
||||
return {
|
||||
data: { paused: body.data.paused },
|
||||
};
|
||||
},
|
||||
},
|
||||
workspaceKinds: {
|
||||
listWorkspaceKinds: async () => ({ data: mockWorkspaceKinds }),
|
||||
getWorkspaceKind: async (kind) => ({
|
||||
data: mockWorkspaceKinds.find((w) => w.name === kind)!,
|
||||
}),
|
||||
createWorkspaceKind: async (body) => {
|
||||
if (isInvalidYaml(body)) {
|
||||
const apiErrorEnvelope: ApiErrorEnvelope = {
|
||||
error: {
|
||||
code: 'invalid_yaml',
|
||||
message: 'Invalid YAML provided',
|
||||
cause: {
|
||||
// eslint-disable-next-line camelcase
|
||||
validation_errors: [
|
||||
{
|
||||
type: FieldErrorType.ErrorTypeRequired,
|
||||
field: 'spec.spawner.displayName',
|
||||
message: "Missing required 'spec.spawner.displayName' property",
|
||||
},
|
||||
{
|
||||
type: FieldErrorType.ErrorTypeInvalid,
|
||||
field: 'spec.spawner.xyz',
|
||||
message: "Unknown property 'spec.spawner.xyz'",
|
||||
},
|
||||
{
|
||||
type: FieldErrorType.ErrorTypeNotSupported,
|
||||
field: 'spec.spawner.hidden',
|
||||
message: "Invalid data type for 'spec.spawner.hidden', expected 'boolean'",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
throw buildAxiosError(apiErrorEnvelope);
|
||||
}
|
||||
return { data: mockWorkspaceKind1 };
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,107 +0,0 @@
|
|||
import { ErrorEnvelopeException } from '~/shared/api/apiUtils';
|
||||
import { FieldErrorType } from '~/shared/api/backendApiTypes';
|
||||
import {
|
||||
CreateWorkspaceAPI,
|
||||
CreateWorkspaceKindAPI,
|
||||
DeleteWorkspaceAPI,
|
||||
DeleteWorkspaceKindAPI,
|
||||
GetHealthCheckAPI,
|
||||
GetWorkspaceAPI,
|
||||
GetWorkspaceKindAPI,
|
||||
ListAllWorkspacesAPI,
|
||||
ListNamespacesAPI,
|
||||
ListWorkspaceKindsAPI,
|
||||
ListWorkspacesAPI,
|
||||
PatchWorkspaceAPI,
|
||||
PatchWorkspaceKindAPI,
|
||||
PauseWorkspaceAPI,
|
||||
UpdateWorkspaceAPI,
|
||||
UpdateWorkspaceKindAPI,
|
||||
} from '~/shared/api/callTypes';
|
||||
import {
|
||||
mockAllWorkspaces,
|
||||
mockedHealthCheckResponse,
|
||||
mockNamespaces,
|
||||
mockWorkspace1,
|
||||
mockWorkspaceKind1,
|
||||
mockWorkspaceKinds,
|
||||
} from '~/shared/mock/mockNotebookServiceData';
|
||||
import { isInvalidYaml } from '~/shared/mock/mockUtils';
|
||||
|
||||
const delay = (ms: number) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
|
||||
export const mockGetHealthCheck: GetHealthCheckAPI = () => async () => mockedHealthCheckResponse;
|
||||
|
||||
export const mockListNamespaces: ListNamespacesAPI = () => async () => mockNamespaces;
|
||||
|
||||
export const mockListAllWorkspaces: ListAllWorkspacesAPI = () => async () => mockAllWorkspaces;
|
||||
|
||||
export const mockListWorkspaces: ListWorkspacesAPI = () => async (_opts, namespace) =>
|
||||
mockAllWorkspaces.filter((workspace) => workspace.namespace === namespace);
|
||||
|
||||
export const mockGetWorkspace: GetWorkspaceAPI = () => async (_opts, namespace, workspace) =>
|
||||
mockAllWorkspaces.find((w) => w.name === workspace && w.namespace === namespace)!;
|
||||
|
||||
export const mockCreateWorkspace: CreateWorkspaceAPI = () => async () => mockWorkspace1;
|
||||
|
||||
export const mockUpdateWorkspace: UpdateWorkspaceAPI = () => async () => mockWorkspace1;
|
||||
|
||||
export const mockPatchWorkspace: PatchWorkspaceAPI = () => async () => mockWorkspace1;
|
||||
|
||||
export const mockDeleteWorkspace: DeleteWorkspaceAPI = () => async () => {
|
||||
await delay(1500);
|
||||
};
|
||||
|
||||
export const mockPauseWorkspace: PauseWorkspaceAPI =
|
||||
() => async (_opts, _namespace, _workspace, requestData) => {
|
||||
await delay(1500);
|
||||
return { paused: requestData.data.paused };
|
||||
};
|
||||
|
||||
export const mockListWorkspaceKinds: ListWorkspaceKindsAPI = () => async () => mockWorkspaceKinds;
|
||||
|
||||
export const mockGetWorkspaceKind: GetWorkspaceKindAPI = () => async (_opts, kind) =>
|
||||
mockWorkspaceKinds.find((w) => w.name === kind)!;
|
||||
|
||||
export const mockCreateWorkspaceKind: CreateWorkspaceKindAPI = () => async (_opts, data) => {
|
||||
if (isInvalidYaml(data)) {
|
||||
throw new ErrorEnvelopeException({
|
||||
error: {
|
||||
code: 'invalid_yaml',
|
||||
message: 'Invalid YAML provided',
|
||||
cause: {
|
||||
// eslint-disable-next-line camelcase
|
||||
validation_errors: [
|
||||
{
|
||||
type: FieldErrorType.FieldValueRequired,
|
||||
field: 'spec.spawner.displayName',
|
||||
message: "Missing required 'spec.spawner.displayName' property",
|
||||
},
|
||||
{
|
||||
type: FieldErrorType.FieldValueUnknown,
|
||||
field: 'spec.spawner.xyz',
|
||||
message: "Unknown property 'spec.spawner.xyz'",
|
||||
},
|
||||
{
|
||||
type: FieldErrorType.FieldValueNotSupported,
|
||||
field: 'spec.spawner.hidden',
|
||||
message: "Invalid data type for 'spec.spawner.hidden', expected 'boolean'",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return mockWorkspaceKind1;
|
||||
};
|
||||
|
||||
export const mockUpdateWorkspaceKind: UpdateWorkspaceKindAPI = () => async () => mockWorkspaceKind1;
|
||||
|
||||
export const mockPatchWorkspaceKind: PatchWorkspaceKindAPI = () => async () => mockWorkspaceKind1;
|
||||
|
||||
export const mockDeleteWorkspaceKind: DeleteWorkspaceKindAPI = () => async () => {
|
||||
await delay(1500);
|
||||
};
|
|
@ -1,9 +1,9 @@
|
|||
import {
|
||||
Workspace,
|
||||
WorkspaceKind,
|
||||
WorkspaceKindInfo,
|
||||
WorkspaceState,
|
||||
} from '~/shared/api/backendApiTypes';
|
||||
WorkspacekindsWorkspaceKind,
|
||||
WorkspacesWorkspace,
|
||||
WorkspacesWorkspaceKindInfo,
|
||||
WorkspacesWorkspaceState,
|
||||
} from '~/generated/data-contracts';
|
||||
import {
|
||||
buildMockHealthCheckResponse,
|
||||
buildMockNamespace,
|
||||
|
@ -24,7 +24,7 @@ export const mockNamespace3 = buildMockNamespace({ name: 'workspace-test-3' });
|
|||
export const mockNamespaces = [mockNamespace1, mockNamespace2, mockNamespace3];
|
||||
|
||||
// WorkspaceKind
|
||||
export const mockWorkspaceKind1: WorkspaceKind = buildMockWorkspaceKind({
|
||||
export const mockWorkspaceKind1: WorkspacekindsWorkspaceKind = buildMockWorkspaceKind({
|
||||
name: 'jupyterlab1',
|
||||
displayName: 'JupyterLab Notebook 1',
|
||||
clusterMetrics: {
|
||||
|
@ -32,7 +32,7 @@ export const mockWorkspaceKind1: WorkspaceKind = buildMockWorkspaceKind({
|
|||
},
|
||||
});
|
||||
|
||||
export const mockWorkspaceKind2: WorkspaceKind = buildMockWorkspaceKind({
|
||||
export const mockWorkspaceKind2: WorkspacekindsWorkspaceKind = buildMockWorkspaceKind({
|
||||
name: 'jupyterlab2',
|
||||
displayName: 'JupyterLab Notebook 2',
|
||||
clusterMetrics: {
|
||||
|
@ -40,7 +40,7 @@ export const mockWorkspaceKind2: WorkspaceKind = buildMockWorkspaceKind({
|
|||
},
|
||||
});
|
||||
|
||||
export const mockWorkspaceKind3: WorkspaceKind = buildMockWorkspaceKind({
|
||||
export const mockWorkspaceKind3: WorkspacekindsWorkspaceKind = buildMockWorkspaceKind({
|
||||
name: 'jupyterlab3',
|
||||
displayName: 'JupyterLab Notebook 3',
|
||||
clusterMetrics: {
|
||||
|
@ -50,25 +50,25 @@ export const mockWorkspaceKind3: WorkspaceKind = buildMockWorkspaceKind({
|
|||
|
||||
export const mockWorkspaceKinds = [mockWorkspaceKind1, mockWorkspaceKind2, mockWorkspaceKind3];
|
||||
|
||||
export const mockWorkspaceKindInfo1: WorkspaceKindInfo = buildMockWorkspaceKindInfo({
|
||||
export const mockWorkspaceKindInfo1: WorkspacesWorkspaceKindInfo = buildMockWorkspaceKindInfo({
|
||||
name: mockWorkspaceKind1.name,
|
||||
});
|
||||
|
||||
export const mockWorkspaceKindInfo2: WorkspaceKindInfo = buildMockWorkspaceKindInfo({
|
||||
export const mockWorkspaceKindInfo2: WorkspacesWorkspaceKindInfo = buildMockWorkspaceKindInfo({
|
||||
name: mockWorkspaceKind2.name,
|
||||
});
|
||||
|
||||
// Workspace
|
||||
export const mockWorkspace1: Workspace = buildMockWorkspace({
|
||||
export const mockWorkspace1: WorkspacesWorkspace = buildMockWorkspace({
|
||||
workspaceKind: mockWorkspaceKindInfo1,
|
||||
namespace: mockNamespace1.name,
|
||||
});
|
||||
|
||||
export const mockWorkspace2: Workspace = buildMockWorkspace({
|
||||
export const mockWorkspace2: WorkspacesWorkspace = buildMockWorkspace({
|
||||
name: 'My Second Jupyter Notebook',
|
||||
workspaceKind: mockWorkspaceKindInfo1,
|
||||
namespace: mockNamespace2.name,
|
||||
state: WorkspaceState.WorkspaceStatePaused,
|
||||
state: WorkspacesWorkspaceState.WorkspaceStatePaused,
|
||||
paused: false,
|
||||
deferUpdates: false,
|
||||
activity: {
|
||||
|
@ -133,11 +133,11 @@ export const mockWorkspace2: Workspace = buildMockWorkspace({
|
|||
},
|
||||
});
|
||||
|
||||
export const mockWorkspace3: Workspace = buildMockWorkspace({
|
||||
export const mockWorkspace3: WorkspacesWorkspace = buildMockWorkspace({
|
||||
name: 'My Third Jupyter Notebook',
|
||||
namespace: mockNamespace1.name,
|
||||
workspaceKind: mockWorkspaceKindInfo1,
|
||||
state: WorkspaceState.WorkspaceStateRunning,
|
||||
state: WorkspacesWorkspaceState.WorkspaceStateRunning,
|
||||
pendingRestart: true,
|
||||
activity: {
|
||||
lastActivity: new Date(2025, 2, 15).getTime(),
|
||||
|
@ -145,17 +145,17 @@ export const mockWorkspace3: Workspace = buildMockWorkspace({
|
|||
},
|
||||
});
|
||||
|
||||
export const mockWorkspace4 = buildMockWorkspace({
|
||||
export const mockWorkspace4: WorkspacesWorkspace = buildMockWorkspace({
|
||||
name: 'My Fourth Jupyter Notebook',
|
||||
namespace: mockNamespace2.name,
|
||||
state: WorkspaceState.WorkspaceStateError,
|
||||
state: WorkspacesWorkspaceState.WorkspaceStateError,
|
||||
workspaceKind: mockWorkspaceKindInfo2,
|
||||
});
|
||||
|
||||
export const mockWorkspace5 = buildMockWorkspace({
|
||||
export const mockWorkspace5: WorkspacesWorkspace = buildMockWorkspace({
|
||||
name: 'My Fifth Jupyter Notebook',
|
||||
namespace: mockNamespace2.name,
|
||||
state: WorkspaceState.WorkspaceStateTerminating,
|
||||
state: WorkspacesWorkspaceState.WorkspaceStateTerminating,
|
||||
workspaceKind: mockWorkspaceKindInfo2,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,38 @@
|
|||
import { AxiosError, AxiosHeaders, AxiosResponse } from 'axios';
|
||||
import yaml from 'js-yaml';
|
||||
import { ApiErrorEnvelope } from '~/generated/data-contracts';
|
||||
|
||||
// For testing purposes, a YAML string is considered invalid if it contains a specific pattern in the metadata name.
|
||||
export function isInvalidYaml(yamlString: string): boolean {
|
||||
const parsed = yaml.load(yamlString) as { metadata?: { name?: string } };
|
||||
return parsed.metadata?.name?.includes('-invalid') ?? false;
|
||||
}
|
||||
|
||||
export function buildAxiosError(
|
||||
envelope: ApiErrorEnvelope,
|
||||
status = 400,
|
||||
configOverrides: Partial<AxiosResponse['config']> = {},
|
||||
): AxiosError<ApiErrorEnvelope> {
|
||||
const config = {
|
||||
url: '',
|
||||
method: 'GET',
|
||||
headers: new AxiosHeaders(),
|
||||
...configOverrides,
|
||||
};
|
||||
|
||||
const response: AxiosResponse<ApiErrorEnvelope> = {
|
||||
data: envelope,
|
||||
status,
|
||||
statusText: 'Bad Request',
|
||||
headers: {},
|
||||
config,
|
||||
};
|
||||
|
||||
return new AxiosError<ApiErrorEnvelope>(
|
||||
envelope.error.message,
|
||||
envelope.error.code,
|
||||
config,
|
||||
undefined,
|
||||
response,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { Workspace, WorkspaceState, WorkspaceOptionLabel } from '~/shared/api/backendApiTypes';
|
||||
import {
|
||||
WorkspacesOptionLabel,
|
||||
WorkspacesWorkspace,
|
||||
WorkspacesWorkspaceState,
|
||||
} from '~/generated/data-contracts';
|
||||
import {
|
||||
CPU_UNITS,
|
||||
MEMORY_UNITS_FOR_PARSING,
|
||||
|
@ -28,7 +32,7 @@ export const parseResourceValue = (
|
|||
};
|
||||
|
||||
export const extractResourceValue = (
|
||||
workspace: Workspace,
|
||||
workspace: WorkspacesWorkspace,
|
||||
resourceType: ResourceType,
|
||||
): string | undefined =>
|
||||
workspace.podTemplate.options.podConfig.current.labels.find((label) => label.key === resourceType)
|
||||
|
@ -48,35 +52,40 @@ export const formatResourceValue = (v: string | undefined, resourceType?: Resour
|
|||
};
|
||||
|
||||
export const formatResourceFromWorkspace = (
|
||||
workspace: Workspace,
|
||||
workspace: WorkspacesWorkspace,
|
||||
resourceType: ResourceType,
|
||||
): string => formatResourceValue(extractResourceValue(workspace, resourceType), resourceType);
|
||||
|
||||
export const formatWorkspaceIdleState = (workspace: Workspace): string =>
|
||||
workspace.state !== WorkspaceState.WorkspaceStateRunning ? YesNoValue.Yes : YesNoValue.No;
|
||||
export const formatWorkspaceIdleState = (workspace: WorkspacesWorkspace): string =>
|
||||
workspace.state !== WorkspacesWorkspaceState.WorkspaceStateRunning
|
||||
? YesNoValue.Yes
|
||||
: YesNoValue.No;
|
||||
|
||||
export const isWorkspaceWithGpu = (workspace: Workspace): boolean =>
|
||||
export const isWorkspaceWithGpu = (workspace: WorkspacesWorkspace): boolean =>
|
||||
workspace.podTemplate.options.podConfig.current.labels.some((label) => label.key === 'gpu');
|
||||
|
||||
export const isWorkspaceIdle = (workspace: Workspace): boolean =>
|
||||
workspace.state !== WorkspaceState.WorkspaceStateRunning;
|
||||
export const isWorkspaceIdle = (workspace: WorkspacesWorkspace): boolean =>
|
||||
workspace.state !== WorkspacesWorkspaceState.WorkspaceStateRunning;
|
||||
|
||||
export const filterWorkspacesWithGpu = (workspaces: Workspace[]): Workspace[] =>
|
||||
export const filterWorkspacesWithGpu = (workspaces: WorkspacesWorkspace[]): WorkspacesWorkspace[] =>
|
||||
workspaces.filter(isWorkspaceWithGpu);
|
||||
|
||||
export const filterIdleWorkspaces = (workspaces: Workspace[]): Workspace[] =>
|
||||
export const filterIdleWorkspaces = (workspaces: WorkspacesWorkspace[]): WorkspacesWorkspace[] =>
|
||||
workspaces.filter(isWorkspaceIdle);
|
||||
|
||||
export const filterRunningWorkspaces = (workspaces: Workspace[]): Workspace[] =>
|
||||
workspaces.filter((workspace) => workspace.state === WorkspaceState.WorkspaceStateRunning);
|
||||
export const filterRunningWorkspaces = (workspaces: WorkspacesWorkspace[]): WorkspacesWorkspace[] =>
|
||||
workspaces.filter(
|
||||
(workspace) => workspace.state === WorkspacesWorkspaceState.WorkspaceStateRunning,
|
||||
);
|
||||
|
||||
export const filterIdleWorkspacesWithGpu = (workspaces: Workspace[]): Workspace[] =>
|
||||
filterIdleWorkspaces(filterWorkspacesWithGpu(workspaces));
|
||||
export const filterIdleWorkspacesWithGpu = (
|
||||
workspaces: WorkspacesWorkspace[],
|
||||
): WorkspacesWorkspace[] => filterIdleWorkspaces(filterWorkspacesWithGpu(workspaces));
|
||||
|
||||
export type WorkspaceGpuCountRecord = { workspaces: Workspace[]; gpuCount: number };
|
||||
export type WorkspaceGpuCountRecord = { workspaces: WorkspacesWorkspace[]; gpuCount: number };
|
||||
|
||||
export const groupWorkspacesByNamespaceAndGpu = (
|
||||
workspaces: Workspace[],
|
||||
workspaces: WorkspacesWorkspace[],
|
||||
order: 'ASC' | 'DESC' = 'DESC',
|
||||
): Record<string, WorkspaceGpuCountRecord> => {
|
||||
const grouped: Record<string, WorkspaceGpuCountRecord> = {};
|
||||
|
@ -97,7 +106,7 @@ export const groupWorkspacesByNamespaceAndGpu = (
|
|||
);
|
||||
};
|
||||
|
||||
export const countGpusFromWorkspaces = (workspaces: Workspace[]): number =>
|
||||
export const countGpusFromWorkspaces = (workspaces: WorkspacesWorkspace[]): number =>
|
||||
workspaces.reduce((total, workspace) => {
|
||||
const [gpuValue] = splitValueUnit(extractResourceValue(workspace, 'gpu') || '0', OTHER);
|
||||
return total + (gpuValue ?? 0);
|
||||
|
@ -124,7 +133,7 @@ export const formatLabelKey = (key: string): string => {
|
|||
export const isPackageLabel = (key: string): boolean => key.endsWith('Version');
|
||||
|
||||
// Extract package labels from workspace image config
|
||||
export const extractPackageLabels = (workspace: Workspace): WorkspaceOptionLabel[] =>
|
||||
export const extractPackageLabels = (workspace: WorkspacesWorkspace): WorkspacesOptionLabel[] =>
|
||||
workspace.podTemplate.options.imageConfig.current.labels.filter((label) =>
|
||||
isPackageLabel(label.key),
|
||||
);
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
const DEV_MODE = process.env.APP_ENV === 'development';
|
||||
const AUTH_HEADER = process.env.AUTH_HEADER || 'kubeflow-userid';
|
||||
export const DEV_MODE = process.env.APP_ENV === 'development';
|
||||
export const AUTH_HEADER = process.env.AUTH_HEADER || 'kubeflow-userid';
|
||||
|
||||
export { DEV_MODE, AUTH_HEADER };
|
||||
export const CONTENT_TYPE_KEY = 'Content-Type';
|
||||
|
||||
export enum ContentType {
|
||||
YAML = 'application/yaml',
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue