[ARVADOS-WORKBENCH2] created: 1.3.0-242-g46751b93

Git user git at public.curoverse.com
Thu Dec 27 03:50:18 EST 2018


        at  46751b9305a7e991d484494facd1f40790254d40 (commit)


commit 46751b9305a7e991d484494facd1f40790254d40
Author: Janicki Artur <artur.janicki at contractors.roche.com>
Date:   Thu Dec 27 09:49:54 2018 +0100

    add middleware and pagination, change resources model and store
    
    Feature #14652_api_tokens_pagination
    
    Arvados-DCO-1.1-Signed-off-by: Janicki Artur <artur.janicki at contractors.roche.com>

diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index b6ca215d..b2377888 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -84,7 +84,7 @@ export const DataExplorer = withStyles(styles)(
                 paperKey
             } = this.props;
             return <Paper className={classes.root} {...paperProps} key={paperKey}>
-                <Toolbar className={classes.toolbar}>
+                {(!hideColumnSelector || !hideSearchInput) && <Toolbar className={classes.toolbar}>
                     <Grid container justify="space-between" wrap="nowrap" alignItems="center">
                         {!hideSearchInput && <div className={classes.searchBox}>
                             <SearchInput
@@ -96,7 +96,7 @@ export const DataExplorer = withStyles(styles)(
                             columns={columns}
                             onColumnToggle={onColumnToggle} />}
                     </Grid>
-                </Toolbar>
+                </Toolbar>}
                 <DataTable
                     columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
                     items={items}
@@ -144,7 +144,6 @@ export const DataExplorer = withStyles(styles)(
             name: "Actions",
             selected: true,
             configurable: false,
-            sortDirection: SortDirection.NONE,
             filters: createTree(),
             key: "context-actions",
             render: this.renderContextMenuTrigger
diff --git a/src/models/api-client-authorization.ts b/src/models/api-client-authorization.ts
index aff50be6..e8f6fac7 100644
--- a/src/models/api-client-authorization.ts
+++ b/src/models/api-client-authorization.ts
@@ -2,6 +2,8 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import { ResourceKind } from '~/models/resource';
+
 export interface ApiClientAuthorization {
     uuid: string;
     apiToken: string;
@@ -16,4 +18,7 @@ export interface ApiClientAuthorization {
     ownerUuid: string;
     defaultOwnerUuid: string;
     scopes: string[];
+    kind: ResourceKind.API_CLIENT_AUTHORIZATION;
+    etag: string;
+    href: string;
 }
\ No newline at end of file
diff --git a/src/models/resource.ts b/src/models/resource.ts
index 31f3eb88..6f824028 100644
--- a/src/models/resource.ts
+++ b/src/models/resource.ts
@@ -2,6 +2,8 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import { ApiClientAuthorization } from '~/models/api-client-authorization';
+
 export interface Resource {
     uuid: string;
     ownerUuid: string;
@@ -14,6 +16,8 @@ export interface Resource {
     etag: string;
 }
 
+export type ResourceTypes = Resource | ApiClientAuthorization;
+
 export interface TrashableResource extends Resource {
     trashAt: string;
     deleteAt: string;
diff --git a/src/services/api/order-builder.test.ts b/src/services/api/order-builder.test.ts
index f56e0634..496b74a2 100644
--- a/src/services/api/order-builder.test.ts
+++ b/src/services/api/order-builder.test.ts
@@ -8,8 +8,8 @@ describe("OrderBuilder", () => {
     it("should build correct order query", () => {
         const order = new OrderBuilder()
             .addAsc("kind")
-            .addDesc("modifiedAt")
+            .addDesc("createdAt")
             .getOrder();
-        expect(order).toEqual("kind asc,modified_at desc");
+        expect(order).toEqual("kind asc,created_at desc");
     });
 });
diff --git a/src/services/api/order-builder.ts b/src/services/api/order-builder.ts
index 03f2696a..a5483670 100644
--- a/src/services/api/order-builder.ts
+++ b/src/services/api/order-builder.ts
@@ -3,11 +3,11 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as _ from "lodash";
-import { Resource } from "src/models/resource";
+import { ResourceTypes } from "src/models/resource";
 
 export enum OrderDirection { ASC, DESC }
 
-export class OrderBuilder<T extends Resource = Resource> {
+export class OrderBuilder<T extends ResourceTypes = ResourceTypes> {
 
     constructor(private order: string[] = []) {}
 
diff --git a/src/store/advanced-tab/advanced-tab.ts b/src/store/advanced-tab/advanced-tab.ts
index 0cb1c740..921b1cd7 100644
--- a/src/store/advanced-tab/advanced-tab.ts
+++ b/src/store/advanced-tab/advanced-tab.ts
@@ -257,7 +257,8 @@ export const openAdvancedTabDialog = (uuid: string) =>
                 dispatch<any>(initAdvancedTabDialog(advanceDataComputeNode));
                 break;
             case ResourceKind.API_CLIENT_AUTHORIZATION:
-                const dataApiClientAuthorization = getState().apiClientAuthorizations.find(item => item.uuid === uuid);
+                const apiClientAuthorizationResources = getState().resources;
+                const dataApiClientAuthorization = getResource<ApiClientAuthorization>(uuid)(apiClientAuthorizationResources);
                 const advanceDataApiClientAuthorization = advancedTabData({
                     uuid,
                     metadata: '',
diff --git a/src/store/api-client-authorizations/api-client-authorizations-actions.ts b/src/store/api-client-authorizations/api-client-authorizations-actions.ts
index 8ed8a389..e4f9e9f7 100644
--- a/src/store/api-client-authorizations/api-client-authorizations-actions.ts
+++ b/src/store/api-client-authorizations/api-client-authorizations-actions.ts
@@ -3,7 +3,6 @@
 // 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";
@@ -11,28 +10,26 @@ import { dialogActions } from '~/store/dialog/dialog-actions';
 import { snackbarActions } from '~/store/snackbar/snackbar-actions';
 import { navigateToRootProject } from '~/store/navigation/navigation-action';
 import { ApiClientAuthorization } from '~/models/api-client-authorization';
+import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action';
+import { getResource } from '~/store/resources/resources';
 
-export const apiClientAuthorizationsActions = unionize({
-    SET_API_CLIENT_AUTHORIZATIONS: ofType<ApiClientAuthorization[]>(),
-    REMOVE_API_CLIENT_AUTHORIZATION: ofType<string>()
-});
 
-export type ApiClientAuthorizationsActions = UnionOf<typeof apiClientAuthorizationsActions>;
+export const API_CLIENT_AUTHORIZATION_PANEL_ID = 'apiClientAuthorizationPanelId';
+export const apiClientAuthorizationsActions = bindDataExplorerActions(API_CLIENT_AUTHORIZATION_PANEL_ID);
 
 export const API_CLIENT_AUTHORIZATION_REMOVE_DIALOG = 'apiClientAuthorizationRemoveDialog';
 export const API_CLIENT_AUTHORIZATION_ATTRIBUTES_DIALOG = 'apiClientAuthorizationAttributesDialog';
 export const API_CLIENT_AUTHORIZATION_HELP_DIALOG = 'apiClientAuthorizationHelpDialog';
 
+
 export const loadApiClientAuthorizationsPanel = () =>
     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
         const user = getState().auth.user;
         if (user && user.isAdmin) {
             try {
                 dispatch(setBreadcrumbs([{ label: 'Api client authorizations' }]));
-                const response = await services.apiClientAuthorizationService.list();
-                dispatch(apiClientAuthorizationsActions.SET_API_CLIENT_AUTHORIZATIONS(response.items));
+                dispatch(apiClientAuthorizationsActions.REQUEST_ITEMS());
             } catch (e) {
-                dispatch(snackbarActions.OPEN_SNACKBAR({ message: "You don't have permissions to view this page", hideDuration: 2000 }));
                 return;
             }
         } else {
@@ -43,7 +40,8 @@ export const loadApiClientAuthorizationsPanel = () =>
 
 export const openApiClientAuthorizationAttributesDialog = (uuid: string) =>
     (dispatch: Dispatch, getState: () => RootState) => {
-        const apiClientAuthorization = getState().apiClientAuthorizations.find(node => node.uuid === uuid);
+        const { resources } = getState();
+        const apiClientAuthorization = getResource<ApiClientAuthorization>(uuid)(resources);
         dispatch(dialogActions.OPEN_DIALOG({ id: API_CLIENT_AUTHORIZATION_ATTRIBUTES_DIALOG, data: { apiClientAuthorization } }));
     };
 
@@ -65,7 +63,7 @@ export const removeApiClientAuthorization = (uuid: string) =>
         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
         try {
             await services.apiClientAuthorizationService.delete(uuid);
-            dispatch(apiClientAuthorizationsActions.REMOVE_API_CLIENT_AUTHORIZATION(uuid));
+            dispatch(apiClientAuthorizationsActions.REQUEST_ITEMS());
             dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Api client authorization has been successfully removed.', hideDuration: 2000 }));
         } catch (e) {
             return;
diff --git a/src/store/api-client-authorizations/api-client-authorizations-middleware-service.ts b/src/store/api-client-authorizations/api-client-authorizations-middleware-service.ts
new file mode 100644
index 00000000..99e2a958
--- /dev/null
+++ b/src/store/api-client-authorizations/api-client-authorizations-middleware-service.ts
@@ -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 { apiClientAuthorizationsActions } from '~/store/api-client-authorizations/api-client-authorizations-actions';
+import { OrderDirection, OrderBuilder } from '~/services/api/order-builder';
+import { ListResults } from '~/services/common-service/common-service';
+import { ApiClientAuthorization } from '~/models/api-client-authorization';
+import { ApiClientAuthorizationPanelColumnNames } from '~/views/api-client-authorization-panel/api-client-authorization-panel-root';
+import { SortDirection } from '~/components/data-table/data-column';
+
+export class ApiClientAuthorizationMiddlewareService 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.apiClientAuthorizationService.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<ApiClientAuthorization>();
+    if (sortColumn) {
+        const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC
+            ? OrderDirection.ASC
+            : OrderDirection.DESC;
+
+        const columnName = sortColumn && sortColumn.name === ApiClientAuthorizationPanelColumnNames.UUID ? "uuid" : "updatedAt";
+        return order
+            .addOrder(sortDirection, columnName)
+            .getOrder();
+    } else {
+        return order.getOrder();
+    }
+};
+
+export const setItems = (listResults: ListResults<ApiClientAuthorization>) =>
+    apiClientAuthorizationsActions.SET_ITEMS({
+        ...listResultsToDataExplorerItemsMeta(listResults),
+        items: listResults.items.map(resource => resource.uuid),
+    });
+
+const couldNotFetchLinks = () =>
+    snackbarActions.OPEN_SNACKBAR({
+        message: 'Could not fetch api client authorizations.',
+        kind: SnackbarKind.ERROR
+    });
diff --git a/src/store/api-client-authorizations/api-client-authorizations-reducer.ts b/src/store/api-client-authorizations/api-client-authorizations-reducer.ts
deleted file mode 100644
index 7084dea7..00000000
--- a/src/store/api-client-authorizations/api-client-authorizations-reducer.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { 
-    apiClientAuthorizationsActions, 
-    ApiClientAuthorizationsActions 
-} from '~/store/api-client-authorizations/api-client-authorizations-actions';
-import { ApiClientAuthorization } from '~/models/api-client-authorization';
-
-export type ApiClientAuthorizationsState = ApiClientAuthorization[];
-
-const initialState: ApiClientAuthorizationsState = [];
-
-export const apiClientAuthorizationsReducer = 
-    (state: ApiClientAuthorizationsState = initialState, action: ApiClientAuthorizationsActions): ApiClientAuthorizationsState =>
-        apiClientAuthorizationsActions.match(action, {
-            SET_API_CLIENT_AUTHORIZATIONS: apiClientAuthorizations => apiClientAuthorizations,
-            REMOVE_API_CLIENT_AUTHORIZATION: (uuid: string) => 
-                state.filter((apiClientAuthorization) => apiClientAuthorization.uuid !== uuid),
-            default: () => state
-        });
\ No newline at end of file
diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts
index c43d5685..ca89f3eb 100644
--- a/src/store/context-menu/context-menu-actions.ts
+++ b/src/store/context-menu/context-menu-actions.ts
@@ -123,12 +123,12 @@ export const openComputeNodeContextMenu = (event: React.MouseEvent<HTMLElement>,
     };
 
 export const openApiClientAuthorizationContextMenu =
-    (event: React.MouseEvent<HTMLElement>, apiClientAuthorization: ApiClientAuthorization) =>
+    (event: React.MouseEvent<HTMLElement>, resourceUuid: string) =>
         (dispatch: Dispatch) => {
             dispatch<any>(openContextMenu(event, {
                 name: '',
-                uuid: apiClientAuthorization.uuid,
-                ownerUuid: apiClientAuthorization.ownerUuid,
+                uuid: resourceUuid,
+                ownerUuid: '',
                 kind: ResourceKind.API_CLIENT_AUTHORIZATION,
                 menuKind: ContextMenuKind.API_CLIENT_AUTHORIZATION
             }));
diff --git a/src/store/resources/resources-actions.ts b/src/store/resources/resources-actions.ts
index 0453236a..e6566ab8 100644
--- a/src/store/resources/resources-actions.ts
+++ b/src/store/resources/resources-actions.ts
@@ -3,20 +3,20 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { unionize, ofType, UnionOf } from '~/common/unionize';
-import { Resource, extractUuidKind } from '~/models/resource';
+import { extractUuidKind, ResourceTypes } from '~/models/resource';
 import { Dispatch } from 'redux';
 import { RootState } from '~/store/store';
 import { ServiceRepository } from '~/services/services';
 import { getResourceService } from '~/services/services';
 
 export const resourcesActions = unionize({
-    SET_RESOURCES: ofType<Resource[]>(),
+    SET_RESOURCES: ofType<ResourceTypes[]>(),
     DELETE_RESOURCES: ofType<string[]>()
 });
 
 export type ResourcesAction = UnionOf<typeof resourcesActions>;
 
-export const updateResources = (resources: Resource[]) => resourcesActions.SET_RESOURCES(resources);
+export const updateResources = (resources: ResourceTypes[]) => resourcesActions.SET_RESOURCES(resources);
 
 export const loadResource = (uuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
diff --git a/src/store/resources/resources.ts b/src/store/resources/resources.ts
index e7153dec..4fec02a5 100644
--- a/src/store/resources/resources.ts
+++ b/src/store/resources/resources.ts
@@ -2,16 +2,16 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Resource } from "~/models/resource";
+import { ResourceTypes } from "~/models/resource";
 import { ResourceKind } from '~/models/resource';
 
-export type ResourcesState = { [key: string]: Resource };
+export type ResourcesState = { [key: string]: ResourceTypes };
 
-export const getResource = <T extends Resource = Resource>(id: string) =>
+export const getResource = <T extends ResourceTypes = ResourceTypes>(id: string) =>
     (state: ResourcesState): T | undefined =>
         state[id] as T;
 
-export const setResource = <T extends Resource>(id: string, data: T) =>
+export const setResource = <T extends ResourceTypes>(id: string, data: T) =>
     (state: ResourcesState) => ({
         ...state,
         [id]: data
@@ -24,7 +24,7 @@ export const deleteResource = (id: string) =>
         return newState;
     };
 
-export const filterResources = (filter: (resource: Resource) => boolean) =>
+export const filterResources = (filter: (resource: ResourceTypes) => boolean) =>
     (state: ResourcesState) =>
         Object
             .keys(state)
diff --git a/src/store/store.ts b/src/store/store.ts
index 14a6ba11..60a4b4f0 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -48,7 +48,6 @@ 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 { apiClientAuthorizationsReducer } from '~/store/api-client-authorizations/api-client-authorizations-reducer';
 import { GroupsPanelMiddlewareService } from '~/store/groups-panel/groups-panel-middleware-service';
 import { GROUPS_PANEL_ID } from '~/store/groups-panel/groups-panel-actions';
 import { GroupDetailsPanelMiddlewareService } from '~/store/group-details-panel/group-details-panel-middleware-service';
@@ -57,6 +56,8 @@ 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';
+import { API_CLIENT_AUTHORIZATION_PANEL_ID } from '~/store/api-client-authorizations/api-client-authorizations-actions';
+import { ApiClientAuthorizationMiddlewareService } from '~/store/api-client-authorizations/api-client-authorizations-middleware-service';
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -97,13 +98,15 @@ export function configureStore(history: History, services: ServiceRepository): R
     const groupDetailsPanelMiddleware = dataExplorerMiddleware(
         new GroupDetailsPanelMiddlewareService(services, GROUP_DETAILS_PANEL_ID)
     );
-
     const linkPanelMiddleware = dataExplorerMiddleware(
         new LinkMiddlewareService(services, LINK_PANEL_ID)
     );
     const computeNodeMiddleware = dataExplorerMiddleware(
         new ComputeNodeMiddlewareService(services, COMPUTE_NODE_PANEL_ID)
     );
+    const apiClientAuthorizationMiddlewareService = dataExplorerMiddleware(
+        new ApiClientAuthorizationMiddlewareService(services, API_CLIENT_AUTHORIZATION_PANEL_ID)
+    );
     const middlewares: Middleware[] = [
         routerMiddleware(history),
         thunkMiddleware.withExtraArgument(services),
@@ -118,6 +121,7 @@ export function configureStore(history: History, services: ServiceRepository): R
         groupDetailsPanelMiddleware,
         linkPanelMiddleware,
         computeNodeMiddleware,
+        apiClientAuthorizationMiddlewareService
     ];
     const enhancer = composeEnhancers(applyMiddleware(...middlewares));
     return createStore(rootReducer, enhancer);
@@ -148,6 +152,5 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({
     searchBar: searchBarReducer,
     virtualMachines: virtualMachinesReducer,
     repositories: repositoriesReducer,
-    keepServices: keepServicesReducer,
-    apiClientAuthorizations: apiClientAuthorizationsReducer
+    keepServices: keepServicesReducer
 });
diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index ce4d430f..0637676c 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -191,33 +191,52 @@ export const ResourceUsername = connect(
         return resource || { username: '' };
     })(renderUsername);
 
-// Compute Node Resources
-const renderNodeDate = (date: string) =>
+// Common methods
+const renderCommonData = (data: string) =>
+    <Typography noWrap>{data}</Typography>;
+
+const renderCommonDate = (date: string) =>
     <Typography noWrap>{formatDate(date)}</Typography>;
 
-const renderNodeData = (data: string) => {
-    return <Typography noWrap>{data}</Typography>;
-};
+export const CommonUuid = withResourceData('uuid', renderCommonData);
+
+// Api Client Authorizations
+export const TokenApiClientId = withResourceData('apiClientId', renderCommonData);
+
+export const TokenApiToken = withResourceData('apiToken', renderCommonData);
+
+export const TokenCreatedByIpAddress = withResourceData('createdByIpAddress', renderCommonDate);
+
+export const TokenDefaultOwnerUuid = withResourceData('defaultOwnerUuid', renderCommonData);
 
+export const TokenExpiresAt = withResourceData('expiresAt', renderCommonDate);
+
+export const TokenLastUsedAt = withResourceData('lastUsedAt', renderCommonDate);
+
+export const TokenLastUsedByIpAddress = withResourceData('lastUsedByIpAddress', renderCommonData);
+
+export const TokenScopes = withResourceData('scopes', renderCommonData);
+
+export const TokenUserId = withResourceData('userId', renderCommonData);
+
+// Compute Node Resources
 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 ComputeNodeDomain = withResourceData('domain', renderCommonData);
 
-export const ComputeNodeFirstPingAt = withResourceData('firstPingAt', renderNodeDate);
+export const ComputeNodeFirstPingAt = withResourceData('firstPingAt', renderCommonDate);
 
-export const ComputeNodeHostname = withResourceData('hostname', renderNodeData);
+export const ComputeNodeHostname = withResourceData('hostname', renderCommonData);
 
-export const ComputeNodeIpAddress = withResourceData('ipAddress', renderNodeData);
+export const ComputeNodeIpAddress = withResourceData('ipAddress', renderCommonData);
 
-export const ComputeNodeJobUuid = withResourceData('jobUuid', renderNodeData);
+export const ComputeNodeJobUuid = withResourceData('jobUuid', renderCommonData);
 
-export const ComputeNodeLastPingAt = withResourceData('lastPingAt', renderNodeDate);
+export const ComputeNodeLastPingAt = withResourceData('lastPingAt', renderCommonDate);
 
 // Links Resources
 const renderLinkName = (item: { name: string }) =>
diff --git a/src/views-components/data-explorer/with-resources.tsx b/src/views-components/data-explorer/with-resources.tsx
index 54c9396c..399a2d05 100644
--- a/src/views-components/data-explorer/with-resources.tsx
+++ b/src/views-components/data-explorer/with-resources.tsx
@@ -6,10 +6,10 @@ 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';
+import { Resource, ResourceTypes } from '~/models/resource';
 
 interface WithResourceProps {
-    resource?: Resource;
+    resource?: ResourceTypes;
 }
 
 export const withResource = (component: React.ComponentType<WithResourceProps & { uuid: string }>) =>
@@ -19,7 +19,7 @@ export const withResource = (component: React.ComponentType<WithResourceProps &
         })
     )(component);
 
-export const getDataFromResource = (property: string, resource?: Resource) => {
+export const getDataFromResource = (property: string, resource?: ResourceTypes) => {
     return resource && resource[property] ? resource[property] : '(none)';
 };
 
diff --git a/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx b/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx
index 52921b30..a25d93b7 100644
--- a/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx
+++ b/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx
@@ -3,53 +3,150 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { 
-    StyleRulesCallback, WithStyles, withStyles, Card, CardContent, Grid, 
-    Table, TableHead, TableRow, TableCell, TableBody, Tooltip, IconButton
+import {
+    StyleRulesCallback, WithStyles, withStyles, Card, CardContent, Grid, Tooltip, IconButton
 } from '@material-ui/core';
 import { ArvadosTheme } from '~/common/custom-theme';
-import { MoreOptionsIcon, HelpIcon } from '~/components/icon/icon';
-import { ApiClientAuthorization } from '~/models/api-client-authorization';
-import { formatDate } from '~/common/formatters';
+import { HelpIcon, ShareMeIcon } from '~/components/icon/icon';
+import { createTree } from '~/models/tree';
+import { DataColumns } from '~/components/data-table/data-table';
+import { SortDirection } from '~/components/data-table/data-column';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+import { API_CLIENT_AUTHORIZATION_PANEL_ID } from '../../store/api-client-authorizations/api-client-authorizations-actions';
+import { DataExplorer } from '~/views-components/data-explorer/data-explorer';
+import { ResourcesState } from '~/store/resources/resources';
+import {
+    CommonUuid, TokenApiClientId, TokenApiToken, TokenCreatedByIpAddress, TokenDefaultOwnerUuid, TokenExpiresAt,
+    TokenLastUsedAt, TokenLastUsedByIpAddress, TokenScopes, TokenUserId
+} from '~/views-components/data-explorer/renderers';
 
-type CssRules = 'root' | 'tableRow' | 'helpIconGrid' | 'tableGrid';
+type CssRules = 'card' | 'cardContent' | 'helpIconGrid';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    root: {
+    card: {
         width: '100%',
         overflow: 'auto'
     },
+    cardContent: {
+        padding: 0,
+        '&:last-child': {
+            paddingBottom: 0
+        }
+    },
     helpIconGrid: {
         textAlign: 'right'
+    }
+});
+
+
+export enum ApiClientAuthorizationPanelColumnNames {
+    UUID = 'UUID',
+    API_CLIENT_ID = 'API Client ID',
+    API_TOKEN = 'API Token',
+    CREATED_BY_IP_ADDRESS = 'Created by IP address',
+    DEFAULT_OWNER_UUID = 'Default owner',
+    EXPIRES_AT = 'Expires at',
+    LAST_USED_AT = 'Last used at',
+    LAST_USED_BY_IP_ADDRESS = 'Last used by IP address',
+    SCOPES = 'Scopes',
+    USER_ID = 'User ID'
+}
+
+export const ApiClientAuthorizationPanelColumns: DataColumns<string> = [
+    {
+        name: ApiClientAuthorizationPanelColumnNames.UUID,
+        selected: true,
+        configurable: true,
+        sortDirection: SortDirection.NONE,
+        filters: createTree(),
+        render: uuid => <CommonUuid uuid={uuid} />
     },
-    tableGrid: {
-        marginTop: theme.spacing.unit
+    {
+        name: ApiClientAuthorizationPanelColumnNames.API_CLIENT_ID,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <TokenApiClientId uuid={uuid} />
     },
-    tableRow: {
-        '& td, th': {
-            whiteSpace: 'nowrap'
-        }
+    {
+        name: ApiClientAuthorizationPanelColumnNames.API_TOKEN,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <TokenApiToken uuid={uuid} />
+    },
+    {
+        name: ApiClientAuthorizationPanelColumnNames.CREATED_BY_IP_ADDRESS,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <TokenCreatedByIpAddress uuid={uuid} />
+    },
+    {
+        name: ApiClientAuthorizationPanelColumnNames.DEFAULT_OWNER_UUID,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <TokenDefaultOwnerUuid uuid={uuid} />
+    },
+    {
+        name: ApiClientAuthorizationPanelColumnNames.EXPIRES_AT,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <TokenExpiresAt uuid={uuid} />
+    },
+    {
+        name: ApiClientAuthorizationPanelColumnNames.LAST_USED_AT,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <TokenLastUsedAt uuid={uuid} />
+    },
+    {
+        name: ApiClientAuthorizationPanelColumnNames.LAST_USED_BY_IP_ADDRESS,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <TokenLastUsedByIpAddress uuid={uuid} />
+    },
+    {
+        name: ApiClientAuthorizationPanelColumnNames.SCOPES,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <TokenScopes uuid={uuid} />
+    },
+    {
+        name: ApiClientAuthorizationPanelColumnNames.USER_ID,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <TokenUserId uuid={uuid} />
     }
-});
+];
+
+const DEFAULT_MESSAGE = 'Your api client authorization list is empty.';
 
 export interface ApiClientAuthorizationPanelRootActionProps {
-    openRowOptions: (event: React.MouseEvent<HTMLElement>, keepService: ApiClientAuthorization) => void;
+    onItemClick: (item: string) => void;
+    onContextMenu: (event: React.MouseEvent<HTMLElement>, item: string) => void;
+    onItemDoubleClick: (item: string) => void;
     openHelpDialog: () => void;
 }
 
 export interface ApiClientAuthorizationPanelRootDataProps {
-    apiClientAuthorizations: ApiClientAuthorization[];
-    hasApiClientAuthorizations: boolean;
+    resources: ResourcesState;
 }
 
-type ApiClientAuthorizationPanelRootProps = ApiClientAuthorizationPanelRootActionProps 
+type ApiClientAuthorizationPanelRootProps = ApiClientAuthorizationPanelRootActionProps
     & ApiClientAuthorizationPanelRootDataProps & WithStyles<CssRules>;
 
 export const ApiClientAuthorizationPanelRoot = withStyles(styles)(
-    ({ classes, hasApiClientAuthorizations, apiClientAuthorizations, openRowOptions, openHelpDialog }: ApiClientAuthorizationPanelRootProps) =>
-        <Card className={classes.root}>
-            <CardContent>
-                {hasApiClientAuthorizations && <Grid container direction="row" justify="flex-end">
+    ({ classes, onItemDoubleClick, onItemClick, onContextMenu, openHelpDialog }: ApiClientAuthorizationPanelRootProps) =>
+        <Card className={classes.card}>
+            <CardContent className={classes.cardContent}>
+                <Grid container direction="row" justify="flex-end">
                     <Grid item xs={12} className={classes.helpIconGrid}>
                         <Tooltip title="Api token - help">
                             <IconButton onClick={openHelpDialog}>
@@ -58,47 +155,21 @@ export const ApiClientAuthorizationPanelRoot = withStyles(styles)(
                         </Tooltip>
                     </Grid>
                     <Grid item xs={12}>
-                        <Table>
-                            <TableHead>
-                                <TableRow className={classes.tableRow}>
-                                    <TableCell>UUID</TableCell>
-                                    <TableCell>API Client ID</TableCell>
-                                    <TableCell>API Token</TableCell>
-                                    <TableCell>Created by IP address</TableCell>
-                                    <TableCell>Default owner</TableCell>
-                                    <TableCell>Expires at</TableCell>
-                                    <TableCell>Last used at</TableCell>
-                                    <TableCell>Last used by IP address</TableCell>
-                                    <TableCell>Scopes</TableCell>
-                                    <TableCell>User ID</TableCell>
-                                    <TableCell />
-                                </TableRow>
-                            </TableHead>
-                            <TableBody>
-                                {apiClientAuthorizations.map((apiClientAuthorizatio, index) =>
-                                    <TableRow key={index} className={classes.tableRow}>
-                                        <TableCell>{apiClientAuthorizatio.uuid}</TableCell>
-                                        <TableCell>{apiClientAuthorizatio.apiClientId}</TableCell>
-                                        <TableCell>{apiClientAuthorizatio.apiToken}</TableCell>
-                                        <TableCell>{apiClientAuthorizatio.createdByIpAddress || '(none)'}</TableCell>
-                                        <TableCell>{apiClientAuthorizatio.defaultOwnerUuid || '(none)'}</TableCell>
-                                        <TableCell>{formatDate(apiClientAuthorizatio.expiresAt) || '(none)'}</TableCell>
-                                        <TableCell>{formatDate(apiClientAuthorizatio.lastUsedAt) || '(none)'}</TableCell>
-                                        <TableCell>{apiClientAuthorizatio.lastUsedByIpAddress || '(none)'}</TableCell>
-                                        <TableCell>{JSON.stringify(apiClientAuthorizatio.scopes)}</TableCell>
-                                        <TableCell>{apiClientAuthorizatio.userId}</TableCell>
-                                        <TableCell>
-                                            <Tooltip title="More options" disableFocusListener>
-                                                <IconButton onClick={event => openRowOptions(event, apiClientAuthorizatio)}>
-                                                    <MoreOptionsIcon />
-                                                </IconButton>
-                                            </Tooltip>
-                                        </TableCell>
-                                    </TableRow>)}
-                            </TableBody>
-                        </Table>
+                        <DataExplorer
+                            id={API_CLIENT_AUTHORIZATION_PANEL_ID}
+                            onRowClick={onItemClick}
+                            onRowDoubleClick={onItemDoubleClick}
+                            onContextMenu={onContextMenu}
+                            contextMenuColumn={true}
+                            hideColumnSelector
+                            hideSearchInput
+                            dataTableDefaultView={
+                                <DataTableDefaultView
+                                    icon={ShareMeIcon}
+                                    messages={[DEFAULT_MESSAGE]} />
+                            } />
                     </Grid>
-                </Grid>}
+                </Grid>
             </CardContent>
         </Card>
 );
\ No newline at end of file
diff --git a/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx b/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx
index 75b79abf..27b6a98d 100644
--- a/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx
+++ b/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx
@@ -15,15 +15,16 @@ import { openApiClientAuthorizationsHelpDialog } from '~/store/api-client-author
 
 const mapStateToProps = (state: RootState): ApiClientAuthorizationPanelRootDataProps => {
     return {
-        apiClientAuthorizations: state.apiClientAuthorizations,
-        hasApiClientAuthorizations: state.apiClientAuthorizations.length > 0
+        resources: state.resources,
     };
 };
 
 const mapDispatchToProps = (dispatch: Dispatch): ApiClientAuthorizationPanelRootActionProps => ({
-    openRowOptions: (event, apiClientAuthorization) => {
+    onContextMenu: (event, apiClientAuthorization) => {
         dispatch<any>(openApiClientAuthorizationContextMenu(event, apiClientAuthorization));
     },
+    onItemClick: (resourceUuid: string) => { return; },
+    onItemDoubleClick: uuid => { return; },
     openHelpDialog: () => {
         dispatch<any>(openApiClientAuthorizationsHelpDialog());
     }
diff --git a/src/views/compute-node-panel/compute-node-panel-root.tsx b/src/views/compute-node-panel/compute-node-panel-root.tsx
index feaadb5e..e49fc79d 100644
--- a/src/views/compute-node-panel/compute-node-panel-root.tsx
+++ b/src/views/compute-node-panel/compute-node-panel-root.tsx
@@ -11,7 +11,7 @@ import { DataColumns } from '~/components/data-table/data-table';
 import { SortDirection } from '~/components/data-table/data-column';
 import { createTree } from '~/models/tree';
 import { 
-    ComputeNodeUuid, ComputeNodeInfo, ComputeNodeDomain, ComputeNodeHostname, ComputeNodeJobUuid,
+    CommonUuid, ComputeNodeInfo, ComputeNodeDomain, ComputeNodeHostname, ComputeNodeJobUuid,
     ComputeNodeFirstPingAt, ComputeNodeLastPingAt, ComputeNodeIpAddress
 } from '~/views-components/data-explorer/renderers';
 import { ResourcesState } from '~/store/resources/resources';
@@ -41,7 +41,7 @@ export const computeNodePanelColumns: DataColumns<string> = [
         configurable: true,
         sortDirection: SortDirection.NONE,
         filters: createTree(),
-        render: uuid => <ComputeNodeUuid uuid={uuid} />
+        render: uuid => <CommonUuid uuid={uuid} />
     },
     {
         name: ComputeNodePanelColumnNames.DOMAIN,

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list