import { MoreOptionsIcon } from '~/components/icon/icon';
import { PaperProps } from '@material-ui/core/Paper';
- type CssRules = 'searchBox' | "toolbar" | "footer" | "root" | 'moreOptionsButton';
-type CssRules = 'searchBox' | "toolbar" | "footer" | "root" | 'moreOptionsButton' | 'title';
++type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
searchBox: {
toolbar: {
paddingTop: theme.spacing.unit * 2
},
++ toolbarUnderTitle: {
++ paddingTop: 0
++ },
footer: {
overflow: 'auto'
},
},
moreOptionsButton: {
padding: 0
+ },
+ title: {
+ paddingLeft: theme.spacing.unit * 3,
+ paddingTop: theme.spacing.unit * 3,
+ fontSize: '18px'
}
});
paperProps?: PaperProps;
actions?: React.ReactNode;
hideSearchInput?: boolean;
- header?: React.ReactNode;
++ title?: React.ReactNode;
paperKey?: string;
currentItemUuid: string;
- title?: string;
}
interface DataExplorerActionProps<T> {
rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch,
items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
- paperKey, fetchMode, currentItemUuid, header
+ paperKey, fetchMode, currentItemUuid, title
} = this.props;
return <Paper className={classes.root} {...paperProps} key={paperKey}>
- {title ? <div className={classes.title}>Content Address: {title}</div> : null}
-- {(!hideColumnSelector || !hideSearchInput) && <Toolbar className={classes.toolbar}>
++ {title && <div className={classes.title}>{title}</div>}
++ {(!hideColumnSelector || !hideSearchInput) && <Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
<Grid container justify="space-between" wrap="nowrap" alignItems="center">
- {header && <div>{header}</div>}
<div className={classes.searchBox}>
{!hideSearchInput && <SearchInput
value={searchValue}
export const Routes = {
ROOT: '/',
TOKEN: '/token',
+ FED_LOGIN: '/fedtoken',
PROJECTS: `/projects/:id(${RESOURCE_UUID_PATTERN})`,
COLLECTIONS: `/collections/:id(${RESOURCE_UUID_PATTERN})`,
PROCESSES: `/processes/:id(${RESOURCE_UUID_PATTERN})`,
GROUPS: '/groups',
GROUP_DETAILS: `/group/:id(${RESOURCE_UUID_PATTERN})`,
LINKS: '/links',
- PUBLIC_FAVORITES: '/public-favorites'
+ PUBLIC_FAVORITES: '/public-favorites',
+ COLLECTIONS_CONTENT_ADDRESS: '/collections/:id',
};
export const getResourceUrl = (uuid: string) => {
export const matchLinksRoute = (route: string) =>
matchPath(route, { path: Routes.LINKS });
- export const matchPublicFavorites = (route: string) =>
+ export const matchPublicFavoritesRoute = (route: string) =>
matchPath(route, { path: Routes.PUBLIC_FAVORITES });
+
+ export const matchCollectionsContentAddressRoute = (route: string) =>
+ matchPath(route, { path: Routes.COLLECTIONS_CONTENT_ADDRESS });
--- /dev/null
- title={this.props.match.params.id}
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+
+ import * as React from 'react';
+ import { StyleRulesCallback, WithStyles, withStyles, Grid, Button } from '@material-ui/core';
+ import { CollectionIcon } from '~/components/icon/icon';
+ import { ArvadosTheme } from '~/common/custom-theme';
+ import { BackIcon } from '~/components/icon/icon';
+ import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+ import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from '~/store/collections-content-address-panel/collections-content-address-panel-actions';
+ import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
+ import { Dispatch } from 'redux';
+ import { getIsAdmin } from '~/store/public-favorites/public-favorites-actions';
+ import { resourceKindToContextMenuKind, openContextMenu } from '~/store/context-menu/context-menu-actions';
+ import { ResourceKind } from '~/models/resource';
+ import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
+ import { connect } from 'react-redux';
+ import { navigateTo } from '~/store/navigation/navigation-action';
+ import { DataColumns } from '~/components/data-table/data-table';
+ import { SortDirection } from '~/components/data-table/data-column';
+ import { createTree } from '~/models/tree';
+ import { ResourceName, ResourceOwnerName, ResourceLastModifiedDate } from '~/views-components/data-explorer/renderers';
+
+ type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link';
+
+ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ backLink: {
+ fontSize: '14px',
+ fontWeight: 600,
+ display: 'flex',
+ alignItems: 'center',
+ padding: theme.spacing.unit,
+ marginBottom: theme.spacing.unit,
+ color: theme.palette.grey["700"],
+ },
+ backIcon: {
+ marginRight: theme.spacing.unit
+ },
+ card: {
+ width: '100%'
+ },
+ title: {
+ color: theme.palette.grey["700"]
+ },
+ iconHeader: {
+ fontSize: '1.875rem',
+ color: theme.customs.colors.green700
+ },
+ link: {
+ fontSize: '0.875rem',
+ color: theme.palette.primary.main,
+ textAlign: 'right',
+ '&:hover': {
+ cursor: 'pointer'
+ }
+ }
+ });
+
+ enum CollectionContentAddressPanelColumnNames {
+ COLLECTION_WITH_THIS_ADDRESS = "Collection with this address",
+ LOCATION = "Location",
+ LAST_MODIFIED = "Last modified"
+ }
+
+ export const collectionContentAddressPanelColumns: DataColumns<string> = [
+ {
+ name: CollectionContentAddressPanelColumnNames.COLLECTION_WITH_THIS_ADDRESS,
+ selected: true,
+ configurable: true,
+ sortDirection: SortDirection.NONE,
+ filters: createTree(),
+ render: uuid => <ResourceName uuid={uuid} />
+ },
+ {
+ name: CollectionContentAddressPanelColumnNames.LOCATION,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <ResourceOwnerName uuid={uuid} />
+ },
+ {
+ name: CollectionContentAddressPanelColumnNames.LAST_MODIFIED,
+ selected: true,
+ configurable: true,
+ sortDirection: SortDirection.DESC,
+ filters: createTree(),
+ render: uuid => <ResourceLastModifiedDate uuid={uuid} />
+ }
+ ];
+
+ export interface CollectionContentAddressPanelActionProps {
+ onContextMenu: (event: React.MouseEvent<any>, uuid: string) => void;
+ onItemClick: (item: string) => void;
+ onItemDoubleClick: (item: string) => void;
+ }
+
+ const mapDispatchToProps = (dispatch: Dispatch): CollectionContentAddressPanelActionProps => ({
+ onContextMenu: (event, resourceUuid) => {
+ const isAdmin = dispatch<any>(getIsAdmin());
+ const kind = resourceKindToContextMenuKind(resourceUuid, isAdmin);
+ if (kind) {
+ dispatch<any>(openContextMenu(event, {
+ name: '',
+ uuid: resourceUuid,
+ ownerUuid: '',
+ kind: ResourceKind.NONE,
+ menuKind: kind
+ }));
+ }
+ dispatch<any>(loadDetailsPanel(resourceUuid));
+ },
+ onItemClick: (uuid: string) => {
+ dispatch<any>(loadDetailsPanel(uuid));
+ },
+ onItemDoubleClick: uuid => {
+ dispatch<any>(navigateTo(uuid));
+ }
+ });
+
+ interface CollectionContentAddressDataProps {
+ match: {
+ params: { id: string }
+ };
+ }
+
+ export const CollectionsContentAddressPanel = withStyles(styles)(
+ connect(null, mapDispatchToProps)(
+ class extends React.Component<CollectionContentAddressPanelActionProps & CollectionContentAddressDataProps & WithStyles<CssRules>> {
+ render() {
+ return <Grid item xs={12}>
+ <Button
+ onClick={() => history.back()}
+ className={this.props.classes.backLink}>
+ <BackIcon className={this.props.classes.backIcon} />
+ Back
+ </Button>
+ <DataExplorer
+ id={COLLECTIONS_CONTENT_ADDRESS_PANEL_ID}
+ onRowClick={this.props.onItemClick}
+ onRowDoubleClick={this.props.onItemDoubleClick}
+ onContextMenu={this.props.onContextMenu}
+ contextMenuColumn={true}
-);
++ title={`Content address: ${this.props.match.params.id}`}
+ dataTableDefaultView={
+ <DataTableDefaultView
+ icon={CollectionIcon}
+ messages={['Collections with this content address not found.']} />
+ } />;
+ </Grid >;
+ }
+ }
+ )
++);
} from '~/views-components/data-explorer/renderers';
import { createTree } from '~/models/tree';
import { getInitialResourceTypeFilters } from '~/store/resource-type-filters/resource-type-filters';
+import { User } from "~/models/user";
+import { Config } from '~/common/config';
+import { Session } from "~/models/session";
export enum SearchResultsPanelColumnNames {
CLUSTER = "Cluster",
export interface SearchResultsPanelDataProps {
data: SearchBarAdvanceFormData;
+ user: User;
+ sessions: Session[];
+ remoteHostsConfig: { [key: string]: Config };
+ localCluster: string;
}
export interface SearchResultsPanelActionProps {
];
export const SearchResultsPanelView = (props: SearchResultsPanelProps) => {
+ const homeCluster = props.user.uuid.substr(0, 5);
return <DataExplorer
id={SEARCH_RESULTS_PANEL_ID}
onRowClick={props.onItemClick}
onRowDoubleClick={props.onItemDoubleClick}
onContextMenu={props.onContextMenu}
contextMenuColumn={true}
- hideSearchInput />;
+ hideSearchInput
- header={
++ title={
+ props.localCluster === homeCluster ?
- <p>Searching clusters: {props.sessions.filter((ss) => ss.loggedIn).map((ss) => <span key={ss.clusterId}> {ss.clusterId}</span>)}</p> :
- <p>Searching local cluster {props.localCluster} only. To search multiple clusters, <a href={props.remoteHostsConfig[homeCluster] && props.remoteHostsConfig[homeCluster].workbench2Url}> start from your home Workbench.</a></p>
++ <div>Searching clusters: {props.sessions.filter((ss) => ss.loggedIn).map((ss) => <span key={ss.clusterId}> {ss.clusterId}</span>)}</div> :
++ <div>Searching local cluster {props.localCluster} only. To search multiple clusters, <a href={props.remoteHostsConfig[homeCluster] && props.remoteHostsConfig[homeCluster].workbench2Url}> start from your home Workbench.</a></div>
+ }
+ />;
};
import { AddGroupMembersDialog } from '~/views-components/dialog-forms/add-group-member-dialog';
import { PartialCopyToCollectionDialog } from '~/views-components/dialog-forms/partial-copy-to-collection-dialog';
import { PublicFavoritePanel } from '~/views/public-favorites-panel/public-favorites-panel';
+import { FedLogin } from './fed-login';
+ import { CollectionsContentAddressPanel } from '~/views/collection-content-address-panel/collection-content-address-panel';
type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
<Route path={Routes.GROUP_DETAILS} component={GroupDetailsPanel} />
<Route path={Routes.LINKS} component={LinkPanel} />
<Route path={Routes.PUBLIC_FAVORITES} component={PublicFavoritePanel} />
- <Route path={Routes.COLLECTIONS_CONTENT_ADDRESS} component={CollectionsContentAddressPanel}/>
++ <Route path={Routes.COLLECTIONS_CONTENT_ADDRESS} component={CollectionsContentAddressPanel} />
</Switch>
</Grid>
</Grid>
<UserAttributesDialog />
<UserManageDialog />
<VirtualMachineAttributesDialog />
+ <FedLogin />
</Grid>
);