Skip to content

Commit

Permalink
Add Bastion Host support
Browse files Browse the repository at this point in the history
  • Loading branch information
simonrho committed Sep 4, 2024
1 parent e871520 commit 70ed0ff
Show file tree
Hide file tree
Showing 21 changed files with 1,014 additions and 221 deletions.
Binary file added demo/jccm-bastionhost-2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/jccm-bastionhost-3.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/jccm-bastionhost.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions jccm/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion jccm/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jccm",
"productName": "Juniper Cloud Connection Manager",
"version": "1.2.0",
"version": "1.2.1",
"description": "Juniper Cloud Connection Manager",
"main": ".webpack/main",
"scripts": {
Expand Down
53 changes: 27 additions & 26 deletions jccm/src/Frontend/Layout/BastionHostButton.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,45 @@
import React, { useEffect } from 'react';
import { Button, Tooltip, tokens } from '@fluentui/react-components';
import { Button, Tooltip, Text, tokens } from '@fluentui/react-components';
import { HexagonThreeFilled, HexagonThreeRegular } from '@fluentui/react-icons';

import useStore from '../Common/StateStore';
import { RotatingIcon } from './ChangeIcon';

export const BastionHostButton = () => {
const { settings, toggleBastionHostActive } = useStore();
const bastionHost = settings?.bastionHost || {};

// Determine the status of bastionHost
const bastionHostStatus = () => {
if (Object.keys(bastionHost).length === 0) return 'not configured';
return bastionHost.active ? 'active' : 'inactive';
const getBastionHostStatus = () => {
const bastionHost = settings?.bastionHost || {};
const status =
Object.keys(bastionHost).length === 0 ? 'not configured' : bastionHost.active ? 'active' : 'inactive';
return status;
};

const getBastionHostName = () => {
const name = settings?.bastionHost ? `Host: ${settings.bastionHost.host} Port: ${settings.bastionHost.port}` : '';
return name;
};

// Get button details based on bastionHost status
const getButtonDetails = () => {
const status = bastionHostStatus();
const status = getBastionHostStatus();

switch (status) {
case 'not configured':
return {
icon: <HexagonThreeRegular fontSize={18} />,
color: tokens.colorNeutralStroke1Hover,
};
case 'active':
case 'inactive':
return {
icon: (
<RotatingIcon
Icon={HexagonThreeFilled}
size={18}
rotationDuration='7000ms'
color={tokens.colorNeutralForeground2BrandHover}
/>
),

color: tokens.colorNeutralForeground2BrandHover,
icon: <HexagonThreeFilled fontSize={15} />,
color: tokens.colorNeutralStroke1Hover,
};
case 'inactive':
case 'active':
return {
icon: <HexagonThreeFilled fontSize={15} />,
color: tokens.colorNeutralStrokeAccessibleHover,
color: tokens.colorNeutralForeground2BrandHover,
};
default:
return {
Expand All @@ -53,15 +51,18 @@ export const BastionHostButton = () => {

const { icon, color } = getButtonDetails();

// useEffect(() => {
// if (process.env.NODE_ENV !== 'production') {
// console.log('BastionHostButton: bastionHost:', settings?.bastionHost);
// }
// }, [settings?.bastionHost]);

return (
<Tooltip
content={`Bastion Host is ${bastionHostStatus()}.`}
content={
<div style={{ display: 'flex', flexDirection: 'column', gap: '5px' }}>
<Text size={200}>
Bastion Host is {getBastionHostStatus()}.
</Text>
<Text size={100}>
{getBastionHostName()}
</Text>
</div>
}
relationship='label'
withArrow
positioning='above-end'
Expand Down
12 changes: 7 additions & 5 deletions jccm/src/Frontend/Layout/Devices.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const { electronAPI } = window;

export const getDeviceFacts = async (device, upperSerialNumber=false) => {
export const getDeviceFacts = async (device, upperSerialNumber=false, bastionHost = {}) => {
const { address, port, username, password, timeout } = device;
const response = await electronAPI.saGetDeviceFacts({ address, port, username, password, timeout, upperSerialNumber });
const response = await electronAPI.saGetDeviceFacts({ address, port, username, password, timeout, upperSerialNumber, bastionHost });

if (response.facts) {
return { status: true, result: response.reply };
Expand All @@ -11,11 +11,13 @@ export const getDeviceFacts = async (device, upperSerialNumber=false) => {
}
};

export const adoptDevices = async (device, jsiTerm=false, deleteOutboundSSHTerm=false) => {
export const adoptDevices = async (device, jsiTerm=false, deleteOutboundSSHTerm=false, bastionHost = {}) => {
const { address, port, username, password, organization, site } = device;
const response = await electronAPI.saAdoptDevice({ address, port, username, password, organization, site, jsiTerm, deleteOutboundSSHTerm });
const response = await electronAPI.saAdoptDevice({ address, port, username, password, organization, site, jsiTerm, deleteOutboundSSHTerm, bastionHost });

if (response.adopt) {
// console.log('>>>>adoptDevices -> response: ', response);

if (response?.adopt) {
return { status: true, result: response.reply };
} else {
console.log('adoptDevice has failed', response);
Expand Down
12 changes: 10 additions & 2 deletions jccm/src/Frontend/Layout/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { BastionHostButton } from './BastionHostButton';
export default () => {
const { isUserLoggedIn, inventory, deviceFacts, cloudDevices, cloudInventory } = useStore();
const [countOfOrgOrSiteUnmatched, setCountOfOrgOrSiteUnmatched] = useState(0);

const { settings } = useStore();
const [isBastionHostEmpty, setIsBastionHostEmpty] = useState(false);

const countOfDeviceFacts = Object.keys(deviceFacts).length;

const countOfAdoptedDevices = Object.values(deviceFacts).filter(
Expand Down Expand Up @@ -60,6 +62,12 @@ export default () => {
return () => clearTimeout(timer); // Cleanup timer on component unmount
}, [inventory, cloudDevices]);

useEffect(() => {
const bastionHost = settings?.bastionHost || {};
const isEmpty = Object.keys(bastionHost).length === 0;
setIsBastionHostEmpty(isEmpty);
}, [settings]);

return (
<div
style={{
Expand All @@ -71,7 +79,7 @@ export default () => {
overflow: 'visible',
}}
>
{/* <BastionHostButton/> */}
{!isBastionHostEmpty && <BastionHostButton/>}
<div style={{ display: 'flex', flexDirection: 'row', gap: '20px', paddingLeft: '5px' }}>
<Label
size='small'
Expand Down
78 changes: 65 additions & 13 deletions jccm/src/Frontend/Layout/GlobalSettings/BastionHostCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
DismissRegular,
SubtractCircleRegular,
SubtractCircleFilled,
ChevronCircleDownRegular,
ChevronCircleDownFilled,
bundleIcon,
} from '@fluentui/react-icons';

Expand All @@ -33,6 +35,7 @@ import { useNotify } from '../../Common/NotificationContext';
import useStore from '../../Common/StateStore';

const Dismiss = bundleIcon(DismissFilled, DismissRegular);
const SaveIcon = bundleIcon(ChevronCircleDownFilled, ChevronCircleDownRegular);
const DeleteIcon = bundleIcon(SubtractCircleFilled, SubtractCircleRegular);
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

Expand Down Expand Up @@ -182,6 +185,8 @@ export const BastionHostCard = () => {
const saveBastionHost = (newBastionHost) => {
const saveFunction = async () => {
if (isFormValid) {
setBastionHost(newBastionHost);

const newSettings = { ...settings, bastionHost: newBastionHost };
setSettings(newSettings);
exportSettings(newSettings);
Expand All @@ -190,14 +195,24 @@ export const BastionHostCard = () => {
saveFunction();
};

const handleClose = () => {
saveBastionHost({ host, port, username, password, active });
const handleSave = () => {
saveBastionHost({ host, port, username, password, active, readyTimeout: 5000 });

notify(
<Toast>
<ToastTitle>Bastion Host</ToastTitle>
<ToastBody>
<Text size={100}>The Bastion Host settings have been successfully saved.</Text>
</ToastBody>
</Toast>,
{ intent: 'success' }
);
};

const handleActive = async (event) => {
const checked = event.currentTarget.checked;

saveBastionHost({ host, port, username, password, active: checked });
saveBastionHost({ host, port, username, password, active: checked, readyTimeout: 5000 });
setActive(checked);

const status = checked ? 'active' : 'inactive';
Expand Down Expand Up @@ -230,6 +245,16 @@ export const BastionHostCard = () => {

setActive(false);
saveBastionHost({});

notify(
<Toast>
<ToastTitle>Bastion Host</ToastTitle>
<ToastBody>
<Text size={100}>The Bastion Host settings have been successfully deleted.</Text>
</ToastBody>
</Toast>,
{ intent: 'success' }
);
};

return (
Expand Down Expand Up @@ -417,18 +442,45 @@ export const BastionHostCard = () => {
height: '25px',
}}
>
<Button
disabled={!isFormValid}
appearance='subtle'
shape='circular'
size='small'
icon={<DeleteIcon fontSize={15} />}
onClick={async () => {
await handleReset();
<div
style={{
display: 'flex',
flexDirection: 'row',
gap: '5px',
alignItems: 'center',
}}
>
Reset
</Button>
<Button
disabled={
!isFormValid ||
(bastionHost?.host === host &&
bastionHost?.port === port &&
bastionHost?.username === username &&
bastionHost?.password === password)
}
appearance='subtle'
shape='circular'
size='small'
icon={<SaveIcon fontSize={15} />}
onClick={async () => {
await handleSave();
}}
>
Save
</Button>
<Button
disabled={!isFormValid}
appearance='subtle'
shape='circular'
size='small'
icon={<DeleteIcon fontSize={15} />}
onClick={async () => {
await handleReset();
}}
>
Clear
</Button>
</div>
{isFormValid && (
<div
style={{
Expand Down
48 changes: 25 additions & 23 deletions jccm/src/Frontend/Layout/GlobalSettings/GlobalSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,13 @@ export const GlobalSettings = ({ title, isOpen, onClose }) => {
height: window.innerHeight,
});


const onTabSelect = (event, data) => {
setSelectedTab(data.value);
};

const handleClose = () => {
onClose();
}
};

return (
<Dialog
Expand Down Expand Up @@ -102,31 +101,34 @@ export const GlobalSettings = ({ title, isOpen, onClose }) => {
height: '100%',
flexDirection: 'column',
overflow: 'hidden',
justifyContent: 'flex-start'
justifyContent: 'flex-start',
}}
>
<TabList
selectedValue={selectedTab}
onTabSelect={onTabSelect}
size='small'
appearance='transparent'
<TabList
selectedValue={selectedTab}
onTabSelect={onTabSelect}
size='small'
appearance='transparent'
>
<Tab
value='General'
>
General
</Tab>
<Tab
value='BastionHost'
>
<Tab
value='General'
// icon={<PersonAvailableRegular />}
>
General
</Tab>
{/* <Tab
value='BastionHost'
// icon={<PersonAvailableRegular />}
>
Bastion Host
</Tab> */}
</TabList>
Bastion Host
</Tab>
</TabList>

<div
style={{ display: selectedTab === 'General' ? 'flex' : 'none', width: '100%', height: '100%', marginTop: '20px' }}
style={{
display: selectedTab === 'General' ? 'flex' : 'none',
width: '100%',
height: '100%',
marginTop: '20px',
}}
>
<GeneralCard />
</div>
Expand All @@ -135,7 +137,7 @@ export const GlobalSettings = ({ title, isOpen, onClose }) => {
display: selectedTab === 'BastionHost' ? 'flex' : 'none',
width: '100%',
height: '100%',
marginTop: '20px'
marginTop: '20px',
}}
>
<BastionHostCard />
Expand Down
Loading

0 comments on commit 70ed0ff

Please sign in to comment.