15064: Fetch discovery documents for all remotes
authorPeter Amstutz <pamstutz@veritasgenetics.com>
Fri, 10 May 2019 17:33:14 +0000 (13:33 -0400)
committerPeter Amstutz <pamstutz@veritasgenetics.com>
Fri, 10 May 2019 17:36:45 +0000 (13:36 -0400)
Start working on rendering iframe to login to remotes

Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz@veritasgenetics.com>

src/common/config.ts
src/index.tsx
src/routes/routes.ts
src/store/auth/auth-action-session.ts
src/store/auth/auth-action.ts
src/store/auth/auth-reducer.ts
src/views-components/api-token/api-token.tsx
src/views/workbench/fed-login.tsx [new file with mode: 0644]
src/views/workbench/workbench.tsx

index 3961d5aa2496fec7fbba912a96738f1bc15b8b5d..7abff5dab0750f03136881f81ba6714e8f763d17 100644 (file)
@@ -51,6 +51,7 @@ export interface Config {
     version: string;
     websocketUrl: string;
     workbenchUrl: string;
     version: string;
     websocketUrl: string;
     workbenchUrl: string;
+    workbench2Url?: string;
     vocabularyUrl: string;
     fileViewersConfigUrl: string;
 }
     vocabularyUrl: string;
     fileViewersConfigUrl: string;
 }
@@ -136,4 +137,4 @@ const getDefaultConfig = (): ConfigJSON => ({
 });
 
 export const DISCOVERY_URL = 'discovery/v1/apis/arvados/v1/rest';
 });
 
 export const DISCOVERY_URL = 'discovery/v1/apis/arvados/v1/rest';
-const getDiscoveryURL = (apiHost: string) => `${window.location.protocol}//${apiHost}/${DISCOVERY_URL}`;
+export const getDiscoveryURL = (apiHost: string) => `${window.location.protocol}//${apiHost}/${DISCOVERY_URL}`;
index 9c7b39aae4d91dd25fad37633666bf3ccf4358f1..ee174b2c5325866878d6d7012552e64a1ae85478 100644 (file)
@@ -113,7 +113,8 @@ fetchConfig()
         store.dispatch(loadVocabulary);
         store.dispatch(loadFileViewersConfig);
 
         store.dispatch(loadVocabulary);
         store.dispatch(loadFileViewersConfig);
 
-        const TokenComponent = (props: any) => <ApiToken authService={services.authService} config={config} {...props} />;
+        const TokenComponent = (props: any) => <ApiToken authService={services.authService} config={config} loadMainApp={true} {...props} />;
+        const FedTokenComponent = (props: any) => <ApiToken authService={services.authService} config={config} loadMainApp={false} {...props} />;
         const MainPanelComponent = (props: any) => <MainPanel {...props} />;
 
         const App = () =>
         const MainPanelComponent = (props: any) => <MainPanel {...props} />;
 
         const App = () =>
@@ -123,6 +124,7 @@ fetchConfig()
                         <ConnectedRouter history={history}>
                             <Switch>
                                 <Route path={Routes.TOKEN} component={TokenComponent} />
                         <ConnectedRouter history={history}>
                             <Switch>
                                 <Route path={Routes.TOKEN} component={TokenComponent} />
+                                <Route path={Routes.FED_LOGIN} component={FedTokenComponent} />
                                 <Route path={Routes.ROOT} component={MainPanelComponent} />
                             </Switch>
                         </ConnectedRouter>
                                 <Route path={Routes.ROOT} component={MainPanelComponent} />
                             </Switch>
                         </ConnectedRouter>
index 3fd6670d8899814662b413e0898b94191dbe46a1..bd47ca2fd38eb752646a03b27365928b7637b22f 100644 (file)
@@ -10,6 +10,7 @@ import { getCollectionUrl } from '~/models/collection';
 export const Routes = {
     ROOT: '/',
     TOKEN: '/token',
 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})`,
     PROJECTS: `/projects/:id(${RESOURCE_UUID_PATTERN})`,
     COLLECTIONS: `/collections/:id(${RESOURCE_UUID_PATTERN})`,
     PROCESSES: `/processes/:id(${RESOURCE_UUID_PATTERN})`,
index 5bb192b8816e6c4fc7bff110424ff7ad83617d02..b889e9cf39d7d301a0c5e8e4b8e70859e7d7c3ff 100644 (file)
@@ -68,7 +68,7 @@ const getTokenUuid = async (baseUrl: string, token: string): Promise<string> =>
     return resp.data.items[0].uuid;
 };
 
     return resp.data.items[0].uuid;
 };
 
-const getSaltedToken = (clusterId: string, tokenUuid: string, token: string) => {
+export const getSaltedToken = (clusterId: string, tokenUuid: string, token: string) => {
     const shaObj = new jsSHA("SHA-1", "TEXT");
     let secret = token;
     if (token.startsWith("v2/")) {
     const shaObj = new jsSHA("SHA-1", "TEXT");
     let secret = token;
     if (token.startsWith("v2/")) {
index baf80595f300ad9fb7d181aa8ba5441303e71aab..09d922e05282ba0809272bca22a238d77e39c29f 100644 (file)
@@ -10,8 +10,9 @@ import { ServiceRepository } from "~/services/services";
 import { SshKeyResource } from '~/models/ssh-key';
 import { User } from "~/models/user";
 import { Session } from "~/models/session";
 import { SshKeyResource } from '~/models/ssh-key';
 import { User } 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 { initSessions } from "~/store/auth/auth-action-session";
+import Axios from "axios";
 
 export const authActions = unionize({
     SAVE_API_TOKEN: ofType<string>(),
 
 export const authActions = unionize({
     SAVE_API_TOKEN: ofType<string>(),
@@ -28,7 +29,8 @@ export const authActions = unionize({
     SET_SESSIONS: ofType<Session[]>(),
     ADD_SESSION: ofType<Session>(),
     REMOVE_SESSION: ofType<string>(),
     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) {
 });
 
 function setAuthorizationHeader(services: ServiceRepository, token: string) {
@@ -57,6 +59,10 @@ export const initAuth = (config: Config) => (dispatch: Dispatch, getState: () =>
         dispatch<any>(getUserDetails()).then((user: User) => {
             dispatch(authActions.INIT({ user, token }));
         });
         dispatch<any>(getUserDetails()).then((user: User) => {
             dispatch(authActions.INIT({ user, token }));
         });
+        Object.keys(config.remoteHosts).map((k) => {
+            Axios.get<Config>(getDiscoveryURL(config.remoteHosts[k]))
+                .then(response => dispatch(authActions.REMOTE_CLUSTER_CONFIG({ config: response.data })));
+        });
     }
 };
 
     }
 };
 
index 0335752678c56421336751dfd79eaef11316db94..e44c81e323297fdfcd8884a4efe08236d911a9f2 100644 (file)
@@ -7,6 +7,7 @@ import { User } from "~/models/user";
 import { ServiceRepository } from "~/services/services";
 import { SshKeyResource } from '~/models/ssh-key';
 import { Session } from "~/models/session";
 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;
 
 export interface AuthState {
     user?: User;
@@ -16,6 +17,7 @@ export interface AuthState {
     localCluster: string;
     homeCluster: string;
     remoteHosts: { [key: string]: string };
     localCluster: string;
     homeCluster: string;
     remoteHosts: { [key: string]: string };
+    remoteHostsConfig: { [key: string]: Config };
 }
 
 const initialState: AuthState = {
 }
 
 const initialState: AuthState = {
@@ -25,7 +27,8 @@ const initialState: AuthState = {
     sessions: [],
     localCluster: "",
     homeCluster: "",
     sessions: [],
     localCluster: "",
     homeCluster: "",
-    remoteHosts: {}
+    remoteHosts: {},
+    remoteHostsConfig: {}
 };
 
 export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => {
 };
 
 export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => {
@@ -41,6 +44,12 @@ export const authReducer = (services: ServiceRepository) => (state = initialStat
                 homeCluster: config.uuidPrefix
             };
         },
                 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) };
         },
         INIT: ({ user, token }) => {
             return { ...state, user, apiToken: token, homeCluster: user.uuid.substr(0, 5) };
         },
index 43c55a92c9addbd8031fd537b5c59528b620e7ca..b78e7192dc0bd24f5c1ef14ed1ab6c0629aea564 100644 (file)
@@ -16,6 +16,7 @@ import { initSessions } from "~/store/auth/auth-action-session";
 interface ApiTokenProps {
     authService: AuthService;
     config: Config;
 interface ApiTokenProps {
     authService: AuthService;
     config: Config;
+    loadMainApp: boolean;
 }
 
 export const ApiToken = connect()(
 }
 
 export const ApiToken = connect()(
@@ -23,15 +24,18 @@ export const ApiToken = connect()(
         componentDidMount() {
             const search = this.props.location ? this.props.location.search : "";
             const apiToken = getUrlParameter(search, 'api_token');
         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(() => {
             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(() => {
-                this.props.dispatch(navigateToRootProject);
+                if (loadMainApp) {
+                    this.props.dispatch(navigateToRootProject);
+                }
             });
         }
         render() {
             });
         }
         render() {
-            return <div/>;
+            return <div />;
         }
     }
 );
         }
     }
 );
diff --git a/src/views/workbench/fed-login.tsx b/src/views/workbench/fed-login.tsx
new file mode 100644 (file)
index 0000000..0e9b530
--- /dev/null
@@ -0,0 +1,31 @@
+// 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 { AuthState } from '~/store/auth/auth-reducer';
+import { getSaltedToken } from '~/store/auth/auth-action-session';
+
+export interface FedLoginProps {
+    auth: AuthState;
+}
+
+const mapStateToProps = ({ auth }: RootState) => ({ auth });
+
+export const FedLogin = connect(mapStateToProps)(
+    class extends React.Component<FedLoginProps> {
+        render() {
+            const auth = this.props.auth;
+            const remoteHostsConfig = auth.remoteHostsConfig;
+            if (!auth.user || !auth.user.uuid.startsWith(auth.homeCluster)) {
+                return <></>;
+            }
+            return <div>
+                {Object.keys(remoteHostsConfig)
+                    .filter((k) => k !== auth.homeCluster)
+                    .map((k) => <iframe key={k} src={"https://" + remoteHostsConfig[k].workbench2Url} style={{ visibility: "hidden" }} />)}
+            </div>;
+        }
+    });
index a31c7d25e00939729d2f9d9bf06b7ce04f7caca5..e0997ea16eb1b78725266a039049021cac489860 100644 (file)
@@ -92,6 +92,7 @@ import { GroupMemberAttributesDialog } from '~/views-components/groups-dialog/me
 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 { 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';
 
 type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
 
 
 type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
 
@@ -240,5 +241,6 @@ export const WorkbenchPanel =
             <UserAttributesDialog />
             <UserManageDialog />
             <VirtualMachineAttributesDialog />
             <UserAttributesDialog />
             <UserManageDialog />
             <VirtualMachineAttributesDialog />
+            <FedLogin />
         </Grid>
     );
         </Grid>
     );