diff --git a/src/HostedExplorer.tsx b/src/HostedExplorer.tsx index 270cba4fa..77cef5a1c 100644 --- a/src/HostedExplorer.tsx +++ b/src/HostedExplorer.tsx @@ -2,18 +2,24 @@ import "./Platform/Hosted/ConnectScreen.less"; import { useBoolean } from "@uifabric/react-hooks"; import { DefaultButton, + DetailsList, DirectionalHint, FocusZone, + IContextualMenuProps, initializeIcons, Panel, + PanelType, Persona, PersonaInitialsColor, - PersonaSize + PersonaSize, + SelectionMode, + Selection } from "office-ui-fabric-react"; import * as React from "react"; import { render } from "react-dom"; import FeedbackIcon from "../images/Feedback.svg"; import ConnectIcon from "../images/HostedConnectwhite.svg"; +import ChevronRight from "../images/chevron-right.svg"; import "../less/hostedexplorer.less"; import { CommandButtonComponent } from "./Explorer/Controls/CommandButton/CommandButtonComponent"; import "./Explorer/Menus/NavBar/MeControlComponent.less"; @@ -22,6 +28,7 @@ import "./Shared/appInsights"; import { AccountSwitchComponent } from "./Explorer/Controls/AccountSwitch/AccountSwitchComponent"; import { AuthContext, AuthProvider } from "./contexts/authContext"; import { usePortalAccessToken } from "./hooks/usePortalAccessToken"; +import { useDirectories } from "./hooks/useDirectories"; import { AuthType } from "./AuthType"; initializeIcons(); @@ -31,11 +38,41 @@ const App: React.FunctionComponent = () => { const encryptedToken = params && params.get("key"); const encryptedTokenMetadata = usePortalAccessToken(encryptedToken); const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false); - const { isLoggedIn, aadlogin: login, account, aadlogout: logout } = React.useContext(AuthContext); + const { isLoggedIn, aadlogin: login, account, aadlogout: logout, tenantId } = React.useContext(AuthContext); const [isConnectionStringVisible, { setTrue: showConnectionString }] = useBoolean(false); const photo = useGraphPhoto(); + const directories = useDirectories(); + // const [selectedItem, setSelectedItem] = React.useState(undefined); + const selection = new Selection({ + getKey: item => item.tenantId, + items: directories, + onSelectionChanged: () => { + const selected = selection.getSelection()[0]; + if (selected.tenantId !== tenantId) { + console.log("new Tenant", selected.tenantId); + } + }, + selectionMode: SelectionMode.single + }); + selection.setKeySelected(tenantId, true, false); - const menuProps = { + // private _renderPersonaComponent = (): JSX.Element => { + // const { user } = this.props; + // const personaProps: IPersonaSharedProps = { + // imageUrl: user.imageUrl, + // text: user.name, + // secondaryText: user.email, + // showSecondaryText: true, + // showInitialsUntilImageLoads: true, + // initialsColor: PersonaInitialsColor.teal, + // size: PersonaSize.size72, + // className: "mecontrolContextualMenuPersona" + // }; + + // return ; + // }; + + const menuProps: IContextualMenuProps = { className: "mecontrolContextualMenu", isBeakVisible: false, directionalHintFixed: true, @@ -44,30 +81,15 @@ const App: React.FunctionComponent = () => { minPagePadding: 0 }, items: [ - { - key: "Persona", - onRender: () => - }, { key: "SwitchDirectory", - onRender: () => ( -
openPanel}> - Switch Directory -
- ) + text: "Switch Directory", + onClick: openPanel }, { key: "SignOut", - onRender: () => ( -
{ - logout(); - }} - > - Sign out -
- ) + text: "Sign Out", + onClick: logout } ] }; @@ -101,8 +123,7 @@ const App: React.FunctionComponent = () => { const buttonProps = { id: "mecontrolHeader", className: "mecontrolHeaderButton", - menuProps: menuProps, - onRenderMenuIcon: () => , + menuProps, styles: { rootHovered: { backgroundColor: "#393939" }, rootFocused: { backgroundColor: "#393939" }, @@ -257,14 +278,34 @@ const App: React.FunctionComponent = () => { )}
- - {/*
- -
-
-
- -
*/} + +
); diff --git a/src/applyExplorerBindings.ts b/src/applyExplorerBindings.ts index 570a65aa6..56114f1dc 100644 --- a/src/applyExplorerBindings.ts +++ b/src/applyExplorerBindings.ts @@ -5,12 +5,12 @@ import Explorer from "./Explorer/Explorer"; export const applyExplorerBindings = (explorer: Explorer) => { if (!!explorer) { - // This message should ideally be sent immediately after explorer has been initialized for optimal data explorer load times. - // TODO: Send another message to describe that the bindings have been applied, and handle message transfers accordingly in the portal - sendMessage("ready"); window.dataExplorer = explorer; BindingHandlersRegisterer.registerBindingHandlers(); ko.applyBindings(explorer); + // This message should ideally be sent immediately after explorer has been initialized for optimal data explorer load times. + // TODO: Send another message to describe that the bindings have been applied, and handle message transfers accordingly in the portal + sendMessage("ready"); $("#divExplorer").show(); } }; diff --git a/src/contexts/authContext.tsx b/src/contexts/authContext.tsx index a8e1a07b1..922d12230 100644 --- a/src/contexts/authContext.tsx +++ b/src/contexts/authContext.tsx @@ -18,6 +18,7 @@ interface AuthContext { account?: Msal.Account; graphToken?: string; armToken?: string; + tenantId?: string; aadlogout: () => unknown; aadlogin: () => unknown; } @@ -37,15 +38,22 @@ export const AuthProvider: React.FunctionComponent = ({ children }) => { const [account, setAccount] = useState(); const [graphToken, setGraphToken] = useState(); const [armToken, setArmToken] = useState(); + const [tenantId, setTenantId] = useState(); const aadlogin = useCallback(async () => { const response = await msal.loginPopup(); setLoggedIn(); setAccount(response.account); + setTenantId(response.tenantId); + msal.authority = "https://login.microsoftonline.com/481f23b0-3fb3-4e76-812d-15513d11dbfc"; const [graphTokenResponse, armTokenResponse] = await Promise.all([ - msal.acquireTokenSilent({ scopes: ["https://graph.windows.net//.default"] }), - msal.acquireTokenSilent({ scopes: ["https://management.azure.com//.default"] }) + msal.acquireTokenSilent({ + scopes: ["https://graph.windows.net//.default"] + }), + msal.acquireTokenSilent({ + scopes: ["https://management.azure.com//.default"] + }) ]); setGraphToken(graphTokenResponse.accessToken); @@ -58,7 +66,7 @@ export const AuthProvider: React.FunctionComponent = ({ children }) => { }, []); return ( - + {children} ); diff --git a/src/hooks/useDirectories.tsx b/src/hooks/useDirectories.tsx new file mode 100644 index 000000000..76be8a91e --- /dev/null +++ b/src/hooks/useDirectories.tsx @@ -0,0 +1,42 @@ +import { useContext, useEffect, useState } from "react"; +import { AuthContext } from "../contexts/authContext"; +import { Tenant } from "../Contracts/DataModels"; + +interface TenantListResult { + nextLink: string; + value: Tenant[]; +} + +export async function fetchDirectories(accessToken: string): Promise { + const headers = new Headers(); + const bearer = `Bearer ${accessToken}`; + + headers.append("Authorization", bearer); + + let tenents: Array = []; + let nextLink = `https://management.azure.com/tenants?api-version=2020-01-01`; + + while (nextLink) { + const response = await fetch(nextLink, { headers }); + const result: TenantListResult = + response.status === 204 || response.status === 304 ? undefined : await response.json(); + if (!response.ok) { + throw result; + } + nextLink = result.nextLink; + tenents = [...tenents, ...result.value]; + } + return tenents; +} + +export function useDirectories(): Tenant[] { + const { armToken } = useContext(AuthContext); + const [state, setState] = useState(); + + useEffect(() => { + if (armToken) { + fetchDirectories(armToken).then(response => setState(response)); + } + }, [armToken]); + return state || []; +}