Skip to content

Commit

Permalink
Provide ability to copy URL to access an internally accessible resour…
Browse files Browse the repository at this point in the history
…ce (#3420)

* ui-changes

* Fix Resource Card

* Update workspaceService.ts

* Update workspaceService.ts

* bump ui version

* bump ui version

* Update workspaceService.ts

* Update workspaceService.ts

---------

Co-authored-by: Anat Balzam <>
Co-authored-by: anatbal <anatbal@example.com>
  • Loading branch information
anatbal and anatbal committed Apr 17, 2023
1 parent 06a68c2 commit de5f662
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 6 deletions.
2 changes: 1 addition & 1 deletion ui/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tre-ui",
"version": "0.5.1",
"version": "0.5.2",
"private": true,
"dependencies": {
"@azure/msal-browser": "^2.35.0",
Expand Down
60 changes: 60 additions & 0 deletions ui/app/src/components/shared/ConfirmCopyUrlToClipboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Dialog, PrimaryButton, DialogType, Stack, TooltipHost, TextField } from '@fluentui/react';
import React, { useState } from 'react';
import { Resource } from '../../models/resource';

interface ConfirmCopyUrlToClipboardProps {
resource: Resource,
onDismiss: () => void
}

// show a explanation about why connect is disabled, and show a copy to clipboard tool tip
export const ConfirmCopyUrlToClipboard: React.FunctionComponent<ConfirmCopyUrlToClipboardProps> = (props: ConfirmCopyUrlToClipboardProps) => {
const COPY_TOOL_TIP_DEFAULT_MESSAGE = "Copy to clipboard"

const [copyToolTipMessage, setCopyToolTipMessage] = useState<string>(COPY_TOOL_TIP_DEFAULT_MESSAGE);

const copyUrlToClipboardProps = {
type: DialogType.normal,
title: 'Access a Protected Endpoint',
closeButtonAriaLabel: 'Close',
subText: `Copy the link below, paste it and use it from a workspace virtual machine`,
};

const dialogStyles = { main: { maxWidth: 450 } };
const modalProps = {
titleAriaId: 'labelId',
subtitleAriaId: 'subTextId',
isBlocking: true,
styles: dialogStyles
};

const handleCopyUrl = () => {
navigator.clipboard.writeText(props.resource.properties.connection_uri);
setCopyToolTipMessage("Copied")
setTimeout(() => setCopyToolTipMessage(COPY_TOOL_TIP_DEFAULT_MESSAGE), 3000);
}

return (<>
<Dialog
hidden={false}
onDismiss={() => props.onDismiss()}
dialogContentProps={copyUrlToClipboardProps}
modalProps={modalProps}
>
<Stack horizontal styles={{ root: { alignItems: 'center', paddingTop: '7px' } }}>
<Stack.Item grow>
<TextField readOnly value={props.resource.properties.connection_uri} />
</Stack.Item>
<TooltipHost content={copyToolTipMessage}>
<PrimaryButton
iconProps={{ iconName: 'copy' }}
styles={{ root: { minWidth: '40px' } }}
onClick={() => { handleCopyUrl() }}
/>
</TooltipHost>
</Stack>
</Dialog>

</>);
};

8 changes: 7 additions & 1 deletion ui/app/src/components/shared/ResourceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PowerStateBadge } from './PowerStateBadge';
import { ResourceType } from '../../models/resourceType';
import { WorkspaceContext } from '../../contexts/WorkspaceContext';
import { CostsTag } from './CostsTag';
import { ConfirmCopyUrlToClipboard } from './ConfirmCopyUrlToClipboard';

interface ResourceCardProps {
resource: Resource,
Expand All @@ -19,10 +20,12 @@ interface ResourceCardProps {
onUpdate: (resource: Resource) => void,
onDelete: (resource: Resource) => void,
readonly?: boolean
isExposedExternally?: boolean
}

export const ResourceCard: React.FunctionComponent<ResourceCardProps> = (props: ResourceCardProps) => {
const [loading] = useState(false);
const [showCopyUrl, setShowCopyUrl] = useState(false);
const [showInfo, setShowInfo] = useState(false);
const workspaceCtx = useContext(WorkspaceContext);
const latestUpdate = useComponentManager(
Expand Down Expand Up @@ -139,12 +142,15 @@ export const ResourceCard: React.FunctionComponent<ResourceCardProps> = (props:
<CostsTag resourceId={props.resource.id} />
{
connectUri && <PrimaryButton
onClick={(e) => {e.stopPropagation(); window.open(connectUri)}}
onClick={(e) => {e.stopPropagation(); props.isExposedExternally === false ? setShowCopyUrl(true) : window.open(connectUri)}}
disabled={shouldDisable()}
title={shouldDisable() ? 'Resource must be enabled, successfully deployed & powered on to connect' : 'Connect to resource'}>
Connect
</PrimaryButton>
}
{
showCopyUrl && <ConfirmCopyUrlToClipboard onDismiss={() => setShowCopyUrl(false)} resource={props.resource} />
}
</Stack>
</Stack>
</TooltipHost>
Expand Down
4 changes: 3 additions & 1 deletion ui/app/src/components/shared/ResourceCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface ResourceCardListProps {
removeResource: (resource: Resource) => void
emptyText: string,
readonly?: boolean
isExposedExternally?: boolean
}

export const ResourceCardList: React.FunctionComponent<ResourceCardListProps> = (props: ResourceCardListProps) => {
Expand All @@ -30,7 +31,8 @@ export const ResourceCardList: React.FunctionComponent<ResourceCardListProps> =
onUpdate={(resource: Resource) => props.updateResource(resource)}
onDelete={(resource: Resource) => props.removeResource(resource)}
itemId={i}
readonly={props.readonly} />
readonly={props.readonly}
isExposedExternally={r.properties.is_exposed_externally === undefined ? props.isExposedExternally : r.properties.is_exposed_externally} />
</Stack.Item>
)
})
Expand Down
19 changes: 18 additions & 1 deletion ui/app/src/components/shared/ResourceContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ApiEndpoint } from '../../models/apiEndpoints';
import { UserResource } from '../../models/userResource';
import { getActionIcon, ResourceTemplate, TemplateAction } from '../../models/resourceTemplate';
import { ConfirmDeleteResource } from './ConfirmDeleteResource';
import { ConfirmCopyUrlToClipboard } from './ConfirmCopyUrlToClipboard';
import { ConfirmDisableEnableResource } from './ConfirmDisableEnableResource';
import { CreateUpdateResourceContext } from '../../contexts/CreateUpdateResourceContext';
import { Workspace } from '../../models/workspace';
Expand All @@ -31,6 +32,7 @@ export const ResourceContextMenu: React.FunctionComponent<ResourceContextMenuPro
const workspaceCtx = useContext(WorkspaceContext);
const [showDisable, setShowDisable] = useState(false);
const [showDelete, setShowDelete] = useState(false);
const [showCopyUrl, setShowCopyUrl] = useState(false);
const [showUpgrade, setShowUpgrade] = useState(false);
const [resourceTemplate, setResourceTemplate] = useState({} as ResourceTemplate);
const createFormCtx = useContext(CreateUpdateResourceContext);
Expand Down Expand Up @@ -142,7 +144,7 @@ export const ResourceContextMenu: React.FunctionComponent<ResourceContextMenuPro
}

// add 'connect' button if we have a URL to connect to
if (props.resource.properties.connection_uri) {
if (props.resource.properties.is_exposed_externally === true) {
menuItems.push({
key: 'connect',
text: 'Connect',
Expand All @@ -152,6 +154,17 @@ export const ResourceContextMenu: React.FunctionComponent<ResourceContextMenuPro
disabled: shouldDisableConnect()
})
}
else if (props.resource.properties.is_exposed_externally === false) {
menuItems.push({
key: 'connect',
text: 'Connect',
title: shouldDisableConnect() ? 'Resource must be deployed, enabled & powered on to connect' : 'Connect to resource',
iconProps: { iconName: 'PlugConnected' },
onClick: () => setShowCopyUrl(true),
disabled: shouldDisableConnect()
})
}


const shouldDisableActions = () => {
return props.componentAction === ComponentAction.Lock
Expand Down Expand Up @@ -221,6 +234,10 @@ export const ResourceContextMenu: React.FunctionComponent<ResourceContextMenuPro
showDelete &&
<ConfirmDeleteResource onDismiss={() => setShowDelete(false)} resource={props.resource} />
}
{
showCopyUrl &&
<ConfirmCopyUrlToClipboard onDismiss={() => setShowCopyUrl(false)} resource={props.resource} />
}
{
showUpgrade &&
<ConfirmUpgradeResource onDismiss={() => setShowUpgrade(false)} resource={props.resource} />
Expand Down
3 changes: 2 additions & 1 deletion ui/app/src/components/workspaces/WorkspaceServiceItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ export const WorkspaceServiceItem: React.FunctionComponent<WorkspaceServiceItemP
selectResource={(r: Resource) => setSelectedUserResource(r as UserResource)}
updateResource={(r: Resource) => updateUserResource(r as UserResource)}
removeResource={(r: Resource) => removeUserResource(r as UserResource)}
emptyText="This workspace service contains no user resources." />
emptyText="This workspace service contains no user resources."
isExposedExternally={workspaceService.properties.is_exposed_externally} />
}
</Stack.Item>
</Stack>
Expand Down
2 changes: 1 addition & 1 deletion ui/app/src/models/workspaceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { Resource } from "./resource";

export interface WorkspaceService extends Resource {
workspaceId: string
}
}

0 comments on commit de5f662

Please sign in to comment.