* Merge notebooks-v2 into kind_logo_modification/#148 branch Signed-off-by: Liav Weiss (EXT-Nokia) <liav.weiss.ext@nokia.com> * feat(ws): Notebooks 2.0 // Frontend // Workspaces table // Workspace Kind column #148 Signed-off-by: Liav Weiss (EXT-Nokia) <liav.weiss.ext@nokia.com> * feat(ws): Notebooks 2.0 // Frontend // Workspaces table // Workspace Kind column #148 Signed-off-by: Liav Weiss (EXT-Nokia) <liav.weiss.ext@nokia.com> * feat(ws): Notebooks 2.0 // Frontend // Workspaces table // Workspace Kind column #148 Signed-off-by: Liav Weiss (EXT-Nokia) <liav.weiss.ext@nokia.com> * feat(ws): Notebooks 2.0 // Frontend // Workspaces table // Workspace Kind column #148 Signed-off-by: Liav Weiss (EXT-Nokia) <liav.weiss.ext@nokia.com> * feat(ws): Notebooks 2.0 // Frontend // Workspaces table // Workspace Kind column #148 Signed-off-by: Liav Weiss (EXT-Nokia) <liav.weiss.ext@nokia.com> * feat(ws): Notebooks 2.0 // Frontend // Workspaces table // Workspace Kind column #148 Signed-off-by: Liav Weiss (EXT-Nokia) <liav.weiss.ext@nokia.com> --------- Signed-off-by: Liav Weiss (EXT-Nokia) <liav.weiss.ext@nokia.com> Co-authored-by: Liav Weiss (EXT-Nokia) <liav.weiss.ext@nokia.com>
This commit is contained in:
parent
2c05c38aaa
commit
055150bb2e
|
|
@ -60,7 +60,7 @@
|
||||||
"react-router-dom": "^6.26.1",
|
"react-router-dom": "^6.26.1",
|
||||||
"regenerator-runtime": "^0.13.11",
|
"regenerator-runtime": "^0.13.11",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"sass": "^1.83.1",
|
"sass": "^1.83.4",
|
||||||
"sass-loader": "^16.0.4",
|
"sass-loader": "^16.0.4",
|
||||||
"serve": "^14.2.1",
|
"serve": "^14.2.1",
|
||||||
"style-loader": "^3.3.4",
|
"style-loader": "^3.3.4",
|
||||||
|
|
@ -18869,10 +18869,11 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.83.1",
|
"version": "1.83.4",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.83.1.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.83.4.tgz",
|
||||||
"integrity": "sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA==",
|
"integrity": "sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.0",
|
"chokidar": "^4.0.0",
|
||||||
"immutable": "^5.0.2",
|
"immutable": "^5.0.2",
|
||||||
|
|
@ -18893,6 +18894,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz",
|
||||||
"integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==",
|
"integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"neo-async": "^2.6.2"
|
"neo-async": "^2.6.2"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
"react-router-dom": "^6.26.1",
|
"react-router-dom": "^6.26.1",
|
||||||
"regenerator-runtime": "^0.13.11",
|
"regenerator-runtime": "^0.13.11",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"sass": "^1.83.1",
|
"sass": "^1.83.4",
|
||||||
"sass-loader": "^16.0.4",
|
"sass-loader": "^16.0.4",
|
||||||
"serve": "^14.2.1",
|
"serve": "^14.2.1",
|
||||||
"style-loader": "^3.3.4",
|
"style-loader": "^3.3.4",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import {
|
||||||
|
mockWorkspaceKindsInValid,
|
||||||
|
mockWorkspaceKindsValid,
|
||||||
|
} from '~/__tests__/cypress/cypress/tests/mocked/workspaceKinds.mock';
|
||||||
|
|
||||||
|
describe('Test buildKindLogoDictionary Functionality', () => {
|
||||||
|
// Mock valid workspace kinds
|
||||||
|
context('With Valid Data', () => {
|
||||||
|
before(() => {
|
||||||
|
// Mock the API response
|
||||||
|
cy.intercept('GET', '/api/v1/workspacekinds', {
|
||||||
|
statusCode: 200,
|
||||||
|
body: mockWorkspaceKindsValid,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Visit the page
|
||||||
|
cy.visit('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch and populate kind logos', () => {
|
||||||
|
// Check that the logos are rendered in the table
|
||||||
|
cy.get('tbody tr').each(($row) => {
|
||||||
|
cy.wrap($row)
|
||||||
|
.find('td[data-label="Kind"]')
|
||||||
|
.within(() => {
|
||||||
|
cy.get('img')
|
||||||
|
.should('exist')
|
||||||
|
.then(($img) => {
|
||||||
|
// Ensure the image is fully loaded
|
||||||
|
cy.wrap($img[0]).should('have.prop', 'complete', true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock invalid workspace kinds
|
||||||
|
context('With Invalid Data', () => {
|
||||||
|
before(() => {
|
||||||
|
// Mock the API response for invalid workspace kinds
|
||||||
|
cy.intercept('GET', '/api/v1/workspacekinds', {
|
||||||
|
statusCode: 200,
|
||||||
|
body: mockWorkspaceKindsInValid,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Visit the page
|
||||||
|
cy.visit('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show a fallback icon when the logo URL is missing', () => {
|
||||||
|
cy.get('tbody tr').each(($row) => {
|
||||||
|
cy.wrap($row)
|
||||||
|
.find('td[data-label="Kind"]')
|
||||||
|
.within(() => {
|
||||||
|
// Ensure that the image is NOT rendered (because it's invalid or missing)
|
||||||
|
cy.get('img').should('not.exist'); // No images should be displayed
|
||||||
|
|
||||||
|
// Check if the fallback icon (TimesCircleIcon) is displayed
|
||||||
|
cy.get('svg').should('exist'); // Look for the SVG (TimesCircleIcon)
|
||||||
|
cy.get('svg').should('have.class', 'pf-v6-svg'); // Ensure the correct fallback icon class is applied (update the class name based on your icon library)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import type { WorkspaceKind } from '~/shared/types';
|
||||||
|
|
||||||
|
// Factory function to create a valid WorkspaceKind
|
||||||
|
function createMockWorkspaceKind(overrides: Partial<WorkspaceKind> = {}): WorkspaceKind {
|
||||||
|
return {
|
||||||
|
name: 'jupyter-lab',
|
||||||
|
displayName: 'JupyterLab Notebook',
|
||||||
|
description: 'A Workspace which runs JupyterLab in a Pod',
|
||||||
|
deprecated: false,
|
||||||
|
deprecationMessage: '',
|
||||||
|
hidden: false,
|
||||||
|
icon: {
|
||||||
|
url: 'https://jupyter.org/assets/favicons/apple-touch-icon-152x152.png',
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
url: 'https://upload.wikimedia.org/wikipedia/commons/3/38/Jupyter_logo.svg',
|
||||||
|
},
|
||||||
|
podTemplate: {
|
||||||
|
podMetadata: {
|
||||||
|
labels: { myWorkspaceKindLabel: 'my-value' },
|
||||||
|
annotations: { myWorkspaceKindAnnotation: 'my-value' },
|
||||||
|
},
|
||||||
|
volumeMounts: { home: '/home/jovyan' },
|
||||||
|
options: {
|
||||||
|
imageConfig: {
|
||||||
|
default: 'jupyterlab_scipy_190',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
id: 'jupyterlab_scipy_180',
|
||||||
|
displayName: 'jupyter-scipy:v1.8.0',
|
||||||
|
labels: { pythonVersion: '3.11' },
|
||||||
|
hidden: true,
|
||||||
|
redirect: {
|
||||||
|
to: 'jupyterlab_scipy_190',
|
||||||
|
message: {
|
||||||
|
text: 'This update will change...',
|
||||||
|
level: 'Info',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
podConfig: {
|
||||||
|
default: 'tiny_cpu',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
id: 'tiny_cpu',
|
||||||
|
displayName: 'Tiny CPU',
|
||||||
|
description: 'Pod with 0.1 CPU, 128 Mb RAM',
|
||||||
|
labels: { cpu: '100m', memory: '128Mi' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...overrides, // Allows customization
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate valid mock data with "data" property
|
||||||
|
export const mockWorkspaceKindsValid = {
|
||||||
|
data: [createMockWorkspaceKind()],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate invalid mock data with "data" property
|
||||||
|
export const mockWorkspaceKindsInValid = {
|
||||||
|
data: [
|
||||||
|
createMockWorkspaceKind({
|
||||||
|
logo: {
|
||||||
|
url: '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { WorkspaceKind } from '~/shared/types';
|
||||||
|
|
||||||
|
type KindLogoDict = Record<string, string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a dictionary of kind names to logos, and returns it.
|
||||||
|
* @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 {
|
||||||
|
const kindLogoDict: KindLogoDict = {};
|
||||||
|
|
||||||
|
for (const workspaceKind of workspaceKinds) {
|
||||||
|
try {
|
||||||
|
kindLogoDict[workspaceKind.name] = workspaceKind.logo.url;
|
||||||
|
} catch {
|
||||||
|
kindLogoDict[workspaceKind.name] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kindLogoDict;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { APIState } from '~/shared/api/types';
|
import { APIState } from '~/shared/api/types';
|
||||||
import { NotebookAPIs } from '~/app/types';
|
import { NotebookAPIs } from '~/app/types';
|
||||||
import { getNamespaces } from '~/shared/api/notebookService';
|
import { getNamespaces, getWorkspaceKinds } from '~/shared/api/notebookService';
|
||||||
import useAPIState from '~/shared/api/useAPIState';
|
import useAPIState from '~/shared/api/useAPIState';
|
||||||
|
|
||||||
export type NotebookAPIState = APIState<NotebookAPIs>;
|
export type NotebookAPIState = APIState<NotebookAPIs>;
|
||||||
|
|
@ -12,6 +12,7 @@ const useNotebookAPIState = (
|
||||||
const createAPI = React.useCallback(
|
const createAPI = React.useCallback(
|
||||||
(path: string) => ({
|
(path: string) => ({
|
||||||
getNamespaces: getNamespaces(path),
|
getNamespaces: getNamespaces(path),
|
||||||
|
getWorkspaceKinds: getWorkspaceKinds(path),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import useFetchState, {
|
||||||
|
FetchState,
|
||||||
|
FetchStateCallbackPromise,
|
||||||
|
} from '~/shared/utilities/useFetchState';
|
||||||
|
import { WorkspaceKind } from '~/shared/types';
|
||||||
|
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||||
|
|
||||||
|
const useWorkspaceKinds = (): FetchState<WorkspaceKind[]> => {
|
||||||
|
const { api, apiAvailable } = useNotebookAPI();
|
||||||
|
const call = React.useCallback<FetchStateCallbackPromise<WorkspaceKind[]>>(
|
||||||
|
(opts) => {
|
||||||
|
if (!apiAvailable) {
|
||||||
|
return Promise.reject(new Error('API not yet available'));
|
||||||
|
}
|
||||||
|
return api.getWorkspaceKinds(opts);
|
||||||
|
},
|
||||||
|
[api, apiAvailable],
|
||||||
|
);
|
||||||
|
|
||||||
|
return useFetchState(call, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useWorkspaceKinds;
|
||||||
|
|
@ -11,6 +11,8 @@ import {
|
||||||
Pagination,
|
Pagination,
|
||||||
Button,
|
Button,
|
||||||
Content,
|
Content,
|
||||||
|
Tooltip,
|
||||||
|
Brand,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
|
|
@ -24,10 +26,13 @@ import {
|
||||||
IActions,
|
IActions,
|
||||||
} from '@patternfly/react-table';
|
} from '@patternfly/react-table';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { CodeIcon } from '@patternfly/react-icons';
|
||||||
import { Workspace, WorkspacesColumnNames, WorkspaceState } from '~/shared/types';
|
import { Workspace, WorkspacesColumnNames, WorkspaceState } from '~/shared/types';
|
||||||
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
|
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
|
||||||
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
|
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
|
||||||
import DeleteModal from '~/shared/components/DeleteModal';
|
import DeleteModal from '~/shared/components/DeleteModal';
|
||||||
|
import { buildKindLogoDictionary } from '~/app/actions/WorkspaceKindsActions';
|
||||||
|
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
|
||||||
import Filter, { FilteredColumn } from 'shared/components/Filter';
|
import Filter, { FilteredColumn } from 'shared/components/Filter';
|
||||||
import { formatRam } from 'shared/utilities/WorkspaceResources';
|
import { formatRam } from 'shared/utilities/WorkspaceResources';
|
||||||
|
|
||||||
|
|
@ -131,6 +136,10 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const [workspaceKinds] = useWorkspaceKinds();
|
||||||
|
let kindLogoDict: Record<string, string> = {};
|
||||||
|
kindLogoDict = buildKindLogoDictionary(workspaceKinds);
|
||||||
|
|
||||||
// Table columns
|
// Table columns
|
||||||
const columnNames: WorkspacesColumnNames = {
|
const columnNames: WorkspacesColumnNames = {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
|
|
@ -419,7 +428,21 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Td dataLabel={columnNames.name}>{workspace.name}</Td>
|
<Td dataLabel={columnNames.name}>{workspace.name}</Td>
|
||||||
<Td dataLabel={columnNames.kind}>{workspace.kind}</Td>
|
<Td dataLabel={columnNames.kind}>
|
||||||
|
{kindLogoDict[workspace.kind] ? (
|
||||||
|
<Tooltip content={workspace.kind}>
|
||||||
|
<Brand
|
||||||
|
src={kindLogoDict[workspace.kind]}
|
||||||
|
alt={workspace.kind}
|
||||||
|
style={{ width: '20px', height: '20px', cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Tooltip content={workspace.kind}>
|
||||||
|
<CodeIcon />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
<Td dataLabel={columnNames.image}>{workspace.options.imageConfig}</Td>
|
<Td dataLabel={columnNames.image}>{workspace.options.imageConfig}</Td>
|
||||||
<Td dataLabel={columnNames.podConfig}>{workspace.options.podConfig}</Td>
|
<Td dataLabel={columnNames.podConfig}>{workspace.options.podConfig}</Td>
|
||||||
<Td dataLabel={columnNames.state}>
|
<Td dataLabel={columnNames.state}>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { APIOptions } from '~/shared/api/types';
|
import { APIOptions } from '~/shared/api/types';
|
||||||
|
import { WorkspaceKind } from '~/shared/types';
|
||||||
|
|
||||||
export type ResponseBody<T> = {
|
export type ResponseBody<T> = {
|
||||||
data: T;
|
data: T;
|
||||||
|
|
@ -64,6 +65,9 @@ export type NamespacesList = Namespace[];
|
||||||
|
|
||||||
export type GetNamespaces = (opts: APIOptions) => Promise<NamespacesList>;
|
export type GetNamespaces = (opts: APIOptions) => Promise<NamespacesList>;
|
||||||
|
|
||||||
|
export type GetWorkspaceKinds = (opts: APIOptions) => Promise<WorkspaceKind[]>;
|
||||||
|
|
||||||
export type NotebookAPIs = {
|
export type NotebookAPIs = {
|
||||||
getNamespaces: GetNamespaces;
|
getNamespaces: GetNamespaces;
|
||||||
|
getWorkspaceKinds: GetWorkspaceKinds;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { NamespacesList } from '~/app/types';
|
||||||
import { isNotebookResponse, restGET } from '~/shared/api/apiUtils';
|
import { isNotebookResponse, restGET } from '~/shared/api/apiUtils';
|
||||||
import { APIOptions } from '~/shared/api/types';
|
import { APIOptions } from '~/shared/api/types';
|
||||||
import { handleRestFailures } from '~/shared/api/errorUtils';
|
import { handleRestFailures } from '~/shared/api/errorUtils';
|
||||||
|
import { WorkspaceKind } from '~/shared/types';
|
||||||
|
|
||||||
export const getNamespaces =
|
export const getNamespaces =
|
||||||
(hostPath: string) =>
|
(hostPath: string) =>
|
||||||
|
|
@ -12,3 +13,13 @@ export const getNamespaces =
|
||||||
}
|
}
|
||||||
throw new Error('Invalid response format');
|
throw new Error('Invalid response format');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getWorkspaceKinds =
|
||||||
|
(hostPath: string) =>
|
||||||
|
(opts: APIOptions): Promise<WorkspaceKind[]> =>
|
||||||
|
handleRestFailures(restGET(hostPath, `/workspacekinds`, {}, opts)).then((response) => {
|
||||||
|
if (isNotebookResponse<WorkspaceKind[]>(response)) {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
throw new Error('Invalid response format');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,60 @@ export interface WorkspaceKind {
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
description: string;
|
description: string;
|
||||||
hidden: boolean;
|
|
||||||
deprecated: boolean;
|
deprecated: boolean;
|
||||||
deprecationWarning: string;
|
deprecationMessage: string;
|
||||||
|
hidden: boolean;
|
||||||
icon: WorkspaceIcon;
|
icon: WorkspaceIcon;
|
||||||
logo: WorkspaceLogo;
|
logo: WorkspaceLogo;
|
||||||
|
podTemplate: {
|
||||||
|
podMetadata: {
|
||||||
|
labels: {
|
||||||
|
myWorkspaceKindLabel: string;
|
||||||
|
};
|
||||||
|
annotations: {
|
||||||
|
myWorkspaceKindAnnotation: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
volumeMounts: {
|
||||||
|
home: string;
|
||||||
|
};
|
||||||
|
options: {
|
||||||
|
imageConfig: {
|
||||||
|
default: string;
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
labels: {
|
||||||
|
pythonVersion: string;
|
||||||
|
};
|
||||||
|
hidden: true;
|
||||||
|
redirect: {
|
||||||
|
to: string;
|
||||||
|
message: {
|
||||||
|
text: string;
|
||||||
|
level: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
podConfig: {
|
||||||
|
default: string;
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
description: string;
|
||||||
|
labels: {
|
||||||
|
cpu: string;
|
||||||
|
memory: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum WorkspaceState {
|
export enum WorkspaceState {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue