import { History, Location } from 'history';
import { RootStore } from '~/store/store';
- import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchRunProcessRoute, matchWorkflowRoute, matchSearchResultsRoute, matchRepositoriesRoute } from './routes';
- import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog, loadRepositories } from '~/store/workbench/workbench-actions';
-import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchRunProcessRoute, matchWorkflowRoute, matchSearchResultsRoute, matchSshKeysRoute } from './routes';
-import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog, loadSshKeys } from '~/store/workbench/workbench-actions';
++import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchRunProcessRoute, matchWorkflowRoute, matchSearchResultsRoute, matchSshKeysRoute, matchRepositoriesRoute } from './routes';
++import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog, loadSshKeys, loadRepositories } from '~/store/workbench/workbench-actions';
import { navigateToRootProject } from '~/store/navigation/navigation-action';
import { loadSharedWithMe, loadRunProcess, loadWorkflow, loadSearchResults } from '~//store/workbench/workbench-actions';
store.dispatch(loadWorkflow);
} else if (searchResultsMatch) {
store.dispatch(loadSearchResults);
+ } else if(repositoryMatch) {
+ store.dispatch(loadRepositories);
+ } else if (sshKeysMatch) {
+ store.dispatch(loadSshKeys);
}
};
export const matchSearchResultsRoute = (route: string) =>
matchPath<ResourceRouteParams>(route, { path: Routes.SEARCH_RESULTS });
- matchPath<ResourceRouteParams>(route, { path: Routes.REPOSITORIES });
+export const matchRepositoriesRoute = (route: string) =>
- matchPath(route, { path: Routes.SSH_KEYS });
++ matchPath<ResourceRouteParams>(route, { path: Routes.REPOSITORIES });
++
+ export const matchSshKeysRoute = (route: string) =>
++ matchPath(route, { path: Routes.SSH_KEYS });
import { WorkflowService } from "~/services/workflow-service/workflow-service";
import { SearchService } from '~/services/search-service/search-service';
import { PermissionService } from "~/services/permission-service/permission-service";
+import { RepositoriesService } from '~/services/repositories-service/repositories-service';
+ import { AuthorizedKeysService } from '~/services/authorized-keys-service/authorized-keys-service';
export type ServiceRepository = ReturnType<typeof createServices>;
export const navigateToSearchResults = push(Routes.SEARCH_RESULTS);
-export const navigateToSshKeys= push(Routes.SSH_KEYS);
+export const navigateToRepositories = push(Routes.REPOSITORIES);
++
++export const navigateToSshKeys= push(Routes.SSH_KEYS);
import * as processCopyActions from '~/store/processes/process-copy-actions';
import { trashPanelColumns } from "~/views/trash-panel/trash-panel";
import { loadTrashPanel, trashPanelActions } from "~/store/trash-panel/trash-panel-action";
-import { initProcessLogsPanel } from '../process-logs-panel/process-logs-panel-actions';
+import { initProcessLogsPanel } from '~/store/process-logs-panel/process-logs-panel-actions';
import { loadProcessPanel } from '~/store/process-panel/process-panel-actions';
import { sharedWithMePanelActions } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
-import { loadSharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel-actions';
+import { loadSharedWithMePanel } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
import { loadWorkflowPanel, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions';
+ import { loadSshKeysPanel } from '~/store/auth/auth-action';
import { workflowPanelColumns } from '~/views/workflow-panel/workflow-panel-view';
import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
-import { getProgressIndicator } from '../progress-indicator/progress-indicator-reducer';
+import { getProgressIndicator } from '~/store/progress-indicator/progress-indicator-reducer';
import { ResourceKind, extractUuidKind } from '~/models/resource';
import { FilterBuilder } from '~/services/api/filter-builder';
import { GroupContentsResource } from '~/services/groups-service/groups-service';
await dispatch(loadSearchResultsPanel());
});
+export const loadRepositories = handleFirstTimeLoad(
+ async (dispatch: Dispatch<any>) => {
+ await dispatch(loadRepositoriesPanel());
+ dispatch(setBreadcrumbs([{ label: 'Repositories' }]));
+ });
+
+ export const loadSshKeys = handleFirstTimeLoad(
+ async (dispatch: Dispatch<any>) => {
+ await dispatch(loadSshKeysPanel());
+ });
+
const finishLoadingProject = (project: GroupContentsResource | string) =>
async (dispatch: Dispatch<any>) => {
const uuid = typeof project === 'string' ? project : project.uuid;
export const PROCESS_NAME_VALIDATION = [require, maxLength(255)];
- export const REPOSITORY_NAME_VALIDATION = [require, maxLength(255)];
++export const REPOSITORY_NAME_VALIDATION = [require, maxLength(255)];
++
+ export const SSH_KEY_PUBLIC_VALIDATION = [require, isRsaKey, maxLength(1024)];
-export const SSH_KEY_NAME_VALIDATION = [require, maxLength(255)];
++export const SSH_KEY_NAME_VALIDATION = [require, maxLength(255)];
import { DropdownMenu } from "~/components/dropdown-menu/dropdown-menu";
import { UserPanelIcon } from "~/components/icon/icon";
import { DispatchProp, connect } from 'react-redux';
- import { logout } from "~/store/auth/auth-action";
+ import { logout } from '~/store/auth/auth-action';
import { RootState } from "~/store/store";
-import { openCurrentTokenDialog } from '../../store/current-token-dialog/current-token-dialog-actions';
+import { openCurrentTokenDialog } from '~/store/current-token-dialog/current-token-dialog-actions';
+import { openRepositoriesPanel } from "~/store/repositories/repositories-actions";
+ import { navigateToSshKeys } from '~/store/navigation/navigation-action';
interface AccountMenuProps {
user?: User;
<MenuItem>
{getUserFullname(user)}
</MenuItem>
+ <MenuItem onClick={() => dispatch(openRepositoriesPanel())}>Repositories</MenuItem>
<MenuItem onClick={() => dispatch(openCurrentTokenDialog)}>Current token</MenuItem>
+ <MenuItem onClick={() => dispatch(navigateToSshKeys)}>Ssh Keys</MenuItem>
<MenuItem>My account</MenuItem>
<MenuItem onClick={() => dispatch(logout())}>Logout</MenuItem>
</DropdownMenu>
--- /dev/null
- In order to clone git repositories using SSH, <Link to='' className={classes.link}>add an SSH key to your account</Link> and clone the git@ URLs.
+// 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 { Grid, Typography, Button, Card, CardContent, TableBody, TableCell, TableHead, TableRow, Table, Tooltip, IconButton } from '@material-ui/core';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { Link } from 'react-router-dom';
+import { Dispatch, compose } from 'redux';
+import { RootState } from '~/store/store';
+import { HelpIcon, AddIcon, MoreOptionsIcon } from '~/components/icon/icon';
+import { loadRepositoriesData, openRepositoriesSampleGitDialog, openRepositoryCreateDialog } from '~/store/repositories/repositories-actions';
+import { RepositoryResource } from '~/models/repositories';
+import { openRepositoryContextMenu } from '~/store/context-menu/context-menu-actions';
++import { Routes } from '~/routes/routes';
+
+
+type CssRules = 'link' | 'button' | 'icon' | 'iconRow' | 'moreOptionsButton' | 'moreOptions' | 'cloneUrls';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ link: {
+ textDecoration: 'none',
+ color: theme.palette.primary.main,
+ "&:hover": {
+ color: theme.palette.primary.dark,
+ transition: 'all 0.5s ease'
+ }
+ },
+ button: {
+ textAlign: 'right',
+ alignSelf: 'center'
+ },
+ icon: {
+ cursor: 'pointer',
+ color: theme.palette.grey["500"],
+ "&:hover": {
+ color: theme.palette.common.black,
+ transition: 'all 0.5s ease'
+ }
+ },
+ iconRow: {
+ paddingTop: theme.spacing.unit * 2,
+ textAlign: 'right'
+ },
+ moreOptionsButton: {
+ padding: 0
+ },
+ moreOptions: {
+ textAlign: 'right',
+ '&:last-child': {
+ paddingRight: 0
+ }
+ },
+ cloneUrls: {
+ whiteSpace: 'pre-wrap'
+ }
+});
+
+const mapStateToProps = (state: RootState) => {
+ return {
+ repositories: state.repositories.items
+ };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick<RepositoriesActionProps, 'onOptionsMenuOpen' | 'loadRepositories' | 'openRepositoriesSampleGitDialog' | 'openRepositoryCreateDialog'> => ({
+ loadRepositories: () => dispatch<any>(loadRepositoriesData()),
+ onOptionsMenuOpen: (event, index, repository) => {
+ dispatch<any>(openRepositoryContextMenu(event, index, repository));
+ },
+ openRepositoriesSampleGitDialog: () => dispatch<any>(openRepositoriesSampleGitDialog()),
+ openRepositoryCreateDialog: () => dispatch<any>(openRepositoryCreateDialog())
+});
+
+interface RepositoriesActionProps {
+ loadRepositories: () => void;
+ onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, index: number, repository: RepositoryResource) => void;
+ openRepositoriesSampleGitDialog: () => void;
+ openRepositoryCreateDialog: () => void;
+}
+
+interface RepositoriesDataProps {
+ repositories: RepositoryResource[];
+}
+
+
+type RepositoriesProps = RepositoriesDataProps & RepositoriesActionProps & WithStyles<CssRules>;
+
+export const RepositoriesPanel = compose(
+ withStyles(styles),
+ connect(mapStateToProps, mapDispatchToProps))(
+ class extends React.Component<RepositoriesProps> {
+ componentDidMount() {
+ this.props.loadRepositories();
+ }
+ render() {
+ const { classes, repositories, onOptionsMenuOpen, openRepositoriesSampleGitDialog, openRepositoryCreateDialog } = this.props;
+ return (
+ <Card>
+ <CardContent>
+ <Grid container direction="row">
+ <Grid item xs={8}>
+ <Typography variant="body2">
+ When you are using an Arvados virtual machine, you should clone the https:// URLs. This will authenticate automatically using your API token. <br />
++ In order to clone git repositories using SSH, <Link to={Routes.SSH_KEYS} className={classes.link}>add an SSH key to your account</Link> and clone the git@ URLs.
+ </Typography>
+ </Grid>
+ <Grid item xs={4} className={classes.button}>
+ <Button variant="contained" color="primary" onClick={openRepositoryCreateDialog}>
+ <AddIcon /> NEW REPOSITORY
+ </Button>
+ </Grid>
+ </Grid>
+ <Grid item xs={12}>
+ <div className={classes.iconRow}>
+ <Tooltip title="Sample git quick start">
+ <IconButton className={classes.moreOptionsButton} onClick={openRepositoriesSampleGitDialog}>
+ <HelpIcon className={classes.icon} />
+ </IconButton>
+ </Tooltip>
+ </div>
+ </Grid>
+ <Grid item xs={12}>
+ {repositories && <Table>
+ <TableHead>
+ <TableRow>
+ <TableCell>Name</TableCell>
+ <TableCell>URL</TableCell>
+ <TableCell />
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {repositories.map((repository, index) =>
+ <TableRow key={index}>
+ <TableCell>{repository.name}</TableCell>
+ <TableCell className={classes.cloneUrls}>{repository.cloneUrls.join("\n")}</TableCell>
+ <TableCell className={classes.moreOptions}>
+ <Tooltip title="More options" disableFocusListener>
+ <IconButton onClick={event => onOptionsMenuOpen(event, index, repository)} className={classes.moreOptionsButton}>
+ <MoreOptionsIcon />
+ </IconButton>
+ </Tooltip>
+ </TableCell>
+ </TableRow>)}
+ </TableBody>
+ </Table>}
+ </Grid>
+ </CardContent>
+ </Card>
+ );
+ }
+ }
+ );
import { AdvancedTabDialog } from '~/views-components/advanced-tab-dialog/advanced-tab-dialog';
import { ProcessInputDialog } from '~/views-components/process-input-dialog/process-input-dialog';
import { ProjectPropertiesDialog } from '~/views-components/project-properties-dialog/project-properties-dialog';
+import { RepositoriesPanel } from '~/views/repositories-panel/repositories-panel';
+import { RepositoriesSampleGitDialog } from '~/views-components/repositories-sample-git-dialog/repositories-sample-git-dialog';
+import { RepositoryAttributesDialog } from '~/views-components/repository-attributes-dialog/repository-attributes-dialog';
+import { CreateRepositoryDialog } from '~/views-components/dialog-forms/create-repository-dialog';
+import { RemoveRepositoryDialog } from '~/views-components/repository-remove-dialog/repository-remove-dialog';
+ import { CreateSshKeyDialog } from '~/views-components/dialog-forms/create-ssh-key-dialog';
type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
<Route path={Routes.RUN_PROCESS} component={RunProcessPanel} />
<Route path={Routes.WORKFLOWS} component={WorkflowPanel} />
<Route path={Routes.SEARCH_RESULTS} component={SearchResultsPanel} />
+ <Route path={Routes.REPOSITORIES} component={RepositoriesPanel} />
+ <Route path={Routes.SSH_KEYS} component={SshKeyPanel} />
</Switch>
</Grid>
</Grid>
<CopyProcessDialog />
<CreateCollectionDialog />
<CreateProjectDialog />
+ <CreateRepositoryDialog />
+ <CreateSshKeyDialog />
<CurrentTokenDialog />
<FileRemoveDialog />
<FilesUploadCollectionDialog />