const projectMatch = Routes.matchProjectRoute(pathname);
const collectionMatch = Routes.matchCollectionRoute(pathname);
const favoriteMatch = Routes.matchFavoritesRoute(pathname);
- const publicFavoritesMatch = Routes.matchPublicFavorites(pathname);
+ const publicFavoritesMatch = Routes.matchPublicFavoritesRoute(pathname);
const trashMatch = Routes.matchTrashRoute(pathname);
const processMatch = Routes.matchProcessRoute(pathname);
const processLogMatch = Routes.matchProcessLogRoute(pathname);
const computeNodesMatch = Routes.matchComputeNodesRoute(pathname);
const apiClientAuthorizationsMatch = Routes.matchApiClientAuthorizationsRoute(pathname);
const myAccountMatch = Routes.matchMyAccountRoute(pathname);
+ const linkAccountMatch = Routes.matchLinkAccountRoute(pathname);
const userMatch = Routes.matchUsersRoute(pathname);
const groupsMatch = Routes.matchGroupsRoute(pathname);
const groupDetailsMatch = Routes.matchGroupDetailsRoute(pathname);
const linksMatch = Routes.matchLinksRoute(pathname);
+ const collectionsContentAddressMatch = Routes.matchCollectionsContentAddressRoute(pathname);
store.dispatch(dialogActions.CLOSE_ALL_DIALOGS());
store.dispatch(contextMenuActions.CLOSE_CONTEXT_MENU());
store.dispatch(WorkbenchActions.loadApiClientAuthorizations);
} else if (myAccountMatch) {
store.dispatch(WorkbenchActions.loadMyAccount);
+ } else if (linkAccountMatch) {
+ store.dispatch(WorkbenchActions.loadLinkAccount);
} else if (userMatch) {
store.dispatch(WorkbenchActions.loadUsers);
} else if (groupsMatch) {
store.dispatch(WorkbenchActions.loadGroupDetailsPanel(groupDetailsMatch.params.id));
} else if (linksMatch) {
store.dispatch(WorkbenchActions.loadLinks);
+ } else if (collectionsContentAddressMatch) {
+ store.dispatch(WorkbenchActions.loadCollectionContentAddress);
}
};
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})`,
SSH_KEYS_USER: `/ssh-keys-user`,
SITE_MANAGER: `/site-manager`,
MY_ACCOUNT: '/my-account',
+ LINK_ACCOUNT: '/link_account',
KEEP_SERVICES: `/keep-services`,
COMPUTE_NODES: `/nodes`,
USERS: '/users',
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 matchMyAccountRoute = (route: string) =>
matchPath(route, { path: Routes.MY_ACCOUNT });
+export const matchLinkAccountRoute = (route: string) =>
+ matchPath(route, { path: Routes.LINK_ACCOUNT });
+
export const matchKeepServicesRoute = (route: string) =>
matchPath(route, { path: Routes.KEEP_SERVICES });
+export const matchTokenRoute = (route: string) =>
+ matchPath(route, { path: Routes.TOKEN });
+
export const matchUsersRoute = (route: string) =>
matchPath(route, { path: Routes.USERS });
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 });
import { RootState } from "../store";
import { ServiceRepository } from "~/services/services";
import { SshKeyResource } from '~/models/ssh-key';
-import { User } from "~/models/user";
+import { User, UserResource } from "~/models/user";
import { Session } from "~/models/session";
- import { Config } from '~/common/config';
+ import { getDiscoveryURL, Config } from '~/common/config';
import { initSessions } from "~/store/auth/auth-action-session";
+import { cancelLinking } from '~/store/link-account-panel/link-account-panel-actions';
+import { matchTokenRoute } from '~/routes/routes';
+ import Axios from "axios";
+ import { AxiosError } from "axios";
export const authActions = unionize({
SAVE_API_TOKEN: ofType<string>(),
+ SAVE_USER: ofType<UserResource>(),
LOGIN: {},
LOGOUT: {},
CONFIG: ofType<{ config: Config }>(),
SET_SESSIONS: ofType<Session[]>(),
ADD_SESSION: ofType<Session>(),
REMOVE_SESSION: ofType<string>(),
- UPDATE_SESSION: ofType<Session>()
+ UPDATE_SESSION: ofType<Session>(),
+ REMOTE_CLUSTER_CONFIG: ofType<{ config: Config }>(),
});
function setAuthorizationHeader(services: ServiceRepository, token: string) {
}
export const initAuth = (config: Config) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ // Cancel any link account ops in progess unless the user has
+ // just logged in or there has been a successful link operation
+ const data = services.linkAccountService.getLinkOpStatus();
+ if (!matchTokenRoute(location.pathname) && data === undefined) {
+ dispatch<any>(cancelLinking());
+ }
+
const user = services.authService.getUser();
const token = services.authService.getApiToken();
+ const homeCluster = services.authService.getHomeCluster();
if (token) {
setAuthorizationHeader(services, token);
}
dispatch(authActions.CONFIG({ config }));
+ dispatch(authActions.SET_HOME_CLUSTER(homeCluster || config.uuidPrefix));
if (token && user) {
dispatch(authActions.INIT({ user, token }));
dispatch<any>(initSessions(services.authService, config, user));
dispatch<any>(getUserDetails()).then((user: User) => {
dispatch(authActions.INIT({ user, token }));
+ }).catch((err: AxiosError) => {
+ if (err.response) {
+ // Bad token
+ if (err.response.status === 401) {
+ logout()(dispatch, getState, services);
+ }
+ }
});
}
+ Object.keys(config.remoteHosts).map((k) => {
+ Axios.get<Config>(getDiscoveryURL(config.remoteHosts[k]))
+ .then(response => dispatch(authActions.REMOTE_CLUSTER_CONFIG({ config: response.data })));
+ });
};
export const saveApiToken = (token: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(authActions.SAVE_API_TOKEN(token));
};
- export const login = (uuidPrefix: string, homeCluster: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- services.authService.login(uuidPrefix, homeCluster);
+export const saveUser = (user: UserResource) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ services.authService.saveUser(user);
+ dispatch(authActions.SAVE_USER(user));
+};
+
+ export const login = (uuidPrefix: string, homeCluster: string, remoteHosts: { [key: string]: string }) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ services.authService.login(uuidPrefix, homeCluster, remoteHosts);
dispatch(authActions.LOGIN());
};
-export const logout = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+export const logout = (deleteLinkData: boolean = false) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ if (deleteLinkData) {
+ services.linkAccountService.removeAccountToLink();
+ }
services.authService.removeApiToken();
services.authService.removeUser();
removeAuthorizationHeader(services.apiClient);
// SPDX-License-Identifier: AGPL-3.0
import { authActions, AuthAction } from "./auth-action";
-import { User } from "~/models/user";
+import { User, UserResource } from "~/models/user";
import { ServiceRepository } from "~/services/services";
import { SshKeyResource } from '~/models/ssh-key';
import { Session } from "~/models/session";
+ import { Config } from '~/common/config';
export interface AuthState {
user?: User;
localCluster: string;
homeCluster: string;
remoteHosts: { [key: string]: string };
+ remoteHostsConfig: { [key: string]: Config };
}
const initialState: AuthState = {
sessions: [],
localCluster: "",
homeCluster: "",
- remoteHosts: {}
+ remoteHosts: {},
+ remoteHostsConfig: {}
};
export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => {
SAVE_API_TOKEN: (token: string) => {
return { ...state, apiToken: token };
},
+ SAVE_USER: (user: UserResource) => {
+ return { ...state, user};
+ },
CONFIG: ({ config }) => {
return {
...state,
homeCluster: config.uuidPrefix
};
},
+ REMOTE_CLUSTER_CONFIG: ({ config }) => {
+ return {
+ ...state,
+ remoteHostsConfig: { ...state.remoteHostsConfig, [config.uuidPrefix]: config },
+ };
+ },
INIT: ({ user, token }) => {
return { ...state, user, apiToken: token, homeCluster: user.uuid.substr(0, 5) };
},
export const navigateToMyAccount = push(Routes.MY_ACCOUNT);
+export const navigateToLinkAccount = push(Routes.LINK_ACCOUNT);
+
export const navigateToKeepServices = push(Routes.KEEP_SERVICES);
export const navigateToComputeNodes = push(Routes.COMPUTE_NODES);
export const navigateToGroupDetails = compose(push, getGroupUrl);
export const navigateToLinks = push(Routes.LINKS);
+
+ export const navigateToCollectionsContentAddress = push(Routes.COLLECTIONS_CONTENT_ADDRESS);
import { PublicFavoritesMiddlewareService } from '~/store/public-favorites-panel/public-favorites-middleware-service';
import { PUBLIC_FAVORITE_PANEL_ID } from '~/store/public-favorites-panel/public-favorites-action';
import { publicFavoritesReducer } from '~/store/public-favorites/public-favorites-reducer';
+import { linkAccountPanelReducer } from './link-account-panel/link-account-panel-reducer';
+ import { CollectionsWithSameContentAddressMiddlewareService } from '~/store/collections-content-address-panel/collections-content-address-middleware-service';
+ import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from '~/store/collections-content-address-panel/collections-content-address-panel-actions';
+ import { ownerNameReducer } from '~/store/owner-name/owner-name-reducer';
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
const publicFavoritesMiddleware = dataExplorerMiddleware(
new PublicFavoritesMiddlewareService(services, PUBLIC_FAVORITE_PANEL_ID)
);
+ const collectionsContentAddress = dataExplorerMiddleware(
+ new CollectionsWithSameContentAddressMiddlewareService(services, COLLECTIONS_CONTENT_ADDRESS_PANEL_ID)
+ );
+
const middlewares: Middleware[] = [
routerMiddleware(history),
thunkMiddleware.withExtraArgument(services),
linkPanelMiddleware,
computeNodeMiddleware,
apiClientAuthorizationMiddlewareService,
- publicFavoritesMiddleware
+ publicFavoritesMiddleware,
+ collectionsContentAddress
];
const enhancer = composeEnhancers(applyMiddleware(...middlewares));
return createStore(rootReducer, enhancer);
detailsPanel: detailsPanelReducer,
dialog: dialogReducer,
favorites: favoritesReducer,
+ ownerName: ownerNameReducer,
publicFavorites: publicFavoritesReducer,
form: formReducer,
processLogsPanel: processLogsPanelReducer,
searchBar: searchBarReducer,
virtualMachines: virtualMachinesReducer,
repositories: repositoriesReducer,
- keepServices: keepServicesReducer
+ keepServices: keepServicesReducer,
+ linkAccountPanel: linkAccountPanelReducer
});
import { loadWorkflowPanel, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions';
import { loadSshKeysPanel } from '~/store/auth/auth-action-ssh';
import { loadMyAccountPanel } from '~/store/my-account/my-account-panel-actions';
+import { loadLinkAccountPanel, linkAccountPanelActions } from '~/store/link-account-panel/link-account-panel-actions';
import { loadSiteManagerPanel } from '~/store/auth/auth-action-session';
import { workflowPanelColumns } from '~/views/workflow-panel/workflow-panel-view';
import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
import { GroupContentsResource } from '~/services/groups-service/groups-service';
import { MatchCases, ofType, unionize, UnionOf } from '~/common/unionize';
import { loadRunProcessPanel } from '~/store/run-process-panel/run-process-panel-actions';
- import { loadCollectionFiles } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
- import { collectionPanelActions } from "~/store/collection-panel/collection-panel-action";
+ import { collectionPanelActions, loadCollectionPanel } from "~/store/collection-panel/collection-panel-action";
import { CollectionResource } from "~/models/collection";
import {
loadSearchResultsPanel,
import { DataTableFetchMode } from "~/components/data-table/data-table";
import { loadPublicFavoritePanel, publicFavoritePanelActions } from '~/store/public-favorites-panel/public-favorites-action';
import { publicFavoritePanelColumns } from '~/views/public-favorites-panel/public-favorites-panel';
+import { USER_LINK_ACCOUNT_KEY } from '~/services/link-account-service/link-account-service';
+ import { loadCollectionsContentAddressPanel, collectionsContentAddressActions } from '~/store/collections-content-address-panel/collections-content-address-panel-actions';
+ import { collectionContentAddressPanelColumns } from '~/views/collection-content-address-panel/collection-content-address-panel';
export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
dispatch(linkPanelActions.SET_COLUMNS({ columns: linkPanelColumns }));
dispatch(computeNodesActions.SET_COLUMNS({ columns: computeNodePanelColumns }));
dispatch(apiClientAuthorizationsActions.SET_COLUMNS({ columns: apiClientAuthorizationPanelColumns }));
+ dispatch(collectionsContentAddressActions.SET_COLUMNS({ columns: collectionContentAddressPanelColumns }));
+ if (sessionStorage.getItem(USER_LINK_ACCOUNT_KEY)) {
+ dispatch(linkAccountPanelActions.HAS_SESSION_DATA());
+ }
+
dispatch<any>(initSidePanelTree());
if (router.location) {
const match = matchRootRoute(router.location.pathname);
dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.FAVORITES));
});
+ export const loadCollectionContentAddress = handleFirstTimeLoad(
+ async (dispatch: Dispatch<any>) => {
+ await dispatch(loadCollectionsContentAddressPanel());
+ });
+
export const loadTrash = () =>
handleFirstTimeLoad(
(dispatch: Dispatch) => {
dispatch(updateResources([collection]));
await dispatch(activateSidePanelTreeItem(collection.ownerUuid));
dispatch(setSidePanelBreadcrumbs(collection.ownerUuid));
- dispatch(loadCollectionFiles(collection.uuid));
+ dispatch(loadCollectionPanel(collection.uuid));
},
SHARED: collection => {
dispatch(collectionPanelActions.SET_COLLECTION(collection as CollectionResource));
dispatch(updateResources([collection]));
dispatch<any>(setSharedWithMeBreadcrumbs(collection.ownerUuid));
dispatch(activateSidePanelTreeItem(collection.ownerUuid));
- dispatch(loadCollectionFiles(collection.uuid));
+ dispatch(loadCollectionPanel(collection.uuid));
},
TRASHED: collection => {
dispatch(collectionPanelActions.SET_COLLECTION(collection as CollectionResource));
dispatch(updateResources([collection]));
dispatch(setTrashBreadcrumbs(''));
dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
- dispatch(loadCollectionFiles(collection.uuid));
+ dispatch(loadCollectionPanel(collection.uuid));
},
});
dispatch(loadMyAccountPanel());
});
+export const loadLinkAccount = handleFirstTimeLoad(
+ (dispatch: Dispatch<any>) => {
+ dispatch(loadLinkAccountPanel());
+ });
+
export const loadKeepServices = handleFirstTimeLoad(
async (dispatch: Dispatch<any>) => {
await dispatch(loadKeepServicesPanel());
import { authActions, getUserDetails, saveApiToken } from "~/store/auth/auth-action";
import { getUrlParameter } from "~/common/url";
import { AuthService } from "~/services/auth-service/auth-service";
-import { navigateToRootProject } from "~/store/navigation/navigation-action";
+import { navigateToRootProject, navigateToLinkAccount } from "~/store/navigation/navigation-action";
import { User } from "~/models/user";
import { Config } from "~/common/config";
import { initSessions } from "~/store/auth/auth-action-session";
+import { getAccountLinkData } from "~/store/link-account-panel/link-account-panel-actions";
interface ApiTokenProps {
authService: AuthService;
config: Config;
+ loadMainApp: boolean;
}
export const ApiToken = connect()(
componentDidMount() {
const search = this.props.location ? this.props.location.search : "";
const apiToken = getUrlParameter(search, 'api_token');
+ const loadMainApp = this.props.loadMainApp;
this.props.dispatch(saveApiToken(apiToken));
this.props.dispatch<any>(getUserDetails()).then((user: User) => {
this.props.dispatch(initSessions(this.props.authService, this.props.config, user));
}).finally(() => {
+ if (loadMainApp) {
+ if (this.props.dispatch(getAccountLinkData())) {
+ this.props.dispatch(navigateToLinkAccount);
+ }
+ else {
this.props.dispatch(navigateToRootProject);
}
++ }
});
}
render() {
- return <div/>;
+ return <div />;
}
}
);
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
+import { connect } from 'react-redux';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
import { Route, Switch } from "react-router";
import { ProjectPanel } from "~/views/project-panel/project-panel";
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 { LinkAccountPanel } from '~/views/link-account-panel/link-account-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';
}
});
-type WorkbenchPanelProps = WithStyles<CssRules>;
+interface WorkbenchDataProps {
+ isUserActive: boolean;
+ isNotLinking: boolean;
+}
+
+type WorkbenchPanelProps = WithStyles<CssRules> & WorkbenchDataProps;
const defaultSplitterSize = 90;
const saveSplitterSize = (size: number) => localStorage.setItem('splitterSize', size.toString());
export const WorkbenchPanel =
- withStyles(styles)(({ classes }: WorkbenchPanelProps) =>
- <Grid container item xs className={classes.root}>
- <Grid container item xs className={classes.container}>
- <SplitterLayout customClassName={classes.splitter} percentage={true}
+ withStyles(styles)((props: WorkbenchPanelProps) =>
+ <Grid container item xs className={props.classes.root}>
+ <Grid container item xs className={props.classes.container}>
+ <SplitterLayout customClassName={props.classes.splitter} percentage={true}
primaryIndex={0} primaryMinSize={10}
secondaryInitialSize={getSplitterInitialSize()} secondaryMinSize={40}
onSecondaryPaneSizeChange={saveSplitterSize}>
- <Grid container item xs component='aside' direction='column' className={classes.asidePanel}>
+ { props.isUserActive && props.isNotLinking && <Grid container item xs component='aside' direction='column' className={props.classes.asidePanel}>
<SidePanel />
- </Grid>
- <Grid container item xs component="main" direction="column" className={classes.contentWrapper}>
+ </Grid> }
+ <Grid container item xs component="main" direction="column" className={props.classes.contentWrapper}>
<Grid item xs>
- <MainContentBar />
+ { props.isNotLinking && <MainContentBar /> }
</Grid>
- <Grid item xs className={classes.content}>
+ <Grid item xs className={props.classes.content}>
<Switch>
<Route path={Routes.PROJECTS} component={ProjectPanel} />
<Route path={Routes.COLLECTIONS} component={CollectionPanel} />
<Route path={Routes.GROUP_DETAILS} component={GroupDetailsPanel} />
<Route path={Routes.LINKS} component={LinkPanel} />
<Route path={Routes.PUBLIC_FAVORITES} component={PublicFavoritePanel} />
+ <Route path={Routes.LINK_ACCOUNT} component={LinkAccountPanel} />
+ <Route path={Routes.COLLECTIONS_CONTENT_ADDRESS} component={CollectionsContentAddressPanel} />
</Switch>
</Grid>
</Grid>
<UserAttributesDialog />
<UserManageDialog />
<VirtualMachineAttributesDialog />
+ <FedLogin />
</Grid>
);