[arvados-workbench2] created: 2.7.0-250-gc3b9ee34
git repository hosting
git at public.arvados.org
Mon Dec 4 22:45:46 UTC 2023
at c3b9ee34064b6fca1647fd0b3d2051573ceba523 (commit)
commit c3b9ee34064b6fca1647fd0b3d2051573ceba523
Author: Stephen Smith <stephen at curii.com>
Date: Mon Dec 4 17:44:23 2023 -0500
19675: Clean up breadcrumb actions and fix error toasts due to missing
breadcrumb navigation handlers
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/src/store/breadcrumbs/breadcrumbs-actions.ts b/src/store/breadcrumbs/breadcrumbs-actions.ts
index e670c420..80348f37 100644
--- a/src/store/breadcrumbs/breadcrumbs-actions.ts
+++ b/src/store/breadcrumbs/breadcrumbs-actions.ts
@@ -20,7 +20,7 @@ import { ProcessResource } from 'models/process';
import { OrderBuilder } from 'services/api/order-builder';
import { Breadcrumb } from 'components/breadcrumbs/breadcrumbs';
import { ContainerRequestResource, containerRequestFieldsNoMounts } from 'models/container-request';
-import { CollectionIcon, IconType, InstanceTypeIcon, ProcessIcon, ProjectIcon, WorkflowIcon } from 'components/icon/icon';
+import { AdminMenuIcon, CollectionIcon, IconType, InstanceTypeIcon, ProcessIcon, ProjectIcon, WorkflowIcon } from 'components/icon/icon';
import { CollectionResource } from 'models/collection';
import { getSidePanelIcon } from 'views-components/side-panel-tree/side-panel-tree';
import { WorkflowResource } from 'models/workflow';
@@ -299,3 +299,30 @@ export const setInstanceTypesBreadcrumbs = () =>
{ label: INSTANCE_TYPES_PANEL_LABEL, uuid: INSTANCE_TYPES_PANEL_LABEL, icon: InstanceTypeIcon },
]));
};
+
+export const VIRTUAL_MACHINES_USER_PANEL_LABEL = 'Virtual Machines';
+
+export const setVirtualMachinesBreadcrumbs = () =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(setBreadcrumbs([
+ { label: VIRTUAL_MACHINES_USER_PANEL_LABEL, uuid: VIRTUAL_MACHINES_USER_PANEL_LABEL },
+ ]));
+ };
+
+export const VIRTUAL_MACHINES_ADMIN_PANEL_LABEL = 'Virtual Machines Admin';
+
+export const setVirtualMachinesAdminBreadcrumbs = () =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(setBreadcrumbs([
+ { label: VIRTUAL_MACHINES_ADMIN_PANEL_LABEL, uuid: VIRTUAL_MACHINES_ADMIN_PANEL_LABEL, icon: AdminMenuIcon },
+ ]));
+ };
+
+export const REPOSITORIES_PANEL_LABEL = 'Repositories';
+
+export const setRepositoriesBreadcrumbs = () =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(setBreadcrumbs([
+ { label: REPOSITORIES_PANEL_LABEL, uuid: REPOSITORIES_PANEL_LABEL, icon: AdminMenuIcon },
+ ]));
+ };
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
index a78eca83..3ab5cd95 100644
--- a/src/store/navigation/navigation-action.ts
+++ b/src/store/navigation/navigation-action.ts
@@ -11,7 +11,7 @@ import { RootState } from "store/store";
import { ServiceRepository } from "services/services";
import { pluginConfig } from "plugins";
import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
-import { USERS_PANEL_LABEL, MY_ACCOUNT_PANEL_LABEL, INSTANCE_TYPES_PANEL_LABEL } from "store/breadcrumbs/breadcrumbs-actions";
+import { USERS_PANEL_LABEL, MY_ACCOUNT_PANEL_LABEL, INSTANCE_TYPES_PANEL_LABEL, VIRTUAL_MACHINES_USER_PANEL_LABEL, VIRTUAL_MACHINES_ADMIN_PANEL_LABEL, REPOSITORIES_PANEL_LABEL } from "store/breadcrumbs/breadcrumbs-actions";
export const navigationNotAvailable = (id: string) =>
snackbarActions.OPEN_SNACKBAR({
@@ -81,6 +81,15 @@ export const navigateTo = (uuid: string) => async (dispatch: Dispatch, getState:
case INSTANCE_TYPES_PANEL_LABEL:
dispatch(navigateToInstanceTypes);
return;
+ case VIRTUAL_MACHINES_USER_PANEL_LABEL:
+ dispatch(navigateToUserVirtualMachines);
+ return;
+ case VIRTUAL_MACHINES_ADMIN_PANEL_LABEL:
+ dispatch(navigateToAdminVirtualMachines);
+ return;
+ case REPOSITORIES_PANEL_LABEL:
+ dispatch(navigateToRepositories);
+ return;
}
dispatch(navigationNotAvailable(uuid));
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index 183cb423..b12f52b5 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -21,7 +21,6 @@ import { projectPanelColumns } from "views/project-panel/project-panel";
import { favoritePanelColumns } from "views/favorite-panel/favorite-panel";
import { matchRootRoute } from "routes/routes";
import {
- setBreadcrumbs,
setGroupDetailsBreadcrumbs,
setGroupsBreadcrumbs,
setProcessBreadcrumbs,
@@ -32,6 +31,9 @@ import {
setMyAccountBreadcrumbs,
setUserProfileBreadcrumbs,
setInstanceTypesBreadcrumbs,
+ setVirtualMachinesBreadcrumbs,
+ setVirtualMachinesAdminBreadcrumbs,
+ setRepositoriesBreadcrumbs,
} from "store/breadcrumbs/breadcrumbs-actions";
import { navigateTo, navigateToRootProject } from "store/navigation/navigation-action";
import { MoveToFormDialogData } from "store/move-to-dialog/move-to-dialog";
@@ -96,7 +98,6 @@ import { subprocessPanelActions } from "store/subprocess-panel/subprocess-panel-
import { subprocessPanelColumns } from "views/subprocess-panel/subprocess-panel-root";
import { loadAllProcessesPanel, allProcessesPanelActions } from "../all-processes-panel/all-processes-panel-action";
import { allProcessesPanelColumns } from "views/all-processes-panel/all-processes-panel";
-import { AdminMenuIcon } from "components/icon/icon";
import { userProfileGroupsColumns } from "views/user-profile-panel/user-profile-panel-root";
import { selectedToArray, selectedToKindSet } from "components/multiselect-toolbar/MultiselectToolbar";
import { multiselectActions } from "store/multiselect/multiselect-actions";
@@ -743,17 +744,17 @@ export const loadLinks = handleFirstTimeLoad(async (dispatch: Dispatch<any>) =>
export const loadVirtualMachines = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
await dispatch(loadVirtualMachinesPanel());
- dispatch(setBreadcrumbs([{ label: "Virtual Machines" }]));
+ dispatch(setVirtualMachinesBreadcrumbs());
});
export const loadVirtualMachinesAdmin = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
await dispatch(loadVirtualMachinesPanel());
- dispatch(setBreadcrumbs([{ label: "Virtual Machines Admin", icon: AdminMenuIcon }]));
+ dispatch(setVirtualMachinesAdminBreadcrumbs());
});
export const loadRepositories = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
await dispatch(loadRepositoriesPanel());
- dispatch(setBreadcrumbs([{ label: "Repositories" }]));
+ dispatch(setRepositoriesBreadcrumbs());
});
export const loadSshKeys = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
commit 88d20af49848708344f9a384ba203b063859c660
Author: Stephen Smith <stephen at curii.com>
Date: Mon Dec 4 17:25:07 2023 -0500
19675: Add instance types panel unit test
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/src/views/instance-types-panel/instance-types-panel.test.tsx b/src/views/instance-types-panel/instance-types-panel.test.tsx
new file mode 100644
index 00000000..ca270517
--- /dev/null
+++ b/src/views/instance-types-panel/instance-types-panel.test.tsx
@@ -0,0 +1,86 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { configure, mount } from "enzyme";
+import { InstanceTypesPanel } from './instance-types-panel';
+import Adapter from "enzyme-adapter-react-16";
+import { combineReducers, createStore } from "redux";
+import { Provider } from "react-redux";
+import { formatFileSize } from 'common/formatters';
+
+configure({ adapter: new Adapter() });
+
+describe('<InstanceTypesPanel />', () => {
+
+ // let props;
+ let store;
+
+ const initialAuthState = {
+ config: {
+ clusterConfig: {
+ InstanceTypes: {
+ "normalType" : {
+ ProviderType: "provider",
+ Price: 0.123,
+ VCPUs: 6,
+ Preemptible: false,
+ IncludedScratch: 1000,
+ RAM: 5000,
+ },
+ "gpuType" : {
+ ProviderType: "gpuProvider",
+ Price: 0.456,
+ VCPUs: 8,
+ Preemptible: true,
+ IncludedScratch: 500,
+ RAM: 6000,
+ CUDA: {
+ DeviceCount: 1,
+ HardwareCapability: '8.6',
+ DriverVersion: '11.4',
+ },
+ },
+ },
+ Containers: {
+ ReserveExtraRAM: 1000,
+ }
+ }
+ }
+ }
+
+ beforeEach(() => {
+
+ store = createStore(combineReducers({
+ auth: (state: any = initialAuthState, action: any) => state,
+ }));
+ });
+
+ it('renders instance types', () => {
+ // when
+ const panel = mount(
+ <Provider store={store}>
+ <InstanceTypesPanel />
+ </Provider>);
+
+ // then
+ Object.keys(initialAuthState.config.clusterConfig.InstanceTypes).forEach((instanceKey) => {
+ const instanceType = initialAuthState.config.clusterConfig.InstanceTypes[instanceKey];
+ const item = panel.find(`Grid[data-cy="${instanceKey}"]`)
+
+ expect(item.find('h6').text()).toContain(instanceKey);
+ expect(item.text()).toContain(`Provider type: ${instanceType.ProviderType}`);
+ expect(item.text()).toContain(`Price: $${instanceType.Price}`);
+ expect(item.text()).toContain(`Cores: ${instanceType.VCPUs}`);
+ expect(item.text()).toContain(`Preemptible: ${instanceType.Preemptible.toString()}`);
+ expect(item.text()).toContain(`Max disk request: ${formatFileSize(instanceType.IncludedScratch)}`);
+ expect(item.text()).toContain(`Max ram request: ${formatFileSize(instanceType.RAM - initialAuthState.config.clusterConfig.Containers.ReserveExtraRAM)}`);
+ if (instanceType.CUDA && instanceType.CUDA.DeviceCount > 0) {
+ expect(item.text()).toContain(`CUDA GPUs: ${instanceType.CUDA.DeviceCount}`);
+ expect(item.text()).toContain(`Hardware capability: ${instanceType.CUDA.HardwareCapability}`);
+ expect(item.text()).toContain(`Driver version: ${instanceType.CUDA.DriverVersion}`);
+ }
+ });
+ });
+});
commit 8ca84832e153ff4e3b93aa7eec26be9c09fc5e9c
Author: Stephen Smith <stephen at curii.com>
Date: Mon Dec 4 17:24:34 2023 -0500
19675: Adds instance types panel breadcrumb
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/src/routes/route-change-handlers.ts b/src/routes/route-change-handlers.ts
index bdc1ddc0..58887452 100644
--- a/src/routes/route-change-handlers.ts
+++ b/src/routes/route-change-handlers.ts
@@ -36,6 +36,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
const virtualMachineAdminMatch = Routes.matchAdminVirtualMachineRoute(pathname);
const sshKeysUserMatch = Routes.matchSshKeysUserRoute(pathname);
const sshKeysAdminMatch = Routes.matchSshKeysAdminRoute(pathname);
+ const instanceTypesMatch = Routes.matchInstanceTypesRoute(pathname);
const siteManagerMatch = Routes.matchSiteManagerRoute(pathname);
const keepServicesMatch = Routes.matchKeepServicesRoute(pathname);
const apiClientAuthorizationsMatch = Routes.matchApiClientAuthorizationsRoute(pathname);
@@ -92,6 +93,8 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
store.dispatch(WorkbenchActions.loadSshKeys);
} else if (sshKeysAdminMatch) {
store.dispatch(WorkbenchActions.loadSshKeys);
+ } else if (instanceTypesMatch) {
+ store.dispatch(WorkbenchActions.loadInstanceTypes);
} else if (siteManagerMatch) {
store.dispatch(WorkbenchActions.loadSiteManager);
} else if (keepServicesMatch) {
diff --git a/src/store/breadcrumbs/breadcrumbs-actions.ts b/src/store/breadcrumbs/breadcrumbs-actions.ts
index 9aebeb90..e670c420 100644
--- a/src/store/breadcrumbs/breadcrumbs-actions.ts
+++ b/src/store/breadcrumbs/breadcrumbs-actions.ts
@@ -20,7 +20,7 @@ import { ProcessResource } from 'models/process';
import { OrderBuilder } from 'services/api/order-builder';
import { Breadcrumb } from 'components/breadcrumbs/breadcrumbs';
import { ContainerRequestResource, containerRequestFieldsNoMounts } from 'models/container-request';
-import { CollectionIcon, IconType, ProcessIcon, ProjectIcon, WorkflowIcon } from 'components/icon/icon';
+import { CollectionIcon, IconType, InstanceTypeIcon, ProcessIcon, ProjectIcon, WorkflowIcon } from 'components/icon/icon';
import { CollectionResource } from 'models/collection';
import { getSidePanelIcon } from 'views-components/side-panel-tree/side-panel-tree';
import { WorkflowResource } from 'models/workflow';
@@ -290,3 +290,12 @@ export const setMyAccountBreadcrumbs = () =>
{ label: MY_ACCOUNT_PANEL_LABEL, uuid: MY_ACCOUNT_PANEL_LABEL },
]));
};
+
+export const INSTANCE_TYPES_PANEL_LABEL = 'Instance Types';
+
+export const setInstanceTypesBreadcrumbs = () =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(setBreadcrumbs([
+ { label: INSTANCE_TYPES_PANEL_LABEL, uuid: INSTANCE_TYPES_PANEL_LABEL, icon: InstanceTypeIcon },
+ ]));
+ };
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index e89a95e0..183cb423 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -31,6 +31,7 @@ import {
setUsersBreadcrumbs,
setMyAccountBreadcrumbs,
setUserProfileBreadcrumbs,
+ setInstanceTypesBreadcrumbs,
} from "store/breadcrumbs/breadcrumbs-actions";
import { navigateTo, navigateToRootProject } from "store/navigation/navigation-action";
import { MoveToFormDialogData } from "store/move-to-dialog/move-to-dialog";
@@ -759,6 +760,10 @@ export const loadSshKeys = handleFirstTimeLoad(async (dispatch: Dispatch<any>) =
await dispatch(loadSshKeysPanel());
});
+export const loadInstanceTypes = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
+ dispatch(setInstanceTypesBreadcrumbs());
+});
+
export const loadSiteManager = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
await dispatch(loadSiteManagerPanel());
});
commit a382fba3ae8f98536c898442e96ae997f41fc920
Author: Stephen Smith <stephen at curii.com>
Date: Mon Dec 4 17:21:56 2023 -0500
19675: Add instance types panel
* Adds InstanceTypes and other exported fields to the config model
* Renames cost formatter to be more generic
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/src/common/config.ts b/src/common/config.ts
index eff998ae..ed99e7d9 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -19,6 +19,25 @@ export interface ClusterConfigJSON {
MaxItemsPerResponse: number
},
ClusterID: string;
+ Containers: {
+ ReserveExtraRAM: number;
+ },
+ InstanceTypes?: {
+ [key: string]: {
+ AddedScratch: number;
+ CUDA?: {
+ DeviceCount: number;
+ DriverVersion: string;
+ HardwareCapability: string;
+ };
+ IncludedScratch: number;
+ Preemptible: boolean;
+ Price: number;
+ ProviderType: string;
+ RAM: number;
+ VCPUs: number;
+ };
+ };
RemoteClusters: {
[key: string]: {
ActivateUsers: boolean
@@ -276,6 +295,9 @@ export const mockClusterConfigJSON = (
MaxItemsPerResponse: 1000,
},
ClusterID: '',
+ Containers: {
+ ReserveExtraRAM: 576716800,
+ },
RemoteClusters: {},
Services: {
Controller: { ExternalURL: '' },
diff --git a/src/common/formatters.test.ts b/src/common/formatters.test.ts
index 04877972..7f9ffa0c 100644
--- a/src/common/formatters.test.ts
+++ b/src/common/formatters.test.ts
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { formatUploadSpeed, formatContainerCost } from "./formatters";
+import { formatUploadSpeed, formatCost } from "./formatters";
describe('formatUploadSpeed', () => {
it('should show speed less than 1MB/s', () => {
@@ -30,16 +30,16 @@ describe('formatUploadSpeed', () => {
describe('formatContainerCost', () => {
it('should correctly round to tenth of a cent', () => {
- expect(formatContainerCost(0.0)).toBe('$0');
- expect(formatContainerCost(0.125)).toBe('$0.125');
- expect(formatContainerCost(0.1254)).toBe('$0.125');
- expect(formatContainerCost(0.1255)).toBe('$0.126');
+ expect(formatCost(0.0)).toBe('$0');
+ expect(formatCost(0.125)).toBe('$0.125');
+ expect(formatCost(0.1254)).toBe('$0.125');
+ expect(formatCost(0.1255)).toBe('$0.126');
});
it('should round up any smaller value to 0.001', () => {
- expect(formatContainerCost(0.0)).toBe('$0');
- expect(formatContainerCost(0.001)).toBe('$0.001');
- expect(formatContainerCost(0.0001)).toBe('$0.001');
- expect(formatContainerCost(0.00001)).toBe('$0.001');
+ expect(formatCost(0.0)).toBe('$0');
+ expect(formatCost(0.001)).toBe('$0.001');
+ expect(formatCost(0.0001)).toBe('$0.001');
+ expect(formatCost(0.00001)).toBe('$0.001');
});
});
diff --git a/src/common/formatters.ts b/src/common/formatters.ts
index a38609a6..3366af0d 100644
--- a/src/common/formatters.ts
+++ b/src/common/formatters.ts
@@ -116,7 +116,7 @@ export const formatPropertyValue = (
return '';
};
-export const formatContainerCost = (cost: number): string => {
+export const formatCost = (cost: number): string => {
const decimalPlaces = 3;
const factor = Math.pow(10, decimalPlaces);
diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 998cd805..69ebffd7 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -77,6 +77,7 @@ import NotInterested from "@material-ui/icons/NotInterested";
import Image from "@material-ui/icons/Image";
import Stop from "@material-ui/icons/Stop";
import FileCopy from "@material-ui/icons/FileCopy";
+import Storage from "@material-ui/icons/Storage";
// Import FontAwesome icons
import { library } from "@fortawesome/fontawesome-svg-core";
@@ -267,3 +268,4 @@ export const StartIcon: IconType = props => <PlayArrow {...props} />;
export const StopIcon: IconType = props => <Stop {...props} />;
export const SelectAllIcon: IconType = props => <CheckboxMultipleOutline {...props} />;
export const SelectNoneIcon: IconType = props => <CheckboxMultipleBlankOutline {...props} />;
+export const InstanceTypeIcon: IconType = props => <Storage {...props} />;
diff --git a/src/routes/routes.ts b/src/routes/routes.ts
index 4dfd998e..cbb09a50 100644
--- a/src/routes/routes.ts
+++ b/src/routes/routes.ts
@@ -35,6 +35,7 @@ export const Routes = {
SEARCH_RESULTS: '/search-results',
SSH_KEYS_ADMIN: `/ssh-keys-admin`,
SSH_KEYS_USER: `/ssh-keys-user`,
+ INSTANCE_TYPES: `/instance-types`,
SITE_MANAGER: `/site-manager`,
MY_ACCOUNT: '/my-account',
LINK_ACCOUNT: '/link_account',
@@ -161,9 +162,12 @@ export const matchRepositoriesRoute = (route: string) =>
export const matchSshKeysUserRoute = (route: string) =>
matchPath(route, { path: Routes.SSH_KEYS_USER });
-export const matchSshKeysAdminRoute = (route: string) =>
+ export const matchSshKeysAdminRoute = (route: string) =>
matchPath(route, { path: Routes.SSH_KEYS_ADMIN });
+export const matchInstanceTypesRoute = (route: string) =>
+ matchPath(route, { path: Routes.INSTANCE_TYPES });
+
export const matchSiteManagerRoute = (route: string) =>
matchPath(route, { path: Routes.SITE_MANAGER });
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
index 55112fb0..a78eca83 100644
--- a/src/store/navigation/navigation-action.ts
+++ b/src/store/navigation/navigation-action.ts
@@ -11,7 +11,7 @@ import { RootState } from "store/store";
import { ServiceRepository } from "services/services";
import { pluginConfig } from "plugins";
import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
-import { USERS_PANEL_LABEL, MY_ACCOUNT_PANEL_LABEL } from "store/breadcrumbs/breadcrumbs-actions";
+import { USERS_PANEL_LABEL, MY_ACCOUNT_PANEL_LABEL, INSTANCE_TYPES_PANEL_LABEL } from "store/breadcrumbs/breadcrumbs-actions";
export const navigationNotAvailable = (id: string) =>
snackbarActions.OPEN_SNACKBAR({
@@ -78,6 +78,9 @@ export const navigateTo = (uuid: string) => async (dispatch: Dispatch, getState:
case MY_ACCOUNT_PANEL_LABEL:
dispatch(navigateToMyAccount);
return;
+ case INSTANCE_TYPES_PANEL_LABEL:
+ dispatch(navigateToInstanceTypes);
+ return;
}
dispatch(navigationNotAvailable(uuid));
@@ -132,6 +135,8 @@ export const navigateToSshKeysAdmin = push(Routes.SSH_KEYS_ADMIN);
export const navigateToSshKeysUser = push(Routes.SSH_KEYS_USER);
+export const navigateToInstanceTypes = push(Routes.INSTANCE_TYPES);
+
export const navigateToSiteManager = push(Routes.SITE_MANAGER);
export const navigateToMyAccount = push(Routes.MY_ACCOUNT);
diff --git a/src/views-components/main-app-bar/account-menu.tsx b/src/views-components/main-app-bar/account-menu.tsx
index c2cc0e2a..b1a71ef2 100644
--- a/src/views-components/main-app-bar/account-menu.tsx
+++ b/src/views-components/main-app-bar/account-menu.tsx
@@ -17,7 +17,8 @@ import {
navigateToSiteManager,
navigateToSshKeysUser,
navigateToMyAccount,
- navigateToLinkAccount
+ navigateToLinkAccount,
+ navigateToInstanceTypes
} from 'store/navigation/navigation-action';
import { openUserVirtualMachines } from "store/virtual-machines/virtual-machines-actions";
import { pluginConfig } from 'plugins';
@@ -58,6 +59,7 @@ export const AccountMenuComponent =
dispatch(openTokenDialog);
}}>Get API token</MenuItem>
<MenuItem onClick={() => dispatch(navigateToSshKeysUser)}>Ssh Keys</MenuItem>
+ <MenuItem onClick={() => dispatch(navigateToInstanceTypes)}>Instance Types</MenuItem>
<MenuItem onClick={() => dispatch(navigateToSiteManager)}>Site Manager</MenuItem>
<MenuItem onClick={() => dispatch(navigateToMyAccount)}>My account</MenuItem>
<MenuItem onClick={() => dispatch(navigateToLinkAccount)}>Link account</MenuItem>
diff --git a/src/views/instance-types-panel/instance-types-panel.tsx b/src/views/instance-types-panel/instance-types-panel.tsx
new file mode 100644
index 00000000..a18f5d23
--- /dev/null
+++ b/src/views/instance-types-panel/instance-types-panel.tsx
@@ -0,0 +1,97 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { StyleRulesCallback, WithStyles, withStyles, Card, CardContent, Typography, Grid } from '@material-ui/core';
+import { ArvadosTheme } from 'common/custom-theme';
+import { InstanceTypeIcon } from 'components/icon/icon';
+import { RootState } from 'store/store';
+import { connect } from 'react-redux';
+import { ClusterConfigJSON } from 'common/config';
+import { NotFoundView } from 'views/not-found-panel/not-found-panel';
+import { formatCost, formatFileSize } from 'common/formatters';
+
+type CssRules = 'root' | 'instanceType';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ width: '100%',
+ overflow: 'auto'
+ },
+ instanceType: {
+ padding: "10px",
+ },
+});
+
+type InstanceTypesPanelConnectedProps = {config: ClusterConfigJSON};
+
+type InstanceTypesPanelRootProps = InstanceTypesPanelConnectedProps & WithStyles<CssRules>;
+
+const mapStateToProps = ({auth}: RootState): InstanceTypesPanelConnectedProps => ({
+ config: auth.config.clusterConfig,
+});
+
+export const InstanceTypesPanel = withStyles(styles)(connect(mapStateToProps)(
+ ({ config, classes }: InstanceTypesPanelRootProps) => {
+
+ const instances = config.InstanceTypes || {};
+
+ return <Card className={classes.root}>
+ <CardContent>
+ <Grid container direction="row">
+ {Object.keys(instances).length > 0 ?
+ Object.keys(instances).map((instanceKey) => {
+ const instanceType = instances[instanceKey];
+
+ return <Grid data-cy={instanceKey} className={classes.instanceType} item sm={6} xs={12} key={instanceKey}>
+ <Card>
+ <CardContent>
+ <Typography variant="h6">
+ {instanceKey}
+ </Typography>
+ <Typography>
+ Provider type: {instanceType.ProviderType}
+ </Typography>
+ <Typography>
+ Price: {formatCost(instanceType.Price)}
+ </Typography>
+ <Typography>
+ Cores: {instanceType.VCPUs}
+ </Typography>
+ <Typography>
+ Preemptible: {instanceType.Preemptible.toString()}
+ </Typography>
+ <Typography>
+ Max disk request: {formatFileSize(instanceType.IncludedScratch)}
+ </Typography>
+ <Typography>
+ Max ram request: {formatFileSize(instanceType.RAM - config.Containers.ReserveExtraRAM)}
+ </Typography>
+ {instanceType.CUDA && instanceType.CUDA.DeviceCount > 0 ?
+ <>
+ <Typography>
+ CUDA GPUs: {instanceType.CUDA.DeviceCount}
+ </Typography>
+ <Typography>
+ Hardware capability: {instanceType.CUDA.HardwareCapability}
+ </Typography>
+ <Typography>
+ Driver version: {instanceType.CUDA.DriverVersion}
+ </Typography>
+ </> : <></>
+ }
+ </CardContent>
+ </Card>
+ </Grid>
+ }) :
+ <NotFoundView
+ icon={InstanceTypeIcon}
+ messages={["No instances found"]}
+ />
+ }
+ </Grid>
+ </CardContent>
+ </Card>
+ }
+));
diff --git a/src/views/process-panel/process-details-attributes.tsx b/src/views/process-panel/process-details-attributes.tsx
index ffacd967..4e5c0383 100644
--- a/src/views/process-panel/process-details-attributes.tsx
+++ b/src/views/process-panel/process-details-attributes.tsx
@@ -5,7 +5,7 @@
import React from "react";
import { Grid, StyleRulesCallback, withStyles } from "@material-ui/core";
import { Dispatch } from 'redux';
-import { formatContainerCost, formatDate } from "common/formatters";
+import { formatCost, formatDate } from "common/formatters";
import { resourceLabel } from "common/labels";
import { DetailsAttribute } from "components/details-attribute/details-attribute";
import { ResourceKind } from "models/resource";
@@ -162,7 +162,7 @@ export const ProcessDetailsAttributes = withStyles(styles, { withTheme: true })(
</Grid>
{container && <Grid item xs={12} md={mdSize}>
<DetailsAttribute label='Cost' value={
- `${hasTotalCost ? formatContainerCost(containerRequest.cumulativeCost) + ' total, ' : (totalCostNotReady ? 'total pending completion, ' : '')}${container.cost > 0 ? formatContainerCost(container.cost) : 'not available'} for this container`
+ `${hasTotalCost ? formatCost(containerRequest.cumulativeCost) + ' total, ' : (totalCostNotReady ? 'total pending completion, ' : '')}${container.cost > 0 ? formatCost(container.cost) : 'not available'} for this container`
} />
{container && workflowCollection && <Grid item xs={12} md={mdSize}>
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index 4a2cd700..05ea215d 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -106,6 +106,7 @@ import { pluginConfig } from "plugins";
import { ElementListReducer } from "common/plugintypes";
import { COLLAPSE_ICON_SIZE } from "views-components/side-panel-toggle/side-panel-toggle";
import { Banner } from "views-components/baner/banner";
+import { InstanceTypesPanel } from "views/instance-types-panel/instance-types-panel";
type CssRules = "root" | "container" | "splitter" | "asidePanel" | "contentWrapper" | "content";
@@ -228,6 +229,10 @@ let routes = (
path={Routes.SSH_KEYS_ADMIN}
component={SshKeyAdminPanel}
/>
+ <Route
+ path={Routes.INSTANCE_TYPES}
+ component={InstanceTypesPanel}
+ />
<Route
path={Routes.SITE_MANAGER}
component={SiteManagerPanel}
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list