diff --git a/src/Platform/Hosted/ConnectScreen.less b/src/Platform/Hosted/ConnectScreen.less new file mode 100644 index 000000000..0514e41ab --- /dev/null +++ b/src/Platform/Hosted/ConnectScreen.less @@ -0,0 +1,101 @@ +.connectExplorerContainer { + height: 100%; + width: 100%; +} +.connectExplorerContainer .connectExplorerFormContainer { + display: -webkit-flex; + display: -ms-flexbox; + display: -ms-flex; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + height: 100%; + width: 100%; +} +.connectExplorerContainer .connectExplorer { + text-align: center; + display: -webkit-flex; + display: -ms-flexbox; + display: -ms-flex; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + justify-content: center; + height: 100%; + margin-bottom: 60px; +} +.connectExplorerContainer .connectExplorer .welcomeText { + font-size: 14px; + color: #393939; + margin: 8px 8px 16px 8px; +} +.connectExplorerContainer .connectExplorer .switchConnectTypeText { + margin: 8px; + font-size: 12px; + color: #0058ad; + cursor: pointer; +} +.connectExplorerContainer .connectExplorer .connectStringText { + font-size: 12px; + color: #393939; +} +.connectExplorerContainer .connectExplorer .connectExplorerContent { + margin: 8px; + color: #393939; +} +.connectExplorerContainer .connectExplorer .connectExplorerContent .inputToken { + width: 300px; + padding: 0px 4px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} +.connectExplorerContainer .connectExplorer .connectExplorerContent .inputToken::placeholder { + font-style: italic; +} +.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip { + position: relative; + display: inline-block; + padding-left: 4px; + vertical-align: top; +} +.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip:hover .errorDetails { + visibility: visible; +} +.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip .errorDetails { + bottom: 24px; + width: 145px; + visibility: hidden; + background-color: #393939; + color: #ffffff; + position: absolute; + z-index: 1; + left: -10px; + padding: 6px; +} +.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip .errorDetails:after { + border-width: 10px 10px 0px 10px; + bottom: -8px; + content: ""; + position: absolute; + right: 100%; + border-style: solid; + left: 12px; + width: 0; + height: 0; + border-color: #3b3b3b transparent; +} +.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip .errorImg { + height: 14px; + width: 14px; +} + +.filterbtnstyle { + background: #0058ad; + width: 90px; + height: 25px; + color: white; + border: solid 1px; +} diff --git a/src/Platform/Hosted/ConnectScreen.tsx b/src/Platform/Hosted/ConnectScreen.tsx new file mode 100644 index 000000000..f7e5bf7b5 --- /dev/null +++ b/src/Platform/Hosted/ConnectScreen.tsx @@ -0,0 +1,52 @@ +import "./ConnectScreen.less"; +import * as React from "react"; +import { useMsal } from "@azure/msal-react"; +import { useBoolean } from "@uifabric/react-hooks"; + +export const ConnectScreen: React.FunctionComponent = () => { + const { instance } = useMsal(); + const [isConnectionStringVisible, { setTrue: showConnectionString }] = useBoolean(false); + + return ( +
+
+
+

+ Azure Cosmos DB +

+

Welcome to Azure Cosmos DB

+ {isConnectionStringVisible ? ( +
+

Connect to your account with connection string

+

+ + + Error notification + + +

+

+ +

+

instance.loginPopup()}> + Sign In with Azure Account +

+
+ ) : ( +
+ instance.loginPopup()} /> +

{ + showConnectionString(); + }} + > + Connect to your account with connection string +

+
+ )} +
+
+
+ ); +}; diff --git a/src/hooks/useAADAccount.tsx b/src/hooks/useAADAccount.tsx new file mode 100644 index 000000000..19b223392 --- /dev/null +++ b/src/hooks/useAADAccount.tsx @@ -0,0 +1,8 @@ +import { AccountInfo } from "@azure/msal-browser"; +import { useAccount, useMsal } from "@azure/msal-react"; + +export function useAADAccount(): AccountInfo { + const { accounts } = useMsal(); + const account = useAccount(accounts[0] || {}); + return account; +} diff --git a/src/hooks/useAADToken.tsx b/src/hooks/useAADToken.tsx new file mode 100644 index 000000000..2582eb54b --- /dev/null +++ b/src/hooks/useAADToken.tsx @@ -0,0 +1,20 @@ +import { useMsal } from "@azure/msal-react"; +import { useEffect, useState } from "react"; +import { useAADAccount } from "./useAADAccount"; + +export function useAADToken(): string { + const { instance } = useMsal(); + const account = useAADAccount(); + const [state, setState] = useState(); + + useEffect(() => { + console.log("Current account", account); + if (account) { + instance.acquireTokenSilent({ account, scopes: ["User.Read"] }).then(response => { + console.log("Fetched token", response.accessToken); + setState(response.accessToken); + }); + } + }, [account]); + return state; +} diff --git a/src/hooks/useGraphPhoto.tsx b/src/hooks/useGraphPhoto.tsx new file mode 100644 index 000000000..3a9f75bff --- /dev/null +++ b/src/hooks/useGraphPhoto.tsx @@ -0,0 +1,31 @@ +import { useEffect, useState } from "react"; +import { useAADToken } from "./useAADToken"; + +export async function fetchPhoto(accessToken: string): Promise { + const headers = new Headers(); + const bearer = `Bearer ${accessToken}`; + + headers.append("Authorization", bearer); + headers.append("Content-Type", "image/jpg"); + + const options = { + method: "GET", + headers: headers + }; + + return fetch("https://graph.microsoft.com/v1.0/me/photo/$value", options) + .then(response => response.blob()) + .catch(error => console.log(error)); +} + +export function useGraphPhoto(): string { + const token = useAADToken(); + const [photo, setPhoto] = useState(); + + useEffect(() => { + if (token) { + fetchPhoto(token).then(response => setPhoto(URL.createObjectURL(response))); + } + }, [token]); + return photo; +} diff --git a/src/hooks/useSubscriptions.tsx b/src/hooks/useSubscriptions.tsx new file mode 100644 index 000000000..f9d89baf6 --- /dev/null +++ b/src/hooks/useSubscriptions.tsx @@ -0,0 +1,75 @@ +import { useEffect, useState } from "react"; +import { Subscription } from "../Contracts/DataModels"; +import { useAADToken } from "./useAADToken"; + +interface SubscriptionListResult { + nextLink: string; + value: Subscription[]; +} + +export async function fetchSubscriptions(accessToken: string): Promise { + const headers = new Headers(); + const bearer = `Bearer ${accessToken}`; + + headers.append("Authorization", bearer); + + let subscriptions: Array = []; + let nextLink = `https://management.azure.com/subscriptions?api-version=2020-01-01`; + + while (nextLink) { + const response = await fetch(nextLink, { headers }); + const result: SubscriptionListResult = + response.status === 204 || response.status === 304 ? undefined : await response.json(); + if (!response.ok) { + throw result; + } + nextLink = result.nextLink; + const validSubscriptions = result.value.filter( + sub => sub.state === "Enabled" || sub.state === "Warned" || sub.state === "PastDue" + ); + subscriptions = [...subscriptions, ...validSubscriptions]; + } + return subscriptions; +} + +export function useSubscriptions(): Subscription[] { + const token = useAADToken(); + const [state, setState] = useState(); + + useEffect(() => { + if (token) { + fetchSubscriptions(token).then(response => setState(response)); + } + }, [token]); + return state || []; +} + +// const { accounts } = useMsal(); +// const account = useAccount(accounts[0] || {}); +// const { isLoading, isError, data, error } = useQuery( +// ["subscriptions", account.tenantId], +// async () => { +// let subscriptions: Array = []; + +// const fetchHeaders = await ArmResourceUtils._getAuthHeader(ArmResourceUtils._armAuthArea, tenantId); +// let nextLink = `${ArmResourceUtils._armEndpoint}/subscriptions?api-version=${ArmResourceUtils._armApiVersion}`; + +// while (nextLink) { +// const response: Response = await fetch(nextLink, { headers: fetchHeaders }); +// const result: SubscriptionListResult = +// response.status === 204 || response.status === 304 ? null : await response.json(); +// if (!response.ok) { +// throw result; +// } +// nextLink = result.nextLink; +// const validSubscriptions = result.value.filter( +// sub => sub.state === "Enabled" || sub.state === "Warned" || sub.state === "PastDue" +// ); +// subscriptions = [...subscriptions, ...validSubscriptions]; +// } +// return subscriptions; +// }, +// { enabled: account.tenantId } +// ); + +// console.log(isLoading, isError, data, error);