Merge branch 'master' of git.curoverse.com:arvados-workbench2 into 14602_admin_comput...
authorJanicki Artur <artur.janicki@contractors.roche.com>
Mon, 17 Dec 2018 06:44:11 +0000 (07:44 +0100)
committerJanicki Artur <artur.janicki@contractors.roche.com>
Mon, 17 Dec 2018 06:44:11 +0000 (07:44 +0100)
refs #2
14602

Arvados-DCO-1.1-Signed-off-by: Janicki Artur <artur.janicki@contractors.roche.com>

15 files changed:
src/common/formatters.ts
src/store/advanced-tab/advanced-tab.ts
src/store/compute-nodes/compute-nodes-actions.ts
src/store/compute-nodes/compute-nodes-middleware-service.ts [new file with mode: 0644]
src/store/compute-nodes/compute-nodes-reducer.ts [deleted file]
src/store/context-menu/context-menu-actions.ts
src/store/link-panel/link-panel-actions.ts
src/store/store.ts
src/store/workbench/workbench-actions.ts
src/views-components/data-explorer/renderers.tsx
src/views-components/data-explorer/with-resources.tsx [new file with mode: 0644]
src/views/compute-node-panel/compute-node-panel-root.tsx
src/views/compute-node-panel/compute-node-panel.tsx
src/views/link-panel/link-panel-root.tsx
src/views/link-panel/link-panel.tsx

index 5383c66e949f59ee1b2d1258f0d2d1402f485bdb..ae50ee8adda3ace82a380b0714961e3ea4fb4394 100644 (file)
@@ -8,9 +8,9 @@ export const formatDate = (isoDate?: string) => {
     if (isoDate) {
         const date = new Date(isoDate);
         const text = date.toLocaleString();
-        return text === 'Invalid Date' ? "" : text;
+        return text === 'Invalid Date' ? "(none)" : text;
     }
-    return "";
+    return "(none)";
 };
 
 export const formatFileSize = (size?: number) => {
index 659b6e49fb7b805aa31d45e960c021808576f849..0cb1c74038503c5c1f80fc58c36cb07d9f0803e5 100644 (file)
@@ -241,7 +241,8 @@ export const openAdvancedTabDialog = (uuid: string) =>
                 dispatch<any>(initAdvancedTabDialog(advanceDataUser));
                 break;
             case ResourceKind.NODE:
-                const dataComputeNode = getState().computeNodes.find(node => node.uuid === uuid);
+                const computeNodeResources = getState().resources;
+                const dataComputeNode = getResource<NodeResource>(uuid)(computeNodeResources);
                 const advanceDataComputeNode = advancedTabData({
                     uuid,
                     metadata: '',
@@ -251,7 +252,7 @@ export const openAdvancedTabDialog = (uuid: string) =>
                     resourceKind: ComputeNodeData.COMPUTE_NODE,
                     resourcePrefix: ResourcePrefix.COMPUTE_NODES,
                     resourceKindProperty: ComputeNodeData.PROPERTIES,
-                    property: dataComputeNode!.properties
+                    property: dataComputeNode ? dataComputeNode.properties : {}
                 });
                 dispatch<any>(initAdvancedTabDialog(advanceDataComputeNode));
                 break;
index 659b1e8674c7c63138d76c106c3cc23941665e02..f2f6ad0741e6312f7f5e9cd0683288c13ae74d93 100644 (file)
@@ -3,21 +3,18 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Dispatch } from "redux";
-import { unionize, ofType, UnionOf } from "~/common/unionize";
 import { RootState } from '~/store/store';
 import { setBreadcrumbs } from '~/store/breadcrumbs/breadcrumbs-actions';
-import { ServiceRepository } from "~/services/services";
-import { NodeResource } from '~/models/node';
 import { dialogActions } from '~/store/dialog/dialog-actions';
 import { snackbarActions } from '~/store/snackbar/snackbar-actions';
 import { navigateToRootProject } from '~/store/navigation/navigation-action';
+import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action';
+import { getResource } from '~/store/resources/resources';
+import { ServiceRepository } from "~/services/services";
+import { NodeResource } from '~/models/node';
 
-export const computeNodesActions = unionize({
-    SET_COMPUTE_NODES: ofType<NodeResource[]>(),
-    REMOVE_COMPUTE_NODE: ofType<string>()
-});
-
-export type ComputeNodesActions = UnionOf<typeof computeNodesActions>;
+export const COMPUTE_NODE_PANEL_ID = "computeNodeId";
+export const computeNodesActions = bindDataExplorerActions(COMPUTE_NODE_PANEL_ID);
 
 export const COMPUTE_NODE_REMOVE_DIALOG = 'computeNodeRemoveDialog';
 export const COMPUTE_NODE_ATTRIBUTES_DIALOG = 'computeNodeAttributesDialog';
@@ -28,8 +25,7 @@ export const loadComputeNodesPanel = () =>
         if (user && user.isAdmin) {
             try {
                 dispatch(setBreadcrumbs([{ label: 'Compute Nodes' }]));
-                const response = await services.nodeService.list();
-                dispatch(computeNodesActions.SET_COMPUTE_NODES(response.items));
+                dispatch(computeNodesActions.REQUEST_ITEMS());
             } catch (e) {
                 return;
             }
@@ -41,7 +37,8 @@ export const loadComputeNodesPanel = () =>
 
 export const openComputeNodeAttributesDialog = (uuid: string) =>
     (dispatch: Dispatch, getState: () => RootState) => {
-        const computeNode = getState().computeNodes.find(node => node.uuid === uuid);
+        const { resources } = getState();
+        const computeNode = getResource<NodeResource>(uuid)(resources);
         dispatch(dialogActions.OPEN_DIALOG({ id: COMPUTE_NODE_ATTRIBUTES_DIALOG, data: { computeNode } }));
     };
 
@@ -63,7 +60,7 @@ export const removeComputeNode = (uuid: string) =>
         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
         try {
             await services.nodeService.delete(uuid);
-            dispatch(computeNodesActions.REMOVE_COMPUTE_NODE(uuid));
+            dispatch(computeNodesActions.REQUEST_ITEMS());
             dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Compute node has been successfully removed.', hideDuration: 2000 }));
         } catch (e) {
             return;
diff --git a/src/store/compute-nodes/compute-nodes-middleware-service.ts b/src/store/compute-nodes/compute-nodes-middleware-service.ts
new file mode 100644 (file)
index 0000000..792da7a
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ServiceRepository } from '~/services/services';
+import { MiddlewareAPI, Dispatch } from 'redux';
+import { DataExplorerMiddlewareService, dataExplorerToListParams, listResultsToDataExplorerItemsMeta } from '~/store/data-explorer/data-explorer-middleware-service';
+import { RootState } from '~/store/store';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
+import { DataExplorer, getDataExplorer } from '~/store/data-explorer/data-explorer-reducer';
+import { updateResources } from '~/store/resources/resources-actions';
+import { getSortColumn } from "~/store/data-explorer/data-explorer-reducer";
+import { computeNodesActions } from '~/store/compute-nodes/compute-nodes-actions';
+import { OrderDirection, OrderBuilder } from '~/services/api/order-builder';
+import { ListResults } from '~/services/common-service/common-service';
+import { NodeResource } from '~/models/node';
+import { SortDirection } from '~/components/data-table/data-column';
+import { ComputeNodePanelColumnNames } from '~/views/compute-node-panel/compute-node-panel-root';
+
+export class ComputeNodeMiddlewareService extends DataExplorerMiddlewareService {
+    constructor(private services: ServiceRepository, id: string) {
+        super(id);
+    }
+
+    async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+        const state = api.getState();
+        const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
+        try {
+            const response = await this.services.nodeService.list(getParams(dataExplorer));
+            api.dispatch(updateResources(response.items));
+            api.dispatch(setItems(response));
+        } catch {
+            api.dispatch(couldNotFetchLinks());
+        }
+    }
+}
+
+export const getParams = (dataExplorer: DataExplorer) => ({
+    ...dataExplorerToListParams(dataExplorer),
+    order: getOrder(dataExplorer)
+});
+
+const getOrder = (dataExplorer: DataExplorer) => {
+    const sortColumn = getSortColumn(dataExplorer);
+    const order = new OrderBuilder<NodeResource>();
+    if (sortColumn) {
+        const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC
+            ? OrderDirection.ASC
+            : OrderDirection.DESC;
+
+        const columnName = sortColumn && sortColumn.name === ComputeNodePanelColumnNames.UUID ? "uuid" : "modifiedAt";
+        return order
+            .addOrder(sortDirection, columnName)
+            .getOrder();
+    } else {
+        return order.getOrder();
+    }
+};
+
+export const setItems = (listResults: ListResults<NodeResource>) =>
+    computeNodesActions.SET_ITEMS({
+        ...listResultsToDataExplorerItemsMeta(listResults),
+        items: listResults.items.map(resource => resource.uuid),
+    });
+
+const couldNotFetchLinks = () =>
+    snackbarActions.OPEN_SNACKBAR({
+        message: 'Could not fetch compute nodes.',
+        kind: SnackbarKind.ERROR
+    });
diff --git a/src/store/compute-nodes/compute-nodes-reducer.ts b/src/store/compute-nodes/compute-nodes-reducer.ts
deleted file mode 100644 (file)
index 44a3780..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { computeNodesActions, ComputeNodesActions } from '~/store/compute-nodes/compute-nodes-actions';
-import { NodeResource } from '~/models/node';
-
-export type ComputeNodesState = NodeResource[];
-
-const initialState: ComputeNodesState = [];
-
-export const computeNodesReducer = (state: ComputeNodesState = initialState, action: ComputeNodesActions): ComputeNodesState =>
-    computeNodesActions.match(action, {
-        SET_COMPUTE_NODES: nodes => nodes,
-        REMOVE_COMPUTE_NODE: (uuid: string) => state.filter((computeNode) => computeNode.uuid !== uuid),
-        default: () => state
-    });
\ No newline at end of file
index e9b08a8417afdb3d69adb32e8ba44847bb6d9a39..6a5e7b788d070e4ace9e27afb7ab730c45df030e 100644 (file)
@@ -17,7 +17,6 @@ import { RepositoryResource } from '~/models/repositories';
 import { SshKeyResource } from '~/models/ssh-key';
 import { VirtualMachinesResource } from '~/models/virtual-machines';
 import { KeepServiceResource } from '~/models/keep-services';
-import { NodeResource } from '~/models/node';
 import { ApiClientAuthorization } from '~/models/api-client-authorization';
 
 export const contextMenuActions = unionize({
@@ -111,12 +110,12 @@ export const openKeepServiceContextMenu = (event: React.MouseEvent<HTMLElement>,
         }));
     };
 
-export const openComputeNodeContextMenu = (event: React.MouseEvent<HTMLElement>, computeNode: NodeResource) =>
+export const openComputeNodeContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) =>
     (dispatch: Dispatch) => {
         dispatch<any>(openContextMenu(event, {
             name: '',
-            uuid: computeNode.uuid,
-            ownerUuid: computeNode.ownerUuid,
+            uuid: resourceUuid,
+            ownerUuid: '',
             kind: ResourceKind.NODE,
             menuKind: ContextMenuKind.NODE
         }));
index 944c1bd17703004742f7b5bc16212bf7982ba84a..7cbc507342cd3b301035852f71b989a36837d4be 100644 (file)
@@ -18,6 +18,12 @@ export const linkPanelActions = bindDataExplorerActions(LINK_PANEL_ID);
 export const LINK_REMOVE_DIALOG = 'linkRemoveDialog';
 export const LINK_ATTRIBUTES_DIALOG = 'linkAttributesDialog';
 
+export const loadLinkPanel = () =>
+    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(setBreadcrumbs([{ label: 'Links' }]));
+        dispatch(linkPanelActions.REQUEST_ITEMS());
+    };
+
 export const openLinkAttributesDialog = (uuid: string) =>
     (dispatch: Dispatch, getState: () => RootState) => {
         const { resources } = getState();
@@ -38,12 +44,6 @@ export const openLinkRemoveDialog = (uuid: string) =>
         }));
     };
 
-export const loadLinkPanel = () =>
-    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch(setBreadcrumbs([{ label: 'Links' }]));
-        dispatch(linkPanelActions.REQUEST_ITEMS());
-    };
-
 export const removeLink = (uuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
index 792224d240f7434595f409db117a85744fbf528a..d196e632d24f8fe3a7f8bc3062d8a7c2bd8cb01f 100644 (file)
@@ -48,10 +48,11 @@ import { repositoriesReducer } from '~/store/repositories/repositories-reducer';
 import { keepServicesReducer } from '~/store/keep-services/keep-services-reducer';
 import { UserMiddlewareService } from '~/store/users/user-panel-middleware-service';
 import { USERS_PANEL_ID } from '~/store/users/users-actions';
-import { computeNodesReducer } from '~/store/compute-nodes/compute-nodes-reducer';
 import { apiClientAuthorizationsReducer } from '~/store/api-client-authorizations/api-client-authorizations-reducer';
 import { LINK_PANEL_ID } from '~/store/link-panel/link-panel-actions';
 import { LinkMiddlewareService } from '~/store/link-panel/link-panel-middleware-service';
+import { COMPUTE_NODE_PANEL_ID } from '~/store/compute-nodes/compute-nodes-actions';
+import { ComputeNodeMiddlewareService } from '~/store/compute-nodes/compute-nodes-middleware-service';
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -89,6 +90,9 @@ export function configureStore(history: History, services: ServiceRepository): R
     const linkPanelMiddleware = dataExplorerMiddleware(
         new LinkMiddlewareService(services, LINK_PANEL_ID)
     );
+    const computeNodeMiddleware = dataExplorerMiddleware(
+        new ComputeNodeMiddlewareService(services, COMPUTE_NODE_PANEL_ID)
+    );
     const middlewares: Middleware[] = [
         routerMiddleware(history),
         thunkMiddleware.withExtraArgument(services),
@@ -99,7 +103,8 @@ export function configureStore(history: History, services: ServiceRepository): R
         sharedWithMePanelMiddleware,
         workflowPanelMiddleware,
         userPanelMiddleware,
-        linkPanelMiddleware
+        linkPanelMiddleware,
+        computeNodeMiddleware
     ];
     const enhancer = composeEnhancers(applyMiddleware(...middlewares));
     return createStore(rootReducer, enhancer);
@@ -131,6 +136,5 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({
     virtualMachines: virtualMachinesReducer,
     repositories: repositoriesReducer,
     keepServices: keepServicesReducer,
-    computeNodes: computeNodesReducer,
     apiClientAuthorizations: apiClientAuthorizationsReducer
 });
index 85540f0b434ac90826b93aba3561f726d3325578..e42e6c3ea1a8520f1961e0d5751bf83bb6e22e44 100644 (file)
@@ -60,9 +60,10 @@ import { loadRepositoriesPanel } from '~/store/repositories/repositories-actions
 import { loadKeepServicesPanel } from '~/store/keep-services/keep-services-actions';
 import { loadUsersPanel, userBindedActions } from '~/store/users/users-actions';
 import { loadLinkPanel, linkPanelActions } from '~/store/link-panel/link-panel-actions';
+import { loadComputeNodesPanel, computeNodesActions } from '~/store/compute-nodes/compute-nodes-actions';
 import { linkPanelColumns } from '~/views/link-panel/link-panel-root';
 import { userPanelColumns } from '~/views/user-panel/user-panel';
-import { loadComputeNodesPanel } from '~/store/compute-nodes/compute-nodes-actions';
+import { computeNodePanelColumns } from '~/views/compute-node-panel/compute-node-panel-root';
 import { loadApiClientAuthorizationsPanel } from '~/store/api-client-authorizations/api-client-authorizations-actions';
 
 export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
@@ -99,6 +100,7 @@ export const loadWorkbench = () =>
                 dispatch(searchResultsPanelActions.SET_COLUMNS({ columns: searchResultsPanelColumns }));
                 dispatch(userBindedActions.SET_COLUMNS({ columns: userPanelColumns }));
                 dispatch(linkPanelActions.SET_COLUMNS({ columns: linkPanelColumns }));
+                dispatch(computeNodesActions.SET_COLUMNS({ columns: computeNodePanelColumns }));
                 dispatch<any>(initSidePanelTree());
                 if (router.location) {
                     const match = matchRootRoute(router.location.pathname);
index a4713c8dc2337a8df20bb27d2094beff6fab32c5..ce4d430fd18597a0c4af04925eb31fa9a636dd77 100644 (file)
@@ -25,7 +25,7 @@ import { UserResource } from '~/models/user';
 import { toggleIsActive, toggleIsAdmin } from '~/store/users/users-actions';
 import { LinkResource } from '~/models/link';
 import { navigateTo } from '~/store/navigation/navigation-action';
-import { Link } from 'react-router-dom';
+import { withResource, getDataFromResource, withResourceData } from '~/views-components/data-explorer/with-resources';
 
 const renderName = (item: { name: string; uuid: string, kind: string }) =>
     <Grid container alignItems="center" wrap="nowrap" spacing={16}>
@@ -191,6 +191,34 @@ export const ResourceUsername = connect(
         return resource || { username: '' };
     })(renderUsername);
 
+// Compute Node Resources
+const renderNodeDate = (date: string) =>
+    <Typography noWrap>{formatDate(date)}</Typography>;
+
+const renderNodeData = (data: string) => {
+    return <Typography noWrap>{data}</Typography>;
+};
+
+const renderNodeInfo = (data: string) => {
+    return <Typography>{JSON.stringify(data, null, 4)}</Typography>;
+};
+
+export const ComputeNodeInfo = withResourceData('info', renderNodeInfo);
+
+export const ComputeNodeUuid = withResourceData('uuid', renderNodeData);
+
+export const ComputeNodeDomain = withResourceData('domain', renderNodeData);
+
+export const ComputeNodeFirstPingAt = withResourceData('firstPingAt', renderNodeDate);
+
+export const ComputeNodeHostname = withResourceData('hostname', renderNodeData);
+
+export const ComputeNodeIpAddress = withResourceData('ipAddress', renderNodeData);
+
+export const ComputeNodeJobUuid = withResourceData('jobUuid', renderNodeData);
+
+export const ComputeNodeLastPingAt = withResourceData('lastPingAt', renderNodeDate);
+
 // Links Resources
 const renderLinkName = (item: { name: string }) =>
     <Typography noWrap>{item.name || '(none)'}</Typography>;
diff --git a/src/views-components/data-explorer/with-resources.tsx b/src/views-components/data-explorer/with-resources.tsx
new file mode 100644 (file)
index 0000000..54c9396
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { RootState } from '~/store/store';
+import { getResource } from '~/store/resources/resources';
+import { Resource } from '~/models/resource';
+
+interface WithResourceProps {
+    resource?: Resource;
+}
+
+export const withResource = (component: React.ComponentType<WithResourceProps & { uuid: string }>) =>
+    connect<WithResourceProps>(
+        (state: RootState, props: { uuid: string }): WithResourceProps => ({
+            resource: getResource(props.uuid)(state.resources)
+        })
+    )(component);
+
+export const getDataFromResource = (property: string, resource?: Resource) => {
+    return resource && resource[property] ? resource[property] : '(none)';
+};
+
+export const withResourceData = (property: string, render: (data: any) => React.ReactElement<any>) =>
+    withResource(({ resource }) => render(getDataFromResource(property, resource)));
index 2d325b514a8bb20897d3f4ca5959b4b5496af14e..feaadb5e5b86a92232354759506c185c3a334cca 100644 (file)
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
+import { ShareMeIcon } from '~/components/icon/icon';
+import { DataExplorer } from '~/views-components/data-explorer/data-explorer';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+import { COMPUTE_NODE_PANEL_ID } from '~/store/compute-nodes/compute-nodes-actions';
+import { DataColumns } from '~/components/data-table/data-table';
+import { SortDirection } from '~/components/data-table/data-column';
+import { createTree } from '~/models/tree';
 import { 
-    StyleRulesCallback, WithStyles, withStyles, Card, CardContent, Grid, Table, 
-    TableHead, TableRow, TableCell, TableBody, Tooltip, IconButton 
-} from '@material-ui/core';
-import { ArvadosTheme } from '~/common/custom-theme';
-import { MoreOptionsIcon } from '~/components/icon/icon';
-import { NodeResource } from '~/models/node';
-import { formatDate } from '~/common/formatters';
+    ComputeNodeUuid, ComputeNodeInfo, ComputeNodeDomain, ComputeNodeHostname, ComputeNodeJobUuid,
+    ComputeNodeFirstPingAt, ComputeNodeLastPingAt, ComputeNodeIpAddress
+} from '~/views-components/data-explorer/renderers';
+import { ResourcesState } from '~/store/resources/resources';
 
-type CssRules = 'root' | 'tableRow';
+export enum ComputeNodePanelColumnNames {
+    INFO = 'Info',
+    UUID = 'UUID',
+    DOMAIN = 'Domain',
+    FIRST_PING_AT = 'First ping at',
+    HOSTNAME = 'Hostname',
+    IP_ADDRESS = 'IP Address',
+    JOB = 'Job',
+    LAST_PING_AT = 'Last ping at'
+}
 
-const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    root: {
-        width: '100%',
-        overflow: 'auto'
+export const computeNodePanelColumns: DataColumns<string> = [
+    {
+        name: ComputeNodePanelColumnNames.INFO,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <ComputeNodeInfo uuid={uuid} />
+    },
+    {
+        name: ComputeNodePanelColumnNames.UUID,
+        selected: true,
+        configurable: true,
+        sortDirection: SortDirection.NONE,
+        filters: createTree(),
+        render: uuid => <ComputeNodeUuid uuid={uuid} />
+    },
+    {
+        name: ComputeNodePanelColumnNames.DOMAIN,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <ComputeNodeDomain uuid={uuid} />
+    },
+    {
+        name: ComputeNodePanelColumnNames.FIRST_PING_AT,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <ComputeNodeFirstPingAt uuid={uuid} />
     },
-    tableRow: {
-        '& th': {
-            whiteSpace: 'nowrap'
-        }
+    {
+        name: ComputeNodePanelColumnNames.HOSTNAME,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <ComputeNodeHostname uuid={uuid} />
+    },
+    {
+        name: ComputeNodePanelColumnNames.IP_ADDRESS,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <ComputeNodeIpAddress uuid={uuid} />
+    },
+    {
+        name: ComputeNodePanelColumnNames.JOB,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <ComputeNodeJobUuid uuid={uuid} />
+    },
+    {
+        name: ComputeNodePanelColumnNames.LAST_PING_AT,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <ComputeNodeLastPingAt uuid={uuid} />
     }
-});
+];
+
+const DEFAULT_MESSAGE = 'Your compute node list is empty.';
 
 export interface ComputeNodePanelRootActionProps {
-    openRowOptions: (event: React.MouseEvent<HTMLElement>, computeNode: NodeResource) => void;
+    onItemClick: (item: string) => void;
+    onContextMenu: (event: React.MouseEvent<HTMLElement>, item: string) => void;
+    onItemDoubleClick: (item: string) => void;
 }
 
 export interface ComputeNodePanelRootDataProps {
-    computeNodes: NodeResource[];
-    hasComputeNodes: boolean;
+    resources: ResourcesState;
 }
 
-type ComputeNodePanelRootProps = ComputeNodePanelRootActionProps & ComputeNodePanelRootDataProps & WithStyles<CssRules>;
+type ComputeNodePanelRootProps = ComputeNodePanelRootActionProps & ComputeNodePanelRootDataProps;
 
-export const ComputeNodePanelRoot = withStyles(styles)(
-    ({ classes, hasComputeNodes, computeNodes, openRowOptions }: ComputeNodePanelRootProps) =>
-        <Card className={classes.root}>
-            <CardContent>
-                {hasComputeNodes && <Grid container direction="row">
-                    <Grid item xs={12}>
-                        <Table>
-                            <TableHead>
-                                <TableRow className={classes.tableRow}>
-                                    <TableCell>Info</TableCell>
-                                    <TableCell>UUID</TableCell>
-                                    <TableCell>Domain</TableCell>
-                                    <TableCell>First ping at</TableCell>
-                                    <TableCell>Hostname</TableCell>
-                                    <TableCell>IP Address</TableCell>
-                                    <TableCell>Job</TableCell>
-                                    <TableCell>Last ping at</TableCell>
-                                    <TableCell />
-                                </TableRow>
-                            </TableHead>
-                            <TableBody>
-                                {computeNodes.map((computeNode, index) =>
-                                    <TableRow key={index} className={classes.tableRow}>
-                                        <TableCell>{JSON.stringify(computeNode.info, null, 4)}</TableCell>
-                                        <TableCell>{computeNode.uuid}</TableCell>
-                                        <TableCell>{computeNode.domain}</TableCell>
-                                        <TableCell>{formatDate(computeNode.firstPingAt) || '(none)'}</TableCell>
-                                        <TableCell>{computeNode.hostname || '(none)'}</TableCell>
-                                        <TableCell>{computeNode.ipAddress || '(none)'}</TableCell>
-                                        <TableCell>{computeNode.jobUuid || '(none)'}</TableCell>
-                                        <TableCell>{formatDate(computeNode.lastPingAt) || '(none)'}</TableCell>
-                                        <TableCell>
-                                            <Tooltip title="More options" disableFocusListener>
-                                                <IconButton onClick={event => openRowOptions(event, computeNode)}>
-                                                    <MoreOptionsIcon />
-                                                </IconButton>
-                                            </Tooltip>
-                                        </TableCell>
-                                    </TableRow>)}
-                            </TableBody>
-                        </Table>
-                    </Grid>
-                </Grid>}
-            </CardContent>
-        </Card>
-);
\ No newline at end of file
+export const ComputeNodePanelRoot = (props: ComputeNodePanelRootProps) => {
+    return <DataExplorer
+        id={COMPUTE_NODE_PANEL_ID}
+        onRowClick={props.onItemClick}
+        onRowDoubleClick={props.onItemDoubleClick}
+        onContextMenu={props.onContextMenu}
+        contextMenuColumn={true}
+        hideColumnSelector
+        hideSearchInput
+        dataTableDefaultView={
+            <DataTableDefaultView
+                icon={ShareMeIcon}
+                messages={[DEFAULT_MESSAGE]} />
+        } />;
+};
\ No newline at end of file
index a4f22c808959cae960d606910411f1d540c7a11f..a531b2d0cb66320b35b1b20200dea4a33e2bdb56 100644 (file)
@@ -5,7 +5,6 @@
 import { RootState } from '~/store/store';
 import { Dispatch } from 'redux';
 import { connect } from 'react-redux';
-import { } from '~/store/compute-nodes/compute-nodes-actions';
 import {
     ComputeNodePanelRoot,
     ComputeNodePanelRootDataProps,
@@ -15,15 +14,16 @@ import { openComputeNodeContextMenu } from '~/store/context-menu/context-menu-ac
 
 const mapStateToProps = (state: RootState): ComputeNodePanelRootDataProps => {
     return {
-        computeNodes: state.computeNodes,
-        hasComputeNodes: state.computeNodes.length > 0
+        resources: state.resources
     };
 };
 
 const mapDispatchToProps = (dispatch: Dispatch): ComputeNodePanelRootActionProps => ({
-    openRowOptions: (event, computeNode) => {
-        dispatch<any>(openComputeNodeContextMenu(event, computeNode));
-    }
+    onContextMenu: (event, resourceUuid) => {
+        dispatch<any>(openComputeNodeContextMenu(event, resourceUuid));
+    },
+    onItemClick: (resourceUuid: string) => { return; },
+    onItemDoubleClick: uuid => { return; }
 });
 
 export const ComputeNodePanel = connect(mapStateToProps, mapDispatchToProps)(ComputeNodePanelRoot);
\ No newline at end of file
index d5ba79b335a1bde9c19f8e7cf28f6047eee223b2..a4c8e010bcb32d0a9617554ff87e965e76ee2e78 100644 (file)
@@ -63,19 +63,19 @@ export const linkPanelColumns: DataColumns<string> = [
     }
 ];
 
-export interface LinkPanelDataProps {
+export interface LinkPanelRootDataProps {
     resources: ResourcesState;
 }
 
-export interface LinkPanelActionProps {
+export interface LinkPanelRootActionProps {
     onItemClick: (item: string) => void;
     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: string) => void;
     onItemDoubleClick: (item: string) => void;
 }
 
-export type LinkPanelProps = LinkPanelDataProps & LinkPanelActionProps;
+export type LinkPanelRootProps = LinkPanelRootDataProps & LinkPanelRootActionProps;
 
-export const LinkPanelRoot = (props: LinkPanelProps) => {
+export const LinkPanelRoot = (props: LinkPanelRootProps) => {
     return <DataExplorer
         id={LINK_PANEL_ID}
         onRowClick={props.onItemClick}
index 2c3bf758850cc30412ba10c42a5dc854f6df7f06..4bff4ee7c6be609cba72092cb0a2d43c153e5685 100644 (file)
@@ -6,16 +6,16 @@ import { Dispatch } from "redux";
 import { connect } from "react-redux";
 import { RootState } from '~/store/store';
 import { openContextMenu, resourceKindToContextMenuKind } from '~/store/context-menu/context-menu-actions';
-import { LinkPanelRoot, LinkPanelActionProps } from '~/views/link-panel/link-panel-root';
+import { LinkPanelRoot, LinkPanelRootActionProps, LinkPanelRootDataProps } from '~/views/link-panel/link-panel-root';
 import { ResourceKind } from '~/models/resource';
 
-const mapStateToProps = (state: RootState) => {
+const mapStateToProps = (state: RootState): LinkPanelRootDataProps => {
     return {
         resources: state.resources
     };
 };
 
-const mapDispatchToProps = (dispatch: Dispatch): LinkPanelActionProps => ({
+const mapDispatchToProps = (dispatch: Dispatch): LinkPanelRootActionProps => ({
     onContextMenu: (event, resourceUuid) => {
         const kind = resourceKindToContextMenuKind(resourceUuid);
         if (kind) {